/src/lhttpc_client.erl
Erlang | 666 lines | 548 code | 40 blank | 78 comment | 0 complexity | ccf58713f94e3fd95a3823cdd525fc73 MD5 | raw file
1%%% ---------------------------------------------------------------------------- 2%%% Copyright (c) 2009, Erlang Training and Consulting Ltd. 3%%% All rights reserved. 4%%% 5%%% Redistribution and use in source and binary forms, with or without 6%%% modification, are permitted provided that the following conditions are met: 7%%% * Redistributions of source code must retain the above copyright 8%%% notice, this list of conditions and the following disclaimer. 9%%% * Redistributions in binary form must reproduce the above copyright 10%%% notice, this list of conditions and the following disclaimer in the 11%%% documentation and/or other materials provided with the distribution. 12%%% * Neither the name of Erlang Training and Consulting Ltd. nor the 13%%% names of its contributors may be used to endorse or promote products 14%%% derived from this software without specific prior written permission. 15%%% 16%%% THIS SOFTWARE IS PROVIDED BY Erlang Training and Consulting Ltd. ''AS IS'' 17%%% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18%%% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19%%% ARE DISCLAIMED. IN NO EVENT SHALL Erlang Training and Consulting Ltd. BE 20%%% LIABLE SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR 21%%% BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22%%% WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 23%%% OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24%%% ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25%%% ---------------------------------------------------------------------------- 26 27%%% @private 28%%% @author Oscar Hellström <oscar@hellstrom.st> 29%%% @doc 30%%% This module implements the HTTP request handling. This should normally 31%%% not be called directly since it should be spawned by the lhttpc module. 32-module(lhttpc_client). 33 34-export([request/9]). 35 36-include("lhttpc_types.hrl"). 37 38-record(client_state, { 39 host :: string(), 40 port = 80 :: integer(), 41 ssl = false :: true | false, 42 method :: string(), 43 request :: iolist(), 44 request_headers :: headers(), 45 socket, 46 connect_timeout = infinity :: timeout(), 47 connect_options = [] :: [any()], 48 attempts :: integer(), 49 requester :: pid(), 50 partial_upload = false :: true | false, 51 chunked_upload = false :: true | false, 52 upload_window :: non_neg_integer() | infinity, 53 partial_download = false :: true | false, 54 download_window = infinity :: timeout(), 55 part_size :: non_neg_integer() | infinity 56 %% in case of infinity we read whatever data we can get from 57 %% the wire at that point or in case of chunked one chunk 58 }). 59 60-define(CONNECTION_HDR(HDRS, DEFAULT), 61 string:to_lower(lhttpc_lib:header_value("connection", HDRS, DEFAULT))). 62 63-spec request(pid(), string(), 1..65535, true | false, string(), 64 string() | atom(), headers(), iolist(), [option()]) -> no_return(). 65%% @spec (From, Host, Port, Ssl, Path, Method, Hdrs, RequestBody, Options) -> ok 66%% From = pid() 67%% Host = string() 68%% Port = integer() 69%% Ssl = boolean() 70%% Method = atom() | string() 71%% Hdrs = [Header] 72%% Header = {string() | atom(), string()} 73%% Body = iolist() 74%% Options = [Option] 75%% Option = {connect_timeout, Milliseconds} 76%% @end 77request(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) -> 78 Result = try 79 execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) 80 catch 81 Reason -> 82 {response, self(), {error, Reason}}; 83 error:closed -> 84 {response, self(), {error, connection_closed}}; 85 error:Error -> 86 {exit, self(), {Error, erlang:get_stacktrace()}} 87 end, 88 case Result of 89 {response, _, {ok, {no_return, _}}} -> ok; 90 _Else -> From ! Result 91 end, 92 % Don't send back {'EXIT', self(), normal} if the process 93 % calling us is trapping exits 94 unlink(From), 95 ok. 96 97execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) -> 98 UploadWindowSize = proplists:get_value(partial_upload, Options), 99 PartialUpload = proplists:is_defined(partial_upload, Options), 100 PartialDownload = proplists:is_defined(partial_download, Options), 101 PartialDownloadOptions = proplists:get_value(partial_download, Options, []), 102 NormalizedMethod = lhttpc_lib:normalize_method(Method), 103 {ChunkedUpload, Request} = lhttpc_lib:format_request(Path, NormalizedMethod, 104 Hdrs, Host, Port, Body, PartialUpload), 105 SocketRequest = {socket, self(), Host, Port, Ssl}, 106 Socket = case gen_server:call(lhttpc_manager, SocketRequest, infinity) of 107 {ok, S} -> S; % Re-using HTTP/1.1 connections 108 no_socket -> undefined % Opening a new HTTP/1.1 connection 109 end, 110 State = #client_state{ 111 host = Host, 112 port = Port, 113 ssl = Ssl, 114 method = NormalizedMethod, 115 request = Request, 116 requester = From, 117 request_headers = Hdrs, 118 socket = Socket, 119 connect_timeout = proplists:get_value(connect_timeout, Options, 120 infinity), 121 connect_options = proplists:get_value(connect_options, Options, []), 122 attempts = 1 + proplists:get_value(send_retry, Options, 1), 123 partial_upload = PartialUpload, 124 upload_window = UploadWindowSize, 125 chunked_upload = ChunkedUpload, 126 partial_download = PartialDownload, 127 download_window = proplists:get_value(window_size, 128 PartialDownloadOptions, infinity), 129 part_size = proplists:get_value(part_size, 130 PartialDownloadOptions, infinity) 131 }, 132 Response = case send_request(State) of 133 {R, undefined} -> 134 {ok, R}; 135 {R, NewSocket} -> 136 % The socket we ended up doing the request over is returned 137 % here, it might be the same as Socket, but we don't know. 138 % I've noticed that we don't want to give send sockets that we 139 % can't change the controlling process for to the manager. This 140 % really shouldn't fail, but it could do if: 141 % * The socket was closed remotely already 142 % * Due to an error in this module (returning dead sockets for 143 % instance) 144 ManagerPid = whereis(lhttpc_manager), 145 case lhttpc_sock:controlling_process(NewSocket, ManagerPid, Ssl) of 146 ok -> 147 gen_server:cast(lhttpc_manager, 148 {done, Host, Port, Ssl, NewSocket}); 149 _ -> 150 ok 151 end, 152 {ok, R} 153 end, 154 {response, self(), Response}. 155 156send_request(#client_state{attempts = 0}) -> 157 % Don't try again if the number of allowed attempts is 0. 158 throw(connection_closed); 159send_request(#client_state{socket = undefined} = State) -> 160 Host = State#client_state.host, 161 Port = State#client_state.port, 162 Ssl = State#client_state.ssl, 163 Timeout = State#client_state.connect_timeout, 164 ConnectOptions = State#client_state.connect_options, 165 SocketOptions = [binary, {packet, http}, {active, false} | ConnectOptions], 166 case lhttpc_sock:connect(Host, Port, SocketOptions, Timeout, Ssl) of 167 {ok, Socket} -> 168 send_request(State#client_state{socket = Socket}); 169 {error, etimedout} -> 170 % TCP stack decided to give up 171 throw(connect_timeout); 172 {error, timeout} -> 173 throw(connect_timeout); 174 {error, Reason} -> 175 erlang:error(Reason) 176 end; 177send_request(State) -> 178 Socket = State#client_state.socket, 179 Ssl = State#client_state.ssl, 180 Request = State#client_state.request, 181 case lhttpc_sock:send(Socket, Request, Ssl) of 182 ok -> 183 if 184 State#client_state.partial_upload -> partial_upload(State); 185 not State#client_state.partial_upload -> read_response(State) 186 end; 187 {error, closed} -> 188 lhttpc_sock:close(Socket, Ssl), 189 NewState = State#client_state{ 190 socket = undefined, 191 attempts = State#client_state.attempts - 1 192 }, 193 send_request(NewState); 194 {error, Reason} -> 195 lhttpc_sock:close(Socket, Ssl), 196 erlang:error(Reason) 197 end. 198 199partial_upload(State) -> 200 Response = {ok, {self(), State#client_state.upload_window}}, 201 State#client_state.requester ! {response, self(), Response}, 202 partial_upload_loop(State#client_state{attempts = 1, request = undefined}). 203 204partial_upload_loop(State = #client_state{requester = Pid}) -> 205 receive 206 {trailers, Pid, Trailers} -> 207 send_trailers(State, Trailers), 208 read_response(State); 209 {body_part, Pid, http_eob} -> 210 send_body_part(State, http_eob), 211 read_response(State); 212 {body_part, Pid, Data} -> 213 send_body_part(State, Data), 214 Pid ! {ack, self()}, 215 partial_upload_loop(State) 216 end. 217 218send_body_part(State = #client_state{socket = Socket, ssl = Ssl}, BodyPart) -> 219 Data = encode_body_part(State, BodyPart), 220 check_send_result(State, lhttpc_sock:send(Socket, Data, Ssl)). 221 222send_trailers(State = #client_state{chunked_upload = true}, Trailers) -> 223 Socket = State#client_state.socket, 224 Ssl = State#client_state.ssl, 225 Data = [<<"0\r\n">>, lhttpc_lib:format_hdrs(Trailers)], 226 check_send_result(State, lhttpc_sock:send(Socket, Data, Ssl)); 227send_trailers(#client_state{chunked_upload = false}, _Trailers) -> 228 erlang:error(trailers_not_allowed). 229 230encode_body_part(#client_state{chunked_upload = true}, http_eob) -> 231 <<"0\r\n\r\n">>; % We don't send trailers after http_eob 232encode_body_part(#client_state{chunked_upload = false}, http_eob) -> 233 <<>>; 234encode_body_part(#client_state{chunked_upload = true}, Data) -> 235 Size = list_to_binary(erlang:integer_to_list(iolist_size(Data), 16)), 236 [Size, <<"\r\n">>, Data, <<"\r\n">>]; 237encode_body_part(#client_state{chunked_upload = false}, Data) -> 238 Data. 239 240check_send_result(_State, ok) -> 241 ok; 242check_send_result(#client_state{socket = Sock, ssl = Ssl}, {error, Reason}) -> 243 lhttpc_sock:close(Sock, Ssl), 244 throw(Reason). 245 246read_response(#client_state{socket = Socket, ssl = Ssl} = State) -> 247 ok = lhttpc_sock:setopts(Socket, [{packet, http}], Ssl), 248 read_response(State, nil, {nil, nil}, []). 249 250read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) -> 251 Socket = State#client_state.socket, 252 Ssl = State#client_state.ssl, 253 case lhttpc_sock:recv(Socket, Ssl) of 254 {ok, {http_response, NewVsn, NewStatusCode, Reason}} -> 255 NewStatus = {NewStatusCode, Reason}, 256 read_response(State, NewVsn, NewStatus, Hdrs); 257 {ok, {http_header, _, Name, _, Value}} -> 258 Header = {lhttpc_lib:maybe_atom_to_list(Name), Value}, 259 read_response(State, Vsn, Status, [Header | Hdrs]); 260 {ok, http_eoh} when StatusCode >= 100, StatusCode =< 199 -> 261 % RFC 2616, section 10.1: 262 % A client MUST be prepared to accept one or more 263 % 1xx status responses prior to a regular 264 % response, even if the client does not expect a 265 % 100 (Continue) status message. Unexpected 1xx 266 % status responses MAY be ignored by a user agent. 267 read_response(State, nil, {nil, nil}, []); 268 {ok, http_eoh} -> 269 ok = lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl), 270 Response = handle_response_body(State, Vsn, Status, Hdrs), 271 NewHdrs = element(2, Response), 272 ReqHdrs = State#client_state.request_headers, 273 NewSocket = maybe_close_socket(Socket, Ssl, Vsn, ReqHdrs, NewHdrs), 274 {Response, NewSocket}; 275 {error, closed} -> 276 % Either we only noticed that the socket was closed after we 277 % sent the request, the server closed it just after we put 278 % the request on the wire or the server has some issues and is 279 % closing connections without sending responses. 280 % If this the first attempt to send the request, we will try again. 281 lhttpc_sock:close(Socket, Ssl), 282 NewState = State#client_state{ 283 socket = undefined, 284 attempts = State#client_state.attempts - 1 285 }, 286 send_request(NewState); 287 {error, Reason} -> 288 erlang:error(Reason) 289 end. 290 291handle_response_body(#client_state{partial_download = false} = State, Vsn, 292 Status, Hdrs) -> 293 Socket = State#client_state.socket, 294 Ssl = State#client_state.ssl, 295 Method = State#client_state.method, 296 {Body, NewHdrs} = case has_body(Method, element(1, Status), Hdrs) of 297 true -> read_body(Vsn, Hdrs, Ssl, Socket, body_type(Hdrs)); 298 false -> {<<>>, Hdrs} 299 end, 300 {Status, NewHdrs, Body}; 301handle_response_body(#client_state{partial_download = true} = State, Vsn, 302 Status, Hdrs) -> 303 Method = State#client_state.method, 304 case has_body(Method, element(1, Status), Hdrs) of 305 true -> 306 Response = {ok, {Status, Hdrs, self()}}, 307 State#client_state.requester ! {response, self(), Response}, 308 MonRef = erlang:monitor(process, State#client_state.requester), 309 Res = read_partial_body(State, Vsn, Hdrs, body_type(Hdrs)), 310 erlang:demonitor(MonRef, [flush]), 311 Res; 312 false -> 313 {Status, Hdrs, undefined} 314 end. 315 316has_body("HEAD", _, _) -> 317 % HEAD responses aren't allowed to include a body 318 false; 319has_body("OPTIONS", _, Hdrs) -> 320 % OPTIONS can include a body, if Content-Length or Transfer-Encoding 321 % indicates it 322 ContentLength = lhttpc_lib:header_value("content-length", Hdrs), 323 TransferEncoding = lhttpc_lib:header_value("transfer-encoding", Hdrs), 324 case {ContentLength, TransferEncoding} of 325 {undefined, undefined} -> false; 326 {_, _} -> true 327 end; 328has_body(_, 204, _) -> 329 false; % RFC 2616 10.2.5: 204 No Content 330has_body(_, 304, _) -> 331 false; % RFC 2616 10.3.5: 304 Not Modified 332has_body(_, _, _) -> 333 true. % All other responses are assumed to have a body 334 335body_type(Hdrs) -> 336 % Find out how to read the entity body from the request. 337 % * If we have a Content-Length, just use that and read the complete 338 % entity. 339 % * If Transfer-Encoding is set to chunked, we should read one chunk at 340 % the time 341 % * If neither of this is true, we need to read until the socket is 342 % closed (AFAIK, this was common in versions before 1.1). 343 case lhttpc_lib:header_value("content-length", Hdrs) of 344 undefined -> 345 TransferEncoding = string:to_lower( 346 lhttpc_lib:header_value("transfer-encoding", Hdrs, "undefined") 347 ), 348 case TransferEncoding of 349 "chunked" -> chunked; 350 _ -> infinite 351 end; 352 ContentLength -> 353 {fixed_length, list_to_integer(ContentLength)} 354 end. 355 356read_partial_body(State, _Vsn, Hdrs, chunked) -> 357 Window = State#client_state.download_window, 358 read_partial_chunked_body(State, Hdrs, Window, 0, [], 0); 359read_partial_body(State, Vsn, Hdrs, infinite) -> 360 check_infinite_response(Vsn, Hdrs), 361 read_partial_infinite_body(State, Hdrs, State#client_state.download_window); 362read_partial_body(State, _Vsn, Hdrs, {fixed_length, ContentLength}) -> 363 read_partial_finite_body(State, Hdrs, ContentLength, 364 State#client_state.download_window). 365 366read_body(_Vsn, Hdrs, Ssl, Socket, chunked) -> 367 read_chunked_body(Socket, Ssl, Hdrs, []); 368read_body(Vsn, Hdrs, Ssl, Socket, infinite) -> 369 check_infinite_response(Vsn, Hdrs), 370 read_infinite_body(Socket, Hdrs, Ssl); 371read_body(_Vsn, Hdrs, Ssl, Socket, {fixed_length, ContentLength}) -> 372 read_length(Hdrs, Ssl, Socket, ContentLength). 373 374read_partial_finite_body(State = #client_state{}, Hdrs, 0, _Window) -> 375 reply_end_of_body(State, [], Hdrs); 376read_partial_finite_body(State = #client_state{requester = To}, Hdrs, 377 ContentLength, 0) -> 378 receive 379 {ack, To} -> 380 read_partial_finite_body(State, Hdrs, ContentLength, 1); 381 {'DOWN', _, process, To, _} -> 382 exit(normal) 383 end; 384read_partial_finite_body(State, Hdrs, ContentLength, Window) when Window >= 0-> 385 Bin = read_body_part(State, ContentLength), 386 State#client_state.requester ! {body_part, self(), Bin}, 387 To = State#client_state.requester, 388 receive 389 {ack, To} -> 390 Length = ContentLength - iolist_size(Bin), 391 read_partial_finite_body(State, Hdrs, Length, Window); 392 {'DOWN', _, process, To, _} -> 393 exit(normal) 394 after 0 -> 395 Length = ContentLength - iolist_size(Bin), 396 read_partial_finite_body(State, Hdrs, Length, lhttpc_lib:dec(Window)) 397 end. 398 399read_body_part(#client_state{part_size = infinity} = State, _ContentLength) -> 400 case lhttpc_sock:recv(State#client_state.socket, State#client_state.ssl) of 401 {ok, Data} -> 402 Data; 403 {error, Reason} -> 404 erlang:error(Reason) 405 end; 406read_body_part(#client_state{part_size = PartSize} = State, ContentLength) 407 when PartSize =< ContentLength -> 408 Socket = State#client_state.socket, 409 Ssl = State#client_state.ssl, 410 PartSize = State#client_state.part_size, 411 case lhttpc_sock:recv(Socket, PartSize, Ssl) of 412 {ok, Data} -> 413 Data; 414 {error, Reason} -> 415 erlang:error(Reason) 416 end; 417read_body_part(#client_state{part_size = PartSize} = State, ContentLength) 418 when PartSize > ContentLength -> 419 Socket = State#client_state.socket, 420 Ssl = State#client_state.ssl, 421 case lhttpc_sock:recv(Socket, ContentLength, Ssl) of 422 {ok, Data} -> 423 Data; 424 {error, Reason} -> 425 erlang:error(Reason) 426 end. 427 428read_length(Hdrs, Ssl, Socket, Length) -> 429 case lhttpc_sock:recv(Socket, Length, Ssl) of 430 {ok, Data} -> 431 {Data, Hdrs}; 432 {error, Reason} -> 433 erlang:error(Reason) 434 end. 435 436read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, 0) -> 437 Socket = State#client_state.socket, 438 Ssl = State#client_state.ssl, 439 PartSize = State#client_state.part_size, 440 case read_chunk_size(Socket, Ssl) of 441 0 -> 442 reply_chunked_part(State, Buffer, Window), 443 {Trailers, NewHdrs} = read_trailers(Socket, Ssl, [], Hdrs, <<>>), 444 reply_end_of_body(State, Trailers, NewHdrs); 445 ChunkSize when PartSize =:= infinity -> 446 Chunk = read_chunk(Socket, Ssl, ChunkSize), 447 NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), 448 read_partial_chunked_body(State, Hdrs, NewWindow, 0, [], 0); 449 ChunkSize when BufferSize + ChunkSize >= PartSize -> 450 {Chunk, RemSize} = read_partial_chunk(Socket, Ssl, 451 PartSize - BufferSize, ChunkSize), 452 NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), 453 read_partial_chunked_body(State, Hdrs, NewWindow, 0, [], RemSize); 454 ChunkSize -> 455 Chunk = read_chunk(Socket, Ssl, ChunkSize), 456 read_partial_chunked_body(State, Hdrs, Window, 457 BufferSize + ChunkSize, [Chunk | Buffer], 0) 458 end; 459read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, RemSize) -> 460 Socket = State#client_state.socket, 461 Ssl = State#client_state.ssl, 462 PartSize = State#client_state.part_size, 463 if 464 BufferSize + RemSize >= PartSize -> 465 {Chunk, NewRemSize} = 466 read_partial_chunk(Socket, Ssl, PartSize - BufferSize, RemSize), 467 NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window), 468 read_partial_chunked_body(State, Hdrs, NewWindow, 0, [], 469 NewRemSize); 470 BufferSize + RemSize < PartSize -> 471 Chunk = read_chunk(Socket, Ssl, RemSize), 472 read_partial_chunked_body(State, Hdrs, Window, BufferSize + RemSize, 473 [Chunk | Buffer], 0) 474 end. 475 476read_chunk_size(Socket, Ssl) -> 477 ok = lhttpc_sock:setopts(Socket, [{packet, line}], Ssl), 478 case lhttpc_sock:recv(Socket, Ssl) of 479 {ok, ChunkSizeExt} -> 480 chunk_size(ChunkSizeExt); 481 {error, Reason} -> 482 erlang:error(Reason) 483 end. 484 485reply_chunked_part(_State, [], Window) -> 486 Window; 487reply_chunked_part(State = #client_state{requester = Pid}, Buff, 0) -> 488 receive 489 {ack, Pid} -> 490 reply_chunked_part(State, Buff, 1); 491 {'DOWN', _, process, Pid, _} -> 492 exit(normal) 493 end; 494reply_chunked_part(#client_state{requester = Pid}, Buffer, Window) -> 495 Pid ! {body_part, self(), list_to_binary(lists:reverse(Buffer))}, 496 receive 497 {ack, Pid} -> Window; 498 {'DOWN', _, process, Pid, _} -> exit(normal) 499 after 0 -> 500 lhttpc_lib:dec(Window) 501 end. 502 503read_chunked_body(Socket, Ssl, Hdrs, Chunks) -> 504 case read_chunk_size(Socket, Ssl) of 505 0 -> 506 Body = list_to_binary(lists:reverse(Chunks)), 507 {_, NewHdrs} = read_trailers(Socket, Ssl, [], Hdrs, <<>>), 508 {Body, NewHdrs}; 509 Size -> 510 Chunk = read_chunk(Socket, Ssl, Size), 511 read_chunked_body(Socket, Ssl, Hdrs, [Chunk | Chunks]) 512 end. 513 514chunk_size(Bin) -> 515 erlang:list_to_integer(lists:reverse(chunk_size(Bin, [])), 16). 516 517chunk_size(<<$;, _/binary>>, Chars) -> 518 Chars; 519chunk_size(<<"\r\n", _/binary>>, Chars) -> 520 Chars; 521chunk_size(<<$\s, Binary/binary>>, Chars) -> 522 %% Facebook's HTTP server returns a chunk size like "6 \r\n" 523 chunk_size(Binary, Chars); 524chunk_size(<<Char, Binary/binary>>, Chars) -> 525 chunk_size(Binary, [Char | Chars]). 526 527read_partial_chunk(Socket, Ssl, ChunkSize, ChunkSize) -> 528 {read_chunk(Socket, Ssl, ChunkSize), 0}; 529read_partial_chunk(Socket, Ssl, Size, ChunkSize) -> 530 ok = lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl), 531 case lhttpc_sock:recv(Socket, Size, Ssl) of 532 {ok, Chunk} -> 533 {Chunk, ChunkSize - Size}; 534 {error, Reason} -> 535 erlang:error(Reason) 536 end. 537 538read_chunk(Socket, Ssl, Size) -> 539 ok = lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl), 540 case lhttpc_sock:recv(Socket, Size + 2, Ssl) of 541 {ok, <<Chunk:Size/binary, "\r\n">>} -> 542 Chunk; 543 {ok, Data} -> 544 erlang:error({invalid_chunk, Data}); 545 {error, Reason} -> 546 erlang:error(Reason) 547 end. 548 549read_trailers(Socket, Ssl, Trailers, Hdrs, <<>>) -> 550 case lhttpc_sock:recv(Socket, Ssl) of 551 {ok, Data} -> 552 read_trailers(Socket, Ssl, Trailers, Hdrs, Data); 553 {error, closed} -> 554 {Hdrs, Trailers}; 555 {error, Error} -> 556 erlang:error(Error) 557 end; 558read_trailers(Socket, Ssl, Trailers, Hdrs, Buffer) -> 559 case erlang:decode_packet(httph, Buffer, []) of 560 {ok, {http_header, _, Name, _, Value}, NextBuffer} -> 561 Header = {Name, Value}, 562 NTrailers = [Header | Trailers], 563 NHeaders = [Header | Hdrs], 564 read_trailers(Socket, Ssl, NTrailers, NHeaders, NextBuffer); 565 {ok, http_eoh, _} -> 566 {Trailers, Hdrs}; 567 {ok, {http_error, HttpString}, _} -> 568 erlang:error({bad_trailer, HttpString}); 569 {more, _} -> 570 case lhttpc_sock:recv(Socket, Ssl) of 571 {ok, Data} -> 572 BufferAndData = list_to_binary([Buffer, Data]), 573 read_trailers(Socket, Ssl, Trailers, Hdrs, BufferAndData); 574 {error, closed} -> 575 {Hdrs, Trailers}; 576 {error, Error} -> 577 erlang:error(Error) 578 end; 579 {error, Reason} -> 580 erlang:error({bad_trailer, Reason}) 581 end. 582 583reply_end_of_body(#client_state{requester = Requester}, Trailers, Hdrs) -> 584 Requester ! {http_eob, self(), Trailers}, 585 {no_return, Hdrs}. 586 587read_partial_infinite_body(State = #client_state{requester = To}, Hdrs, 0) -> 588 receive 589 {ack, To} -> 590 read_partial_infinite_body(State, Hdrs, 1); 591 {'DOWN', _, process, To, _} -> 592 exit(normal) 593 end; 594read_partial_infinite_body(State = #client_state{requester = To}, Hdrs, Window) 595 when Window >= 0 -> 596 case read_infinite_body_part(State) of 597 http_eob -> reply_end_of_body(State, [], Hdrs); 598 Bin -> 599 State#client_state.requester ! {body_part, self(), Bin}, 600 receive 601 {ack, To} -> 602 read_partial_infinite_body(State, Hdrs, Window); 603 {'DOWN', _, process, To, _} -> 604 exit(normal) 605 after 0 -> 606 read_partial_infinite_body(State, Hdrs, lhttpc_lib:dec(Window)) 607 end 608 end. 609 610read_infinite_body_part(#client_state{socket = Socket, ssl = Ssl}) -> 611 case lhttpc_sock:recv(Socket, Ssl) of 612 {ok, Data} -> 613 Data; 614 {error, closed} -> 615 http_eob; 616 {error, Reason} -> 617 erlang:error(Reason) 618 end. 619 620check_infinite_response({1, Minor}, Hdrs) when Minor >= 1 -> 621 HdrValue = lhttpc_lib:header_value("connection", Hdrs, "keep-alive"), 622 case string:to_lower(HdrValue) of 623 "close" -> ok; 624 _ -> erlang:error(no_content_length) 625 end; 626check_infinite_response(_, Hdrs) -> 627 HdrValue = lhttpc_lib:header_value("connection", Hdrs, "close"), 628 case string:to_lower(HdrValue) of 629 "keep-alive" -> erlang:error(no_content_length); 630 _ -> ok 631 end. 632 633read_infinite_body(Socket, Hdrs, Ssl) -> 634 read_until_closed(Socket, <<>>, Hdrs, Ssl). 635 636read_until_closed(Socket, Acc, Hdrs, Ssl) -> 637 case lhttpc_sock:recv(Socket, Ssl) of 638 {ok, Body} -> 639 NewAcc = <<Acc/binary, Body/binary>>, 640 read_until_closed(Socket, NewAcc, Hdrs, Ssl); 641 {error, closed} -> 642 {Acc, Hdrs}; 643 {error, Reason} -> 644 erlang:error(Reason) 645 end. 646 647maybe_close_socket(Socket, Ssl, {1, Minor}, ReqHdrs, RespHdrs) when Minor >= 1-> 648 ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"), 649 ServerConnection = ?CONNECTION_HDR(RespHdrs, "keep-alive"), 650 if 651 ClientConnection =:= "close"; ServerConnection =:= "close" -> 652 lhttpc_sock:close(Socket, Ssl), 653 undefined; 654 ClientConnection =/= "close", ServerConnection =/= "close" -> 655 Socket 656 end; 657maybe_close_socket(Socket, Ssl, _, ReqHdrs, RespHdrs) -> 658 ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"), 659 ServerConnection = ?CONNECTION_HDR(RespHdrs, "close"), 660 if 661 ClientConnection =:= "close"; ServerConnection =/= "keep-alive" -> 662 lhttpc_sock:close(Socket, Ssl), 663 undefined; 664 ClientConnection =/= "close", ServerConnection =:= "keep-alive" -> 665 Socket 666 end.