aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/cowboy.app.src2
-rw-r--r--src/cowboy_http_protocol.erl29
-rw-r--r--src/cowboy_http_req.erl17
-rw-r--r--src/cowboy_http_websocket.erl64
-rw-r--r--src/cowboy_http_websocket_handler.erl8
-rw-r--r--src/cowboy_ssl_transport.erl8
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.