PageRenderTime 83ms CodeModel.GetById 13ms app.highlight 63ms RepoModel.GetById 1ms app.codeStats 1ms

/src/lhttpc_client.erl

https://bitbucket.org/tamasnagy/lhttpc
Erlang | 636 lines | 518 code | 43 blank | 75 comment | 1 complexity | d0f6a488a355f960050174c0a9841367 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@erlang-consulting.com>
 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%%% @end
 33-module(lhttpc_client).
 34
 35-export([request/9]).
 36
 37-include("lhttpc_types.hrl").
 38
 39-record(client_state, {
 40        host :: string(),
 41        port = 80 :: integer(),
 42        ssl = false :: true | false,
 43        method :: string(),
 44        request :: iolist(),
 45        request_headers :: headers(),
 46        socket,
 47        connect_timeout = infinity :: timeout(),
 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 
 58        %% in case of chunked one chunk
 59    }).
 60
 61-define(CONNECTION_HDR(HDRS, DEFAULT),
 62    string:to_lower(lhttpc_lib:header_value("connection", HDRS, DEFAULT))).
 63
 64-spec request(pid(), string(), 1..65535, true | false, string(),
 65        string() | atom(), headers(), iolist(), [option()]) -> no_return().
 66%% @spec (From, Host, Port, Ssl, Path, Method, Hdrs, RequestBody, Options) -> ok
 67%%    From = pid()
 68%%    Host = string()
 69%%    Port = integer()
 70%%    Ssl = boolean()
 71%%    Method = atom() | string()
 72%%    Hdrs = [Header]
 73%%    Header = {string() | atom(), string()}
 74%%    Body = iolist()
 75%%    Options = [Option]
 76%%    Option = {connect_timeout, Milliseconds}
 77%% @end
 78request(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) ->
 79    Result = try
 80        execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options)
 81    catch 
 82        Reason ->
 83            {response, self(), {error, Reason}};
 84        error:closed ->
 85            {response, self(), {error, connection_closed}};
 86        error:Error ->
 87            {exit, self(), {Error, erlang:get_stacktrace()}}
 88    end,
 89    case Result of
 90        {response, _, {ok, {no_return, _}}} -> ok;
 91        _Else -> From ! Result
 92    end,
 93    % Don't send back {'EXIT', self(), normal} if the process
 94    % calling us is trapping exits
 95    unlink(From), 
 96    ok.
 97
 98execute(From, Host, Port, Ssl, Path, Method, Hdrs, Body, Options) ->
 99    UploadWindowSize = proplists:get_value(partial_upload, Options),
100    PartialUpload = proplists:is_defined(partial_upload, Options),
101    PartialDownload = proplists:is_defined(partial_download, Options),
102    PartialDownloadOptions = proplists:get_value(partial_download, Options, []),
103    NormalizedMethod = lhttpc_lib:normalize_method(Method),
104    {ChunkedUpload, Request} = lhttpc_lib:format_request(Path, NormalizedMethod,
105        Hdrs, Host, Port, Body, PartialUpload),
106    SocketRequest = {socket, self(), Host, Port, Ssl},
107    Socket = case gen_server:call(lhttpc_manager, SocketRequest, infinity) of
108        {ok, S}   -> S; % Re-using HTTP/1.1 connections
109        no_socket -> undefined % Opening a new HTTP/1.1 connection
110    end,
111    State = #client_state{
112        host = Host,
113        port = Port,
114        ssl = Ssl,
115        method = NormalizedMethod,
116        request = Request,
117        requester = From,
118        request_headers = Hdrs,
119        socket = Socket,
120        connect_timeout = proplists:get_value(connect_timeout, Options,
121            infinity),
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    SocketOptions = [binary, {packet, http}, {active, false}],
165    case lhttpc_sock:connect(Host, Port, SocketOptions, Timeout, Ssl) of
166        {ok, Socket} ->
167            send_request(State#client_state{socket = Socket});
168        {error, etimedout} ->
169            % TCP stack decided to give up
170            throw(connect_timeout);
171        {error, timeout} ->
172            throw(connect_timeout);
173        {error, Reason} ->
174            erlang:error(Reason)
175    end;
176send_request(State) ->
177    Socket = State#client_state.socket,
178    Ssl = State#client_state.ssl,
179    Request = State#client_state.request,
180    case lhttpc_sock:send(Socket, Request, Ssl) of
181        ok ->
182            if
183                State#client_state.partial_upload     -> partial_upload(State);
184                not State#client_state.partial_upload -> read_response(State)
185            end;
186        {error, closed} ->
187            lhttpc_sock:close(Socket, Ssl),
188            NewState = State#client_state{
189                socket = undefined,
190                attempts = State#client_state.attempts - 1
191            },
192            send_request(NewState);
193        {error, Reason} ->
194            lhttpc_sock:close(Socket, Ssl),
195            erlang:error(Reason)
196    end.
197
198partial_upload(State) ->
199    Response = {ok, {self(), State#client_state.upload_window}},
200    State#client_state.requester ! {response, self(), Response},
201    partial_upload_loop(State#client_state{attempts = 1, request = undefined}).
202
203partial_upload_loop(State = #client_state{requester = Pid}) ->
204    receive
205        {trailers, Pid, Trailers} ->
206            send_trailers(State, Trailers),
207            read_response(State);
208        {body_part, Pid, http_eob} ->
209            send_body_part(State, http_eob),
210            read_response(State);
211        {body_part, Pid, Data} ->
212            send_body_part(State, Data),
213            Pid ! {ack, self()},
214            partial_upload_loop(State)
215    end.
216
217send_body_part(State = #client_state{socket = Socket, ssl = Ssl}, BodyPart) ->
218    Data = encode_body_part(State, BodyPart),
219    check_send_result(State, lhttpc_sock:send(Socket, Data, Ssl)).
220 
221send_trailers(State = #client_state{chunked_upload = true}, Trailers) ->
222    Socket = State#client_state.socket,
223    Ssl = State#client_state.ssl,
224    Data = [<<"0\r\n">>, lhttpc_lib:format_hdrs(Trailers)],
225    check_send_result(State, lhttpc_sock:send(Socket, Data, Ssl));
226send_trailers(#client_state{chunked_upload = false}, _Trailers) ->
227    erlang:error(trailers_not_allowed).
228
229encode_body_part(#client_state{chunked_upload = true}, http_eob) ->
230    <<"0\r\n\r\n">>; % We don't send trailers after http_eob
231encode_body_part(#client_state{chunked_upload = false}, http_eob) ->
232    <<>>;
233encode_body_part(#client_state{chunked_upload = true}, Data) ->
234    Size = list_to_binary(erlang:integer_to_list(iolist_size(Data), 16)),
235    [Size, <<"\r\n">>, Data, <<"\r\n">>];
236encode_body_part(#client_state{chunked_upload = false}, Data) ->
237    Data.
238
239check_send_result(_State, ok) ->
240    ok;
241check_send_result(#client_state{socket = Sock, ssl = Ssl}, {error, Reason}) ->
242    lhttpc_sock:close(Sock, Ssl),
243    throw(Reason).
244 
245read_response(#client_state{socket = Socket, ssl = Ssl} = State) ->
246    lhttpc_sock:setopts(Socket, [{packet, http}], Ssl),
247    read_response(State, nil, nil, []).
248
249read_response(State, Vsn, Status, Hdrs) ->
250    Socket = State#client_state.socket,
251    Ssl = State#client_state.ssl,
252    case lhttpc_sock:recv(Socket, Ssl) of
253        {ok, {http_response, NewVsn, StatusCode, Reason}} ->
254            NewStatus = {StatusCode, Reason},
255            read_response(State, NewVsn, NewStatus, Hdrs);
256        {ok, {http_header, _, Name, _, Value}} ->
257            Header = {lhttpc_lib:maybe_atom_to_list(Name), Value},
258            read_response(State, Vsn, Status, [Header | Hdrs]);
259        {ok, http_eoh} ->
260            lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl),
261            Response = handle_response_body(State, Vsn, Status, Hdrs),
262            NewHdrs = element(2, Response),
263            ReqHdrs = State#client_state.request_headers,
264            NewSocket = maybe_close_socket(Socket, Ssl, Vsn, ReqHdrs, NewHdrs),
265            {Response, NewSocket};
266        {error, closed} ->
267            % Either we only noticed that the socket was closed after we
268            % sent the request, the server closed it just after we put
269            % the request on the wire or the server has some issues and is
270            % closing connections without sending responses.
271            % If this the first attempt to send the request, we will try again.
272            lhttpc_sock:close(Socket, Ssl),
273            NewState = State#client_state{
274                socket = undefined,
275                attempts = State#client_state.attempts - 1
276            },
277            send_request(NewState);
278        {error, Reason} ->
279            erlang:error(Reason)
280    end.
281
282handle_response_body(State = #client_state{partial_download = false},
283        Vsn, Status, Hdrs) ->
284    Socket = State#client_state.socket,
285    Ssl = State#client_state.ssl,
286    Method = State#client_state.method,
287    {Body, NewHdrs} = case has_body(Method, element(1, Status), Hdrs) of
288        true  -> read_body(Vsn, Hdrs, Ssl, Socket, body_type(Hdrs));
289        false -> {<<>>, Hdrs}
290    end,
291    {Status, NewHdrs, Body};
292handle_response_body(#client_state{partial_download = true} = State,
293        Vsn, Status, Hdrs) ->
294    Method = State#client_state.method,
295    case has_body(Method, element(1, Status), Hdrs) of
296        true ->
297            Response = {ok, {Status, Hdrs, self()}},
298            State#client_state.requester ! {response, self(), Response},
299            MonRef = erlang:monitor(process, State#client_state.requester),
300            Res = read_partial_body(State, Vsn, Hdrs, body_type(Hdrs)),
301            erlang:demonitor(MonRef, [flush]),
302            Res;
303        false ->
304            {Status, Hdrs, undefined}
305    end.
306
307has_body("HEAD", _, _) ->
308    % HEAD responses aren't allowed to include a body
309    false;
310has_body("OPTIONS", _, Hdrs) ->
311    % OPTIONS can include a body, if Content-Length or Transfer-Encoding
312    % indicates it.
313    ContentLength = lhttpc_lib:header_value("content-length", Hdrs),
314    TransferEncoding = lhttpc_lib:header_value("transfer-encoding", Hdrs),
315    case {ContentLength, TransferEncoding} of
316        {undefined, undefined} -> false;
317        {_, _}                 -> true
318    end;
319has_body(_, 204, _) ->
320    false; % 204 No content can't have a body
321has_body(_, _, _) ->
322    true. % All other responses are assumed to have a body
323
324body_type(Hdrs) ->
325    % Find out how to read the entity body from the request.
326    % * If we have a Content-Length, just use that and read the complete
327    %   entity.
328    % * If Transfer-Encoding is set to chunked, we should read one chunk at
329    %   the time
330    % * If neither of this is true, we need to read until the socket is
331    %   closed (AFAIK, this was common in versions before 1.1).
332    case lhttpc_lib:header_value("content-length", Hdrs) of
333        undefined ->
334            TransferEncoding = string:to_lower(
335                lhttpc_lib:header_value("transfer-encoding", Hdrs, "undefined")
336            ),
337            case TransferEncoding of
338                "chunked" -> chunked;
339                _         -> infinite
340            end;
341        ContentLength ->
342            {fixed_length, list_to_integer(ContentLength)}
343    end.
344
345read_partial_body(State, _Vsn, Hdrs, chunked) ->
346    read_partial_chunked_body(State, Hdrs, State#client_state.download_window, 
347        0, [], 0);
348read_partial_body(State, Vsn, Hdrs, infinite) ->
349    check_infinite_response(Vsn, Hdrs),
350    read_partial_infinite_body(State, Hdrs, State#client_state.download_window);
351read_partial_body(State, _Vsn, Hdrs, {fixed_length, ContentLength}) ->
352    read_partial_finite_body(State, Hdrs, ContentLength, 
353        State#client_state.download_window).
354    
355read_body(_Vsn, Hdrs, Ssl, Socket, chunked) ->
356    read_chunked_body(Socket, Ssl, Hdrs, []);
357read_body(Vsn, Hdrs, Ssl, Socket, infinite) ->
358    check_infinite_response(Vsn, Hdrs),
359    read_infinite_body(Socket, Hdrs, Ssl);
360read_body(_Vsn, Hdrs, Ssl, Socket, {fixed_length, ContentLength}) ->
361    read_length(Hdrs, Ssl, Socket, ContentLength).
362
363read_partial_finite_body(State = #client_state{}, Hdrs, 0, _Window) ->
364    send_end_of_body(State, [], Hdrs);
365read_partial_finite_body(State = #client_state{requester = To}, Hdrs, 
366        ContentLength, 0) ->
367    receive
368        {ack, To} -> read_partial_finite_body(State, Hdrs, ContentLength, 1);
369        {'DOWN', _, process, To, _} -> exit(normal)
370    end;
371read_partial_finite_body(State = #client_state{requester = To}, Hdrs, 
372        ContentLength, Window) when Window >= 0->
373    Bin = read_body_part(State, ContentLength),
374    State#client_state.requester ! {body_part, self(), Bin},
375    receive
376        {ack, To} -> read_partial_finite_body(State, Hdrs, 
377                        ContentLength - iolist_size(Bin), Window);
378        {'DOWN', _, process, To, _} -> exit(normal)
379    after 0 ->
380        read_partial_finite_body(State, Hdrs, ContentLength - iolist_size(Bin), 
381            lhttpc_lib:dec(Window))
382    end.
383
384read_body_part(#client_state{socket = Socket, ssl = Ssl, part_size = infinity},
385        _ContentLength) ->
386    case lhttpc_sock:recv(Socket, Ssl) of
387        {ok, Data} ->
388            Data;
389        {error, Reason} ->
390            erlang:error(Reason)
391    end; 
392read_body_part(#client_state{socket = Socket, ssl = Ssl, part_size = PartSize},
393        ContentLength) when PartSize =< ContentLength ->
394    case lhttpc_sock:recv(Socket, PartSize, Ssl) of
395        {ok, Data} ->
396            Data;
397        {error, Reason} ->
398            erlang:error(Reason)
399    end;
400read_body_part(#client_state{socket = Socket, ssl = Ssl, part_size = PartSize},
401        ContentLength) when PartSize > ContentLength ->
402    case lhttpc_sock:recv(Socket, ContentLength, Ssl) of
403        {ok, Data} ->
404            Data;
405        {error, Reason} ->
406            erlang:error(Reason)
407    end.
408
409read_length(Hdrs, Ssl, Socket, Length) ->
410    case lhttpc_sock:recv(Socket, Length, Ssl) of
411        {ok, Data} ->
412            {Data, Hdrs};
413        {error, Reason} ->
414            erlang:error(Reason)
415    end.
416
417read_partial_chunked_body(State = #client_state{socket = Socket, ssl = Ssl, 
418        part_size = PartSize}, Hdrs, Window, CurrentSize, CurrentData, 0) -> 
419    case read_chunk_size(Socket, Ssl) of
420    0 -> 
421        send_chunked_body_part(State, CurrentData, Window, CurrentSize),
422        {Trailers, NewHdrs} = read_trailers(Socket, Ssl, [], Hdrs),
423        send_end_of_body(State, Trailers, NewHdrs);
424    ChunkSize when PartSize =:= infinity ->
425        Chunk = read_chunk(Socket, Ssl, ChunkSize),
426        NewWindow = send_chunked_body_part(State, [Chunk | CurrentData], Window,
427            not_empty),
428        read_partial_chunked_body(State, Hdrs, NewWindow, 0, [], 0);
429    ChunkSize when CurrentSize + ChunkSize >= PartSize ->
430        {Chunk, RestChunkSize} = 
431            read_partial_chunk(Socket, Ssl, PartSize - CurrentSize, ChunkSize),
432        NewWindow = send_chunked_body_part(State, [Chunk | CurrentData], Window,
433            not_empty),
434        read_partial_chunked_body(State, Hdrs, NewWindow, 0, [], RestChunkSize);
435    ChunkSize ->
436        Chunk = read_chunk(Socket, Ssl, ChunkSize),
437        read_partial_chunked_body(State, Hdrs, Window, CurrentSize + ChunkSize,
438            [Chunk | CurrentData], 0)
439    end;
440read_partial_chunked_body(State = #client_state{socket = Socket, ssl = Ssl,
441        part_size = PartSize}, Hdrs, Window, CurrentSize, CurrentData, 
442        RestChunkSize) ->
443    if 
444        CurrentSize + RestChunkSize >= PartSize ->
445            {Chunk, NewRestChunkSize} = 
446                read_partial_chunk(Socket, Ssl, PartSize - CurrentSize, 
447                    RestChunkSize),
448            NewWindow = send_chunked_body_part(State, [Chunk | CurrentData], 
449                Window, not_empty),
450            read_partial_chunked_body(State, Hdrs, NewWindow, 0, [], 
451                NewRestChunkSize);            
452        CurrentSize + RestChunkSize < PartSize ->
453            Chunk = read_chunk(Socket, Ssl, RestChunkSize),
454            read_partial_chunked_body(State, Hdrs, Window, 
455                CurrentSize + RestChunkSize, [Chunk | CurrentData], 0)
456    end.             
457
458read_chunk_size(Socket, Ssl) ->
459    lhttpc_sock:setopts(Socket, [{packet, line}], Ssl),
460    case lhttpc_sock:recv(Socket, Ssl) of
461        {ok, ChunkSizeExt} ->
462            chunk_size(ChunkSizeExt);
463        {error, Reason} ->
464            erlang:error(Reason)
465    end.
466
467
468send_chunked_body_part(_State, _Bin, Window, 0) -> 
469    Window;
470send_chunked_body_part(State = #client_state{requester = Pid}, Bin, 0, Size) ->
471    receive
472        {ack, Pid} -> send_chunked_body_part(State, Bin, 1, Size);
473        {'DOWN', _, process, Pid, _} -> exit(normal)
474    end;
475send_chunked_body_part(#client_state{requester = Pid}, Bin, Window, _Size) ->
476    Pid ! {body_part, self(), preformat(Bin)},
477    receive
478        {ack, Pid} ->  Window; 
479        {'DOWN', _, process, Pid, _} -> exit(normal)
480    after 0 ->
481        lhttpc_lib:dec(Window)
482    end.
483            
484preformat(Bin) when is_list(Bin) -> lists:reverse(Bin); 
485%%Maybe turn it to one binary
486preformat(Bin) -> Bin.                
487
488read_chunked_body(Socket, Ssl, Hdrs, Chunks) ->
489    case read_chunk_size(Socket, Ssl) of
490        0 ->    
491            Body = list_to_binary(lists:reverse(Chunks)),
492            {Body, read_trailers(Socket, Ssl, Hdrs)};
493        Size ->
494            Chunk = read_chunk(Socket, Ssl, Size),
495            read_chunked_body(Socket, Ssl, Hdrs, [Chunk | Chunks])
496    end.
497
498chunk_size(Bin) ->
499    erlang:list_to_integer(lists:reverse(chunk_size(Bin, [])), 16).
500
501chunk_size(<<$;, _/binary>>, Chars) ->
502    Chars;
503chunk_size(<<"\r\n", _/binary>>, Chars) ->
504    Chars;
505chunk_size(<<$\s, Binary/binary>>, Chars) ->
506    %% Facebook's HTTP server returns a chunk size like "6  \r\n"
507    chunk_size(Binary, Chars);
508chunk_size(<<Char, Binary/binary>>, Chars) ->
509    chunk_size(Binary, [Char | Chars]).
510
511read_partial_chunk(Socket, Ssl, ChunkSize, ChunkSize) ->
512    {read_chunk(Socket, Ssl, ChunkSize), 0};
513read_partial_chunk(Socket, Ssl, Size, ChunkSize) ->
514    lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl),
515    case lhttpc_sock:recv(Socket, Size, Ssl) of
516        {ok, Chunk} ->
517            {Chunk, ChunkSize - Size};
518        {error, Reason} ->
519            erlang:error(Reason)
520    end.
521
522read_chunk(Socket, Ssl, Size) ->
523    lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl),
524    case lhttpc_sock:recv(Socket, Size + 2, Ssl) of
525        {ok, <<Chunk:Size/binary, "\r\n">>} ->
526            Chunk;
527        {ok, Data} ->
528            erlang:error({invalid_chunk, Data});
529        {error, Reason} ->
530            erlang:error(Reason)
531    end.
532
533read_trailers(Socket, Ssl, Trailers, Hdrs) ->
534    lhttpc_sock:setopts(Socket, [{packet, httph}], Ssl),
535    case lhttpc_sock:recv(Socket, Ssl) of
536        {ok, http_eoh} ->
537            {Trailers, Hdrs};
538        {ok, {http_header, _, Name, _, Value}} ->
539            Header = {lhttpc_lib:maybe_atom_to_list(Name), Value},
540            read_trailers(Socket, Ssl, [Header | Trailers], [Header | Hdrs]);
541        {error, {http_error, Data}} ->
542            erlang:error({bad_trailer, Data})
543    end.
544    
545read_trailers(Socket, Ssl, Hdrs) ->
546    lhttpc_sock:setopts(Socket, [{packet, httph}], Ssl),
547    case lhttpc_sock:recv(Socket, Ssl) of
548        {ok, http_eoh} ->
549            Hdrs;
550        {ok, {http_header, _, Name, _, Value}} ->
551            Header = {lhttpc_lib:maybe_atom_to_list(Name), Value},
552            read_trailers(Socket, Ssl, [Header | Hdrs]);
553        {error, {http_error, Data}} ->
554            erlang:error({bad_trailer, Data})
555    end.
556
557send_end_of_body(#client_state{requester = Requester}, Trailers, Hdrs) ->
558    Requester ! {http_eob, self(), Trailers},
559    {no_return, Hdrs}.
560    
561read_partial_infinite_body(State = #client_state{requester = To}, Hdrs, 0) ->
562    receive
563        {ack, To} -> read_partial_infinite_body(State, Hdrs, 1);
564        {'DOWN', _, process, To, _} -> exit(normal)
565    end;
566read_partial_infinite_body(State = #client_state{requester = To}, Hdrs, Window) 
567        when Window >= 0 ->
568    case read_infinite_body_part(State) of
569        http_eob -> send_end_of_body(State, [], Hdrs);
570        Bin ->
571            State#client_state.requester ! {body_part, self(), Bin},
572            receive
573                {ack, To} -> read_partial_infinite_body(State, Hdrs, Window);
574                {'DOWN', _, process, To, _} -> exit(normal)
575            after 0 ->
576                read_partial_infinite_body(State, Hdrs, lhttpc_lib:dec(Window))
577            end
578    end.
579
580read_infinite_body_part(#client_state{socket = Socket, ssl = Ssl}) ->
581    case lhttpc_sock:recv(Socket, Ssl) of
582        {ok, Data} ->
583            Data;
584        {error, closed} ->
585            http_eob;
586        {error, Reason} ->
587            erlang:error(Reason)
588    end.
589
590check_infinite_response({1, Minor}, Hdrs) when Minor >= 1 ->
591    HdrValue = lhttpc_lib:header_value("connection", Hdrs, "keep-alive"),
592    case string:to_lower(HdrValue) of
593        "close" -> ok;
594        _       -> erlang:error(no_content_length)
595    end;
596check_infinite_response(_, Hdrs) ->
597    HdrValue = lhttpc_lib:header_value("connection", Hdrs, "close"),
598    case string:to_lower(HdrValue) of
599        "keep-alive" -> erlang:error(no_content_length);
600        _            -> ok
601    end.
602
603read_infinite_body(Socket, Hdrs, Ssl) ->   
604    read_until_closed(Socket, <<>>, Hdrs, Ssl).
605
606read_until_closed(Socket, Acc, Hdrs, Ssl) ->
607    case lhttpc_sock:recv(Socket, Ssl) of
608        {ok, Body} ->
609            NewAcc = <<Acc/binary, Body/binary>>,
610            read_until_closed(Socket, NewAcc, Hdrs, Ssl);
611        {error, closed} ->
612            {Acc, Hdrs};
613        {error, Reason} ->
614            erlang:error(Reason)
615    end.
616
617maybe_close_socket(Socket, Ssl, {1, Minor}, ReqHdrs, RespHdrs) when Minor >= 1->
618    ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"),
619    ServerConnection = ?CONNECTION_HDR(RespHdrs, "keep-alive"),
620    if
621        ClientConnection =:= "close"; ServerConnection =:= "close" ->
622            lhttpc_sock:close(Socket, Ssl),
623            undefined;
624        ClientConnection =/= "close", ServerConnection =/= "close" ->
625            Socket
626    end;
627maybe_close_socket(Socket, Ssl, _, ReqHdrs, RespHdrs) ->
628    ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"),
629    ServerConnection = ?CONNECTION_HDR(RespHdrs, "close"),
630    if
631        ClientConnection =:= "close"; ServerConnection =/= "keep-alive" ->
632            lhttpc_sock:close(Socket, Ssl),
633            undefined;
634        ClientConnection =/= "close", ServerConnection =:= "keep-alive" ->
635            Socket
636    end.