cowboy_handler.erl 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. %% Copyright (c) 2011-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. %% @doc 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. %% @see cowboy_http_handler
  21. -module(cowboy_handler).
  22. -behaviour(cowboy_middleware).
  23. -export([execute/2]).
  24. -export([handler_loop/4]).
  25. -record(state, {
  26. env :: cowboy_middleware:env(),
  27. hibernate = false :: boolean(),
  28. loop_timeout = infinity :: timeout(),
  29. loop_timeout_ref :: undefined | reference()
  30. }).
  31. %% @private
  32. -spec execute(Req, Env)
  33. -> {ok, Req, Env} | {error, 500, Req}
  34. | {suspend, ?MODULE, handler_loop, [any()]}
  35. when Req::cowboy_req:req(), Env::cowboy_middleware:env().
  36. execute(Req, Env) ->
  37. {_, Handler} = lists:keyfind(handler, 1, Env),
  38. {_, HandlerOpts} = lists:keyfind(handler_opts, 1, Env),
  39. handler_init(Req, #state{env=Env}, Handler, HandlerOpts).
  40. -spec handler_init(Req, #state{}, module(), any())
  41. -> {ok, Req, cowboy_middleware:env()}
  42. | {error, 500, Req} | {suspend, module(), function(), [any()]}
  43. when Req::cowboy_req:req().
  44. handler_init(Req, State, Handler, HandlerOpts) ->
  45. Transport = cowboy_req:get(transport, Req),
  46. try Handler:init({Transport:name(), http}, Req, HandlerOpts) of
  47. {ok, Req2, HandlerState} ->
  48. handler_handle(Req2, State, Handler, HandlerState);
  49. {loop, Req2, HandlerState} ->
  50. handler_before_loop(Req2, State#state{hibernate=false},
  51. Handler, HandlerState);
  52. {loop, Req2, HandlerState, hibernate} ->
  53. handler_before_loop(Req2, State#state{hibernate=true},
  54. Handler, HandlerState);
  55. {loop, Req2, HandlerState, Timeout} ->
  56. handler_before_loop(Req2, State#state{loop_timeout=Timeout},
  57. Handler, HandlerState);
  58. {loop, Req2, HandlerState, Timeout, hibernate} ->
  59. handler_before_loop(Req2, State#state{
  60. hibernate=true, loop_timeout=Timeout}, Handler, HandlerState);
  61. {shutdown, Req2, HandlerState} ->
  62. terminate_request(Req2, State, Handler, HandlerState);
  63. %% @todo {upgrade, transport, Module}
  64. {upgrade, protocol, Module} ->
  65. upgrade_protocol(Req, State, Handler, HandlerOpts, Module);
  66. {upgrade, protocol, Module, Req2, HandlerOpts2} ->
  67. upgrade_protocol(Req2, State, Handler, HandlerOpts2, Module)
  68. catch Class:Reason ->
  69. error_logger:error_msg(
  70. "** Cowboy handler ~p terminating in ~p/~p~n"
  71. " for the reason ~p:~p~n"
  72. "** Options were ~p~n"
  73. "** Request was ~p~n"
  74. "** Stacktrace: ~p~n~n",
  75. [Handler, init, 3, Class, Reason, HandlerOpts,
  76. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  77. {error, 500, Req}
  78. end.
  79. -spec upgrade_protocol(Req, #state{}, module(), any(), module())
  80. -> {ok, Req, Env}
  81. | {suspend, module(), atom(), any()}
  82. | {halt, Req}
  83. | {error, cowboy_http:status(), Req}
  84. when Req::cowboy_req:req(), Env::cowboy_middleware:env().
  85. upgrade_protocol(Req, #state{env=Env},
  86. Handler, HandlerOpts, Module) ->
  87. Module:upgrade(Req, Env, Handler, HandlerOpts).
  88. -spec handler_handle(Req, #state{}, module(), any())
  89. -> {ok, Req, cowboy_middleware:env()}
  90. | {error, 500, Req}
  91. when Req::cowboy_req:req().
  92. handler_handle(Req, State, Handler, HandlerState) ->
  93. try Handler:handle(Req, HandlerState) of
  94. {ok, Req2, HandlerState2} ->
  95. terminate_request(Req2, State, Handler, HandlerState2)
  96. catch Class:Reason ->
  97. error_logger:error_msg(
  98. "** Cowboy handler ~p terminating in ~p/~p~n"
  99. " for the reason ~p:~p~n"
  100. "** Handler state was ~p~n"
  101. "** Request was ~p~n"
  102. "** Stacktrace: ~p~n~n",
  103. [Handler, handle, 2, Class, Reason, HandlerState,
  104. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  105. handler_terminate(Req, Handler, HandlerState),
  106. {error, 500, Req}
  107. end.
  108. %% We don't listen for Transport closes because that would force us
  109. %% to receive data and buffer it indefinitely.
  110. -spec handler_before_loop(Req, #state{}, module(), any())
  111. -> {ok, Req, cowboy_middleware:env()}
  112. | {error, 500, Req} | {suspend, module(), function(), [any()]}
  113. when Req::cowboy_req:req().
  114. handler_before_loop(Req, State=#state{hibernate=true}, Handler, HandlerState) ->
  115. State2 = handler_loop_timeout(State),
  116. {suspend, ?MODULE, handler_loop,
  117. [Req, State2#state{hibernate=false}, Handler, HandlerState]};
  118. handler_before_loop(Req, State, Handler, HandlerState) ->
  119. State2 = handler_loop_timeout(State),
  120. handler_loop(Req, State2, Handler, HandlerState).
  121. %% Almost the same code can be found in cowboy_websocket.
  122. -spec handler_loop_timeout(#state{}) -> #state{}.
  123. handler_loop_timeout(State=#state{loop_timeout=infinity}) ->
  124. State#state{loop_timeout_ref=undefined};
  125. handler_loop_timeout(State=#state{loop_timeout=Timeout,
  126. loop_timeout_ref=PrevRef}) ->
  127. _ = case PrevRef of undefined -> ignore; PrevRef ->
  128. erlang:cancel_timer(PrevRef) end,
  129. TRef = erlang:start_timer(Timeout, self(), ?MODULE),
  130. State#state{loop_timeout_ref=TRef}.
  131. %% @private
  132. -spec handler_loop(Req, #state{}, module(), any())
  133. -> {ok, Req, cowboy_middleware:env()}
  134. | {error, 500, Req} | {suspend, module(), function(), [any()]}
  135. when Req::cowboy_req:req().
  136. handler_loop(Req, State=#state{loop_timeout_ref=TRef}, Handler, HandlerState) ->
  137. receive
  138. {timeout, TRef, ?MODULE} ->
  139. terminate_request(Req, State, Handler, HandlerState);
  140. {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
  141. handler_loop(Req, State, Handler, HandlerState);
  142. Message ->
  143. handler_call(Req, State, Handler, HandlerState, Message)
  144. end.
  145. -spec handler_call(Req, #state{}, module(), any(), any())
  146. -> {ok, Req, cowboy_middleware:env()}
  147. | {error, 500, Req} | {suspend, module(), function(), [any()]}
  148. when Req::cowboy_req:req().
  149. handler_call(Req, State, Handler, HandlerState, Message) ->
  150. try Handler:info(Message, Req, HandlerState) of
  151. {ok, Req2, HandlerState2} ->
  152. terminate_request(Req2, State, Handler, HandlerState2);
  153. {loop, Req2, HandlerState2} ->
  154. handler_before_loop(Req2, State, Handler, HandlerState2);
  155. {loop, Req2, HandlerState2, hibernate} ->
  156. handler_before_loop(Req2, State#state{hibernate=true},
  157. Handler, HandlerState2)
  158. catch Class:Reason ->
  159. error_logger:error_msg(
  160. "** Cowboy handler ~p terminating in ~p/~p~n"
  161. " for the reason ~p:~p~n"
  162. "** Handler state was ~p~n"
  163. "** Request was ~p~n"
  164. "** Stacktrace: ~p~n~n",
  165. [Handler, info, 3, Class, Reason, HandlerState,
  166. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  167. handler_terminate(Req, Handler, HandlerState),
  168. {error, 500, Req}
  169. end.
  170. -spec terminate_request(Req, #state{}, module(), any()) ->
  171. {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req().
  172. terminate_request(Req, #state{env=Env}, Handler, HandlerState) ->
  173. HandlerRes = handler_terminate(Req, Handler, HandlerState),
  174. {ok, Req, [{result, HandlerRes}|Env]}.
  175. -spec handler_terminate(cowboy_req:req(), module(), any()) -> ok.
  176. handler_terminate(Req, Handler, HandlerState) ->
  177. try
  178. Handler:terminate(cowboy_req:lock(Req), HandlerState)
  179. catch Class:Reason ->
  180. error_logger:error_msg(
  181. "** Cowboy handler ~p terminating in ~p/~p~n"
  182. " for the reason ~p:~p~n"
  183. "** Handler state was ~p~n"
  184. "** Request was ~p~n"
  185. "** Stacktrace: ~p~n~n",
  186. [Handler, terminate, 2, Class, Reason, HandlerState,
  187. cowboy_req:to_list(Req), erlang:get_stacktrace()])
  188. end.