PageRenderTime 55ms CodeModel.GetById 1ms app.highlight 46ms RepoModel.GetById 1ms app.codeStats 1ms

/src/lhttpc_client.erl

https://github.com/abeniaminov/lhttpc
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.