diff options
author | Micael Karlberg <[email protected]> | 2010-01-13 16:18:25 +0000 |
---|---|---|
committer | Erlang/OTP <[email protected]> | 2010-01-13 16:18:25 +0000 |
commit | 7ff3da1953b411d7c56f0334f774f951d7f88a00 (patch) | |
tree | e5beda6c56a3e49c173aa956f443605cb65cff45 /lib/inets/src/http_client/httpc_manager.erl | |
parent | 6153ba7599f2ce1ab22959a40b6ca33b4238f0d0 (diff) | |
download | otp-7ff3da1953b411d7c56f0334f774f951d7f88a00.tar.gz otp-7ff3da1953b411d7c56f0334f774f951d7f88a00.tar.bz2 otp-7ff3da1953b411d7c56f0334f774f951d7f88a00.zip |
OTP-8016 TBD
Diffstat (limited to 'lib/inets/src/http_client/httpc_manager.erl')
-rw-r--r-- | lib/inets/src/http_client/httpc_manager.erl | 713 |
1 files changed, 534 insertions, 179 deletions
diff --git a/lib/inets/src/http_client/httpc_manager.erl b/lib/inets/src/http_client/httpc_manager.erl index 63b00c7dce..915f4c024d 100644 --- a/lib/inets/src/http_client/httpc_manager.erl +++ b/lib/inets/src/http_client/httpc_manager.erl @@ -1,19 +1,19 @@ %% %% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2002-2009. All Rights Reserved. -%% +%% +%% Copyright Ericsson AB 2002-2010. All Rights Reserved. +%% %% The contents of this file are subject to the Erlang Public License, %% Version 1.1, (the "License"); you may not use this file except in %% compliance with the License. You should have received a copy of the %% Erlang Public License along with this software. If not, it can be %% retrieved online at http://www.erlang.org/. -%% +%% %% Software distributed under the License is distributed on an "AS IS" %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See %% the License for the specific language governing rights and limitations %% under the License. -%% +%% %% %CopyrightEnd% %% %% @@ -25,51 +25,84 @@ -include("http_internal.hrl"). %% Internal Application API --export([start_link/1, start_link/2, request/2, cancel_request/2, - request_canceled/2, retry_request/2, redirect_request/2, - insert_session/2, delete_session/2, set_options/2, store_cookies/3, - cookies/2, session_type/1]). +-export([ + start_link/3, + request/2, + cancel_request/2, + request_canceled/2, + retry_request/2, + redirect_request/2, + insert_session/2, + delete_session/2, + set_options/2, + store_cookies/3, + which_cookies/1, which_cookies/2, + reset_cookies/1, + session_type/1 + ]). %% gen_server callbacks -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). --record(state, { +-record(state, + { cancel = [], % [{RequestId, HandlerPid, ClientPid}] - handler_db, % ets() - Entry: {Requestid, HandlerPid, ClientPid} - cookie_db, % {ets(), dets()} - {session_cookie_db, cookie_db} + handler_db, % ets() - Entry: #handler_info{} + cookie_db, % cookie_db() session_db, % ets() - Entry: #tcp_session{} profile_name, % atom() options = #options{} }). +-record(handler_info, + { + id, % Id of the request: request_id() + 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 + }). + +%% Entries in the handler / request cross-ref table +%% -record(request_info, +%% { +%% id, % Id of the request +%% handler, % Pid of the handler process +%% from, % The From value for the caller +%% mref % Monitor ref for the caller +%% }). + %%==================================================================== %% Internal Application API %%==================================================================== %%-------------------------------------------------------------------- -%% Function: start_link({ProfileName, CookieDir}) -> {ok, Pid} +%% Function: start_link(ProfileName, CookieDir, ManagedHow) -> {ok, Pid} %% %% ProfileName - httpc_manager_<Profile> %% CookieDir - directory() +%% ManagedHow - stand_alone | inets %% -%% Description: Starts the http request manger process. (Started by -%% the intes supervisor.) -%%-------------------------------------------------------------------- -start_link({default, CookieDir}) -> - gen_server:start_link({local, ?MODULE}, ?MODULE, - [?MODULE, {http_default_cookie_db, CookieDir}], - []); -start_link({Profile, CookieDir}) -> - ProfileName = list_to_atom("httpc_manager_" ++ atom_to_list(Profile)), - gen_server:start_link({local, ProfileName}, ?MODULE, - [ProfileName, - {http_default_cookie_db, CookieDir}], []). -start_link({Profile, CookieDir}, stand_alone) -> - ProfileName = list_to_atom("stand_alone_" ++ atom_to_list(Profile)), - gen_server:start_link(?MODULE, [ProfileName, - {http_default_cookie_db, CookieDir}], - []). +%% Description: Starts the http request manager process. +%% (If ManagedHow = inets then started by the inets supervisor.) +%%-------------------------------------------------------------------- + +start_link(Profile, CookieDir, stand_alone) -> + ProfileName = httpc:profile_name("stand_alone_", Profile), + Args = [ProfileName, CookieDir], + Opts = [], + %% Opts = [{debug, [log, statistics]}], + gen_server:start_link(?MODULE, Args, Opts); +start_link(Profile, CookieDir, _) -> + ProfileName = httpc:profile_name(Profile), + Server = {local, ProfileName}, + Args = [ProfileName, CookieDir], + Opts = [], + %% Opts = [{debug, [log, statistics]}], + gen_server:start_link(Server, ?MODULE, Args, Opts). + + %%-------------------------------------------------------------------- %% Function: request(Request, ProfileName) -> %% {ok, Requestid} | {error, Reason} @@ -78,8 +111,10 @@ start_link({Profile, CookieDir}, stand_alone) -> %% %% Description: Sends a request to the httpc manager process. %%-------------------------------------------------------------------- + request(Request, ProfileName) -> - call(ProfileName, {request, Request}, infinity). + call(ProfileName, {request, Request}). + %%-------------------------------------------------------------------- %% Function: retry_request(Request, ProfileName) -> _ @@ -90,9 +125,11 @@ request(Request, ProfileName) -> %% to be called by the httpc handler process if it has to terminate with %% a non empty pipeline. %%-------------------------------------------------------------------- + retry_request(Request, ProfileName) -> cast(ProfileName, {retry_or_redirect_request, Request}). + %%-------------------------------------------------------------------- %% Function: redirect_request(Request, ProfileName) -> _ %% Request = #request{} @@ -102,9 +139,11 @@ retry_request(Request, ProfileName) -> %% manager process, intended to be called by the httpc handler process %% when the automatic redirect option is set. %%-------------------------------------------------------------------- + redirect_request(Request, ProfileName) -> cast(ProfileName, {retry_or_redirect_request, Request}). + %%-------------------------------------------------------------------- %% Function: cancel_request(RequestId, ProfileName) -> ok %% RequestId - ref() @@ -112,8 +151,10 @@ redirect_request(Request, ProfileName) -> %% %% Description: Cancels the request with <RequestId>. %%-------------------------------------------------------------------- + cancel_request(RequestId, ProfileName) -> - call(ProfileName, {cancel_request, RequestId}, infinity). + call(ProfileName, {cancel_request, RequestId}). + %%-------------------------------------------------------------------- %% Function: request_canceled(RequestId, ProfileName) -> ok @@ -123,9 +164,11 @@ cancel_request(RequestId, ProfileName) -> %% Description: Confirms that a request has been canceld. Intended to %% be called by the httpc handler process. %%-------------------------------------------------------------------- + request_canceled(RequestId, ProfileName) -> cast(ProfileName, {request_canceled, RequestId}). + %%-------------------------------------------------------------------- %% Function: insert_session(Session, ProfileName) -> _ %% Session - #tcp_session{} @@ -135,9 +178,11 @@ request_canceled(RequestId, ProfileName) -> %% table <ProfileName>_session_db. Intended to be called by %% the httpc request handler process. %%-------------------------------------------------------------------- + insert_session(Session, ProfileName) -> - Db = list_to_atom(atom_to_list(ProfileName) ++ "_session_db"), - ets:insert(Db, Session). + SessionDbName = session_db_name(ProfileName), + ets:insert(SessionDbName, Session). + %%-------------------------------------------------------------------- %% Function: delete_session(SessionId, ProfileName) -> _ @@ -148,9 +193,11 @@ insert_session(Session, ProfileName) -> %% table httpc_manager_session_db_<Profile>. Intended to be called by %% the httpc request handler process. %%-------------------------------------------------------------------- + delete_session(SessionId, ProfileName) -> - Db = list_to_atom(atom_to_list(ProfileName) ++ "_session_db"), - ets:delete(Db, SessionId). + SessionDbName = session_db_name(ProfileName), + ets:delete(SessionDbName, SessionId). + %%-------------------------------------------------------------------- %% Function: set_options(Options, ProfileName) -> ok @@ -166,9 +213,11 @@ delete_session(SessionId, ProfileName) -> %% %% Description: Sets the options to be used by the client. %%-------------------------------------------------------------------- + set_options(Options, ProfileName) -> cast(ProfileName, {set_options, Options}). + %%-------------------------------------------------------------------- %% Function: store_cookies(Cookies, Address, ProfileName) -> ok %% @@ -183,8 +232,22 @@ store_cookies([], _, _) -> store_cookies(Cookies, Address, ProfileName) -> cast(ProfileName, {store_cookies, {Cookies, Address}}). + %%-------------------------------------------------------------------- -%% Function: cookies(Url, ProfileName) -> ok +%% Function: reset_cookies(ProfileName) -> void() +%% +%% Url = string() +%% ProfileName = atom() +%% +%% Description: Resets the cookie database +%%-------------------------------------------------------------------- + +reset_cookies(ProfileName) -> + call(ProfileName, reset_cookies). + + +%%-------------------------------------------------------------------- +%% Function: which_cookies(Url, ProfileName) -> [cookie()] %% %% Url = string() %% ProfileName = atom() @@ -192,8 +255,12 @@ store_cookies(Cookies, Address, ProfileName) -> %% Description: Retrieves the cookies that would be sent when %% requesting <Url>. %%-------------------------------------------------------------------- -cookies(Url, ProfileName) -> - call(ProfileName, {cookies, Url}, infinity). + +which_cookies(ProfileName) -> + call(ProfileName, which_cookies). +which_cookies(Url, ProfileName) -> + call(ProfileName, {which_cookies, Url}). + %%-------------------------------------------------------------------- %% Function: session_type(Options) -> ok @@ -202,11 +269,13 @@ cookies(Url, ProfileName) -> %% %% Description: Determines if to use pipelined sessions or not. %%-------------------------------------------------------------------- + session_type(#options{pipeline_timeout = 0}) -> keep_alive; session_type(_) -> pipeline. + %%==================================================================== %% gen_server callback functions %%==================================================================== @@ -216,19 +285,46 @@ session_type(_) -> %% {ok, State, Timeout} | ignore |{stop, Reason} %% Description: Initiates the httpc_manger process %%-------------------------------------------------------------------- -init([ProfileName, CookiesConf | _]) -> +init([ProfileName, CookiesDir]) -> process_flag(trap_exit, true), - SessionDb = list_to_atom(atom_to_list(ProfileName) ++ "_session_db"), - ets:new(SessionDb, + ?hcrv("starting", [{profile, ProfileName}]), + case (catch do_init(ProfileName, CookiesDir)) of + {ok, _} = OK -> + ?hcrd("started", [OK]), + OK; + {error, Reason} -> + {stop, Reason}; + Crap -> + {stop, Crap} + end. + + +do_init(ProfileName, CookiesDir) -> + %% Create session db + ?hcrt("create session db", []), + SessionDbName = session_db_name(ProfileName), + ets:new(SessionDbName, [public, set, named_table, {keypos, #tcp_session.id}]), - ?hcri("starting", [{profile, ProfileName}]), - {ok, #state{handler_db = ets:new(handler_db, [protected, set]), - cookie_db = - http_cookie:open_cookie_db({CookiesConf, - http_session_cookie_db}), - session_db = SessionDb, - profile_name = ProfileName - }}. + + %% Create handler db + ?hcrt("create handler/request db", []), + HandlerDbName = handler_db_name(ProfileName), + ets:new(HandlerDbName, + [protected, set, named_table, {keypos, #handler_info.id}]), + + %% Cookie DB + ?hcrt("create cookie db", []), + SessionCookieDbName = session_cookie_db_name(ProfileName), + CookieDbName = cookie_db_name(ProfileName), + CookieDb = httpc_cookie:open_db(CookieDbName, CookiesDir, + SessionCookieDbName), + + State = #state{handler_db = HandlerDbName, + cookie_db = CookieDb, + session_db = SessionDbName, + profile_name = ProfileName}, + {ok, State}. + %%-------------------------------------------------------------------- %% Function: handle_call(Request, From, State) -> {reply, Reply, State} | @@ -239,45 +335,77 @@ init([ProfileName, CookiesConf | _]) -> %% {stop, Reason, State} (terminate/2 is called) %% Description: Handling call messages %%-------------------------------------------------------------------- -handle_call({request, Request}, _, State) -> - ?hcri("request", [{request, Request}]), +handle_call({request, Request}, _From, State) -> + ?hcrv("request", [{request, Request}]), case (catch handle_request(Request, State)) of - {reply, Msg, NewState} -> - {reply, Msg, NewState}; + {ok, ReqId, NewState} -> + {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} end; -handle_call({cancel_request, RequestId}, From, State) -> - ?hcri("cancel_request", [{request_id, RequestId}]), +handle_call({cancel_request, RequestId}, From, + #state{handler_db = HandlerDb} = State) -> + ?hcrv("cancel_request", [{request_id, RequestId}]), case ets:lookup(State#state.handler_db, RequestId) of [] -> - ok, %% Nothing to cancel - {reply, ok, State}; - [{_, Pid, _}] -> + ?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} | + [{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}), + {noreply, State#state{cancel = + [{RequestId, Pid, From} | State#state.cancel]}} + end; -handle_call({cookies, Url}, _, 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) -> + ?hcrv("which cookies", []), + CookieHeaders = httpc_cookie:which_cookies(CookieDb), + {reply, CookieHeaders, 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, _} -> CookieHeaders = - http_cookie:header(Scheme, {Host, Port}, - Path, State#state.cookie_db), + httpc_cookie:header(CookieDb, Scheme, {Host, Port}, Path), {reply, CookieHeaders, State}; Msg -> {reply, Msg, State} end; -handle_call(Msg, From, State) -> - Report = io_lib:format("HTTPC_MANAGER recived unkown call: ~p" - "from: ~p~n", [Msg, From]), - error_logger:error_report(Report), +handle_call(Req, From, #state{profile_name = ProfileName} = State) -> + error_report(ProfileName, + "received unkown request" + "~n Req: ~p" + "~n From: ~p", [Req, From]), {reply, {error, 'API_violation'}, State}. + %%-------------------------------------------------------------------- %% Function: handle_cast(Msg, State) -> {noreply, State} | %% {noreply, State, Timeout} | @@ -286,29 +414,49 @@ handle_call(Msg, From, State) -> %%-------------------------------------------------------------------- handle_cast({retry_or_redirect_request, {Time, Request}}, #state{profile_name = ProfileName} = State) -> - {ok, _} = timer:apply_after(Time, ?MODULE, retry_request, [Request, ProfileName]), - {noreply, 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; handle_cast({retry_or_redirect_request, Request}, State) -> + ?hcrv("retry or redirect request", [{request, Request}]), case (catch handle_request(Request, State)) of - {reply, {ok, _}, NewState} -> + {ok, _, NewState} -> {noreply, NewState}; + Error -> - httpc_response:error(Request, Error), + %% This is *way* too severe. + %% To crash the manager simply because + %% it failed to properly handle *one* request {stop, Error, State} end; handle_cast({request_canceled, RequestId}, State) -> + ?hcrv("request canceled", [{request_id, RequestId}]), ets:delete(State#state.handler_db, RequestId), 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 -> + ?hcrt("not found in cancel", [{else, Else}]), {noreply, State} end; + handle_cast({set_options, Options}, State = #state{options = OldOptions}) -> + ?hcrv("set options", [{options, Options}, {old_options, OldOptions}]), NewOptions = #options{proxy = get_proxy(Options, OldOptions), pipeline_timeout = get_pipeline_timeout(Options, OldOptions), @@ -345,10 +493,10 @@ handle_cast({store_cookies, {Cookies, _}}, State) -> ok = do_store_cookies(Cookies, State), {noreply, State}; -handle_cast(Msg, State) -> - Report = io_lib:format("HTTPC_MANAGER recived unkown cast: ~p", - [Msg]), - error_logger:error_report(Report), +handle_cast(Msg, #state{profile_name = ProfileName} = State) -> + error_report(ProfileName, + "recived unknown message" + "~n Msg: ~p", [Msg]), {noreply, State}. @@ -359,17 +507,32 @@ handle_cast(Msg, State) -> %% {stop, Reason, State} (terminate/2 is called) %% Description: Handling all non call/cast messages %%--------------------------------------------------------- -handle_info({'EXIT', _, _}, State) -> - %% Handled in DOWN +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), + {noreply, State}; + handle_info({'DOWN', _, _, Pid, _}, State) -> - ets:match_delete(State#state.handler_db, {'_', Pid, '_'}), + + %% + %% Check what happens to waiting requests! Chall we not send a reply? + %% + + Pattern = #handler_info{handler = Pid, _ = '_'}, + ets:match_delete(State#state.handler_db, Pattern), %% If there where any canceled request, handled by the %% the process that now has terminated, the %% cancelation can be viewed as sucessfull! - NewCanceldList = - lists:foldl(fun(Entry = {_, HandlerPid, From}, Acc) -> + NewCanceledList = + lists:foldl(fun({_, HandlerPid, From} = Entry, Acc) -> case HandlerPid of Pid -> gen_server:reply(From, ok), @@ -378,21 +541,25 @@ handle_info({'DOWN', _, _, Pid, _}, State) -> Acc end end, State#state.cancel, State#state.cancel), - {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#state{cancel = NewCanceledList}}; + +handle_info(Info, #state{profile_name = ProfileName} = State) -> + error_report(ProfileName, + "received unknown info" + "~n Info: ~p", [Info]), {noreply, State}. + + %%-------------------------------------------------------------------- %% Function: terminate(Reason, State) -> _ (ignored by gen_server) %% Description: Shutdown the httpc_handler %%-------------------------------------------------------------------- terminate(_, State) -> - http_cookie:close_cookie_db(State#state.cookie_db), + httpc_cookie:close_db(State#state.cookie_db), ets:delete(State#state.session_db), ets:delete(State#state.handler_db). + %%-------------------------------------------------------------------- %% Func: code_change(_OldVsn, State, Extra) -> {ok, NewState} %% Purpose: Convert process state when code is changed @@ -400,70 +567,188 @@ terminate(_, State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. + %%-------------------------------------------------------------------- %% Internal functions %%-------------------------------------------------------------------- -handle_request(#request{settings = - #http_options{version = "HTTP/0.9"}} = Request, - State) -> - %% 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}; +%% +%% 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 = initiating} = HandlerInfo] when Result =:= ok -> + ?hcri("received connect-and-send ack for initiating 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{from = From}] -> + error_report(Profile, + "handler (~p) failed to connect and/or " + "send request ~p" + "~n Error: ~p", [HandlerPid, ReqId, Result]), + ?hcri("received connect-and-send error", [{result, Result}]), + Reason2 = + case Result of + {error, Reason} -> + {failed_connecting, Reason}; + _ -> + {failed_connecting, Result} + end, + DummyReq = #request{id = ReqId}, + httpc_response:send(From, httpc_response:error(DummyReq, Reason2)), + %% gen_server:reply(From, Error), + ets:delete(HandlerDb, ReqId), + ok; + + [] -> + error_report(Profile, + "handler successfully (~p) started for unknown request ~p", + [HandlerPid, 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, + from = From}] -> + error_report(Profile, + "failed starting handler for request ~p" + "~n Error: ~p", [ReqId, Error]), + request_canceled(Profile, ReqId), % Fake signal from handler + gen_server:reply(From, Error), + ets:delete(HandlerDb, ReqId), + ok; + + [#handler_info{from = From}] -> + error_report(Profile, + "failed starting handler for request ~p" + "~n Error: ~p", [ReqId, Error]), + gen_server:reply(From, Error), + 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}] -> + Error = {error, {failed_starting_request_handler, Reason}}, + gen_server:reply(From, Error), + ets:delete(HandlerDb, ReqId), + 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, + 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 handle_request(#request{settings = - #http_options{version = "HTTP/1.0"}} = Request, + #http_options{version = "HTTP/1.0"}} = Request0, State) -> - %% 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 + 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 {ok, HandlerPid} -> - pipeline_or_keep_alive(NewRequest, HandlerPid, State); + pipeline_or_keep_alive(Request1, HandlerPid, State); no_connection -> - start_handler(NewRequest, State); - {no_session, OpenSessions} when OpenSessions - < Options#options.max_sessions -> - start_handler(NewRequest, State); + create_handler_starter(Request1, State); + {no_session, OpenSessions} + when OpenSessions < Opts#options.max_sessions -> + create_handler_starter(Request1, State); {no_session, _} -> %% Do not start any more persistent connections %% towards this server. - NewHeaders = - (NewRequest#request.headers)#http_request_h{connection - = "close"}, - start_handler(NewRequest#request{headers = NewHeaders}, State) + Hdrs0 = Request1#request.headers, + Hdrs1 = Hdrs0#http_request_h{connection = "close"}, + Request2 = Request1#request{headers = Hdrs1}, + create_handler_starter(Request2, State) end, - {reply, {ok, NewRequest#request.id}, State}. + {ok, Request1#request.id, State}. -select_session(Method, HostPort, Scheme, SessionTyp, - #state{options = #options{max_pipeline_length = - MaxPipe, + +select_session(Method, HostPort, Scheme, SessionType, + #state{options = #options{max_pipeline_length = MaxPipe, max_keep_alive_length = MaxKeepAlive}, session_db = SessionDb}) -> - case httpc_request:is_idempotent(Method) or (SessionTyp == keep_alive) of + ?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 true -> - Candidates = ets:match(SessionDb, - {'_', {HostPort, '$1'}, - false, Scheme, '_', '$2', SessionTyp}), - select_session(Candidates, MaxKeepAlive, MaxPipe, SessionTyp); + %% Look for handlers connecting to this host (HostPort) + %% tcp_session with record name field (tcp_session) and + %% socket fields ignored. The fields id (part of: HostPort), + %% client_close, scheme and type specified. + %% The fields id (part of: HandlerPid) and queue_length + %% specified. + Pattern = #tcp_session{id = {HostPort, '$1'}, + client_close = false, + scheme = Scheme, + socket = '_', + queue_length = '$2', + type = SessionType}, + %% {'_', {HostPort, '$1'}, false, Scheme, '_', '$2', SessionTyp}, + Candidates = ets:match(SessionDb, Pattern), + ?hcrd("select session", [{candidates, Candidates}]), + select_session(Candidates, MaxKeepAlive, MaxPipe, SessionType); false -> no_connection end. @@ -473,51 +758,92 @@ select_session(Candidates, Max, _, keep_alive) -> select_session(Candidates, _, Max, pipeline) -> select_session(Candidates, Max). +select_session([] = _Candidates, _Max) -> + ?hcrd("select session - no candicate", []), + no_connection; select_session(Candidates, Max) -> - case Candidates of + NewCandidates = + [{Pid, Length} || [Pid, Length] <- Candidates, Length =< Max], + case lists:keysort(2, NewCandidates) of [] -> - no_connection; - _ -> - NewCandidates = - lists:foldl( - fun([Pid, Length], Acc) when Length =< Max -> - [{Pid, Length} | Acc]; - (_, Acc) -> - Acc - end, [], Candidates), - - case lists:keysort(2, NewCandidates) of - [] -> - {no_session, length(Candidates)}; - [{HandlerPid, _} | _] -> - {ok, HandlerPid} - end + {no_session, length(Candidates)}; + [{HandlerPid, _} | _] -> + ?hcrd("select session - found one", [{handler, HandlerPid}]), + {ok, HandlerPid} end. -pipeline_or_keep_alive(Request, HandlerPid, State) -> +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 -> - ets:insert(State#state.handler_db, {Request#request.id, - HandlerPid, - Request#request.from}); - _ -> %timeout pipelining failed - start_handler(Request, State) + ?hcrd("pipeline of 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 -> " + "start a new handler", []), + create_handler_starter(Request, State) end. -start_handler(Request, State) -> - {ok, Pid} = - case is_inets_manager() of - true -> - httpc_handler_sup:start_child([Request, State#state.options, - State#state.profile_name]); - false -> - httpc_handler:start_link(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). +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} -> + 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 | _] -> @@ -539,25 +865,48 @@ generate_request_id(Request) -> handle_cookies(Request, #state{options = #options{cookies = disabled}}) -> Request; -handle_cookies(Request = #request{scheme = Scheme, address = Address, - path = Path, headers = - Headers = #http_request_h{other = Other}}, - #state{cookie_db = Db}) -> - case http_cookie:header(Scheme, Address, Path, Db) of +handle_cookies( + #request{scheme = Scheme, + address = Address, + path = Path, + headers = #http_request_h{other = Other} = Hdrs} = Request, + #state{cookie_db = CookieDb}) -> + case httpc_cookie:header(CookieDb, Scheme, Address, Path) of {"cookie", ""} -> Request; CookieHeader -> - NewHeaders = - Headers#http_request_h{other = [CookieHeader | Other]}, + NewHeaders = Hdrs#http_request_h{other = [CookieHeader | Other]}, Request#request{headers = NewHeaders} end. do_store_cookies([], _) -> ok; -do_store_cookies([Cookie | Cookies], State) -> - ok = http_cookie:insert(Cookie, State#state.cookie_db), +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"). + +cookie_db_name(ProfileName) -> + make_db_name(ProfileName, "__cookie_db"). + +session_cookie_db_name(ProfileName) -> + make_db_name(ProfileName, "__session_cookie_db"). + +handler_db_name(ProfileName) -> + make_db_name(ProfileName, "__handler_db"). + +make_db_name(ProfileName, Post) -> + list_to_atom(atom_to_list(ProfileName) ++ Post). + + + +call(ProfileName, Msg) -> + Timeout = infinity, + call(ProfileName, Msg, Timeout). call(ProfileName, Msg, Timeout) -> gen_server:call(ProfileName, Msg, Timeout). @@ -621,6 +970,12 @@ 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, []). |