PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

/src/lhttpc_client.erl

https://bitbucket.org/zajda/lhttpc
Erlang | 685 lines | 563 code | 41 blank | 81 comment | 0 complexity | acbe0500eafc4c800288c972f35cff51 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. %%% @type boolean() = bool().
  33. %%% @type iolist() = [] | binary() | [char() | binary() | iolist()].
  34. -module(lhttpc_client).
  35. -export([request/9]).
  36. -include("lhttpc_types.hrl").
  37. -record(client_state, {
  38. host :: string(),
  39. port = 80 :: integer(),
  40. ssl = false :: true | false,
  41. method :: string(),
  42. request :: iolist(),
  43. request_headers :: headers(),
  44. socket,
  45. connect_timeout = infinity :: timeout(),
  46. connect_options = [] :: [any()],
  47. attempts :: integer(),
  48. requester :: pid(),
  49. partial_upload = false :: true | false,
  50. chunked_upload = false :: true | false,
  51. upload_window :: non_neg_integer() | infinity,
  52. partial_download = false :: true | false,
  53. download_window = infinity :: timeout(),
  54. part_size :: non_neg_integer() | infinity,
  55. %% in case of infinity we read whatever data we can get from
  56. %% the wire at that point or in case of chunked one chunk
  57. proxy_host :: string(),
  58. proxy_port :: integer(),
  59. proxy_auth :: {string(),string()},
  60. ignore_proxy :: [{string(),integer()}]
  61. }).
  62. -define(CONNECTION_HDR(HDRS, DEFAULT),
  63. string:to_lower(lhttpc_lib:header_value("connection", HDRS, DEFAULT))).
  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
  78. request(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. execute(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,
  105. PartialUpload, Options),
  106. ProxyAuth = proplists:get_value(proxy_auth, Options),
  107. ProxyHost = proplists:get_value(proxy_host, Options),
  108. ProxyPort = proplists:get_value(proxy_port, Options, 80),
  109. IgnoreProxy = proplists:get_value(ignore_proxy, Options),
  110. {SocketHost, SocketPort} = determine_destination({Host,Port},
  111. {ProxyHost,ProxyPort},
  112. IgnoreProxy
  113. ),
  114. SocketRequest = {socket, self(), SocketHost, SocketPort, Ssl},
  115. Socket = case gen_server:call(lhttpc_manager, SocketRequest, infinity) of
  116. {ok, S} -> S; % Re-using HTTP/1.1 connections
  117. no_socket -> undefined % Opening a new HTTP/1.1 connection
  118. end,
  119. State = #client_state{
  120. host = Host,
  121. port = Port,
  122. ssl = Ssl,
  123. method = NormalizedMethod,
  124. request = Request,
  125. requester = From,
  126. request_headers = Hdrs,
  127. socket = Socket,
  128. connect_timeout = proplists:get_value(connect_timeout, Options,
  129. infinity),
  130. connect_options = proplists:get_value(connect_options, Options, []),
  131. attempts = 1 + proplists:get_value(send_retry, Options, 1),
  132. partial_upload = PartialUpload,
  133. upload_window = UploadWindowSize,
  134. chunked_upload = ChunkedUpload,
  135. partial_download = PartialDownload,
  136. download_window = proplists:get_value(window_size,
  137. PartialDownloadOptions, infinity),
  138. part_size = proplists:get_value(part_size,
  139. PartialDownloadOptions, infinity),
  140. proxy_auth = ProxyAuth,
  141. proxy_host = ProxyHost,
  142. proxy_port = ProxyPort,
  143. ignore_proxy = IgnoreProxy
  144. },
  145. Response = case send_request(State) of
  146. {R, undefined} ->
  147. {ok, R};
  148. {R, NewSocket} ->
  149. % The socket we ended up doing the request over is returned
  150. % here, it might be the same as Socket, but we don't know.
  151. % I've noticed that we don't want to give send sockets that we
  152. % can't change the controlling process for to the manager. This
  153. % really shouldn't fail, but it could do if:
  154. % * The socket was closed remotely already
  155. % * Due to an error in this module (returning dead sockets for
  156. % instance)
  157. ManagerPid = whereis(lhttpc_manager),
  158. case lhttpc_sock:controlling_process(NewSocket, ManagerPid, Ssl) of
  159. ok ->
  160. gen_server:cast(lhttpc_manager,
  161. {done, Host, Port, Ssl, NewSocket});
  162. _ ->
  163. ok
  164. end,
  165. {ok, R}
  166. end,
  167. {response, self(), Response}.
  168. send_request(#client_state{attempts = 0}) ->
  169. % Don't try again if the number of allowed attempts is 0.
  170. throw(connection_closed);
  171. send_request(#client_state{socket = undefined} = State) ->
  172. Host = State#client_state.host,
  173. Port = State#client_state.port,
  174. Ssl = State#client_state.ssl,
  175. Timeout = State#client_state.connect_timeout,
  176. ConnectOptions = State#client_state.connect_options,
  177. SocketOptions = [binary, {packet, http}, {active, false} | ConnectOptions],
  178. ProxyHost = State#client_state.proxy_host,
  179. ProxyPort = State#client_state.proxy_port,
  180. IgnoreProxy = State#client_state.ignore_proxy,
  181. {SocketHost, SocketPort} = determine_destination({Host,Port},
  182. {ProxyHost,ProxyPort},
  183. IgnoreProxy
  184. ),
  185. case lhttpc_sock:connect(SocketHost, SocketPort, SocketOptions, Timeout, Ssl) of
  186. {ok, Socket} ->
  187. send_request(State#client_state{socket = Socket});
  188. {error, etimedout} ->
  189. % TCP stack decided to give up
  190. throw(connect_timeout);
  191. {error, timeout} ->
  192. throw(connect_timeout);
  193. {error, Reason} ->
  194. erlang:error(Reason)
  195. end;
  196. send_request(State) ->
  197. Socket = State#client_state.socket,
  198. Ssl = State#client_state.ssl,
  199. Request = State#client_state.request,
  200. case lhttpc_sock:send(Socket, Request, Ssl) of
  201. ok ->
  202. if
  203. State#client_state.partial_upload -> partial_upload(State);
  204. not State#client_state.partial_upload -> read_response(State)
  205. end;
  206. {error, closed} ->
  207. lhttpc_sock:close(Socket, Ssl),
  208. NewState = State#client_state{
  209. socket = undefined,
  210. attempts = State#client_state.attempts - 1
  211. },
  212. send_request(NewState);
  213. {error, Reason} ->
  214. lhttpc_sock:close(Socket, Ssl),
  215. erlang:error(Reason)
  216. end.
  217. partial_upload(State) ->
  218. Response = {ok, {self(), State#client_state.upload_window}},
  219. State#client_state.requester ! {response, self(), Response},
  220. partial_upload_loop(State#client_state{attempts = 1, request = undefined}).
  221. partial_upload_loop(State = #client_state{requester = Pid}) ->
  222. receive
  223. {trailers, Pid, Trailers} ->
  224. send_trailers(State, Trailers),
  225. read_response(State);
  226. {body_part, Pid, http_eob} ->
  227. send_body_part(State, http_eob),
  228. read_response(State);
  229. {body_part, Pid, Data} ->
  230. send_body_part(State, Data),
  231. Pid ! {ack, self()},
  232. partial_upload_loop(State)
  233. end.
  234. send_body_part(State = #client_state{socket = Socket, ssl = Ssl}, BodyPart) ->
  235. Data = encode_body_part(State, BodyPart),
  236. check_send_result(State, lhttpc_sock:send(Socket, Data, Ssl)).
  237. send_trailers(State = #client_state{chunked_upload = true}, Trailers) ->
  238. Socket = State#client_state.socket,
  239. Ssl = State#client_state.ssl,
  240. Data = [<<"0\r\n">>, lhttpc_lib:format_hdrs(Trailers)],
  241. check_send_result(State, lhttpc_sock:send(Socket, Data, Ssl));
  242. send_trailers(#client_state{chunked_upload = false}, _Trailers) ->
  243. erlang:error(trailers_not_allowed).
  244. encode_body_part(#client_state{chunked_upload = true}, http_eob) ->
  245. <<"0\r\n\r\n">>; % We don't send trailers after http_eob
  246. encode_body_part(#client_state{chunked_upload = false}, http_eob) ->
  247. <<>>;
  248. encode_body_part(#client_state{chunked_upload = true}, Data) ->
  249. Size = list_to_binary(erlang:integer_to_list(iolist_size(Data), 16)),
  250. [Size, <<"\r\n">>, Data, <<"\r\n">>];
  251. encode_body_part(#client_state{chunked_upload = false}, Data) ->
  252. Data.
  253. check_send_result(_State, ok) ->
  254. ok;
  255. check_send_result(#client_state{socket = Sock, ssl = Ssl}, {error, Reason}) ->
  256. lhttpc_sock:close(Sock, Ssl),
  257. throw(Reason).
  258. read_response(#client_state{socket = Socket, ssl = Ssl} = State) ->
  259. lhttpc_sock:setopts(Socket, [{packet, http}], Ssl),
  260. read_response(State, nil, {nil, nil}, []).
  261. read_response(State, Vsn, {StatusCode, _} = Status, Hdrs) ->
  262. Socket = State#client_state.socket,
  263. Ssl = State#client_state.ssl,
  264. case lhttpc_sock:recv(Socket, Ssl) of
  265. {ok, {http_response, NewVsn, NewStatusCode, Reason}} ->
  266. NewStatus = {NewStatusCode, Reason},
  267. read_response(State, NewVsn, NewStatus, Hdrs);
  268. {ok, {http_header, _, Name, _, Value}} ->
  269. Header = {lhttpc_lib:maybe_atom_to_list(Name), Value},
  270. read_response(State, Vsn, Status, [Header | Hdrs]);
  271. {ok, http_eoh} when StatusCode >= 100, StatusCode =< 199 ->
  272. % RFC 2616, section 10.1:
  273. % A client MUST be prepared to accept one or more
  274. % 1xx status responses prior to a regular
  275. % response, even if the client does not expect a
  276. % 100 (Continue) status message. Unexpected 1xx
  277. % status responses MAY be ignored by a user agent.
  278. read_response(State, nil, {nil, nil}, []);
  279. {ok, http_eoh} ->
  280. lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl),
  281. Response = handle_response_body(State, Vsn, Status, Hdrs),
  282. NewHdrs = element(2, Response),
  283. ReqHdrs = State#client_state.request_headers,
  284. NewSocket = maybe_close_socket(Socket, Ssl, Vsn, ReqHdrs, NewHdrs),
  285. {Response, NewSocket};
  286. {error, closed} ->
  287. % Either we only noticed that the socket was closed after we
  288. % sent the request, the server closed it just after we put
  289. % the request on the wire or the server has some issues and is
  290. % closing connections without sending responses.
  291. % If this the first attempt to send the request, we will try again.
  292. lhttpc_sock:close(Socket, Ssl),
  293. NewState = State#client_state{
  294. socket = undefined,
  295. attempts = State#client_state.attempts - 1
  296. },
  297. send_request(NewState);
  298. {error, Reason} ->
  299. erlang:error(Reason)
  300. end.
  301. handle_response_body(#client_state{partial_download = false} = State, Vsn,
  302. Status, Hdrs) ->
  303. Socket = State#client_state.socket,
  304. Ssl = State#client_state.ssl,
  305. Method = State#client_state.method,
  306. {Body, NewHdrs} = case has_body(Method, element(1, Status), Hdrs) of
  307. true -> read_body(Vsn, Hdrs, Ssl, Socket, body_type(Hdrs));
  308. false -> {<<>>, Hdrs}
  309. end,
  310. {Status, NewHdrs, Body};
  311. handle_response_body(#client_state{partial_download = true} = State, Vsn,
  312. Status, Hdrs) ->
  313. Method = State#client_state.method,
  314. case has_body(Method, element(1, Status), Hdrs) of
  315. true ->
  316. Response = {ok, {Status, Hdrs, self()}},
  317. State#client_state.requester ! {response, self(), Response},
  318. MonRef = erlang:monitor(process, State#client_state.requester),
  319. Res = read_partial_body(State, Vsn, Hdrs, body_type(Hdrs)),
  320. erlang:demonitor(MonRef, [flush]),
  321. Res;
  322. false ->
  323. {Status, Hdrs, undefined}
  324. end.
  325. has_body("HEAD", _, _) ->
  326. % HEAD responses aren't allowed to include a body
  327. false;
  328. has_body("OPTIONS", _, Hdrs) ->
  329. % OPTIONS can include a body, if Content-Length or Transfer-Encoding
  330. % indicates it
  331. ContentLength = lhttpc_lib:header_value("content-length", Hdrs),
  332. TransferEncoding = lhttpc_lib:header_value("transfer-encoding", Hdrs),
  333. case {ContentLength, TransferEncoding} of
  334. {undefined, undefined} -> false;
  335. {_, _} -> true
  336. end;
  337. has_body(_, 204, _) ->
  338. false; % RFC 2616 10.2.5: 204 No Content
  339. has_body(_, 304, _) ->
  340. false; % RFC 2616 10.3.5: 304 Not Modified
  341. has_body(_, _, _) ->
  342. true. % All other responses are assumed to have a body
  343. body_type(Hdrs) ->
  344. % Find out how to read the entity body from the request.
  345. % * If we have a Content-Length, just use that and read the complete
  346. % entity.
  347. % * If Transfer-Encoding is set to chunked, we should read one chunk at
  348. % the time
  349. % * If neither of this is true, we need to read until the socket is
  350. % closed (AFAIK, this was common in versions before 1.1).
  351. case lhttpc_lib:header_value("content-length", Hdrs) of
  352. undefined ->
  353. TransferEncoding = string:to_lower(
  354. lhttpc_lib:header_value("transfer-encoding", Hdrs, "undefined")
  355. ),
  356. case TransferEncoding of
  357. "chunked" -> chunked;
  358. _ -> infinite
  359. end;
  360. ContentLength ->
  361. {fixed_length, list_to_integer(ContentLength)}
  362. end.
  363. read_partial_body(State, _Vsn, Hdrs, chunked) ->
  364. Window = State#client_state.download_window,
  365. read_partial_chunked_body(State, Hdrs, Window, 0, [], 0);
  366. read_partial_body(State, Vsn, Hdrs, infinite) ->
  367. check_infinite_response(Vsn, Hdrs),
  368. read_partial_infinite_body(State, Hdrs, State#client_state.download_window);
  369. read_partial_body(State, _Vsn, Hdrs, {fixed_length, ContentLength}) ->
  370. read_partial_finite_body(State, Hdrs, ContentLength,
  371. State#client_state.download_window).
  372. read_body(_Vsn, Hdrs, Ssl, Socket, chunked) ->
  373. read_chunked_body(Socket, Ssl, Hdrs, []);
  374. read_body(Vsn, Hdrs, Ssl, Socket, infinite) ->
  375. check_infinite_response(Vsn, Hdrs),
  376. read_infinite_body(Socket, Hdrs, Ssl);
  377. read_body(_Vsn, Hdrs, Ssl, Socket, {fixed_length, ContentLength}) ->
  378. read_length(Hdrs, Ssl, Socket, ContentLength).
  379. read_partial_finite_body(State = #client_state{}, Hdrs, 0, _Window) ->
  380. reply_end_of_body(State, [], Hdrs);
  381. read_partial_finite_body(State = #client_state{requester = To}, Hdrs,
  382. ContentLength, 0) ->
  383. receive
  384. {ack, To} ->
  385. read_partial_finite_body(State, Hdrs, ContentLength, 1);
  386. {'DOWN', _, process, To, _} ->
  387. exit(normal)
  388. end;
  389. read_partial_finite_body(State, Hdrs, ContentLength, Window) when Window >= 0->
  390. Bin = read_body_part(State, ContentLength),
  391. State#client_state.requester ! {body_part, self(), Bin},
  392. To = State#client_state.requester,
  393. receive
  394. {ack, To} ->
  395. Length = ContentLength - iolist_size(Bin),
  396. read_partial_finite_body(State, Hdrs, Length, Window);
  397. {'DOWN', _, process, To, _} ->
  398. exit(normal)
  399. after 0 ->
  400. Length = ContentLength - iolist_size(Bin),
  401. read_partial_finite_body(State, Hdrs, Length, lhttpc_lib:dec(Window))
  402. end.
  403. read_body_part(#client_state{part_size = infinity} = State, _ContentLength) ->
  404. case lhttpc_sock:recv(State#client_state.socket, State#client_state.ssl) of
  405. {ok, Data} ->
  406. Data;
  407. {error, Reason} ->
  408. erlang:error(Reason)
  409. end;
  410. read_body_part(#client_state{part_size = PartSize} = State, ContentLength)
  411. when PartSize =< ContentLength ->
  412. Socket = State#client_state.socket,
  413. Ssl = State#client_state.ssl,
  414. PartSize = State#client_state.part_size,
  415. case lhttpc_sock:recv(Socket, PartSize, Ssl) of
  416. {ok, Data} ->
  417. Data;
  418. {error, Reason} ->
  419. erlang:error(Reason)
  420. end;
  421. read_body_part(#client_state{part_size = PartSize} = State, ContentLength)
  422. when PartSize > ContentLength ->
  423. Socket = State#client_state.socket,
  424. Ssl = State#client_state.ssl,
  425. case lhttpc_sock:recv(Socket, ContentLength, Ssl) of
  426. {ok, Data} ->
  427. Data;
  428. {error, Reason} ->
  429. erlang:error(Reason)
  430. end.
  431. read_length(Hdrs, Ssl, Socket, Length) ->
  432. case lhttpc_sock:recv(Socket, Length, Ssl) of
  433. {ok, Data} ->
  434. {Data, Hdrs};
  435. {error, Reason} ->
  436. erlang:error(Reason)
  437. end.
  438. read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, 0) ->
  439. Socket = State#client_state.socket,
  440. Ssl = State#client_state.ssl,
  441. PartSize = State#client_state.part_size,
  442. case read_chunk_size(Socket, Ssl) of
  443. 0 ->
  444. reply_chunked_part(State, Buffer, Window),
  445. {Trailers, NewHdrs} = read_trailers(Socket, Ssl, [], Hdrs),
  446. reply_end_of_body(State, Trailers, NewHdrs);
  447. ChunkSize when PartSize =:= infinity ->
  448. Chunk = read_chunk(Socket, Ssl, ChunkSize),
  449. NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window),
  450. read_partial_chunked_body(State, Hdrs, NewWindow, 0, [], 0);
  451. ChunkSize when BufferSize + ChunkSize >= PartSize ->
  452. {Chunk, RemSize} = read_partial_chunk(Socket, Ssl,
  453. PartSize - BufferSize, ChunkSize),
  454. NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window),
  455. read_partial_chunked_body(State, Hdrs, NewWindow, 0, [], RemSize);
  456. ChunkSize ->
  457. Chunk = read_chunk(Socket, Ssl, ChunkSize),
  458. read_partial_chunked_body(State, Hdrs, Window,
  459. BufferSize + ChunkSize, [Chunk | Buffer], 0)
  460. end;
  461. read_partial_chunked_body(State, Hdrs, Window, BufferSize, Buffer, RemSize) ->
  462. Socket = State#client_state.socket,
  463. Ssl = State#client_state.ssl,
  464. PartSize = State#client_state.part_size,
  465. if
  466. BufferSize + RemSize >= PartSize ->
  467. {Chunk, NewRemSize} =
  468. read_partial_chunk(Socket, Ssl, PartSize - BufferSize, RemSize),
  469. NewWindow = reply_chunked_part(State, [Chunk | Buffer], Window),
  470. read_partial_chunked_body(State, Hdrs, NewWindow, 0, [],
  471. NewRemSize);
  472. BufferSize + RemSize < PartSize ->
  473. Chunk = read_chunk(Socket, Ssl, RemSize),
  474. read_partial_chunked_body(State, Hdrs, Window, BufferSize + RemSize,
  475. [Chunk | Buffer], 0)
  476. end.
  477. read_chunk_size(Socket, Ssl) ->
  478. lhttpc_sock:setopts(Socket, [{packet, line}], Ssl),
  479. case lhttpc_sock:recv(Socket, Ssl) of
  480. {ok, ChunkSizeExt} ->
  481. chunk_size(ChunkSizeExt);
  482. {error, Reason} ->
  483. erlang:error(Reason)
  484. end.
  485. reply_chunked_part(_State, [], Window) ->
  486. Window;
  487. reply_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;
  494. reply_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. read_chunked_body(Socket, Ssl, Hdrs, Chunks) ->
  503. case read_chunk_size(Socket, Ssl) of
  504. 0 ->
  505. Body = list_to_binary(lists:reverse(Chunks)),
  506. {_, NewHdrs} = read_trailers(Socket, Ssl, [], Hdrs),
  507. {Body, NewHdrs};
  508. Size ->
  509. Chunk = read_chunk(Socket, Ssl, Size),
  510. read_chunked_body(Socket, Ssl, Hdrs, [Chunk | Chunks])
  511. end.
  512. chunk_size(Bin) ->
  513. erlang:list_to_integer(lists:reverse(chunk_size(Bin, [])), 16).
  514. chunk_size(<<$;, _/binary>>, Chars) ->
  515. Chars;
  516. chunk_size(<<"\r\n", _/binary>>, Chars) ->
  517. Chars;
  518. chunk_size(<<$\s, Binary/binary>>, Chars) ->
  519. %% Facebook's HTTP server returns a chunk size like "6 \r\n"
  520. chunk_size(Binary, Chars);
  521. chunk_size(<<Char, Binary/binary>>, Chars) ->
  522. chunk_size(Binary, [Char | Chars]).
  523. read_partial_chunk(Socket, Ssl, ChunkSize, ChunkSize) ->
  524. {read_chunk(Socket, Ssl, ChunkSize), 0};
  525. read_partial_chunk(Socket, Ssl, Size, ChunkSize) ->
  526. lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl),
  527. case lhttpc_sock:recv(Socket, Size, Ssl) of
  528. {ok, Chunk} ->
  529. {Chunk, ChunkSize - Size};
  530. {error, Reason} ->
  531. erlang:error(Reason)
  532. end.
  533. read_chunk(Socket, Ssl, Size) ->
  534. lhttpc_sock:setopts(Socket, [{packet, raw}], Ssl),
  535. case lhttpc_sock:recv(Socket, Size + 2, Ssl) of
  536. {ok, <<Chunk:Size/binary, "\r\n">>} ->
  537. Chunk;
  538. {ok, Data} ->
  539. erlang:error({invalid_chunk, Data});
  540. {error, Reason} ->
  541. erlang:error(Reason)
  542. end.
  543. read_trailers(Socket, Ssl, Trailers, Hdrs) ->
  544. lhttpc_sock:setopts(Socket, [{packet, httph}], Ssl),
  545. case lhttpc_sock:recv(Socket, Ssl) of
  546. {ok, http_eoh} ->
  547. {Trailers, Hdrs};
  548. {ok, {http_header, _, Name, _, Value}} ->
  549. Header = {lhttpc_lib:maybe_atom_to_list(Name), Value},
  550. read_trailers(Socket, Ssl, [Header | Trailers], [Header | Hdrs]);
  551. {error, {http_error, Data}} ->
  552. erlang:error({bad_trailer, Data})
  553. end.
  554. reply_end_of_body(#client_state{requester = Requester}, Trailers, Hdrs) ->
  555. Requester ! {http_eob, self(), Trailers},
  556. {no_return, Hdrs}.
  557. read_partial_infinite_body(State = #client_state{requester = To}, Hdrs, 0) ->
  558. receive
  559. {ack, To} ->
  560. read_partial_infinite_body(State, Hdrs, 1);
  561. {'DOWN', _, process, To, _} ->
  562. exit(normal)
  563. end;
  564. read_partial_infinite_body(State = #client_state{requester = To}, Hdrs, Window)
  565. when Window >= 0 ->
  566. case read_infinite_body_part(State) of
  567. http_eob -> reply_end_of_body(State, [], Hdrs);
  568. Bin ->
  569. State#client_state.requester ! {body_part, self(), Bin},
  570. receive
  571. {ack, To} ->
  572. read_partial_infinite_body(State, Hdrs, Window);
  573. {'DOWN', _, process, To, _} ->
  574. exit(normal)
  575. after 0 ->
  576. read_partial_infinite_body(State, Hdrs, lhttpc_lib:dec(Window))
  577. end
  578. end.
  579. read_infinite_body_part(#client_state{socket = Socket, ssl = Ssl}) ->
  580. case lhttpc_sock:recv(Socket, Ssl) of
  581. {ok, Data} ->
  582. Data;
  583. {error, closed} ->
  584. http_eob;
  585. {error, Reason} ->
  586. erlang:error(Reason)
  587. end.
  588. check_infinite_response({1, Minor}, Hdrs) when Minor >= 1 ->
  589. HdrValue = lhttpc_lib:header_value("connection", Hdrs, "keep-alive"),
  590. case string:to_lower(HdrValue) of
  591. "close" -> ok;
  592. _ -> erlang:error(no_content_length)
  593. end;
  594. check_infinite_response(_, Hdrs) ->
  595. HdrValue = lhttpc_lib:header_value("connection", Hdrs, "close"),
  596. case string:to_lower(HdrValue) of
  597. "keep-alive" -> erlang:error(no_content_length);
  598. _ -> ok
  599. end.
  600. read_infinite_body(Socket, Hdrs, Ssl) ->
  601. read_until_closed(Socket, <<>>, Hdrs, Ssl).
  602. read_until_closed(Socket, Acc, Hdrs, Ssl) ->
  603. case lhttpc_sock:recv(Socket, Ssl) of
  604. {ok, Body} ->
  605. NewAcc = <<Acc/binary, Body/binary>>,
  606. read_until_closed(Socket, NewAcc, Hdrs, Ssl);
  607. {error, closed} ->
  608. {Acc, Hdrs};
  609. {error, Reason} ->
  610. erlang:error(Reason)
  611. end.
  612. maybe_close_socket(Socket, Ssl, {1, Minor}, ReqHdrs, RespHdrs) when Minor >= 1->
  613. ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"),
  614. ServerConnection = ?CONNECTION_HDR(RespHdrs, "keep-alive"),
  615. if
  616. ClientConnection =:= "close"; ServerConnection =:= "close" ->
  617. lhttpc_sock:close(Socket, Ssl),
  618. undefined;
  619. ClientConnection =/= "close", ServerConnection =/= "close" ->
  620. Socket
  621. end;
  622. maybe_close_socket(Socket, Ssl, _, ReqHdrs, RespHdrs) ->
  623. ClientConnection = ?CONNECTION_HDR(ReqHdrs, "keep-alive"),
  624. ServerConnection = ?CONNECTION_HDR(RespHdrs, "close"),
  625. if
  626. ClientConnection =:= "close"; ServerConnection =/= "keep-alive" ->
  627. lhttpc_sock:close(Socket, Ssl),
  628. undefined;
  629. ClientConnection =/= "close", ServerConnection =:= "keep-alive" ->
  630. Socket
  631. end.
  632. determine_destination({ReqHost, ReqPort}, {undefined, _}, _) ->
  633. {ReqHost, ReqPort};
  634. determine_destination(_, {ProxyHost, ProxyPort}, []) ->
  635. {ProxyHost, ProxyPort};
  636. determine_destination(_, {ProxyHost, ProxyPort}, undefined) ->
  637. {ProxyHost, ProxyPort};
  638. determine_destination(OrigAddr, Proxy, NoProxy) ->
  639. case lists:member(Proxy, NoProxy) of
  640. true ->
  641. OrigAddr;
  642. false ->
  643. Proxy
  644. end.