cowboy_http_protocol.erl 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434
  1. %% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.eu>
  2. %% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
  3. %%
  4. %% Permission to use, copy, modify, and/or distribute this software for any
  5. %% purpose with or without fee is hereby granted, provided that the above
  6. %% copyright notice and this permission notice appear in all copies.
  7. %%
  8. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. %% @doc HTTP protocol handler.
  16. %%
  17. %% The available options are:
  18. %% <dl>
  19. %% <dt>dispatch</dt><dd>The dispatch list for this protocol.</dd>
  20. %% <dt>max_empty_lines</dt><dd>Max number of empty lines before a request.
  21. %% Defaults to 5.</dd>
  22. %% <dt>timeout</dt><dd>Time in milliseconds before an idle
  23. %% connection is closed. Defaults to 5000 milliseconds.</dd>
  24. %% </dl>
  25. %%
  26. %% Note that there is no need to monitor these processes when using Cowboy as
  27. %% an application as it already supervises them under the listener supervisor.
  28. %%
  29. %% @see cowboy_dispatcher
  30. %% @see cowboy_http_handler
  31. -module(cowboy_http_protocol).
  32. -behaviour(cowboy_protocol).
  33. -export([start_link/4]). %% API.
  34. -export([init/4, parse_request/1, handler_loop/3]). %% FSM.
  35. -include("include/http.hrl").
  36. -include_lib("eunit/include/eunit.hrl").
  37. -record(state, {
  38. listener :: pid(),
  39. socket :: inet:socket(),
  40. transport :: module(),
  41. dispatch :: cowboy_dispatcher:dispatch_rules(),
  42. handler :: {module(), any()},
  43. req_empty_lines = 0 :: integer(),
  44. max_empty_lines :: integer(),
  45. max_line_length :: integer(),
  46. timeout :: timeout(),
  47. buffer = <<>> :: binary(),
  48. hibernate = false :: boolean(),
  49. loop_timeout = infinity :: timeout(),
  50. loop_timeout_ref :: undefined | reference()
  51. }).
  52. %% API.
  53. %% @doc Start an HTTP protocol process.
  54. -spec start_link(pid(), inet:socket(), module(), any()) -> {ok, pid()}.
  55. start_link(ListenerPid, Socket, Transport, Opts) ->
  56. Pid = spawn_link(?MODULE, init, [ListenerPid, Socket, Transport, Opts]),
  57. {ok, Pid}.
  58. %% FSM.
  59. %% @private
  60. -spec init(pid(), inet:socket(), module(), any()) -> ok | none().
  61. init(ListenerPid, Socket, Transport, Opts) ->
  62. Dispatch = proplists:get_value(dispatch, Opts, []),
  63. MaxEmptyLines = proplists:get_value(max_empty_lines, Opts, 5),
  64. MaxLineLength = proplists:get_value(max_line_length, Opts, 4096),
  65. Timeout = proplists:get_value(timeout, Opts, 5000),
  66. receive shoot -> ok end,
  67. wait_request(#state{listener=ListenerPid, socket=Socket, transport=Transport,
  68. dispatch=Dispatch, max_empty_lines=MaxEmptyLines,
  69. max_line_length=MaxLineLength, timeout=Timeout}).
  70. %% @private
  71. -spec parse_request(#state{}) -> ok | none().
  72. %% We limit the length of the Request-line to MaxLength to avoid endlessly
  73. %% reading from the socket and eventually crashing.
  74. parse_request(State=#state{buffer=Buffer, max_line_length=MaxLength}) ->
  75. case erlang:decode_packet(http_bin, Buffer, []) of
  76. {ok, Request, Rest} -> request(Request, State#state{buffer=Rest});
  77. {more, _Length} when byte_size(Buffer) > MaxLength ->
  78. error_terminate(413, State);
  79. {more, _Length} -> wait_request(State);
  80. {error, _Reason} -> error_terminate(400, State)
  81. end.
  82. -spec wait_request(#state{}) -> ok | none().
  83. wait_request(State=#state{socket=Socket, transport=Transport,
  84. timeout=T, buffer=Buffer}) ->
  85. case Transport:recv(Socket, 0, T) of
  86. {ok, Data} -> parse_request(State#state{
  87. buffer= << Buffer/binary, Data/binary >>});
  88. {error, _Reason} -> terminate(State)
  89. end.
  90. -spec request({http_request, http_method(), http_uri(),
  91. http_version()}, #state{}) -> ok | none().
  92. %% @todo We probably want to handle some things differently between versions.
  93. request({http_request, _Method, _URI, Version}, State)
  94. when Version =/= {1, 0}, Version =/= {1, 1} ->
  95. error_terminate(505, State);
  96. %% @todo We need to cleanup the URI properly.
  97. request({http_request, Method, {abs_path, AbsPath}, Version},
  98. State=#state{socket=Socket, transport=Transport}) ->
  99. {Path, RawPath, Qs} = cowboy_dispatcher:split_path(AbsPath),
  100. ConnAtom = version_to_connection(Version),
  101. parse_header(#http_req{socket=Socket, transport=Transport,
  102. connection=ConnAtom, method=Method, version=Version,
  103. path=Path, raw_path=RawPath, raw_qs=Qs}, State);
  104. request({http_request, Method, '*', Version},
  105. State=#state{socket=Socket, transport=Transport}) ->
  106. ConnAtom = version_to_connection(Version),
  107. parse_header(#http_req{socket=Socket, transport=Transport,
  108. connection=ConnAtom, method=Method, version=Version,
  109. path='*', raw_path= <<"*">>, raw_qs= <<>>}, State);
  110. request({http_request, _Method, _URI, _Version}, State) ->
  111. error_terminate(501, State);
  112. request({http_error, <<"\r\n">>},
  113. State=#state{req_empty_lines=N, max_empty_lines=N}) ->
  114. error_terminate(400, State);
  115. request({http_error, <<"\r\n">>}, State=#state{req_empty_lines=N}) ->
  116. parse_request(State#state{req_empty_lines=N + 1});
  117. request({http_error, _Any}, State) ->
  118. error_terminate(400, State).
  119. -spec parse_header(#http_req{}, #state{}) -> ok | none().
  120. parse_header(Req, State=#state{buffer=Buffer, max_line_length=MaxLength}) ->
  121. case erlang:decode_packet(httph_bin, Buffer, []) of
  122. {ok, Header, Rest} -> header(Header, Req, State#state{buffer=Rest});
  123. {more, _Length} when byte_size(Buffer) > MaxLength ->
  124. error_terminate(413, State);
  125. {more, _Length} -> wait_header(Req, State);
  126. {error, _Reason} -> error_terminate(400, State)
  127. end.
  128. -spec wait_header(#http_req{}, #state{}) -> ok | none().
  129. wait_header(Req, State=#state{socket=Socket,
  130. transport=Transport, timeout=T, buffer=Buffer}) ->
  131. case Transport:recv(Socket, 0, T) of
  132. {ok, Data} -> parse_header(Req, State#state{
  133. buffer= << Buffer/binary, Data/binary >>});
  134. {error, timeout} -> error_terminate(408, State);
  135. {error, closed} -> terminate(State)
  136. end.
  137. -spec header({http_header, integer(), http_header(), any(), binary()}
  138. | http_eoh, #http_req{}, #state{}) -> ok | none().
  139. header({http_header, _I, 'Host', _R, RawHost}, Req=#http_req{
  140. transport=Transport, host=undefined}, State) ->
  141. RawHost2 = cowboy_bstr:to_lower(RawHost),
  142. case catch cowboy_dispatcher:split_host(RawHost2) of
  143. {Host, RawHost3, undefined} ->
  144. Port = default_port(Transport:name()),
  145. dispatch(fun parse_header/2, Req#http_req{
  146. host=Host, raw_host=RawHost3, port=Port,
  147. headers=[{'Host', RawHost3}|Req#http_req.headers]}, State);
  148. {Host, RawHost3, Port} ->
  149. dispatch(fun parse_header/2, Req#http_req{
  150. host=Host, raw_host=RawHost3, port=Port,
  151. headers=[{'Host', RawHost3}|Req#http_req.headers]}, State);
  152. {'EXIT', _Reason} ->
  153. error_terminate(400, State)
  154. end;
  155. %% Ignore Host headers if we already have it.
  156. header({http_header, _I, 'Host', _R, _V}, Req, State) ->
  157. parse_header(Req, State);
  158. header({http_header, _I, 'Connection', _R, Connection},
  159. Req=#http_req{headers=Headers}, State) ->
  160. Req2 = Req#http_req{headers=[{'Connection', Connection}|Headers]},
  161. {ConnTokens, Req3}
  162. = cowboy_http_req:parse_header('Connection', Req2),
  163. ConnAtom = cowboy_http:connection_to_atom(ConnTokens),
  164. parse_header(Req3#http_req{connection=ConnAtom}, State);
  165. header({http_header, _I, Field, _R, Value}, Req, State) ->
  166. Field2 = format_header(Field),
  167. parse_header(Req#http_req{headers=[{Field2, Value}|Req#http_req.headers]},
  168. State);
  169. %% The Host header is required in HTTP/1.1.
  170. header(http_eoh, #http_req{version={1, 1}, host=undefined}, State) ->
  171. error_terminate(400, State);
  172. %% It is however optional in HTTP/1.0.
  173. header(http_eoh, Req=#http_req{version={1, 0}, transport=Transport,
  174. host=undefined}, State=#state{buffer=Buffer}) ->
  175. Port = default_port(Transport:name()),
  176. dispatch(fun handler_init/2, Req#http_req{host=[], raw_host= <<>>,
  177. port=Port, buffer=Buffer}, State#state{buffer= <<>>});
  178. header(http_eoh, Req, State=#state{buffer=Buffer}) ->
  179. handler_init(Req#http_req{buffer=Buffer}, State#state{buffer= <<>>});
  180. header({http_error, _Bin}, _Req, State) ->
  181. error_terminate(500, State).
  182. -spec dispatch(fun((#http_req{}, #state{}) -> ok),
  183. #http_req{}, #state{}) -> ok | none().
  184. dispatch(Next, Req=#http_req{host=Host, path=Path},
  185. State=#state{dispatch=Dispatch}) ->
  186. %% @todo We probably want to filter the Host and Path here to allow
  187. %% things like url rewriting.
  188. case cowboy_dispatcher:match(Host, Path, Dispatch) of
  189. {ok, Handler, Opts, Binds, HostInfo, PathInfo} ->
  190. Next(Req#http_req{host_info=HostInfo, path_info=PathInfo,
  191. bindings=Binds}, State#state{handler={Handler, Opts}});
  192. {error, notfound, host} ->
  193. error_terminate(400, State);
  194. {error, notfound, path} ->
  195. error_terminate(404, State)
  196. end.
  197. -spec handler_init(#http_req{}, #state{}) -> ok | none().
  198. handler_init(Req, State=#state{listener=ListenerPid,
  199. transport=Transport, handler={Handler, Opts}}) ->
  200. try Handler:init({Transport:name(), http}, Req, Opts) of
  201. {ok, Req2, HandlerState} ->
  202. handler_handle(HandlerState, Req2, State);
  203. {loop, Req2, HandlerState} ->
  204. handler_before_loop(HandlerState, Req2, State);
  205. {loop, Req2, HandlerState, hibernate} ->
  206. handler_before_loop(HandlerState, Req2,
  207. State#state{hibernate=true});
  208. {loop, Req2, HandlerState, Timeout} ->
  209. handler_before_loop(HandlerState, Req2,
  210. State#state{loop_timeout=Timeout});
  211. {loop, Req2, HandlerState, Timeout, hibernate} ->
  212. handler_before_loop(HandlerState, Req2,
  213. State#state{hibernate=true, loop_timeout=Timeout});
  214. {shutdown, Req2, HandlerState} ->
  215. handler_terminate(HandlerState, Req2, State);
  216. %% @todo {upgrade, transport, Module}
  217. {upgrade, protocol, Module} ->
  218. Module:upgrade(ListenerPid, Handler, Opts, Req)
  219. catch Class:Reason ->
  220. error_terminate(500, State),
  221. error_logger:error_msg(
  222. "** Handler ~p terminating in init/3~n"
  223. " for the reason ~p:~p~n"
  224. "** Options were ~p~n"
  225. "** Request was ~p~n** Stacktrace: ~p~n~n",
  226. [Handler, Class, Reason, Opts, Req, erlang:get_stacktrace()])
  227. end.
  228. -spec handler_handle(any(), #http_req{}, #state{}) -> ok | none().
  229. handler_handle(HandlerState, Req, State=#state{handler={Handler, Opts}}) ->
  230. try Handler:handle(Req, HandlerState) of
  231. {ok, Req2, HandlerState2} ->
  232. next_request(HandlerState2, Req2, State)
  233. catch Class:Reason ->
  234. error_logger:error_msg(
  235. "** Handler ~p terminating in handle/2~n"
  236. " for the reason ~p:~p~n"
  237. "** Options were ~p~n** Handler state was ~p~n"
  238. "** Request was ~p~n** Stacktrace: ~p~n~n",
  239. [Handler, Class, Reason, Opts,
  240. HandlerState, Req, erlang:get_stacktrace()]),
  241. handler_terminate(HandlerState, Req, State),
  242. terminate(State)
  243. end.
  244. %% We don't listen for Transport closes because that would force us
  245. %% to receive data and buffer it indefinitely.
  246. -spec handler_before_loop(any(), #http_req{}, #state{}) -> ok | none().
  247. handler_before_loop(HandlerState, Req, State=#state{hibernate=true}) ->
  248. State2 = handler_loop_timeout(State),
  249. erlang:hibernate(?MODULE, handler_loop,
  250. [HandlerState, Req, State2#state{hibernate=false}]);
  251. handler_before_loop(HandlerState, Req, State) ->
  252. State2 = handler_loop_timeout(State),
  253. handler_loop(HandlerState, Req, State2).
  254. %% Almost the same code can be found in cowboy_http_websocket.
  255. -spec handler_loop_timeout(#state{}) -> #state{}.
  256. handler_loop_timeout(State=#state{loop_timeout=infinity}) ->
  257. State#state{loop_timeout_ref=undefined};
  258. handler_loop_timeout(State=#state{loop_timeout=Timeout,
  259. loop_timeout_ref=PrevRef}) ->
  260. _ = case PrevRef of undefined -> ignore; PrevRef ->
  261. erlang:cancel_timer(PrevRef) end,
  262. TRef = make_ref(),
  263. erlang:send_after(Timeout, self(), {?MODULE, timeout, TRef}),
  264. State#state{loop_timeout_ref=TRef}.
  265. -spec handler_loop(any(), #http_req{}, #state{}) -> ok | none().
  266. handler_loop(HandlerState, Req, State=#state{loop_timeout_ref=TRef}) ->
  267. receive
  268. {?MODULE, timeout, TRef} ->
  269. next_request(HandlerState, Req, State);
  270. {?MODULE, timeout, OlderTRef} when is_reference(OlderTRef) ->
  271. handler_loop(HandlerState, Req, State);
  272. Message ->
  273. handler_call(HandlerState, Req, State, Message)
  274. end.
  275. -spec handler_call(any(), #http_req{}, #state{}, any()) -> ok | none().
  276. handler_call(HandlerState, Req, State=#state{handler={Handler, Opts}},
  277. Message) ->
  278. try Handler:info(Message, Req, HandlerState) of
  279. {ok, Req2, HandlerState2} ->
  280. next_request(HandlerState2, Req2, State);
  281. {loop, Req2, HandlerState2} ->
  282. handler_before_loop(HandlerState2, Req2, State);
  283. {loop, Req2, HandlerState2, hibernate} ->
  284. handler_before_loop(HandlerState2, Req2,
  285. State#state{hibernate=true})
  286. catch Class:Reason ->
  287. error_logger:error_msg(
  288. "** Handler ~p terminating in info/3~n"
  289. " for the reason ~p:~p~n"
  290. "** Options were ~p~n** Handler state was ~p~n"
  291. "** Request was ~p~n** Stacktrace: ~p~n~n",
  292. [Handler, Class, Reason, Opts,
  293. HandlerState, Req, erlang:get_stacktrace()])
  294. end.
  295. -spec handler_terminate(any(), #http_req{}, #state{}) -> ok.
  296. handler_terminate(HandlerState, Req, #state{handler={Handler, Opts}}) ->
  297. try
  298. Handler:terminate(Req#http_req{resp_state=locked}, HandlerState)
  299. catch Class:Reason ->
  300. error_logger:error_msg(
  301. "** Handler ~p terminating in terminate/2~n"
  302. " for the reason ~p:~p~n"
  303. "** Options were ~p~n** Handler state was ~p~n"
  304. "** Request was ~p~n** Stacktrace: ~p~n~n",
  305. [Handler, Class, Reason, Opts,
  306. HandlerState, Req, erlang:get_stacktrace()])
  307. end.
  308. -spec next_request(any(), #http_req{}, #state{}) -> ok | none().
  309. next_request(HandlerState, Req=#http_req{connection=Conn, buffer=Buffer},
  310. State) ->
  311. HandlerRes = handler_terminate(HandlerState, Req, State),
  312. BodyRes = ensure_body_processed(Req),
  313. RespRes = ensure_response(Req),
  314. case {HandlerRes, BodyRes, RespRes, Conn} of
  315. {ok, ok, ok, keepalive} ->
  316. ?MODULE:parse_request(State#state{
  317. buffer=Buffer, req_empty_lines=0});
  318. _Closed ->
  319. terminate(State)
  320. end.
  321. -spec ensure_body_processed(#http_req{}) -> ok | close.
  322. ensure_body_processed(#http_req{body_state=done}) ->
  323. ok;
  324. ensure_body_processed(Req=#http_req{body_state=waiting}) ->
  325. case cowboy_http_req:body(Req) of
  326. {error, badarg} -> ok; %% No body.
  327. {error, _Reason} -> close;
  328. _Any -> ok
  329. end.
  330. -spec ensure_response(#http_req{}) -> ok.
  331. %% The handler has already fully replied to the client.
  332. ensure_response(#http_req{resp_state=done}) ->
  333. ok;
  334. %% No response has been sent but everything apparently went fine.
  335. %% Reply with 204 No Content to indicate this.
  336. ensure_response(Req=#http_req{resp_state=waiting}) ->
  337. _ = cowboy_http_req:reply(204, [], [], Req),
  338. ok;
  339. %% Close the chunked reply.
  340. ensure_response(#http_req{method='HEAD', resp_state=chunks}) ->
  341. close;
  342. ensure_response(#http_req{socket=Socket, transport=Transport,
  343. resp_state=chunks}) ->
  344. Transport:send(Socket, <<"0\r\n\r\n">>),
  345. close.
  346. -spec error_terminate(http_status(), #state{}) -> ok.
  347. error_terminate(Code, State=#state{socket=Socket, transport=Transport}) ->
  348. _ = cowboy_http_req:reply(Code, [], [], #http_req{
  349. socket=Socket, transport=Transport,
  350. connection=close, resp_state=waiting}),
  351. terminate(State).
  352. -spec terminate(#state{}) -> ok.
  353. terminate(#state{socket=Socket, transport=Transport}) ->
  354. Transport:close(Socket),
  355. ok.
  356. %% Internal.
  357. -spec version_to_connection(http_version()) -> keepalive | close.
  358. version_to_connection({1, 1}) -> keepalive;
  359. version_to_connection(_Any) -> close.
  360. -spec default_port(atom()) -> 80 | 443.
  361. default_port(ssl) -> 443;
  362. default_port(_) -> 80.
  363. %% @todo While 32 should be enough for everybody, we should probably make
  364. %% this configurable or something.
  365. -spec format_header(atom()) -> atom(); (binary()) -> binary().
  366. format_header(Field) when is_atom(Field) ->
  367. Field;
  368. format_header(Field) when byte_size(Field) =< 20; byte_size(Field) > 32 ->
  369. Field;
  370. format_header(Field) ->
  371. format_header(Field, true, <<>>).
  372. -spec format_header(binary(), boolean(), binary()) -> binary().
  373. format_header(<<>>, _Any, Acc) ->
  374. Acc;
  375. %% Replicate a bug in OTP for compatibility reasons when there's a - right
  376. %% after another. Proper use should always be 'true' instead of 'not Bool'.
  377. format_header(<< $-, Rest/bits >>, Bool, Acc) ->
  378. format_header(Rest, not Bool, << Acc/binary, $- >>);
  379. format_header(<< C, Rest/bits >>, true, Acc) ->
  380. format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_upper(C)) >>);
  381. format_header(<< C, Rest/bits >>, false, Acc) ->
  382. format_header(Rest, false, << Acc/binary, (cowboy_bstr:char_to_lower(C)) >>).
  383. %% Tests.
  384. -ifdef(TEST).
  385. format_header_test_() ->
  386. %% {Header, Result}
  387. Tests = [
  388. {<<"Sec-Websocket-Version">>, <<"Sec-Websocket-Version">>},
  389. {<<"Sec-WebSocket-Version">>, <<"Sec-Websocket-Version">>},
  390. {<<"sec-websocket-version">>, <<"Sec-Websocket-Version">>},
  391. {<<"SEC-WEBSOCKET-VERSION">>, <<"Sec-Websocket-Version">>},
  392. %% These last tests ensures we're formatting headers exactly like OTP.
  393. %% Even though it's dumb, it's better for compatibility reasons.
  394. {<<"Sec-WebSocket--Version">>, <<"Sec-Websocket--version">>},
  395. {<<"Sec-WebSocket---Version">>, <<"Sec-Websocket---Version">>}
  396. ],
  397. [{H, fun() -> R = format_header(H) end} || {H, R} <- Tests].
  398. -endif.