diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/cowboy.app.src | 2 | ||||
-rw-r--r-- | src/cowboy.erl | 39 | ||||
-rw-r--r-- | src/cowboy_client.erl | 18 | ||||
-rw-r--r-- | src/cowboy_handler.erl | 2 | ||||
-rw-r--r-- | src/cowboy_http.erl | 22 | ||||
-rw-r--r-- | src/cowboy_http_handler.erl | 2 | ||||
-rw-r--r-- | src/cowboy_loop_handler.erl | 2 | ||||
-rw-r--r-- | src/cowboy_middleware.erl | 4 | ||||
-rw-r--r-- | src/cowboy_protocol.erl | 205 | ||||
-rw-r--r-- | src/cowboy_req.erl | 253 | ||||
-rw-r--r-- | src/cowboy_rest.erl | 15 | ||||
-rw-r--r-- | src/cowboy_spdy.erl | 544 | ||||
-rw-r--r-- | src/cowboy_spdy.hrl | 181 | ||||
-rw-r--r-- | src/cowboy_sub_protocol.erl | 4 | ||||
-rw-r--r-- | src/cowboy_websocket.erl | 4 |
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. |