Skip to content
Browse files

add SOAP support, fix bugs in the recorder, more XML attributes entit…

…y normalisation, fix cookie separator bug (mickael.remond@erlang-fr.org)

SVN Revision: 259
  • Loading branch information...
1 parent 37167ed commit 73fdc6c4d760444d6038d1fc76615b065a6314a0 @nniclausse nniclausse committed Mar 18, 2004
View
3 include/ts_http.hrl
@@ -33,7 +33,8 @@
body=[],
id = 0,
userid, % for www_authentication
- passwd % for www_authentication
+ passwd, % for www_authentication
+ soap_action % for SOAP support
}).
-record(url,
View
43 src/tsunami/ts_http_common.erl
@@ -48,22 +48,24 @@
%% Args: #http_request
%%----------------------------------------------------------------------
http_get(Req=#http_request{url=URL, version=Version, cookie=Cookie,
- get_ims_date=undefined,
- server_name = Host, userid=UserId, passwd=Passwd})->
+ get_ims_date=undefined, soap_action=SOAPAction,
+ server_name=Host, userid=UserId, passwd=Passwd})->
list_to_binary([?GET, " ", URL," ", "HTTP/", Version, ?CRLF,
"Host: ", Host, ?CRLF,
user_agent(),
authenticate(UserId,Passwd),
+ soap_action(SOAPAction),
set_cookie_header({Cookie, Host, URL}),
?CRLF]);
http_get(Req=#http_request{url=URL, version=Version, cookie=Cookie,
- get_ims_date=Date, server_name=Host,
- userid=UserId, passwd=Passwd}) ->
+ get_ims_date=Date, soap_action=SOAPAction,
+ server_name=Host, userid=UserId, passwd=Passwd}) ->
list_to_binary([?GET, " ", URL," ", "HTTP/", Version, ?CRLF,
["If-Modified-Since: ", Date, ?CRLF],
"Host: ", Host, ?CRLF,
user_agent(),
+ soap_action(SOAPAction),
authenticate(UserId,Passwd),
set_cookie_header({Cookie, Host, URL}),
?CRLF]).
@@ -73,14 +75,16 @@ http_get(Req=#http_request{url=URL, version=Version, cookie=Cookie,
%% Args: #http_request
%%----------------------------------------------------------------------
http_post(Req=#http_request{url=URL, version=Version, cookie=Cookie,
- content_type=ContentType, body=Content, server_name=Host,
- userid=UserId, passwd=Passwd}) ->
+ soap_action=SOAPAction, content_type=ContentType,
+ body=Content, server_name=Host,
+ userid=UserId, passwd=Passwd}) ->
ContentLength=integer_to_list(size(Content)),
?DebugF("Content Length of POST: ~p~n.", [ContentLength]),
Headers = [?POST, " ", URL," ", "HTTP/", Version, ?CRLF,
"Host: ", Host, ?CRLF,
user_agent(),
authenticate(UserId,Passwd),
+ soap_action(SOAPAction),
set_cookie_header({Cookie, Host, URL}),
"Content-Type: ", ContentType, ?CRLF,
"Content-Length: ",ContentLength, ?CRLF,
@@ -100,6 +104,10 @@ authenticate(UserId,Passwd)->
user_agent() ->
["User-Agent: ", ?USER_AGENT, ?CRLF].
+soap_action(undefined) -> [];
+soap_action(SOAPAction) -> ["SOAPAction: \"", SOAPAction, "\"", ?CRLF].
+
+
%%----------------------------------------------------------------------
%% Function: set_cookie_header/1
%% Args: Cookies (list), Hostname (string), URL
@@ -159,18 +167,28 @@ parse_config(Element = #xmlElement{name=http},
version = Version,
get_ims_date= Date,
server_name = ServerName,
- content_type= ContentType,
+ content_type= ContentType,
body = list_to_binary(Contents)},
+ %% SOAP Support: Add SOAPAction header to the message
+ Request2 = case lists:keysearch(soap,#xmlElement.name,
+ Element#xmlElement.content) of
+ {value, SoapEl=#xmlElement{} } ->
+ SOAPAction = ts_config:getAttr(SoapEl#xmlElement.attributes,
+ action, []),
+ Request#http_request{soap_action=SOAPAction};
+ _ ->
+ Request
+ end,
Msg = case lists:keysearch(www_authenticate,#xmlElement.name,
Element#xmlElement.content) of
{value, AuthEl=#xmlElement{} } ->
UserId = ts_config:getAttr(AuthEl#xmlElement.attributes,
userid, undefined),
Passwd = ts_config:getAttr(AuthEl#xmlElement.attributes,
passwd, undefined),
- set_msg(Request#http_request{userid=UserId, passwd=Passwd}, 0);
- _Data ->
- set_msg(Request, 0)
+ set_msg(Request2#http_request{userid=UserId, passwd=Passwd}, 0);
+ _ ->
+ set_msg(Request2, 0)
end,
ts_config:mark_prev_req(Id-1, Tab, CurS),
ets:insert(Tab,{{CurS#session.id, Id}, Msg#message{endpage=true}}),
@@ -352,7 +370,8 @@ add_new_cookie(Cookie, Host, OldCookies) ->
%%----------------------------------------------------------------------
splitcookie(Cookie) -> splitcookie(Cookie, [], []).
splitcookie([], Cur, Acc) -> [lists:reverse(Cur)|Acc];
-splitcookie("; "++Rest,Cur,Acc) ->splitcookie(Rest,[],[lists:reverse(Cur)|Acc]);
+splitcookie(";"++Rest,Cur,Acc) ->
+ splitcookie(string:strip(Rest, both),[],[lists:reverse(Cur)|Acc]);
splitcookie([Char|Rest],Cur,Acc)->splitcookie(Rest, [Char|Cur], Acc).
%%----------------------------------------------------------------------
@@ -403,7 +422,7 @@ set_cookie_key([L|"omment"],Val,Cookie) when L == $C; L==$c ->
Cookie; %don't care about comment
set_cookie_key(Key,Val,Cookie) ->
Cookie#cookie{key=Key,value=Val}.
-
+
get_cookie_key([],Acc) -> {lists:reverse(Acc), []};
get_cookie_key([$=|Rest],Acc) -> {lists:reverse(Acc), Rest};
get_cookie_key([Char|Rest],Acc)-> get_cookie_key(Rest, [Char|Acc]).
View
42 src/tsunami/ts_utils.erl
@@ -27,7 +27,7 @@
-export([debug/3, debug/4, get_val/1, init_seed/0, chop/1, elapsed/2,
now_sec/0, inet_setopts/4, node_to_hostname/1, add_time/2,
level2int/1, mkey1search/2, close_socket/2, datestr/0, datestr/1,
- erl_system_args/0, setsubdir/1, stop_all/2, stop_all/3]).
+ erl_system_args/0, setsubdir/1, stop_all/2, stop_all/3, export_text/1]).
level2int("debug") -> ?DEB;
level2int("info") -> ?INFO;
@@ -45,12 +45,25 @@ level2int("emergency") -> ?EMERG.
get_val(Var) ->
case application:get_env(Var) of
{ok, Val} ->
- Val;
+ ensure_string(Var, Val);
_ ->
?LOGF("WARNING, env ~p is not defined ! ~n", [Var], ?ERR),
undef_var
end.
+
+%% ensure atom to string conversion of environnement variable
+%% This is intended to fix a problem making Tsunami run under Windows
+%% I convert parameter that are called from the command-line
+ensure_string(log_file, Atom) when atom(Atom) ->
+ atom_to_list(Atom);
+ensure_string(proxy_log_file, Atom) when atom(Atom) ->
+ atom_to_list(Atom);
+ensure_string(config_file, Atom) when atom(Atom) ->
+ atom_to_list(Atom);
+ensure_string(_, Other) ->
+ Other.
+
%%----------------------------------------------------------------------
%% Func: debug/3
%% Purpose: print debug message if level is high enough
@@ -217,6 +230,31 @@ setsubdir(FileName) ->
{error, Err}
end.
+%% Escape special characters `<', `&', `'' and `"' flattening the text.
+export_text(T) ->
+ export_text(T, []).
+
+export_text([$< | T], Cont) ->
+ "&lt;" ++ export_text(T, Cont);
+export_text([$> | T], Cont) ->
+ "&gt;" ++ export_text(T, Cont);
+export_text([$& | T], Cont) ->
+ "&amp;" ++ export_text(T, Cont);
+export_text([$' | T], Cont) ->
+ "&quot;" ++ export_text(T, Cont);
+export_text([$" | T], Cont) ->
+ "&quot;" ++ export_text(T, Cont);
+export_text([C | T], Cont) when integer(C) ->
+ [C | export_text(T, Cont)];
+export_text([T | T1], Cont) ->
+ export_text(T, [T1 | Cont]);
+export_text([], [T | Cont]) ->
+ export_text(T, Cont);
+export_text([], []) ->
+ [];
+export_text(Bin, Cont) ->
+ export_text(binary_to_list(Bin), Cont).
+
stop_all(Host, Name) ->
stop_all(Host, Name, "IDX-Tsunami").
View
2 src/tsunami_controller/ts_config_server.erl
@@ -86,7 +86,7 @@ newbeam(Host, {Arrivals, MaxUsers})->
gen_server:cast({global, ?MODULE},{newbeam, Host, {Arrivals, MaxUsers} }).
%%--------------------------------------------------------------------
-%% Function: read_config/2
+%% Function: get_req/2
%% Description: get Nth request from given session Id
%% Returns: #message | {error, Reason}
%%--------------------------------------------------------------------
View
40 src/tsunami_recorder/ts_client_proxy.erl
@@ -50,6 +50,7 @@
-record(state, {
clientsock,
+ http_version,
parse_status = new, %% http status = body|new
body_size = 0,
content_length = 0,
@@ -129,7 +130,7 @@ handle_info({tcp, ServerSock, String}, State)
0 -> ok;
Count-> ?LOGF("substitute https: ~p times~n",[Count],?DEB)
end,
- gen_tcp:send(State#state.clientsock, NewString),
+ send(State#state.clientsock, NewString),
{noreply, State, ?lifetime};
% ssl server data, send it to the client
@@ -142,19 +143,29 @@ handle_info({ssl, ServerSock, String}, State)
0 -> ok;
Count-> ?LOGF("substitute https: ~p times~n",[Count],?DEB)
end,
- gen_tcp:send(State#state.clientsock, NewString),
+ send(State#state.clientsock, NewString),
{noreply, State, ?lifetime};
%%%%%%%%%%%% Errors and termination %%%%%%%%%%%%%%%%%%%
% Log who did close the connection, and exit.
-handle_info({tcp_closed, Socket}, State=#state{serversock=Socket})->
+handle_info({tcp_closed, Socket}, State=#state{serversock=Socket,http_version=HTTPVersion})->
?LOG("socket closed by server~n",?INFO),
- {noreply, State#state{serversock=undefined}, ?lifetime};
+ case HTTPVersion of
+ "HTTP/1.0" ->
+ {stop, normal, ?lifetime};%Disconnect client if it requires HTTP/1.0
+ _ ->
+ {noreply, State#state{serversock=undefined}, ?lifetime}
+ end;
-handle_info({ssl_closed, Socket}, State=#state{serversock=Socket})->
+handle_info({ssl_closed, Socket}, State=#state{serversock=Socket,http_version=HTTPVersion})->
?LOG("ssl socket closed by server~n",?INFO),
- {noreply, State#state{serversock=undefined}, ?lifetime};
+ case HTTPVersion of
+ "HTTP/1.0" ->
+ {stop, normal, ?lifetime};%Disconnect client if it requires HTTP/1.0
+ _ ->
+ {noreply, State#state{serversock=undefined}, ?lifetime}
+ end;
handle_info({tcp_closed, Socket}, State) ->
?LOG("socket closed by client~n",?INFO),
@@ -249,7 +260,8 @@ parse(State=#state{parse_status=Status},ClientSock,ServerSocket,String) when Sta
[RequestURI,RelURL,NewString], ?INFO),
{ok, RealString} = relative_url(NewString,RequestURI,RelURL),
send(NewSocket,RealString),
- {ok, State#state{parse_status = new, buffer=[],
+ {ok, State#state{http_version=HTTPVersion,
+ parse_status = new, buffer=[],
serversock=NewSocket}};
{undefined, Diff} ->
{error, undefined};
@@ -270,16 +282,18 @@ parse(State=#state{parse_status=Status},ClientSock,ServerSocket,String) when Sta
body=Body,
headers=ParsedHeader}
}),
- {ok, State#state{parse_status = new, buffer=[],
+ {ok, State#state{http_version=HTTPVersion,
+ parse_status = new, buffer=[],
serversock=NewSocket}};
BodySize > CLength ->
{error, bad_content_length};
true ->
{NewSocket,RelURL} = check_serversocket(ServerSocket,RequestURI),
{ok,RealString,_Count} = regexp:gsub(NewString,RequestURI,RelURL),
send(NewSocket,RealString),
- {ok, State#state{content_length = CLength,
- body_size = TotalSize,
+ {ok, State#state{http_version=HTTPVersion,
+ content_length = CLength,
+ body_size = TotalSize - HeaderSize, %% mremond fix
serversock=NewSocket,
buffer = #http_request{method=Method,
url=RequestURI,
@@ -301,9 +315,10 @@ parse(State=#state{parse_status=Status, buffer=Http},ClientSock,ServerSocket,Str
CLength = State#state.content_length,
send(ServerSocket, String),
Buffer=lists:append(Http#http_request.body,String),
+ %% Should be checked before
case Size of
CLength -> % end of response
- ts_proxy_recorder:dorecord(Http#http_request{ body=Buffer} ),
+ ts_proxy_recorder:dorecord( {Http#http_request{ body=Buffer }} ),
{ok, State#state{body_size=0,parse_status=new, content_length=0,buffer=[]}};
_ ->
{ok, State#state{body_size = Size, buffer = Http#http_request{body=Buffer}}}
@@ -323,7 +338,6 @@ check_serversocket(undefined, URL) ->
Port = ts_http_common:set_port(URL),
?LOGF("Connecting to ~p:~p ...~n", [URL#url.host, Port],?DEB),
-
{ok, Socket} = connect(URL#url.scheme, URL#url.host,Port),
?LOGF("Connected to server ~p on port ~p (socket is ~p)~n",
@@ -387,5 +401,3 @@ relative_url(NewString,RequestURI,RelURL)->
[RelURL_noargs|_] = string:tokens(RelURL,"?"),
{ok,RealString,_Count} = regexp:gsub(NewString,FullURL_noargs,RelURL_noargs),
{ok, RealString}.
-
-
View
20 src/tsunami_recorder/ts_proxy_recorder.erl
@@ -216,12 +216,15 @@ record_http_request(State=#state{prev_host=Host, prev_port=Port},
case Body of
[] -> ok;
_ ->
- Body2 = case regexp:gsub(Body,"&","&amp;") of
- {ok,NewBody,_} -> NewBody;
- _ -> Body
- end,
+ Body2 = ts_utils:export_text(Body),
io:format(Fd," contents='~s' ", [Body2]) % must be a POST method
end,
+ %% Content-type recording (This is usefull for SOAP post for example):
+ case httpd_util:key1search(ParsedHeader,"content-type") of
+ undefined -> ok;
+ ContentType ->
+ io:format(Fd,"content_type='~s' ",[ContentType])
+ end,
case httpd_util:key1search(ParsedHeader,"if-modified-since") of
undefined ->
io:format(Fd,"method='~s'>", [ Method]);
@@ -231,9 +234,16 @@ record_http_request(State=#state{prev_host=Host, prev_port=Port},
case httpd_util:key1search(ParsedHeader,"authorization") of
"Basic "++Base64 ->
{User,Passwd}=decode_basic_auth(Base64),
- io:format(Fd,"~n <www_authenticate userid=~p passwd=~p/>~n",[User,Passwd]);
+ io:format(Fd,"~n <www_authenticate userid=~p passwd=~p></www_authenticate>",[User,Passwd]);
_ -> ok
end,
+ %% SOAP Support: Need to record use of the SOAPAction header
+ case httpd_util:key1search(ParsedHeader,"soapaction") of
+ undefined -> ok;
+ QuotedSOAPAction ->
+ SOAPAction = string:strip(QuotedSOAPAction, both, $"),
+ io:format(Fd,"~n <soap action='~s'></soap>~n",[SOAPAction])
+ end,
io:format(Fd,"</http></request>~n",[]),
{ok, State#state{prev_port=NewPort,prev_host=NewHost}}.

0 comments on commit 73fdc6c

Please sign in to comment.
Something went wrong with that request. Please try again.