cowboy_client.erl 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. %% Copyright (c) 2012-2013, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. %% @private
  15. -module(cowboy_client).
  16. -export([init/1]).
  17. -export([state/1]).
  18. -export([transport/1]).
  19. -export([connect/4]).
  20. -export([raw_request/2]).
  21. -export([request/3]).
  22. -export([request/4]).
  23. -export([request/5]).
  24. -export([response/1]).
  25. -export([response_body/1]).
  26. -export([skip_body/1]).
  27. -export([stream_status/1]).
  28. -export([stream_headers/1]).
  29. -export([stream_header/1]).
  30. -export([stream_body/1]).
  31. -record(client, {
  32. state = wait :: wait | request | response | response_body,
  33. opts = [] :: [any()],
  34. socket = undefined :: undefined | inet:socket(),
  35. transport = undefined :: module(),
  36. timeout = 5000 :: timeout(), %% @todo Configurable.
  37. buffer = <<>> :: binary(),
  38. connection = keepalive :: keepalive | close,
  39. version = {1, 1} :: cowboy_http:version(),
  40. response_body = undefined :: undefined | non_neg_integer()
  41. }).
  42. init(Opts) ->
  43. {ok, #client{opts=Opts}}.
  44. state(#client{state=State}) ->
  45. State.
  46. transport(#client{socket=undefined}) ->
  47. {error, notconnected};
  48. transport(#client{transport=Transport, socket=Socket}) ->
  49. {ok, Transport, Socket}.
  50. connect(Transport, Host, Port, Client)
  51. when is_binary(Host) ->
  52. connect(Transport, binary_to_list(Host), Port, Client);
  53. connect(Transport, Host, Port, Client=#client{state=State, opts=Opts})
  54. when is_atom(Transport), is_list(Host),
  55. is_integer(Port), is_record(Client, client),
  56. State =:= wait ->
  57. {ok, Socket} = Transport:connect(Host, Port, Opts),
  58. {ok, Client#client{state=request, socket=Socket, transport=Transport}}.
  59. raw_request(Data, Client=#client{state=response_body}) ->
  60. {done, Client2} = skip_body(Client),
  61. raw_request(Data, Client2);
  62. raw_request(Data, Client=#client{
  63. state=State, socket=Socket, transport=Transport})
  64. when State =:= request ->
  65. ok = Transport:send(Socket, Data),
  66. {ok, Client}.
  67. request(Method, URL, Client) ->
  68. request(Method, URL, [], <<>>, Client).
  69. request(Method, URL, Headers, Client) ->
  70. request(Method, URL, Headers, <<>>, Client).
  71. request(Method, URL, Headers, Body, Client=#client{state=response_body}) ->
  72. {done, Client2} = skip_body(Client),
  73. request(Method, URL, Headers, Body, Client2);
  74. request(Method, URL, Headers, Body, Client=#client{
  75. state=State, version=Version})
  76. when State =:= wait; State =:= request ->
  77. {Transport, FullHost, Host, Port, Path} = parse_url(URL),
  78. {ok, Client2} = case State of
  79. wait -> connect(Transport, Host, Port, Client);
  80. request -> {ok, Client}
  81. end,
  82. VersionBin = cowboy_http:version_to_binary(Version),
  83. %% @todo do keepalive too, allow override...
  84. Headers2 = [
  85. {<<"host">>, FullHost},
  86. {<<"user-agent">>, <<"Cow">>}
  87. |Headers],
  88. Headers3 = case iolist_size(Body) of
  89. 0 -> Headers2;
  90. Length -> [{<<"content-length">>, integer_to_list(Length)}|Headers2]
  91. end,
  92. HeadersData = [[Name, <<": ">>, Value, <<"\r\n">>]
  93. || {Name, Value} <- Headers3],
  94. Data = [Method, <<" ">>, Path, <<" ">>, VersionBin, <<"\r\n">>,
  95. HeadersData, <<"\r\n">>, Body],
  96. raw_request(Data, Client2).
  97. parse_url(<< "https://", Rest/binary >>) ->
  98. parse_url(Rest, ranch_ssl);
  99. parse_url(<< "http://", Rest/binary >>) ->
  100. parse_url(Rest, ranch_tcp);
  101. parse_url(URL) ->
  102. parse_url(URL, ranch_tcp).
  103. parse_url(URL, Transport) ->
  104. case binary:split(URL, <<"/">>) of
  105. [Peer] ->
  106. {Host, Port} = parse_peer(Peer, Transport),
  107. {Transport, Peer, Host, Port, <<"/">>};
  108. [Peer, Path] ->
  109. {Host, Port} = parse_peer(Peer, Transport),
  110. {Transport, Peer, Host, Port, [<<"/">>, Path]}
  111. end.
  112. parse_peer(Peer, Transport) ->
  113. case binary:split(Peer, <<":">>) of
  114. [Host] when Transport =:= ranch_tcp ->
  115. {binary_to_list(Host), 80};
  116. [Host] when Transport =:= ranch_ssl ->
  117. {binary_to_list(Host), 443};
  118. [Host, Port] ->
  119. {binary_to_list(Host), list_to_integer(binary_to_list(Port))}
  120. end.
  121. response(Client=#client{state=response_body}) ->
  122. {done, Client2} = skip_body(Client),
  123. response(Client2);
  124. response(Client=#client{state=request}) ->
  125. case stream_status(Client) of
  126. {ok, Status, _, Client2} ->
  127. case stream_headers(Client2) of
  128. {ok, Headers, Client3} ->
  129. {ok, Status, Headers, Client3};
  130. {error, Reason} ->
  131. {error, Reason}
  132. end;
  133. {error, Reason} ->
  134. {error, Reason}
  135. end.
  136. response_body(Client=#client{state=response_body}) ->
  137. response_body_loop(Client, <<>>).
  138. response_body_loop(Client, Acc) ->
  139. case stream_body(Client) of
  140. {ok, Data, Client2} ->
  141. response_body_loop(Client2, << Acc/binary, Data/binary >>);
  142. {done, Client2} ->
  143. {ok, Acc, Client2};
  144. {error, Reason} ->
  145. {error, Reason}
  146. end.
  147. skip_body(Client=#client{state=response_body}) ->
  148. case stream_body(Client) of
  149. {ok, _, Client2} -> skip_body(Client2);
  150. Done -> Done
  151. end.
  152. stream_status(Client=#client{state=State, buffer=Buffer})
  153. when State =:= request ->
  154. case binary:split(Buffer, <<"\r\n">>) of
  155. [Line, Rest] ->
  156. parse_status(Client#client{state=response, buffer=Rest}, Line);
  157. _ ->
  158. case recv(Client) of
  159. {ok, Data} ->
  160. Buffer2 = << Buffer/binary, Data/binary >>,
  161. stream_status(Client#client{buffer=Buffer2});
  162. {error, Reason} ->
  163. {error, Reason}
  164. end
  165. end.
  166. parse_status(Client, << "HTTP/", High, ".", Low, " ",
  167. S3, S2, S1, " ", StatusStr/binary >>)
  168. when High >= $0, High =< $9, Low >= $0, Low =< $9,
  169. S3 >= $0, S3 =< $9, S2 >= $0, S2 =< $9, S1 >= $0, S1 =< $9 ->
  170. Version = {High - $0, Low - $0},
  171. Status = (S3 - $0) * 100 + (S2 - $0) * 10 + S1 - $0,
  172. {ok, Status, StatusStr, Client#client{version=Version}}.
  173. stream_headers(Client=#client{state=State})
  174. when State =:= response ->
  175. stream_headers(Client, []).
  176. stream_headers(Client, Acc) ->
  177. case stream_header(Client) of
  178. {ok, Name, Value, Client2} ->
  179. stream_headers(Client2, [{Name, Value}|Acc]);
  180. {done, Client2} ->
  181. {ok, Acc, Client2};
  182. {error, Reason} ->
  183. {error, Reason}
  184. end.
  185. stream_header(Client=#client{state=State, buffer=Buffer,
  186. response_body=RespBody}) when State =:= response ->
  187. case binary:split(Buffer, <<"\r\n">>) of
  188. [<<>>, Rest] ->
  189. %% If we have a body, set response_body.
  190. Client2 = case RespBody of
  191. undefined -> Client#client{state=request};
  192. 0 -> Client#client{state=request};
  193. _ -> Client#client{state=response_body}
  194. end,
  195. {done, Client2#client{buffer=Rest}};
  196. [Line, Rest] ->
  197. %% @todo Do a better parsing later on.
  198. [Name, Value] = binary:split(Line, <<": ">>),
  199. Name2 = cowboy_bstr:to_lower(Name),
  200. Client2 = case Name2 of
  201. <<"content-length">> ->
  202. Length = list_to_integer(binary_to_list(Value)),
  203. if Length >= 0 -> ok end,
  204. Client#client{response_body=Length};
  205. _ ->
  206. Client
  207. end,
  208. {ok, Name2, Value, Client2#client{buffer=Rest}};
  209. _ ->
  210. case recv(Client) of
  211. {ok, Data} ->
  212. Buffer2 = << Buffer/binary, Data/binary >>,
  213. stream_header(Client#client{buffer=Buffer2});
  214. {error, Reason} ->
  215. {error, Reason}
  216. end
  217. end.
  218. stream_body(Client=#client{state=response_body, response_body=RespBody})
  219. when RespBody =:= undefined; RespBody =:= 0 ->
  220. {done, Client#client{state=request, response_body=undefined}};
  221. stream_body(Client=#client{state=response_body, buffer=Buffer,
  222. response_body=Length}) when is_integer(Length) ->
  223. case byte_size(Buffer) of
  224. 0 ->
  225. case recv(Client) of
  226. {ok, Body} when byte_size(Body) =< Length ->
  227. Length2 = Length - byte_size(Body),
  228. {ok, Body, Client#client{response_body=Length2}};
  229. {ok, Data} ->
  230. << Body:Length/binary, Rest/binary >> = Data,
  231. {ok, Body, Client#client{buffer=Rest,
  232. response_body=undefined}};
  233. {error, Reason} ->
  234. {error, Reason}
  235. end;
  236. N when N =< Length ->
  237. Length2 = Length - N,
  238. {ok, Buffer, Client#client{buffer= <<>>, response_body=Length2}};
  239. _ ->
  240. << Body:Length/binary, Rest/binary >> = Buffer,
  241. {ok, Body, Client#client{buffer=Rest, response_body=undefined}}
  242. end.
  243. recv(#client{socket=Socket, transport=Transport, timeout=Timeout}) ->
  244. Transport:recv(Socket, 0, Timeout).