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