diff options
Diffstat (limited to 'lib/inets')
-rw-r--r-- | lib/inets/doc/src/httpc.xml | 60 | ||||
-rw-r--r-- | lib/inets/doc/src/notes.xml | 10 | ||||
-rw-r--r-- | lib/inets/src/http_client/httpc.erl | 115 | ||||
-rw-r--r-- | lib/inets/src/http_client/httpc_handler.erl | 271 | ||||
-rw-r--r-- | lib/inets/src/http_client/httpc_internal.hrl | 34 | ||||
-rw-r--r-- | lib/inets/src/http_client/httpc_manager.erl | 118 | ||||
-rw-r--r-- | lib/inets/src/http_client/httpc_request.erl | 42 | ||||
-rw-r--r-- | lib/inets/src/http_lib/http_transport.erl | 136 | ||||
-rw-r--r-- | lib/inets/test/httpc_SUITE.erl | 134 | ||||
-rw-r--r-- | lib/inets/vsn.mk | 3 |
10 files changed, 718 insertions, 205 deletions
diff --git a/lib/inets/doc/src/httpc.xml b/lib/inets/doc/src/httpc.xml index 680473cc38..e143ba2c1a 100644 --- a/lib/inets/doc/src/httpc.xml +++ b/lib/inets/doc/src/httpc.xml @@ -21,7 +21,7 @@ </legalnotice> - <title>http</title> + <title>httpc</title> <prepared>Ingela Anderton Andin</prepared> <responsible></responsible> <docno></docno> @@ -64,6 +64,8 @@ request_id() = ref() profile() = atom() path() = string() representing a file path or directory path ip_address() = See inet(3) +socket_opt() = See the Options used by gen_tcp(3) and + ssl(3) connect(s) ]]></code> </section> @@ -162,13 +164,13 @@ ssl_options() = {verify, code()} | <v>Request = request()</v> <v>HTTPOptions = http_options()</v> <v>http_options() = [http_option()]</v> - <v>http_option() = {timeout, timeout()} | + <v>http_option() = {timeout, timeout()} | {connect_timeout, timeout()} | - {ssl, ssl_options()} | - {autoredirect, boolean()} | + {ssl, ssl_options()} | + {autoredirect, boolean()} | {proxy_auth, {userstring(), passwordstring()}} | - {version, http_version()} | - {relaxed, boolean()}</v> + {version, http_version()} | + {relaxed, boolean()}</v> <v>timeout() = integer() >= 0 | infinity</v> <v>Options = options()</v> <v>options() = [option()]</v> @@ -177,8 +179,10 @@ ssl_options() = {verify, code()} | {body_format, body_format()} | {full_result, boolean()} | {headers_as_is, boolean() | + {socket_opts, socket_opts()} | {receiver, receiver()}}</v> <v>stream_to() = none | self | {self, once} | filename() </v> + <v>socket_opts() = [socket_opt()]</v> <v>receiver() = pid() | function()/1 | {Module, Function, Args} </v> <v>Module = atom() </v> <v>Function = atom() </v> @@ -315,6 +319,24 @@ ssl_options() = {verify, code()} | <p>Defaults to <c>false</c>. </p> </item> + <tag><c><![CDATA[socket_opts]]></c></tag> + <item> + <p>Socket options to be used for this and subsequent + request(s). </p> + <p>Overrides any value set by the + <seealso marker="set_options">set_options</seealso> + function. </p> + <p>Note that the validity of the options are <em>not</em> + checked in any way. </p> + <p>Note that this may change the socket behaviour + (see <seealso marker="inet#setopts">inet:setopts/2</seealso>) + for an already existing, and therefor already connected + request handler. </p> + <p>By defaults the socket options set by the + <seealso marker="#set_options">set_options/1,2</seealso> + function is used when establishing connection. </p> + </item> + <tag><c><![CDATA[receiver]]></c></tag> <item> <p>Defines how the client will deliver the result for a @@ -393,17 +415,30 @@ apply(Module, Function, [ReplyInfo | Args]) <fsummary>Sets options to be used for subsequent requests.</fsummary> <type> <v>Options = [Option]</v> - <v>Option = {proxy, {Proxy, NoProxy}} | {max_sessions, MaxSessions} | - {max_keep_alive_length, MaxKeepAlive} | {keep_alive_timeout, KeepAliveTimeout} | - {max_pipeline_length, MaxPipeline} | {pipeline_timeout, PipelineTimeout} | - {cookies | CookieMode} | - {ipfamily, IpFamily} | {ip, IpAddress} | {port, Port} | - {verbose, VerboseMode} </v> + <v>Option = {proxy, {Proxy, NoProxy}} | + {max_sessions, MaxSessions} | + {max_keep_alive_length, MaxKeepAlive} | + {keep_alive_timeout, KeepAliveTimeout} | + {max_pipeline_length, MaxPipeline} | + {pipeline_timeout, PipelineTimeout} | + {cookies, CookieMode} | + {ipfamily, IpFamily} | + {ip, IpAddress} | + {port, Port} | + {socket_opts, socket_opts()} | + {verbose, VerboseMode} </v> <v>Proxy = {Hostname, Port}</v> <v>Hostname = string() </v> <d>ex: "localhost" or "foo.bar.se"</d> <v>Port = integer()</v> <d>ex: 8080 </d> + <v>socket_opts() = [socket_opt()]</v> + <d>The options are appended to the socket options used by the + client. </d> + <d>These are the default values when a new request handler + is started (for the initial connect). They are passed directly + to the underlying transport (gen_tcp or ssl) <em>without</em> + verification! </d> <v>NoProxy = [NoProxyDesc]</v> <v>NoProxyDesc = DomainDesc | HostName | IPDesc</v> <v>DomainDesc = "*.Domain"</v> @@ -573,6 +608,7 @@ apply(Module, Function, [ReplyInfo | Args]) <section> <title>SEE ALSO</title> <p>RFC 2616, <seealso marker="inets">inets(3)</seealso>, + <seealso marker="kernel:gen_tcp">gen_tcp(3)</seealso>, <seealso marker="ssl:ssl">ssl(3)</seealso> </p> </section> diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml index ed83708940..e95c8d6e97 100644 --- a/lib/inets/doc/src/notes.xml +++ b/lib/inets/doc/src/notes.xml @@ -41,6 +41,16 @@ <list> <item> + <p>[httpc] - Allow users to pass socket options to the transport + module when making requests. </p> + <p>See the <c>socket_opts</c> option in the + <seealso marker="httpc#request2">request/4</seealso> or + <seealso marker="httpc#set_options">set_options/1,2</seealso> + for more info, </p> + <p>Own Id: OTP-8352</p> + </item> + + <item> <p>[httpc] Fix bug crafting Host header when port is not 80. </p> <p>The host header should include the port number as well as the host name when making a request to a server listening on a port diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl index c4ee4f1fda..5205605e0a 100644 --- a/lib/inets/src/http_client/httpc.erl +++ b/lib/inets/src/http_client/httpc.erl @@ -28,7 +28,8 @@ -behaviour(inets_service). %% API --export([request/1, request/2, request/4, request/5, +-export([ + request/1, request/2, request/4, request/5, cancel_request/1, cancel_request/2, set_option/2, set_option/3, set_options/1, set_options/2, @@ -38,7 +39,9 @@ reset_cookies/0, reset_cookies/1, stream_next/1, default_profile/0, - profile_name/1, profile_name/2]). + profile_name/1, profile_name/2, + info/0, info/1 + ]). %% Behavior callbacks -export([start_standalone/1, start_service/1, @@ -314,6 +317,27 @@ which_cookies(Profile) -> %%-------------------------------------------------------------------------- +%% info() -> list() +%% info(Profile) -> list() +%% +%% Description: Debug function, retreive info about the profile +%%------------------------------------------------------------------------- +info() -> + info(default_profile()). + +info(Profile) -> + ?hcrt("info", [{profile, Profile}]), + try + begin + httpc_manager:info(profile_name(Profile)) + end + catch + exit:{noproc, _} -> + {error, {not_started, Profile}} + end. + + +%%-------------------------------------------------------------------------- %% reset_cookies() -> void() %% reset_cookies(Profile) -> void() %% @@ -399,35 +423,34 @@ handle_request(Method, Url, Headers, ContentType, Body, HTTPOptions0, Options0, Profile) -> - Started = http_util:timestamp(), - NewHeaders = [{http_util:to_lower(Key), Val} || {Key, Val} <- Headers], + Started = http_util:timestamp(), + NewHeaders = [{http_util:to_lower(Key), Val} || {Key, Val} <- Headers], try begin - HTTPOptions = http_options(HTTPOptions0), - Options = request_options(Options0), - Sync = proplists:get_value(sync, Options), - Stream = proplists:get_value(stream, Options), - HeadersRecord = - header_record(NewHeaders, - #http_request_h{}, - header_host(Host, Port), - HTTPOptions#http_options.version), - Receiver = proplists:get_value(receiver, Options), - Request = #request{from = Receiver, - scheme = Scheme, - address = {Host,Port}, - path = Path, - pquery = Query, - method = Method, - headers = HeadersRecord, - content = {ContentType,Body}, - settings = HTTPOptions, - abs_uri = Url, - userinfo = UserInfo, - stream = Stream, + HTTPOptions = http_options(HTTPOptions0), + Options = request_options(Options0), + Sync = proplists:get_value(sync, Options), + Stream = proplists:get_value(stream, Options), + Host2 = header_host(Host, Port), + HeadersRecord = header_record(NewHeaders, Host2, HTTPOptions), + Receiver = proplists:get_value(receiver, Options), + SocketOpts = proplists:get_value(socket_opts, Options), + Request = #request{from = Receiver, + scheme = Scheme, + address = {Host, Port}, + path = Path, + pquery = Query, + method = Method, + headers = HeadersRecord, + content = {ContentType, Body}, + settings = HTTPOptions, + abs_uri = Url, + userinfo = UserInfo, + stream = Stream, headers_as_is = headers_as_is(Headers, Options), - started = Started}, + socket_opts = SocketOpts, + started = Started}, case httpc_manager:request(Request, profile_name(Profile)) of {ok, RequestId} -> handle_answer(RequestId, Sync, Options); @@ -591,6 +614,7 @@ http_options_default() -> {connect_timeout, {field, #http_options.timeout}, #http_options.connect_timeout, ConnTimeoutPost} ]. + request_options_defaults() -> VerifyBoolean = fun(Value) when ((Value =:= true) orelse (Value =:= false)) -> @@ -640,13 +664,23 @@ request_options_defaults() -> error end, + VerifySocketOpts = + fun([]) -> + {ok, undefined}; + (Value) when is_list(Value) -> + ok; + (_) -> + error + end, + [ - {sync, true, VerifySync}, - {stream, none, VerifyStream}, - {body_format, string, VerifyBodyFormat}, - {full_result, true, VerifyFullResult}, - {headers_as_is, false, VerifyHeaderAsIs}, - {receiver, self(), VerifyReceiver} + {sync, true, VerifySync}, + {stream, none, VerifyStream}, + {body_format, string, VerifyBodyFormat}, + {full_result, true, VerifyFullResult}, + {headers_as_is, false, VerifyHeaderAsIs}, + {receiver, self(), VerifyReceiver}, + {socket_opts, undefined, VerifySocketOpts} ]. request_options(Options) -> @@ -671,6 +705,9 @@ request_options([{Key, DefaultVal, Verify} | Defaults], Options, Acc) -> ok -> Options2 = lists:keydelete(Key, 1, Options), request_options(Defaults, Options2, [{Key, Value} | Acc]); + {ok, Value2} -> + Options2 = lists:keydelete(Key, 1, Options), + request_options(Defaults, Options2, [{Key, Value2} | Acc]); error -> Report = io_lib:format("Invalid option ~p:~p ignored ~n", [Key, Value]), @@ -756,6 +793,10 @@ validate_options([{port, Value} = Opt| Tail], Acc) -> validate_port(Value), validate_options(Tail, [Opt | Acc]); +validate_options([{socket_opts, Value} = Opt| Tail], Acc) -> + validate_socket_opts(Value), + validate_options(Tail, [Opt | Acc]); + validate_options([{verbose, Value} = Opt| Tail], Acc) -> validate_verbose(Value), validate_options(Tail, [Opt | Acc]); @@ -836,6 +877,11 @@ validate_port(Value) when is_integer(Value) -> validate_port(BadValue) -> bad_option(port, BadValue). +validate_socket_opts(Value) when is_list(Value) -> + Value; +validate_socket_opts(BadValue) -> + bad_option(socket_opts, BadValue). + validate_verbose(Value) when ((Value =:= false) orelse (Value =:= verbose) orelse @@ -855,6 +901,9 @@ header_host(Host, Port) -> Host ++ ":" ++ integer_to_list(Port). +header_record(NewHeaders, Host, #http_options{version = Version}) -> + header_record(NewHeaders, #http_request_h{}, Host, Version). + header_record([], RequestHeaders, Host, Version) -> validate_headers(RequestHeaders, Host, Version); header_record([{"cache-control", Val} | Rest], RequestHeaders, Host, Version) -> diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index 25f9b0777f..fec74932a2 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -28,8 +28,15 @@ %%-------------------------------------------------------------------- %% Internal Application API --export([start_link/2, connect_and_send/2, - send/2, cancel/2, stream/3, stream_next/1]). +-export([ + start_link/2, + connect_and_send/2, + send/2, + cancel/2, + stream/3, + stream_next/1, + info/1 + ]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, @@ -131,6 +138,18 @@ stream_next(Pid) -> %%-------------------------------------------------------------------- +%% Function: info(Pid) -> [{Key, Val}] +%% Pid = pid() - the pid of the http-request handler process. +%% +%% Description: +%% Returns various information related to this handler +%% Used for debugging and testing +%%-------------------------------------------------------------------- +info(Pid) -> + call(info, Pid). + + +%%-------------------------------------------------------------------- %% Function: stream(BodyPart, Request, Code) -> _ %% BodyPart = binary() %% Request = #request{} @@ -143,21 +162,21 @@ stream_next(Pid) -> %%-------------------------------------------------------------------- %% Request should not be streamed stream(BodyPart, Request = #request{stream = none}, _) -> - ?hcrt("stream - none", [{body_part, BodyPart}]), + ?hcrt("stream - none", []), {BodyPart, Request}; %% Stream to caller stream(BodyPart, Request = #request{stream = Self}, Code) when ((Code =:= 200) orelse (Code =:= 206)) andalso ((Self =:= self) orelse (Self =:= {self, once})) -> - ?hcrt("stream - self", [{stream, Self}, {code, Code}, {body_part, BodyPart}]), + ?hcrt("stream - self", [{stream, Self}, {code, Code}]), httpc_response:send(Request#request.from, {Request#request.id, stream, BodyPart}), {<<>>, Request}; stream(BodyPart, Request = #request{stream = Self}, 404) when (Self =:= self) orelse (Self =:= {self, once}) -> - ?hcrt("stream - self with 404", [{stream, Self}, {body_part, BodyPart}]), + ?hcrt("stream - self with 404", [{stream, Self}]), httpc_response:send(Request#request.from, {Request#request.id, stream, BodyPart}), {<<>>, Request}; @@ -167,7 +186,7 @@ stream(BodyPart, Request = #request{stream = Self}, 404) %% We keep this for backward compatibillity... stream(BodyPart, Request = #request{stream = Filename}, Code) when ((Code =:= 200) orelse (Code =:= 206)) andalso is_list(Filename) -> - ?hcrt("stream - filename", [{stream, Filename}, {code, Code}, {body_part, BodyPart}]), + ?hcrt("stream - filename", [{stream, Filename}, {code, Code}]), case file:open(Filename, [write, raw, append, delayed_write]) of {ok, Fd} -> ?hcrt("stream - file open ok", [{fd, Fd}]), @@ -179,7 +198,7 @@ stream(BodyPart, Request = #request{stream = Filename}, Code) %% Stream to file stream(BodyPart, Request = #request{stream = Fd}, Code) when ((Code =:= 200) orelse (Code =:= 206)) -> - ?hcrt("stream to file", [{stream, Fd}, {code, Code}, {body_part, BodyPart}]), + ?hcrt("stream to file", [{stream, Fd}, {code, Code}]), case file:write(Fd, BodyPart) of ok -> {<<>>, Request}; @@ -188,7 +207,7 @@ stream(BodyPart, Request = #request{stream = Fd}, Code) end; stream(BodyPart, Request,_) -> % only 200 and 206 responses can be streamed - ?hcrt("stream - ignore", [{request, Request}, {body_part, BodyPart}]), + ?hcrt("stream - ignore", [{request, Request}]), {BodyPart, Request}. @@ -260,22 +279,22 @@ handle_call({connect_and_send, #request{address = Address0, end end; -handle_call(Request, _, +handle_call(#request{address = Addr} = Request, _, #state{status = Status, session = #tcp_session{socket = Socket, type = pipeline} = Session, timers = Timers, - options = Options, + options = #options{proxy = Proxy} = _Options, profile_name = ProfileName} = State) when Status =/= undefined -> - ?hcrv("new request", [{request, Request}, - {profile, ProfileName}, - {status, Status}, - {session_type, pipeline}, - {timers, Timers}]), + ?hcrv("new request on a pipeline session", + [{request, Request}, + {profile, ProfileName}, + {status, Status}, + {timers, Timers}]), - Address = handle_proxy(Request#request.address, Options#options.proxy), + Address = handle_proxy(Addr, Proxy), case httpc_request:send(Address, Request, Socket) of ok -> @@ -331,21 +350,21 @@ handle_call(Request, _, {reply, {pipeline_failed, Reason}, State} end; -handle_call(Request, _, +handle_call(#request{address = Addr} = Request, _, #state{status = Status, session = #tcp_session{socket = Socket, type = keep_alive} = Session, timers = Timers, - options = Options, + options = #options{proxy = Proxy} = _Options, profile_name = ProfileName} = State) when Status =/= undefined -> - ?hcrv("new request", [{request, Request}, - {profile, ProfileName}, - {status, Status}, - {session_type, keep_alive}]), + ?hcrv("new request on a keep-alive session", + [{request, Request}, + {profile, ProfileName}, + {status, Status}]), - Address = handle_proxy(Request#request.address, Options#options.proxy), + Address = handle_proxy(Addr, Proxy), case httpc_request:send(Address, Request, Socket) of ok -> @@ -396,7 +415,12 @@ handle_call(Request, _, {error, Reason} -> ?hcri("failed sending request", [{reason, Reason}]), {reply, {request_failed, Reason}, State} - end. + end; + + +handle_call(info, _, State) -> + Info = handler_info(State), + {reply, Info, State}. %%-------------------------------------------------------------------- @@ -441,8 +465,7 @@ handle_cast({cancel, RequestId}, {noreply, State#state{canceled = [RequestId | Canceled]}}; handle_cast(stream_next, #state{session = Session} = State) -> - http_transport:setopts(socket_type(Session#tcp_session.scheme), - Session#tcp_session.socket, [{active, once}]), + activate_once(Session), {noreply, State#state{once = once}}. @@ -453,7 +476,7 @@ handle_cast(stream_next, #state{session = Session} = State) -> %% Description: Handling all non call/cast messages %%-------------------------------------------------------------------- handle_info({Proto, _Socket, Data}, - #state{mfa = {Module, Function, Args} = MFA, + #state{mfa = {Module, Function, Args}, request = #request{method = Method, stream = Stream} = Request, session = Session, @@ -463,8 +486,8 @@ handle_info({Proto, _Socket, Data}, (Proto =:= httpc_handler) -> ?hcri("received data", [{proto, Proto}, - {data, Data}, - {mfa, MFA}, + {module, Module}, + {function, Function}, {method, Method}, {stream, Stream}, {session, Session}, @@ -473,14 +496,13 @@ handle_info({Proto, _Socket, Data}, FinalResult = try Module:Function([Data | Args]) of {ok, Result} -> - ?hcrd("data processed - ok", [{result, Result}]), + ?hcrd("data processed - ok", []), handle_http_msg(Result, State); {_, whole_body, _} when Method =:= head -> ?hcrd("data processed - whole body", []), handle_response(State#state{body = <<>>}); {Module, whole_body, [Body, Length]} -> - ?hcrd("data processed - whole body", - [{module, Module}, {body, Body}, {length, Length}]), + ?hcrd("data processed - whole body", [{length, Length}]), {_, Code, _} = StatusLine, {NewBody, NewRequest} = stream(Body, Request, Code), %% When we stream we will not keep the already @@ -498,25 +520,25 @@ handle_info({Proto, _Socket, Data}, {noreply, NewState#state{mfa = NewMFA, request = NewRequest}}; NewMFA -> - ?hcrd("data processed", [{new_mfa, NewMFA}]), - http_transport:setopts(socket_type(Session#tcp_session.scheme), - Session#tcp_session.socket, - [{active, once}]), + ?hcrd("data processed - new mfa", []), + activate_once(Session), {noreply, State#state{mfa = NewMFA}} catch - exit:_ -> + exit:_Exit -> + ?hcrd("data processing exit", [{exit, _Exit}]), ClientReason = {could_not_parse_as_http, Data}, ClientErrMsg = httpc_response:error(Request, ClientReason), NewState = answer_request(Request, ClientErrMsg, State), {stop, normal, NewState}; - error:_ -> + error:_Error -> + ?hcrd("data processing error", [{error, _Error}]), ClientReason = {could_not_parse_as_http, Data}, ClientErrMsg = httpc_response:error(Request, ClientReason), NewState = answer_request(Request, ClientErrMsg, State), {stop, normal, NewState} end, - ?hcri("data processed", [{result, FinalResult}]), + ?hcri("data processed", []), FinalResult; @@ -667,6 +689,9 @@ terminate(normal, request = Request, timers = Timers, pipeline = Pipeline}) -> + ?hcrt("terminate(normal) - remote close", + [{id, Id}, {profile, ProfileName}]), + %% Clobber session (catch httpc_manager:delete_session(Id, ProfileName)), @@ -776,19 +801,22 @@ new_queue(Queue, Fun) -> end, List), queue:from_list(NewList). -%%-------------------------------------------------------------------- + +%%%-------------------------------------------------------------------- %%% Internal functions -%%-------------------------------------------------------------------- +%%%-------------------------------------------------------------------- -connect(SocketType, ToAddress, #options{ipfamily = IpFamily, - ip = FromAddress, - port = FromPort}, Timeout) -> +connect(SocketType, ToAddress, + #options{ipfamily = IpFamily, + ip = FromAddress, + port = FromPort, + socket_opts = Opts0}, Timeout) -> Opts1 = case FromPort of default -> - []; + Opts0; _ -> - [{port, FromPort}] + [{port, FromPort} | Opts0] end, Opts2 = case FromAddress of @@ -814,10 +842,10 @@ connect(SocketType, ToAddress, #options{ipfamily = IpFamily, end. connect_and_send_first_request(Address, - #request{settings = Settings, - headers = Headers, - address = OrigAddress, - scheme = Scheme} = Request, + #request{settings = Settings, + headers = Headers, + address = OrigAddress, + scheme = Scheme} = Request, #state{options = Options} = State) -> ?hcrd("connect", @@ -841,13 +869,13 @@ connect_and_send_first_request(Address, client_close = ClientClose, type = SessionType}, TmpState = - State#state{request = Request, - session = Session, - mfa = init_mfa(Request, State), + State#state{request = Request, + session = Session, + mfa = init_mfa(Request, State), status_line = init_status_line(Request), - headers = undefined, - body = undefined, - status = new}, + headers = undefined, + body = undefined, + status = new}, ?hcrt("activate socket", []), activate_once(Session), NewState = activate_request_timeout(TmpState), @@ -867,12 +895,87 @@ connect_and_send_first_request(Address, {stop, Error, State#state{request = Request}} end. + +handler_info(#state{request = Request, + session = Session, + status_line = _StatusLine, + pipeline = Pipeline, + keep_alive = KeepAlive, + status = Status, + canceled = _Canceled, + options = _Options, + timers = _Timers} = _State) -> + + ?hcrt("handler info", [{request, Request}, + {session, Session}, + {pipeline, Pipeline}, + {keep_alive, KeepAlive}, + {status, Status}]), + + %% Info about the current request + RequestInfo = + case Request of + undefined -> + []; + #request{id = Id, + started = ReqStarted} -> + [{id, Id}, {started, ReqStarted}] + end, + + ?hcrt("handler info", [{request_info, RequestInfo}]), + + %% Info about the current session/socket + SessionType = Session#tcp_session.type, + QueueLen = case Session#tcp_session.type of + pipeline -> + queue:len(Pipeline); + keep_alive -> + queue:len(KeepAlive) + end, + Socket = Session#tcp_session.socket, + Scheme = Session#tcp_session.scheme, + SocketType = socket_type(Scheme), + + ?hcrt("handler info", [{session_type, SessionType}, + {queue_length, QueueLen}, + {scheme, Scheme}, + {socket_type, SocketType}, + {socket, Socket}]), + + SocketOpts = http_transport:getopts(SocketType, Socket), + SocketStats = http_transport:getstat(SocketType, Socket), + + Remote = http_transport:peername(SocketType, Socket), + Local = http_transport:sockname(SocketType, Socket), + + ?hcrt("handler info", [{remote, Remote}, + {local, Local}, + {socket_opts, SocketOpts}, + {socket_stats, SocketStats}]), + + SocketInfo = [{remote, Remote}, + {local, Local}, + {socket_opts, SocketOpts}, + {socket_stats, SocketStats}], + + SessionInfo = + [{type, SessionType}, + {queue_length, QueueLen}, + {scheme, Scheme}, + {socket_info, SocketInfo}], + + [{status, Status}, + {current_request, RequestInfo}, + {session, SessionInfo}]. + + + handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body}, State = #state{request = Request}) -> - ?hcrt("handle_http_msg", [{body, Body}]), + ?hcrt("handle_http_msg", [{headers, Headers}]), case Headers#http_response_h.'content-type' of "multipart/byteranges" ++ _Param -> - exit({not_yet_implemented, multypart_nyteranges}); + exit({not_yet_implemented, multypart_byteranges}); _ -> StatusLine = {Version, StatusCode, ReasonPharse}, {ok, NewRequest} = start_stream(StatusLine, Headers, Request), @@ -883,11 +986,11 @@ handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body}, end; handle_http_msg({ChunkedHeaders, Body}, #state{headers = Headers} = State) -> ?hcrt("handle_http_msg", - [{chunked_headers, ChunkedHeaders}, {body, Body}]), + [{chunked_headers, ChunkedHeaders}, {headers, Headers}]), NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders), handle_response(State#state{headers = NewHeaders, body = Body}); handle_http_msg(Body, #state{status_line = {_,Code, _}} = State) -> - ?hcrt("handle_http_msg", [{body, Body}, {code, Code}]), + ?hcrt("handle_http_msg", [{code, Code}]), {NewBody, NewRequest} = stream(Body, State#state.request, Code), handle_response(State#state{body = NewBody, request = NewRequest}). @@ -903,15 +1006,12 @@ handle_http_body(<<>>, State = #state{request = #request{method = head}}) -> ?hcrt("handle_http_body - head", []), handle_response(State#state{body = <<>>}); -handle_http_body(Body, State = #state{headers = Headers, - max_body_size = MaxBodySize, - status_line = {_,Code, _}, - request = Request}) -> +handle_http_body(Body, #state{headers = Headers, + max_body_size = MaxBodySize, + status_line = {_,Code, _}, + request = Request} = State) -> ?hcrt("handle_http_body", - [{headers, Headers}, - {body, Body}, - {max_body_size, MaxBodySize}, - {code, Code}]), + [{max_body_size, MaxBodySize}, {headers, Headers}, {code, Code}]), TransferEnc = Headers#http_response_h.'transfer-encoding', case case_insensitive_header(TransferEnc) of "chunked" -> @@ -1000,9 +1100,7 @@ handle_response(#state{request = Request, Session#tcp_session.socket, RequestBody), %% Wait for next response - http_transport:setopts(socket_type(Session#tcp_session.scheme), - Session#tcp_session.socket, - [{active, once}]), + activate_once(Session), Relaxed = (Request#request.settings)#http_options.relaxed, MFA = {httpc_response, parse, [State#state.max_header_size, Relaxed]}, @@ -1038,7 +1136,7 @@ handle_response(#state{request = Request, ok = httpc_manager:retry_request(TimeNewRequest, ProfileName), handle_queue(State#state{request = undefined}, Data); {ok, Msg, Data} -> - ?hcrd("handle response - ok", [{msg, Msg}, {data, Data}]), + ?hcrd("handle response - ok", []), end_stream(StatusLine, Request), NewState = answer_request(Request, Msg, State), handle_queue(NewState, Data); @@ -1137,10 +1235,7 @@ handle_pipeline(#state{status = pipeline, body = undefined}, case Data of <<>> -> - http_transport:setopts( - socket_type(Session#tcp_session.scheme), - Session#tcp_session.socket, - [{active, once}]), + activate_once(Session), {noreply, NewState}; _ -> %% If we already received some bytes of @@ -1164,14 +1259,12 @@ handle_keep_alive_queue( case queue:out(State#state.keep_alive) of {empty, _} -> - ?hcrd("epmty keep_alive queue", []), + ?hcrd("empty keep_alive queue", []), %% The server may choose too terminate an idle keep_alive session %% in this case we want to receive the close message %% at once and not when trying to send the next %% request. - http_transport:setopts(socket_type(Session#tcp_session.scheme), - Session#tcp_session.socket, - [{active, once}]), + activate_once(Session), %% If a keep_alive session has been idle for some time is not %% closed by the server, the client may want to close it. NewState = activate_queue_timeout(TimeOut, State), @@ -1276,9 +1369,8 @@ is_keep_alive_enabled_server("HTTP/1.0", is_keep_alive_enabled_server(_,_) -> false. -is_keep_alive_connection(Headers, Session) -> - (not ((Session#tcp_session.client_close) or - httpc_response:is_server_closing(Headers))). +is_keep_alive_connection(Headers, #tcp_session{client_close = ClientClose}) -> + (not ((ClientClose) orelse httpc_response:is_server_closing(Headers))). try_to_enable_pipeline_or_keep_alive( #state{session = Session, @@ -1286,8 +1378,12 @@ try_to_enable_pipeline_or_keep_alive( status_line = {Version, _, _}, headers = Headers, profile_name = ProfileName} = State) -> - case (is_keep_alive_enabled_server(Version, Headers) andalso - is_keep_alive_connection(Headers, Session)) of + ?hcrd("try to enable pipeline or keep-alive", + [{version, Version}, + {headers, Headers}, + {session, Session}]), + case is_keep_alive_enabled_server(Version, Headers) andalso + is_keep_alive_connection(Headers, Session) of true -> case (is_pipeline_enabled_client(Session) andalso httpc_request:is_idempotent(Method)) of @@ -1307,7 +1403,7 @@ try_to_enable_pipeline_or_keep_alive( end. answer_request(Request, Msg, #state{timers = Timers} = State) -> - ?hcrt("answer request", [{request, Request}, {msg, Msg}]), + ?hcrt("answer request", [{request, Request}]), httpc_response:send(Request#request.from, Msg), RequestTimers = Timers#timers.request_timers, TimerRef = @@ -1435,7 +1531,7 @@ socket_type(#request{scheme = https, settings = Settings}) -> socket_type(http) -> ip_comm; socket_type(https) -> - {ssl, []}. %% Dummy value ok for ex setops that does not use this value + {ssl, []}. %% Dummy value ok for ex setopts that does not use this value start_stream({_Version, _Code, _ReasonPhrase}, _Headers, #request{stream = none} = Request) -> @@ -1529,6 +1625,7 @@ handle_verbose(trace) -> handle_verbose(_) -> ok. + %%% Normaly I do not comment out code, I throw it away. But this might %%% actually be used one day if ssl is improved. %% send_ssl_tunnel_request(Address, Request = #request{address = {Host, Port}}, diff --git a/lib/inets/src/http_client/httpc_internal.hrl b/lib/inets/src/http_client/httpc_internal.hrl index 4c5e6ed5d8..4d76c4beb3 100644 --- a/lib/inets/src/http_client/httpc_internal.hrl +++ b/lib/inets/src/http_client/httpc_internal.hrl @@ -46,7 +46,7 @@ %% bool() - true if auto redirect on 30x response autoredirect = true, - %% Ssl socket options + %% ssl socket options ssl = [], %% {User, Password} = {string(), string()} @@ -63,19 +63,20 @@ %%% HTTP Client per profile setting. -record(options, { - proxy = {undefined, []}, % {{ProxyHost, ProxyPort}, [NoProxy]}, - %% 0 means persistent connections are used without pipelining - pipeline_timeout = ?HTTP_PIPELINE_TIMEOUT, - max_pipeline_length = ?HTTP_PIPELINE_LENGTH, - max_keep_alive_length = ?HTTP_KEEP_ALIVE_LENGTH, - keep_alive_timeout = ?HTTP_KEEP_ALIVE_TIMEOUT, % Used when pipeline_timeout = 0 - max_sessions = ?HTTP_MAX_TCP_SESSIONS, - cookies = disabled, % enabled | disabled | verify - verbose = false, - ipfamily = inet, % inet | inet6 | inet6fb4 - ip = default, % specify local interface - port = default % specify local port - } + proxy = {undefined, []}, % {{ProxyHost, ProxyPort}, [NoProxy]}, + %% 0 means persistent connections are used without pipelining + pipeline_timeout = ?HTTP_PIPELINE_TIMEOUT, + max_pipeline_length = ?HTTP_PIPELINE_LENGTH, + max_keep_alive_length = ?HTTP_KEEP_ALIVE_LENGTH, + keep_alive_timeout = ?HTTP_KEEP_ALIVE_TIMEOUT, % Used when pipeline_timeout = 0 + max_sessions = ?HTTP_MAX_TCP_SESSIONS, + cookies = disabled, % enabled | disabled | verify + verbose = false, + ipfamily = inet, % inet | inet6 | inet6fb4 + ip = default, % specify local interface + port = default, % specify local port + socket_opts = [] % other socket options + } ). %%% All data associated to a specific HTTP request @@ -98,7 +99,8 @@ headers_as_is, % Boolean() - workaround for servers that does % not honor the http standard, can also be used for testing purposes. started, % integer() > 0 - When we started processing the request - timer % undefined | ref() + timer, % undefined | ref() + socket_opts % undefined | [socket_option()] } ). @@ -109,7 +111,7 @@ scheme, % http (HTTP/TCP) | https (HTTP/SSL/TCP) socket, % Open socket, used by connection queue_length = 1, % Current length of pipeline or keep alive queue - type % pipeline | keep_alive (wait for response before sending new request) + type % pipeline | keep_alive (wait for response before sending new request) }). -record(http_cookie, diff --git a/lib/inets/src/http_client/httpc_manager.erl b/lib/inets/src/http_client/httpc_manager.erl index 915f4c024d..f8fc6322ed 100644 --- a/lib/inets/src/http_client/httpc_manager.erl +++ b/lib/inets/src/http_client/httpc_manager.erl @@ -38,7 +38,8 @@ store_cookies/3, which_cookies/1, which_cookies/2, reset_cookies/1, - session_type/1 + session_type/1, + info/1 ]). %% gen_server callbacks @@ -61,7 +62,7 @@ starter, % Pid of the handler starter process (temp): pid() handler, % Pid of the handler process: pid() from, % From for the request: from() - state % State of the handler: initiating | operational + state % State of the handler: initiating | operational | canceled }). %% Entries in the handler / request cross-ref table @@ -181,6 +182,7 @@ request_canceled(RequestId, ProfileName) -> insert_session(Session, ProfileName) -> SessionDbName = session_db_name(ProfileName), + ?hcrt("insert session", [{session, Session}, {profile, ProfileName}]), ets:insert(SessionDbName, Session). @@ -196,6 +198,7 @@ insert_session(Session, ProfileName) -> delete_session(SessionId, ProfileName) -> SessionDbName = session_db_name(ProfileName), + ?hcrt("delete session", [{session_is, SessionId}, {profile, ProfileName}]), ets:delete(SessionDbName, SessionId). @@ -263,6 +266,19 @@ which_cookies(Url, ProfileName) -> %%-------------------------------------------------------------------- +%% Function: info(ProfileName) -> list() +%% +%% ProfileName = atom() +%% +%% Description: Retrieves various info about the manager and the +%% handlers it manages +%%-------------------------------------------------------------------- + +info(ProfileName) -> + call(ProfileName, info). + + +%%-------------------------------------------------------------------- %% Function: session_type(Options) -> ok %% %% Options = #options{} @@ -342,10 +358,8 @@ handle_call({request, Request}, _From, State) -> {reply, {ok, ReqId}, NewState}; Error -> - %% This is way too severe - %% To crash the manager simply because - %% it failed to properly handle a request - {stop, Error, httpc_response:error(Request, Error), State} + NewError = {error, {failed_process_request, Error}}, + {reply, NewError, State} end; handle_call({cancel_request, RequestId}, From, @@ -377,17 +391,17 @@ handle_call({cancel_request, RequestId}, From, end; -handle_call(reset_cookies, _, #state{cookie_db = CookieDb} = State) -> +handle_call(reset_cookies, _, #state{cookie_db = CookieDb} = State) -> ?hcrv("reset cookies", []), httpc_cookie:reset_db(CookieDb), {reply, ok, State}; -handle_call(which_cookies, _, #state{cookie_db = CookieDb} = State) -> +handle_call(which_cookies, _, #state{cookie_db = CookieDb} = State) -> ?hcrv("which cookies", []), CookieHeaders = httpc_cookie:which_cookies(CookieDb), {reply, CookieHeaders, State}; -handle_call({which_cookies, Url}, _, #state{cookie_db = CookieDb} = State) -> +handle_call({which_cookies, Url}, _, #state{cookie_db = CookieDb} = State) -> ?hcrv("which cookies", [{url, Url}]), case http_uri:parse(Url) of {Scheme, _, Host, Port, Path, _} -> @@ -398,6 +412,11 @@ handle_call({which_cookies, Url}, _, #state{cookie_db = CookieDb} = State) -> {reply, Msg, State} end; +handle_call(info, _, State) -> + ?hcrv("info", []), + Info = get_manager_info(State), + {reply, Info, State}; + handle_call(Req, From, #state{profile_name = ProfileName} = State) -> error_report(ProfileName, "received unkown request" @@ -428,17 +447,29 @@ handle_cast({retry_or_redirect_request, {Time, Request}}, {noreply, State} end; -handle_cast({retry_or_redirect_request, Request}, State) -> +handle_cast({retry_or_redirect_request, Request}, + #state{profile_name = Profile, + handler_db = HandlerDb} = State) -> ?hcrv("retry or redirect request", [{request, Request}]), case (catch handle_request(Request, State)) of {ok, _, NewState} -> {noreply, NewState}; Error -> - %% This is *way* too severe. - %% To crash the manager simply because - %% it failed to properly handle *one* request - {stop, Error, State} + ReqId = Request#request.id, + error_report(Profile, + "failed to retry or redirect request ~p" + "~n Error: ~p", [ReqId, Error]), + case ets:lookup(HandlerDb, ReqId) of + [#handler_info{from = From}] -> + Error2 = httpc_response:error(Request, Error), + httpc_response:send(From, Error2), + ok; + + _ -> + ok + end, + {noreply, State} end; handle_cast({request_canceled, RequestId}, State) -> @@ -468,7 +499,8 @@ handle_cast({set_options, Options}, State = #state{options = OldOptions}) -> ipfamily = get_ipfamily(Options, OldOptions), ip = get_ip(Options, OldOptions), port = get_port(Options, OldOptions), - verbose = get_verbose(Options, OldOptions) + verbose = get_verbose(Options, OldOptions), + socket_opts = get_socket_opts(Options, OldOptions) }, case {OldOptions#options.verbose, NewOptions#options.verbose} of {Same, Same} -> @@ -572,6 +604,32 @@ code_change(_OldVsn, State, _Extra) -> %% Internal functions %%-------------------------------------------------------------------- +get_manager_info(#state{handler_db = HDB, + cookie_db = CDB} = _State) -> + HandlerInfo = get_handler_info(HDB), + CookieInfo = httpc_cookie:which_cookies(CDB), + [{handlers, HandlerInfo}, {cookies, CookieInfo}]. + +get_handler_info(Tab) -> + Pattern = #handler_info{handler = '$1', + state = '$2', + _ = '_'}, + Handlers1 = [{Pid, State} || [Pid, State] <- ets:match(Tab, Pattern)], + F = fun({Pid, State} = Elem, Acc) when State =/= canceled -> + case lists:keymember(Pid, 1, Acc) of + true -> + Acc; + false -> + [Elem | Acc] + end; + (_, Acc) -> + Acc + end, + Handlers2 = lists:foldl(F, [], Handlers1), + Handlers3 = [{Pid, State, httpc_handler:info(Pid)} || + {Pid, State} <- Handlers2], + Handlers3. + %% %% The request handler process is started asynchronously by a @@ -606,7 +664,7 @@ handle_connect_and_send(_StarterPid, ReqId, HandlerPid, Result, "send request ~p" "~n Error: ~p", [HandlerPid, ReqId, Result]), ?hcri("received connect-and-send error", [{result, Result}]), - Reason2 = + Reason2 = case Result of {error, Reason} -> {failed_connecting, Reason}; @@ -747,7 +805,10 @@ select_session(Method, HostPort, Scheme, SessionType, type = SessionType}, %% {'_', {HostPort, '$1'}, false, Scheme, '_', '$2', SessionTyp}, Candidates = ets:match(SessionDb, Pattern), - ?hcrd("select session", [{candidates, Candidates}]), + ?hcrd("select session", [{host_port, HostPort}, + {scheme, Scheme}, + {type, SessionType}, + {candidates, Candidates}]), select_session(Candidates, MaxKeepAlive, MaxPipe, SessionType); false -> no_connection @@ -776,20 +837,30 @@ pipeline_or_keep_alive(#request{id = Id} = Request, HandlerPid, State) -> ?hcrd("pipeline of keep-alive", [{id, Id}, {handler, HandlerPid}]), case (catch httpc_handler:send(Request, HandlerPid)) of ok -> - ?hcrd("pipeline of keep-alive - successfully sent", []), + ?hcrd("pipeline or keep-alive - successfully sent", []), Entry = #handler_info{id = Id, handler = HandlerPid, state = operational}, ets:insert(State#state.handler_db, Entry); _ -> %% timeout pipelining failed - ?hcrd("pipeline of keep-alive - failed sending -> " + ?hcrd("pipeline or keep-alive - failed sending -> " "start a new handler", []), create_handler_starter(Request, State) end. -create_handler_starter(#request{id = Id, from = From} = Request, +create_handler_starter(#request{socket_opts = SocketOpts} = Request, + #state{options = Options} = State) + when is_list(SocketOpts) -> + %% The user provided us with (override) socket options + ?hcrt("create handler starter", [{socket_opts, SocketOpts}, {options, Options}]), + Options2 = Options#options{socket_opts = SocketOpts}, + create_handler_starter(Request#request{socket_opts = undefined}, + State#state{options = Options2}); + +create_handler_starter(#request{id = Id, + from = From} = Request, #state{profile_name = ProfileName, options = Options, handler_db = HandlerDb} = _State) -> @@ -858,8 +929,8 @@ generate_request_id(Request) -> RequestId = make_ref(), Request#request{id = RequestId}; _ -> - %% This is an automatic redirect or a retryed pipelined - %% request keep the old id. + %% This is an automatic redirect or a retryed pipelined request + %% => keep the old id. Request end. @@ -960,6 +1031,9 @@ get_port(Opts, #options{port = Default}) -> get_verbose(Opts, #options{verbose = Default}) -> proplists:get_value(verbose, Opts, Default). +get_socket_opts(Opts, #options{socket_opts = Default}) -> + proplists:get_value(socket_opts, Opts, Default). + handle_verbose(debug) -> dbg:p(self(), [call]), diff --git a/lib/inets/src/http_client/httpc_request.erl b/lib/inets/src/http_client/httpc_request.erl index f15c5d4381..55e0af4b42 100644 --- a/lib/inets/src/http_client/httpc_request.erl +++ b/lib/inets/src/http_client/httpc_request.erl @@ -39,24 +39,37 @@ %% %% Description: Composes and sends a HTTP-request. %%------------------------------------------------------------------------- -send(SendAddr, #request{method = Method, - scheme = Scheme, - path = Path, - pquery = Query, - headers = Headers, - content = Content, - address = Address, - abs_uri = AbsUri, - headers_as_is = HeadersAsIs, - settings = HttpOptions, - userinfo = UserInfo}, - Socket) -> +send(SendAddr, #request{scheme = Scheme, socket_opts = SocketOpts} = Request, + Socket) + when is_list(SocketOpts) -> + SocketType = socket_type(Scheme), + case http_transport:setopts(SocketType, Socket, SocketOpts) of + ok -> + send(SendAddr, Socket, SocketType, + Request#request{socket_opts = undefined}); + {error, Reason} -> + {error, {setopts_failed, Reason}} + end; +send(SendAddr, #request{scheme = Scheme} = Request, Socket) -> + SocketType = socket_type(Scheme), + send(SendAddr, Socket, SocketType, Request). + +send(SendAddr, Socket, SocketType, + #request{method = Method, + path = Path, + pquery = Query, + headers = Headers, + content = Content, + address = Address, + abs_uri = AbsUri, + headers_as_is = HeadersAsIs, + settings = HttpOptions, + userinfo = UserInfo}) -> ?hcrt("send", [{send_addr, SendAddr}, {socket, Socket}, {method, Method}, - {scheme, Scheme}, {path, Path}, {pquery, Query}, {headers, Headers}, @@ -95,7 +108,8 @@ send(SendAddr, #request{method = Method, ?hcrd("send", [{message, Message}]), - http_transport:send(socket_type(Scheme), Socket, lists:append(Message)). + http_transport:send(SocketType, Socket, lists:append(Message)). + %%------------------------------------------------------------------------- diff --git a/lib/inets/src/http_lib/http_transport.erl b/lib/inets/src/http_lib/http_transport.erl index 27a950174f..7c2ac626e6 100644 --- a/lib/inets/src/http_lib/http_transport.erl +++ b/lib/inets/src/http_lib/http_transport.erl @@ -16,17 +16,33 @@ %% %% %CopyrightEnd% %% -% + -module(http_transport). % Internal application API --export([start/1, connect/3, connect/4, listen/2, listen/3, - accept/2, accept/3, close/2, - send/3, controlling_process/3, setopts/3, - peername/2, resolve/0]). +-export([ + start/1, + connect/3, connect/4, + listen/2, listen/3, + accept/2, accept/3, + close/2, + send/3, + controlling_process/3, + setopts/3, getopts/2, getopts/3, + getstat/2, + peername/2, sockname/2, + resolve/0 + ]). -export([negotiate/3]). +-include("inets_internal.hrl"). +-define(SERVICE, httpl). +-define(hlri(Label, Content), ?report_important(Label, ?SERVICE, Content)). +-define(hlrv(Label, Content), ?report_verbose(Label, ?SERVICE, Content)). +-define(hlrd(Label, Content), ?report_debug(Label, ?SERVICE, Content)). +-define(hlrt(Label, Content), ?report_trace(Label, ?SERVICE, Content)). + %%%========================================================================= %%% Internal application API @@ -77,14 +93,22 @@ connect(SocketType, Address, Opts) -> connect(ip_comm = _SocketType, {Host, Port}, Opts0, Timeout) when is_list(Opts0) -> Opts = [binary, {packet, 0}, {active, false}, {reuseaddr, true} | Opts0], + ?hlrt("connect using gen_tcp", + [{host, Host}, {port, Port}, {opts, Opts}, {timeout, Timeout}]), gen_tcp:connect(Host, Port, Opts, Timeout); connect({ssl, SslConfig}, {Host, Port}, _, Timeout) -> Opts = [binary, {active, false}] ++ SslConfig, + ?hlrt("connect using ssl", + [{host, Host}, {port, Port}, {ssl_config, SslConfig}, + {timeout, Timeout}]), ssl:connect(Host, Port, Opts, Timeout); connect({erl_ssl, SslConfig}, {Host, Port}, _, Timeout) -> Opts = [binary, {active, false}, {ssl_imp, new}] ++ SslConfig, + ?hlrt("connect using erl_ssl", + [{host, Host}, {port, Port}, {ssl_config, SslConfig}, + {timeout, Timeout}]), ssl:connect(Host, Port, Opts, Timeout). @@ -209,6 +233,7 @@ accept(ip_comm, ListenSocket, Timeout) -> accept({ssl,_SSLConfig}, ListenSocket, Timeout) -> ssl:transport_accept(ListenSocket, Timeout). + %%------------------------------------------------------------------------- %% controlling_process(SocketType, Socket, NewOwner) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, _} @@ -222,6 +247,7 @@ controlling_process(ip_comm, Socket, NewOwner) -> controlling_process({ssl, _}, Socket, NewOwner) -> ssl:controlling_process(Socket, NewOwner). + %%------------------------------------------------------------------------- %% setopts(SocketType, Socket, Options) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, _} @@ -231,10 +257,62 @@ controlling_process({ssl, _}, Socket, NewOwner) -> %% gen_tcp or ssl. %%------------------------------------------------------------------------- setopts(ip_comm, Socket, Options) -> - inet:setopts(Socket,Options); + ?hlrt("ip_comm setopts", [{socket, Socket}, {options, Options}]), + inet:setopts(Socket, Options); setopts({ssl, _}, Socket, Options) -> + ?hlrt("ssl setopts", [{socket, Socket}, {options, Options}]), ssl:setopts(Socket, Options). + +%%------------------------------------------------------------------------- +%% getopts(SocketType, Socket [, Opts]) -> ok | {error, Reason} +%% SocketType = ip_comm | {ssl, _} +%% Socket = socket() +%% Opts = socket_options() +%% Description: Gets the values for some options. +%%------------------------------------------------------------------------- +getopts(SocketType, Socket) -> + Opts = [packet, packet_size, recbuf, sndbuf, priority, tos, send_timeout], + getopts(SocketType, Socket, Opts). + +getopts(ip_comm, Socket, Options) -> + ?hlrt("ip_comm getopts", [{socket, Socket}, {options, Options}]), + case inet:getopts(Socket, Options) of + {ok, SocketOpts} -> + SocketOpts; + {error, _} -> + [] + end; +getopts({ssl, _}, Socket, Options) -> + ?hlrt("ssl getopts", [{socket, Socket}, {options, Options}]), + case ssl:getopts(Socket, Options) of + {ok, SocketOpts} -> + SocketOpts; + {error, _} -> + [] + end. + + +%%------------------------------------------------------------------------- +%% getstat(SocketType, Socket) -> socket_stats() +%% SocketType = ip_comm | {ssl, _} +%% Socket = socket() +%% socket_stats() = list() +%% Description: Gets the socket stats values for the socket +%%------------------------------------------------------------------------- +getstat(ip_comm = _SocketType, Socket) -> + ?hlrt("ip_comm getstat", [{socket, Socket}]), + case inet:getstat(Socket) of + {ok, Stats} -> + Stats; + {error, _} -> + [] + end; +getstat({ssl, _} = _SocketType, _Socket) -> + %% ?hlrt("ssl getstat", [{socket, Socket}]), + []. + + %%------------------------------------------------------------------------- %% send(RequestOrSocketType, Socket, Message) -> ok | {error, Reason} %% SocketType = ip_comm | {ssl, _} @@ -261,9 +339,11 @@ close({ssl, _}, Socket) -> ssl:close(Socket). %%------------------------------------------------------------------------- -%% peername(SocketType, Socket) -> ok | {error, Reason} +%% peername(SocketType, Socket) -> {Port, SockName} %% SocketType = ip_comm | {ssl, _} %% Socket = socket() +%% Port = integer() (-1 if error occured) +%% PeerName = string() %% %% Description: Returns the address and port for the other end of a %% connection, usning either gen_tcp or ssl. @@ -298,6 +378,48 @@ peername({ssl, _}, Socket) -> {-1, "unknown"} end. + +%%------------------------------------------------------------------------- +%% sockname(SocketType, Socket) -> {Port, SockName} +%% SocketType = ip_comm | {ssl, _} +%% Socket = socket() +%% Port = integer() (-1 if error occured) +%% SockName = string() +%% +%% Description: Returns the address and port for the local (our) end +%% other end of connection, using either gen_tcp or ssl. +%%------------------------------------------------------------------------- +sockname(ip_comm, Socket) -> + case inet:sockname(Socket) of + {ok,{{A, B, C, D}, Port}} -> + SockName = integer_to_list(A)++"."++integer_to_list(B)++"."++ + integer_to_list(C)++"."++integer_to_list(D), + {Port, SockName}; + {ok,{{A, B, C, D, E, F, G, H}, Port}} -> + SockName = http_util:integer_to_hexlist(A) ++ ":"++ + http_util:integer_to_hexlist(B) ++ ":" ++ + http_util:integer_to_hexlist(C) ++ ":" ++ + http_util:integer_to_hexlist(D) ++ ":" ++ + http_util:integer_to_hexlist(E) ++ ":" ++ + http_util:integer_to_hexlist(F) ++ ":" ++ + http_util:integer_to_hexlist(G) ++":"++ + http_util:integer_to_hexlist(H), + {Port, SockName}; + {error, _} -> + {-1, "unknown"} + end; + +sockname({ssl, _}, Socket) -> + case ssl:sockname(Socket) of + {ok,{{A, B, C, D}, Port}} -> + SockName = integer_to_list(A)++"."++integer_to_list(B)++"."++ + integer_to_list(C)++"."++integer_to_list(D), + {Port, SockName}; + {error, _} -> + {-1, "unknown"} + end. + + %%------------------------------------------------------------------------- %% resolve() -> HostName %% HostName = string() diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index 4914a16264..96099c49fd 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -164,7 +164,7 @@ end_per_suite(Config) -> %%-------------------------------------------------------------------- %% Function: init_per_testcase(Case, Config) -> Config -% Case - atom() +%% Case - atom() %% Name of the test case that is about to be run. %% Config - [tuple()] %% A list of key/value pairs, holding the test case configuration. @@ -234,6 +234,7 @@ init_per_testcase(Case, Timeout, Config) -> http:set_options([{proxy, {{?PROXY, ?PROXY_PORT}, ["localhost", ?IPV6_LOCAL_HOST]}}]), inets:enable_trace(max, io, httpc), + %% inets:enable_trace(max, io, all), %% snmp:set_trace([gen_tcp, inet_tcp, prim_inet]), NewConfig. @@ -282,6 +283,7 @@ tickets(suite) -> otp_8154, otp_8106, otp_8056, + otp_8352, otp_8371 ]. @@ -977,6 +979,29 @@ http_redirect(Config) when is_list(Config) -> "~n ~p", [URL302]), {ok, {{_,200,_}, [_ | _], [_|_]}} = http:request(get, {URL302, []}, [], []), + case http:request(get, {URL302, []}, [], []) of + {ok, Reply7} -> + case Reply7 of + {{_,200,_}, [_ | _], [_|_]} -> + tsp("http_redirect -> " + "expected reply for request 7"), + ok; + {StatusLine, Headers, Body} -> + tsp("http_redirect -> " + "unexpected reply for request 7: " + "~n StatusLine: ~p" + "~n Headers: ~p" + "~n Body: ~p", + [StatusLine, Headers, Body]), + tsf({unexpected_reply, Reply7}) + end; + Error7 -> + tsp("http_redirect -> " + "unexpected result for request 7: " + "~n Error7: ~p", + [Error7]), + tsf({unexpected_result, Error7}) + end, tsp("http_redirect -> issue request 7: " "~n ~p", [URL302]), @@ -1019,6 +1044,7 @@ http_redirect(Config) when is_list(Config) -> end. + %%------------------------------------------------------------------------- http_redirect_loop(doc) -> ["Test redirect loop detection"]; @@ -1197,26 +1223,42 @@ proxy_emulate_lower_versions(suite) -> proxy_emulate_lower_versions(Config) when is_list(Config) -> case ?config(skip, Config) of undefined -> - {ok, Body0 = [_| _]} = http:request(get, {?PROXY_URL, []}, - [{version, "HTTP/0.9"}], []), - inets_test_lib:check_body(Body0), + Result09 = pelv_get("HTTP/0.9"), + case Result09 of + {ok, [_| _] = Body0} -> + inets_test_lib:check_body(Body0), + ok; + _ -> + tsf({unexpected_result, "HTTP/0.9", Result09}) + end, %% We do not check the version here as many servers %% do not behave according to the rfc and send %% 1.1 in its response. - {ok,{{_, 200, _}, [_ | _], Body1 = [_ | _]}} = - http:request(get, {?PROXY_URL, []}, - [{version, "HTTP/1.0"}], []), - inets_test_lib:check_body(Body1), - - {ok, {{"HTTP/1.1", 200, _}, [_ | _], Body2 = [_ | _]}} = - http:request(get, {?PROXY_URL, []}, - [{version, "HTTP/1.1"}], []), - inets_test_lib:check_body(Body2); + Result10 = pelv_get("HTTP/1.0"), + case Result10 of + {ok,{{_, 200, _}, [_ | _], Body1 = [_ | _]}} -> + inets_test_lib:check_body(Body1), + ok; + _ -> + tsf({unexpected_result, "HTTP/1.0", Result10}) + end, + + Result11 = pelv_get("HTTP/1.1"), + case Result11 of + {ok, {{"HTTP/1.1", 200, _}, [_ | _], Body2 = [_ | _]}} -> + inets_test_lib:check_body(Body2); + _ -> + tsf({unexpected_result, "HTTP/1.1", Result11}) + end; + Reason -> {skip, Reason} end. +pelv_get(Version) -> + http:request(get, {?PROXY_URL, []}, [{version, Version}], []). + %%------------------------------------------------------------------------- proxy_trace(doc) -> ["Perform a TRACE request that goes through a proxy."]; @@ -2275,6 +2317,72 @@ otp_8056(Config) when is_list(Config) -> %%------------------------------------------------------------------------- +otp_8352(doc) -> + "OTP-8352"; +otp_8352(suite) -> + []; +otp_8352(Config) when is_list(Config) -> + tsp("otp_8352 -> entry with" + "~n Config: ~p", [Config]), + case ?config(local_server, Config) of + ok -> + tsp("local-server running"), + + tsp("initial profile info(1): ~p", [httpc:info()]), + + MaxSessions = 5, + MaxKeepAlive = 10, + KeepAliveTimeout = timer:minutes(2), + ConnOptions = [{max_sessions, MaxSessions}, + {max_keep_alive_length, MaxKeepAlive}, + {keep_alive_timeout, KeepAliveTimeout}], + http:set_options(ConnOptions), + + Method = get, + Port = ?config(local_port, Config), + URL = ?URL_START ++ integer_to_list(Port) ++ "/dummy.html", + Request = {URL, []}, + Timeout = timer:seconds(1), + ConnTimeout = Timeout + timer:seconds(1), + HttpOptions1 = [{timeout, Timeout}, {connect_timeout, ConnTimeout}], + Options1 = [{socket_opts, [{tos, 87}, + {recbuf, 16#FFFF}, + {sndbuf, 16#FFFF}]}], + case http:request(Method, Request, HttpOptions1, Options1) of + {ok, {{_,200,_}, [_ | _], ReplyBody1 = [_ | _]}} -> + %% equivaliant to http:request(get, {URL, []}, [], []), + inets_test_lib:check_body(ReplyBody1); + {ok, UnexpectedReply1} -> + tsf({unexpected_reply, UnexpectedReply1}); + {error, _} = Error1 -> + tsf({bad_reply, Error1}) + end, + + tsp("profile info (2): ~p", [httpc:info()]), + + HttpOptions2 = [], + Options2 = [{socket_opts, [{tos, 84}, + {recbuf, 32#1FFFF}, + {sndbuf, 32#1FFFF}]}], + case http:request(Method, Request, HttpOptions2, Options2) of + {ok, {{_,200,_}, [_ | _], ReplyBody2 = [_ | _]}} -> + %% equivaliant to http:request(get, {URL, []}, [], []), + inets_test_lib:check_body(ReplyBody2); + {ok, UnexpectedReply2} -> + tsf({unexpected_reply, UnexpectedReply2}); + {error, _} = Error2 -> + tsf({bad_reply, Error2}) + end, + tsp("profile info (3): ~p", [httpc:info()]), + ok; + + _ -> + {skip, "Failed to start local http-server"} + end. + + +%%------------------------------------------------------------------------- + otp_8371(doc) -> ["OTP-8371"]; otp_8371(suite) -> diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk index 433724f190..746517eed5 100644 --- a/lib/inets/vsn.mk +++ b/lib/inets/vsn.mk @@ -19,7 +19,7 @@ APPLICATION = inets INETS_VSN = 5.3 -PRE_VSN =-p12 +PRE_VSN =-p13 APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)" TICKETS = \ @@ -32,6 +32,7 @@ TICKETS = \ OTP-8327 \ OTP-8349 \ OTP-8351 \ + OTP-8352 \ OTP-8359 \ OTP-8371 |