aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_http_protocol.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cowboy_http_protocol.erl')
-rw-r--r--src/cowboy_http_protocol.erl90
1 files changed, 56 insertions, 34 deletions
diff --git a/src/cowboy_http_protocol.erl b/src/cowboy_http_protocol.erl
index 602ea6a..221e316 100644
--- a/src/cowboy_http_protocol.erl
+++ b/src/cowboy_http_protocol.erl
@@ -14,7 +14,7 @@
-module(cowboy_http_protocol).
-export([start_link/3]). %% API.
--export([init/3, wait_request/1]). %% FSM.
+-export([init/3, parse_request/1]). %% FSM.
-include("include/http.hrl").
@@ -26,7 +26,8 @@
req_empty_lines = 0 :: integer(),
max_empty_lines :: integer(),
timeout :: timeout(),
- connection = keepalive :: keepalive | close
+ connection = keepalive :: keepalive | close,
+ buffer = <<>> :: binary()
}).
%% API.
@@ -47,11 +48,21 @@ init(Socket, Transport, Opts) ->
wait_request(#state{socket=Socket, transport=Transport,
dispatch=Dispatch, max_empty_lines=MaxEmptyLines, timeout=Timeout}).
+-spec parse_request(State::#state{}) -> ok.
+%% @todo Use decode_packet options to limit length?
+parse_request(State=#state{buffer=Buffer}) ->
+ case erlang:decode_packet(http_bin, Buffer, []) of
+ {ok, Request, Rest} -> request(Request, State#state{buffer=Rest});
+ {more, _Length} -> wait_request(State);
+ {error, _Reason} -> error_response(400, State)
+ end.
+
-spec wait_request(State::#state{}) -> ok.
-wait_request(State=#state{socket=Socket, transport=Transport, timeout=T}) ->
- Transport:setopts(Socket, [{packet, http}]),
+wait_request(State=#state{socket=Socket, transport=Transport,
+ timeout=T, buffer=Buffer}) ->
case Transport:recv(Socket, 0, T) of
- {ok, Request} -> request(Request, State);
+ {ok, Data} -> parse_request(State#state{
+ buffer= << Buffer/binary, Data/binary >>});
{error, timeout} -> error_terminate(408, State);
{error, closed} -> terminate(State)
end.
@@ -67,41 +78,50 @@ request({http_request, Method, {abs_path, AbsPath}, Version},
State=#state{socket=Socket, transport=Transport}) ->
{Path, RawPath, Qs} = cowboy_dispatcher:split_path(AbsPath),
ConnAtom = version_to_connection(Version),
- wait_header(#http_req{socket=Socket, transport=Transport,
+ parse_header(#http_req{socket=Socket, transport=Transport,
connection=ConnAtom, method=Method, version=Version,
path=Path, raw_path=RawPath, raw_qs=Qs},
State#state{connection=ConnAtom});
request({http_request, Method, '*', Version},
State=#state{socket=Socket, transport=Transport}) ->
ConnAtom = version_to_connection(Version),
- wait_header(#http_req{socket=Socket, transport=Transport,
+ parse_header(#http_req{socket=Socket, transport=Transport,
connection=ConnAtom, method=Method, version=Version,
- path='*', raw_path="*", raw_qs=[]},
+ path='*', raw_path= <<"*">>, raw_qs= <<>>},
State#state{connection=ConnAtom});
request({http_request, _Method, _URI, _Version}, State) ->
error_terminate(501, State);
-request({http_error, "\r\n"},
+request({http_error, <<"\r\n">>},
State=#state{req_empty_lines=N, max_empty_lines=N}) ->
error_terminate(400, State);
-request({http_error, "\r\n"}, State=#state{req_empty_lines=N}) ->
- wait_request(State#state{req_empty_lines=N + 1});
+request({http_error, <<"\r\n">>}, State=#state{req_empty_lines=N}) ->
+ parse_request(State#state{req_empty_lines=N + 1});
request({http_error, _Any}, State) ->
error_terminate(400, State).
+-spec parse_header(Req::#http_req{}, State::#state{}) -> ok.
+parse_header(Req, State=#state{buffer=Buffer}) ->
+ case erlang:decode_packet(httph_bin, Buffer, []) of
+ {ok, Header, Rest} -> header(Header, Req, State#state{buffer=Rest});
+ {more, _Length} -> wait_header(Req, State);
+ {error, _Reason} -> error_response(400, State)
+ end.
+
-spec wait_header(Req::#http_req{}, State::#state{}) -> ok.
wait_header(Req, State=#state{socket=Socket,
- transport=Transport, timeout=T}) ->
+ transport=Transport, timeout=T, buffer=Buffer}) ->
case Transport:recv(Socket, 0, T) of
- {ok, Header} -> header(Header, Req, State);
+ {ok, Data} -> parse_header(Req, State#state{
+ buffer= << Buffer/binary, Data/binary >>});
{error, timeout} -> error_terminate(408, State);
{error, closed} -> terminate(State)
end.
-spec header({http_header, I::integer(), Field::http_header(), R::term(),
- Value::string()} | http_eoh, Req::#http_req{}, State::#state{}) -> ok.
+ Value::binary()} | http_eoh, Req::#http_req{}, State::#state{}) -> ok.
header({http_header, _I, 'Host', _R, RawHost}, Req=#http_req{
transport=Transport, host=undefined}, State) ->
- RawHost2 = string_to_lower(RawHost),
+ RawHost2 = binary_to_lower(RawHost),
case catch cowboy_dispatcher:split_host(RawHost2) of
{Host, RawHost3, undefined} ->
Port = default_port(Transport:name()),
@@ -115,21 +135,21 @@ header({http_header, _I, 'Host', _R, RawHost}, Req=#http_req{
end;
%% Ignore Host headers if we already have it.
header({http_header, _I, 'Host', _R, _V}, Req, State) ->
- wait_header(Req, State);
+ parse_header(Req, State);
header({http_header, _I, 'Connection', _R, Connection}, Req, State) ->
ConnAtom = connection_to_atom(Connection),
- wait_header(Req#http_req{connection=ConnAtom,
+ parse_header(Req#http_req{connection=ConnAtom,
headers=[{'Connection', Connection}|Req#http_req.headers]},
State#state{connection=ConnAtom});
header({http_header, _I, Field, _R, Value}, Req, State) ->
- wait_header(Req#http_req{headers=[{Field, Value}|Req#http_req.headers]},
+ parse_header(Req#http_req{headers=[{Field, Value}|Req#http_req.headers]},
State);
%% The Host header is required.
header(http_eoh, #http_req{host=undefined}, State) ->
error_terminate(400, State);
-header(http_eoh, Req, State) ->
- handler_init(Req, State);
-header({http_error, _String}, _Req, State) ->
+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(Req::#http_req{}, State::#state{}) -> ok.
@@ -139,7 +159,7 @@ dispatch(Req=#http_req{host=Host, path=Path},
%% things like url rewriting.
case cowboy_dispatcher:match(Host, Path, Dispatch) of
{ok, Handler, Opts, Binds} ->
- wait_header(Req#http_req{bindings=Binds},
+ parse_header(Req#http_req{bindings=Binds},
State#state{handler={Handler, Opts}});
{error, notfound, host} ->
error_terminate(400, State);
@@ -173,14 +193,17 @@ handler_loop(HandlerState, Req, State=#state{handler={Handler, _Opts}}) ->
-spec handler_terminate(HandlerState::term(), Req::#http_req{},
State::#state{}) -> ok.
-handler_terminate(HandlerState, Req, State=#state{handler={Handler, _Opts}}) ->
+handler_terminate(HandlerState, Req=#http_req{buffer=Buffer},
+ State=#state{handler={Handler, _Opts}}) ->
HandlerRes = (catch Handler:terminate(
Req#http_req{resp_state=locked}, HandlerState)),
BodyRes = ensure_body_processed(Req),
ensure_response(Req, State),
case {HandlerRes, BodyRes, State#state.connection} of
- {ok, ok, keepalive} -> ?MODULE:wait_request(State);
- _Closed -> terminate(State)
+ {ok, ok, keepalive} ->
+ ?MODULE:parse_request(State#state{buffer=Buffer});
+ _Closed ->
+ terminate(State)
end.
-spec ensure_body_processed(Req::#http_req{}) -> ok | close.
@@ -226,14 +249,14 @@ terminate(#state{socket=Socket, transport=Transport}) ->
version_to_connection({1, 1}) -> keepalive;
version_to_connection(_Any) -> close.
--spec connection_to_atom(Connection::string()) -> keepalive | close.
-connection_to_atom("keep-alive") ->
+-spec connection_to_atom(Connection::binary()) -> keepalive | close.
+connection_to_atom(<<"keep-alive">>) ->
keepalive;
-connection_to_atom("close") ->
+connection_to_atom(<<"close">>) ->
close;
connection_to_atom(Connection) ->
- case string_to_lower(Connection) of
- "close" -> close;
+ case binary_to_lower(Connection) of
+ <<"close">> -> close;
_Any -> keepalive
end.
@@ -241,11 +264,10 @@ connection_to_atom(Connection) ->
default_port(ssl) -> 443;
default_port(_) -> 80.
-%% More efficient implementation of string:to_lower.
%% We are excluding a few characters on purpose.
--spec string_to_lower(string()) -> string().
-string_to_lower(L) ->
- [char_to_lower(C) || C <- L].
+-spec binary_to_lower(binary()) -> binary().
+binary_to_lower(L) ->
+ << << (char_to_lower(C)) >> || << C >> <= L >>.
%% We gain noticeable speed by matching each value directly.
-spec char_to_lower(char()) -> char().