diff options
author | Loïc Hoguin <[email protected]> | 2016-03-06 17:48:35 +0100 |
---|---|---|
committer | Loïc Hoguin <[email protected]> | 2016-03-06 17:48:35 +0100 |
commit | 7bdd710849a35c12afe3f91bc5df4006db4c0282 (patch) | |
tree | 17b17bdaa94d529fd254ddbff545865e48214783 | |
parent | b370442a6352c5acb13b88e135c32ca1720095bd (diff) | |
download | cowboy-7bdd710849a35c12afe3f91bc5df4006db4c0282.tar.gz cowboy-7bdd710849a35c12afe3f91bc5df4006db4c0282.tar.bz2 cowboy-7bdd710849a35c12afe3f91bc5df4006db4c0282.zip |
Completely remove SPDY
-rw-r--r-- | README.md | 3 | ||||
-rw-r--r-- | doc/src/guide/broken_clients.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/guide/erlang_web.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/guide/introduction.asciidoc | 2 | ||||
-rw-r--r-- | doc/src/guide/modern_web.asciidoc | 28 | ||||
-rw-r--r-- | doc/src/guide/overview.asciidoc | 8 | ||||
-rw-r--r-- | doc/src/manual/cowboy.asciidoc | 10 | ||||
-rw-r--r-- | doc/src/manual/cowboy_spdy.asciidoc | 42 | ||||
-rw-r--r-- | ebin/cowboy.app | 2 | ||||
-rw-r--r-- | src/cowboy.erl | 4 | ||||
-rw-r--r-- | src/cowboy_req.erl | 25 | ||||
-rw-r--r-- | src/cowboy_spdy.erl | 488 | ||||
-rw-r--r-- | src/cowboy_tls.erl | 3 | ||||
-rw-r--r-- | test/cowboy_test.erl | 21 | ||||
-rw-r--r-- | test/spdy_SUITE.erl | 147 |
15 files changed, 21 insertions, 766 deletions
@@ -24,8 +24,9 @@ Sponsors The project is currently sponsored by [Sameroom](https://sameroom.io). -The SPDY implementation was sponsored by +The original SPDY implementation was sponsored by [LeoFS Cloud Storage](http://leo-project.net/leofs/). +It has since been superseded by HTTP/2. Online documentation -------------------- diff --git a/doc/src/guide/broken_clients.asciidoc b/doc/src/guide/broken_clients.asciidoc index e91e9a2..17bb892 100644 --- a/doc/src/guide/broken_clients.asciidoc +++ b/doc/src/guide/broken_clients.asciidoc @@ -33,7 +33,7 @@ capitalize_hook(Status, Headers, Body, Req) -> cowboy_req:reply(Status, Headers2, Body, Req). ---- -Note that SPDY clients do not have that particular issue +Note that HTTP/2 clients do not have that particular issue because the specification explicitly says all headers are lowercase, unlike HTTP which allows any case but treats them as case insensitive. diff --git a/doc/src/guide/erlang_web.asciidoc b/doc/src/guide/erlang_web.asciidoc index 91a9eca..702e043 100644 --- a/doc/src/guide/erlang_web.asciidoc +++ b/doc/src/guide/erlang_web.asciidoc @@ -106,7 +106,7 @@ new messages, and perform the operations required by only activating the required parts of the system. The more recent Web technologies, like Websocket of course, but also -SPDY and HTTP/2.0, are all fully asynchronous protocols. The concept +HTTP/2.0, are all fully asynchronous protocols. The concept of requests and responses is retained of course, but anything could be sent in between, by both the client or the browser, and the responses could also be received in a completely different order. diff --git a/doc/src/guide/introduction.asciidoc b/doc/src/guide/introduction.asciidoc index 0ffeb91..9cdcbc9 100644 --- a/doc/src/guide/introduction.asciidoc +++ b/doc/src/guide/introduction.asciidoc @@ -4,7 +4,7 @@ Cowboy is a small, fast and modular HTTP server written in Erlang. Cowboy aims to provide a complete HTTP stack, including its derivatives -SPDY, Websocket and REST. Cowboy currently supports HTTP/1.0, HTTP/1.1, +Websocket and REST. Cowboy currently supports HTTP/1.0, HTTP/1.1, HTTP/2, Websocket (all implemented drafts + standard) and Webmachine-based REST. Cowboy is a high quality project. It has a small code base, is very diff --git a/doc/src/guide/modern_web.asciidoc b/doc/src/guide/modern_web.asciidoc index 7dc812b..732972f 100644 --- a/doc/src/guide/modern_web.asciidoc +++ b/doc/src/guide/modern_web.asciidoc @@ -180,35 +180,21 @@ A Websocket connection can be used to transfer any kind of data, small or big, text or binary. Because of this Websocket is sometimes used for communication between systems. -=== SPDY +=== HTTP/2 -SPDY is an attempt to reduce page loading time by opening a +HTTP/2 is an attempt to reduce page loading time by opening a single connection per server, keeping it open for subsequent requests, and also by compressing the HTTP headers to reduce the size of requests. -SPDY is compatible with HTTP/1.1 semantics, and is actually +HTTP/2 is compatible with HTTP/1.1 semantics, and is actually just a different way of performing HTTP requests and responses, by using binary frames instead of a text-based protocol. -SPDY also allows the server to send extra responses following +HTTP/2 also allows the server to send extra responses following a request. This is meant to allow sending the resources associated with the request before the client requests them, saving latency when loading websites. -SPDY is an experiment that has proven successful and is used -as the basis for the HTTP/2.0 standard. - -Browsers make use of TLS Next Protocol Negotiation to upgrade -to a SPDY connection seamlessly if the protocol supports it. - -The protocol itself has a few shortcomings which are being -fixed in HTTP/2.0. - -=== HTTP/2.0 - -HTTP/2.0 is the long-awaited update to the HTTP/1.1 protocol. -It is based on SPDY although a lot has been improved at the -time of writing. - -HTTP/2.0 is an asynchronous two-ways communication channel -between two endpoints. +Browsers make use of TLS Application-Layer Protocol Negotiation +extension to upgrade to an HTTP/2 connection seamlessly if the +server supports it. diff --git a/doc/src/guide/overview.asciidoc b/doc/src/guide/overview.asciidoc index b337e3d..3e5cbb7 100644 --- a/doc/src/guide/overview.asciidoc +++ b/doc/src/guide/overview.asciidoc @@ -55,7 +55,7 @@ HTTP/1.1 allows the client to request that the server keeps the connection alive. This mechanism is described in the next section. -SPDY is designed to allow sending multiple requests +HTTP/2 is designed to allow sending multiple requests asynchronously on the same connection. Details on what this means for your application is described in this chapter. @@ -126,9 +126,9 @@ static files for example. This is handled automatically by the server. -=== Asynchronous requests (SPDY) +=== Asynchronous requests (HTTP/2) -In SPDY, the client can send a request at any time. +In HTTP/2, the client can send a request at any time. And the server can send a response at any time too. This means for example that the client does not need @@ -142,7 +142,7 @@ Cowboy creates a new process for each request, and these processes are managed by another process that handles the connection itself. -SPDY servers may also decide to send resources to the +HTTP/2 servers may also decide to send resources to the client before the client requests them. This is especially useful for sending static files associated with the HTML page requested, as this reduces the latency of the overall diff --git a/doc/src/manual/cowboy.asciidoc b/doc/src/manual/cowboy.asciidoc index cd83b3d..10e3696 100644 --- a/doc/src/manual/cowboy.asciidoc +++ b/doc/src/manual/cowboy.asciidoc @@ -67,16 +67,6 @@ ProtoOpts = cowboy_protocol:opts():: HTTP protocol options. Start listening for HTTPS connections. Returns the pid for this listener's supervisor. -=== start_spdy(Ref, NbAcceptors, TransOpts, ProtoOpts) -> {ok, pid()} - -Ref = ranch:ref():: Listener name. -NbAcceptors = non_neg_integer():: Number of acceptor processes. -TransOpts = ranch_ssl:opts():: SSL transport options. -ProtoOpts = cowboy_spdy:opts():: SPDY protocol options. - -Start listening for SPDY connections. Returns the pid for this -listener's supervisor. - === stop_listener(Ref) -> ok | {error, not_found} Ref = ranch:ref():: Listener name. diff --git a/doc/src/manual/cowboy_spdy.asciidoc b/doc/src/manual/cowboy_spdy.asciidoc deleted file mode 100644 index b0dcb70..0000000 --- a/doc/src/manual/cowboy_spdy.asciidoc +++ /dev/null @@ -1,42 +0,0 @@ -= cowboy_spdy(3) - -== Name - -cowboy_spdy - SPDY protocol - -== Description - -The `cowboy_spdy` module implements SPDY/3 as a Ranch protocol. - -== Types - -=== opts() = [Option] - -[source,erlang] ----- -Option = {env, cowboy_middleware:env()} - | {middlewares, [module()]} - | {onresponse, cowboy:onresponse_fun()} ----- - -Configuration for the SPDY protocol handler. - -This configuration is passed to Cowboy when starting listeners -using the `cowboy:start_spdy/4` function. - -It can be updated without restarting listeners using the -Ranch functions `ranch:get_protocol_options/1` and -`ranch:set_protocol_options/2`. - -== Option descriptions - -The default value is given next to the option name. - -env ([{listener, Ref}]):: - Initial middleware environment. - -middlewares ([cowboy_router, cowboy_handler]):: - List of middlewares to execute for every requests. - -onresponse (undefined):: - Fun called every time a response is sent. diff --git a/ebin/cowboy.app b/ebin/cowboy.app index a450961..f8608c3 100644 --- a/ebin/cowboy.app +++ b/ebin/cowboy.app @@ -1,7 +1,7 @@ {application, cowboy, [ {description, "Small, fast, modular HTTP server."}, {vsn, "2.0.0-pre.2"}, - {modules, ['cowboy','cowboy_app','cowboy_bstr','cowboy_clear','cowboy_clock','cowboy_constraints','cowboy_handler','cowboy_http','cowboy_http2','cowboy_loop','cowboy_middleware','cowboy_protocol','cowboy_req','cowboy_rest','cowboy_router','cowboy_spdy','cowboy_static','cowboy_stream','cowboy_stream_h','cowboy_sub_protocol','cowboy_sup','cowboy_tls','cowboy_websocket']}, + {modules, ['cowboy','cowboy_app','cowboy_bstr','cowboy_clear','cowboy_clock','cowboy_constraints','cowboy_handler','cowboy_http','cowboy_http2','cowboy_loop','cowboy_middleware','cowboy_req','cowboy_rest','cowboy_router','cowboy_static','cowboy_stream','cowboy_stream_h','cowboy_sub_protocol','cowboy_sup','cowboy_tls','cowboy_websocket']}, {registered, [cowboy_sup,cowboy_clock]}, {applications, [kernel,stdlib,crypto,cowlib,ranch]}, {mod, {cowboy_app, []}} diff --git a/src/cowboy.erl b/src/cowboy.erl index 67afd7b..d9c14ea 100644 --- a/src/cowboy.erl +++ b/src/cowboy.erl @@ -52,8 +52,8 @@ start_tls(Ref, NbAcceptors, TransOpts0, ProtoOpts) when is_integer(NbAcceptors), NbAcceptors > 0 -> TransOpts = [ connection_type(ProtoOpts), - {next_protocols_advertised, [<<"h2">>, <<"spdy/3">>, <<"http/1.1">>]}, - {alpn_preferred_protocols, [<<"h2">>, <<"spdy/3">>, <<"http/1.1">>]} + {next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]}, + {alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]} |TransOpts0], ranch:start_listener(Ref, NbAcceptors, ranch_ssl, TransOpts, cowboy_tls, ProtoOpts). diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 16b1fd1..b01a70c 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -769,11 +769,6 @@ response_headers(Headers, Req) -> % %% We stream the response body until we close the connection. % RespConn = close, % {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">>}, @@ -896,9 +891,6 @@ chunk(Data, #{pid := Pid, streamid := StreamID}) -> %% 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}. @@ -1028,15 +1020,6 @@ to_list(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=RespState, resp_headers=RespHeaders}) % when RespState =:= waiting; RespState =:= waiting_stream -> @@ -1094,14 +1077,6 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{ end end, ReplyType = case Req2#http_req.resp_state of - waiting when Transport =:= cowboy_spdy, Body =:= stream -> - cowboy_spdy:stream_reply(Socket, status(Status2), FullHeaders2), - ReqPid ! {?MODULE, resp_sent}, - normal; - waiting when Transport =:= cowboy_spdy -> - cowboy_spdy:reply(Socket, status(Status2), FullHeaders2, Body), - ReqPid ! {?MODULE, resp_sent}, - normal; RespState when RespState =:= waiting; RespState =:= waiting_stream -> HTTPVer = atom_to_binary(Version, latin1), StatusLine = << HTTPVer/binary, " ", diff --git a/src/cowboy_spdy.erl b/src/cowboy_spdy.erl deleted file mode 100644 index 2628ab5..0000000 --- a/src/cowboy_spdy.erl +++ /dev/null @@ -1,488 +0,0 @@ -%% Copyright (c) 2013-2014, 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. - --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/10]). --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([messages/0]). --export([recv/3]). --export([send/2]). --export([sendfile/2]). --export([setopts/2]). - --type streamid() :: non_neg_integer(). --type socket() :: {pid(), streamid()}. - --record(child, { - streamid :: streamid(), - pid :: pid(), - input = nofin :: fin | nofin, - in_buffer = <<>> :: binary(), - is_recv = false :: false | {active, socket(), pid()} - | {passive, socket(), pid(), non_neg_integer(), reference()}, - output = nofin :: fin | nofin -}). - --record(state, { - parent = undefined :: pid(), - socket :: inet:socket(), - transport :: module(), - buffer = <<>> :: binary(), - middlewares :: [module()], - env :: cowboy_middleware:env(), - onresponse :: cowboy:onresponse_fun(), - peer :: {inet:ip_address(), inet:port_number()}, - zdef :: zlib:zstream(), - zinf :: zlib:zstream(), - last_streamid = 0 :: streamid(), - children = [] :: [#child{}] -}). - --type opts() :: [{env, cowboy_middleware:env()} - | {middlewares, [module()]} - | {onresponse, cowboy:onresponse_fun()}]. --export_type([opts/0]). - -%% API. - --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. - -%% Faster alternative to proplists:get_value/3. -get_value(Key, Opts, Default) -> - case lists:keyfind(Key, 1, Opts) of - {_, Value} -> Value; - _ -> Default - end. - --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, [])], - OnResponse = get_value(onresponse, Opts, undefined), - Zdef = cow_spdy:deflate_init(), - Zinf = cow_spdy:inflate_init(), - ok = ranch:accept_ack(Ref), - loop(#state{parent=Parent, socket=Socket, transport=Transport, - middlewares=Middlewares, env=Env, - 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} -> - parse_frame(State, << Buffer/binary, Data/binary >>); - {Closed, Socket} -> - terminate(State); - {Error, Socket, _Reason} -> - terminate(State); - {recv, FromSocket = {Pid, StreamID}, FromPid, Length, Timeout} - when Pid =:= self() -> - Child = #child{in_buffer=InBuffer, is_recv=false} - = get_child(StreamID, State), - if - Length =:= 0, InBuffer =/= <<>> -> - FromPid ! {recv, FromSocket, {ok, InBuffer}}, - loop(replace_child(Child#child{in_buffer= <<>>}, State)); - byte_size(InBuffer) >= Length -> - << Data:Length/binary, Rest/bits >> = InBuffer, - FromPid ! {recv, FromSocket, {ok, Data}}, - loop(replace_child(Child#child{in_buffer=Rest}, State)); - true -> - TRef = erlang:send_after(Timeout, self(), - {recv_timeout, FromSocket}), - loop(replace_child(Child#child{ - is_recv={passive, FromSocket, FromPid, Length, TRef}}, - State)) - end; - {recv_timeout, {Pid, StreamID}} - when Pid =:= self() -> - Child = #child{is_recv={passive, FromSocket, FromPid, _, _}} - = get_child(StreamID, State), - FromPid ! {recv, FromSocket, {error, timeout}}, - loop(replace_child(Child, State)); - {reply, {Pid, StreamID}, Status, Headers} - when Pid =:= self() -> - Child = #child{output=nofin} = get_child(StreamID, State), - syn_reply(State, StreamID, true, Status, Headers), - loop(replace_child(Child#child{output=fin}, State)); - {reply, {Pid, StreamID}, Status, Headers, Body} - when Pid =:= self() -> - Child = #child{output=nofin} = get_child(StreamID, State), - syn_reply(State, StreamID, false, Status, Headers), - data(State, StreamID, true, Body), - loop(replace_child(Child#child{output=fin}, State)); - {stream_reply, {Pid, StreamID}, Status, Headers} - when Pid =:= self() -> - #child{output=nofin} = get_child(StreamID, State), - syn_reply(State, StreamID, false, Status, Headers), - loop(State); - {stream_data, {Pid, StreamID}, Data} - when Pid =:= self() -> - #child{output=nofin} = get_child(StreamID, State), - data(State, StreamID, false, Data), - loop(State); - {stream_close, {Pid, StreamID}} - when Pid =:= self() -> - Child = #child{output=nofin} = get_child(StreamID, State), - data(State, StreamID, true, <<>>), - loop(replace_child(Child#child{output=fin}, State)); - {sendfile, {Pid, StreamID}, Filepath} - when Pid =:= self() -> - Child = #child{output=nofin} = get_child(StreamID, State), - data_from_file(State, StreamID, Filepath), - loop(replace_child(Child#child{output=fin}, State)); - {active, FromSocket = {Pid, StreamID}, FromPid} when Pid =:= self() -> - Child = #child{in_buffer=InBuffer, is_recv=false} - = get_child(StreamID, State), - case InBuffer of - <<>> -> - loop(replace_child(Child#child{ - is_recv={active, FromSocket, FromPid}}, State)); - _ -> - FromPid ! {spdy, FromSocket, InBuffer}, - loop(replace_child(Child#child{in_buffer= <<>>}, State)) - end; - {passive, FromSocket = {Pid, StreamID}, FromPid} when Pid =:= self() -> - Child = #child{is_recv=IsRecv} = get_child(StreamID, State), - %% Make sure we aren't in the middle of a recv call. - case IsRecv of false -> ok; {active, FromSocket, FromPid} -> ok end, - loop(replace_child(Child#child{is_recv=false}, State)); - {'EXIT', Parent, Reason} -> - exit(Reason); - {'EXIT', Pid, _} -> - %% @todo Report the error if any. - loop(delete_child(Pid, State)); - {system, From, Request} -> - sys:handle_system_msg(Request, From, Parent, ?MODULE, [], State); - %% Calls from the supervisor module. - {'$gen_call', {To, Tag}, which_children} -> - Workers = [{?MODULE, Pid, worker, [?MODULE]} - || #child{pid=Pid} <- Children], - To ! {Tag, Workers}, - 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. - --spec system_continue(_, _, #state{}) -> ok. -system_continue(_, _, State) -> - loop(State). - --spec system_terminate(any(), _, _, _) -> no_return(). -system_terminate(Reason, _, _, _) -> - exit(Reason). - --spec system_code_change(Misc, _, _, _) -> {ok, Misc} when Misc::#state{}. -system_code_change(Misc, _, _, _) -> - {ok, Misc}. - -parse_frame(State=#state{zinf=Zinf}, Data) -> - case cow_spdy:split(Data) of - {true, Frame, Rest} -> - P = cow_spdy:parse(Frame, Zinf), - case handle_frame(State#state{buffer = Rest}, P) of - error -> - terminate(State); - State2 -> - parse_frame(State2, Rest) - end; - false -> - loop(State#state{buffer=Data}) - end. - -%% FLAG_UNIDIRECTIONAL can only be set by the server. -handle_frame(State, {syn_stream, StreamID, _, _, true, - _, _, _, _, _, _, _}) -> - rst_stream(State, StreamID, protocol_error), - State; -%% We do not support Associated-To-Stream-ID. -handle_frame(State, {syn_stream, StreamID, AssocToStreamID, - _, _, _, _, _, _, _, _, _}) when AssocToStreamID =/= 0 -> - rst_stream(State, StreamID, internal_error), - State; -%% SYN_STREAM. -%% -%% Erlang does not allow us to control the priority of processes -%% so we ignore that value entirely. -handle_frame(State=#state{middlewares=Middlewares, env=Env, - onresponse=OnResponse, peer=Peer}, - {syn_stream, StreamID, _, IsFin, _, _, - Method, _, Host, Path, Version, Headers}) -> - Pid = spawn_link(?MODULE, request_init, [ - {self(), StreamID}, Peer, OnResponse, - Env, Middlewares, Method, Host, Path, Version, Headers - ]), - new_child(State, StreamID, Pid, IsFin); -%% RST_STREAM. -handle_frame(State, {rst_stream, StreamID, Status}) -> - error_logger:error_msg("Received RST_STREAM frame ~p ~p", - [StreamID, Status]), - %% @todo Stop StreamID. - State; -%% PING initiated by the server; ignore, we don't send any. -handle_frame(State, {ping, PingID}) when PingID rem 2 =:= 0 -> - error_logger:error_msg("Ignored PING control frame: ~p~n", [PingID]), - State; -%% PING initiated by the client; send it back. -handle_frame(State=#state{socket=Socket, transport=Transport}, - {ping, PingID}) -> - Transport:send(Socket, cow_spdy:ping(PingID)), - State; -%% Data received for a stream. -handle_frame(State, {data, StreamID, IsFin, Data}) -> - Child = #child{input=nofin, in_buffer=Buffer, is_recv=IsRecv} - = get_child(StreamID, State), - Data2 = << Buffer/binary, Data/binary >>, - IsFin2 = if IsFin -> fin; true -> nofin end, - Child2 = case IsRecv of - {active, FromSocket, FromPid} -> - FromPid ! {spdy, FromSocket, Data}, - Child#child{input=IsFin2, is_recv=false}; - {passive, FromSocket, FromPid, 0, TRef} -> - FromPid ! {recv, FromSocket, {ok, Data2}}, - cancel_recv_timeout(StreamID, TRef), - Child#child{input=IsFin2, in_buffer= <<>>, is_recv=false}; - {passive, FromSocket, FromPid, Length, TRef} - when byte_size(Data2) >= Length -> - << Data3:Length/binary, Rest/bits >> = Data2, - FromPid ! {recv, FromSocket, {ok, Data3}}, - cancel_recv_timeout(StreamID, TRef), - Child#child{input=IsFin2, in_buffer=Rest, is_recv=false}; - _ -> - Child#child{input=IsFin2, in_buffer=Data2} - end, - replace_child(Child2, State); -%% General error, can't recover. -handle_frame(State, {error, badprotocol}) -> - goaway(State, protocol_error), - error; -%% Ignore all other frames for now. -handle_frame(State, Frame) -> - error_logger:error_msg("Ignored frame ~p", [Frame]), - State. - -cancel_recv_timeout(StreamID, TRef) -> - _ = erlang:cancel_timer(TRef), - receive - {recv_timeout, {Pid, StreamID}} - when Pid =:= self() -> - ok - after 0 -> - ok - end. - -%% @todo We must wait for the children to finish here, -%% but only up to N milliseconds. Then we shutdown. -terminate(_State) -> - ok. - -syn_reply(#state{socket=Socket, transport=Transport, zdef=Zdef}, - StreamID, IsFin, Status, Headers) -> - Transport:send(Socket, cow_spdy:syn_reply(Zdef, StreamID, IsFin, - Status, <<"HTTP/1.1">>, Headers)). - -rst_stream(#state{socket=Socket, transport=Transport}, StreamID, Status) -> - Transport:send(Socket, cow_spdy:rst_stream(StreamID, Status)). - -goaway(#state{socket=Socket, transport=Transport, last_streamid=LastStreamID}, - Status) -> - Transport:send(Socket, cow_spdy:goaway(LastStreamID, Status)). - -data(#state{socket=Socket, transport=Transport}, StreamID, IsFin, Data) -> - Transport:send(Socket, cow_spdy:data(StreamID, IsFin, Data)). - -data_from_file(#state{socket=Socket, transport=Transport}, - StreamID, Filepath) -> - {ok, IoDevice} = file:open(Filepath, [read, binary, raw]), - data_from_file(Socket, Transport, StreamID, IoDevice). - -data_from_file(Socket, Transport, StreamID, IoDevice) -> - case file:read(IoDevice, 16#1fff) of - eof -> - _ = Transport:send(Socket, cow_spdy:data(StreamID, true, <<>>)), - ok; - {ok, Data} -> - case Transport:send(Socket, cow_spdy:data(StreamID, false, Data)) of - ok -> - data_from_file(Socket, Transport, StreamID, IoDevice); - {error, _} -> - ok - end - end. - -%% Children. - -new_child(State=#state{children=Children}, StreamID, Pid, IsFin) -> - IsFin2 = if IsFin -> fin; true -> nofin end, - State#state{last_streamid=StreamID, - children=[#child{streamid=StreamID, - pid=Pid, input=IsFin2}|Children]}. - -get_child(StreamID, #state{children=Children}) -> - lists:keyfind(StreamID, #child.streamid, Children). - -replace_child(Child=#child{streamid=StreamID}, - State=#state{children=Children}) -> - Children2 = lists:keyreplace(StreamID, #child.streamid, Children, Child), - State#state{children=Children2}. - -delete_child(Pid, State=#state{children=Children}) -> - Children2 = lists:keydelete(Pid, #child.pid, Children), - State#state{children=Children2}. - -%% Request process. - --spec request_init(socket(), {inet:ip_address(), inet:port_number()}, - cowboy:onresponse_fun(), cowboy_middleware:env(), [module()], - binary(), binary(), binary(), binary(), [{binary(), binary()}]) - -> ok. -request_init(FakeSocket, Peer, OnResponse, - Env, Middlewares, Method, Host, Path, Version, Headers) -> - {Host2, Port} = cow_http_hd:parse_host(Host), - {Path2, Qs} = cow_http:parse_fullpath(Path), - Version2 = cow_http:parse_version(Version), - Req = cowboy_req:new(FakeSocket, ?MODULE, Peer, - Method, Path2, Qs, Version2, Headers, - Host2, Port, <<>>, true, false, OnResponse), - execute(Req, Env, Middlewares). - --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]); - {stop, Req2} -> - cowboy_req:ensure_response(Req2, 204) - end. - --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]); - {stop, Req2} -> - cowboy_req:ensure_response(Req2, 204) - end. - -%% Reply functions used by cowboy_req. - --spec reply(socket(), binary(), cowboy:http_headers(), iodata()) -> ok. -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. - --spec stream_reply(socket(), binary(), cowboy:http_headers()) -> ok. -stream_reply(Socket = {Pid, _}, Status, Headers) -> - _ = Pid ! {stream_reply, Socket, Status, Headers}, - ok. - --spec stream_data(socket(), iodata()) -> ok. -stream_data(Socket = {Pid, _}, Data) -> - _ = Pid ! {stream_data, Socket, Data}, - ok. - --spec stream_close(socket()) -> ok. -stream_close(Socket = {Pid, _}) -> - _ = Pid ! {stream_close, Socket}, - ok. - -%% Internal transport functions. - --spec name() -> spdy. -name() -> - spdy. - --spec messages() -> {spdy, spdy_closed, spdy_error}. -messages() -> - {spdy, spdy_closed, spdy_error}. - --spec recv(socket(), non_neg_integer(), timeout()) - -> {ok, binary()} | {error, timeout}. -recv(Socket = {Pid, _}, Length, Timeout) -> - _ = Pid ! {recv, Socket, self(), Length, Timeout}, - receive - {recv, Socket, Ret} -> - Ret - end. - --spec send(socket(), iodata()) -> ok. -send(Socket, Data) -> - stream_data(Socket, Data). - -%% We don't wait for the result of the actual sendfile call, -%% therefore we can't know how much was actually sent. -%% This isn't a problem as we don't use this value in Cowboy. --spec sendfile(socket(), file:name_all()) -> {ok, undefined}. -sendfile(Socket = {Pid, _}, Filepath) -> - _ = Pid ! {sendfile, Socket, Filepath}, - {ok, undefined}. - --spec setopts({pid(), _}, list()) -> ok. -setopts(Socket = {Pid, _}, [{active, once}]) -> - _ = Pid ! {active, Socket, self()}, - ok; -setopts(Socket = {Pid, _}, [{active, false}]) -> - _ = Pid ! {passive, Socket, self()}, - ok. diff --git a/src/cowboy_tls.erl b/src/cowboy_tls.erl index 0ccf733..375b005 100644 --- a/src/cowboy_tls.erl +++ b/src/cowboy_tls.erl @@ -29,9 +29,6 @@ init(Parent, Ref, Socket, Transport, Opts) -> case ssl:negotiated_protocol(Socket) of {ok, <<"h2">>} -> init(Parent, Ref, Socket, Transport, Opts, cowboy_http2); - %% @todo Implement cowboy_spdy and cowboy_http. - {ok, <<"spdy/3">>} -> - init(Parent, Ref, Socket, Transport, Opts, cowboy_spdy); _ -> %% http/1.1 or no protocol negotiated. init(Parent, Ref, Socket, Transport, Opts, cowboy_http) end. diff --git a/test/cowboy_test.erl b/test/cowboy_test.erl index e3aeb97..07faf8e 100644 --- a/test/cowboy_test.erl +++ b/test/cowboy_test.erl @@ -30,40 +30,28 @@ init_https(Ref, ProtoOpts, Config) -> Port = ranch:get_port(Ref), [{type, ssl}, {protocol, http}, {port, Port}, {opts, Opts}|Config]. -init_spdy(Ref, ProtoOpts, Config) -> - Opts = ct_helper:get_certs_from_ets(), - {ok, _} = cowboy:start_tls(Ref, 100, Opts ++ [{port, 0}], ProtoOpts), - Port = ranch:get_port(Ref), - [{type, ssl}, {protocol, spdy}, {port, Port}, {opts, Opts}|Config]. - %% Common group of listeners used by most suites. common_all() -> [ {group, http}, {group, https}, - {group, spdy}, {group, http_compress}, - {group, https_compress}, - {group, spdy_compress} + {group, https_compress} ]. common_groups(Tests) -> [ {http, [parallel], Tests}, {https, [parallel], Tests}, - {spdy, [parallel], Tests}, {http_compress, [parallel], Tests}, - {https_compress, [parallel], Tests}, - {spdy_compress, [parallel], Tests} + {https_compress, [parallel], Tests} ]. init_common_groups(Name = http, Config, Mod) -> init_http(Name, #{env => #{dispatch => Mod:init_dispatch(Config)}}, Config); init_common_groups(Name = https, Config, Mod) -> init_https(Name, #{env => #{dispatch => Mod:init_dispatch(Config)}}, Config); -init_common_groups(Name = spdy, Config, Mod) -> - init_https(Name, #{env => #{dispatch => Mod:init_dispatch(Config)}}, Config); init_common_groups(Name = http_compress, Config, Mod) -> init_http(Name, #{ env => #{dispatch => Mod:init_dispatch(Config)}, @@ -73,11 +61,6 @@ init_common_groups(Name = https_compress, Config, Mod) -> init_https(Name, #{ env => #{dispatch => Mod:init_dispatch(Config)}, compress => true - }, Config); -init_common_groups(Name = spdy_compress, Config, Mod) -> - init_spdy(Name, #{ - env => #{dispatch => Mod:init_dispatch(Config)}, - compress => true }, Config). %% Support functions for testing using Gun. diff --git a/test/spdy_SUITE.erl b/test/spdy_SUITE.erl deleted file mode 100644 index c097e53..0000000 --- a/test/spdy_SUITE.erl +++ /dev/null @@ -1,147 +0,0 @@ -%% Copyright (c) 2013-2014, 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. - --module(spdy_SUITE). --compile(export_all). - --import(ct_helper, [config/2]). --import(cowboy_test, [gun_open/1]). --import(cowboy_test, [raw_open/1]). --import(cowboy_test, [raw_send/2]). - -%% ct. - -all() -> - [{group, spdy}]. - -groups() -> - [{spdy, [], ct_helper:all(?MODULE)}]. - -init_per_suite(Config) -> - case proplists:get_value(ssl_app, ssl:versions()) of - Version when Version < "5.2.1" -> - {skip, "No NPN support in SSL application."}; - _ -> - Dir = config(priv_dir, Config) ++ "/static", - ct_helper:create_static_dir(Dir), - [{static_dir, Dir}|Config] - end. - -end_per_suite(Config) -> - ct_helper:delete_static_dir(config(static_dir, Config)). - -init_per_group(Name, Config) -> - cowboy_test:init_spdy(Name, [ - {env, [{dispatch, init_dispatch(Config)}]} - ], Config). - -end_per_group(Name, _) -> - cowboy:stop_listener(Name). - -%% Dispatch configuration. - -init_dispatch(Config) -> - cowboy_router:compile([ - {"localhost", [ - {"/static/[...]", cowboy_static, - {dir, config(static_dir, Config)}}, - {"/echo/body", http_echo_body, []}, - {"/chunked", http_chunked, []}, - {"/", http_handler, []} - ]} - ]). - -%% Convenience functions. - -do_get(ConnPid, Host, Path) -> - StreamRef = gun:get(ConnPid, Path, [{<<"host">>, Host}]), - {response, IsFin, Status, _} = gun:await(ConnPid, StreamRef), - {IsFin, Status}. - -%% Tests. - -check_status(Config) -> - Tests = [ - {200, nofin, "localhost", "/"}, - {200, nofin, "localhost", "/chunked"}, - {200, nofin, "localhost", "/static/style.css"}, - {400, fin, "bad-host", "/"}, - {400, fin, "localhost", "bad-path"}, - {404, fin, "localhost", "/this/path/does/not/exist"} - ], - ConnPid = gun_open(Config), - _ = [{Status, Fin, Host, Path} = begin - {IsFin, Ret} = do_get(ConnPid, Host, Path), - {Ret, IsFin, Host, Path} - end || {Status, Fin, Host, Path} <- Tests], - gun:close(ConnPid). - -echo_body(Config) -> - ConnPid = gun_open(Config), - Body = << 0:800000 >>, - StreamRef = gun:post(ConnPid, "/echo/body", [ - {<<"content-type">>, "application/octet-stream"} - ], Body), - {response, nofin, 200, _} = gun:await(ConnPid, StreamRef), - {ok, Body} = gun:await_body(ConnPid, StreamRef), - gun:close(ConnPid). - -echo_body_multi(Config) -> - ConnPid = gun_open(Config), - BodyChunk = << 0:80000 >>, - StreamRef = gun:post(ConnPid, "/echo/body", [ - %% @todo I'm still unhappy with this. It shouldn't be required... - {<<"content-length">>, integer_to_list(byte_size(BodyChunk) * 10)}, - {<<"content-type">>, "application/octet-stream"} - ]), - _ = [gun:data(ConnPid, StreamRef, nofin, BodyChunk) || _ <- lists:seq(1, 9)], - gun:data(ConnPid, StreamRef, fin, BodyChunk), - {response, nofin, 200, _} = gun:await(ConnPid, StreamRef), - {ok, << 0:800000 >>} = gun:await_body(ConnPid, StreamRef), - gun:close(ConnPid). - -two_frames_one_packet(Config) -> - {raw_client, Socket, Transport} = Client = raw_open([ - {opts, [{client_preferred_next_protocols, - {client, [<<"spdy/3">>], <<"spdy/3">>}}]} - |Config]), - Zdef = cow_spdy:deflate_init(), - Zinf = cow_spdy:inflate_init(), - ok = raw_send(Client, iolist_to_binary([ - cow_spdy:syn_stream(Zdef, 1, 0, true, false, - 0, <<"GET">>, <<"https">>, <<"localhost">>, - <<"/">>, <<"HTTP/1.1">>, []), - cow_spdy:syn_stream(Zdef, 3, 0, true, false, - 0, <<"GET">>, <<"https">>, <<"localhost">>, - <<"/">>, <<"HTTP/1.1">>, []) - ])), - {Frame1, Rest1} = spdy_recv(Socket, Transport, <<>>), - {syn_reply, _, false, <<"200 OK">>, _, _} = cow_spdy:parse(Frame1, Zinf), - {Frame2, Rest2} = spdy_recv(Socket, Transport, Rest1), - {data, 1, true, _} = cow_spdy:parse(Frame2, Zinf), - {Frame3, Rest3} = spdy_recv(Socket, Transport, Rest2), - {syn_reply, _, false, <<"200 OK">>, _, _} = cow_spdy:parse(Frame3, Zinf), - {Frame4, <<>>} = spdy_recv(Socket, Transport, Rest3), - {data, 3, true, _} = cow_spdy:parse(Frame4, Zinf), - ok. - -spdy_recv(Socket, Transport, Acc) -> - {ok, Data} = Transport:recv(Socket, 0, 5000), - Data2 = << Acc/binary, Data/bits >>, - case cow_spdy:split(Data2) of - false -> - spdy_recv(Socket, Transport, Data2); - {true, Frame, Rest} -> - {Frame, Rest} - end. |