From 5d32eaf750cff98d242c8048eeb6b49c94fb6ee6 Mon Sep 17 00:00:00 2001 From: Ingela Anderton Andin Date: Wed, 11 May 2011 12:05:11 +0200 Subject: Fixed httpc manager crash httpc manager crashes.When a request results in a retry, the request id will be "reused" in the previous implementation a race condition could occur causing the manager to crash. This is now avoided by using proc_lib:init_ack and gen_server:enter_loop to allow more advanced initialization of httpc_handlers without blocking the httpc_manger and eliminating extra processes that can cause race conditions. --- lib/inets/src/http_client/httpc_handler.erl | 256 ++++-------- lib/inets/src/http_client/httpc_handler_sup.erl | 8 +- lib/inets/src/http_client/httpc_manager.erl | 509 +++++------------------- lib/inets/src/http_client/httpc_request.erl | 8 +- lib/inets/src/inets_app/inets.appup.src | 60 +-- lib/inets/test/httpc_SUITE.erl | 211 ++++++---- 6 files changed, 324 insertions(+), 728 deletions(-) diff --git a/lib/inets/src/http_client/httpc_handler.erl b/lib/inets/src/http_client/httpc_handler.erl index 5e22400fe0..1f0e012e7e 100644 --- a/lib/inets/src/http_client/httpc_handler.erl +++ b/lib/inets/src/http_client/httpc_handler.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% Copyright Ericsson AB 2002-2011. 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 @@ -29,10 +29,10 @@ %%-------------------------------------------------------------------- %% Internal Application API -export([ - start_link/2, - connect_and_send/2, + start_link/4, + %% connect_and_send/2, send/2, - cancel/2, + cancel/3, stream/3, stream_next/1, info/1 @@ -51,7 +51,7 @@ -record(state, { request, % #request{} - session, % #tcp_session{} + session, % #session{} status_line, % {Version, StatusCode, ReasonPharse} headers, % #http_response_h{} body, % binary() @@ -94,13 +94,9 @@ %%-------------------------------------------------------------------- %%-------------------------------------------------------------------- -start_link(Options, ProfileName) -> - Args = [Options, ProfileName], - gen_server:start_link(?MODULE, Args, []). - -connect_and_send(Request, HandlerPid) -> - call({connect_and_send, Request}, HandlerPid). - +start_link(Parent, Request, Options, ProfileName) -> + {ok, proc_lib:start_link(?MODULE, init, [[Parent, Request, Options, + ProfileName]])}. %%-------------------------------------------------------------------- %% Function: send(Request, Pid) -> ok @@ -122,8 +118,8 @@ send(Request, Pid) -> %% Description: Cancels a request. Intended to be called by the httpc %% manager process. %%-------------------------------------------------------------------- -cancel(RequestId, Pid) -> - cast({cancel, RequestId}, Pid). +cancel(RequestId, Pid, From) -> + cast({cancel, RequestId, From}, Pid). %%-------------------------------------------------------------------- @@ -229,16 +225,27 @@ stream(BodyPart, Request,_) -> % only 200 and 206 responses can be streamed %% but we do not want that so errors will be handled by the process %% sending an init_error message to itself. %%-------------------------------------------------------------------- -init([Options, ProfileName]) -> - ?hcrv("init - starting", [{options, Options}, {profile, ProfileName}]), +init([Parent, Request, Options, ProfileName]) -> process_flag(trap_exit, true), - handle_verbose(Options#options.verbose), - State = #state{status = undefined, - options = Options, - profile_name = ProfileName}, - ?hcrd("init - started", []), - {ok, State}. + %% Do not let initial tcp-connection block the manager-process + proc_lib:init_ack(Parent, self()), + handle_verbose(Options#options.verbose), + Address = handle_proxy(Request#request.address, Options#options.proxy), + {ok, State} = + case {Address /= Request#request.address, Request#request.scheme} of + {true, https} -> + Error = https_through_proxy_is_not_currently_supported, + self() ! {init_error, + Error, httpc_response:error(Request, Error)}, + {ok, #state{request = Request, options = Options, + status = ssl_tunnel}}; + {_, _} -> + connect_and_send_first_request(Address, Request, + #state{options = Options, + profile_name = ProfileName}) + end, + gen_server:enter_loop(?MODULE, [], State). %%-------------------------------------------------------------------- %% Function: handle_call(Request, From, State) -> {reply, Reply, State} | @@ -249,41 +256,6 @@ init([Options, ProfileName]) -> %% {stop, Reason, State} (terminate/2 is called) %% Description: Handling call messages %%-------------------------------------------------------------------- - - -%% This is the first request, the reason the proc was started -handle_call({connect_and_send, #request{address = Address0, - scheme = Scheme} = Request}, - _From, - #state{options = #options{proxy = Proxy}, - status = undefined, - session = undefined} = State) -> - ?hcrv("connect and send", [{address0, Address0}, {proxy, Proxy}]), - Address = handle_proxy(Address0, Proxy), - if - ((Address =/= Address0) andalso (Scheme =:= https)) -> - %% This is what we should do if and when ssl supports - %% "socket upgrading" - %%send_ssl_tunnel_request(Address, Request, - %% #state{options = Options, - %% status = ssl_tunnel}); - Reason = {failed_connecting, - https_through_proxy_is_not_currently_supported}, - %% Send a reply to the original caller - ErrorResponse = httpc_response:error(Request, Reason), - httpc_response:send(Request#request.from, ErrorResponse), - %% Reply to the manager - ErrorReply = {error, Reason}, - {stop, normal, ErrorReply, State}; - true -> - case connect_and_send_first_request(Address, Request, State) of - {ok, NewState} -> - {reply, ok, NewState}; - {stop, Error, NewState} -> - {stop, normal, Error, NewState} - end - end; - handle_call(#request{address = Addr} = Request, _, #state{status = Status, session = #session{type = pipeline} = Session, @@ -445,25 +417,27 @@ handle_call(info, _, State) -> %% handle_keep_alive_queue/2 on the other hand will just skip the %% request as if it was never issued as in this case the request will %% not have been sent. -handle_cast({cancel, RequestId}, +handle_cast({cancel, RequestId, From}, #state{request = #request{id = RequestId} = Request, profile_name = ProfileName, canceled = Canceled} = State) -> ?hcrv("cancel current request", [{request_id, RequestId}, {profile, ProfileName}, {canceled, Canceled}]), - httpc_manager:request_canceled(RequestId, ProfileName), + httpc_manager:request_canceled(RequestId, ProfileName, From), ?hcrv("canceled", []), {stop, normal, State#state{canceled = [RequestId | Canceled], request = Request#request{from = answer_sent}}}; -handle_cast({cancel, RequestId}, +handle_cast({cancel, RequestId, From}, #state{profile_name = ProfileName, + request = #request{id = CurrId}, canceled = Canceled} = State) -> - ?hcrv("cancel", [{request_id, RequestId}, + ?hcrv("cancel", [{request_id, RequestId}, + {curr_req_id, CurrId}, {profile, ProfileName}, {canceled, Canceled}]), - httpc_manager:request_canceled(RequestId, ProfileName), + httpc_manager:request_canceled(RequestId, ProfileName, From), ?hcrv("canceled", []), {noreply, State#state{canceled = [RequestId | Canceled]}}; @@ -872,62 +846,55 @@ connect(SocketType, ToAddress, Opts3 = [IpFamily | Opts2], http_transport:connect(SocketType, ToAddress, Opts3, Timeout) end. - -connect_and_send_first_request(Address, - #request{settings = Settings, - headers = Headers, - address = OrigAddress, - scheme = Scheme} = Request, - #state{options = Options} = State) -> - - ?hcrd("connect", - [{address, Address}, {request, Request}, {options, Options}]), +connect_and_send_first_request(Address, Request, #state{options = Options} = State) -> SocketType = socket_type(Request), - ConnTimeout = Settings#http_options.connect_timeout, + ConnTimeout = (Request#request.settings)#http_options.connect_timeout, + ?hcri("connect", + [{address, Address}, {request, Request}, {options, Options}]), case connect(SocketType, Address, Options, ConnTimeout) of {ok, Socket} -> - Session = #session{id = {OrigAddress, self()}, - scheme = Scheme, - socket = Socket, - socket_type = SocketType}, - ?hcrd("connected - now send first request", [{socket, Socket}]), + ClientClose = + httpc_request:is_client_closing( + Request#request.headers), + SessionType = httpc_manager:session_type(Options), + SocketType = socket_type(Request), + Session = #session{id = {Request#request.address, self()}, + scheme = Request#request.scheme, + socket = Socket, + socket_type = SocketType, + client_close = ClientClose, + type = SessionType}, + ?hcri("connected - now send first request", [{socket, Socket}]), + case httpc_request:send(Address, Session, Request) of ok -> - ?hcrd("first request sent", []), - ClientClose = - httpc_request:is_client_closing(Headers), - SessionType = httpc_manager:session_type(Options), - Session2 = - Session#session{client_close = ClientClose, - type = SessionType}, - TmpState = - State#state{request = Request, - session = Session2, - mfa = init_mfa(Request, State), - status_line = init_status_line(Request), - headers = undefined, - body = undefined, - status = new}, - ?hcrt("activate socket", []), - activate_once(Session), + ?hcri("first request sent", []), + TmpState = State#state{request = Request, + session = Session, + mfa = init_mfa(Request, State), + status_line = + init_status_line(Request), + headers = undefined, + body = undefined, + status = new}, + http_transport:setopts(SocketType, + Socket, [{active, once}]), NewState = activate_request_timeout(TmpState), {ok, NewState}; - - {error, Reason} = Error -> - ?hcrv("failed sending request", [{reason, Reason}]), - {stop, Error, - State#state{session = {send_failed, Reason}, - request = Request}} + {error, Reason} -> + self() ! {init_error, error_sending, + httpc_response:error(Request, Reason)}, + {ok, State#state{request = Request, + session = + #session{socket = Socket}}} end; - - {error, Reason} = Error -> - ?hcri("connect failed", [{reason, Reason}]), - {stop, Error, State#state{session = {connect_failed, Reason}, - request = Request}} + {error, Reason} -> + self() ! {init_error, error_connecting, + httpc_response:error(Request, Reason)}, + {ok, State#state{request = Request}} end. - handler_info(#state{request = Request, session = Session, status_line = _StatusLine, @@ -1167,12 +1134,12 @@ handle_response(#state{request = Request, {ok, Msg, Data} -> ?hcrd("handle response - ok", []), end_stream(StatusLine, Request), - NewState = answer_request(Request, Msg, State), + NewState = maybe_send_answer(Request, Msg, State), handle_queue(NewState, Data); {stop, Msg} -> ?hcrd("handle response - stop", [{msg, Msg}]), end_stream(StatusLine, Request), - NewState = answer_request(Request, Msg, State), + NewState = maybe_send_answer(Request, Msg, State), {stop, normal, NewState} end. @@ -1242,7 +1209,8 @@ handle_pipeline(#state{status = pipeline, %% See comment for handle_cast({cancel, RequestId}) {stop, normal, State#state{request = - NextRequest#request{from = answer_sent}}}; + NextRequest#request{from = answer_sent}, + pipeline = Pipeline}}; false -> ?hcrv("next request", [{request, NextRequest}]), NewSession = @@ -1443,6 +1411,7 @@ answer_request(#request{id = RequestId, from = From} = Request, Msg, Timer = {RequestId, TimerRef}, cancel_timer(TimerRef, {timeout, Request#request.id}), httpc_manager:request_done(RequestId, ProfileName), + State#state{request = Request#request{from = answer_sent}, timers = Timers#timers{request_timers = @@ -1662,67 +1631,6 @@ 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}}, -%% State) -> -%% %% A ssl tunnel request is a special http request that looks like -%% %% CONNECT host:port HTTP/1.1 -%% SslTunnelRequest = #request{method = connect, scheme = http, -%% headers = -%% #http_request_h{ -%% host = Host, -%% address = Address, -%% path = Host ++ ":", -%% pquery = integer_to_list(Port), -%% other = [{ "Proxy-Connection", "keep-alive"}]}, -%% Ipv6 = (State#state.options)#options.ipv6, -%% SocketType = socket_type(SslTunnelRequest), -%% case http_transport:connect(SocketType, -%% SslTunnelRequest#request.address, Ipv6) of -%% {ok, Socket} -> -%% case httpc_request:send(Address, SslTunnelRequest, Socket) of -%% ok -> -%% Session = #tcp_session{id = -%% {SslTunnelRequest#request.address, -%% self()}, -%% scheme = -%% SslTunnelRequest#request.scheme, -%% socket = Socket}, -%% NewState = State#state{mfa = -%% {httpc_response, parse, -%% [State#state.max_header_size]}, -%% request = Request, -%% session = Session}, -%% http_transport:setopts(socket_type( -%% SslTunnelRequest#request.scheme), -%% Socket, -%% [{active, once}]), -%% {ok, NewState}; -%% {error, Reason} -> -%% self() ! {init_error, error_sending, -%% httpc_response:error(Request, Reason)}, -%% {ok, State#state{request = Request, -%% session = #tcp_session{socket = -%% Socket}}} -%% end; -%% {error, Reason} -> -%% self() ! {init_error, error_connecting, -%% httpc_response:error(Request, Reason)}, -%% {ok, State#state{request = Request}} -%% end. - -%% d(F) -> -%% d(F, []). - -%% d(F, A) -> -%% d(get(dbg), F, A). - -%% d(true, F, A) -> -%% io:format(user, "~w:~w:" ++ F ++ "~n", [self(), ?MODULE | A]); -%% d(_, _, _) -> -%% ok. - send_raw(#session{socket = Socket, socket_type = SocketType}, {ProcessBody, Acc}) when is_function(ProcessBody, 1) -> @@ -1756,11 +1664,5 @@ call(Msg, Pid, Timeout) -> cast(Msg, Pid) -> gen_server:cast(Pid, Msg). - -%% to(To, Start) when is_integer(Start) andalso (Start >= 0) -> -%% http_util:timeout(To, Start); -%% to(To, _Start) -> -%% http_util:timeout(To, t()). - t() -> http_util:timestamp(). diff --git a/lib/inets/src/http_client/httpc_handler_sup.erl b/lib/inets/src/http_client/httpc_handler_sup.erl index 2a69fd15d0..f7a0b014b3 100644 --- a/lib/inets/src/http_client/httpc_handler_sup.erl +++ b/lib/inets/src/http_client/httpc_handler_sup.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2007-2010. All Rights Reserved. +%% Copyright Ericsson AB 2007-2011. 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 @@ -23,7 +23,7 @@ %% API -export([start_link/0]). --export([start_child/2]). +-export([start_child/1]). %% Supervisor callback -export([init/1]). @@ -34,11 +34,9 @@ start_link() -> supervisor:start_link({local, ?MODULE}, ?MODULE, []). -start_child(Options, Profile) -> - Args = [Options, Profile], +start_child(Args) -> supervisor:start_child(?MODULE, Args). - %%%========================================================================= %%% Supervisor callback %%%========================================================================= diff --git a/lib/inets/src/http_client/httpc_manager.erl b/lib/inets/src/http_client/httpc_manager.erl index 591cb78c29..7f66b477eb 100644 --- a/lib/inets/src/http_client/httpc_manager.erl +++ b/lib/inets/src/http_client/httpc_manager.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% Copyright Ericsson AB 2002-2011. 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 @@ -29,7 +29,7 @@ start_link/3, request/2, cancel_request/2, - request_canceled/2, + request_canceled/3, request_done/2, retry_request/2, redirect_request/2, @@ -66,6 +66,7 @@ state % State of the handler: initiating | started | operational | canceled }). +-define(DELAY, 500). %%==================================================================== %% Internal Application API @@ -158,7 +159,8 @@ cancel_request(RequestId, ProfileName) -> %% be called by the httpc handler process. %%-------------------------------------------------------------------- -request_canceled(RequestId, ProfileName) -> +request_canceled(RequestId, ProfileName, From) -> + gen_server:reply(From, ok), cast(ProfileName, {request_canceled, RequestId}). @@ -355,44 +357,32 @@ do_init(ProfileName, CookiesDir) -> %% {stop, Reason, State} (terminate/2 is called) %% Description: Handling call messages %%-------------------------------------------------------------------- -handle_call({request, Request}, _From, State) -> - ?hcrv("request", [{request, Request}]), +handle_call({request, Request}, _, State) -> + ?hcri("request", [{request, Request}]), case (catch handle_request(Request, State)) of - {ok, ReqId, NewState} -> - {reply, {ok, ReqId}, NewState}; - + {reply, Msg, NewState} -> + {reply, Msg, NewState}; Error -> - NewError = {error, {failed_process_request, Error}}, - {reply, NewError, State} + {stop, Error, httpc_response:error(Request, Error), State} end; - -handle_call({cancel_request, RequestId}, From, - #state{handler_db = HandlerDb} = State) -> - ?hcrv("cancel_request", [{request_id, RequestId}]), + +handle_call({cancel_request, RequestId}, From, State) -> + ?hcri("cancel_request", [{request_id, RequestId}]), case ets:lookup(State#state.handler_db, RequestId) of [] -> - ?hcrd("nothing to cancel", []), - Reply = ok, %% Nothing to cancel - {reply, Reply, State}; - - [#handler_info{handler = Pid}] when is_pid(Pid) -> - ?hcrd("found operational handler for this request", - [{handler, Pid}]), - httpc_handler:cancel(RequestId, Pid), - {noreply, State#state{cancel = - [{RequestId, Pid, From} | - State#state.cancel]}}; - - [#handler_info{starter = Pid, state = HandlerState}] - when is_pid(Pid) -> - ?hcri("found *initiating* handler for this request", - [{starter, Pid}, {state, HandlerState}]), - ets:update_element(HandlerDb, RequestId, - {#handler_info.state, canceled}), + %% The request has allready compleated make sure + %% it is deliverd to the client process queue so + %% it can be thrown away by httpc:cancel_request + %% This delay is hopfully a temporary workaround. + %% Note that it will not not delay the manager, + %% only the client that called httpc:cancel_request + timer:apply_after(?DELAY, gen_server, reply, [From, ok]), + {noreply, State}; + [{_, Pid, _}] -> + httpc_handler:cancel(RequestId, Pid, From), {noreply, State#state{cancel = - [{RequestId, Pid, From} | + [{RequestId, Pid, From} | State#state.cancel]}} - end; handle_call(reset_cookies, _, #state{cookie_db = CookieDb} = State) -> @@ -437,43 +427,16 @@ handle_call(Req, From, #state{profile_name = ProfileName} = State) -> %%-------------------------------------------------------------------- handle_cast({retry_or_redirect_request, {Time, Request}}, #state{profile_name = ProfileName} = State) -> - ?hcrv("retry or redirect request", [{time, Time}, {request, Request}]), - case timer:apply_after(Time, ?MODULE, retry_request, - [Request, ProfileName]) of - {ok, _} -> - {noreply, State}; - {error, Reason} -> - error_report(ProfileName, - "failed scheduling retry/redirect request" - "~n Time: ~p" - "~n Request: ~p" - "~n Reason: ~p", [Time, Request, Reason]), - {noreply, State} - end; + {ok, _} = timer:apply_after(Time, ?MODULE, retry_request, [Request, ProfileName]), + {noreply, State}; -handle_cast({retry_or_redirect_request, Request}, - #state{profile_name = Profile, - handler_db = HandlerDb} = State) -> - ?hcrv("retry or redirect request", [{request, Request}]), +handle_cast({retry_or_redirect_request, Request}, State) -> case (catch handle_request(Request, State)) of - {ok, _, NewState} -> + {reply, {ok, _}, NewState} -> {noreply, NewState}; - Error -> - 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} + httpc_response:error(Request, Error), + {stop, Error, State} end; handle_cast({request_canceled, RequestId}, State) -> @@ -482,7 +445,6 @@ handle_cast({request_canceled, RequestId}, State) -> case lists:keysearch(RequestId, 1, State#state.cancel) of {value, Entry = {RequestId, _, From}} -> ?hcrt("found in cancel", [{from, From}]), - gen_server:reply(From, ok), {noreply, State#state{cancel = lists:delete(Entry, State#state.cancel)}}; Else -> @@ -539,8 +501,6 @@ handle_cast(Msg, #state{profile_name = ProfileName} = State) -> "recived unknown message" "~n Msg: ~p", [Msg]), {noreply, State}. - - %%-------------------------------------------------------------------- %% Function: handle_info(Info, State) -> {noreply, State} | @@ -548,39 +508,17 @@ handle_cast(Msg, #state{profile_name = ProfileName} = State) -> %% {stop, Reason, State} (terminate/2 is called) %% Description: Handling all non call/cast messages %%--------------------------------------------------------- - -handle_info({started, StarterPid, ReqId, HandlerPid}, State) -> - handle_started(StarterPid, ReqId, HandlerPid, State), - {noreply, State}; - -handle_info({connect_and_send, StarterPid, ReqId, HandlerPid, Res}, State) -> - handle_connect_and_send(StarterPid, ReqId, HandlerPid, Res, State), - {noreply, State}; - -handle_info({failed_starting_handler, StarterPid, ReqId, Res}, State) -> - handle_failed_starting_handler(StarterPid, ReqId, Res, State), - {noreply, State}; - -handle_info({'EXIT', Pid, Reason}, #state{handler_db = HandlerDb} = State) -> - maybe_handle_terminating_starter(Pid, Reason, HandlerDb), +handle_info({'EXIT', _, _}, State) -> + %% Handled in DOWN {noreply, State}; - handle_info({'DOWN', _, _, Pid, _}, State) -> - - %% - %% Normally this should have been cleaned up already - %% (when receiving {request_done, PequestId}), but - %% just in case there is a glitch, cleanup anyway. - %% - - Pattern = #handler_info{handler = Pid, _ = '_'}, - ets:match_delete(State#state.handler_db, Pattern), + ets:match_delete(State#state.handler_db, {'_', Pid, '_'}), %% If there where any canceled request, handled by the %% the process that now has terminated, the %% cancelation can be viewed as sucessfull! - NewCanceledList = - lists:foldl(fun({_, HandlerPid, From} = Entry, Acc) -> + NewCanceldList = + lists:foldl(fun(Entry = {_, HandlerPid, From}, Acc) -> case HandlerPid of Pid -> gen_server:reply(From, ok), @@ -589,15 +527,13 @@ handle_info({'DOWN', _, _, Pid, _}, State) -> Acc end end, State#state.cancel, State#state.cancel), - {noreply, State#state{cancel = NewCanceledList}}; - -handle_info(Info, #state{profile_name = ProfileName} = State) -> - error_report(ProfileName, - "received unknown info" - "~n Info: ~p", [Info]), + {noreply, State#state{cancel = NewCanceldList}}; +handle_info(Info, State) -> + Report = io_lib:format("Unknown message in " + "httpc_manager:handle_info ~p~n", [Info]), + error_logger:error_report(Report), {noreply, State}. - %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> _ (ignored by gen_server) %% Description: Shutdown the httpc_handler @@ -655,224 +591,79 @@ get_handler_info(Tab) -> {Pid, State} <- Handlers2], Handlers3. - -%% -%% The request handler process is started asynchronously by a -%% "starter process". When the handler has sucessfully been started, -%% this message (started) is sent. -%% - -handle_started(StarterPid, ReqId, HandlerPid, - #state{profile_name = Profile, - handler_db = HandlerDb}) -> - case ets:lookup(HandlerDb, ReqId) of - [#handler_info{state = initiating} = HandlerInfo] -> - ?hcri("received started ack for initiating handler", []), - %% As a last resort, make sure we know when it exits, - %% in case it forgets to notify us. - %% We dont need to know the ref id? - erlang:monitor(process, HandlerPid), - HandlerInfo2 = HandlerInfo#handler_info{handler = HandlerPid, - state = started}, - ets:insert(HandlerDb, HandlerInfo2), - ok; - - [#handler_info{state = State}] -> - error_report(Profile, - "unexpected (started) message for handler (~p) in state " - "~p regarding request ~p - ignoring", [HandlerPid, State, ReqId]), - ?hcri("received unexpected started message", [{state, State}]), - ok; - - [] -> - error_report(Profile, - "unknown handler ~p (~p) started for request ~w - canceling", - [HandlerPid, StarterPid, ReqId]), - httpc_handler:cancel(ReqId, HandlerPid) - end. - - -%% -%% The request handler process is started asynchronously by a -%% "starter process". When that process terminates it sends -%% one of two messages. These ara handled by the two functions -%% below. -%% - -handle_connect_and_send(_StarterPid, ReqId, HandlerPid, Result, - #state{profile_name = Profile, - handler_db = HandlerDb}) -> - case ets:lookup(HandlerDb, ReqId) of - [#handler_info{state = started} = HandlerInfo] when Result =:= ok -> - ?hcri("received connect-and-send ack for started handler", []), - HandlerInfo2 = HandlerInfo#handler_info{starter = undefined, - handler = HandlerPid, - state = operational}, - ets:insert(HandlerDb, HandlerInfo2), - ok; - - [#handler_info{state = canceled} = HandlerInfo] when Result =:= ok -> - ?hcri("received connect-and-send ack for canceled handler", []), - httpc_handler:cancel(ReqId, HandlerPid), - HandlerInfo2 = HandlerInfo#handler_info{starter = undefined, - handler = HandlerPid}, - ets:insert(HandlerDb, HandlerInfo2), - ok; - - [#handler_info{state = State}] when Result =/= ok -> - error_report(Profile, - "handler (~p, ~w) failed to connect and/or " - "send request ~p" - "~n Result: ~p", - [HandlerPid, State, ReqId, Result]), - ?hcri("received connect-and-send error", - [{result, Result}, {state, State}]), - %% We don't need to send a response to the original caller - %% because the handler already sent one in its terminate - %% function. - ets:delete(HandlerDb, ReqId), - ok; - - [] -> - ?hcri("handler successfully started " - "for unknown request => canceling", - [{profile, Profile}, - {handler, HandlerPid}, - {request, ReqId}]), - httpc_handler:cancel(ReqId, HandlerPid) - end. - - -handle_failed_starting_handler(_StarterPid, ReqId, Error, - #state{profile_name = Profile, - handler_db = HandlerDb}) -> - case ets:lookup(HandlerDb, ReqId) of - [#handler_info{state = canceled}] -> - error_report(Profile, - "failed starting handler for request ~p" - "~n Error: ~p", [ReqId, Error]), - request_canceled(Profile, ReqId), % Fake signal from handler - ets:delete(HandlerDb, ReqId), - ok; - - [#handler_info{from = From}] -> - error_report(Profile, - "failed starting handler for request ~p" - "~n Error: ~p", [ReqId, Error]), - Reason2 = - case Error of - {error, Reason} -> - {failed_connecting, Reason}; - _ -> - {failed_connecting, Error} - end, - DummyReq = #request{id = ReqId}, - httpc_response:send(From, httpc_response:error(DummyReq, Reason2)), - ets:delete(HandlerDb, ReqId), - ok; - - [] -> - error_report(Profile, - "failed starting handler for unknown request ~p" - "~n Error: ~p", [ReqId, Error]), - ok - end. - - -maybe_handle_terminating_starter(MeybeStarterPid, Reason, HandlerDb) -> - Pattern = #handler_info{starter = MeybeStarterPid, _ = '_'}, - case ets:match_object(HandlerDb, Pattern) of - [#handler_info{id = ReqId, from = From, state = initiating}] -> - %% The starter process crashed before it could start the - %% the handler process, therefor we need to answer the - %% original caller. - ?hcri("starter process crashed bfore starting handler", - [{starter, MeybeStarterPid}, {reason, Reason}]), - Reason2 = - case Reason of - {error, Error} -> - {failed_connecting, Error}; - _ -> - {failed_connecting, Reason} - end, - DummyReq = #request{id = ReqId}, - httpc_response:send(From, httpc_response:error(DummyReq, Reason2)), - ets:delete(HandlerDb, ReqId), - ok; - - [#handler_info{state = State} = HandlerInfo] -> - %% The starter process crashed after the handler was started. - %% The handler will answer to the original caller. - ?hcri("starter process crashed after starting handler", - [{starter, MeybeStarterPid}, {reason, Reason}, {state, State}]), - HandlerInfo2 = HandlerInfo#handler_info{starter = undefined}, - ets:insert(HandlerDb, HandlerInfo2), - ok; - - _ -> - ok - end. - - -%% ----- -%% Act as an HTTP/0.9 client that does not know anything -%% about persistent connections handle_request(#request{settings = - #http_options{version = "HTTP/0.9"}} = Request0, + #http_options{version = "HTTP/0.9"}} = Request, State) -> - Request1 = handle_cookies(generate_request_id(Request0), State), - Hdrs0 = Request1#request.headers, - Hdrs1 = Hdrs0#http_request_h{connection = undefined}, - Request2 = Request1#request{headers = Hdrs1}, - create_handler_starter(Request2, State), - {ok, Request2#request.id, State}; - -%% ----- -%% Act as an HTTP/1.0 client that does not -%% use persistent connections + %% Act as an HTTP/0.9 client that does not know anything + %% about persistent connections + + NewRequest = handle_cookies(generate_request_id(Request), State), + NewHeaders = + (NewRequest#request.headers)#http_request_h{connection + = undefined}, + start_handler(NewRequest#request{headers = NewHeaders}, State), + {reply, {ok, NewRequest#request.id}, State}; + handle_request(#request{settings = - #http_options{version = "HTTP/1.0"}} = Request0, + #http_options{version = "HTTP/1.0"}} = Request, State) -> - Request1 = handle_cookies(generate_request_id(Request0), State), - Hdrs0 = Request1#request.headers, - Hdrs1 = Hdrs0#http_request_h{connection = "close"}, - Request2 = Request1#request{headers = Hdrs1}, - create_handler_starter(Request2, State), - {ok, Request2#request.id, State}; - - -%% ----- -handle_request(#request{method = Method, - address = Address, - scheme = Scheme} = Request0, - #state{options = Opts} = State) -> - Request1 = handle_cookies(generate_request_id(Request0), State), - SessionType = session_type(Opts), - case select_session(Method, Address, Scheme, SessionType, State) of + %% Act as an HTTP/1.0 client that does not + %% use persistent connections + + NewRequest = handle_cookies(generate_request_id(Request), State), + NewHeaders = + (NewRequest#request.headers)#http_request_h{connection + = "close"}, + start_handler(NewRequest#request{headers = NewHeaders}, State), + {reply, {ok, NewRequest#request.id}, State}; + +handle_request(Request, State = #state{options = Options}) -> + + NewRequest = handle_cookies(generate_request_id(Request), State), + SessionType = session_type(Options), + case select_session(Request#request.method, + Request#request.address, + Request#request.scheme, SessionType, State) of {ok, HandlerPid} -> - pipeline_or_keep_alive(Request1, HandlerPid, State); + pipeline_or_keep_alive(NewRequest, HandlerPid, State); no_connection -> - create_handler_starter(Request1, State); - {no_session, OpenSessions} - when OpenSessions < Opts#options.max_sessions -> - create_handler_starter(Request1, State); + start_handler(NewRequest, State); + {no_session, OpenSessions} when OpenSessions + < Options#options.max_sessions -> + start_handler(NewRequest, State); {no_session, _} -> %% Do not start any more persistent connections %% towards this server. - Hdrs0 = Request1#request.headers, - Hdrs1 = Hdrs0#http_request_h{connection = "close"}, - Request2 = Request1#request{headers = Hdrs1}, - create_handler_starter(Request2, State) + NewHeaders = + (NewRequest#request.headers)#http_request_h{connection + = "close"}, + start_handler(NewRequest#request{headers = NewHeaders}, State) end, - {ok, Request1#request.id, State}. + {reply, {ok, NewRequest#request.id}, State}. + + +start_handler(Request, State) -> + {ok, Pid} = + case is_inets_manager() of + true -> + httpc_handler_sup:start_child([whereis(httpc_handler_sup), + Request, State#state.options, + State#state.profile_name]); + false -> + httpc_handler:start_link(self(), Request, State#state.options, + State#state.profile_name) + end, + ets:insert(State#state.handler_db, {Request#request.id, + Pid, Request#request.from}), + erlang:monitor(process, Pid). select_session(Method, HostPort, Scheme, SessionType, #state{options = #options{max_pipeline_length = MaxPipe, max_keep_alive_length = MaxKeepAlive}, session_db = SessionDb}) -> - ?hcrd("select session", [{session_type, SessionType}, - {max_pipeline_length, MaxPipe}, + ?hcrd("select session", [{session_type, SessionType}, + {max_pipeline_length, MaxPipe}, {max_keep_alive_length, MaxKeepAlive}]), case httpc_request:is_idempotent(Method) orelse (SessionType =:= keep_alive) of @@ -918,92 +709,17 @@ select_session(Candidates, Max) -> ?hcrd("select session - found one", [{handler, HandlerPid}]), {ok, HandlerPid} end. - -pipeline_or_keep_alive(#request{id = Id} = Request, HandlerPid, State) -> - ?hcrd("pipeline of keep-alive", [{id, Id}, {handler, HandlerPid}]), + +pipeline_or_keep_alive(Request, HandlerPid, State) -> case (catch httpc_handler:send(Request, HandlerPid)) of ok -> - ?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 or keep-alive - failed sending -> " - "start a new handler", []), - create_handler_starter(Request, State) + ets:insert(State#state.handler_db, {Request#request.id, + HandlerPid, + Request#request.from}); + _ -> %timeout pipelining failed + start_handler(Request, State) end. - -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) -> - ?hcrv("create handler starter", [{id, Id}, {profile, ProfileName}]), - IsInetsManager = is_inets_manager(), - ManagerPid = self(), - StarterFun = - fun() -> - ?hcrd("handler starter - start", - [{id, Id}, - {profile, ProfileName}, - {inets_manager, IsInetsManager}]), - Result1 = - case IsInetsManager of - true -> - httpc_handler_sup:start_child(Options, - ProfileName); - false -> - httpc_handler:start_link(Options, - ProfileName) - end, - ?hcrd("handler starter - maybe connect and send", - [{id, Id}, {profile, ProfileName}, {result, Result1}]), - case Result1 of - {ok, HandlerPid} -> - StartedMessage = - {started, self(), Id, HandlerPid}, - ManagerPid ! StartedMessage, - Result2 = httpc_handler:connect_and_send(Request, - HandlerPid), - ?hcrd("handler starter - connected and sent", - [{id, Id}, {profile, ProfileName}, - {handler, HandlerPid}, {result, Result2}]), - ConnAndSendMessage = - {connect_and_send, - self(), Id, HandlerPid, Result2}, - ManagerPid ! ConnAndSendMessage; - {error, Reason} -> - StartFailureMessage = - {failed_starting_handler, self(), Id, Reason}, - ManagerPid ! StartFailureMessage; - _ -> - StartFailureMessage = - {failed_starting_handler, self(), Id, Result1}, - ManagerPid ! StartFailureMessage - end - end, - Starter = erlang:spawn_link(StarterFun), - ?hcrd("create handler starter - started", [{id, Id}, {starter, Starter}]), - Entry = #handler_info{id = Id, - starter = Starter, - from = From, - state = initiating}, - ets:insert(HandlerDb, Entry), - ok. - - is_inets_manager() -> case get('$ancestors') of [httpc_profile_sup | _] -> @@ -1045,8 +761,6 @@ do_store_cookies([Cookie | Cookies], #state{cookie_db = CookieDb} = State) -> ok = httpc_cookie:insert(CookieDb, Cookie), do_store_cookies(Cookies, State). - - session_db_name(ProfileName) -> make_db_name(ProfileName, "__session_db"). @@ -1074,7 +788,6 @@ cast(ProfileName, Msg) -> gen_server:cast(ProfileName, Msg). - get_proxy(Opts, #options{proxy = Default}) -> proplists:get_value(proxy, Opts, Default). @@ -1133,20 +846,6 @@ handle_verbose(trace) -> handle_verbose(_) -> ok. - error_report(Profile, F, A) -> Report = io_lib:format("HTTPC-MANAGER<~p> " ++ F ++ "~n", [Profile | A]), error_logger:error_report(Report). - - -%% d(F) -> -%% d(F, []). - -%% d(F, A) -> -%% d(get(dbg), F, A). - -%% d(true, F, A) -> -%% io:format(user, "~w:~w:" ++ F ++ "~n", [self(), ?MODULE | A]); -%% d(_, _, _) -> -%% ok. - diff --git a/lib/inets/src/http_client/httpc_request.erl b/lib/inets/src/http_client/httpc_request.erl index 0d602adb11..879053f0f2 100644 --- a/lib/inets/src/http_client/httpc_request.erl +++ b/lib/inets/src/http_client/httpc_request.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2004-2010. All Rights Reserved. +%% Copyright Ericsson AB 2004-2011. 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 @@ -120,7 +120,7 @@ do_send_body(SocketType, Socket, Method, Uri, Version, Headers, Body) -> version(Version), ?CRLF, headers(Headers, Version), ?CRLF, Body], ?hcrd("send", [{message, Message}]), - http_transport:send(SocketType, Socket, lists:append(Message)). + http_transport:send(SocketType, Socket, Message). do_send_body(SocketType, Socket, ProcessBody, Acc) -> @@ -128,9 +128,7 @@ do_send_body(SocketType, Socket, ProcessBody, Acc) -> eof -> ok; {ok, Data, NewAcc} -> - DataBin = iolist_to_binary(Data), - ?hcrd("send", [{data, DataBin}]), - case http_transport:send(SocketType, Socket, DataBin) of + case http_transport:send(SocketType, Socket, Data) of ok -> do_send_body(SocketType, Socket, ProcessBody, NewAcc); Error -> diff --git a/lib/inets/src/inets_app/inets.appup.src b/lib/inets/src/inets_app/inets.appup.src index 0a590c9c36..91fb064eec 100644 --- a/lib/inets/src/inets_app/inets.appup.src +++ b/lib/inets/src/inets_app/inets.appup.src @@ -20,38 +20,12 @@ [ {"5.5.2", [ - {load_module, ftp, soft_purge, soft_purge, []}, - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, http_transport, soft_purge, soft_purge, []}, - {load_module, httpd_util, soft_purge, soft_purge, [http_util]}, - {load_module, httpd_conf, soft_purge, soft_purge, []}, - {load_module, httpd_file, soft_purge, soft_purge, []}, - {load_module, httpd_log, soft_purge, soft_purge, []}, - {load_module, mod_esi, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, [httpc_handler]}, - {load_module, httpc_request, soft_purge, soft_purge, []}, - {update, httpd_sup, soft, soft_purge, soft_purge, [http_transport]}, - {update, httpd_request_handler, soft, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_request]} + {restart_application, inets} ] }, {"5.5.1", [ - {load_module, ftp, soft_purge, soft_purge, []}, - {load_module, http_chunk, soft_purge, soft_purge, []}, - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, http_transport, soft_purge, soft_purge, []}, - {load_module, httpd_util, soft_purge, soft_purge, [http_util]}, - {load_module, httpd_conf, soft_purge, soft_purge, []}, - {load_module, httpd_file, soft_purge, soft_purge, []}, - {load_module, httpd_log, soft_purge, soft_purge, []}, - {load_module, mod_esi, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, [httpc_handler]}, - {load_module, httpc_request, soft_purge, soft_purge, []}, - {update, httpd_sup, soft, soft_purge, soft_purge, [http_transport]}, - {update, httpd_request_handler, soft, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, - [httpc_request, http_chunk]} + {restart_application, inets} ] }, {"5.5", @@ -68,38 +42,12 @@ [ {"5.5.2", [ - {load_module, ftp, soft_purge, soft_purge, []}, - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, http_transport, soft_purge, soft_purge, []}, - {load_module, httpd_util, soft_purge, soft_purge, [http_util]}, - {load_module, httpd_conf, soft_purge, soft_purge, []}, - {load_module, httpd_file, soft_purge, soft_purge, []}, - {load_module, httpd_log, soft_purge, soft_purge, []}, - {load_module, mod_esi, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, [httpc_handler]}, - {load_module, httpc_request, soft_purge, soft_purge, []}, - {update, httpd_sup, soft, soft_purge, soft_purge, [http_transport]}, - {update, httpd_request_handler, soft, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, [httpc_request]} + {restart_application, inets} ] }, {"5.5.1", [ - {load_module, ftp, soft_purge, soft_purge, []}, - {load_module, http_chunk, soft_purge, soft_purge, []}, - {load_module, http_util, soft_purge, soft_purge, []}, - {load_module, http_transport, soft_purge, soft_purge, []}, - {load_module, httpd_util, soft_purge, soft_purge, [http_util]}, - {load_module, httpd_conf, soft_purge, soft_purge, []}, - {load_module, httpd_file, soft_purge, soft_purge, []}, - {load_module, httpd_log, soft_purge, soft_purge, []}, - {load_module, mod_esi, soft_purge, soft_purge, []}, - {load_module, httpc, soft_purge, soft_purge, [httpc_handler]}, - {load_module, httpc_request, soft_purge, soft_purge, []}, - {update, httpd_sup, soft, soft_purge, soft_purge, [http_transport]}, - {update, httpd_request_handler, soft, soft_purge, soft_purge, []}, - {update, httpc_handler, soft, soft_purge, soft_purge, - [httpc_request, http_chunk]} + {restart_application, inets} ] }, {"5.5", diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index 81e9c2b230..7607bc9eb6 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -115,7 +115,8 @@ all() -> options, ipv6, headers_as_is, - {group, tickets} + {group, tickets}, + initial_server_connect ]. groups() -> @@ -140,6 +141,7 @@ groups() -> otp_8106_fun, otp_8106_mfa]}]. + init_per_group(_GroupName, Config) -> Config. @@ -199,7 +201,6 @@ end_per_suite(Config) -> application:stop(ssl), ok. - %%-------------------------------------------------------------------- %% Function: init_per_testcase(Case, Config) -> Config %% Case - atom() @@ -214,6 +215,15 @@ end_per_suite(Config) -> %%-------------------------------------------------------------------- init_per_testcase(otp_8154_1 = Case, Config) -> init_per_testcase(Case, 5, Config); + +init_per_testcase(initial_server_connect, Config) -> + inets:start(), + application:start(crypto), + application:start(public_key), + application:start(ssl), + application:start(inets), + Config; + init_per_testcase(Case, Config) -> init_per_testcase(Case, 2, Config). @@ -601,34 +611,35 @@ http_inets_pipe(Config) when is_list(Config) -> {skip, "Failed to start local http-server"} end. + test_pipeline(URL) -> - p("test_pipeline -> entry with" - "~n URL: ~p", [URL]), + p("test_pipeline -> entry with" + "~n URL: ~p", [URL]), - httpc:set_options([{pipeline_timeout, 50000}]), - - p("test_pipeline -> issue (async) request 1"), - {ok, RequestId1} = + httpc:set_options([{pipeline_timeout, 50000}]), + + p("test_pipeline -> issue (async) request 1"), + {ok, RequestId1} = httpc:request(get, {URL, []}, [], [{sync, false}]), - test_server:format("RequestId1: ~p~n", [RequestId1]), - p("test_pipeline -> RequestId1: ~p", [RequestId1]), + test_server:format("RequestId1: ~p~n", [RequestId1]), + p("test_pipeline -> RequestId1: ~p", [RequestId1]), - %% Make sure pipeline is initiated - p("test_pipeline -> sleep some", []), - test_server:sleep(4000), + %% Make sure pipeline is initiated + p("test_pipeline -> sleep some", []), + test_server:sleep(4000), - p("test_pipeline -> issue (async) request 2"), - {ok, RequestId2} = + p("test_pipeline -> issue (async) request 2"), + {ok, RequestId2} = httpc:request(get, {URL, []}, [], [{sync, false}]), - tsp("RequestId2: ~p", [RequestId2]), - p("test_pipeline -> RequestId2: ~p", [RequestId2]), + tsp("RequestId2: ~p", [RequestId2]), + p("test_pipeline -> RequestId2: ~p", [RequestId2]), - p("test_pipeline -> issue (sync) request 3"), - {ok, {{_,200,_}, [_ | _], [_ | _]}} = + p("test_pipeline -> issue (sync) request 3"), + {ok, {{_,200,_}, [_ | _], [_ | _]}} = httpc:request(get, {URL, []}, [], []), p("test_pipeline -> expect reply for (async) request 1 or 2"), - receive + receive {http, {RequestId1, {{_, 200, _}, _, _}}} -> p("test_pipeline -> received reply for (async) request 1 - now wait for 2"), receive @@ -646,46 +657,46 @@ test_pipeline(URL) -> ok; {http, Msg2} -> test_server:fail(Msg2) - end; + end; {http, Msg3} -> test_server:fail(Msg3) - after 60000 -> - receive Any1 -> - tsp("received crap after timeout: ~n ~p", [Any1]), - test_server:fail({error, {timeout, Any1}}) - end + after 60000 -> + receive Any1 -> + tsp("received crap after timeout: ~n ~p", [Any1]), + test_server:fail({error, {timeout, Any1}}) + end end, - - p("test_pipeline -> sleep some"), - test_server:sleep(4000), - p("test_pipeline -> issue (async) request 4"), - {ok, RequestId3} = - httpc:request(get, {URL, []}, [], [{sync, false}]), - tsp("RequestId3: ~p", [RequestId3]), - p("test_pipeline -> RequestId3: ~p", [RequestId3]), + p("test_pipeline -> sleep some"), + test_server:sleep(4000), - p("test_pipeline -> issue (async) request 5"), - {ok, RequestId4} = + p("test_pipeline -> issue (async) request 4"), + {ok, RequestId3} = httpc:request(get, {URL, []}, [], [{sync, false}]), - tsp("RequestId4: ~p~n", [RequestId4]), - p("test_pipeline -> RequestId4: ~p", [RequestId4]), - - p("test_pipeline -> cancel (async) request 4"), - ok = httpc:cancel_request(RequestId3), - - p("test_pipeline -> expect *no* reply for cancelled (async) request 4 (for 3 secs)"), - receive - {http, {RequestId3, _}} -> - test_server:fail(http_cancel_request_failed) - after 3000 -> - ok - end, + tsp("RequestId3: ~p", [RequestId3]), + p("test_pipeline -> RequestId3: ~p", [RequestId3]), - p("test_pipeline -> expect reply for (async) request 4"), - Body = - receive - {http, {RequestId4, {{_, 200, _}, _, BinBody4}}} = Res -> + p("test_pipeline -> issue (async) request 5"), + {ok, RequestId4} = + httpc:request(get, {URL, []}, [], [{sync, false}]), + tsp("RequestId4: ~p~n", [RequestId4]), + p("test_pipeline -> RequestId4: ~p", [RequestId4]), + + p("test_pipeline -> cancel (async) request 4"), + ok = httpc:cancel_request(RequestId3), + + p("test_pipeline -> expect *no* reply for cancelled (async) request 4 (for 3 secs)"), + receive + {http, {RequestId3, _}} -> + test_server:fail(http_cancel_request_failed) + after 3000 -> + ok + end, + + p("test_pipeline -> expect reply for (async) request 4"), + Body = + receive + {http, {RequestId4, {{_, 200, _}, _, BinBody4}}} = Res -> p("test_pipeline -> received reply for (async) request 5"), tsp("Receive : ~p", [Res]), BinBody4; @@ -700,9 +711,9 @@ test_pipeline(URL) -> p("test_pipeline -> check reply for (async) request 5"), inets_test_lib:check_body(binary_to_list(Body)), - + p("test_pipeline -> ensure no unexpected incomming"), - receive + receive {http, Any} -> test_server:fail({unexpected_message, Any}) after 500 -> @@ -712,8 +723,6 @@ test_pipeline(URL) -> p("test_pipeline -> done"), ok. - - %%------------------------------------------------------------------------- http_trace(doc) -> ["Perform a TRACE request that goes through a proxy."]; @@ -1675,25 +1684,11 @@ proxy_https_not_supported(suite) -> proxy_https_not_supported(Config) when is_list(Config) -> Result = httpc:request(get, {"https://login.yahoo.com", []}, [], []), case Result of - {error, Reason} -> - %% ok so far - case Reason of - {failed_connecting, Why} -> - %% ok, now check why - case Why of - https_through_proxy_is_not_currently_supported -> - ok; - _ -> - tsf({unexpected_why, Why}) - end; - _ -> - tsf({unexpected_reason, Reason}) - end; + {error, https_through_proxy_is_not_currently_supported} -> + ok; _ -> - tsf({unexpected_result, Result}) - end, - ok. - + tsf({unexpected_reason, Result}) + end. %%------------------------------------------------------------------------- @@ -2446,7 +2441,7 @@ otp_8106_fun(Config) when is_list(Config) -> ok; _ -> {skip, "Failed to start local http-server"} - end. + end. otp_8106_mfa(doc) -> @@ -2672,7 +2667,7 @@ otp_8739(Config) when is_list(Config) -> Request = {URL, []}, HttpOptions = [{connect_timeout, 500}, {timeout, 1}], Options = [{sync, true}], - case http:request(Method, Request, HttpOptions, Options) of + case httpc:request(Method, Request, HttpOptions, Options) of {error, timeout} -> %% And now we check the size of the handler db Info = httpc:info(), @@ -2729,7 +2724,31 @@ otp_8739_dummy_server_main(_Parent, ListenSocket) -> exit(Error) end. - +%%------------------------------------------------------------------------- + +initial_server_connect(doc) -> + ["If this test cases times out the init of httpc_handler process is" + "blocking the manager/client process (implementation dependent which) but nither" + "should be blocked."]; +initial_server_connect(suite) -> + []; +initial_server_connect(Config) when is_list(Config) -> + DataDir = ?config(data_dir, Config), + ok = httpc:set_options([{ipfamily, inet}]), + + CertFile = filename:join(DataDir, "ssl_server_cert.pem"), + SSLOptions = [{certfile, CertFile}, {keyfile, CertFile}], + + {DummyServerPid, Port} = dummy_ssl_server_hang(self(), ipv4, SSLOptions), + + URL = ?SSL_URL_START ++ integer_to_list(Port) ++ "/index.html", + + httpc:request(get, {URL, []}, [{ssl,{essl,[]}}], [{sync, false}]), + + [{session_cookies,[]}] = httpc:which_cookies(), + + DummyServerPid ! stop, + ok = httpc:set_options([{ipfamily, inet6fb4}]). %%-------------------------------------------------------------------- %% Internal functions @@ -3242,11 +3261,9 @@ pick_header(Headers, Name) -> Val end. - not_implemented_yet() -> exit(not_implemented_yet). - p(F) -> p(F, []). @@ -3260,3 +3277,37 @@ tsp(F, A) -> tsf(Reason) -> test_server:fail(Reason). + + +dummy_ssl_server_hang(Caller, IpV, SslOpt) -> + Pid = spawn(httpc_SUITE, dummy_ssl_server_hang_init, [Caller, IpV, SslOpt]), + receive + {port, Port} -> + {Pid, Port} + end. + +dummy_ssl_server_hang_init(Caller, IpV, SslOpt) -> + {ok, ListenSocket} = + case IpV of + ipv4 -> + ssl:listen(0, [binary, inet, {packet, 0}, + {reuseaddr,true}, + {active, false}] ++ SslOpt); + ipv6 -> + ssl:listen(0, [binary, inet6, {packet, 0}, + {reuseaddr,true}, + {active, false}] ++ SslOpt) + end, + {ok, {_,Port}} = ssl:sockname(ListenSocket), + tsp("dummy_ssl_server_hang_init -> Port: ~p", [Port]), + Caller ! {port, Port}, + {ok, AcceptSocket} = ssl:transport_accept(ListenSocket), + dummy_ssl_server_hang_loop(AcceptSocket). + +dummy_ssl_server_hang_loop(_) -> + %% Do not do ssl:ssl_accept as we + %% want to time out the underlying gen_tcp:connect + receive + stop -> + ok + end. -- cgit v1.2.3