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