diff options
Diffstat (limited to 'src/cowboy_websocket.erl')
-rw-r--r-- | src/cowboy_websocket.erl | 70 |
1 files changed, 33 insertions, 37 deletions
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl index b8ec2e1..bc9bd31 100644 --- a/src/cowboy_websocket.erl +++ b/src/cowboy_websocket.erl @@ -32,6 +32,12 @@ | {module(), Req, any(), timeout()} | {module(), Req, any(), timeout(), hibernate} when Req::cowboy_req:req(). + +-callback websocket_init(Req, State) + -> {ok, Req, State} + when Req::cowboy_req:req(), State::any(). +-optional_callbacks([websocket_init/2]). + -callback websocket_handle({text | binary | ping | pong, binary()}, Req, State) -> {ok, Req, State} | {ok, Req, State, hibernate} @@ -70,17 +76,18 @@ -spec upgrade(Req, Env, module(), any(), timeout(), run | hibernate) -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). -upgrade(Req, Env, Handler, HandlerState, Timeout, Hibernate) -> - State = #state{handler=Handler, timeout=Timeout, - hibernate=Hibernate =:= hibernate}, - %% @todo We need to fail if HTTP/2. - try websocket_upgrade(State, Req) of - {ok, State2, Req2} -> - websocket_handshake(State2, Req2, HandlerState, Env) +%% @todo Immediately crash if a response has already been sent. +%% @todo Error out if HTTP/2. +upgrade(Req0, Env, Handler, HandlerState, Timeout, Hibernate) -> + try websocket_upgrade(#state{handler=Handler, timeout=Timeout, + hibernate=Hibernate =:= hibernate}, Req0) of + {ok, State, Req} -> + websocket_handshake(State, Req, HandlerState, Env) catch _:_ -> + %% @todo Probably log something here? %% @todo Test that we can have 2 /ws 400 status code in a row on the same connection. - cowboy_req:reply(400, Req), - {ok, Req, Env} + %% @todo Does this even work? + {ok, cowboy_req:reply(400, Req0), Env} end. -spec websocket_upgrade(#state{}, Req) @@ -91,7 +98,7 @@ websocket_upgrade(State, Req) -> %% @todo Should probably send a 426 if the Upgrade header is missing. [<<"websocket">>] = cowboy_req:parse_header(<<"upgrade">>, Req), Version = cowboy_req:header(<<"sec-websocket-version">>, Req), - IntVersion = list_to_integer(binary_to_list(Version)), + IntVersion = binary_to_integer(Version), true = (IntVersion =:= 7) orelse (IntVersion =:= 8) orelse (IntVersion =:= 13), Key = cowboy_req:header(<<"sec-websocket-key">>, Req), @@ -100,45 +107,44 @@ websocket_upgrade(State, Req) -> -spec websocket_extensions(#state{}, Req) -> {ok, #state{}, Req} when Req::cowboy_req:req(). -websocket_extensions(State, Req) -> +websocket_extensions(State, Req=#{ref := Ref}) -> %% @todo We want different options for this. For example %% * compress everything auto %% * compress only text auto %% * compress only binary auto %% * compress nothing auto (but still enabled it) %% * disable compression - Compress = maps:get(websocket_compress, Req, false), - Req2 = Req#{websocket_compress => false}, - case {Compress, cowboy_req:parse_header(<<"sec-websocket-extensions">>, Req2)} of + Compress = maps:get(websocket_compress, ranch:get_protocol_options(Ref), false), + case {Compress, cowboy_req:parse_header(<<"sec-websocket-extensions">>, Req)} of {true, Extensions} when Extensions =/= undefined -> - websocket_extensions(State, Req2, Extensions, []); + websocket_extensions(State, Req, Extensions, []); _ -> - {ok, State, Req2} + {ok, State, Req} end. websocket_extensions(State, Req, [], []) -> {ok, State, Req}; websocket_extensions(State, Req, [], [<<", ">>|RespHeader]) -> {ok, State, cowboy_req:set_resp_header(<<"sec-websocket-extensions">>, lists:reverse(RespHeader), Req)}; -websocket_extensions(State=#state{extensions=Extensions}, Req, [{<<"permessage-deflate">>, Params}|Tail], RespHeader) -> +websocket_extensions(State=#state{extensions=Extensions}, Req=#{pid := Pid}, + [{<<"permessage-deflate">>, Params}|Tail], RespHeader) -> %% @todo Make deflate options configurable. Opts = #{level => best_compression, mem_level => 8, strategy => default}, - case cow_ws:negotiate_permessage_deflate(Params, Extensions, Opts) of + case cow_ws:negotiate_permessage_deflate(Params, Extensions, Opts#{owner => Pid}) of {ok, RespExt, Extensions2} -> - Req2 = Req#{websocket_compress => true}, websocket_extensions(State#state{extensions=Extensions2}, - Req2, Tail, [<<", ">>, RespExt|RespHeader]); + Req, Tail, [<<", ">>, RespExt|RespHeader]); ignore -> websocket_extensions(State, Req, Tail, RespHeader) end; -websocket_extensions(State=#state{extensions=Extensions}, Req, [{<<"x-webkit-deflate-frame">>, Params}|Tail], RespHeader) -> +websocket_extensions(State=#state{extensions=Extensions}, Req=#{pid := Pid}, + [{<<"x-webkit-deflate-frame">>, Params}|Tail], RespHeader) -> %% @todo Make deflate options configurable. Opts = #{level => best_compression, mem_level => 8, strategy => default}, - case cow_ws:negotiate_x_webkit_deflate_frame(Params, Extensions, Opts) of + case cow_ws:negotiate_x_webkit_deflate_frame(Params, Extensions, Opts#{owner => Pid}) of {ok, RespExt, Extensions2} -> - Req2 = cowboy_req:set_meta(websocket_compress, true, Req), websocket_extensions(State#state{extensions=Extensions2}, - Req2, Tail, [<<", ">>, RespExt|RespHeader]); + Req, Tail, [<<", ">>, RespExt|RespHeader]); ignore -> websocket_extensions(State, Req, Tail, RespHeader) end; @@ -152,13 +158,11 @@ websocket_handshake(State=#state{key=Key}, Req=#{pid := Pid, streamid := StreamID}, HandlerState, Env) -> Challenge = base64:encode(crypto:hash(sha, << Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>)), - RespHeaders = maps:get(resp_headers, Req, #{}), - Headers = maps:merge(RespHeaders, #{ - %% @todo Hmm should those be here or in cowboy_http? + Headers = cowboy_req:response_headers(#{ <<"connection">> => <<"Upgrade">>, <<"upgrade">> => <<"websocket">>, <<"sec-websocket-accept">> => Challenge - }), + }, Req), Pid ! {{Pid, StreamID}, {switch_protocol, Headers, ?MODULE, {Req, State, HandlerState}}}, {ok, Req, Env}. @@ -369,15 +373,7 @@ handler_call(State=#state{handler=Handler}, Req, HandlerState, websocket_close(State, Req2, HandlerState2, stop) catch Class:Reason -> _ = websocket_close(State, Req, HandlerState, {crash, Class, Reason}), - exit({cowboy_handler, [ - {class, Class}, - {reason, Reason}, - {mfa, {Handler, Callback, 3}}, - {stacktrace, erlang:get_stacktrace()}, - {msg, Message}, - {req, Req}, - {state, HandlerState} - ]}) + erlang:raise(Class, Reason, erlang:get_stacktrace()) end. -spec websocket_send(cow_ws:frame(), #state{}) -> ok | stop | {error, atom()}. |