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.erl39
-rw-r--r--src/cowboy_client.erl18
-rw-r--r--src/cowboy_handler.erl2
-rw-r--r--src/cowboy_http.erl22
-rw-r--r--src/cowboy_http_handler.erl2
-rw-r--r--src/cowboy_loop_handler.erl2
-rw-r--r--src/cowboy_middleware.erl4
-rw-r--r--src/cowboy_protocol.erl205
-rw-r--r--src/cowboy_req.erl253
-rw-r--r--src/cowboy_rest.erl15
-rw-r--r--src/cowboy_spdy.erl544
-rw-r--r--src/cowboy_spdy.hrl181
-rw-r--r--src/cowboy_sub_protocol.erl4
-rw-r--r--src/cowboy_websocket.erl4
15 files changed, 1042 insertions, 255 deletions
diff --git a/src/cowboy.app.src b/src/cowboy.app.src
index 92dd124..e9cfcb8 100644
--- a/src/cowboy.app.src
+++ b/src/cowboy.app.src
@@ -17,7 +17,7 @@
{description, "Small, fast, modular HTTP server."},
{sub_description, "Cowboy is also a socket acceptor pool, "
"able to accept connections for any kind of TCP protocol."},
- {vsn, "0.8.4"},
+ {vsn, "0.8.5"},
{modules, []},
{registered, [cowboy_clock, cowboy_sup]},
{applications, [
diff --git a/src/cowboy.erl b/src/cowboy.erl
index 257172d..16445e1 100644
--- a/src/cowboy.erl
+++ b/src/cowboy.erl
@@ -17,25 +17,56 @@
-export([start_http/4]).
-export([start_https/4]).
+-export([start_spdy/4]).
-export([stop_listener/1]).
-export([set_env/3]).
+-type http_headers() :: [{binary(), iodata()}].
+-export_type([http_headers/0]).
+
+-type http_status() :: non_neg_integer() | binary().
+-export_type([http_status/0]).
+
+-type http_version() :: 'HTTP/1.1' | 'HTTP/1.0'.
+-export_type([http_version/0]).
+
+-type onrequest_fun() :: fun((Req) -> Req).
+-export_type([onrequest_fun/0]).
+
+-type onresponse_fun() ::
+ fun((http_status(), http_headers(), iodata(), Req) -> Req).
+-export_type([onresponse_fun/0]).
+
%% @doc Start an HTTP listener.
--spec start_http(any(), non_neg_integer(), any(), any()) -> {ok, pid()}.
+-spec start_http(ranch:ref(), non_neg_integer(), ranch_tcp:opts(),
+ cowboy_protocol:opts()) -> {ok, pid()}.
start_http(Ref, NbAcceptors, TransOpts, ProtoOpts)
when is_integer(NbAcceptors), NbAcceptors > 0 ->
ranch:start_listener(Ref, NbAcceptors,
ranch_tcp, TransOpts, cowboy_protocol, ProtoOpts).
%% @doc Start an HTTPS listener.
--spec start_https(any(), non_neg_integer(), any(), any()) -> {ok, pid()}.
+-spec start_https(ranch:ref(), non_neg_integer(), ranch_ssl:opts(),
+ cowboy_protocol:opts()) -> {ok, pid()}.
start_https(Ref, NbAcceptors, TransOpts, ProtoOpts)
when is_integer(NbAcceptors), NbAcceptors > 0 ->
ranch:start_listener(Ref, NbAcceptors,
ranch_ssl, TransOpts, cowboy_protocol, ProtoOpts).
+%% @doc Start a SPDY listener.
+-spec start_spdy(any(), non_neg_integer(), any(), any()) -> {ok, pid()}.
+start_spdy(Ref, NbAcceptors, TransOpts, ProtoOpts)
+ when is_integer(NbAcceptors), NbAcceptors > 0 ->
+ TransOpts2 = [
+ {connection_type, supervisor},
+ {next_protocols_advertised,
+ [<<"spdy/3">>, <<"http/1.1">>, <<"http/1.0">>]}
+ |TransOpts],
+ ranch:start_listener(Ref, NbAcceptors,
+ ranch_ssl, TransOpts2, cowboy_spdy, ProtoOpts).
+
%% @doc Stop a listener.
--spec stop_listener(any()) -> ok.
+-spec stop_listener(ranch:ref()) -> ok.
stop_listener(Ref) ->
ranch:stop_listener(Ref).
@@ -44,7 +75,7 @@ stop_listener(Ref) ->
%% Allows you to update live an environment value used by middlewares.
%% This function is primarily intended to simplify updating the dispatch
%% list used for routing.
--spec set_env(any(), atom(), any()) -> ok.
+-spec set_env(ranch:ref(), atom(), any()) -> ok.
set_env(Ref, Name, Value) ->
Opts = ranch:get_protocol_options(Ref),
{_, Env} = lists:keyfind(env, 1, Opts),
diff --git a/src/cowboy_client.erl b/src/cowboy_client.erl
index 4d958b1..b5f96b3 100644
--- a/src/cowboy_client.erl
+++ b/src/cowboy_client.erl
@@ -40,7 +40,7 @@
timeout = 5000 :: timeout(), %% @todo Configurable.
buffer = <<>> :: binary(),
connection = keepalive :: keepalive | close,
- version = {1, 1} :: cowboy_http:version(),
+ version = 'HTTP/1.1' :: cowboy:http_version(),
response_body = undefined :: undefined | non_neg_integer()
}).
@@ -91,7 +91,7 @@ request(Method, URL, Headers, Body, Client=#client{
wait -> connect(Transport, Host, Port, Client);
request -> {ok, Client}
end,
- VersionBin = cowboy_http:version_to_binary(Version),
+ VersionBin = atom_to_binary(Version, latin1),
%% @todo do keepalive too, allow override...
Headers2 = [
{<<"host">>, FullHost},
@@ -173,7 +173,7 @@ stream_status(Client=#client{state=State, buffer=Buffer})
when State =:= request ->
case binary:split(Buffer, <<"\r\n">>) of
[Line, Rest] ->
- parse_status(Client#client{state=response, buffer=Rest}, Line);
+ parse_version(Client#client{state=response, buffer=Rest}, Line);
_ ->
case recv(Client) of
{ok, Data} ->
@@ -184,11 +184,13 @@ stream_status(Client=#client{state=State, buffer=Buffer})
end
end.
-parse_status(Client, << "HTTP/", High, ".", Low, " ",
- S3, S2, S1, " ", StatusStr/binary >>)
- when High >= $0, High =< $9, Low >= $0, Low =< $9,
- S3 >= $0, S3 =< $9, S2 >= $0, S2 =< $9, S1 >= $0, S1 =< $9 ->
- Version = {High - $0, Low - $0},
+parse_version(Client, << "HTTP/1.1 ", Rest/binary >>) ->
+ parse_status(Client, Rest, 'HTTP/1.1');
+parse_version(Client, << "HTTP/1.0 ", Rest/binary >>) ->
+ parse_status(Client, Rest, 'HTTP/1.0').
+
+parse_status(Client, << S3, S2, S1, " ", StatusStr/binary >>, Version)
+ when S3 >= $0, S3 =< $9, S2 >= $0, S2 =< $9, S1 >= $0, S1 =< $9 ->
Status = (S3 - $0) * 100 + (S2 - $0) * 10 + S1 - $0,
{ok, Status, StatusStr, Client#client{version=Version}}.
diff --git a/src/cowboy_handler.erl b/src/cowboy_handler.erl
index e040554..2074b4e 100644
--- a/src/cowboy_handler.erl
+++ b/src/cowboy_handler.erl
@@ -105,7 +105,7 @@ handler_init(Req, State, Handler, HandlerOpts) ->
-> {ok, Req, Env}
| {suspend, module(), atom(), any()}
| {halt, Req}
- | {error, cowboy_http:status(), Req}
+ | {error, cowboy:http_status(), Req}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
upgrade_protocol(Req, #state{env=Env},
Handler, HandlerOpts, Module) ->
diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl
index f889b52..af60dd9 100644
--- a/src/cowboy_http.erl
+++ b/src/cowboy_http.erl
@@ -46,21 +46,12 @@
%% Interpretation.
-export([cookie_to_iodata/3]).
--export([version_to_binary/1]).
-export([urldecode/1]).
-export([urldecode/2]).
-export([urlencode/1]).
-export([urlencode/2]).
-export([x_www_form_urlencoded/1]).
--type version() :: {Major::non_neg_integer(), Minor::non_neg_integer()}.
--type headers() :: [{binary(), iodata()}].
--type status() :: non_neg_integer() | binary().
-
--export_type([version/0]).
--export_type([headers/0]).
--export_type([status/0]).
-
%% Parsing.
%% @doc Parse a non-empty list of the given type.
@@ -802,7 +793,7 @@ qvalue(Data, Fun, Q, _M) ->
%% Only Basic authorization is supported so far.
-spec authorization(binary(), binary()) -> {binary(), any()} | {error, badarg}.
authorization(UserPass, Type = <<"basic">>) ->
- cowboy_http:whitespace(UserPass,
+ whitespace(UserPass,
fun(D) ->
authorization_basic_userid(base64:mime_decode(D),
fun(Rest, Userid) ->
@@ -813,7 +804,7 @@ authorization(UserPass, Type = <<"basic">>) ->
end)
end);
authorization(String, Type) ->
- cowboy_http:whitespace(String, fun(Rest) -> {Type, Rest} end).
+ whitespace(String, fun(Rest) -> {Type, Rest} end).
%% @doc Parse user credentials.
-spec authorization_basic_userid(binary(), fun()) -> any().
@@ -849,12 +840,12 @@ authorization_basic_password(<<C, Rest/binary>>, Fun, Acc) ->
Unit :: binary(),
Range :: {non_neg_integer(), non_neg_integer() | infinity} | neg_integer().
range(Data) ->
- cowboy_http:token_ci(Data, fun range/2).
+ token_ci(Data, fun range/2).
range(Data, Token) ->
whitespace(Data,
fun(<<"=", Rest/binary>>) ->
- case cowboy_http:list(Rest, fun range_beginning/2) of
+ case list(Rest, fun range_beginning/2) of
{error, badarg} ->
{error, badarg};
Ranges ->
@@ -1001,11 +992,6 @@ cookie_to_iodata(Name, Value, Opts) ->
[Name, <<"=">>, Value, <<"; Version=1">>,
MaxAgeBin, DomainBin, PathBin, SecureBin, HttpOnlyBin].
-%% @doc Convert an HTTP version tuple to its binary form.
--spec version_to_binary(version()) -> binary().
-version_to_binary({1, 1}) -> <<"HTTP/1.1">>;
-version_to_binary({1, 0}) -> <<"HTTP/1.0">>.
-
%% @doc Decode a URL encoded binary.
%% @equiv urldecode(Bin, crash)
-spec urldecode(binary()) -> binary().
diff --git a/src/cowboy_http_handler.erl b/src/cowboy_http_handler.erl
index 0393153..3ad8f88 100644
--- a/src/cowboy_http_handler.erl
+++ b/src/cowboy_http_handler.erl
@@ -35,6 +35,8 @@
-type state() :: any().
-type terminate_reason() :: {normal, shutdown}
| {normal, timeout} %% Only occurs in loop handlers.
+ | {error, closed} %% Only occurs in loop handlers.
+ | {error, overflow} %% Only occurs in loop handlers.
| {error, atom()}.
-callback init({atom(), http}, Req, opts())
diff --git a/src/cowboy_loop_handler.erl b/src/cowboy_loop_handler.erl
index f8d008f..af49e57 100644
--- a/src/cowboy_loop_handler.erl
+++ b/src/cowboy_loop_handler.erl
@@ -41,6 +41,8 @@
-type state() :: any().
-type terminate_reason() :: {normal, shutdown}
| {normal, timeout}
+ | {error, closed}
+ | {error, overflow}
| {error, atom()}.
-callback init({atom(), http}, Req, opts())
diff --git a/src/cowboy_middleware.erl b/src/cowboy_middleware.erl
index 0c1ca77..40c9407 100644
--- a/src/cowboy_middleware.erl
+++ b/src/cowboy_middleware.erl
@@ -30,7 +30,7 @@
-callback execute(Req, Env)
-> {ok, Req, Env}
- | {suspend, module(), atom(), any()}
+ | {suspend, module(), atom(), [any()]}
| {halt, Req}
- | {error, cowboy_http:status(), Req}
+ | {error, cowboy:http_status(), Req}
when Req::cowboy_req:req(), Env::env().
diff --git a/src/cowboy_protocol.erl b/src/cowboy_protocol.erl
index be351b7..b42f524 100644
--- a/src/cowboy_protocol.erl
+++ b/src/cowboy_protocol.erl
@@ -54,13 +54,22 @@
%% Internal.
-export([init/4]).
-export([parse_request/3]).
+-export([parse_host/2]).
-export([resume/6]).
--type onrequest_fun() :: fun((Req) -> Req).
--type onresponse_fun() ::
- fun((cowboy_http:status(), cowboy_http:headers(), iodata(), Req) -> Req).
--export_type([onrequest_fun/0]).
--export_type([onresponse_fun/0]).
+-type opts() :: [{compress, boolean()}
+ | {env, cowboy_middleware:env()}
+ | {max_empty_lines, non_neg_integer()}
+ | {max_header_name_length, non_neg_integer()}
+ | {max_header_value_length, non_neg_integer()}
+ | {max_headers, non_neg_integer()}
+ | {max_keepalive, non_neg_integer()}
+ | {max_request_line_length, non_neg_integer()}
+ | {middlewares, [module()]}
+ | {onrequest, cowboy:onrequest_fun()}
+ | {onresponse, cowboy:onresponse_fun()}
+ | {timeout, timeout()}].
+-export_type([opts/0]).
-record(state, {
socket :: inet:socket(),
@@ -68,8 +77,8 @@
middlewares :: [module()],
compress :: boolean(),
env :: cowboy_middleware:env(),
- onrequest :: undefined | onrequest_fun(),
- onresponse = undefined :: undefined | onresponse_fun(),
+ onrequest :: undefined | cowboy:onrequest_fun(),
+ onresponse = undefined :: undefined | cowboy:onresponse_fun(),
max_empty_lines :: non_neg_integer(),
req_keepalive = 1 :: non_neg_integer(),
max_keepalive :: non_neg_integer(),
@@ -84,7 +93,7 @@
%% API.
%% @doc Start an HTTP protocol process.
--spec start_link(any(), inet:socket(), module(), any()) -> {ok, pid()}.
+-spec start_link(ranch:ref(), inet:socket(), module(), opts()) -> {ok, pid()}.
start_link(Ref, Socket, Transport, Opts) ->
Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
{ok, Pid}.
@@ -100,7 +109,7 @@ get_value(Key, Opts, Default) ->
end.
%% @private
--spec init(any(), inet:socket(), module(), any()) -> ok.
+-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok.
init(Ref, Socket, Transport, Opts) ->
Compress = get_value(compress, Opts, false),
MaxEmptyLines = get_value(max_empty_lines, Opts, 5),
@@ -202,7 +211,7 @@ parse_method(<< C, Rest/bits >>, State, SoFar) ->
parse_uri(<< $\r, _/bits >>, State, _) ->
error_terminate(400, State);
parse_uri(<< "* ", Rest/bits >>, State, Method) ->
- parse_version(Rest, State, Method, <<"*">>, <<>>, <<>>);
+ parse_version(Rest, State, Method, <<"*">>, <<>>);
parse_uri(<< "http://", Rest/bits >>, State, Method) ->
parse_uri_skip_host(Rest, State, Method);
parse_uri(<< "https://", Rest/bits >>, State, Method) ->
@@ -220,61 +229,61 @@ parse_uri_skip_host(<< C, Rest/bits >>, State, Method) ->
parse_uri_path(<< C, Rest/bits >>, State, Method, SoFar) ->
case C of
$\r -> error_terminate(400, State);
- $\s -> parse_version(Rest, State, Method, SoFar, <<>>, <<>>);
+ $\s -> parse_version(Rest, State, Method, SoFar, <<>>);
$? -> parse_uri_query(Rest, State, Method, SoFar, <<>>);
- $# -> parse_uri_fragment(Rest, State, Method, SoFar, <<>>, <<>>);
+ $# -> skip_uri_fragment(Rest, State, Method, SoFar, <<>>);
_ -> parse_uri_path(Rest, State, Method, << SoFar/binary, C >>)
end.
parse_uri_query(<< C, Rest/bits >>, S, M, P, SoFar) ->
case C of
$\r -> error_terminate(400, S);
- $\s -> parse_version(Rest, S, M, P, SoFar, <<>>);
- $# -> parse_uri_fragment(Rest, S, M, P, SoFar, <<>>);
+ $\s -> parse_version(Rest, S, M, P, SoFar);
+ $# -> skip_uri_fragment(Rest, S, M, P, SoFar);
_ -> parse_uri_query(Rest, S, M, P, << SoFar/binary, C >>)
end.
-parse_uri_fragment(<< C, Rest/bits >>, S, M, P, Q, SoFar) ->
+skip_uri_fragment(<< C, Rest/bits >>, S, M, P, Q) ->
case C of
$\r -> error_terminate(400, S);
- $\s -> parse_version(Rest, S, M, P, Q, SoFar);
- _ -> parse_uri_fragment(Rest, S, M, P, Q, << SoFar/binary, C >>)
+ $\s -> parse_version(Rest, S, M, P, Q);
+ _ -> skip_uri_fragment(Rest, S, M, P, Q)
end.
-parse_version(<< "HTTP/1.1\r\n", Rest/bits >>, S, M, P, Q, F) ->
- parse_header(Rest, S, M, P, Q, F, {1, 1}, []);
-parse_version(<< "HTTP/1.0\r\n", Rest/bits >>, S, M, P, Q, F) ->
- parse_header(Rest, S, M, P, Q, F, {1, 0}, []);
-parse_version(_, State, _, _, _, _) ->
+parse_version(<< "HTTP/1.1\r\n", Rest/bits >>, S, M, P, Q) ->
+ parse_header(Rest, S, M, P, Q, 'HTTP/1.1', []);
+parse_version(<< "HTTP/1.0\r\n", Rest/bits >>, S, M, P, Q) ->
+ parse_header(Rest, S, M, P, Q, 'HTTP/1.0', []);
+parse_version(_, State, _, _, _) ->
error_terminate(505, State).
%% Stop receiving data if we have more than allowed number of headers.
-wait_header(_, State=#state{max_headers=MaxHeaders}, _, _, _, _, _, Headers)
+wait_header(_, State=#state{max_headers=MaxHeaders}, _, _, _, _, Headers)
when length(Headers) >= MaxHeaders ->
error_terminate(400, State);
wait_header(Buffer, State=#state{socket=Socket, transport=Transport,
- until=Until}, M, P, Q, F, V, H) ->
+ until=Until}, M, P, Q, V, H) ->
case recv(Socket, Transport, Until) of
{ok, Data} ->
parse_header(<< Buffer/binary, Data/binary >>,
- State, M, P, Q, F, V, H);
+ State, M, P, Q, V, H);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
terminate(State)
end.
-parse_header(<< $\r, $\n, Rest/bits >>, S, M, P, Q, F, V, Headers) ->
- request(Rest, S, M, P, Q, F, V, lists:reverse(Headers));
+parse_header(<< $\r, $\n, Rest/bits >>, S, M, P, Q, V, Headers) ->
+ request(Rest, S, M, P, Q, V, lists:reverse(Headers));
parse_header(Buffer, State=#state{max_header_name_length=MaxLength},
- M, P, Q, F, V, H) ->
+ M, P, Q, V, H) ->
case match_colon(Buffer, 0) of
nomatch when byte_size(Buffer) > MaxLength ->
error_terminate(400, State);
nomatch ->
- wait_header(Buffer, State, M, P, Q, F, V, H);
+ wait_header(Buffer, State, M, P, Q, V, H);
_ ->
- parse_hd_name(Buffer, State, M, P, Q, F, V, H, <<>>)
+ parse_hd_name(Buffer, State, M, P, Q, V, H, <<>>)
end.
match_colon(<< $:, _/bits >>, N) ->
@@ -290,73 +299,73 @@ match_colon(_, _) ->
%% ... Sorry for your eyes.
%%
%% But let's be honest, that's still pretty readable.
-parse_hd_name(<< C, Rest/bits >>, S, M, P, Q, F, V, H, SoFar) ->
+parse_hd_name(<< C, Rest/bits >>, S, M, P, Q, V, H, SoFar) ->
case C of
- $: -> parse_hd_before_value(Rest, S, M, P, Q, F, V, H, SoFar);
- $\s -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, SoFar);
- $\t -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, SoFar);
- $A -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $a >>);
- $B -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $b >>);
- $C -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $c >>);
- $D -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $d >>);
- $E -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $e >>);
- $F -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $f >>);
- $G -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $g >>);
- $H -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $h >>);
- $I -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $i >>);
- $J -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $j >>);
- $K -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $k >>);
- $L -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $l >>);
- $M -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $m >>);
- $N -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $n >>);
- $O -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $o >>);
- $P -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $p >>);
- $Q -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $q >>);
- $R -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $r >>);
- $S -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $s >>);
- $T -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $t >>);
- $U -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $u >>);
- $V -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $v >>);
- $W -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $w >>);
- $X -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $x >>);
- $Y -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $y >>);
- $Z -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, $z >>);
- C -> parse_hd_name(Rest, S, M, P, Q, F, V, H, << SoFar/binary, C >>)
+ $: -> parse_hd_before_value(Rest, S, M, P, Q, V, H, SoFar);
+ $\s -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar);
+ $\t -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, SoFar);
+ $A -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $a >>);
+ $B -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $b >>);
+ $C -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $c >>);
+ $D -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $d >>);
+ $E -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $e >>);
+ $F -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $f >>);
+ $G -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $g >>);
+ $H -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $h >>);
+ $I -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $i >>);
+ $J -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $j >>);
+ $K -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $k >>);
+ $L -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $l >>);
+ $M -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $m >>);
+ $N -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $n >>);
+ $O -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $o >>);
+ $P -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $p >>);
+ $Q -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $q >>);
+ $R -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $r >>);
+ $S -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $s >>);
+ $T -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $t >>);
+ $U -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $u >>);
+ $V -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $v >>);
+ $W -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $w >>);
+ $X -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $x >>);
+ $Y -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $y >>);
+ $Z -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, $z >>);
+ C -> parse_hd_name(Rest, S, M, P, Q, V, H, << SoFar/binary, C >>)
end.
-parse_hd_name_ws(<< C, Rest/bits >>, S, M, P, Q, F, V, H, Name) ->
+parse_hd_name_ws(<< C, Rest/bits >>, S, M, P, Q, V, H, Name) ->
case C of
- $\s -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, Name);
- $\t -> parse_hd_name_ws(Rest, S, M, P, Q, F, V, H, Name);
- $: -> parse_hd_before_value(Rest, S, M, P, Q, F, V, H, Name)
+ $\s -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, Name);
+ $\t -> parse_hd_name_ws(Rest, S, M, P, Q, V, H, Name);
+ $: -> parse_hd_before_value(Rest, S, M, P, Q, V, H, Name)
end.
wait_hd_before_value(Buffer, State=#state{
socket=Socket, transport=Transport, until=Until},
- M, P, Q, F, V, H, N) ->
+ M, P, Q, V, H, N) ->
case recv(Socket, Transport, Until) of
{ok, Data} ->
parse_hd_before_value(<< Buffer/binary, Data/binary >>,
- State, M, P, Q, F, V, H, N);
+ State, M, P, Q, V, H, N);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
terminate(State)
end.
-parse_hd_before_value(<< $\s, Rest/bits >>, S, M, P, Q, F, V, H, N) ->
- parse_hd_before_value(Rest, S, M, P, Q, F, V, H, N);
-parse_hd_before_value(<< $\t, Rest/bits >>, S, M, P, Q, F, V, H, N) ->
- parse_hd_before_value(Rest, S, M, P, Q, F, V, H, N);
+parse_hd_before_value(<< $\s, Rest/bits >>, S, M, P, Q, V, H, N) ->
+ parse_hd_before_value(Rest, S, M, P, Q, V, H, N);
+parse_hd_before_value(<< $\t, Rest/bits >>, S, M, P, Q, V, H, N) ->
+ parse_hd_before_value(Rest, S, M, P, Q, V, H, N);
parse_hd_before_value(Buffer, State=#state{
- max_header_value_length=MaxLength}, M, P, Q, F, V, H, N) ->
+ max_header_value_length=MaxLength}, M, P, Q, V, H, N) ->
case match_eol(Buffer, 0) of
nomatch when byte_size(Buffer) > MaxLength ->
error_terminate(400, State);
nomatch ->
- wait_hd_before_value(Buffer, State, M, P, Q, F, V, H, N);
+ wait_hd_before_value(Buffer, State, M, P, Q, V, H, N);
_ ->
- parse_hd_value(Buffer, State, M, P, Q, F, V, H, N, <<>>)
+ parse_hd_value(Buffer, State, M, P, Q, V, H, N, <<>>)
end.
%% We completely ignore the first argument which is always
@@ -365,10 +374,10 @@ parse_hd_before_value(Buffer, State=#state{
%% operations for no reasons.
wait_hd_value(_, State=#state{
socket=Socket, transport=Transport, until=Until},
- M, P, Q, F, V, H, N, SoFar) ->
+ M, P, Q, V, H, N, SoFar) ->
case recv(Socket, Transport, Until) of
{ok, Data} ->
- parse_hd_value(Data, State, M, P, Q, F, V, H, N, SoFar);
+ parse_hd_value(Data, State, M, P, Q, V, H, N, SoFar);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
@@ -380,51 +389,51 @@ wait_hd_value(_, State=#state{
%% the critical path, but forces us to have a special function.
wait_hd_value_nl(_, State=#state{
socket=Socket, transport=Transport, until=Until},
- M, P, Q, F, V, Headers, Name, SoFar) ->
+ M, P, Q, V, Headers, Name, SoFar) ->
case recv(Socket, Transport, Until) of
{ok, << C, Data/bits >>} when C =:= $\s; C =:= $\t ->
- parse_hd_value(Data, State, M, P, Q, F, V, Headers, Name, SoFar);
+ parse_hd_value(Data, State, M, P, Q, V, Headers, Name, SoFar);
{ok, Data} ->
- parse_header(Data, State, M, P, Q, F, V, [{Name, SoFar}|Headers]);
+ parse_header(Data, State, M, P, Q, V, [{Name, SoFar}|Headers]);
{error, timeout} ->
error_terminate(408, State);
{error, _} ->
terminate(State)
end.
-parse_hd_value(<< $\r, Rest/bits >>, S, M, P, Q, F, V, Headers, Name, SoFar) ->
+parse_hd_value(<< $\r, Rest/bits >>, S, M, P, Q, V, Headers, Name, SoFar) ->
case Rest of
<< $\n >> ->
- wait_hd_value_nl(<<>>, S, M, P, Q, F, V, Headers, Name, SoFar);
+ wait_hd_value_nl(<<>>, S, M, P, Q, V, Headers, Name, SoFar);
<< $\n, C, Rest2/bits >> when C =:= $\s; C =:= $\t ->
- parse_hd_value(Rest2, S, M, P, Q, F, V, Headers, Name, SoFar);
+ parse_hd_value(Rest2, S, M, P, Q, V, Headers, Name, SoFar);
<< $\n, Rest2/bits >> ->
- parse_header(Rest2, S, M, P, Q, F, V, [{Name, SoFar}|Headers])
+ parse_header(Rest2, S, M, P, Q, V, [{Name, SoFar}|Headers])
end;
-parse_hd_value(<< C, Rest/bits >>, S, M, P, Q, F, V, H, N, SoFar) ->
- parse_hd_value(Rest, S, M, P, Q, F, V, H, N, << SoFar/binary, C >>);
+parse_hd_value(<< C, Rest/bits >>, S, M, P, Q, V, H, N, SoFar) ->
+ parse_hd_value(Rest, S, M, P, Q, V, H, N, << SoFar/binary, C >>);
parse_hd_value(<<>>, State=#state{max_header_value_length=MaxLength},
- _, _, _, _, _, _, _, SoFar) when byte_size(SoFar) > MaxLength ->
+ _, _, _, _, _, _, SoFar) when byte_size(SoFar) > MaxLength ->
error_terminate(400, State);
-parse_hd_value(<<>>, S, M, P, Q, F, V, H, N, SoFar) ->
- wait_hd_value(<<>>, S, M, P, Q, F, V, H, N, SoFar).
+parse_hd_value(<<>>, S, M, P, Q, V, H, N, SoFar) ->
+ wait_hd_value(<<>>, S, M, P, Q, V, H, N, SoFar).
-request(B, State=#state{transport=Transport}, M, P, Q, F, Version, Headers) ->
+request(B, State=#state{transport=Transport}, M, P, Q, Version, Headers) ->
case lists:keyfind(<<"host">>, 1, Headers) of
- false when Version =:= {1, 1} ->
+ false when Version =:= 'HTTP/1.1' ->
error_terminate(400, State);
false ->
- request(B, State, M, P, Q, F, Version, Headers,
+ request(B, State, M, P, Q, Version, Headers,
<<>>, default_port(Transport:name()));
{_, RawHost} ->
case catch parse_host(RawHost, <<>>) of
{'EXIT', _} ->
error_terminate(400, State);
{Host, undefined} ->
- request(B, State, M, P, Q, F, Version, Headers,
+ request(B, State, M, P, Q, Version, Headers,
Host, default_port(Transport:name()));
{Host, Port} ->
- request(B, State, M, P, Q, F, Version, Headers,
+ request(B, State, M, P, Q, Version, Headers,
Host, Port)
end
end.
@@ -476,11 +485,11 @@ parse_host(<< C, Rest/bits >>, Acc) ->
request(Buffer, State=#state{socket=Socket, transport=Transport,
req_keepalive=ReqKeepalive, max_keepalive=MaxKeepalive,
compress=Compress, onresponse=OnResponse},
- Method, Path, Query, Fragment, Version, Headers, Host, Port) ->
+ Method, Path, Query, Version, Headers, Host, Port) ->
case Transport:peername(Socket) of
{ok, Peer} ->
Req = cowboy_req:new(Socket, Transport, Peer, Method, Path,
- Query, Fragment, Version, Headers, Host, Port, Buffer,
+ Query, Version, Headers, Host, Port, Buffer,
ReqKeepalive < MaxKeepalive, Compress, OnResponse),
onrequest(Req, State);
{error, _} ->
@@ -565,7 +574,7 @@ next_request(Req, State=#state{req_keepalive=Keepalive, timeout=Timeout},
end.
%% Only send an error reply if there is no resp_sent message.
--spec error_terminate(cowboy_http:status(), cowboy_req:req(), #state{}) -> ok.
+-spec error_terminate(cowboy:http_status(), cowboy_req:req(), #state{}) -> ok.
error_terminate(Code, Req, State) ->
receive
{cowboy_req, resp_sent} -> ok
@@ -576,14 +585,14 @@ error_terminate(Code, Req, State) ->
terminate(State).
%% Only send an error reply if there is no resp_sent message.
--spec error_terminate(cowboy_http:status(), #state{}) -> ok.
+-spec error_terminate(cowboy:http_status(), #state{}) -> ok.
error_terminate(Code, State=#state{socket=Socket, transport=Transport,
compress=Compress, onresponse=OnResponse}) ->
receive
{cowboy_req, resp_sent} -> ok
after 0 ->
_ = cowboy_req:reply(Code, cowboy_req:new(Socket, Transport,
- undefined, <<"GET">>, <<>>, <<>>, <<>>, {1, 1}, [], <<>>,
+ undefined, <<"GET">>, <<>>, <<>>, 'HTTP/1.1', [], <<>>,
undefined, <<>>, false, Compress, OnResponse)),
ok
end,
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 5b8157d..093663c 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -41,7 +41,7 @@
-module(cowboy_req).
%% Request API.
--export([new/15]).
+-export([new/14]).
-export([method/1]).
-export([version/1]).
-export([peer/1]).
@@ -54,7 +54,6 @@
-export([qs_val/2]).
-export([qs_val/3]).
-export([qs_vals/1]).
--export([fragment/1]).
-export([host_url/1]).
-export([url/1]).
-export([binding/2]).
@@ -121,20 +120,30 @@
-type cookie_opts() :: [cookie_option()].
-export_type([cookie_opts/0]).
--type resp_body_fun() :: fun((inet:socket(), module()) -> ok).
+-type content_decode_fun() :: fun((binary())
+ -> {ok, binary()}
+ | {error, atom()}).
+-type transfer_decode_fun() :: fun((binary(), any())
+ -> {ok, binary(), binary(), any()}
+ | more | {more, non_neg_integer(), binary(), any()}
+ | {done, non_neg_integer(), binary()}
+ | {done, binary(), non_neg_integer(), binary()}
+ | {error, atom()}).
+
+-type resp_body_fun() :: fun((any(), module()) -> ok).
-type send_chunk_fun() :: fun((iodata()) -> ok | {error, atom()}).
-type resp_chunked_fun() :: fun((send_chunk_fun()) -> ok).
-record(http_req, {
%% Transport.
- socket = undefined :: undefined | inet:socket(),
+ socket = undefined :: any(),
transport = undefined :: undefined | module(),
connection = keepalive :: keepalive | close,
%% Request.
pid = undefined :: pid(),
method = <<"GET">> :: binary(),
- version = {1, 1} :: cowboy_http:version(),
+ version = 'HTTP/1.1' :: cowboy:http_version(),
peer = undefined :: undefined | {inet:ip_address(), inet:port_number()},
host = undefined :: undefined | binary(),
host_info = undefined :: undefined | cowboy_router:tokens(),
@@ -143,30 +152,29 @@
path_info = undefined :: undefined | cowboy_router:tokens(),
qs = undefined :: binary(),
qs_vals = undefined :: undefined | list({binary(), binary() | true}),
- fragment = undefined :: binary(),
bindings = undefined :: undefined | cowboy_router:bindings(),
- headers = [] :: cowboy_http:headers(),
+ headers = [] :: cowboy:http_headers(),
p_headers = [] :: [any()], %% @todo Improve those specs.
cookies = undefined :: undefined | [{binary(), binary()}],
meta = [] :: [{atom(), any()}],
%% Request body.
- body_state = waiting :: waiting | done
- | {stream, non_neg_integer(), fun(), any(), fun()},
+ body_state = waiting :: waiting | done | {stream, non_neg_integer(),
+ transfer_decode_fun(), any(), content_decode_fun()},
multipart = undefined :: undefined | {non_neg_integer(), fun()},
buffer = <<>> :: binary(),
%% Response.
resp_compress = false :: boolean(),
resp_state = waiting :: locked | waiting | chunks | done,
- resp_headers = [] :: cowboy_http:headers(),
+ resp_headers = [] :: cowboy:http_headers(),
resp_body = <<>> :: iodata() | resp_body_fun()
| {non_neg_integer(), resp_body_fun()}
| {chunked, resp_chunked_fun()},
%% Functions.
onresponse = undefined :: undefined | already_called
- | cowboy_protocol:onresponse_fun()
+ | cowboy:onresponse_fun()
}).
-opaque req() :: #http_req{}.
@@ -181,21 +189,21 @@
%%
%% Since we always need to parse the Connection header, we do it
%% in an optimized way and add the parsed value to p_headers' cache.
--spec new(inet:socket(), module(),
+-spec new(any(), module(),
undefined | {inet:ip_address(), inet:port_number()},
- binary(), binary(), binary(), binary(),
- cowboy_http:version(), cowboy_http:headers(), binary(),
+ binary(), binary(), binary(),
+ cowboy:http_version(), cowboy:http_headers(), binary(),
inet:port_number() | undefined, binary(), boolean(), boolean(),
- undefined | cowboy_protocol:onresponse_fun())
+ undefined | cowboy:onresponse_fun())
-> req().
-new(Socket, Transport, Peer, Method, Path, Query, Fragment,
+new(Socket, Transport, Peer, Method, Path, Query,
Version, Headers, Host, Port, Buffer, CanKeepalive,
Compress, OnResponse) ->
Req = #http_req{socket=Socket, transport=Transport, pid=self(), peer=Peer,
- method=Method, path=Path, qs=Query, fragment=Fragment, version=Version,
+ method=Method, path=Path, qs=Query, version=Version,
headers=Headers, host=Host, port=Port, buffer=Buffer,
resp_compress=Compress, onresponse=OnResponse},
- case CanKeepalive and (Version =:= {1, 1}) of
+ case CanKeepalive and (Version =:= 'HTTP/1.1') of
false ->
Req#http_req{connection=close};
true ->
@@ -216,13 +224,13 @@ method(Req) ->
{Req#http_req.method, Req}.
%% @doc Return the HTTP version used for the request.
--spec version(Req) -> {cowboy_http:version(), Req} when Req::req().
+-spec version(Req) -> {cowboy:http_version(), Req} when Req::req().
version(Req) ->
{Req#http_req.version, Req}.
%% @doc Return the peer address and port number of the remote host.
-spec peer(Req)
- -> {undefined | {inet:ip_address(), inet:port_number()}, Req}
+ -> {{inet:ip_address(), inet:port_number()}, Req}
when Req::req().
peer(Req) ->
{Req#http_req.peer, Req}.
@@ -289,11 +297,6 @@ qs_vals(Req=#http_req{qs=RawQs, qs_vals=undefined}) ->
qs_vals(Req=#http_req{qs_vals=QsVals}) ->
{QsVals, Req}.
-%% @doc Return the raw fragment directly taken from the request.
--spec fragment(Req) -> {binary(), Req} when Req::req().
-fragment(Req) ->
- {Req#http_req.fragment, Req}.
-
%% @doc Return the request URL as a binary without the path and query string.
%%
%% The URL includes the scheme, host and port only.
@@ -316,7 +319,7 @@ host_url(Req=#http_req{transport=Transport, host=Host, port=Port}) ->
%% @doc Return the full request URL as a binary.
%%
-%% The URL includes the scheme, host, port, path, query string and fragment.
+%% The URL includes the scheme, host, port, path and query string.
-spec url(Req) -> {undefined | binary(), Req} when Req::req().
url(Req=#http_req{}) ->
{HostURL, Req2} = host_url(Req),
@@ -324,16 +327,12 @@ url(Req=#http_req{}) ->
url(undefined, Req=#http_req{}) ->
{undefined, Req};
-url(HostURL, Req=#http_req{path=Path, qs=QS, fragment=Fragment}) ->
+url(HostURL, Req=#http_req{path=Path, qs=QS}) ->
QS2 = case QS of
<<>> -> <<>>;
_ -> << "?", QS/binary >>
end,
- Fragment2 = case Fragment of
- <<>> -> <<>>;
- _ -> << "#", Fragment/binary >>
- end,
- {<< HostURL/binary, Path/binary, QS2/binary, Fragment2/binary >>, Req}.
+ {<< HostURL/binary, Path/binary, QS2/binary >>, Req}.
%% @equiv binding(Name, Req, undefined)
-spec binding(atom(), Req) -> {binary() | undefined, Req} when Req::req().
@@ -371,7 +370,7 @@ header(Name, Req, Default) ->
end.
%% @doc Return the full list of headers.
--spec headers(Req) -> {cowboy_http:headers(), Req} when Req::req().
+-spec headers(Req) -> {cowboy:http_headers(), Req} when Req::req().
headers(Req) ->
{Req#http_req.headers, Req}.
@@ -480,14 +479,14 @@ parse_header(Name, Req=#http_req{p_headers=PHeaders}, Default, Fun) ->
%% @equiv cookie(Name, Req, undefined)
-spec cookie(binary(), Req)
- -> {binary() | true | undefined, Req} when Req::req().
+ -> {binary() | undefined, Req} when Req::req().
cookie(Name, Req) when is_binary(Name) ->
cookie(Name, Req, undefined).
%% @doc Return the cookie value for the given key, or a default if
%% missing.
-spec cookie(binary(), Req, Default)
- -> {binary() | true | Default, Req} when Req::req(), Default::any().
+ -> {binary() | Default, Req} when Req::req(), Default::any().
cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) ->
case parse_header(<<"cookie">>, Req) of
{ok, undefined, Req2} ->
@@ -502,7 +501,7 @@ cookie(Name, Req, Default) ->
end.
%% @doc Return the full list of cookie values.
--spec cookies(Req) -> {list({binary(), binary() | true}), Req} when Req::req().
+-spec cookies(Req) -> {list({binary(), binary()}), Req} when Req::req().
cookies(Req=#http_req{cookies=undefined}) ->
case parse_header(<<"cookie">>, Req) of
{ok, undefined, Req2} ->
@@ -583,7 +582,7 @@ body_length(Req) ->
%% Content encoding is generally used for compression.
%%
%% Standard encodings can be found in cowboy_http.
--spec init_stream(fun(), any(), fun(), Req)
+-spec init_stream(transfer_decode_fun(), any(), content_decode_fun(), Req)
-> {ok, Req} when Req::req().
init_stream(TransferDecode, TransferState, ContentDecode, Req) ->
{ok, Req#http_req{body_state=
@@ -608,7 +607,7 @@ stream_body(Req) ->
%% for each streamed part, and {done, Req} when it's finished streaming.
%%
%% You can limit the size of the chunks being returned by using the
-%% second argument which is the size in bytes. It defaults to 1000000 bytes.
+%% first argument which is the size in bytes. It defaults to 1000000 bytes.
-spec stream_body(non_neg_integer(), Req) -> {ok, binary(), Req}
| {done, Req} | {error, atom()} when Req::req().
stream_body(MaxLength, Req=#http_req{body_state=waiting, version=Version,
@@ -616,7 +615,7 @@ stream_body(MaxLength, Req=#http_req{body_state=waiting, version=Version,
{ok, ExpectHeader, Req1} = parse_header(<<"expect">>, Req),
case ExpectHeader of
[<<"100-continue">>] ->
- HTTPVer = cowboy_http:version_to_binary(Version),
+ HTTPVer = atom_to_binary(Version, latin1),
Transport:send(Socket,
<< HTTPVer/binary, " ", (status(100))/binary, "\r\n\r\n" >>);
undefined ->
@@ -702,7 +701,7 @@ transfer_decode_done(Length, Rest, Req=#http_req{
headers=Headers3, p_headers=PHeaders3}.
%% @todo Probably needs a Rest.
--spec content_decode(fun(), binary(), Req)
+-spec content_decode(content_decode_fun(), binary(), Req)
-> {ok, binary(), Req} | {error, atom()} when Req::req().
content_decode(ContentDecode, Data, Req) ->
case ContentDecode(Data) of
@@ -787,11 +786,8 @@ body_qs(MaxBodyLength, Req) ->
%% this function returns <em>{headers, Headers}</em> followed by a sequence of
%% <em>{body, Data}</em> tuples and finally <em>end_of_part</em>. When there
%% is no part to parse anymore, <em>eof</em> is returned.
-%%
-%% If the request Content-Type is not a multipart one, <em>{error, badarg}</em>
-%% is returned.
-spec multipart_data(Req)
- -> {headers, cowboy_http:headers(), Req} | {body, binary(), Req}
+ -> {headers, cowboy:http_headers(), Req} | {body, binary(), Req}
| {end_of_part | eof, Req} when Req::req().
multipart_data(Req=#http_req{body_state=waiting}) ->
{ok, {<<"multipart">>, _SubType, Params}, Req2} =
@@ -921,7 +917,7 @@ has_resp_body(#http_req{resp_body={Length, _}}) ->
has_resp_body(#http_req{resp_body=RespBody}) ->
iolist_size(RespBody) > 0.
-%% Remove a header previously set for the response.
+%% @doc Remove a header previously set for the response.
-spec delete_resp_header(binary(), Req)
-> Req when Req::req().
delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) ->
@@ -929,18 +925,18 @@ delete_resp_header(Name, Req=#http_req{resp_headers=RespHeaders}) ->
Req#http_req{resp_headers=RespHeaders2}.
%% @equiv reply(Status, [], [], Req)
--spec reply(cowboy_http:status(), Req) -> {ok, Req} when Req::req().
+-spec reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req().
reply(Status, Req=#http_req{resp_body=Body}) ->
reply(Status, [], Body, Req).
%% @equiv reply(Status, Headers, [], Req)
--spec reply(cowboy_http:status(), cowboy_http:headers(), Req)
+-spec reply(cowboy:http_status(), cowboy:http_headers(), Req)
-> {ok, Req} when Req::req().
reply(Status, Headers, Req=#http_req{resp_body=Body}) ->
reply(Status, Headers, Body, Req).
%% @doc Send a reply to the client.
--spec reply(cowboy_http:status(), cowboy_http:headers(),
+-spec reply(cowboy:http_status(), cowboy:http_headers(),
iodata() | {non_neg_integer() | resp_body_fun()}, Req)
-> {ok, Req} when Req::req().
reply(Status, Headers, Body, Req=#http_req{
@@ -948,20 +944,30 @@ reply(Status, Headers, Body, Req=#http_req{
version=Version, connection=Connection,
method=Method, resp_compress=Compress,
resp_state=waiting, resp_headers=RespHeaders}) ->
- HTTP11Headers = case Version of
- {1, 1} -> [{<<"connection">>, atom_to_connection(Connection)}];
- _ -> []
+ HTTP11Headers = if
+ Transport =/= cowboy_spdy, Version =:= 'HTTP/1.1' ->
+ [{<<"connection">>, atom_to_connection(Connection)}];
+ true ->
+ []
end,
Req3 = case Body of
BodyFun when is_function(BodyFun) ->
%% We stream the response body until we close the connection.
RespConn = close,
- {RespType, Req2} = response(Status, Headers, RespHeaders, [
- {<<"connection">>, <<"close">>},
- {<<"date">>, cowboy_clock:rfc1123()},
- {<<"server">>, <<"Cowboy">>},
- {<<"transfer-encoding">>, <<"identity">>}
- ], <<>>, Req),
+ {RespType, Req2} = if
+ Transport =:= cowboy_spdy ->
+ response(Status, Headers, RespHeaders, [
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>}
+ ], stream, Req);
+ true ->
+ response(Status, Headers, RespHeaders, [
+ {<<"connection">>, <<"close">>},
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>},
+ {<<"transfer-encoding">>, <<"identity">>}
+ ], <<>>, Req)
+ end,
if RespType =/= hook, Method =/= <<"HEAD">> ->
BodyFun(Socket, Transport);
true -> ok
@@ -974,13 +980,12 @@ reply(Status, Headers, Body, Req=#http_req{
ChunkFun = fun(IoData) -> chunk(IoData, Req2) end,
BodyFun(ChunkFun),
%% Terminate the chunked body for HTTP/1.1 only.
- _ = case Version of
- {1, 0} -> ok;
- _ -> Transport:send(Socket, <<"0\r\n\r\n">>)
+ case Version of
+ 'HTTP/1.0' -> Req2;
+ _ -> last_chunk(Req2)
end;
- true -> ok
- end,
- Req2;
+ true -> Req2
+ end;
{ContentLength, BodyFun} ->
%% We stream the response body for ContentLength bytes.
RespConn = response_connection(Headers, Connection),
@@ -988,7 +993,7 @@ reply(Status, Headers, Body, Req=#http_req{
{<<"content-length">>, integer_to_list(ContentLength)},
{<<"date">>, cowboy_clock:rfc1123()},
{<<"server">>, <<"Cowboy">>}
- |HTTP11Headers], <<>>, Req),
+ |HTTP11Headers], stream, Req),
if RespType =/= hook, Method =/= <<"HEAD">> ->
BodyFun(Socket, Transport);
true -> ok
@@ -1005,7 +1010,7 @@ reply(Status, Headers, Body, Req=#http_req{
RespHeaders, HTTP11Headers, Method, iolist_size(Body)),
Req2#http_req{connection=RespConn}
end,
- {ok, Req3#http_req{resp_state=done,resp_headers=[], resp_body= <<>>}}.
+ {ok, Req3#http_req{resp_state=done, resp_headers=[], resp_body= <<>>}}.
reply_may_compress(Status, Headers, Body, Req,
RespHeaders, HTTP11Headers, Method) ->
@@ -1051,13 +1056,13 @@ reply_no_compress(Status, Headers, Body, Req,
Req2.
%% @equiv chunked_reply(Status, [], Req)
--spec chunked_reply(cowboy_http:status(), Req) -> {ok, Req} when Req::req().
+-spec chunked_reply(cowboy:http_status(), Req) -> {ok, Req} when Req::req().
chunked_reply(Status, Req) ->
chunked_reply(Status, [], Req).
%% @doc Initiate the sending of a chunked reply to the client.
%% @see cowboy_req:chunk/2
--spec chunked_reply(cowboy_http:status(), cowboy_http:headers(), Req)
+-spec chunked_reply(cowboy:http_status(), cowboy:http_headers(), Req)
-> {ok, Req} when Req::req().
chunked_reply(Status, Headers, Req) ->
{_, Req2} = chunked_response(Status, Headers, Req),
@@ -1069,18 +1074,34 @@ chunked_reply(Status, Headers, Req) ->
-spec chunk(iodata(), req()) -> ok | {error, atom()}.
chunk(_Data, #http_req{method= <<"HEAD">>}) ->
ok;
-chunk(Data, #http_req{socket=Socket, transport=Transport, version={1, 0}}) ->
+chunk(Data, #http_req{socket=Socket, transport=cowboy_spdy,
+ resp_state=chunks}) ->
+ cowboy_spdy:stream_data(Socket, Data);
+chunk(Data, #http_req{socket=Socket, transport=Transport,
+ resp_state=chunks, version='HTTP/1.0'}) ->
Transport:send(Socket, Data);
-chunk(Data, #http_req{socket=Socket, transport=Transport, resp_state=chunks}) ->
+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">>]).
+%% @doc Finish the chunked reply.
+%% @todo If ever made public, need to send nothing if HEAD.
+-spec last_chunk(Req) -> Req when Req::req().
+last_chunk(Req=#http_req{socket=Socket, transport=cowboy_spdy}) ->
+ _ = cowboy_spdy:stream_close(Socket),
+ Req#http_req{resp_state=done};
+last_chunk(Req=#http_req{socket=Socket, transport=Transport}) ->
+ _ = Transport:send(Socket, <<"0\r\n\r\n">>),
+ Req#http_req{resp_state=done}.
+
%% @doc Send an upgrade reply.
%% @private
--spec upgrade_reply(cowboy_http:status(), cowboy_http:headers(), Req)
+-spec upgrade_reply(cowboy:http_status(), cowboy:http_headers(), Req)
-> {ok, Req} when Req::req().
-upgrade_reply(Status, Headers, Req=#http_req{
- resp_state=waiting, resp_headers=RespHeaders}) ->
+upgrade_reply(Status, Headers, Req=#http_req{transport=Transport,
+ resp_state=waiting, resp_headers=RespHeaders})
+ when Transport =/= cowboy_spdy ->
{_, Req2} = response(Status, Headers, RespHeaders, [
{<<"connection">>, <<"Upgrade">>}
], <<>>, Req),
@@ -1088,7 +1109,7 @@ upgrade_reply(Status, Headers, Req=#http_req{
%% @doc Ensure the response has been sent fully.
%% @private
--spec ensure_response(req(), cowboy_http:status()) -> ok.
+-spec ensure_response(req(), cowboy:http_status()) -> ok.
%% The response has already been fully sent to the client.
ensure_response(#http_req{resp_state=done}, _) ->
ok;
@@ -1100,11 +1121,10 @@ ensure_response(Req=#http_req{resp_state=waiting}, Status) ->
%% Terminate the chunked body for HTTP/1.1 only.
ensure_response(#http_req{method= <<"HEAD">>, resp_state=chunks}, _) ->
ok;
-ensure_response(#http_req{version={1, 0}, resp_state=chunks}, _) ->
+ensure_response(#http_req{version='HTTP/1.0', resp_state=chunks}, _) ->
ok;
-ensure_response(#http_req{socket=Socket, transport=Transport,
- resp_state=chunks}, _) ->
- Transport:send(Socket, <<"0\r\n\r\n">>),
+ensure_response(Req=#http_req{resp_state=chunks}, _) ->
+ _ = last_chunk(Req),
ok.
%% Private setter/getter API.
@@ -1126,7 +1146,6 @@ g(body_state, #http_req{body_state=Ret}) -> Ret;
g(buffer, #http_req{buffer=Ret}) -> Ret;
g(connection, #http_req{connection=Ret}) -> Ret;
g(cookies, #http_req{cookies=Ret}) -> Ret;
-g(fragment, #http_req{fragment=Ret}) -> Ret;
g(headers, #http_req{headers=Ret}) -> Ret;
g(host, #http_req{host=Ret}) -> Ret;
g(host_info, #http_req{host_info=Ret}) -> Ret;
@@ -1157,7 +1176,6 @@ set([{body_state, Val}|Tail], Req) -> set(Tail, Req#http_req{body_state=Val});
set([{buffer, Val}|Tail], Req) -> set(Tail, Req#http_req{buffer=Val});
set([{connection, Val}|Tail], Req) -> set(Tail, Req#http_req{connection=Val});
set([{cookies, Val}|Tail], Req) -> set(Tail, Req#http_req{cookies=Val});
-set([{fragment, Val}|Tail], Req) -> set(Tail, Req#http_req{fragment=Val});
set([{headers, Val}|Tail], Req) -> set(Tail, Req#http_req{headers=Val});
set([{host, Val}|Tail], Req) -> set(Tail, Req#http_req{host=Val});
set([{host_info, Val}|Tail], Req) -> set(Tail, Req#http_req{host_info=Val});
@@ -1216,14 +1234,23 @@ to_list(Req) ->
%% Internal.
--spec chunked_response(cowboy_http:status(), cowboy_http:headers(), Req) ->
+-spec chunked_response(cowboy:http_status(), cowboy:http_headers(), Req) ->
{normal | hook, Req} when Req::req().
chunked_response(Status, Headers, Req=#http_req{
+ transport=cowboy_spdy, resp_state=waiting,
+ resp_headers=RespHeaders}) ->
+ {RespType, Req2} = response(Status, Headers, RespHeaders, [
+ {<<"date">>, cowboy_clock:rfc1123()},
+ {<<"server">>, <<"Cowboy">>}
+ ], stream, Req),
+ {RespType, Req2#http_req{resp_state=chunks,
+ resp_headers=[], resp_body= <<>>}};
+chunked_response(Status, Headers, Req=#http_req{
version=Version, connection=Connection,
resp_state=waiting, resp_headers=RespHeaders}) ->
RespConn = response_connection(Headers, Connection),
HTTP11Headers = case Version of
- {1, 1} -> [
+ 'HTTP/1.1' -> [
{<<"connection">>, atom_to_connection(Connection)},
{<<"transfer-encoding">>, <<"chunked">>}];
_ -> []
@@ -1235,8 +1262,8 @@ chunked_response(Status, Headers, Req=#http_req{
{RespType, Req2#http_req{connection=RespConn, resp_state=chunks,
resp_headers=[], resp_body= <<>>}}.
--spec response(cowboy_http:status(), cowboy_http:headers(),
- cowboy_http:headers(), cowboy_http:headers(), iodata(), Req)
+-spec response(cowboy:http_status(), cowboy:http_headers(),
+ cowboy:http_headers(), cowboy:http_headers(), stream | iodata(), Req)
-> {normal | hook, Req} when Req::req().
response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
socket=Socket, transport=Transport, version=Version,
@@ -1245,22 +1272,32 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
already_called -> Headers;
_ -> response_merge_headers(Headers, RespHeaders, DefaultHeaders)
end,
+ Body2 = case Body of stream -> <<>>; _ -> Body end,
Req2 = case OnResponse of
already_called -> Req;
undefined -> Req;
- OnResponse -> OnResponse(Status, FullHeaders, Body,
- %% Don't call 'onresponse' from the hook itself.
- Req#http_req{resp_headers=[], resp_body= <<>>,
- onresponse=already_called})
+ OnResponse ->
+ OnResponse(Status, FullHeaders, Body2,
+ %% Don't call 'onresponse' from the hook itself.
+ Req#http_req{resp_headers=[], resp_body= <<>>,
+ onresponse=already_called})
end,
ReplyType = case Req2#http_req.resp_state of
+ waiting when Transport =:= cowboy_spdy, Body =:= stream ->
+ cowboy_spdy:stream_reply(Socket, status(Status), FullHeaders),
+ ReqPid ! {?MODULE, resp_sent},
+ normal;
+ waiting when Transport =:= cowboy_spdy ->
+ cowboy_spdy:reply(Socket, status(Status), FullHeaders, Body),
+ ReqPid ! {?MODULE, resp_sent},
+ normal;
waiting ->
- HTTPVer = cowboy_http:version_to_binary(Version),
+ HTTPVer = atom_to_binary(Version, latin1),
StatusLine = << HTTPVer/binary, " ",
(status(Status))/binary, "\r\n" >>,
HeaderLines = [[Key, <<": ">>, Value, <<"\r\n">>]
|| {Key, Value} <- FullHeaders],
- Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body]),
+ Transport:send(Socket, [StatusLine, HeaderLines, <<"\r\n">>, Body2]),
ReqPid ! {?MODULE, resp_sent},
normal;
_ ->
@@ -1268,7 +1305,7 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
end,
{ReplyType, Req2}.
--spec response_connection(cowboy_http:headers(), keepalive | close)
+-spec response_connection(cowboy:http_headers(), keepalive | close)
-> keepalive | close.
response_connection([], Connection) ->
Connection;
@@ -1281,16 +1318,16 @@ response_connection([{Name, Value}|Tail], Connection) ->
response_connection(Tail, Connection)
end.
--spec response_merge_headers(cowboy_http:headers(), cowboy_http:headers(),
- cowboy_http:headers()) -> cowboy_http:headers().
+-spec response_merge_headers(cowboy:http_headers(), cowboy:http_headers(),
+ cowboy:http_headers()) -> cowboy:http_headers().
response_merge_headers(Headers, RespHeaders, DefaultHeaders) ->
Headers2 = [{Key, Value} || {Key, Value} <- Headers],
merge_headers(
merge_headers(Headers2, RespHeaders),
DefaultHeaders).
--spec merge_headers(cowboy_http:headers(), cowboy_http:headers())
- -> cowboy_http:headers().
+-spec merge_headers(cowboy:http_headers(), cowboy:http_headers())
+ -> cowboy:http_headers().
%% Merge headers by prepending the tuples in the second list to the
%% first list. It also handles Set-Cookie properly, which supports
@@ -1377,7 +1414,7 @@ connection_to_atom([<<"close">>|_]) ->
connection_to_atom([_|Tail]) ->
connection_to_atom(Tail).
--spec status(cowboy_http:status()) -> binary().
+-spec status(cowboy:http_status()) -> binary().
status(100) -> <<"100 Continue">>;
status(101) -> <<"101 Switching Protocols">>;
status(102) -> <<"102 Processing">>;
@@ -1444,38 +1481,28 @@ status(B) when is_binary(B) -> B.
url_test() ->
{undefined, _} =
url(#http_req{transport=ranch_tcp, host= <<>>, port= undefined,
- path= <<>>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<>>, qs= <<>>, pid=self()}),
{<<"http://localhost/path">>, _ } =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=80,
- path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"http://localhost:443/path">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=443,
- path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"http://localhost:8080/path">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
- path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"http://localhost:8080/path?dummy=2785">>, _} =
url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
- path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<>>,
- pid=self()}),
- {<<"http://localhost:8080/path?dummy=2785#fragment">>, _} =
- url(#http_req{transport=ranch_tcp, host= <<"localhost">>, port=8080,
- path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<"fragment">>,
- pid=self()}),
+ path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
{<<"https://localhost/path">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=443,
- path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"https://localhost:8443/path">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
- path= <<"/path">>, qs= <<>>, fragment= <<>>, pid=self()}),
+ path= <<"/path">>, qs= <<>>, pid=self()}),
{<<"https://localhost:8443/path?dummy=2785">>, _} =
url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
- path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<>>,
- pid=self()}),
- {<<"https://localhost:8443/path?dummy=2785#fragment">>, _} =
- url(#http_req{transport=ranch_ssl, host= <<"localhost">>, port=8443,
- path= <<"/path">>, qs= <<"dummy=2785">>, fragment= <<"fragment">>,
- pid=self()}),
+ path= <<"/path">>, qs= <<"dummy=2785">>, pid=self()}),
ok.
parse_connection_test_() ->
diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl
index 0913b26..ecbe7bc 100644
--- a/src/cowboy_rest.erl
+++ b/src/cowboy_rest.erl
@@ -45,7 +45,7 @@
language_a :: undefined | binary(),
%% Charset.
- charsets_p = [] :: [{binary(), integer()}],
+ charsets_p = [] :: [binary()],
charset_a :: undefined | binary(),
%% Whether the resource exists.
@@ -412,8 +412,7 @@ charsets_provided(Req, State) ->
cowboy_req:parse_header(<<"accept-charset">>, Req2),
case AcceptCharset of
undefined ->
- set_content_type(Req3, State2#state{
- charset_a=element(1, hd(CP))});
+ set_content_type(Req3, State2#state{charset_a=hd(CP)});
AcceptCharset ->
AcceptCharset2 = prioritize_charsets(AcceptCharset),
choose_charset(Req3, State2, AcceptCharset2)
@@ -433,7 +432,11 @@ prioritize_charsets(AcceptCharsets) ->
end, AcceptCharsets),
case lists:keymember(<<"*">>, 1, AcceptCharsets2) of
true -> AcceptCharsets2;
- false -> [{<<"iso-8859-1">>, 1000}|AcceptCharsets2]
+ false ->
+ case lists:keymember(<<"iso-8859-1">>, 1, AcceptCharsets2) of
+ true -> AcceptCharsets2;
+ false -> [{<<"iso-8859-1">>, 1000}|AcceptCharsets2]
+ end
end.
choose_charset(Req, State, []) ->
@@ -443,7 +446,7 @@ choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) ->
match_charset(Req, State, Accept, [], _Charset) ->
choose_charset(Req, State, Accept);
-match_charset(Req, State, _Accept, [{Provided, _}|_], {Provided, _}) ->
+match_charset(Req, State, _Accept, [Provided|_], {Provided, _}) ->
set_content_type(Req, State#state{charset_a=Provided});
match_charset(Req, State, Accept, [_|Tail], Charset) ->
match_charset(Req, State, Accept, Tail, Charset).
@@ -848,7 +851,7 @@ process_content_type(Req, State=#state{method=Method,
{false, Req2, HandlerState2} ->
State2 = State#state{handler_state=HandlerState2},
respond(Req2, State2, 422);
- {ResURL, Req2, HandlerState2} when Method =:= <<"POST">> ->
+ {{true, ResURL}, Req2, HandlerState2} when Method =:= <<"POST">> ->
State2 = State#state{handler_state=HandlerState2},
Req3 = cowboy_req:set_resp_header(
<<"location">>, ResURL, Req2),
diff --git a/src/cowboy_spdy.erl b/src/cowboy_spdy.erl
new file mode 100644
index 0000000..ba02706
--- /dev/null
+++ b/src/cowboy_spdy.erl
@@ -0,0 +1,544 @@
+%% Copyright (c) 2013, Loïc Hoguin <[email protected]>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% @doc SPDY protocol handler.
+%%
+%% The available options are:
+%% <dl>
+%% </dl>
+%%
+%% Note that there is no need to monitor these processes when using Cowboy as
+%% an application as it already supervises them under the listener supervisor.
+-module(cowboy_spdy).
+
+%% API.
+-export([start_link/4]).
+
+%% Internal.
+-export([init/5]).
+-export([system_continue/3]).
+-export([system_terminate/4]).
+-export([system_code_change/4]).
+
+%% Internal request process.
+-export([request_init/9]).
+-export([resume/5]).
+-export([reply/4]).
+-export([stream_reply/3]).
+-export([stream_data/2]).
+-export([stream_close/1]).
+
+%% Internal transport functions.
+-export([name/0]).
+-export([send/2]).
+
+-record(child, {
+ streamid :: non_neg_integer(),
+ pid :: pid(),
+ input = nofin :: fin | nofin,
+ output = nofin :: fin | nofin
+}).
+
+-record(state, {
+ parent = undefined :: pid(),
+ socket,
+ transport,
+ buffer = <<>> :: binary(),
+ middlewares,
+ env,
+ onrequest,
+ onresponse,
+ peer,
+ zdef,
+ zinf,
+ last_streamid = 0 :: non_neg_integer(),
+ children = [] :: [#child{}]
+}).
+
+-record(special_headers, {
+ method,
+ path,
+ version,
+ host,
+ scheme %% @todo We don't use it.
+}).
+
+-type opts() :: [].
+-export_type([opts/0]).
+
+-include("cowboy_spdy.hrl").
+
+%% API.
+
+%% @doc Start a SPDY protocol process.
+-spec start_link(any(), inet:socket(), module(), any()) -> {ok, pid()}.
+start_link(Ref, Socket, Transport, Opts) ->
+ proc_lib:start_link(?MODULE, init,
+ [self(), Ref, Socket, Transport, Opts]).
+
+%% Internal.
+
+%% @doc Faster alternative to proplists:get_value/3.
+%% @private
+get_value(Key, Opts, Default) ->
+ case lists:keyfind(Key, 1, Opts) of
+ {_, Value} -> Value;
+ _ -> Default
+ end.
+
+%% @private
+-spec init(pid(), ranch:ref(), inet:socket(), module(), opts()) -> ok.
+init(Parent, Ref, Socket, Transport, Opts) ->
+ process_flag(trap_exit, true),
+ ok = proc_lib:init_ack(Parent, {ok, self()}),
+ {ok, Peer} = Transport:peername(Socket),
+ Middlewares = get_value(middlewares, Opts, [cowboy_router, cowboy_handler]),
+ Env = [{listener, Ref}|get_value(env, Opts, [])],
+ OnRequest = get_value(onrequest, Opts, undefined),
+ OnResponse = get_value(onresponse, Opts, undefined),
+ Zdef = zlib:open(),
+ ok = zlib:deflateInit(Zdef),
+ _ = zlib:deflateSetDictionary(Zdef, ?ZDICT),
+ Zinf = zlib:open(),
+ ok = zlib:inflateInit(Zinf),
+ ok = ranch:accept_ack(Ref),
+ loop(#state{parent=Parent, socket=Socket, transport=Transport,
+ middlewares=Middlewares, env=Env, onrequest=OnRequest,
+ onresponse=OnResponse, peer=Peer, zdef=Zdef, zinf=Zinf}).
+
+loop(State=#state{parent=Parent, socket=Socket, transport=Transport,
+ buffer=Buffer, children=Children}) ->
+ {OK, Closed, Error} = Transport:messages(),
+ Transport:setopts(Socket, [{active, once}]),
+ receive
+ {OK, Socket, Data} ->
+ Data2 = << Buffer/binary, Data/binary >>,
+ case Data2 of
+ << _:40, Length:24, _/bits >>
+ when byte_size(Data2) >= Length + 8 ->
+ Length2 = Length + 8,
+ << Frame:Length2/binary, Rest/bits >> = Data2,
+ control_frame(State#state{buffer=Rest}, Frame);
+ Rest ->
+ loop(State#state{buffer=Rest})
+ end;
+ {Closed, Socket} ->
+ terminate(State);
+ {Error, Socket, _Reason} ->
+ terminate(State);
+ {reply, {Pid, StreamID}, Status, Headers}
+ when Pid =:= self() ->
+ Child = #child{output=nofin} = lists:keyfind(StreamID,
+ #child.streamid, Children),
+ syn_reply(State, fin, StreamID, Status, Headers),
+ Children2 = lists:keyreplace(StreamID,
+ #child.streamid, Children, Child#child{output=fin}),
+ loop(State#state{children=Children2});
+ {reply, {Pid, StreamID}, Status, Headers, Body}
+ when Pid =:= self() ->
+ Child = #child{output=nofin} = lists:keyfind(StreamID,
+ #child.streamid, Children),
+ syn_reply(State, nofin, StreamID, Status, Headers),
+ data(State, fin, StreamID, Body),
+ Children2 = lists:keyreplace(StreamID,
+ #child.streamid, Children, Child#child{output=fin}),
+ loop(State#state{children=Children2});
+ {stream_reply, {Pid, StreamID}, Status, Headers}
+ when Pid =:= self() ->
+ #child{output=nofin} = lists:keyfind(StreamID,
+ #child.streamid, Children),
+ syn_reply(State, nofin, StreamID, Status, Headers),
+ loop(State);
+ {stream_data, {Pid, StreamID}, Data}
+ when Pid =:= self() ->
+ #child{output=nofin} = lists:keyfind(StreamID,
+ #child.streamid, Children),
+ data(State, nofin, StreamID, Data),
+ loop(State);
+ {stream_close, {Pid, StreamID}}
+ when Pid =:= self() ->
+ Child = #child{output=nofin} = lists:keyfind(StreamID,
+ #child.streamid, Children),
+ data(State, fin, StreamID),
+ Children2 = lists:keyreplace(StreamID,
+ #child.streamid, Children, Child#child{output=fin}),
+ loop(State#state{children=Children2});
+ {'EXIT', Parent, Reason} ->
+ exit(Reason);
+ {'EXIT', Pid, _} ->
+ Children2 = lists:keydelete(Pid, #child.pid, Children),
+ loop(State#state{children=Children2});
+ {system, From, Request} ->
+ sys:handle_system_msg(Request, From, Parent, ?MODULE, [], State);
+ %% Calls from the supervisor module.
+ {'$gen_call', {To, Tag}, which_children} ->
+ Children = [{?MODULE, Pid, worker, [?MODULE]}
+ || #child{pid=Pid} <- Children],
+ To ! {Tag, Children},
+ loop(State);
+ {'$gen_call', {To, Tag}, count_children} ->
+ NbChildren = length(Children),
+ Counts = [{specs, 1}, {active, NbChildren},
+ {supervisors, 0}, {workers, NbChildren}],
+ To ! {Tag, Counts},
+ loop(State);
+ {'$gen_call', {To, Tag}, _} ->
+ To ! {Tag, {error, ?MODULE}},
+ loop(State)
+ after 60000 ->
+ goaway(State, ok),
+ terminate(State)
+ end.
+
+system_continue(_, _, State) ->
+ loop(State).
+
+-spec system_terminate(any(), _, _, _) -> no_return().
+system_terminate(Reason, _, _, _) ->
+ exit(Reason).
+
+system_code_change(Misc, _, _, _) ->
+ {ok, Misc}.
+
+%% We do not support SYN_STREAM with FLAG_UNIDIRECTIONAL set.
+control_frame(State, << _:38, 1:1, _:26, StreamID:31, _/bits >>) ->
+ rst_stream(State, StreamID, internal_error),
+ loop(State);
+%% We do not support Associated-To-Stream-ID and CREDENTIAL Slot.
+control_frame(State, << _:65, StreamID:31, _:1, AssocToStreamID:31,
+ _:8, Slot:8, _/bits >>) when AssocToStreamID =/= 0; Slot =/= 0 ->
+ rst_stream(State, StreamID, internal_error),
+ loop(State);
+%% SYN_STREAM
+%%
+%% Erlang does not allow us to control the priority of processes
+%% so we ignore that value entirely.
+control_frame(State=#state{middlewares=Middlewares, env=Env,
+ onrequest=OnRequest, onresponse=OnResponse, peer=Peer,
+ zinf=Zinf, children=Children},
+ << 1:1, 3:15, 1:16, Flags:8, _:25, StreamID:31,
+ _:32, _Priority:3, _:13, Rest/bits >>) ->
+ IsFin = case Flags of
+ 1 -> fin;
+ 0 -> nofin
+ end,
+ [<< NbHeaders:32, Rest2/bits >>] = try
+ zlib:inflate(Zinf, Rest)
+ catch _:_ ->
+ ok = zlib:inflateSetDictionary(Zinf, ?ZDICT),
+ zlib:inflate(Zinf, <<>>)
+ end,
+ case syn_stream_headers(Rest2, NbHeaders, [], #special_headers{}) of
+ {ok, Headers, Special} ->
+ Pid = spawn_link(?MODULE, request_init,
+ [self(), StreamID, Peer, Headers,
+ OnRequest, OnResponse, Env, Middlewares, Special]),
+ loop(State#state{last_streamid=StreamID,
+ children=[#child{streamid=StreamID, pid=Pid,
+ input=IsFin, output=nofin}|Children]});
+ {error, special} ->
+ rst_stream(State, StreamID, protocol_error),
+ loop(State#state{last_streamid=StreamID})
+ end;
+%% SYN_REPLY
+control_frame(State, << 1:1, 3:15, 2:16, _/bits >>) ->
+ error_logger:error_msg("Ignored SYN_REPLY control frame~n"),
+ loop(State);
+%% RST_STREAM
+control_frame(State, << 1:1, 3:15, 3:16, _Flags:8, _Length:24,
+ _:1, _StreamID:31, StatusCode:32 >>) ->
+ Status = case StatusCode of
+ 1 -> protocol_error;
+ 2 -> invalid_stream;
+ 3 -> refused_stream;
+ 4 -> unsupported_version;
+ 5 -> cancel;
+ 6 -> internal_error;
+ 7 -> flow_control_error;
+ 8 -> stream_in_use;
+ 9 -> stream_already_closed;
+ 10 -> invalid_credentials;
+ 11 -> frame_too_large
+ end,
+ error_logger:error_msg("Received RST_STREAM control frame: ~p~n", [Status]),
+ %% @todo Stop StreamID.
+ loop(State);
+%% SETTINGS
+control_frame(State, << 1:1, 3:15, 4:16, 0:8, _:24,
+ NbEntries:32, Rest/bits >>) ->
+ Settings = [begin
+ Name = case ID of
+ 1 -> upload_bandwidth;
+ 2 -> download_bandwidth;
+ 3 -> round_trip_time;
+ 4 -> max_concurrent_streams;
+ 5 -> current_cwnd;
+ 6 -> download_retrans_rate;
+ 7 -> initial_window_size;
+ 8 -> client_certificate_vector_size
+ end,
+ {Flags, Name, Value}
+ end || << Flags:8, ID:24, Value:32 >> <= Rest],
+ if
+ NbEntries =/= length(Settings) ->
+ goaway(State, protocol_error),
+ terminate(State);
+ true ->
+ error_logger:error_msg("Ignored SETTINGS control frame: ~p~n",
+ [Settings]),
+ loop(State)
+ end;
+%% PING initiated by the server; ignore, we don't send any
+control_frame(State, << 1:1, 3:15, 6:16, 0:8, 4:24, PingID:32 >>)
+ when PingID rem 2 =:= 0 ->
+ error_logger:error_msg("Ignored PING control frame: ~p~n", [PingID]),
+ loop(State);
+%% PING initiated by the client; send it back
+control_frame(State=#state{socket=Socket, transport=Transport},
+ Data = << 1:1, 3:15, 6:16, 0:8, 4:24, _:32 >>) ->
+ Transport:send(Socket, Data),
+ loop(State);
+%% GOAWAY
+control_frame(State, << 1:1, 3:15, 7:16, _/bits >>) ->
+ error_logger:error_msg("Ignored GOAWAY control frame~n"),
+ loop(State);
+%% HEADERS
+control_frame(State, << 1:1, 3:15, 8:16, _/bits >>) ->
+ error_logger:error_msg("Ignored HEADERS control frame~n"),
+ loop(State);
+%% WINDOW_UPDATE
+control_frame(State, << 1:1, 3:15, 9:16, 0:8, _/bits >>) ->
+ error_logger:error_msg("Ignored WINDOW_UPDATE control frame~n"),
+ loop(State);
+%% CREDENTIAL
+control_frame(State, << 1:1, 3:15, 10:16, _/bits >>) ->
+ error_logger:error_msg("Ignored CREDENTIAL control frame~n"),
+ loop(State);
+%% ???
+control_frame(State, _) ->
+ goaway(State, protocol_error),
+ terminate(State).
+
+%% @todo We must wait for the children to finish here,
+%% but only up to N milliseconds. Then we shutdown.
+terminate(_State) ->
+ ok.
+
+syn_stream_headers(<<>>, 0, Acc, Special=#special_headers{
+ method=Method, path=Path, version=Version, host=Host, scheme=Scheme}) ->
+ if
+ Method =:= undefined; Path =:= undefined; Version =:= undefined;
+ Host =:= undefined; Scheme =:= undefined ->
+ {error, special};
+ true ->
+ {ok, lists:reverse(Acc), Special}
+ end;
+syn_stream_headers(<< NameLen:32, Rest/bits >>, NbHeaders, Acc, Special) ->
+ << Name:NameLen/binary, ValueLen:32, Rest2/bits >> = Rest,
+ << Value:ValueLen/binary, Rest3/bits >> = Rest2,
+ case Name of
+ <<":host">> ->
+ syn_stream_headers(Rest3, NbHeaders - 1,
+ [{<<"host">>, Value}|Acc],
+ Special#special_headers{host=Value});
+ <<":method">> ->
+ syn_stream_headers(Rest3, NbHeaders - 1, Acc,
+ Special#special_headers{method=Value});
+ <<":path">> ->
+ syn_stream_headers(Rest3, NbHeaders - 1, Acc,
+ Special#special_headers{path=Value});
+ <<":version">> ->
+ syn_stream_headers(Rest3, NbHeaders - 1, Acc,
+ Special#special_headers{version=Value});
+ <<":scheme">> ->
+ syn_stream_headers(Rest3, NbHeaders - 1, Acc,
+ Special#special_headers{scheme=Value});
+ _ ->
+ syn_stream_headers(Rest3, NbHeaders - 1,
+ [{Name, Value}|Acc], Special)
+ end.
+
+syn_reply(#state{socket=Socket, transport=Transport, zdef=Zdef},
+ IsFin, StreamID, Status, Headers) ->
+ Headers2 = [{<<":status">>, Status},
+ {<<":version">>, <<"HTTP/1.1">>}|Headers],
+ NbHeaders = length(Headers2),
+ HeaderBlock = [begin
+ NameLen = byte_size(Name),
+ ValueLen = iolist_size(Value),
+ [<< NameLen:32, Name/binary, ValueLen:32 >>, Value]
+ end || {Name, Value} <- Headers2],
+ HeaderBlock2 = [<< NbHeaders:32 >>, HeaderBlock],
+ HeaderBlock3 = zlib:deflate(Zdef, HeaderBlock2, full),
+ Flags = case IsFin of
+ fin -> 1;
+ nofin -> 0
+ end,
+ Len = 4 + iolist_size(HeaderBlock3),
+ Transport:send(Socket, [
+ << 1:1, 3:15, 2:16, Flags:8, Len:24, 0:1, StreamID:31 >>,
+ HeaderBlock3]).
+
+rst_stream(#state{socket=Socket, transport=Transport}, StreamID, Status) ->
+ StatusCode = case Status of
+ protocol_error -> 1;
+%% invalid_stream -> 2;
+%% refused_stream -> 3;
+%% unsupported_version -> 4;
+%% cancel -> 5;
+ internal_error -> 6
+%% flow_control_error -> 7;
+%% stream_in_use -> 8;
+%% stream_already_closed -> 9;
+%% invalid_credentials -> 10;
+%% frame_too_large -> 11
+ end,
+ Transport:send(Socket, << 1:1, 3:15, 3:16, 0:8, 8:24,
+ 0:1, StreamID:31, StatusCode:32 >>).
+
+goaway(#state{socket=Socket, transport=Transport, last_streamid=LastStreamID},
+ Status) ->
+ StatusCode = case Status of
+ ok -> 0;
+ protocol_error -> 1
+%% internal_error -> 2
+ end,
+ Transport:send(Socket, << 1:1, 3:15, 7:16, 0:8, 8:24,
+ 0:1, LastStreamID:31, StatusCode:32 >>).
+
+data(#state{socket=Socket, transport=Transport}, fin, StreamID) ->
+ Transport:send(Socket, << 0:1, StreamID:31, 1:8, 0:24 >>).
+
+data(#state{socket=Socket, transport=Transport}, IsFin, StreamID, Data) ->
+ Flags = case IsFin of
+ fin -> 1;
+ nofin -> 0
+ end,
+ Len = iolist_size(Data),
+ Transport:send(Socket, [
+ << 0:1, StreamID:31, Flags:8, Len:24 >>,
+ Data]).
+
+%% Request process.
+
+request_init(Parent, StreamID, Peer,
+ Headers, OnRequest, OnResponse, Env, Middlewares,
+ #special_headers{method=Method, path=Path, version=Version,
+ host=Host}) ->
+ Version2 = parse_version(Version),
+ {Host2, Port} = cowboy_protocol:parse_host(Host, <<>>),
+ {Path2, Query} = parse_path(Path, <<>>),
+ Req = cowboy_req:new({Parent, StreamID}, ?MODULE, Peer,
+ Method, Path2, Query, Version2, Headers,
+ Host2, Port, <<>>, true, false, OnResponse),
+ case OnRequest of
+ undefined ->
+ execute(Req, Env, Middlewares);
+ _ ->
+ Req2 = OnRequest(Req),
+ case cowboy_req:get(resp_state, Req2) of
+ waiting -> execute(Req2, Env, Middlewares);
+ _ -> ok
+ end
+ end.
+
+parse_version(<<"HTTP/1.1">>) ->
+ 'HTTP/1.1';
+parse_version(<<"HTTP/1.0">>) ->
+ 'HTTP/1.0'.
+
+parse_path(<<>>, Path) ->
+ {Path, <<>>};
+parse_path(<< $?, Rest/binary >>, Path) ->
+ parse_query(Rest, Path, <<>>);
+parse_path(<< C, Rest/binary >>, SoFar) ->
+ parse_path(Rest, << SoFar/binary, C >>).
+
+parse_query(<<>>, Path, Query) ->
+ {Path, Query};
+parse_query(<< C, Rest/binary >>, Path, SoFar) ->
+ parse_query(Rest, Path, << SoFar/binary, C >>).
+
+-spec execute(cowboy_req:req(), cowboy_middleware:env(), [module()])
+ -> ok.
+execute(Req, _, []) ->
+ cowboy_req:ensure_response(Req, 204);
+execute(Req, Env, [Middleware|Tail]) ->
+ case Middleware:execute(Req, Env) of
+ {ok, Req2, Env2} ->
+ execute(Req2, Env2, Tail);
+ {suspend, Module, Function, Args} ->
+ erlang:hibernate(?MODULE, resume,
+ [Env, Tail, Module, Function, Args]);
+ {halt, Req2} ->
+ cowboy_req:ensure_response(Req2, 204);
+ {error, Code, Req2} ->
+ error_terminate(Code, Req2)
+ end.
+
+%% @private
+-spec resume(cowboy_middleware:env(), [module()],
+ module(), module(), [any()]) -> ok.
+resume(Env, Tail, Module, Function, Args) ->
+ case apply(Module, Function, Args) of
+ {ok, Req2, Env2} ->
+ execute(Req2, Env2, Tail);
+ {suspend, Module2, Function2, Args2} ->
+ erlang:hibernate(?MODULE, resume,
+ [Env, Tail, Module2, Function2, Args2]);
+ {halt, Req2} ->
+ cowboy_req:ensure_response(Req2, 204);
+ {error, Code, Req2} ->
+ error_terminate(Code, Req2)
+ end.
+
+%% Only send an error reply if there is no resp_sent message.
+-spec error_terminate(cowboy:http_status(), cowboy_req:req()) -> ok.
+error_terminate(Code, Req) ->
+ receive
+ {cowboy_req, resp_sent} -> ok
+ after 0 ->
+ _ = cowboy_req:reply(Code, Req),
+ ok
+ end.
+
+%% Reply functions used by cowboy_req.
+
+reply(Socket = {Pid, _}, Status, Headers, Body) ->
+ _ = case iolist_size(Body) of
+ 0 -> Pid ! {reply, Socket, Status, Headers};
+ _ -> Pid ! {reply, Socket, Status, Headers, Body}
+ end,
+ ok.
+
+stream_reply(Socket = {Pid, _}, Status, Headers) ->
+ _ = Pid ! {stream_reply, Socket, Status, Headers},
+ ok.
+
+stream_data(Socket = {Pid, _}, Data) ->
+ _ = Pid ! {stream_data, Socket, Data},
+ ok.
+
+stream_close(Socket = {Pid, _}) ->
+ _ = Pid ! {stream_close, Socket},
+ ok.
+
+%% Internal transport functions.
+%% @todo recv, sendfile
+
+name() ->
+ spdy.
+
+send(Socket, Data) ->
+ stream_data(Socket, Data).
diff --git a/src/cowboy_spdy.hrl b/src/cowboy_spdy.hrl
new file mode 100644
index 0000000..9637b1c
--- /dev/null
+++ b/src/cowboy_spdy.hrl
@@ -0,0 +1,181 @@
+%% Zlib dictionary.
+
+-define(ZDICT, <<
+ 16#00, 16#00, 16#00, 16#07, 16#6f, 16#70, 16#74, 16#69,
+ 16#6f, 16#6e, 16#73, 16#00, 16#00, 16#00, 16#04, 16#68,
+ 16#65, 16#61, 16#64, 16#00, 16#00, 16#00, 16#04, 16#70,
+ 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#03, 16#70,
+ 16#75, 16#74, 16#00, 16#00, 16#00, 16#06, 16#64, 16#65,
+ 16#6c, 16#65, 16#74, 16#65, 16#00, 16#00, 16#00, 16#05,
+ 16#74, 16#72, 16#61, 16#63, 16#65, 16#00, 16#00, 16#00,
+ 16#06, 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#00,
+ 16#00, 16#00, 16#0e, 16#61, 16#63, 16#63, 16#65, 16#70,
+ 16#74, 16#2d, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65,
+ 16#74, 16#00, 16#00, 16#00, 16#0f, 16#61, 16#63, 16#63,
+ 16#65, 16#70, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f,
+ 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#0f,
+ 16#61, 16#63, 16#63, 16#65, 16#70, 16#74, 16#2d, 16#6c,
+ 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65, 16#00,
+ 16#00, 16#00, 16#0d, 16#61, 16#63, 16#63, 16#65, 16#70,
+ 16#74, 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#73,
+ 16#00, 16#00, 16#00, 16#03, 16#61, 16#67, 16#65, 16#00,
+ 16#00, 16#00, 16#05, 16#61, 16#6c, 16#6c, 16#6f, 16#77,
+ 16#00, 16#00, 16#00, 16#0d, 16#61, 16#75, 16#74, 16#68,
+ 16#6f, 16#72, 16#69, 16#7a, 16#61, 16#74, 16#69, 16#6f,
+ 16#6e, 16#00, 16#00, 16#00, 16#0d, 16#63, 16#61, 16#63,
+ 16#68, 16#65, 16#2d, 16#63, 16#6f, 16#6e, 16#74, 16#72,
+ 16#6f, 16#6c, 16#00, 16#00, 16#00, 16#0a, 16#63, 16#6f,
+ 16#6e, 16#6e, 16#65, 16#63, 16#74, 16#69, 16#6f, 16#6e,
+ 16#00, 16#00, 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#62, 16#61, 16#73, 16#65,
+ 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#65, 16#6e, 16#63, 16#6f,
+ 16#64, 16#69, 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10,
+ 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d,
+ 16#6c, 16#61, 16#6e, 16#67, 16#75, 16#61, 16#67, 16#65,
+ 16#00, 16#00, 16#00, 16#0e, 16#63, 16#6f, 16#6e, 16#74,
+ 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#65, 16#6e, 16#67,
+ 16#74, 16#68, 16#00, 16#00, 16#00, 16#10, 16#63, 16#6f,
+ 16#6e, 16#74, 16#65, 16#6e, 16#74, 16#2d, 16#6c, 16#6f,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00,
+ 16#00, 16#0b, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e,
+ 16#74, 16#2d, 16#6d, 16#64, 16#35, 16#00, 16#00, 16#00,
+ 16#0d, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e, 16#74,
+ 16#2d, 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00,
+ 16#00, 16#0c, 16#63, 16#6f, 16#6e, 16#74, 16#65, 16#6e,
+ 16#74, 16#2d, 16#74, 16#79, 16#70, 16#65, 16#00, 16#00,
+ 16#00, 16#04, 16#64, 16#61, 16#74, 16#65, 16#00, 16#00,
+ 16#00, 16#04, 16#65, 16#74, 16#61, 16#67, 16#00, 16#00,
+ 16#00, 16#06, 16#65, 16#78, 16#70, 16#65, 16#63, 16#74,
+ 16#00, 16#00, 16#00, 16#07, 16#65, 16#78, 16#70, 16#69,
+ 16#72, 16#65, 16#73, 16#00, 16#00, 16#00, 16#04, 16#66,
+ 16#72, 16#6f, 16#6d, 16#00, 16#00, 16#00, 16#04, 16#68,
+ 16#6f, 16#73, 16#74, 16#00, 16#00, 16#00, 16#08, 16#69,
+ 16#66, 16#2d, 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00,
+ 16#00, 16#00, 16#11, 16#69, 16#66, 16#2d, 16#6d, 16#6f,
+ 16#64, 16#69, 16#66, 16#69, 16#65, 16#64, 16#2d, 16#73,
+ 16#69, 16#6e, 16#63, 16#65, 16#00, 16#00, 16#00, 16#0d,
+ 16#69, 16#66, 16#2d, 16#6e, 16#6f, 16#6e, 16#65, 16#2d,
+ 16#6d, 16#61, 16#74, 16#63, 16#68, 16#00, 16#00, 16#00,
+ 16#08, 16#69, 16#66, 16#2d, 16#72, 16#61, 16#6e, 16#67,
+ 16#65, 16#00, 16#00, 16#00, 16#13, 16#69, 16#66, 16#2d,
+ 16#75, 16#6e, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69,
+ 16#65, 16#64, 16#2d, 16#73, 16#69, 16#6e, 16#63, 16#65,
+ 16#00, 16#00, 16#00, 16#0d, 16#6c, 16#61, 16#73, 16#74,
+ 16#2d, 16#6d, 16#6f, 16#64, 16#69, 16#66, 16#69, 16#65,
+ 16#64, 16#00, 16#00, 16#00, 16#08, 16#6c, 16#6f, 16#63,
+ 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00,
+ 16#0c, 16#6d, 16#61, 16#78, 16#2d, 16#66, 16#6f, 16#72,
+ 16#77, 16#61, 16#72, 16#64, 16#73, 16#00, 16#00, 16#00,
+ 16#06, 16#70, 16#72, 16#61, 16#67, 16#6d, 16#61, 16#00,
+ 16#00, 16#00, 16#12, 16#70, 16#72, 16#6f, 16#78, 16#79,
+ 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e, 16#74,
+ 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00, 16#00,
+ 16#13, 16#70, 16#72, 16#6f, 16#78, 16#79, 16#2d, 16#61,
+ 16#75, 16#74, 16#68, 16#6f, 16#72, 16#69, 16#7a, 16#61,
+ 16#74, 16#69, 16#6f, 16#6e, 16#00, 16#00, 16#00, 16#05,
+ 16#72, 16#61, 16#6e, 16#67, 16#65, 16#00, 16#00, 16#00,
+ 16#07, 16#72, 16#65, 16#66, 16#65, 16#72, 16#65, 16#72,
+ 16#00, 16#00, 16#00, 16#0b, 16#72, 16#65, 16#74, 16#72,
+ 16#79, 16#2d, 16#61, 16#66, 16#74, 16#65, 16#72, 16#00,
+ 16#00, 16#00, 16#06, 16#73, 16#65, 16#72, 16#76, 16#65,
+ 16#72, 16#00, 16#00, 16#00, 16#02, 16#74, 16#65, 16#00,
+ 16#00, 16#00, 16#07, 16#74, 16#72, 16#61, 16#69, 16#6c,
+ 16#65, 16#72, 16#00, 16#00, 16#00, 16#11, 16#74, 16#72,
+ 16#61, 16#6e, 16#73, 16#66, 16#65, 16#72, 16#2d, 16#65,
+ 16#6e, 16#63, 16#6f, 16#64, 16#69, 16#6e, 16#67, 16#00,
+ 16#00, 16#00, 16#07, 16#75, 16#70, 16#67, 16#72, 16#61,
+ 16#64, 16#65, 16#00, 16#00, 16#00, 16#0a, 16#75, 16#73,
+ 16#65, 16#72, 16#2d, 16#61, 16#67, 16#65, 16#6e, 16#74,
+ 16#00, 16#00, 16#00, 16#04, 16#76, 16#61, 16#72, 16#79,
+ 16#00, 16#00, 16#00, 16#03, 16#76, 16#69, 16#61, 16#00,
+ 16#00, 16#00, 16#07, 16#77, 16#61, 16#72, 16#6e, 16#69,
+ 16#6e, 16#67, 16#00, 16#00, 16#00, 16#10, 16#77, 16#77,
+ 16#77, 16#2d, 16#61, 16#75, 16#74, 16#68, 16#65, 16#6e,
+ 16#74, 16#69, 16#63, 16#61, 16#74, 16#65, 16#00, 16#00,
+ 16#00, 16#06, 16#6d, 16#65, 16#74, 16#68, 16#6f, 16#64,
+ 16#00, 16#00, 16#00, 16#03, 16#67, 16#65, 16#74, 16#00,
+ 16#00, 16#00, 16#06, 16#73, 16#74, 16#61, 16#74, 16#75,
+ 16#73, 16#00, 16#00, 16#00, 16#06, 16#32, 16#30, 16#30,
+ 16#20, 16#4f, 16#4b, 16#00, 16#00, 16#00, 16#07, 16#76,
+ 16#65, 16#72, 16#73, 16#69, 16#6f, 16#6e, 16#00, 16#00,
+ 16#00, 16#08, 16#48, 16#54, 16#54, 16#50, 16#2f, 16#31,
+ 16#2e, 16#31, 16#00, 16#00, 16#00, 16#03, 16#75, 16#72,
+ 16#6c, 16#00, 16#00, 16#00, 16#06, 16#70, 16#75, 16#62,
+ 16#6c, 16#69, 16#63, 16#00, 16#00, 16#00, 16#0a, 16#73,
+ 16#65, 16#74, 16#2d, 16#63, 16#6f, 16#6f, 16#6b, 16#69,
+ 16#65, 16#00, 16#00, 16#00, 16#0a, 16#6b, 16#65, 16#65,
+ 16#70, 16#2d, 16#61, 16#6c, 16#69, 16#76, 16#65, 16#00,
+ 16#00, 16#00, 16#06, 16#6f, 16#72, 16#69, 16#67, 16#69,
+ 16#6e, 16#31, 16#30, 16#30, 16#31, 16#30, 16#31, 16#32,
+ 16#30, 16#31, 16#32, 16#30, 16#32, 16#32, 16#30, 16#35,
+ 16#32, 16#30, 16#36, 16#33, 16#30, 16#30, 16#33, 16#30,
+ 16#32, 16#33, 16#30, 16#33, 16#33, 16#30, 16#34, 16#33,
+ 16#30, 16#35, 16#33, 16#30, 16#36, 16#33, 16#30, 16#37,
+ 16#34, 16#30, 16#32, 16#34, 16#30, 16#35, 16#34, 16#30,
+ 16#36, 16#34, 16#30, 16#37, 16#34, 16#30, 16#38, 16#34,
+ 16#30, 16#39, 16#34, 16#31, 16#30, 16#34, 16#31, 16#31,
+ 16#34, 16#31, 16#32, 16#34, 16#31, 16#33, 16#34, 16#31,
+ 16#34, 16#34, 16#31, 16#35, 16#34, 16#31, 16#36, 16#34,
+ 16#31, 16#37, 16#35, 16#30, 16#32, 16#35, 16#30, 16#34,
+ 16#35, 16#30, 16#35, 16#32, 16#30, 16#33, 16#20, 16#4e,
+ 16#6f, 16#6e, 16#2d, 16#41, 16#75, 16#74, 16#68, 16#6f,
+ 16#72, 16#69, 16#74, 16#61, 16#74, 16#69, 16#76, 16#65,
+ 16#20, 16#49, 16#6e, 16#66, 16#6f, 16#72, 16#6d, 16#61,
+ 16#74, 16#69, 16#6f, 16#6e, 16#32, 16#30, 16#34, 16#20,
+ 16#4e, 16#6f, 16#20, 16#43, 16#6f, 16#6e, 16#74, 16#65,
+ 16#6e, 16#74, 16#33, 16#30, 16#31, 16#20, 16#4d, 16#6f,
+ 16#76, 16#65, 16#64, 16#20, 16#50, 16#65, 16#72, 16#6d,
+ 16#61, 16#6e, 16#65, 16#6e, 16#74, 16#6c, 16#79, 16#34,
+ 16#30, 16#30, 16#20, 16#42, 16#61, 16#64, 16#20, 16#52,
+ 16#65, 16#71, 16#75, 16#65, 16#73, 16#74, 16#34, 16#30,
+ 16#31, 16#20, 16#55, 16#6e, 16#61, 16#75, 16#74, 16#68,
+ 16#6f, 16#72, 16#69, 16#7a, 16#65, 16#64, 16#34, 16#30,
+ 16#33, 16#20, 16#46, 16#6f, 16#72, 16#62, 16#69, 16#64,
+ 16#64, 16#65, 16#6e, 16#34, 16#30, 16#34, 16#20, 16#4e,
+ 16#6f, 16#74, 16#20, 16#46, 16#6f, 16#75, 16#6e, 16#64,
+ 16#35, 16#30, 16#30, 16#20, 16#49, 16#6e, 16#74, 16#65,
+ 16#72, 16#6e, 16#61, 16#6c, 16#20, 16#53, 16#65, 16#72,
+ 16#76, 16#65, 16#72, 16#20, 16#45, 16#72, 16#72, 16#6f,
+ 16#72, 16#35, 16#30, 16#31, 16#20, 16#4e, 16#6f, 16#74,
+ 16#20, 16#49, 16#6d, 16#70, 16#6c, 16#65, 16#6d, 16#65,
+ 16#6e, 16#74, 16#65, 16#64, 16#35, 16#30, 16#33, 16#20,
+ 16#53, 16#65, 16#72, 16#76, 16#69, 16#63, 16#65, 16#20,
+ 16#55, 16#6e, 16#61, 16#76, 16#61, 16#69, 16#6c, 16#61,
+ 16#62, 16#6c, 16#65, 16#4a, 16#61, 16#6e, 16#20, 16#46,
+ 16#65, 16#62, 16#20, 16#4d, 16#61, 16#72, 16#20, 16#41,
+ 16#70, 16#72, 16#20, 16#4d, 16#61, 16#79, 16#20, 16#4a,
+ 16#75, 16#6e, 16#20, 16#4a, 16#75, 16#6c, 16#20, 16#41,
+ 16#75, 16#67, 16#20, 16#53, 16#65, 16#70, 16#74, 16#20,
+ 16#4f, 16#63, 16#74, 16#20, 16#4e, 16#6f, 16#76, 16#20,
+ 16#44, 16#65, 16#63, 16#20, 16#30, 16#30, 16#3a, 16#30,
+ 16#30, 16#3a, 16#30, 16#30, 16#20, 16#4d, 16#6f, 16#6e,
+ 16#2c, 16#20, 16#54, 16#75, 16#65, 16#2c, 16#20, 16#57,
+ 16#65, 16#64, 16#2c, 16#20, 16#54, 16#68, 16#75, 16#2c,
+ 16#20, 16#46, 16#72, 16#69, 16#2c, 16#20, 16#53, 16#61,
+ 16#74, 16#2c, 16#20, 16#53, 16#75, 16#6e, 16#2c, 16#20,
+ 16#47, 16#4d, 16#54, 16#63, 16#68, 16#75, 16#6e, 16#6b,
+ 16#65, 16#64, 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f,
+ 16#68, 16#74, 16#6d, 16#6c, 16#2c, 16#69, 16#6d, 16#61,
+ 16#67, 16#65, 16#2f, 16#70, 16#6e, 16#67, 16#2c, 16#69,
+ 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#6a, 16#70, 16#67,
+ 16#2c, 16#69, 16#6d, 16#61, 16#67, 16#65, 16#2f, 16#67,
+ 16#69, 16#66, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78,
+ 16#6d, 16#6c, 16#2c, 16#61, 16#70, 16#70, 16#6c, 16#69,
+ 16#63, 16#61, 16#74, 16#69, 16#6f, 16#6e, 16#2f, 16#78,
+ 16#68, 16#74, 16#6d, 16#6c, 16#2b, 16#78, 16#6d, 16#6c,
+ 16#2c, 16#74, 16#65, 16#78, 16#74, 16#2f, 16#70, 16#6c,
+ 16#61, 16#69, 16#6e, 16#2c, 16#74, 16#65, 16#78, 16#74,
+ 16#2f, 16#6a, 16#61, 16#76, 16#61, 16#73, 16#63, 16#72,
+ 16#69, 16#70, 16#74, 16#2c, 16#70, 16#75, 16#62, 16#6c,
+ 16#69, 16#63, 16#70, 16#72, 16#69, 16#76, 16#61, 16#74,
+ 16#65, 16#6d, 16#61, 16#78, 16#2d, 16#61, 16#67, 16#65,
+ 16#3d, 16#67, 16#7a, 16#69, 16#70, 16#2c, 16#64, 16#65,
+ 16#66, 16#6c, 16#61, 16#74, 16#65, 16#2c, 16#73, 16#64,
+ 16#63, 16#68, 16#63, 16#68, 16#61, 16#72, 16#73, 16#65,
+ 16#74, 16#3d, 16#75, 16#74, 16#66, 16#2d, 16#38, 16#63,
+ 16#68, 16#61, 16#72, 16#73, 16#65, 16#74, 16#3d, 16#69,
+ 16#73, 16#6f, 16#2d, 16#38, 16#38, 16#35, 16#39, 16#2d,
+ 16#31, 16#2c, 16#75, 16#74, 16#66, 16#2d, 16#2c, 16#2a,
+ 16#2c, 16#65, 16#6e, 16#71, 16#3d, 16#30, 16#2e >>).
diff --git a/src/cowboy_sub_protocol.erl b/src/cowboy_sub_protocol.erl
index 0b231d3..26ccd7e 100644
--- a/src/cowboy_sub_protocol.erl
+++ b/src/cowboy_sub_protocol.erl
@@ -31,7 +31,7 @@
-callback upgrade(Req, Env, module(), any())
-> {ok, Req, Env}
- | {suspend, module(), atom(), any()}
+ | {suspend, module(), atom(), [any()]}
| {halt, Req}
- | {error, cowboy_http:status(), Req}
+ | {error, cowboy:http_status(), Req}
when Req::cowboy_req:req(), Env::cowboy_middleware:env().
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index b5075c0..3667797 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -29,8 +29,8 @@
-export_type([close_code/0]).
-type frame() :: close | ping | pong
- | {text | binary | close | ping | pong, binary()}
- | {close, close_code(), binary()}.
+ | {text | binary | close | ping | pong, iodata()}
+ | {close, close_code(), iodata()}.
-export_type([frame/0]).
-type opcode() :: 0 | 1 | 2 | 8 | 9 | 10.