aboutsummaryrefslogtreecommitdiffstats
path: root/lib/inets
diff options
context:
space:
mode:
Diffstat (limited to 'lib/inets')
-rw-r--r--lib/inets/doc/src/httpc.xml60
-rw-r--r--lib/inets/doc/src/notes.xml10
-rw-r--r--lib/inets/src/http_client/httpc.erl115
-rw-r--r--lib/inets/src/http_client/httpc_handler.erl271
-rw-r--r--lib/inets/src/http_client/httpc_internal.hrl34
-rw-r--r--lib/inets/src/http_client/httpc_manager.erl118
-rw-r--r--lib/inets/src/http_client/httpc_request.erl42
-rw-r--r--lib/inets/src/http_lib/http_transport.erl136
-rw-r--r--lib/inets/test/httpc_SUITE.erl134
-rw-r--r--lib/inets/vsn.mk3
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