aboutsummaryrefslogblamecommitdiffstats
path: root/lib/dialyzer/test/r9c_SUITE_data/src/inets/httpc_handler.erl
blob: 920948164e8e9f3d2822d02548d43b93ed256eb5 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11


                                                                    
  






                                                                           
  


                                                                     
  



















































































                                                                                     
   


































































                                                                              
                           




























                                                                             
 










                                                                                
                                       


                                                           
                                                                  


                                                           
                                                                  



















































                                                                          
 



































































































                                                                                
                 


                                                                                
                                        













































































































































































































                                                                                
                                                
                                                                            
                                                       







                                                                            

                                                       






















































































                                                                                  

                                                  






































                                                                     
%% ``Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
%% You may obtain a copy of the License at
%%
%%     http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing, software
%% distributed under the License is distributed on an "AS IS" BASIS,
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
%% See the License for the specific language governing permissions and
%% limitations under the License.
%%
%% The Initial Developer of the Original Code is Mobile Arts AB
%% Portions created by Mobile Arts are Copyright 2002, Mobile Arts AB
%% All Rights Reserved.''
%%

%%% TODO:
%%% - If an error is returned when sending a request, don't use this
%%%   session anymore.
%%% - Closing of sessions not properly implemented for some cases

%%% File    : httpc_handler.erl
%%% Author  : Johan Blom <[email protected]>
%%% Description : Handles HTTP client responses, for a single TCP session
%%% Created :  4 Mar 2002 by Johan Blom

-module(httpc_handler).

-include("http.hrl").
-include("jnets_httpd.hrl").

-export([init_connection/2,http_request/2]).

%%% ==========================================================================
%%% "Main" function in the spawned process for the session.
init_connection(Req,Session) when record(Req,request) ->
    case catch http_lib:connect(Req) of
	{ok,Socket} ->
	    case catch http_request(Req,Socket) of
		ok ->
		    case Session#session.clientclose of
			true ->
			    ok;
			false ->
			    httpc_manager:register_socket(Req#request.address,
							  Session#session.id,
							  Socket)
		    end,
		    next_response_with_request(Req,
					       Session#session{socket=Socket});
		{error,Reason} -> % Not possible to use new session
		    gen_server:cast(Req#request.from,
				    {Req#request.ref,Req#request.id,{error,Reason}}),
		    exit_session_ok(Req#request.address,
				    Session#session{socket=Socket})
	    end;
	{error,Reason} -> % Not possible to set up new session
	    gen_server:cast(Req#request.from,
			    {Req#request.ref,Req#request.id,{error,Reason}}),
	    exit_session_ok2(Req#request.address,
			     Session#session.clientclose,Session#session.id)
    end.

next_response_with_request(Req,Session) ->
    Timeout=(Req#request.settings)#client_settings.timeout,
    case catch read(Timeout,Session#session.scheme,Session#session.socket) of
	{Status,Headers,Body} ->
	    NewReq=handle_response({Status,Headers,Body},Timeout,Req,Session),
	    next_response_with_request(NewReq,Session);
	{error,Reason} ->
	    gen_server:cast(Req#request.from,
			    {Req#request.ref,Req#request.id,{error,Reason}}),
	    exit_session(Req#request.address,Session,aborted_request);
	{'EXIT',Reason} ->
	    gen_server:cast(Req#request.from,
			    {Req#request.ref,Req#request.id,{error,Reason}}),
	    exit_session(Req#request.address,Session,aborted_request)
    end.

handle_response(Response,Timeout,Req,Session) ->
    case http_response(Response,Req,Session) of
	ok ->
	    next_response(Timeout,Req#request.address,Session);
	stop ->
	    exit(normal);
	{error,Reason} ->
	    gen_server:cast(Req#request.from,
			    {Req#request.ref,Req#request.id,{error,Reason}}),
	    exit_session(Req#request.address,Session,aborted_request)
    end.



%%% Wait for the next respond until
%%% - session is closed by the other side
%%%      => set up a new a session, if there are pending requests in the que
%%% - "Connection:close" header is received
%%%      => close the connection (release socket) then
%%%         set up a new a session, if there are pending requests in the que
%%%
%%% Note:
%%% - When invoked there are no pending responses on received requests.
%%% - Never close the session explicitly, let it timeout instead!
next_response(Timeout,Address,Session) ->
    case httpc_manager:next_request(Address,Session#session.id) of
	no_more_requests ->
	    %% There are no more pending responses, now just wait for
	    %% timeout or a new response.
	    case catch read(Timeout,
			    Session#session.scheme,Session#session.socket) of
		{error,Reason} when Reason==session_remotely_closed;
				    Reason==session_local_timeout ->
		    exit_session_ok(Address,Session);
		{error,Reason} ->
		    exit_session(Address,Session,aborted_request);
		{'EXIT',Reason} ->
		    exit_session(Address,Session,aborted_request);
		{Status2,Headers2,Body2} ->
		    case httpc_manager:next_request(Address,
						    Session#session.id) of
			no_more_requests -> % Should not happen!
			    exit_session(Address,Session,aborted_request);
			{error,Reason} -> % Should not happen!
			    exit_session(Address,Session,aborted_request);
			NewReq ->
			    handle_response({Status2,Headers2,Body2},
					    Timeout,NewReq,Session)
		    end
	    end;
	{error,Reason} -> % The connection has been closed by httpc_manager
	    exit_session(Address,Session,aborted_request);
	NewReq ->
	    NewReq
    end.

%% ===========================================================================
%% Internals

%%% Read in and parse response data from the socket
read(Timeout,SockType,Socket) ->
    Info=#response{scheme=SockType,socket=Socket},
    http_lib:setopts(SockType,Socket,[{packet, http}]),
    Info1=read_response(SockType,Socket,Info,Timeout),
    http_lib:setopts(SockType,Socket,[binary,{packet, raw}]),
    case (Info1#response.headers)#res_headers.content_type of
	"multipart/byteranges"++Param ->
	    range_response_body(Info1,Timeout,Param);
	_ ->
	    #response{status=Status2,headers=Headers2,body=Body2}=
		http_lib:read_client_body(Info1,Timeout),
	    {Status2,Headers2,Body2}
    end.


%%% From RFC 2616:
%%%      Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
%%%      HTTP-Version   = "HTTP" "/" 1*DIGIT "." 1*DIGIT
%%%      Status-Code    = 3DIGIT
%%%      Reason-Phrase  = *<TEXT, excluding CR, LF>
read_response(SockType,Socket,Info,Timeout) ->
    case http_lib:recv0(SockType,Socket,Timeout) of
	{ok,{http_response,{1,VerMin}, Status, _Phrase}} when VerMin==0;
							      VerMin==1 ->
	    Info1=Info#response{status=Status,http_version=VerMin},
	    http_lib:read_client_headers(Info1,Timeout);
	{ok,{http_response,_Version, _Status, _Phrase}} ->
	    throw({error,bad_status_line});
	{error, timeout} ->
	    throw({error,session_local_timeout});
	{error, Reason} when Reason==closed;Reason==enotconn ->
	    throw({error,session_remotely_closed});
	{error, Reason} ->
	    throw({error,Reason})
    end.

%%% From RFC 2616, Section 4.4, Page 34
%% 4.If the message uses the media type "multipart/byteranges", and the
%%   transfer-length is not otherwise specified, then this self-
%%   delimiting media type defines the transfer-length. This media type
%%   MUST NOT be used unless the sender knows that the recipient can parse
%%   it; the presence in a request of a Range header with multiple byte-
%%   range specifiers from a 1.1 client implies that the client can parse
%%   multipart/byteranges responses.
%%% FIXME !!
range_response_body(Info,Timeout,Param) ->
    Headers=Info#response.headers,
    case {Headers#res_headers.content_length,
	  Headers#res_headers.transfer_encoding} of
	{undefined,undefined} ->
	    #response{status=Status2,headers=Headers2,body=Body2}=
		http_lib:read_client_multipartrange_body(Info,Param,Timeout),
	    {Status2,Headers2,Body2};
	_ ->
	    #response{status=Status2,headers=Headers2,body=Body2}=
		http_lib:read_client_body(Info,Timeout),
	    {Status2,Headers2,Body2}
    end.


%%% ----------------------------------------------------------------------------
%%% Host: field is required when addressing multi-homed sites ...
%%% It must not be present when the request is being made to a proxy.
http_request(#request{method=Method,id=Id,
		      scheme=Scheme,address={Host,Port},pathquery=PathQuery,
		      headers=Headers, content={ContentType,Body},
		      settings=Settings},
	     Socket) ->
    PostData=
	if
	    Method==post;Method==put ->
		case Headers#req_headers.expect of
		    "100-continue" ->
			content_type_header(ContentType) ++
			    content_length_header(length(Body)) ++
			    "\r\n";
		    _ ->
			content_type_header(ContentType) ++
			    content_length_header(length(Body)) ++
			    "\r\n" ++ Body
		end;
	    true ->
		"\r\n"
	end,
    Message=
	case useProxy(Settings#client_settings.useproxy,
		      {Scheme,Host,Port,PathQuery}) of
	    false ->
		method(Method)++" "++PathQuery++" HTTP/1.1\r\n"++
		    host_header(Host)++te_header()++
		    headers(Headers) ++ PostData;
	    AbsURI ->
		method(Method)++" "++AbsURI++" HTTP/1.1\r\n"++
		    te_header()++
		    headers(Headers)++PostData
	end,
    http_lib:send(Scheme,Socket,Message).

useProxy(false,_) ->
    false;
useProxy(true,{Scheme,Host,Port,PathQuery}) ->
    [atom_to_list(Scheme),"://",Host,":",integer_to_list(Port),PathQuery].



headers(#req_headers{expect=Expect,
		     other=Other}) ->
    H1=case Expect of
	   undefined ->[];
	   _ -> "Expect: "++Expect++"\r\n"
       end,
    H1++headers_other(Other).


headers_other([]) ->
    [];
headers_other([{Key,Value}|Rest]) when atom(Key) ->
    Head = atom_to_list(Key)++": "++Value++"\r\n",
    Head ++ headers_other(Rest);
headers_other([{Key,Value}|Rest]) ->
    Head = Key++": "++Value++"\r\n",
    Head ++ headers_other(Rest).

host_header(Host) ->
    "Host: "++lists:concat([Host])++"\r\n".
content_type_header(ContentType) ->
    "Content-Type: " ++ ContentType ++ "\r\n".
content_length_header(ContentLength) ->
    "Content-Length: "++integer_to_list(ContentLength) ++ "\r\n".
te_header() ->
    "TE: \r\n".

method(Method) ->
    httpd_util:to_upper(atom_to_list(Method)).


%%% ----------------------------------------------------------------------------
http_response({Status,Headers,Body},Req,Session) ->
    case Status of
	100 ->
	    status_continue(Req,Session);
	200 ->
	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
					      {Status,Headers,Body}}),
	    ServerClose=http_lib:connection_close(Headers),
	    handle_connection(Session#session.clientclose,ServerClose,
			      Req,Session);
	300 -> status_multiple_choices(Headers,Body,Req,Session);
	301 -> status_moved_permanently(Req#request.method,
					Headers,Body,Req,Session);
	302 -> status_found(Headers,Body,Req,Session);
	303 -> status_see_other(Headers,Body,Req,Session);
	304 -> status_not_modified(Headers,Body,Req,Session);
	305 -> status_use_proxy(Headers,Body,Req,Session);
	%% 306 This Status code is not used in HTTP 1.1
	307 -> status_temporary_redirect(Headers,Body,Req,Session);
	503 -> status_service_unavailable({Status,Headers,Body},Req,Session);
	Status50x when Status50x==500;Status50x==501;Status50x==502;
		       Status50x==504;Status50x==505 ->
	    status_server_error_50x({Status,Headers,Body},Req,Session);
	_ -> % FIXME May want to take some action on other Status codes as well
	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
					      {Status,Headers,Body}}),
	    ServerClose=http_lib:connection_close(Headers),
	    handle_connection(Session#session.clientclose,ServerClose,
			      Req,Session)
    end.


%%% Status code dependent functions.

%%% Received a 100 Status code ("Continue")
%%% From RFC2616
%%% The client SHOULD continue with its request. This interim response is
%%% used to inform the client that the initial part of the request has
%%% been received and has not yet been rejected by the server. The client
%%% SHOULD continue by sending the remainder of the request or, if the
%%% request has already been completed, ignore this response. The server
%%% MUST send a final response after the request has been completed. See
%%% section 8.2.3 for detailed discussion of the use and handling of this
%%% status code.
status_continue(Req,Session) ->
    {_,Body}=Req#request.content,
    http_lib:send(Session#session.scheme,Session#session.socket,Body),
    next_response_with_request(Req,Session).


%%% Received a 300 Status code ("Multiple Choices")
%%% The resource is located in any one of a set of locations
%%% - If a 'Location' header is present (preserved server choice), use that
%%%   to automatically redirect to the given URL
%%% - else if the Content-Type/Body both are non-empty let the user agent make
%%%   the choice and thus return a response with status 300
%%% Note:
%%% - If response to a HEAD request, the Content-Type/Body both should be empty.
%%% - The behaviour on an empty Content-Type or Body is unspecified.
%%%   However, e.g. "Apache/1.3" servers returns both empty if the header
%%%   'if-modified-since: Date' was sent in the request and the content is
%%%   "not modified" (instead of 304). Thus implicitly giving the cache as the
%%%   only choice.
status_multiple_choices(Headers,Body,Req,Session)
  when ((Req#request.settings)#client_settings.autoredirect)==true ->
    ServerClose=http_lib:connection_close(Headers),
    case Headers#res_headers.location of
	undefined ->
	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
					      {300,Headers,Body}}),
	    handle_connection(Session#session.clientclose,ServerClose,
			      Req,Session);
	RedirUrl ->
	    Scheme=Session#session.scheme,
	    case uri:parse(RedirUrl) of
		{error,Reason} ->
		    {error,Reason};
		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
		    NewReq=Req#request{redircount=Req#request.redircount+1,
				       address={Host,Port},pathquery=PathQuery},
		    handle_redirect(Session#session.clientclose,ServerClose,
				    NewReq,Session)
	    end
    end;
status_multiple_choices(Headers,Body,Req,Session) ->
    ServerClose=http_lib:connection_close(Headers),
    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
				      {300,Headers,Body}}),
    handle_connection(Session#session.clientclose,ServerClose,Req,Session).


%%% Received a 301 Status code ("Moved Permanently")
%%% The resource has been assigned a new permanent URI
%%% - If a 'Location' header is present, use that to automatically redirect to
%%%   the given URL if GET or HEAD request
%%% - else return
%%% Note:
%%% - The Body should contain a short hypertext note with a hyperlink to the
%%%   new URI. Return this if Content-Type acceptable (some HTTP servers doesn't
%%%   deal properly with Accept headers)
status_moved_permanently(Method,Headers,Body,Req,Session)
  when (((Req#request.settings)#client_settings.autoredirect)==true) and
       (Method==get) or (Method==head) ->
    ServerClose=http_lib:connection_close(Headers),
    case Headers#res_headers.location of
	undefined ->
	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
					      {301,Headers,Body}}),
	    handle_connection(Session#session.clientclose,ServerClose,
			      Req,Session);
	RedirUrl ->
	    Scheme=Session#session.scheme,
	    case uri:parse(RedirUrl) of
		{error,Reason} ->
		    {error,Reason};
		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
		    NewReq=Req#request{redircount=Req#request.redircount+1,
				       address={Host,Port},pathquery=PathQuery},
		    handle_redirect(Session#session.clientclose,ServerClose,
				    NewReq,Session)
	    end
    end;
status_moved_permanently(_Method,Headers,Body,Req,Session) ->
    ServerClose=http_lib:connection_close(Headers),
    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
				      {301,Headers,Body}}),
    handle_connection(Session#session.clientclose,ServerClose,Req,Session).


%%% Received a 302 Status code ("Found")
%%% The requested resource resides temporarily under a different URI.
%%% Note:
%%% - Only cacheable if indicated by a Cache-Control or Expires header
status_found(Headers,Body,Req,Session)
  when ((Req#request.settings)#client_settings.autoredirect)==true ->
    ServerClose=http_lib:connection_close(Headers),
    case Headers#res_headers.location of
	undefined ->
	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
					      {302,Headers,Body}}),
	    handle_connection(Session#session.clientclose,ServerClose,
			      Req,Session);
	RedirUrl ->
	    Scheme=Session#session.scheme,
	    case uri:parse(RedirUrl) of
		{error,Reason} ->
		    {error,Reason};
		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
		    NewReq=Req#request{redircount=Req#request.redircount+1,
				       address={Host,Port},pathquery=PathQuery},
		    handle_redirect(Session#session.clientclose,ServerClose,
				    NewReq,Session)
	    end
    end;
status_found(Headers,Body,Req,Session) ->
    ServerClose=http_lib:connection_close(Headers),
    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
				      {302,Headers,Body}}),
    handle_connection(Session#session.clientclose,ServerClose,Req,Session).

%%% Received a 303 Status code ("See Other")
%%% The request found under a different URI and should be retrieved using GET
%%% Note:
%%% - Must not be cached
status_see_other(Headers,Body,Req,Session)
  when ((Req#request.settings)#client_settings.autoredirect)==true ->
    ServerClose=http_lib:connection_close(Headers),
    case Headers#res_headers.location of
	undefined ->
	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
					      {303,Headers,Body}}),
	    handle_connection(Session#session.clientclose,ServerClose,
			      Req,Session);
	RedirUrl ->
	    Scheme=Session#session.scheme,
	    case uri:parse(RedirUrl) of
		{error,Reason} ->
		    {error,Reason};
		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
		    NewReq=Req#request{redircount=Req#request.redircount+1,
				       method=get,
				       address={Host,Port},pathquery=PathQuery},
		    handle_redirect(Session#session.clientclose,ServerClose,
				    NewReq,Session)
	    end
    end;
status_see_other(Headers,Body,Req,Session) ->
    ServerClose=http_lib:connection_close(Headers),
    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
				      {303,Headers,Body}}),
    handle_connection(Session#session.clientclose,ServerClose,Req,Session).


%%% Received a 304 Status code ("Not Modified")
%%% Note:
%%% - The response MUST NOT contain a body.
%%% - The response MUST include the following header fields:
%%%   - Date, unless its omission is required
%%%   - ETag and/or Content-Location, if the header would have been sent
%%%        in a 200 response to the same request
%%%   - Expires, Cache-Control, and/or Vary, if the field-value might
%%%        differ from that sent in any previous response for the same
%%%        variant
status_not_modified(Headers,Body,Req,Session)
  when ((Req#request.settings)#client_settings.autoredirect)==true ->
    ServerClose=http_lib:connection_close(Headers),
    case Headers#res_headers.location of
	undefined ->
	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
					      {304,Headers,Body}}),
	    handle_connection(Session#session.clientclose,ServerClose,
			      Req,Session);
	RedirUrl ->
	    Scheme=Session#session.scheme,
	    case uri:parse(RedirUrl) of
		{error,Reason} ->
		    {error,Reason};
		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
		    NewReq=Req#request{redircount=Req#request.redircount+1,
				       address={Host,Port},pathquery=PathQuery},
		    handle_redirect(Session#session.clientclose,ServerClose,
				    NewReq,Session)
	    end
    end;
status_not_modified(Headers,Body,Req,Session) ->
    ServerClose=http_lib:connection_close(Headers),
    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
				      {304,Headers,Body}}),
    handle_connection(Session#session.clientclose,ServerClose,Req,Session).



%%% Received a 305 Status code ("Use Proxy")
%%% The requested resource MUST be accessed through the proxy given by the
%%% Location field
status_use_proxy(Headers,Body,Req,Session)
  when ((Req#request.settings)#client_settings.autoredirect)==true ->
    ServerClose=http_lib:connection_close(Headers),
    case Headers#res_headers.location of
	undefined ->
	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
					      {305,Headers,Body}}),
	    handle_connection(Session#session.clientclose,ServerClose,
			      Req,Session);
	RedirUrl ->
	    Scheme=Session#session.scheme,
	    case uri:parse(RedirUrl) of
		{error,Reason} ->
		    {error,Reason};
		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
		    NewReq=Req#request{redircount=Req#request.redircount+1,
				       address={Host,Port},pathquery=PathQuery},
		    handle_redirect(Session#session.clientclose,ServerClose,
				    NewReq,Session)
	    end
    end;
status_use_proxy(Headers,Body,Req,Session) ->
    ServerClose=http_lib:connection_close(Headers),
    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
				      {305,Headers,Body}}),
    handle_connection(Session#session.clientclose,ServerClose,Req,Session).


%%% Received a 307 Status code ("Temporary Redirect")
status_temporary_redirect(Headers,Body,Req,Session)
  when ((Req#request.settings)#client_settings.autoredirect)==true ->
    ServerClose=http_lib:connection_close(Headers),
    case Headers#res_headers.location of
	undefined ->
	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
					      {307,Headers,Body}}),
	    handle_connection(Session#session.clientclose,ServerClose,
			      Req,Session);
	RedirUrl ->
	    Scheme=Session#session.scheme,
	    case uri:parse(RedirUrl) of
		{error,Reason} ->
		    {error,Reason};
		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
		    NewReq=Req#request{redircount=Req#request.redircount+1,
				       address={Host,Port},pathquery=PathQuery},
		    handle_redirect(Session#session.clientclose,ServerClose,
				    NewReq,Session)
	    end
    end;
status_temporary_redirect(Headers,Body,Req,Session) ->
    ServerClose=http_lib:connection_close(Headers),
    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
				      {307,Headers,Body}}),
    handle_connection(Session#session.clientclose,ServerClose,Req,Session).



%%% Received a 503 Status code ("Service Unavailable")
%%%    The server is currently unable to handle the request due to a
%%%    temporary overloading or maintenance of the server. The implication
%%%    is that this is a temporary condition which will be alleviated after
%%%    some delay. If known, the length of the delay MAY be indicated in a
%%%    Retry-After header. If no Retry-After is given, the client SHOULD
%%%    handle the response as it would for a 500 response.
%% Note:
%% - This session is now considered busy, thus cancel any requests in the
%%   pipeline and close the session.
%% FIXME! Implement a user option to automatically retry if the 'Retry-After'
%%        header is given.
status_service_unavailable(Resp,Req,Session) ->
%    RetryAfter=Headers#res_headers.retry_after,
    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,Resp}),
    close_session(server_connection_close,Req,Session).


%%% Received a 50x Status code (~ "Service Error")
%%%   Response status codes beginning with the digit "5" indicate cases in
%%%   which the server is aware that it has erred or is incapable of
%%%   performing the request.
status_server_error_50x(Resp,Req,Session) ->
    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,Resp}),
    close_session(server_connection_close,Req,Session).


%%% Handles requests for redirects
%%% The redirected request might be:
%%% - FIXME! on another TCP session, another scheme
%%% - on the same TCP session, same scheme
%%% - on another TCP session , same scheme
%%% However, in all cases treat it as a new request, with redircount updated.
%%%
%%% The redirect may fail, but this not a reason to close this session.
%%% Instead return a error for this request, and continue as ok.
handle_redirect(ClientClose,ServerClose,Req,Session) ->
    case httpc_manager:request(Req) of
	{ok,_ReqId} -> % FIXME Should I perhaps reuse the Reqid?
	    handle_connection(ClientClose,ServerClose,Req,Session);
	{error,Reason} ->
	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
					      {error,Reason}}),
	    handle_connection(ClientClose,ServerClose,Req,Session)
    end.

%%% Check if the persistent connection flag is false (ie client request
%%% non-persistive connection), or if the server requires a closed connection
%%% (by sending a "Connection: close" header). If the connection required
%%% non-persistent, we may close the connection immediately.
handle_connection(ClientClose,ServerClose,Req,Session) ->
    case {ClientClose,ServerClose} of
	{false,false} ->
	    ok;
	{false,true} -> % The server requests this session to be closed.
	    close_session(server_connection_close,Req,Session);
	{true,_} -> % The client requested a non-persistent connection
	    close_session(client_connection_close,Req,Session)
    end.


%%% Close the session.
%%% We now have three cases:
%%% - Client request a non-persistent connection when initiating the request.
%%%   Session info not stored in httpc_manager
%%% - Server requests a non-persistent connection when answering a request.
%%%   No need to resend request, but there might be a pipeline.
%%% - Some kind of error
%%%   Close the session, we may then try resending all requests in the pipeline
%%%   including the current depending on the error.
%%% FIXME! Should not always abort the session (see close_session in
%%%     httpc_manager for more details)
close_session(client_connection_close,_Req,Session) ->
    http_lib:close(Session#session.scheme,Session#session.socket),
    stop;
close_session(server_connection_close,Req,Session) ->
    http_lib:close(Session#session.scheme,Session#session.socket),
    httpc_manager:abort_session(Req#request.address,Session#session.id,
			       aborted_request),
    stop.

exit_session(Address,Session,Reason) ->
    http_lib:close(Session#session.scheme,Session#session.socket),
    httpc_manager:abort_session(Address,Session#session.id,Reason),
    exit(normal).

%%% This is the "normal" case to close a persistent connection. I.e., there are
%%% no more requests waiting and the session was closed by the client, or
%%% server because of a timeout or user request.
exit_session_ok(Address,Session) ->
    http_lib:close(Session#session.scheme,Session#session.socket),
    exit_session_ok2(Address,Session#session.clientclose,Session#session.id).

exit_session_ok2(Address,ClientClose,Sid) ->
    case ClientClose of
	false ->
	    httpc_manager:close_session(Address,Sid);
	true ->
	    ok
    end,
    exit(normal).

%%% ============================================================================
%%% This is deprecated code, to be removed

format_time() ->
    {_,_,MicroSecs}=TS=now(),
    {{Y,Mon,D},{H,M,S}}=calendar:now_to_universal_time(TS),
    lists:flatten(io_lib:format("~4.4.0w-~2.2.0w-~2.2.0w,~2.2.0w:~2.2.0w:~6.3.0f",
				[Y,Mon,D,H,M,S+(MicroSecs/1000000)])).

%%% Read more data from the open socket.
%%% Two different read functions is used because for the {active, once} socket
%%% option is (currently) not available for SSL...
%%% FIXME
% read_more_data(http,Socket,Timeout) ->
%     io:format("read_more_data(ip_comm) -> "
% 	"~n   set active = 'once' and "
% 	"await a chunk data", []),
%     http_lib:setopts(Socket, [{active,once}]),
%     read_more_data_ipcomm(Socket,Timeout);
% read_more_data(https,Socket,Timeout) ->
%     case ssl:recv(Socket,0,Timeout) of
% 	{ok,MoreData} ->
% 	    MoreData;
% 	{error,closed} ->
% 	    throw({error, session_remotely_closed});
% 	{error,etimedout} ->
% 	    throw({error, session_local_timeout});
% 	{error,Reason} ->
% 	    throw({error, Reason});
% 	Other ->
% 	    throw({error, Other})
%     end.

% %%% Send any incoming requests on the open session immediately
% read_more_data_ipcomm(Socket,Timeout) ->
%     receive
% 	{tcp,Socket,MoreData} ->
% %	    ?vtrace("read_more_data(ip_comm) -> got some data:~p",
% %		[MoreData]),
% 	    MoreData;
% 	{tcp_closed,Socket} ->
% %	    ?vtrace("read_more_data(ip_comm) -> socket closed",[]),
% 	    throw({error,session_remotely_closed});
% 	{tcp_error,Socket,Reason} ->
% %	    ?vtrace("read_more_data(ip_comm) -> ~p socket error: ~p",
% %		[self(),Reason]),
% 	    throw({error, Reason});
% 	stop ->
% 	    throw({error, user_req})
%     after Timeout ->
% 	    throw({error, session_local_timeout})
%     end.