cowboy_handler.erl 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. %% Copyright (c) 2011-2014, 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. %% Handler middleware.
  15. %%
  16. %% Execute the handler given by the <em>handler</em> and <em>handler_opts</em>
  17. %% environment values. The result of this execution is added to the
  18. %% environment under the <em>result</em> value.
  19. %%
  20. %% When using loop handlers, we are receiving data from the socket because we
  21. %% want to know when the socket gets closed. This is generally not an issue
  22. %% because these kinds of requests are generally not pipelined, and don't have
  23. %% a body. If they do have a body, this body is often read in the
  24. %% <em>init/3</em> callback and this is no problem. Otherwise, this data
  25. %% accumulates in a buffer until we reach a certain threshold of 5000 bytes
  26. %% by default. This can be configured through the <em>loop_max_buffer</em>
  27. %% environment value. The request will be terminated with an
  28. %% <em>{error, overflow}</em> reason if this threshold is reached.
  29. -module(cowboy_handler).
  30. -behaviour(cowboy_middleware).
  31. -export([execute/2]).
  32. -export([handler_loop/4]).
  33. -record(state, {
  34. env :: cowboy_middleware:env(),
  35. hibernate = false :: boolean(),
  36. loop_buffer_size = 0 :: non_neg_integer(),
  37. loop_max_buffer = 5000 :: non_neg_integer() | infinity,
  38. loop_timeout = infinity :: timeout(),
  39. loop_timeout_ref = undefined :: undefined | reference(),
  40. resp_sent = false :: boolean()
  41. }).
  42. -spec execute(Req, Env)
  43. -> {ok, Req, Env} | {error, 500, Req}
  44. | {suspend, ?MODULE, handler_loop, [any()]}
  45. when Req::cowboy_req:req(), Env::cowboy_middleware:env().
  46. execute(Req, Env) ->
  47. {_, Handler} = lists:keyfind(handler, 1, Env),
  48. {_, HandlerOpts} = lists:keyfind(handler_opts, 1, Env),
  49. MaxBuffer = case lists:keyfind(loop_max_buffer, 1, Env) of
  50. false -> 5000;
  51. {_, MaxBuffer0} -> MaxBuffer0
  52. end,
  53. handler_init(Req, #state{env=Env, loop_max_buffer=MaxBuffer},
  54. Handler, HandlerOpts).
  55. -spec handler_init(Req, #state{}, module(), any())
  56. -> {ok, Req, cowboy_middleware:env()}
  57. | {error, 500, Req} | {suspend, module(), atom(), [any()]}
  58. when Req::cowboy_req:req().
  59. handler_init(Req, State, Handler, HandlerOpts) ->
  60. Transport = cowboy_req:get(transport, Req),
  61. try Handler:init({Transport:name(), http}, Req, HandlerOpts) of
  62. {ok, Req2, HandlerState} ->
  63. handler_handle(Req2, State, Handler, HandlerState);
  64. {loop, Req2, HandlerState} ->
  65. handler_after_callback(Req2, State, Handler, HandlerState);
  66. {loop, Req2, HandlerState, hibernate} ->
  67. handler_after_callback(Req2, State#state{hibernate=true},
  68. Handler, HandlerState);
  69. {loop, Req2, HandlerState, Timeout} ->
  70. State2 = handler_loop_timeout(State#state{loop_timeout=Timeout}),
  71. handler_after_callback(Req2, State2, Handler, HandlerState);
  72. {loop, Req2, HandlerState, Timeout, hibernate} ->
  73. State2 = handler_loop_timeout(State#state{
  74. hibernate=true, loop_timeout=Timeout}),
  75. handler_after_callback(Req2, State2, Handler, HandlerState);
  76. {shutdown, Req2, HandlerState} ->
  77. terminate_request(Req2, State, Handler, HandlerState,
  78. {normal, shutdown});
  79. %% @todo {upgrade, transport, Module}
  80. {upgrade, protocol, Module} ->
  81. upgrade_protocol(Req, State, Handler, HandlerOpts, Module);
  82. {upgrade, protocol, Module, Req2, HandlerOpts2} ->
  83. upgrade_protocol(Req2, State, Handler, HandlerOpts2, Module)
  84. catch Class:Reason ->
  85. cowboy_req:maybe_reply(500, Req),
  86. erlang:Class([
  87. {reason, Reason},
  88. {mfa, {Handler, init, 3}},
  89. {stacktrace, erlang:get_stacktrace()},
  90. {req, cowboy_req:to_list(Req)},
  91. {opts, HandlerOpts}
  92. ])
  93. end.
  94. -spec upgrade_protocol(Req, #state{}, module(), any(), module())
  95. -> {ok, Req, Env}
  96. | {suspend, module(), atom(), any()}
  97. | {halt, Req}
  98. | {error, cowboy:http_status(), Req}
  99. when Req::cowboy_req:req(), Env::cowboy_middleware:env().
  100. upgrade_protocol(Req, #state{env=Env},
  101. Handler, HandlerOpts, Module) ->
  102. Module:upgrade(Req, Env, Handler, HandlerOpts).
  103. -spec handler_handle(Req, #state{}, module(), any())
  104. -> {ok, Req, cowboy_middleware:env()}
  105. | {error, 500, Req}
  106. when Req::cowboy_req:req().
  107. handler_handle(Req, State, Handler, HandlerState) ->
  108. try Handler:handle(Req, HandlerState) of
  109. {ok, Req2, HandlerState2} ->
  110. terminate_request(Req2, State, Handler, HandlerState2,
  111. {normal, shutdown})
  112. catch Class:Reason ->
  113. cowboy_req:maybe_reply(500, Req),
  114. handler_terminate(Req, Handler, HandlerState, Reason),
  115. erlang:Class([
  116. {reason, Reason},
  117. {mfa, {Handler, handle, 2}},
  118. {stacktrace, erlang:get_stacktrace()},
  119. {req, cowboy_req:to_list(Req)},
  120. {state, HandlerState}
  121. ])
  122. end.
  123. %% Update the state if the response was sent in the callback.
  124. -spec handler_after_callback(Req, #state{}, module(), any())
  125. -> {ok, Req, cowboy_middleware:env()}
  126. | {error, 500, Req} | {suspend, module(), atom(), [any()]}
  127. when Req::cowboy_req:req().
  128. handler_after_callback(Req, State=#state{resp_sent=false}, Handler,
  129. HandlerState) ->
  130. receive
  131. {cowboy_req, resp_sent} ->
  132. handler_before_loop(Req, State#state{resp_sent=true}, Handler,
  133. HandlerState)
  134. after 0 ->
  135. handler_before_loop(Req, State, Handler, HandlerState)
  136. end;
  137. handler_after_callback(Req, State, Handler, HandlerState) ->
  138. handler_before_loop(Req, State, Handler, HandlerState).
  139. -spec handler_before_loop(Req, #state{}, module(), any())
  140. -> {ok, Req, cowboy_middleware:env()}
  141. | {error, 500, Req} | {suspend, module(), atom(), [any()]}
  142. when Req::cowboy_req:req().
  143. handler_before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) ->
  144. [Socket, Transport] = cowboy_req:get([socket, transport], Req),
  145. Transport:setopts(Socket, [{active, once}]),
  146. {suspend, ?MODULE, handler_loop,
  147. [Req, State#state{hibernate=false}, Handler, HandlerState]};
  148. handler_before_loop(Req, State, Handler, HandlerState) ->
  149. [Socket, Transport] = cowboy_req:get([socket, transport], Req),
  150. Transport:setopts(Socket, [{active, once}]),
  151. handler_loop(Req, State, Handler, HandlerState).
  152. %% Almost the same code can be found in cowboy_websocket.
  153. -spec handler_loop_timeout(#state{}) -> #state{}.
  154. handler_loop_timeout(State=#state{loop_timeout=infinity}) ->
  155. State#state{loop_timeout_ref=undefined};
  156. handler_loop_timeout(State=#state{loop_timeout=Timeout,
  157. loop_timeout_ref=PrevRef}) ->
  158. _ = case PrevRef of
  159. undefined -> ignore;
  160. PrevRef -> erlang:cancel_timer(PrevRef)
  161. end,
  162. TRef = erlang:start_timer(Timeout, self(), ?MODULE),
  163. State#state{loop_timeout_ref=TRef}.
  164. -spec handler_loop(Req, #state{}, module(), any())
  165. -> {ok, Req, cowboy_middleware:env()}
  166. | {error, 500, Req} | {suspend, module(), atom(), [any()]}
  167. when Req::cowboy_req:req().
  168. handler_loop(Req, State=#state{loop_buffer_size=NbBytes,
  169. loop_max_buffer=Threshold, loop_timeout_ref=TRef},
  170. Handler, HandlerState) ->
  171. [Socket, Transport] = cowboy_req:get([socket, transport], Req),
  172. {OK, Closed, Error} = Transport:messages(),
  173. receive
  174. {OK, Socket, Data} ->
  175. NbBytes2 = NbBytes + byte_size(Data),
  176. if NbBytes2 > Threshold ->
  177. _ = handler_terminate(Req, Handler, HandlerState,
  178. {error, overflow}),
  179. cowboy_req:maybe_reply(500, Req),
  180. exit(normal);
  181. true ->
  182. Req2 = cowboy_req:append_buffer(Data, Req),
  183. State2 = handler_loop_timeout(State#state{
  184. loop_buffer_size=NbBytes2}),
  185. handler_before_loop(Req2, State2, Handler, HandlerState)
  186. end;
  187. {Closed, Socket} ->
  188. terminate_request(Req, State, Handler, HandlerState,
  189. {error, closed});
  190. {Error, Socket, Reason} ->
  191. terminate_request(Req, State, Handler, HandlerState,
  192. {error, Reason});
  193. {timeout, TRef, ?MODULE} ->
  194. handler_after_loop(Req, State, Handler, HandlerState,
  195. {normal, timeout});
  196. {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
  197. handler_loop(Req, State, Handler, HandlerState);
  198. Message ->
  199. %% We set the socket back to {active, false} mode in case
  200. %% the handler is going to call recv. We also flush any
  201. %% data received after that and put it into the buffer.
  202. %% We do not check the size here, if data keeps coming
  203. %% we'll error out on the next packet received.
  204. Transport:setopts(Socket, [{active, false}]),
  205. Req2 = receive {OK, Socket, Data} ->
  206. cowboy_req:append_buffer(Data, Req)
  207. after 0 ->
  208. Req
  209. end,
  210. handler_call(Req2, State, Handler, HandlerState, Message)
  211. end.
  212. -spec handler_call(Req, #state{}, module(), any(), any())
  213. -> {ok, Req, cowboy_middleware:env()}
  214. | {error, 500, Req} | {suspend, module(), atom(), [any()]}
  215. when Req::cowboy_req:req().
  216. handler_call(Req, State=#state{resp_sent=RespSent},
  217. Handler, HandlerState, Message) ->
  218. try Handler:info(Message, Req, HandlerState) of
  219. {ok, Req2, HandlerState2} ->
  220. handler_after_loop(Req2, State, Handler, HandlerState2,
  221. {normal, shutdown});
  222. {loop, Req2, HandlerState2} ->
  223. handler_after_callback(Req2, State, Handler, HandlerState2);
  224. {loop, Req2, HandlerState2, hibernate} ->
  225. handler_after_callback(Req2, State#state{hibernate=true},
  226. Handler, HandlerState2)
  227. catch Class:Reason ->
  228. if RespSent ->
  229. ok;
  230. true ->
  231. cowboy_req:maybe_reply(500, Req)
  232. end,
  233. handler_terminate(Req, Handler, HandlerState, Reason),
  234. erlang:Class([
  235. {reason, Reason},
  236. {mfa, {Handler, info, 3}},
  237. {stacktrace, erlang:get_stacktrace()},
  238. {req, cowboy_req:to_list(Req)},
  239. {state, HandlerState}
  240. ])
  241. end.
  242. %% It is sometimes important to make a socket passive as it was initially
  243. %% and as it is expected to be by cowboy_protocol, right after we're done
  244. %% with loop handling. The browser may freely pipeline a bunch of requests
  245. %% if previous one was, say, a JSONP long-polling request.
  246. -spec handler_after_loop(Req, #state{}, module(), any(),
  247. {normal, timeout | shutdown} | {error, atom()}) ->
  248. {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
  249. handler_after_loop(Req, State, Handler, HandlerState, Reason) ->
  250. [Socket, Transport] = cowboy_req:get([socket, transport], Req),
  251. Transport:setopts(Socket, [{active, false}]),
  252. {OK, _Closed, _Error} = Transport:messages(),
  253. Req2 = receive
  254. {OK, Socket, Data} ->
  255. cowboy_req:append_buffer(Data, Req)
  256. after 0 ->
  257. Req
  258. end,
  259. terminate_request(Req2, State, Handler, HandlerState, Reason).
  260. -spec terminate_request(Req, #state{}, module(), any(),
  261. {normal, timeout | shutdown} | {error, atom()}) ->
  262. {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
  263. terminate_request(Req, #state{env=Env, loop_timeout_ref=TRef},
  264. Handler, HandlerState, Reason) ->
  265. HandlerRes = handler_terminate(Req, Handler, HandlerState, Reason),
  266. _ = case TRef of
  267. undefined -> ignore;
  268. TRef -> erlang:cancel_timer(TRef)
  269. end,
  270. flush_timeouts(),
  271. {ok, Req, [{result, HandlerRes}|Env]}.
  272. -spec handler_terminate(cowboy_req:req(), module(), any(),
  273. {normal, timeout | shutdown} | {error, atom()}) -> ok.
  274. handler_terminate(Req, Handler, HandlerState, Reason) ->
  275. try
  276. Handler:terminate(Reason, cowboy_req:lock(Req), HandlerState)
  277. catch Class:Reason2 ->
  278. erlang:Class([
  279. {reason, Reason2},
  280. {mfa, {Handler, terminate, 3}},
  281. {stacktrace, erlang:get_stacktrace()},
  282. {req, cowboy_req:to_list(Req)},
  283. {state, HandlerState},
  284. {terminate_reason, Reason}
  285. ])
  286. end.
  287. -spec flush_timeouts() -> ok.
  288. flush_timeouts() ->
  289. receive
  290. {timeout, TRef, ?MODULE} when is_reference(TRef) ->
  291. flush_timeouts()
  292. after 0 ->
  293. ok
  294. end.