PageRenderTime 53ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

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