diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cowboy.app.src | 2 | ||||
-rw-r--r-- | src/cowboy_http_protocol.erl | 29 | ||||
-rw-r--r-- | src/cowboy_http_req.erl | 17 | ||||
-rw-r--r-- | src/cowboy_http_websocket.erl | 64 | ||||
-rw-r--r-- | src/cowboy_http_websocket_handler.erl | 8 | ||||
-rw-r--r-- | src/cowboy_ssl_transport.erl | 8 |
6 files changed, 91 insertions, 37 deletions
diff --git a/src/cowboy.app.src b/src/cowboy.app.src index 912673a..264607f 100644 --- a/src/cowboy.app.src +++ b/src/cowboy.app.src @@ -14,7 +14,7 @@ {application, cowboy, [ {description, "Small, fast, modular HTTP server."}, - {vsn, "0.2.0"}, + {vsn, "0.3.0"}, {modules, []}, {registered, [cowboy_clock, cowboy_sup]}, {applications, [ diff --git a/src/cowboy_http_protocol.erl b/src/cowboy_http_protocol.erl index 9923591..63c971d 100644 --- a/src/cowboy_http_protocol.erl +++ b/src/cowboy_http_protocol.erl @@ -148,10 +148,12 @@ header({http_header, _I, 'Host', _R, RawHost}, Req=#http_req{ case catch cowboy_dispatcher:split_host(RawHost2) of {Host, RawHost3, undefined} -> Port = default_port(Transport:name()), - dispatch(Req#http_req{host=Host, raw_host=RawHost3, port=Port, + dispatch(fun parse_header/2, Req#http_req{ + host=Host, raw_host=RawHost3, port=Port, headers=[{'Host', RawHost3}|Req#http_req.headers]}, State); {Host, RawHost3, Port} -> - dispatch(Req#http_req{host=Host, raw_host=RawHost3, port=Port, + dispatch(fun parse_header/2, Req#http_req{ + host=Host, raw_host=RawHost3, port=Port, headers=[{'Host', RawHost3}|Req#http_req.headers]}, State); {'EXIT', _Reason} -> error_terminate(400, State) @@ -168,24 +170,30 @@ header({http_header, _I, Field, _R, Value}, Req, State) -> Field2 = format_header(Field), parse_header(Req#http_req{headers=[{Field2, Value}|Req#http_req.headers]}, State); -%% The Host header is required. -header(http_eoh, #http_req{host=undefined}, State) -> +%% The Host header is required in HTTP/1.1. +header(http_eoh, #http_req{version={1, 1}, host=undefined}, State) -> error_terminate(400, State); +%% It is however optional in HTTP/1.0. +header(http_eoh, Req=#http_req{version={1, 0}, transport=Transport, + host=undefined}, State=#state{buffer=Buffer}) -> + Port = default_port(Transport:name()), + dispatch(fun handler_init/2, Req#http_req{host=[], raw_host= <<>>, + port=Port, buffer=Buffer}, State#state{buffer= <<>>}); header(http_eoh, Req, State=#state{buffer=Buffer}) -> handler_init(Req#http_req{buffer=Buffer}, State#state{buffer= <<>>}); header({http_error, _Bin}, _Req, State) -> error_terminate(500, State). --spec dispatch(#http_req{}, #state{}) -> ok. -dispatch(Req=#http_req{host=Host, path=Path}, +-spec dispatch(fun((#http_req{}, #state{}) -> ok), + #http_req{}, #state{}) -> ok. +dispatch(Next, Req=#http_req{host=Host, path=Path}, State=#state{dispatch=Dispatch}) -> %% @todo We probably want to filter the Host and Path here to allow %% things like url rewriting. case cowboy_dispatcher:match(Host, Path, Dispatch) of {ok, Handler, Opts, Binds, HostInfo, PathInfo} -> - parse_header(Req#http_req{host_info=HostInfo, path_info=PathInfo, - bindings=Binds}, - State#state{handler={Handler, Opts}}); + Next(Req#http_req{host_info=HostInfo, path_info=PathInfo, + bindings=Binds}, State#state{handler={Handler, Opts}}); {error, notfound, host} -> error_terminate(400, State); {error, notfound, path} -> @@ -246,7 +254,8 @@ next_request(HandlerState, Req=#http_req{buffer=Buffer}, State) -> RespRes = ensure_response(Req, State), case {HandlerRes, BodyRes, RespRes, State#state.connection} of {ok, ok, ok, keepalive} -> - ?MODULE:parse_request(State#state{buffer=Buffer}); + ?MODULE:parse_request(State#state{ + buffer=Buffer, req_empty_lines=0}); _Closed -> terminate(State) end. diff --git a/src/cowboy_http_req.erl b/src/cowboy_http_req.erl index 69d47b0..5b63599 100644 --- a/src/cowboy_http_req.erl +++ b/src/cowboy_http_req.erl @@ -264,7 +264,7 @@ body_qs(Req) -> -> {ok, #http_req{}}. reply(Code, Headers, Body, Req=#http_req{socket=Socket, transport=Transport, connection=Connection, - resp_state=waiting}) -> + method=Method, resp_state=waiting}) -> Head = response_head(Code, Headers, [ {<<"Connection">>, atom_to_connection(Connection)}, {<<"Content-Length">>, @@ -272,7 +272,10 @@ reply(Code, Headers, Body, Req=#http_req{socket=Socket, {<<"Date">>, cowboy_clock:rfc1123()}, {<<"Server">>, <<"Cowboy">>} ]), - Transport:send(Socket, [Head, Body]), + case Method of + 'HEAD' -> Transport:send(Socket, Head); + _ -> Transport:send(Socket, [Head, Body]) + end, {ok, Req#http_req{resp_state=done}}. %% @doc Initiate the sending of a chunked reply to the client. @@ -280,6 +283,14 @@ reply(Code, Headers, Body, Req=#http_req{socket=Socket, -spec chunked_reply(http_status(), http_headers(), #http_req{}) -> {ok, #http_req{}}. chunked_reply(Code, Headers, Req=#http_req{socket=Socket, transport=Transport, + method='HEAD', resp_state=waiting}) -> + Head = response_head(Code, Headers, [ + {<<"Date">>, cowboy_clock:rfc1123()}, + {<<"Server">>, <<"Cowboy">>} + ]), + Transport:send(Socket, Head), + {ok, Req#http_req{resp_state=done}}; +chunked_reply(Code, Headers, Req=#http_req{socket=Socket, transport=Transport, resp_state=waiting}) -> Head = response_head(Code, Headers, [ {<<"Connection">>, <<"close">>}, @@ -294,6 +305,8 @@ chunked_reply(Code, Headers, Req=#http_req{socket=Socket, transport=Transport, %% %% A chunked reply must have been initiated before calling this function. -spec chunk(iodata(), #http_req{}) -> ok. +chunk(_Data, #http_req{socket=_Socket, transport=_Transport, method='HEAD'}) -> + ok; chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) -> Transport:send(Socket, [integer_to_list(iolist_size(Data), 16), <<"\r\n">>, Data, <<"\r\n">>]). diff --git a/src/cowboy_http_websocket.erl b/src/cowboy_http_websocket.erl index 8e951a5..1164684 100644 --- a/src/cowboy_http_websocket.erl +++ b/src/cowboy_http_websocket.erl @@ -52,6 +52,7 @@ opts :: any(), challenge = undefined :: undefined | binary(), timeout = infinity :: timeout(), + timeout_ref = undefined :: undefined | reference(), messages = undefined :: undefined | {atom(), atom(), atom()}, hibernate = false :: boolean(), eop :: undefined | tuple(), %% hixie-76 specific. @@ -116,9 +117,15 @@ handler_init(State=#state{handler=Handler, opts=Opts}, try Handler:websocket_init(Transport:name(), Req, Opts) of {ok, Req2, HandlerState} -> websocket_handshake(State, Req2, HandlerState); + {ok, Req2, HandlerState, hibernate} -> + websocket_handshake(State#state{hibernate=true}, + Req2, HandlerState); {ok, Req2, HandlerState, Timeout} -> websocket_handshake(State#state{timeout=Timeout}, - Req2, HandlerState) + Req2, HandlerState); + {ok, Req2, HandlerState, Timeout, hibernate} -> + websocket_handshake(State#state{timeout=Timeout, + hibernate=true}, Req2, HandlerState) catch Class:Reason -> upgrade_error(Req), error_logger:error_msg( @@ -137,8 +144,8 @@ upgrade_error(Req=#http_req{socket=Socket, transport=Transport}) -> -spec websocket_handshake(#state{}, #http_req{}, any()) -> ok. websocket_handshake(State=#state{version=0, origin=Origin, challenge=Challenge}, Req=#http_req{transport=Transport, - raw_host=Host, port=Port, raw_path=Path}, HandlerState) -> - Location = hixie76_location(Transport:name(), Host, Port, Path), + raw_host=Host, port=Port, raw_path=Path, raw_qs=QS}, HandlerState) -> + Location = hixie76_location(Transport:name(), Host, Port, Path, QS), {ok, Req2} = cowboy_http_req:reply( <<"101 WebSocket Protocol Handshake">>, [{<<"Connection">>, <<"Upgrade">>}, @@ -164,16 +171,28 @@ handler_before_loop(State=#state{hibernate=true}, Req=#http_req{socket=Socket, transport=Transport}, HandlerState, SoFar) -> Transport:setopts(Socket, [{active, once}]), - erlang:hibernate(?MODULE, handler_loop, [State#state{hibernate=false}, + State2 = handler_loop_timeout(State), + erlang:hibernate(?MODULE, handler_loop, [State2#state{hibernate=false}, Req, HandlerState, SoFar]); handler_before_loop(State, Req=#http_req{socket=Socket, transport=Transport}, HandlerState, SoFar) -> Transport:setopts(Socket, [{active, once}]), - handler_loop(State, Req, HandlerState, SoFar). + State2 = handler_loop_timeout(State), + handler_loop(State2, Req, HandlerState, SoFar). + +-spec handler_loop_timeout(#state{}) -> #state{}. +handler_loop_timeout(State=#state{timeout=infinity}) -> + State#state{timeout_ref=undefined}; +handler_loop_timeout(State=#state{timeout=Timeout, timeout_ref=PrevRef}) -> + _ = case PrevRef of undefined -> ignore; PrevRef -> + erlang:cancel_timer(PrevRef) end, + TRef = make_ref(), + erlang:send_after(Timeout, self(), {?MODULE, timeout, TRef}), + State#state{timeout_ref=TRef}. %% @private -spec handler_loop(#state{}, #http_req{}, any(), binary()) -> ok. -handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout}, +handler_loop(State=#state{messages={OK, Closed, Error}, timeout_ref=TRef}, Req=#http_req{socket=Socket}, HandlerState, SoFar) -> receive {OK, Socket, Data} -> @@ -183,11 +202,13 @@ handler_loop(State=#state{messages={OK, Closed, Error}, timeout=Timeout}, handler_terminate(State, Req, HandlerState, {error, closed}); {Error, Socket, Reason} -> handler_terminate(State, Req, HandlerState, {error, Reason}); + {?MODULE, timeout, TRef} -> + websocket_close(State, Req, HandlerState, {normal, timeout}); + {?MODULE, timeout, OlderTRef} when is_reference(OlderTRef) -> + handler_loop(State, Req, HandlerState, SoFar); Message -> handler_call(State, Req, HandlerState, SoFar, websocket_info, Message, fun handler_before_loop/4) - after Timeout -> - websocket_close(State, Req, HandlerState, {normal, timeout}) end. -spec websocket_data(#state{}, #http_req{}, any(), binary()) -> ok. @@ -396,11 +417,14 @@ hixie76_key_to_integer(Key) -> Spaces = length([C || << C >> <= Key, C =:= 32]), Number div Spaces. --spec hixie76_location(atom(), binary(), inet:ip_port(), binary()) +-spec hixie76_location(atom(), binary(), inet:ip_port(), binary(), binary()) -> binary(). -hixie76_location(Protocol, Host, Port, Path) -> - << (hixie76_location_protocol(Protocol))/binary, "://", Host/binary, - (hixie76_location_port(ssl, Port))/binary, Path/binary >>. +hixie76_location(Protocol, Host, Port, Path, <<>>) -> + << (hixie76_location_protocol(Protocol))/binary, "://", Host/binary, + (hixie76_location_port(ssl, Port))/binary, Path/binary>>; +hixie76_location(Protocol, Host, Port, Path, QS) -> + << (hixie76_location_protocol(Protocol))/binary, "://", Host/binary, + (hixie76_location_port(ssl, Port))/binary, Path/binary, "?", QS/binary >>. -spec hixie76_location_protocol(atom()) -> binary(). hixie76_location_protocol(ssl) -> <<"wss">>; @@ -408,9 +432,9 @@ hixie76_location_protocol(_) -> <<"ws">>. -spec hixie76_location_port(atom(), inet:ip_port()) -> binary(). hixie76_location_port(ssl, 443) -> - <<"">>; + <<>>; hixie76_location_port(_, 80) -> - <<"">>; + <<>>; hixie76_location_port(_, Port) -> <<":", (list_to_binary(integer_to_list(Port)))/binary>>. @@ -436,13 +460,17 @@ hybi_payload_length(N) -> hixie76_location_test() -> ?assertEqual(<<"ws://localhost/path">>, - hixie76_location(other, <<"localhost">>, 80, <<"/path">>)), + hixie76_location(other, <<"localhost">>, 80, <<"/path">>, <<>>)), ?assertEqual(<<"ws://localhost:8080/path">>, - hixie76_location(other, <<"localhost">>, 8080, <<"/path">>)), + hixie76_location(other, <<"localhost">>, 8080, <<"/path">>, <<>>)), + ?assertEqual(<<"ws://localhost:8080/path?dummy=2785">>, + hixie76_location(other, <<"localhost">>, 8080, <<"/path">>, <<"dummy=2785">>)), ?assertEqual(<<"wss://localhost/path">>, - hixie76_location(ssl, <<"localhost">>, 443, <<"/path">>)), + hixie76_location(ssl, <<"localhost">>, 443, <<"/path">>, <<>>)), ?assertEqual(<<"wss://localhost:8443/path">>, - hixie76_location(ssl, <<"localhost">>, 8443, <<"/path">>)), + hixie76_location(ssl, <<"localhost">>, 8443, <<"/path">>, <<>>)), + ?assertEqual(<<"wss://localhost:8443/path?dummy=2785">>, + hixie76_location(ssl, <<"localhost">>, 8443, <<"/path">>, <<"dummy=2785">>)), ok. -endif. diff --git a/src/cowboy_http_websocket_handler.erl b/src/cowboy_http_websocket_handler.erl index 90cf7ac..2ea0a46 100644 --- a/src/cowboy_http_websocket_handler.erl +++ b/src/cowboy_http_websocket_handler.erl @@ -29,8 +29,7 @@ %% here. %% %% <em>websocket_handle/3</em> receives the data from the socket. It can reply -%% something, do nothing or close the connection. You can choose to hibernate -%% the process by returning <em>hibernate</em> to save memory and CPU. +%% something, do nothing or close the connection. %% %% <em>websocket_info/3</em> receives messages sent to the process. It has %% the same reply format as <em>websocket_handle/3</em> described above. Note @@ -41,6 +40,11 @@ %% <em>websocket_terminate/3</em> is meant for cleaning up. It also receives %% the request and the state previously defined, along with a reason for %% termination. +%% +%% All of <em>websocket_init/3</em>, <em>websocket_handle/3</em> and +%% <em>websocket_info/3</em> can decide to hibernate the process by adding +%% an extra element to the returned tuple, containing the atom +%% <em>hibernate</em>. Doing so helps save memory and improve CPU usage. -module(cowboy_http_websocket_handler). -export([behaviour_info/1]). diff --git a/src/cowboy_ssl_transport.erl b/src/cowboy_ssl_transport.erl index bb53418..bf8b1fb 100644 --- a/src/cowboy_ssl_transport.erl +++ b/src/cowboy_ssl_transport.erl @@ -79,10 +79,10 @@ listen(Opts) -> Ip -> [Ip|ListenOpts0] end, ListenOpts = - case lists:keyfind(cacertfile, 1, Opts) of - false -> ListenOpts1; - CACertFile -> [CACertFile|ListenOpts1] - end, + case lists:keyfind(cacertfile, 1, Opts) of + false -> ListenOpts1; + CACertFile -> [CACertFile|ListenOpts1] + end, ssl:listen(Port, ListenOpts). %% @doc Accept an incoming connection on a listen socket. |