1%% ``Licensed under the Apache License, Version 2.0 (the "License");
2%% you may not use this file except in compliance with the License.
3%% You may obtain a copy of the License at
4%%
5%%     http://www.apache.org/licenses/LICENSE-2.0
6%%
7%% Unless required by applicable law or agreed to in writing, software
8%% distributed under the License is distributed on an "AS IS" BASIS,
9%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10%% See the License for the specific language governing permissions and
11%% limitations under the License.
12%%
13%% The Initial Developer of the Original Code is Mobile Arts AB
14%% Portions created by Mobile Arts are Copyright 2002, Mobile Arts AB
15%% All Rights Reserved.''
16%%
17
18%%% TODO:
19%%% - If an error is returned when sending a request, don't use this
20%%%   session anymore.
21%%% - Closing of sessions not properly implemented for some cases
22
23%%% File    : httpc_handler.erl
24%%% Author  : Johan Blom <johan.blom@mobilearts.se>
25%%% Description : Handles HTTP client responses, for a single TCP session
26%%% Created :  4 Mar 2002 by Johan Blom
27
28-module(httpc_handler).
29
30-include("http.hrl").
31-include("jnets_httpd.hrl").
32
33-export([init_connection/2,http_request/2]).
34
35%%% ==========================================================================
36%%% "Main" function in the spawned process for the session.
37init_connection(Req,Session) when record(Req,request) ->
38    case catch http_lib:connect(Req) of
39	{ok,Socket} ->
40	    case catch http_request(Req,Socket) of
41		ok ->
42		    case Session#session.clientclose of
43			true ->
44			    ok;
45			false ->
46			    httpc_manager:register_socket(Req#request.address,
47							  Session#session.id,
48							  Socket)
49		    end,
50		    next_response_with_request(Req,
51					       Session#session{socket=Socket});
52		{error,Reason} -> % Not possible to use new session
53		    gen_server:cast(Req#request.from,
54				    {Req#request.ref,Req#request.id,{error,Reason}}),
55		    exit_session_ok(Req#request.address,
56				    Session#session{socket=Socket})
57	    end;
58	{error,Reason} -> % Not possible to set up new session
59	    gen_server:cast(Req#request.from,
60			    {Req#request.ref,Req#request.id,{error,Reason}}),
61	    exit_session_ok2(Req#request.address,
62			     Session#session.clientclose,Session#session.id)
63    end.
64
65next_response_with_request(Req,Session) ->
66    Timeout=(Req#request.settings)#client_settings.timeout,
67    case catch read(Timeout,Session#session.scheme,Session#session.socket) of
68	{Status,Headers,Body} ->
69	    NewReq=handle_response({Status,Headers,Body},Timeout,Req,Session),
70	    next_response_with_request(NewReq,Session);
71	{error,Reason} ->
72	    gen_server:cast(Req#request.from,
73			    {Req#request.ref,Req#request.id,{error,Reason}}),
74	    exit_session(Req#request.address,Session,aborted_request);
75	{'EXIT',Reason} ->
76	    gen_server:cast(Req#request.from,
77			    {Req#request.ref,Req#request.id,{error,Reason}}),
78	    exit_session(Req#request.address,Session,aborted_request)
79    end.
80
81handle_response(Response,Timeout,Req,Session) ->
82    case http_response(Response,Req,Session) of
83	ok ->
84	    next_response(Timeout,Req#request.address,Session);
85	stop ->
86	    exit(normal);
87	{error,Reason} ->
88	    gen_server:cast(Req#request.from,
89			    {Req#request.ref,Req#request.id,{error,Reason}}),
90	    exit_session(Req#request.address,Session,aborted_request)
91    end.
92
93
94
95%%% Wait for the next respond until
96%%% - session is closed by the other side
97%%%      => set up a new a session, if there are pending requests in the que
98%%% - "Connection:close" header is received
99%%%      => close the connection (release socket) then
100%%%         set up a new a session, if there are pending requests in the que
101%%%
102%%% Note:
103%%% - When invoked there are no pending responses on received requests.
104%%% - Never close the session explicitly, let it timeout instead!
105next_response(Timeout,Address,Session) ->
106    case httpc_manager:next_request(Address,Session#session.id) of
107	no_more_requests ->
108	    %% There are no more pending responses, now just wait for
109	    %% timeout or a new response.
110	    case catch read(Timeout,
111			    Session#session.scheme,Session#session.socket) of
112		{error,Reason} when Reason==session_remotely_closed;
113				    Reason==session_local_timeout ->
114		    exit_session_ok(Address,Session);
115		{error,Reason} ->
116		    exit_session(Address,Session,aborted_request);
117		{'EXIT',Reason} ->
118		    exit_session(Address,Session,aborted_request);
119		{Status2,Headers2,Body2} ->
120		    case httpc_manager:next_request(Address,
121						    Session#session.id) of
122			no_more_requests -> % Should not happen!
123			    exit_session(Address,Session,aborted_request);
124			{error,Reason} -> % Should not happen!
125			    exit_session(Address,Session,aborted_request);
126			NewReq ->
127			    handle_response({Status2,Headers2,Body2},
128					    Timeout,NewReq,Session)
129		    end
130	    end;
131	{error,Reason} -> % The connection has been closed by httpc_manager
132	    exit_session(Address,Session,aborted_request);
133	NewReq ->
134	    NewReq
135    end.
136
137%% ===========================================================================
138%% Internals
139
140%%% Read in and parse response data from the socket
141read(Timeout,SockType,Socket) ->
142    Info=#response{scheme=SockType,socket=Socket},
143    http_lib:setopts(SockType,Socket,[{packet, http}]),
144    Info1=read_response(SockType,Socket,Info,Timeout),
145    http_lib:setopts(SockType,Socket,[binary,{packet, raw}]),
146    case (Info1#response.headers)#res_headers.content_type of
147	"multipart/byteranges"++Param ->
148	    range_response_body(Info1,Timeout,Param);
149	_ ->
150	    #response{status=Status2,headers=Headers2,body=Body2}=
151		http_lib:read_client_body(Info1,Timeout),
152	    {Status2,Headers2,Body2}
153    end.
154
155
156%%% From RFC 2616:
157%%%      Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
158%%%      HTTP-Version   = "HTTP" "/" 1*DIGIT "." 1*DIGIT
159%%%      Status-Code    = 3DIGIT
160%%%      Reason-Phrase  = *<TEXT, excluding CR, LF>
161read_response(SockType,Socket,Info,Timeout) ->
162    case http_lib:recv0(SockType,Socket,Timeout) of
163	{ok,{http_response,{1,VerMin}, Status, _Phrase}} when VerMin==0;
164							      VerMin==1 ->
165	    Info1=Info#response{status=Status,http_version=VerMin},
166	    http_lib:read_client_headers(Info1,Timeout);
167	{ok,{http_response,_Version, _Status, _Phrase}} ->
168	    throw({error,bad_status_line});
169	{error, timeout} ->
170	    throw({error,session_local_timeout});
171	{error, Reason} when Reason==closed;Reason==enotconn ->
172	    throw({error,session_remotely_closed});
173	{error, Reason} ->
174	    throw({error,Reason})
175    end.
176
177%%% From RFC 2616, Section 4.4, Page 34
178%% 4.If the message uses the media type "multipart/byteranges", and the
179%%   transfer-length is not otherwise specified, then this self-
180%%   delimiting media type defines the transfer-length. This media type
181%%   MUST NOT be used unless the sender knows that the recipient can parse
182%%   it; the presence in a request of a Range header with multiple byte-
183%%   range specifiers from a 1.1 client implies that the client can parse
184%%   multipart/byteranges responses.
185%%% FIXME !!
186range_response_body(Info,Timeout,Param) ->
187    Headers=Info#response.headers,
188    case {Headers#res_headers.content_length,
189	  Headers#res_headers.transfer_encoding} of
190	{undefined,undefined} ->
191	    #response{status=Status2,headers=Headers2,body=Body2}=
192		http_lib:read_client_multipartrange_body(Info,Param,Timeout),
193	    {Status2,Headers2,Body2};
194	_ ->
195	    #response{status=Status2,headers=Headers2,body=Body2}=
196		http_lib:read_client_body(Info,Timeout),
197	    {Status2,Headers2,Body2}
198    end.
199
200
201%%% ----------------------------------------------------------------------------
202%%% Host: field is required when addressing multi-homed sites ...
203%%% It must not be present when the request is being made to a proxy.
204http_request(#request{method=Method,id=Id,
205		      scheme=Scheme,address={Host,Port},pathquery=PathQuery,
206		      headers=Headers, content={ContentType,Body},
207		      settings=Settings},
208	     Socket) ->
209    PostData=
210	if
211	    Method==post;Method==put ->
212		case Headers#req_headers.expect of
213		    "100-continue" ->
214			content_type_header(ContentType) ++
215			    content_length_header(length(Body)) ++
216			    "\r\n";
217		    _ ->
218			content_type_header(ContentType) ++
219			    content_length_header(length(Body)) ++
220			    "\r\n" ++ Body
221		end;
222	    true ->
223		"\r\n"
224	end,
225    Message=
226	case useProxy(Settings#client_settings.useproxy,
227		      {Scheme,Host,Port,PathQuery}) of
228	    false ->
229		method(Method)++" "++PathQuery++" HTTP/1.1\r\n"++
230		    host_header(Host)++te_header()++
231		    headers(Headers) ++ PostData;
232	    AbsURI ->
233		method(Method)++" "++AbsURI++" HTTP/1.1\r\n"++
234		    te_header()++
235		    headers(Headers)++PostData
236	end,
237    http_lib:send(Scheme,Socket,Message).
238
239useProxy(false,_) ->
240    false;
241useProxy(true,{Scheme,Host,Port,PathQuery}) ->
242    [atom_to_list(Scheme),"://",Host,":",integer_to_list(Port),PathQuery].
243
244
245
246headers(#req_headers{expect=Expect,
247		     other=Other}) ->
248    H1=case Expect of
249	   undefined ->[];
250	   _ -> "Expect: "++Expect++"\r\n"
251       end,
252    H1++headers_other(Other).
253
254
255headers_other([]) ->
256    [];
257headers_other([{Key,Value}|Rest]) when atom(Key) ->
258    Head = atom_to_list(Key)++": "++Value++"\r\n",
259    Head ++ headers_other(Rest);
260headers_other([{Key,Value}|Rest]) ->
261    Head = Key++": "++Value++"\r\n",
262    Head ++ headers_other(Rest).
263
264host_header(Host) ->
265    "Host: "++lists:concat([Host])++"\r\n".
266content_type_header(ContentType) ->
267    "Content-Type: " ++ ContentType ++ "\r\n".
268content_length_header(ContentLength) ->
269    "Content-Length: "++integer_to_list(ContentLength) ++ "\r\n".
270te_header() ->
271    "TE: \r\n".
272
273method(Method) ->
274    httpd_util:to_upper(atom_to_list(Method)).
275
276
277%%% ----------------------------------------------------------------------------
278http_response({Status,Headers,Body},Req,Session) ->
279    case Status of
280	100 ->
281	    status_continue(Req,Session);
282	200 ->
283	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
284					      {Status,Headers,Body}}),
285	    ServerClose=http_lib:connection_close(Headers),
286	    handle_connection(Session#session.clientclose,ServerClose,
287			      Req,Session);
288	300 -> status_multiple_choices(Headers,Body,Req,Session);
289	301 -> status_moved_permanently(Req#request.method,
290					Headers,Body,Req,Session);
291	302 -> status_found(Headers,Body,Req,Session);
292	303 -> status_see_other(Headers,Body,Req,Session);
293	304 -> status_not_modified(Headers,Body,Req,Session);
294	305 -> status_use_proxy(Headers,Body,Req,Session);
295	%% 306 This Status code is not used in HTTP 1.1
296	307 -> status_temporary_redirect(Headers,Body,Req,Session);
297	503 -> status_service_unavailable({Status,Headers,Body},Req,Session);
298	Status50x when Status50x==500;Status50x==501;Status50x==502;
299		       Status50x==504;Status50x==505 ->
300	    status_server_error_50x({Status,Headers,Body},Req,Session);
301	_ -> % FIXME May want to take some action on other Status codes as well
302	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
303					      {Status,Headers,Body}}),
304	    ServerClose=http_lib:connection_close(Headers),
305	    handle_connection(Session#session.clientclose,ServerClose,
306			      Req,Session)
307    end.
308
309
310%%% Status code dependent functions.
311
312%%% Received a 100 Status code ("Continue")
313%%% From RFC2616
314%%% The client SHOULD continue with its request. This interim response is
315%%% used to inform the client that the initial part of the request has
316%%% been received and has not yet been rejected by the server. The client
317%%% SHOULD continue by sending the remainder of the request or, if the
318%%% request has already been completed, ignore this response. The server
319%%% MUST send a final response after the request has been completed. See
320%%% section 8.2.3 for detailed discussion of the use and handling of this
321%%% status code.
322status_continue(Req,Session) ->
323    {_,Body}=Req#request.content,
324    http_lib:send(Session#session.scheme,Session#session.socket,Body),
325    next_response_with_request(Req,Session).
326
327
328%%% Received a 300 Status code ("Multiple Choices")
329%%% The resource is located in any one of a set of locations
330%%% - If a 'Location' header is present (preserved server choice), use that
331%%%   to automatically redirect to the given URL
332%%% - else if the Content-Type/Body both are non-empty let the user agent make
333%%%   the choice and thus return a response with status 300
334%%% Note:
335%%% - If response to a HEAD request, the Content-Type/Body both should be empty.
336%%% - The behaviour on an empty Content-Type or Body is unspecified.
337%%%   However, e.g. "Apache/1.3" servers returns both empty if the header
338%%%   'if-modified-since: Date' was sent in the request and the content is
339%%%   "not modified" (instead of 304). Thus implicitly giving the cache as the
340%%%   only choice.
341status_multiple_choices(Headers,Body,Req,Session)
342  when ((Req#request.settings)#client_settings.autoredirect)==true ->
343    ServerClose=http_lib:connection_close(Headers),
344    case Headers#res_headers.location of
345	undefined ->
346	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
347					      {300,Headers,Body}}),
348	    handle_connection(Session#session.clientclose,ServerClose,
349			      Req,Session);
350	RedirUrl ->
351	    Scheme=Session#session.scheme,
352	    case uri:parse(RedirUrl) of
353		{error,Reason} ->
354		    {error,Reason};
355		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
356		    NewReq=Req#request{redircount=Req#request.redircount+1,
357				       address={Host,Port},pathquery=PathQuery},
358		    handle_redirect(Session#session.clientclose,ServerClose,
359				    NewReq,Session)
360	    end
361    end;
362status_multiple_choices(Headers,Body,Req,Session) ->
363    ServerClose=http_lib:connection_close(Headers),
364    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
365				      {300,Headers,Body}}),
366    handle_connection(Session#session.clientclose,ServerClose,Req,Session).
367
368
369%%% Received a 301 Status code ("Moved Permanently")
370%%% The resource has been assigned a new permanent URI
371%%% - If a 'Location' header is present, use that to automatically redirect to
372%%%   the given URL if GET or HEAD request
373%%% - else return
374%%% Note:
375%%% - The Body should contain a short hypertext note with a hyperlink to the
376%%%   new URI. Return this if Content-Type acceptable (some HTTP servers doesn't
377%%%   deal properly with Accept headers)
378status_moved_permanently(Method,Headers,Body,Req,Session)
379  when (((Req#request.settings)#client_settings.autoredirect)==true) and
380       (Method==get) or (Method==head) ->
381    ServerClose=http_lib:connection_close(Headers),
382    case Headers#res_headers.location of
383	undefined ->
384	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
385					      {301,Headers,Body}}),
386	    handle_connection(Session#session.clientclose,ServerClose,
387			      Req,Session);
388	RedirUrl ->
389	    Scheme=Session#session.scheme,
390	    case uri:parse(RedirUrl) of
391		{error,Reason} ->
392		    {error,Reason};
393		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
394		    NewReq=Req#request{redircount=Req#request.redircount+1,
395				       address={Host,Port},pathquery=PathQuery},
396		    handle_redirect(Session#session.clientclose,ServerClose,
397				    NewReq,Session)
398	    end
399    end;
400status_moved_permanently(_Method,Headers,Body,Req,Session) ->
401    ServerClose=http_lib:connection_close(Headers),
402    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
403				      {301,Headers,Body}}),
404    handle_connection(Session#session.clientclose,ServerClose,Req,Session).
405
406
407%%% Received a 302 Status code ("Found")
408%%% The requested resource resides temporarily under a different URI.
409%%% Note:
410%%% - Only cacheable if indicated by a Cache-Control or Expires header
411status_found(Headers,Body,Req,Session)
412  when ((Req#request.settings)#client_settings.autoredirect)==true ->
413    ServerClose=http_lib:connection_close(Headers),
414    case Headers#res_headers.location of
415	undefined ->
416	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
417					      {302,Headers,Body}}),
418	    handle_connection(Session#session.clientclose,ServerClose,
419			      Req,Session);
420	RedirUrl ->
421	    Scheme=Session#session.scheme,
422	    case uri:parse(RedirUrl) of
423		{error,Reason} ->
424		    {error,Reason};
425		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
426		    NewReq=Req#request{redircount=Req#request.redircount+1,
427				       address={Host,Port},pathquery=PathQuery},
428		    handle_redirect(Session#session.clientclose,ServerClose,
429				    NewReq,Session)
430	    end
431    end;
432status_found(Headers,Body,Req,Session) ->
433    ServerClose=http_lib:connection_close(Headers),
434    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
435				      {302,Headers,Body}}),
436    handle_connection(Session#session.clientclose,ServerClose,Req,Session).
437
438%%% Received a 303 Status code ("See Other")
439%%% The request found under a different URI and should be retrieved using GET
440%%% Note:
441%%% - Must not be cached
442status_see_other(Headers,Body,Req,Session)
443  when ((Req#request.settings)#client_settings.autoredirect)==true ->
444    ServerClose=http_lib:connection_close(Headers),
445    case Headers#res_headers.location of
446	undefined ->
447	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
448					      {303,Headers,Body}}),
449	    handle_connection(Session#session.clientclose,ServerClose,
450			      Req,Session);
451	RedirUrl ->
452	    Scheme=Session#session.scheme,
453	    case uri:parse(RedirUrl) of
454		{error,Reason} ->
455		    {error,Reason};
456		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
457		    NewReq=Req#request{redircount=Req#request.redircount+1,
458				       method=get,
459				       address={Host,Port},pathquery=PathQuery},
460		    handle_redirect(Session#session.clientclose,ServerClose,
461				    NewReq,Session)
462	    end
463    end;
464status_see_other(Headers,Body,Req,Session) ->
465    ServerClose=http_lib:connection_close(Headers),
466    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
467				      {303,Headers,Body}}),
468    handle_connection(Session#session.clientclose,ServerClose,Req,Session).
469
470
471%%% Received a 304 Status code ("Not Modified")
472%%% Note:
473%%% - The response MUST NOT contain a body.
474%%% - The response MUST include the following header fields:
475%%%   - Date, unless its omission is required
476%%%   - ETag and/or Content-Location, if the header would have been sent
477%%%        in a 200 response to the same request
478%%%   - Expires, Cache-Control, and/or Vary, if the field-value might
479%%%        differ from that sent in any previous response for the same
480%%%        variant
481status_not_modified(Headers,Body,Req,Session)
482  when ((Req#request.settings)#client_settings.autoredirect)==true ->
483    ServerClose=http_lib:connection_close(Headers),
484    case Headers#res_headers.location of
485	undefined ->
486	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
487					      {304,Headers,Body}}),
488	    handle_connection(Session#session.clientclose,ServerClose,
489			      Req,Session);
490	RedirUrl ->
491	    Scheme=Session#session.scheme,
492	    case uri:parse(RedirUrl) of
493		{error,Reason} ->
494		    {error,Reason};
495		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
496		    NewReq=Req#request{redircount=Req#request.redircount+1,
497				       address={Host,Port},pathquery=PathQuery},
498		    handle_redirect(Session#session.clientclose,ServerClose,
499				    NewReq,Session)
500	    end
501    end;
502status_not_modified(Headers,Body,Req,Session) ->
503    ServerClose=http_lib:connection_close(Headers),
504    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
505				      {304,Headers,Body}}),
506    handle_connection(Session#session.clientclose,ServerClose,Req,Session).
507
508
509
510%%% Received a 305 Status code ("Use Proxy")
511%%% The requested resource MUST be accessed through the proxy given by the
512%%% Location field
513status_use_proxy(Headers,Body,Req,Session)
514  when ((Req#request.settings)#client_settings.autoredirect)==true ->
515    ServerClose=http_lib:connection_close(Headers),
516    case Headers#res_headers.location of
517	undefined ->
518	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
519					      {305,Headers,Body}}),
520	    handle_connection(Session#session.clientclose,ServerClose,
521			      Req,Session);
522	RedirUrl ->
523	    Scheme=Session#session.scheme,
524	    case uri:parse(RedirUrl) of
525		{error,Reason} ->
526		    {error,Reason};
527		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
528		    NewReq=Req#request{redircount=Req#request.redircount+1,
529				       address={Host,Port},pathquery=PathQuery},
530		    handle_redirect(Session#session.clientclose,ServerClose,
531				    NewReq,Session)
532	    end
533    end;
534status_use_proxy(Headers,Body,Req,Session) ->
535    ServerClose=http_lib:connection_close(Headers),
536    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
537				      {305,Headers,Body}}),
538    handle_connection(Session#session.clientclose,ServerClose,Req,Session).
539
540
541%%% Received a 307 Status code ("Temporary Redirect")
542status_temporary_redirect(Headers,Body,Req,Session)
543  when ((Req#request.settings)#client_settings.autoredirect)==true ->
544    ServerClose=http_lib:connection_close(Headers),
545    case Headers#res_headers.location of
546	undefined ->
547	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
548					      {307,Headers,Body}}),
549	    handle_connection(Session#session.clientclose,ServerClose,
550			      Req,Session);
551	RedirUrl ->
552	    Scheme=Session#session.scheme,
553	    case uri:parse(RedirUrl) of
554		{error,Reason} ->
555		    {error,Reason};
556		{Scheme,Host,Port,PathQuery} -> % Automatic redirection
557		    NewReq=Req#request{redircount=Req#request.redircount+1,
558				       address={Host,Port},pathquery=PathQuery},
559		    handle_redirect(Session#session.clientclose,ServerClose,
560				    NewReq,Session)
561	    end
562    end;
563status_temporary_redirect(Headers,Body,Req,Session) ->
564    ServerClose=http_lib:connection_close(Headers),
565    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
566				      {307,Headers,Body}}),
567    handle_connection(Session#session.clientclose,ServerClose,Req,Session).
568
569
570
571%%% Received a 503 Status code ("Service Unavailable")
572%%%    The server is currently unable to handle the request due to a
573%%%    temporary overloading or maintenance of the server. The implication
574%%%    is that this is a temporary condition which will be alleviated after
575%%%    some delay. If known, the length of the delay MAY be indicated in a
576%%%    Retry-After header. If no Retry-After is given, the client SHOULD
577%%%    handle the response as it would for a 500 response.
578%% Note:
579%% - This session is now considered busy, thus cancel any requests in the
580%%   pipeline and close the session.
581%% FIXME! Implement a user option to automatically retry if the 'Retry-After'
582%%        header is given.
583status_service_unavailable(Resp,Req,Session) ->
584%    RetryAfter=Headers#res_headers.retry_after,
585    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,Resp}),
586    close_session(server_connection_close,Req,Session).
587
588
589%%% Received a 50x Status code (~ "Service Error")
590%%%   Response status codes beginning with the digit "5" indicate cases in
591%%%   which the server is aware that it has erred or is incapable of
592%%%   performing the request.
593status_server_error_50x(Resp,Req,Session) ->
594    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,Resp}),
595    close_session(server_connection_close,Req,Session).
596
597
598%%% Handles requests for redirects
599%%% The redirected request might be:
600%%% - FIXME! on another TCP session, another scheme
601%%% - on the same TCP session, same scheme
602%%% - on another TCP session , same scheme
603%%% However, in all cases treat it as a new request, with redircount updated.
604%%%
605%%% The redirect may fail, but this not a reason to close this session.
606%%% Instead return a error for this request, and continue as ok.
607handle_redirect(ClientClose,ServerClose,Req,Session) ->
608    case httpc_manager:request(Req) of
609	{ok,_ReqId} -> % FIXME Should I perhaps reuse the Reqid?
610	    handle_connection(ClientClose,ServerClose,Req,Session);
611	{error,Reason} ->
612	    gen_server:cast(Req#request.from,{Req#request.ref,Req#request.id,
613					      {error,Reason}}),
614	    handle_connection(ClientClose,ServerClose,Req,Session)
615    end.
616
617%%% Check if the persistent connection flag is false (ie client request
618%%% non-persistive connection), or if the server requires a closed connection
619%%% (by sending a "Connection: close" header). If the connection required
620%%% non-persistent, we may close the connection immediately.
621handle_connection(ClientClose,ServerClose,Req,Session) ->
622    case {ClientClose,ServerClose} of
623	{false,false} ->
624	    ok;
625	{false,true} -> % The server requests this session to be closed.
626	    close_session(server_connection_close,Req,Session);
627	{true,_} -> % The client requested a non-persistent connection
628	    close_session(client_connection_close,Req,Session)
629    end.
630
631
632%%% Close the session.
633%%% We now have three cases:
634%%% - Client request a non-persistent connection when initiating the request.
635%%%   Session info not stored in httpc_manager
636%%% - Server requests a non-persistent connection when answering a request.
637%%%   No need to resend request, but there might be a pipeline.
638%%% - Some kind of error
639%%%   Close the session, we may then try resending all requests in the pipeline
640%%%   including the current depending on the error.
641%%% FIXME! Should not always abort the session (see close_session in
642%%%     httpc_manager for more details)
643close_session(client_connection_close,_Req,Session) ->
644    http_lib:close(Session#session.scheme,Session#session.socket),
645    stop;
646close_session(server_connection_close,Req,Session) ->
647    http_lib:close(Session#session.scheme,Session#session.socket),
648    httpc_manager:abort_session(Req#request.address,Session#session.id,
649			       aborted_request),
650    stop.
651
652exit_session(Address,Session,Reason) ->
653    http_lib:close(Session#session.scheme,Session#session.socket),
654    httpc_manager:abort_session(Address,Session#session.id,Reason),
655    exit(normal).
656
657%%% This is the "normal" case to close a persistent connection. I.e., there are
658%%% no more requests waiting and the session was closed by the client, or
659%%% server because of a timeout or user request.
660exit_session_ok(Address,Session) ->
661    http_lib:close(Session#session.scheme,Session#session.socket),
662    exit_session_ok2(Address,Session#session.clientclose,Session#session.id).
663
664exit_session_ok2(Address,ClientClose,Sid) ->
665    case ClientClose of
666	false ->
667	    httpc_manager:close_session(Address,Sid);
668	true ->
669	    ok
670    end,
671    exit(normal).
672
673%%% ============================================================================
674%%% This is deprecated code, to be removed
675
676format_time() ->
677    {_,_,MicroSecs}=TS=now(),
678    {{Y,Mon,D},{H,M,S}}=calendar:now_to_universal_time(TS),
679    lists:flatten(io_lib:format("~4.4.0w-~2.2.0w-~2.2.0w,~2.2.0w:~2.2.0w:~6.3.0f",
680				[Y,Mon,D,H,M,S+(MicroSecs/1000000)])).
681
682%%% Read more data from the open socket.
683%%% Two different read functions is used because for the {active, once} socket
684%%% option is (currently) not available for SSL...
685%%% FIXME
686% read_more_data(http,Socket,Timeout) ->
687%     io:format("read_more_data(ip_comm) -> "
688% 	"~n   set active = 'once' and "
689% 	"await a chunk data", []),
690%     http_lib:setopts(Socket, [{active,once}]),
691%     read_more_data_ipcomm(Socket,Timeout);
692% read_more_data(https,Socket,Timeout) ->
693%     case ssl:recv(Socket,0,Timeout) of
694% 	{ok,MoreData} ->
695% 	    MoreData;
696% 	{error,closed} ->
697% 	    throw({error, session_remotely_closed});
698% 	{error,etimedout} ->
699% 	    throw({error, session_local_timeout});
700% 	{error,Reason} ->
701% 	    throw({error, Reason});
702% 	Other ->
703% 	    throw({error, Other})
704%     end.
705
706% %%% Send any incoming requests on the open session immediately
707% read_more_data_ipcomm(Socket,Timeout) ->
708%     receive
709% 	{tcp,Socket,MoreData} ->
710% %	    ?vtrace("read_more_data(ip_comm) -> got some data:~p",
711% %		[MoreData]),
712% 	    MoreData;
713% 	{tcp_closed,Socket} ->
714% %	    ?vtrace("read_more_data(ip_comm) -> socket closed",[]),
715% 	    throw({error,session_remotely_closed});
716% 	{tcp_error,Socket,Reason} ->
717% %	    ?vtrace("read_more_data(ip_comm) -> ~p socket error: ~p",
718% %		[self(),Reason]),
719% 	    throw({error, Reason});
720% 	stop ->
721% 	    throw({error, user_req})
722%     after Timeout ->
723% 	    throw({error, session_local_timeout})
724%     end.
725