diff options
-rw-r--r-- | erts/doc/src/erlc.xml | 6 | ||||
-rw-r--r-- | erts/etc/common/erlc.c | 10 | ||||
-rw-r--r-- | erts/test/erlc_SUITE.erl | 10 | ||||
-rw-r--r-- | lib/compiler/doc/src/compile.xml | 6 | ||||
-rw-r--r-- | lib/compiler/src/compile.erl | 10 | ||||
-rw-r--r-- | lib/compiler/test/error_SUITE.erl | 10 | ||||
-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 |
16 files changed, 744 insertions, 231 deletions
diff --git a/erts/doc/src/erlc.xml b/erts/doc/src/erlc.xml index 395daa87e7..1e8960c22c 100644 --- a/erts/doc/src/erlc.xml +++ b/erts/doc/src/erlc.xml @@ -4,7 +4,7 @@ <comref> <header> <copyright> - <year>1997</year><year>2009</year> + <year>1997</year><year>2010</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -13,12 +13,12 @@ compliance with the License. You should have received a copy of the Erlang Public License along with this software. If not, it can be retrieved online at http://www.erlang.org/. - + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - + </legalnotice> <title>erlc</title> diff --git a/erts/etc/common/erlc.c b/erts/etc/common/erlc.c index 216ff7f40e..09aca19e6c 100644 --- a/erts/etc/common/erlc.c +++ b/erts/etc/common/erlc.c @@ -1,19 +1,19 @@ /* * %CopyrightBegin% - * - * Copyright Ericsson AB 1997-2009. All Rights Reserved. - * + * + * Copyright Ericsson AB 1997-2010. All Rights Reserved. + * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. - * + * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. - * + * * %CopyrightEnd% */ /* diff --git a/erts/test/erlc_SUITE.erl b/erts/test/erlc_SUITE.erl index c91471e412..6b4484b31e 100644 --- a/erts/test/erlc_SUITE.erl +++ b/erts/test/erlc_SUITE.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1997-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1997-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(erlc_SUITE). diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml index d6e81165d8..daa686bc56 100644 --- a/lib/compiler/doc/src/compile.xml +++ b/lib/compiler/doc/src/compile.xml @@ -4,7 +4,7 @@ <erlref> <header> <copyright> - <year>1996</year><year>2009</year> + <year>1996</year><year>2010</year> <holder>Ericsson AB. All Rights Reserved.</holder> </copyright> <legalnotice> @@ -13,12 +13,12 @@ compliance with the License. You should have received a copy of the Erlang Public License along with this software. If not, it can be retrieved online at http://www.erlang.org/. - + Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. - + </legalnotice> <title>compile</title> diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 8dd7cea38c..58e147d508 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1996-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1996-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% Purpose: Run the Erlang compiler. diff --git a/lib/compiler/test/error_SUITE.erl b/lib/compiler/test/error_SUITE.erl index 757e1c5725..cdd2434b25 100644 --- a/lib/compiler/test/error_SUITE.erl +++ b/lib/compiler/test/error_SUITE.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 1998-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 1998-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% -module(error_SUITE). 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 |