From bbee34fe1638b742796b00b39c0859395a752167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Sat, 24 Aug 2013 20:36:23 +0200 Subject: Crash on failure, don't report errors When something went wrong in a handler we used to report errors and then terminate the process normally. This doesn't work so well with links which won't detect failure. Now we still catch the error, but throw another one with more details on why it happened, including the Req object information and the stacktrace. Ranch will then print an error message with all this information. Because we crash directly, this also means that we will not hog resources unnecessarily for too long when something bad happens. --- src/cowboy_handler.erl | 86 +++++++++++----------- src/cowboy_rest.erl | 184 +++++++++++++---------------------------------- src/cowboy_websocket.erl | 82 ++++++++++----------- 3 files changed, 126 insertions(+), 226 deletions(-) (limited to 'src') diff --git a/src/cowboy_handler.erl b/src/cowboy_handler.erl index 2074b4e..fcbfe55 100644 --- a/src/cowboy_handler.erl +++ b/src/cowboy_handler.erl @@ -90,15 +90,14 @@ handler_init(Req, State, Handler, HandlerOpts) -> {upgrade, protocol, Module, Req2, HandlerOpts2} -> upgrade_protocol(Req2, State, Handler, HandlerOpts2, Module) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n" - "** Options were ~p~n" - "** Request was ~p~n" - "** Stacktrace: ~p~n~n", - [Handler, init, 3, Class, Reason, HandlerOpts, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + cowboy_req:maybe_reply(500, Req), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, init, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {opts, HandlerOpts} + ]) end. -spec upgrade_protocol(Req, #state{}, module(), any(), module()) @@ -121,16 +120,15 @@ handler_handle(Req, State, Handler, HandlerState) -> terminate_request(Req2, State, Handler, HandlerState2, {normal, shutdown}) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n" - "** Handler state was ~p~n" - "** Request was ~p~n" - "** Stacktrace: ~p~n~n", - [Handler, handle, 2, Class, Reason, HandlerState, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), + cowboy_req:maybe_reply(500, Req), handler_terminate(Req, Handler, HandlerState, Reason), - error_terminate(Req, State) + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, handle, 2}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState} + ]) end. %% Update the state if the response was sent in the callback. @@ -195,7 +193,8 @@ handler_loop(Req, State=#state{loop_buffer_size=NbBytes, if NbBytes2 > Threshold -> _ = handler_terminate(Req, Handler, HandlerState, {error, overflow}), - error_terminate(Req, State); + cowboy_req:maybe_reply(500, Req), + exit(normal); true -> Req2 = cowboy_req:append_buffer(Data, Req), State2 = handler_loop_timeout(State#state{ @@ -232,7 +231,8 @@ handler_loop(Req, State=#state{loop_buffer_size=NbBytes, -> {ok, Req, cowboy_middleware:env()} | {error, 500, Req} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). -handler_call(Req, State, Handler, HandlerState, Message) -> +handler_call(Req, State=#state{resp_sent=RespSent}, + Handler, HandlerState, Message) -> try Handler:info(Message, Req, HandlerState) of {ok, Req2, HandlerState2} -> handler_after_loop(Req2, State, Handler, HandlerState2, @@ -243,16 +243,19 @@ handler_call(Req, State, Handler, HandlerState, Message) -> handler_after_callback(Req2, State#state{hibernate=true}, Handler, HandlerState2) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n" - "** Handler state was ~p~n" - "** Request was ~p~n" - "** Stacktrace: ~p~n~n", - [Handler, info, 3, Class, Reason, HandlerState, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), + if RespSent -> + ok; + true -> + cowboy_req:maybe_reply(500, Req) + end, handler_terminate(Req, Handler, HandlerState, Reason), - error_terminate(Req, State) + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, info, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState} + ]) end. %% It is sometimes important to make a socket passive as it was initially @@ -287,21 +290,12 @@ handler_terminate(Req, Handler, HandlerState, Reason) -> try Handler:terminate(Reason, cowboy_req:lock(Req), HandlerState) catch Class:Reason2 -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n" - "** Handler state was ~p~n" - "** Request was ~p~n" - "** Stacktrace: ~p~n~n", - [Handler, terminate, 3, Class, Reason2, HandlerState, - cowboy_req:to_list(Req), erlang:get_stacktrace()]) + erlang:Class([ + {reason, Reason2}, + {mfa, {Handler, terminate, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState}, + {terminate_reason, Reason} + ]) end. - -%% Only send an error reply if there is no resp_sent message. --spec error_terminate(Req, #state{}) - -> {error, 500, Req} | {halt, Req} when Req::cowboy_req:req(). -error_terminate(Req, #state{resp_sent=true}) -> - %% Close the connection, but do not attempt sending a reply. - {halt, cowboy_req:set([{connection, close}, {resp_state, done}], Req)}; -error_terminate(Req, _) -> - {error, 500, Req}. diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index 34bfce1..862ebbf 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -66,30 +66,26 @@ -> {ok, Req, Env} | {error, 500, Req} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). upgrade(Req, Env, Handler, HandlerOpts) -> - try - Method = cowboy_req:get(method, Req), - case erlang:function_exported(Handler, rest_init, 2) of - true -> - try Handler:rest_init(Req, HandlerOpts) of - {ok, Req2, HandlerState} -> - service_available(Req2, #state{env=Env, method=Method, - handler=Handler, handler_state=HandlerState}) - catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Options were ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, rest_init, 2, Class, Reason, HandlerOpts, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - {error, 500, Req} - end; - false -> - service_available(Req, #state{env=Env, method=Method, - handler=Handler}) - end - catch - throw:{?MODULE, error} -> - {error, 500, Req} + Method = cowboy_req:get(method, Req), + case erlang:function_exported(Handler, rest_init, 2) of + true -> + try Handler:rest_init(Req, HandlerOpts) of + {ok, Req2, HandlerState} -> + service_available(Req2, #state{env=Env, method=Method, + handler=Handler, handler_state=HandlerState}) + catch Class:Reason -> + cowboy_req:maybe_reply(500, Req), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, rest_init, 2}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {opts, HandlerOpts} + ]) + end; + false -> + service_available(Req, #state{env=Env, method=Method, + handler=Handler}) end. service_available(Req, State) -> @@ -516,14 +512,7 @@ variances(Req, State=#state{content_types_p=CTP, resource_exists(Req3, State2) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, variances, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, variances) end. variances(Req, State, Variances) -> @@ -559,14 +548,7 @@ if_match(Req, State, EtagsList) -> false -> precondition_failed(Req2, State2) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, generate_etag, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, generate_etag) end. if_match_must_not_exist(Req, State) -> @@ -594,14 +576,7 @@ if_unmodified_since(Req, State, IfUnmodifiedSince) -> false -> if_none_match_exists(Req2, State2) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, last_modified, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, last_modified) end. if_none_match_exists(Req, State) -> @@ -627,14 +602,7 @@ if_none_match(Req, State, EtagsList) -> end end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, generate_etag, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, generate_etag) end. precondition_is_head_get(Req, State=#state{method=Method}) @@ -669,14 +637,7 @@ if_modified_since(Req, State, IfModifiedSince) -> false -> not_modified(Req2, State2) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, last_modified, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, last_modified) end. not_modified(Req, State) -> @@ -687,24 +648,10 @@ not_modified(Req, State) -> {Req4, State3} -> respond(Req4, State3, 304) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, expires, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req2, State) + error_terminate(Req, State, Class, Reason, expires) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, generate_etag, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req2, State) + error_terminate(Req, State, Class, Reason, generate_etag) end. precondition_failed(Req, State) -> @@ -829,17 +776,8 @@ choose_content_type(Req, State, {Type, SubType, Param}, choose_content_type(Req, State, ContentType, [_Any|Tail]) -> choose_content_type(Req, State, ContentType, Tail). -process_content_type(Req, State=#state{method=Method, - handler=Handler, handler_state=HandlerState, - exists=Exists}, Fun) -> - case call(Req, State, Fun) of - no_call -> - error_logger:error_msg( - "** Cowboy handler ~p terminating; " - "function ~p/~p was not exported~n" - "** Request was ~p~n** State was ~p~n~n", - [Handler, Fun, 2, cowboy_req:to_list(Req), HandlerState]), - {error, 500, Req}; +process_content_type(Req, State=#state{method=Method, exists=Exists}, Fun) -> + try case call(Req, State, Fun) of {halt, Req2, HandlerState2} -> terminate(Req2, State#state{handler_state=HandlerState2}); {true, Req2, HandlerState2} when Exists -> @@ -859,6 +797,8 @@ process_content_type(Req, State=#state{method=Method, Exists -> respond(Req3, State2, 303); true -> respond(Req3, State2, 201) end + end catch Class:Reason = {case_clause, no_call} -> + error_terminate(Req, State, Class, Reason, Fun) end. %% If the resource is new and has been created at another location @@ -881,14 +821,7 @@ set_resp_body_etag(Req, State) -> {Req2, State2} -> set_resp_body_last_modified(Req2, State2) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, generate_etag, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, generate_etag) end. %% Set the Last-Modified header if any for the response provided. @@ -905,14 +838,7 @@ set_resp_body_last_modified(Req, State) -> set_resp_body_expires(Req3, State2) end catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, last_modified, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, last_modified) end. %% Set the Expires header if any for the response provided. @@ -921,29 +847,14 @@ set_resp_body_expires(Req, State) -> {Req2, State2} -> set_resp_body(Req2, State2) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [State#state.handler, expires, 2, - Class, Reason, State#state.handler_state, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, expires) end. %% Set the response headers and call the callback found using %% content_types_provided/2 to obtain the request body and add %% it to the response. -set_resp_body(Req, State=#state{handler=Handler, handler_state=HandlerState, - content_type_a={_Type, Callback}}) -> - case call(Req, State, Callback) of - no_call -> - error_logger:error_msg( - "** Cowboy handler ~p terminating; " - "function ~p/~p was not exported~n" - "** Request was ~p~n** State was ~p~n~n", - [Handler, Callback, 2, cowboy_req:to_list(Req), HandlerState]), - {error, 500, Req}; +set_resp_body(Req, State=#state{content_type_a={_, Callback}}) -> + try case call(Req, State, Callback) of {halt, Req2, HandlerState2} -> terminate(Req2, State#state{handler_state=HandlerState2}); {Body, Req2, HandlerState2} -> @@ -959,6 +870,8 @@ set_resp_body(Req, State=#state{handler=Handler, handler_state=HandlerState, cowboy_req:set_resp_body(Body, Req2) end, multiple_choices(Req3, State2) + end catch Class:Reason = {case_clause, no_call} -> + error_terminate(Req, State, Class, Reason, Callback) end. multiple_choices(Req, State) -> @@ -1057,13 +970,7 @@ call(Req, State=#state{handler=Handler, handler_state=HandlerState}, try Handler:Callback(Req, HandlerState) catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, Callback, 2, Class, Reason, HandlerState, - cowboy_req:to_list(Req), erlang:get_stacktrace()]), - error_terminate(Req, State) + error_terminate(Req, State, Class, Reason, Callback) end; false -> no_call @@ -1089,10 +996,17 @@ terminate(Req, State=#state{env=Env}) -> rest_terminate(Req, State), {ok, Req, [{result, ok}|Env]}. --spec error_terminate(cowboy_req:req(), #state{}) -> no_return(). -error_terminate(Req, State) -> +error_terminate(Req, State=#state{handler=Handler, handler_state=HandlerState}, + Class, Reason, Callback) -> rest_terminate(Req, State), - erlang:raise(throw, {?MODULE, error}, erlang:get_stacktrace()). + cowboy_req:maybe_reply(500, Req), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, Callback, 2}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState} + ]). rest_terminate(Req, #state{handler=Handler, handler_state=HandlerState}) -> case erlang:function_exported(Handler, rest_terminate, 2) of diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl index 073d7c6..88afbd0 100644 --- a/src/cowboy_websocket.erl +++ b/src/cowboy_websocket.erl @@ -48,7 +48,6 @@ socket = undefined :: inet:socket(), transport = undefined :: module(), handler :: module(), - handler_opts :: any(), key = undefined :: undefined | binary(), timeout = infinity :: timeout(), timeout_ref = undefined :: undefined | reference(), @@ -75,10 +74,13 @@ upgrade(Req, Env, Handler, HandlerOpts) -> ranch:remove_connection(Ref), [Socket, Transport] = cowboy_req:get([socket, transport], Req), State = #state{env=Env, socket=Socket, transport=Transport, - handler=Handler, handler_opts=HandlerOpts}, - case catch websocket_upgrade(State, Req) of - {ok, State2, Req2} -> handler_init(State2, Req2); - {'EXIT', _Reason} -> upgrade_error(Req, Env) + handler=Handler}, + try websocket_upgrade(State, Req) of + {ok, State2, Req2} -> + handler_init(State2, Req2, HandlerOpts) + catch _:_ -> + cowboy_req:maybe_reply(400, Req), + exit(normal) end. -spec websocket_upgrade(#state{}, Req) @@ -129,12 +131,12 @@ websocket_extensions(State, Req) -> {ok, State, Req} end. --spec handler_init(#state{}, Req) +-spec handler_init(#state{}, Req, any()) -> {ok, Req, cowboy_middleware:env()} | {error, 400, Req} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). handler_init(State=#state{env=Env, transport=Transport, - handler=Handler, handler_opts=HandlerOpts}, Req) -> + handler=Handler}, Req, HandlerOpts) -> try Handler:websocket_init(Transport:name(), Req, HandlerOpts) of {ok, Req2, HandlerState} -> websocket_handshake(State, Req2, HandlerState); @@ -151,24 +153,14 @@ handler_init(State=#state{env=Env, transport=Transport, cowboy_req:ensure_response(Req2, 400), {ok, Req2, [{result, closed}|Env]} catch Class:Reason -> - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Options were ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, websocket_init, 3, Class, Reason, HandlerOpts, - cowboy_req:to_list(Req),erlang:get_stacktrace()]), - upgrade_error(Req, Env) - end. - -%% Only send an error reply if there is no resp_sent message. --spec upgrade_error(Req, Env) -> {ok, Req, Env} | {error, 400, Req} - when Req::cowboy_req:req(), Env::cowboy_middleware:env(). -upgrade_error(Req, Env) -> - receive - {cowboy_req, resp_sent} -> - {ok, Req, [{result, closed}|Env]} - after 0 -> - {error, 400, Req} + cowboy_req:maybe_reply(400, Req), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, websocket_init, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {opts, HandlerOpts} + ]) end. -spec websocket_handshake(#state{}, Req, any()) @@ -601,8 +593,8 @@ websocket_dispatch(State, Req, HandlerState, RemainingData, 10, Payload) -> -> {ok, Req, cowboy_middleware:env()} | {suspend, module(), atom(), [any()]} when Req::cowboy_req:req(). -handler_call(State=#state{handler=Handler, handler_opts=HandlerOpts}, Req, - HandlerState, RemainingData, Callback, Message, NextState) -> +handler_call(State=#state{handler=Handler}, Req, HandlerState, + RemainingData, Callback, Message, NextState) -> try Handler:Callback(Message, Req, HandlerState) of {ok, Req2, HandlerState2} -> NextState(State, Req2, HandlerState2, RemainingData); @@ -658,15 +650,15 @@ handler_call(State=#state{handler=Handler, handler_opts=HandlerOpts}, Req, {shutdown, Req2, HandlerState2} -> websocket_close(State, Req2, HandlerState2, {normal, shutdown}) catch Class:Reason -> - PLReq = cowboy_req:to_list(Req), - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Message was ~p~n" - "** Options were ~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, Callback, 3, Class, Reason, Message, HandlerOpts, - HandlerState, PLReq, erlang:get_stacktrace()]), - websocket_close(State, Req, HandlerState, {error, handler}) + _ = websocket_close(State, Req, HandlerState, {error, handler}), + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, Callback, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {msg, Message}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState} + ]) end. websocket_opcode(text) -> 1; @@ -765,19 +757,19 @@ websocket_close(State=#state{socket=Socket, transport=Transport}, -spec handler_terminate(#state{}, Req, any(), atom() | {atom(), atom()}) -> {ok, Req, cowboy_middleware:env()} when Req::cowboy_req:req(). -handler_terminate(#state{env=Env, handler=Handler, handler_opts=HandlerOpts}, +handler_terminate(#state{env=Env, handler=Handler}, Req, HandlerState, TerminateReason) -> try Handler:websocket_terminate(TerminateReason, Req, HandlerState) catch Class:Reason -> - PLReq = cowboy_req:to_list(Req), - error_logger:error_msg( - "** Cowboy handler ~p terminating in ~p/~p~n" - " for the reason ~p:~p~n** Initial reason was ~p~n" - "** Options were ~p~n** Handler state was ~p~n" - "** Request was ~p~n** Stacktrace: ~p~n~n", - [Handler, websocket_terminate, 3, Class, Reason, TerminateReason, - HandlerOpts, HandlerState, PLReq, erlang:get_stacktrace()]) + erlang:Class([ + {reason, Reason}, + {mfa, {Handler, websocket_terminate, 3}}, + {stacktrace, erlang:get_stacktrace()}, + {req, cowboy_req:to_list(Req)}, + {state, HandlerState}, + {terminate_reason, TerminateReason} + ]) end, {ok, Req, [{result, closed}|Env]}. -- cgit v1.2.3