aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2017-08-12 16:06:59 +0200
committerLoïc Hoguin <[email protected]>2017-08-12 16:06:59 +0200
commit24a777945ae57940f8480b4940599187bba2edcb (patch)
treeae5947ce7e582c0be5eb97891da94bb254e28103
parent354c1c4ab611d3df81caac928dec1dcc888821e0 (diff)
downloadgun-24a777945ae57940f8480b4940599187bba2edcb.tar.gz
gun-24a777945ae57940f8480b4940599187bba2edcb.tar.bz2
gun-24a777945ae57940f8480b4940599187bba2edcb.zip
Remove SPDY; document HTTP/2
I just replaced "SPDY" with "HTTP/2" in the documentation. I suspect that's all that's needed, but if there's something off we can fix it later.
-rw-r--r--README.asciidoc6
-rw-r--r--doc/src/guide/connect.asciidoc8
-rw-r--r--doc/src/guide/http.asciidoc8
-rw-r--r--doc/src/guide/introduction.asciidoc4
-rw-r--r--doc/src/guide/protocols.asciidoc60
-rw-r--r--doc/src/manual/gun.asciidoc40
-rw-r--r--doc/src/manual/gun_app.asciidoc4
-rw-r--r--ebin/gun.app2
-rw-r--r--src/gun_spdy.erl317
-rw-r--r--test/spdy_SUITE.erl384
-rw-r--r--test/spdy_server.erl140
-rw-r--r--test/twitter_SUITE.erl7
12 files changed, 67 insertions, 913 deletions
diff --git a/README.asciidoc b/README.asciidoc
index 7715978..7aca263 100644
--- a/README.asciidoc
+++ b/README.asciidoc
@@ -1,11 +1,11 @@
= Gun
-Gun is an Erlang HTTP client with support for HTTP/1.1, SPDY and Websocket.
+Gun is an Erlang HTTP client with support for HTTP/1.1, HTTP/2 and Websocket.
== Goals
Gun aims to provide an *easy to use* client compatible with
-HTTP, SPDY and Websocket.
+HTTP/1.1, HTTP/2 and Websocket.
Gun is *always connected*. It will maintain a permanent
connection to the server, reopening it as soon as the server
@@ -19,7 +19,7 @@ developers to focus on writing their code without worrying.
The project is currently sponsored by
https://kato.im[Kato.im] and https://sameroom.io[Sameroom].
-The SPDY implementation was sponsored by
+The now removed SPDY implementation was sponsored by
http://www.leofs.org[LeoFS Cloud Storage].
== Support
diff --git a/doc/src/guide/connect.asciidoc b/doc/src/guide/connect.asciidoc
index c2e887c..e0b09d6 100644
--- a/doc/src/guide/connect.asciidoc
+++ b/doc/src/guide/connect.asciidoc
@@ -5,10 +5,10 @@ a connection using the Gun client.
=== Gun connections
-Gun is designed with the SPDY and Websocket protocols in mind.
+Gun is designed with the HTTP/2 and Websocket protocols in mind.
They are built for long-running connections that allow concurrent
exchange of data, either in the form of request/responses for
-SPDY or in the form of messages for Websocket.
+HTTP/2 or in the form of messages for Websocket.
A Gun connection is an Erlang process that manages a socket to
a remote endpoint. This Gun connection is owned by a user
@@ -35,8 +35,8 @@ The `gun:open/{2,3}` function must be used to open a connection.
If the port given is 443, Gun will attempt to connect using
SSL. The protocol will be selected automatically using the
-NPN extension for TLS. By default Gun supports SPDY/3.1,
-SPDY/3 and HTTP/1.1 when connecting using SSL.
+ALPN extension for TLS. By default Gun supports HTTP/2
+and HTTP/1.1 when connecting using SSL.
For any other port, Gun will attempt to connect using TCP
and will use the HTTP/1.1 protocol.
diff --git a/doc/src/guide/http.asciidoc b/doc/src/guide/http.asciidoc
index e856fb1..ff4aa0a 100644
--- a/doc/src/guide/http.asciidoc
+++ b/doc/src/guide/http.asciidoc
@@ -1,7 +1,7 @@
== HTTP
This chapter describes how to use the Gun client for
-communicating with an HTTP/1.1 or SPDY server.
+communicating with an HTTP/1.1 or HTTP/2 server.
=== Streams
@@ -130,7 +130,7 @@ the request has no body.
It is recommended to send the content-length header if you
know it in advance, although this is not required. If it
is not set, HTTP/1.1 will use the chunked transfer-encoding,
-and SPDY will continue normally as it is chunked by design.
+and HTTP/2 will continue normally as it is chunked by design.
.POST "/organizations/ninenines" with delayed body
@@ -234,7 +234,7 @@ data messages following. If it contains `fin` there will be
no data messages. If it contains `nofin` then one or more data
messages will follow.
-When using SPDY this value is sent with the frame and simply
+When using HTTP/2 this value is sent with the frame and simply
passed on in the message. When using HTTP/1.1 however Gun must
guess whether data will follow by looking at the response headers.
@@ -309,7 +309,7 @@ end.
=== Handling streams pushed by the server
-The SPDY protocol allows the server to push more than one
+The HTTP/2 protocol allows the server to push more than one
resource for every request. It will start sending those
extra resources before it starts sending the response itself,
so Gun will send you `gun_push` messages before `gun_response`
diff --git a/doc/src/guide/introduction.asciidoc b/doc/src/guide/introduction.asciidoc
index 2914ef6..89f2ae7 100644
--- a/doc/src/guide/introduction.asciidoc
+++ b/doc/src/guide/introduction.asciidoc
@@ -1,10 +1,10 @@
== Introduction
-Gun is an Erlang HTTP client with support for HTTP/1.1, SPDY and Websocket.
+Gun is an Erlang HTTP client with support for HTTP/1.1, HTTP/2 and Websocket.
=== Prerequisites
-Knowledge of Erlang, but also of the HTTP/1.1, SPDY and Websocket
+Knowledge of Erlang, but also of the HTTP/1.1, HTTP/2 and Websocket
protocols is required in order to read this guide.
=== Supported platforms
diff --git a/doc/src/guide/protocols.asciidoc b/doc/src/guide/protocols.asciidoc
index 5e3b273..ae7705f 100644
--- a/doc/src/guide/protocols.asciidoc
+++ b/doc/src/guide/protocols.asciidoc
@@ -37,29 +37,29 @@ It provides the `gun:ws_upgrade/{2,3,4}` function for that
purpose. A `gun_ws_upgrade` message will be sent on success;
a `gun_response` message otherwise.
-=== SPDY
+=== HTTP/2
-SPDY is a binary protocol based on HTTP, compatible with
+HTTP/2 is a binary protocol based on HTTP, compatible with
the HTTP semantics, that reduces the complexity of parsing
requests and responses, compresses the HTTP headers and
allows the server to push multiple responses to a single
request.
-The SPDY interface is very similar to HTTP/1.1, so this
+The HTTP/2 interface is very similar to HTTP/1.1, so this
section instead focuses on the differences in the interface
for the two protocols.
-Because a SPDY server can push multiple responses to a
+Because a HTTP/2 server can push multiple responses to a
single request, Gun might send `gun_push` messages for
every push received. They can be ignored safely if they
are not needed.
-The `gun:cancel/2` function will use the SPDY stream
+The `gun:cancel/2` function will use the HTTP/2 stream
cancellation mechanism which allows Gun to inform the
server to stop sending a response for this particular
request, saving resources.
-It is not possible to upgrade a SPDY connection to Websocket
+It is not possible to upgrade an HTTP/2 connection to Websocket
due to protocol limitations.
=== Websocket
@@ -87,33 +87,33 @@ current protocol.
.Supported operations per protocol
[cols="<,3*^",options="header"]
|===
-| Operation | HTTP/1.1 | SPDY | Websocket
-| delete | yes | yes | no
-| get | yes | yes | no
-| head | yes | yes | no
-| options | yes | yes | no
-| patch | yes | yes | no
-| post | yes | yes | no
-| put | yes | yes | no
-| request | yes | yes | no
-| data | yes | yes | no
-| await | yes | yes | no
-| await_body | yes | yes | no
-| flush | yes | yes | no
-| cancel | yes | yes | no
-| ws_upgrade | yes | no | no
-| ws_send | no | no | yes
+| Operation | HTTP/1.1 | HTTP/2 | Websocket
+| delete | yes | yes | no
+| get | yes | yes | no
+| head | yes | yes | no
+| options | yes | yes | no
+| patch | yes | yes | no
+| post | yes | yes | no
+| put | yes | yes | no
+| request | yes | yes | no
+| data | yes | yes | no
+| await | yes | yes | no
+| await_body | yes | yes | no
+| flush | yes | yes | no
+| cancel | yes | yes | no
+| ws_upgrade | yes | no | no
+| ws_send | no | no | yes
|===
.Messages sent per protocol
[cols="<,3*^",options="header"]
|===
-| Message | HTTP/1.1 | SPDY | Websocket
-| gun_push | no | yes | no
-| gun_response | yes | yes | no
-| gun_data | yes | yes | no
-| gun_error (StreamRef) | yes | yes | no
-| gun_error | yes | yes | yes
-| gun_ws_upgrade | yes | no | no
-| gun_ws | no | no | yes
+| Message | HTTP/1.1 | HTTP/2 | Websocket
+| gun_push | no | yes | no
+| gun_response | yes | yes | no
+| gun_data | yes | yes | no
+| gun_error (StreamRef) | yes | yes | no
+| gun_error | yes | yes | yes
+| gun_ws_upgrade | yes | no | no
+| gun_ws | no | no | yes
|===
diff --git a/doc/src/manual/gun.asciidoc b/doc/src/manual/gun.asciidoc
index 7c794ee..2487150 100644
--- a/doc/src/manual/gun.asciidoc
+++ b/doc/src/manual/gun.asciidoc
@@ -7,8 +7,8 @@ gun - asynchronous HTTP client
== Description
The `gun` module provides an asynchronous interface for
-connecting and communicating with Web servers over SPDY,
-HTTP or Websocket.
+connecting and communicating with Web servers over HTTP,
+HTTP/2 or Websocket.
== Types
@@ -22,21 +22,21 @@ connect_timeout => timeout()::
Connection timeout. Defaults to `infinity`.
http_opts => http_opts()::
Options specific to the HTTP protocol. See below.
-protocols => [http | spdy]::
+http2_opts => http2_opts()::
+ Options specific to the HTTP/2 protocol. See below.
+protocols => [http | http2]::
Ordered list of preferred protocols. When the transport is tcp,
this list must contain exactly one protocol. When the transport
is ssl, this list must contain at least one protocol and will be
- used using the NPN protocol negotiation method. When the server
- does not support NPN then http will always be used. Defaults to
- [http] when the transport is tcp, and [spdy, http] when the
+ used using the ALPN protocol negotiation method. When the server
+ does not support ALPN then http will always be used. Defaults to
+ [http] when the transport is tcp, and [http2, http] when the
transport is ssl.
retry => non_neg_integer()::
Number of times Gun will try to reconnect on failure before giving up.
Defaults to 5.
retry_timeout => pos_integer()::
Time between retries in milliseconds. Defaults to 5000.
-spdy_opts => spdy_opts()::
- Options specific to the SPDY protocol. See below.
trace => boolean()::
Whether to enable `dbg` tracing of the connection process. Should
only be used during debugging. Defaults to false.
@@ -70,23 +70,23 @@ transform_header_name => fun((LowercaseName :: binary()) -> TransformedName :: b
version => 'HTTP/1.1' | 'HTTP/1.0'::
HTTP version to use. Defaults to 'HTTP/1.1'.
-=== req_opts() = map()
+=== http2_opts() = map()
-Configuration for a particular request.
+Configuration for the HTTP/2 protocol.
The following keys are defined:
-reply_to => pid()::
- The pid of a process that is responsible for the response handling.
+keepalive => pos_integer()::
+ Time between pings in milliseconds. Defaults to 5000.
-=== spdy_opts() = map()
+=== req_opts() = map()
-Configuration for the SPDY protocol.
+Configuration for a particular request.
The following keys are defined:
-keepalive => pos_integer()::
- Time between pings in milliseconds. Defaults to 5000.
+reply_to => pid()::
+ The pid of a process that is responsible for the response handling.
=== ws_opts() = map()
@@ -107,7 +107,7 @@ messages being sent.
=== {gun_up, ConnPid, Protocol}
ConnPid = pid():: The pid of the Gun connection process.
-Protocol = http | spdy:: The protocol selected for this connection.
+Protocol = http | http2:: The protocol selected for this connection.
The connection is up.
@@ -127,7 +127,7 @@ subsequent messages ignored.
=== {gun_down, ConnPid, Protocol, Reason, KilledStreams, UnprocessedStreams}
ConnPid = pid():: The pid of the Gun connection process.
-Protocol = http | spdy | ws:: The protocol in use when the connection was lost.
+Protocol = http | http2 | ws:: The protocol in use when the connection was lost.
Reason = normal | closed | {error, atom()}:: The reason for the loss of the connection.
KilledStreams = [reference()]:: List of streams that have been brutally terminated.
UnprocessedStreams = [reference()]:: List of streams that have not been processed by the server.
@@ -165,7 +165,7 @@ Headers = [{binary(), binary()}]:: Headers @todo
A resource pushed alongside an HTTP response.
-This message can only be sent when the protocol is SPDY.
+This message can only be sent when the protocol is HTTP/2.
@todo I fear we also need the scheme; resource is identified by URI
@todo Perhaps we really should send the URI entirely, because cache
@@ -685,7 +685,7 @@ the stream and stop relaying messages.
@todo of a response Gun may also attempt to reconnect rather than
@todo receive the entire response body.
-SPDY streams can however be cancelled at any time.
+HTTP/2 streams can however be cancelled at any time.
=== ws_upgrade(ConnPid, Path) -> ws_upgrade(ConnPid, Path, [], #{})
diff --git a/doc/src/manual/gun_app.asciidoc b/doc/src/manual/gun_app.asciidoc
index e4447d6..219a323 100644
--- a/doc/src/manual/gun_app.asciidoc
+++ b/doc/src/manual/gun_app.asciidoc
@@ -2,14 +2,14 @@
== Name
-gun - Erlang HTTP client with support for HTTP/1.1, SPDY and Websocket.
+gun - Erlang HTTP client with support for HTTP/1.1, HTTP/2 and Websocket.
== Dependencies
The `gun` application uses the Erlang applications `ranch`
for abstracting TCP and TLS over a common interface, and
the `ssl` application for TLS support, required for HTTPS
-and SPDY support. In addition, Gun requires the `crypto`
+and secure HTTP/2 support. In addition, Gun requires the `crypto`
application (a dependency of `ssl`) for Websocket.
These dependencies must be started for the `gun`
diff --git a/ebin/gun.app b/ebin/gun.app
index 7fd12ea..9a728cd 100644
--- a/ebin/gun.app
+++ b/ebin/gun.app
@@ -1,7 +1,7 @@
{application, gun, [
{description, "HTTP/1.1, HTTP/2 and Websocket client for Erlang/OTP."},
{vsn, "1.0.0-pre.2"},
- {modules, ['gun','gun_app','gun_content_handler','gun_data','gun_http','gun_http2','gun_spdy','gun_sse','gun_sup','gun_ws','gun_ws_handler']},
+ {modules, ['gun','gun_app','gun_content_handler','gun_data','gun_http','gun_http2','gun_sse','gun_sup','gun_ws','gun_ws_handler']},
{registered, [gun_sup]},
{applications, [kernel,stdlib,ssl,cowlib,ranch]},
{mod, {gun_app, []}},
diff --git a/src/gun_spdy.erl b/src/gun_spdy.erl
deleted file mode 100644
index b7306a8..0000000
--- a/src/gun_spdy.erl
+++ /dev/null
@@ -1,317 +0,0 @@
-%% Copyright (c) 2013-2015, 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(gun_spdy).
-
--export([check_options/1]).
--export([name/0]).
--export([init/4]).
--export([handle/2]).
--export([close/1]).
--export([keepalive/1]).
--export([request/8]).
--export([request/9]).
--export([data/5]).
--export([cancel/3]).
--export([down/1]).
-
--record(stream, {
- id :: non_neg_integer(),
- ref :: reference(),
- reply_to :: pid(),
- in :: boolean(), %% true = open
- out :: boolean(), %% true = open
- version :: binary()
-}).
-
--record(spdy_state, {
- owner :: pid(),
- socket :: inet:socket() | ssl:sslsocket(),
- transport :: module(),
- buffer = <<>> :: binary(),
- zdef :: zlib:zstream(),
- zinf :: zlib:zstream(),
- streams = [] :: [#stream{}],
- stream_id = 1 :: non_neg_integer(),
- ping_id = 1 :: non_neg_integer()
-}).
-
-check_options(Opts) ->
- do_check_options(maps:to_list(Opts)).
-
-do_check_options([]) ->
- ok;
-do_check_options([{keepalive, K}|Opts]) when is_integer(K), K > 0 ->
- do_check_options(Opts);
-do_check_options([Opt|_]) ->
- {error, {options, {spdy, Opt}}}.
-
-name() -> spdy.
-
-init(Owner, Socket, Transport, _Opts) ->
- #spdy_state{owner=Owner, socket=Socket, transport=Transport,
- zdef=cow_spdy:deflate_init(), zinf=cow_spdy:inflate_init()}.
-
-handle(Data, State=#spdy_state{buffer=Buffer}) ->
- handle_loop(<< Buffer/binary, Data/binary >>,
- State#spdy_state{buffer= <<>>}).
-
-handle_loop(Data, State=#spdy_state{zinf=Zinf}) ->
- case cow_spdy:split(Data) of
- {true, Frame, Rest} ->
- P = cow_spdy:parse(Frame, Zinf),
- handle_frame(Rest, State, P);
- false ->
- State#spdy_state{buffer=Data}
- end.
-
-handle_frame(Rest, State=#spdy_state{socket=Socket, transport=Transport},
- {data, StreamID, IsFin, Data}) ->
- case get_stream_by_id(StreamID, State) of
- #stream{in=false} ->
- Transport:send(Socket,
- cow_spdy:rst_stream(StreamID, stream_already_closed)),
- handle_loop(Rest, delete_stream(StreamID, State));
- S = #stream{ref=StreamRef, reply_to=ReplyTo} when IsFin ->
- ReplyTo ! {gun_data, self(), StreamRef, fin, Data},
- handle_loop(Rest, in_fin_stream(S, State));
- #stream{ref=StreamRef, reply_to=ReplyTo} ->
- ReplyTo ! {gun_data, self(), StreamRef, nofin, Data},
- handle_loop(Rest, State);
- false ->
- Transport:send(Socket,
- cow_spdy:rst_stream(StreamID, invalid_stream)),
- handle_loop(Rest, delete_stream(StreamID, State))
- end;
-handle_frame(Rest, State=#spdy_state{socket=Socket, transport=Transport},
- {syn_stream, StreamID, AssocToStreamID, IsFin, IsUnidirectional,
- _, Method, Scheme, Host, Path, Version, Headers})
- when AssocToStreamID =/= 0, IsUnidirectional ->
- case get_stream_by_id(StreamID, State) of
- false ->
- case get_stream_by_id(AssocToStreamID, State) of
- #stream{ref=AssocToStreamRef, reply_to=ReplyTo} ->
- StreamRef = make_ref(),
- ReplyTo ! {gun_push, self(), AssocToStreamRef, StreamRef, Method,
- iolist_to_binary([Scheme, <<"://">>, Host, Path]), Headers},
- handle_loop(Rest, new_stream(StreamID, StreamRef, ReplyTo,
- not IsFin, false, Version, State));
- false ->
- Transport:send(Socket,
- cow_spdy:rst_stream(AssocToStreamID, invalid_stream)),
- handle_loop(Rest, State)
- end;
- #stream{} ->
- Transport:send(Socket,
- cow_spdy:rst_stream(StreamID, stream_in_use)),
- handle_loop(Rest, State)
- end;
-handle_frame(Rest, State=#spdy_state{socket=Socket, transport=Transport},
- {syn_stream, StreamID, _, _, _, _, _, _, _, _, _, _}) ->
- Transport:send(Socket,
- cow_spdy:rst_stream(StreamID, protocol_error)),
- handle_loop(Rest, State);
-handle_frame(Rest, State=#spdy_state{socket=Socket, transport=Transport},
- {syn_reply, StreamID, IsFin, Status, _, Headers}) ->
- case get_stream_by_id(StreamID, State) of
- #stream{in=false} ->
- Transport:send(Socket,
- cow_spdy:rst_stream(StreamID, stream_already_closed)),
- handle_loop(Rest, delete_stream(StreamID, State));
- S = #stream{ref=StreamRef, reply_to=ReplyTo} when IsFin ->
- ReplyTo ! {gun_response, self(), StreamRef, fin,
- parse_status(Status), Headers},
- handle_loop(Rest, in_fin_stream(S, State));
- #stream{ref=StreamRef, reply_to=ReplyTo} ->
- ReplyTo ! {gun_response, self(), StreamRef, nofin,
- parse_status(Status), Headers},
- handle_loop(Rest, State);
- false ->
- Transport:send(Socket,
- cow_spdy:rst_stream(StreamID, invalid_stream)),
- handle_loop(Rest, delete_stream(StreamID, State))
- end;
-handle_frame(Rest, State, {rst_stream, StreamID, Status}) ->
- case get_stream_by_id(StreamID, State) of
- #stream{ref=StreamRef, reply_to=ReplyTo} ->
- ReplyTo ! {gun_error, self(), StreamRef, Status},
- handle_loop(Rest, delete_stream(StreamID, State));
- false ->
- handle_loop(Rest, State)
- end;
-handle_frame(Rest, State, {settings, ClearSettings, Settings}) ->
- error_logger:error_msg("Ignored SETTINGS control frame ~p ~p~n",
- [ClearSettings, Settings]),
- handle_loop(Rest, State);
-%% Server PING.
-handle_frame(Rest, State=#spdy_state{socket=Socket, transport=Transport},
- {ping, PingID}) when PingID rem 2 =:= 0 ->
- Transport:send(Socket, cow_spdy:ping(PingID)),
- handle_loop(Rest, State);
-%% Client PING.
-handle_frame(Rest, State, {ping, _}) ->
- handle_loop(Rest, State);
-handle_frame(Rest, State, {goaway, LastGoodStreamID, Status}) ->
- error_logger:error_msg("Ignored GOAWAY control frame ~p ~p~n",
- [LastGoodStreamID, Status]),
- handle_loop(Rest, State);
-handle_frame(Rest, State, {headers, StreamID, IsFin, Headers}) ->
- error_logger:error_msg("Ignored HEADERS control frame ~p ~p ~p~n",
- [StreamID, IsFin, Headers]),
- handle_loop(Rest, State);
-handle_frame(Rest, State, {window_update, StreamID, DeltaWindowSize}) ->
- error_logger:error_msg("Ignored WINDOW_UPDATE control frame ~p ~p~n",
- [StreamID, DeltaWindowSize]),
- handle_loop(Rest, State);
-handle_frame(_, #spdy_state{streams=Streams, socket=Socket, transport=Transport},
- {error, badprotocol}) ->
- %% Because a particular stream is unknown,
- %% we're sending the error message to all streams.
- Reason = {badprotocol, "The remote endpoint sent invalid data."},
- _ = [ReplyTo ! {gun_error, self(), Reason} || #stream{reply_to=ReplyTo} <- Streams],
- %% @todo LastGoodStreamID
- Transport:send(Socket, cow_spdy:goaway(0, protocol_error)),
- close.
-
-parse_status(Status) ->
- << Code:3/binary, _/bits >> = Status,
- list_to_integer(binary_to_list(Code)).
-
-close(#spdy_state{streams=Streams}) ->
- close_streams(Streams).
-
-close_streams([]) ->
- ok;
-close_streams([#stream{ref=StreamRef, reply_to=ReplyTo}|Tail]) ->
- ReplyTo ! {gun_error, self(), StreamRef, {closed,
- "The connection was lost."}},
- close_streams(Tail).
-
-keepalive(State=#spdy_state{socket=Socket, transport=Transport,
- ping_id=PingID}) ->
- Transport:send(Socket, cow_spdy:ping(PingID)),
- State#spdy_state{ping_id=PingID + 2}.
-
-%% @todo Always https scheme?
-request(State=#spdy_state{socket=Socket, transport=Transport, zdef=Zdef,
- stream_id=StreamID}, StreamRef, ReplyTo, Method, Host, Port, Path, Headers) ->
- {Host2, Headers2} = prepare_request(Headers, Host, Port),
- Out = (false =/= lists:keyfind(<<"content-type">>, 1, Headers2))
- orelse (false =/= lists:keyfind(<<"content-length">>, 1, Headers2)),
- Transport:send(Socket, cow_spdy:syn_stream(Zdef,
- StreamID, 0, not Out, false, 0,
- Method, <<"https">>, Host2, Path, <<"HTTP/1.1">>, Headers2)),
- new_stream(StreamID, StreamRef, ReplyTo, true, Out, <<"HTTP/1.1">>,
- State#spdy_state{stream_id=StreamID + 2}).
-
-%% @todo Handle Body > 16MB. (split it out into many frames)
-%% @todo Always https scheme?
-request(State=#spdy_state{socket=Socket, transport=Transport, zdef=Zdef,
- stream_id=StreamID}, StreamRef, ReplyTo, Method, Host, Port, Path, Headers, Body) ->
- {Host2, Headers2} = prepare_request(Headers, Host, Port),
- Headers3 = lists:keystore(<<"content-length">>, 1, Headers2,
- {<<"content-length">>, integer_to_binary(iolist_size(Body))}),
- Transport:send(Socket, [
- cow_spdy:syn_stream(Zdef,
- StreamID, 0, false, false, 0,
- Method, <<"https">>, Host2, Path, <<"HTTP/1.1">>, Headers3),
- cow_spdy:data(StreamID, true, Body)
- ]),
- new_stream(StreamID, StreamRef, ReplyTo, true, false, <<"HTTP/1.1">>,
- State#spdy_state{stream_id=StreamID + 2}).
-
-prepare_request(Headers, Host, Port) ->
- Headers2 = lists:keydelete(<<"keep-alive">>, 1,
- lists:keydelete(<<"proxy-connection">>, 1,
- lists:keydelete(<<"transfer-encoding">>, 1,
- lists:keydelete(<<"connection">>, 1, Headers)))),
- case lists:keytake(<<"host">>, 1, Headers2) of
- false -> {[Host, $:, integer_to_binary(Port)], Headers2};
- {value, {_, Host1}, Headers3} -> {Host1, Headers3}
- end.
-
-data(State=#spdy_state{socket=Socket, transport=Transport},
- StreamRef, ReplyTo, IsFin, Data) ->
- case get_stream_by_ref(StreamRef, State) of
- #stream{out=false} ->
- error_stream_closed(State, StreamRef, ReplyTo);
- S = #stream{} ->
- IsFin2 = IsFin =:= fin,
- Transport:send(Socket, cow_spdy:data(S#stream.id, IsFin2, Data)),
- if IsFin2 ->
- out_fin_stream(S, State);
- true ->
- State
- end;
- false ->
- error_stream_not_found(State, StreamRef, ReplyTo)
- end.
-
-cancel(State=#spdy_state{socket=Socket, transport=Transport},
- StreamRef, ReplyTo) ->
- case get_stream_by_ref(StreamRef, State) of
- #stream{id=StreamID} ->
- Transport:send(Socket, cow_spdy:rst_stream(StreamID, cancel)),
- delete_stream(StreamID, State);
- false ->
- error_stream_not_found(State, StreamRef, ReplyTo)
- end.
-
-%% @todo Add unprocessed streams when GOAWAY handling is done.
-down(#spdy_state{streams=Streams}) ->
- KilledStreams = [Ref || #stream{ref=Ref} <- Streams],
- {KilledStreams, []}.
-
-error_stream_closed(State, StreamRef, ReplyTo) ->
- ReplyTo ! {gun_error, self(), StreamRef, {badstate,
- "The stream has already been closed."}},
- State.
-
-error_stream_not_found(State, StreamRef, ReplyTo) ->
- ReplyTo ! {gun_error, self(), StreamRef, {badstate,
- "The stream cannot be found."}},
- State.
-
-%% Streams.
-%% @todo probably change order of args and have state first?
-
-new_stream(StreamID, StreamRef, ReplyTo, In, Out, Version,
- State=#spdy_state{streams=Streams}) ->
- New = #stream{id=StreamID, ref=StreamRef, reply_to=ReplyTo,
- in=In, out=Out, version=Version},
- State#spdy_state{streams=[New|Streams]}.
-
-get_stream_by_id(StreamID, #spdy_state{streams=Streams}) ->
- lists:keyfind(StreamID, #stream.id, Streams).
-
-get_stream_by_ref(StreamRef, #spdy_state{streams=Streams}) ->
- lists:keyfind(StreamRef, #stream.ref, Streams).
-
-delete_stream(StreamID, State=#spdy_state{streams=Streams}) ->
- Streams2 = lists:keydelete(StreamID, #stream.id, Streams),
- State#spdy_state{streams=Streams2}.
-
-in_fin_stream(S=#stream{out=false}, State) ->
- delete_stream(S#stream.id, State);
-in_fin_stream(S, State=#spdy_state{streams=Streams}) ->
- Streams2 = lists:keyreplace(S#stream.id, #stream.id, Streams,
- S#stream{in=false}),
- State#spdy_state{streams=Streams2}.
-
-out_fin_stream(S=#stream{in=false}, State) ->
- delete_stream(S#stream.id, State);
-out_fin_stream(S, State=#spdy_state{streams=Streams}) ->
- Streams2 = lists:keyreplace(S#stream.id, #stream.id, Streams,
- S#stream{out=false}),
- State#spdy_state{streams=Streams2}.
diff --git a/test/spdy_SUITE.erl b/test/spdy_SUITE.erl
deleted file mode 100644
index 52068b6..0000000
--- a/test/spdy_SUITE.erl
+++ /dev/null
@@ -1,384 +0,0 @@
-%% Copyright (c) 2015, 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, [doc/1]).
-
-%% ct.
-
-all() -> [{group, spdy31}].
-
-groups() -> [{spdy31, [parallel], ct_helper:all(?MODULE)}].
-
-%% Helper functions.
-
-wait() ->
- receive after 500 -> ok end.
-
-down() ->
- receive {gun_down, _, _, _, _, _} ->
- ok
- after 5000 ->
- exit(timeout)
- end.
-
-not_down() ->
- receive {gun_down, _, _, _, _, _} ->
- exit(down)
- after 0 ->
- ok
- end.
-
-do_req_resp(ConnPid, ServerPid, ServerStreamID) ->
- StreamRef = gun:get(ConnPid, "/"),
- spdy_server:send(ServerPid, [
- {syn_reply, ServerStreamID, false, <<"200">>, <<"HTTP/1.1">>, []},
- {data, ServerStreamID, true, <<"Hello world!">>}
- ]),
- receive {gun_response, _, StreamRef, _, _, _} ->
- ok
- after 5000 ->
- exit(timeout)
- end,
- ok.
-
-%% SPDY/3.1 test suite.
-
-goaway_on_close(_) ->
- doc("Send a GOAWAY when the client closes the connection (spdy-protocol-draft3-1 2.1)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- gun:close(ConnPid),
- wait(),
- [{goaway, 0, ok}] = spdy_server:stop(ServerPid),
- down().
-
-goaway_on_shutdown(_) ->
- doc("Send a GOAWAY when the client closes the connection (spdy-protocol-draft3-1 2.1)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- gun:shutdown(ConnPid),
- wait(),
- [{goaway, 0, ok}] = spdy_server:stop(ServerPid),
- down().
-
-%% @todo This probably applies to HEADERS frame or SYN_STREAM from server push.
-reject_data_on_non_existing_stream(_) ->
- doc("DATA frames received for non-existing streams must be rejected with "
- "an INVALID_STREAM stream error. (spdy-protocol-draft3-1 2.2.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- spdy_server:send(ServerPid, [
- {data, 1, true, <<"Hello world!">>}
- ]),
- wait(),
- [{rst_stream, 1, invalid_stream}] = spdy_server:stop(ServerPid).
-
-%% @todo This probably applies to HEADERS frame or SYN_STREAM from server push.
-ignore_data_on_non_existing_stream_after_goaway(_) ->
- %% Note: this is not explicitly written in the specification.
- %% However the HTTP/2 draft tells us that we can discard frames
- %% with identifiers higher than the identified last stream,
- %% which falls under this case. (draft-ietf-httpbis-http2-17 6.8)
- doc("DATA frames received for non-existing streams after a GOAWAY has been "
- "sent must be ignored. (spdy-protocol-draft3-1 2.2.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- gun:shutdown(ConnPid),
- spdy_server:send(ServerPid, [
- {data, 1, true, <<"Hello world!">>}
- ]),
- wait(),
- [{goaway, 0, ok}] = spdy_server:stop(ServerPid),
- down().
-
-%% @todo This probably applies to HEADERS frame or SYN_STREAM from server push.
-reject_data_before_syn_reply(_) ->
- doc("A DATA frame received before a SYN_REPLY must be rejected "
- "with a PROTOCOL_ERROR stream error. (spdy-protocol-draft3-1 2.2.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- _ = gun:get(ConnPid, "/"),
- spdy_server:send(ServerPid, [
- {data, 1, true, <<"Hello world!">>},
- {syn_reply, 1, false, <<"200">>, <<"HTTP/1.1">>, []}
- ]),
- wait(),
- [_, {rst_stream, 1, protocol_error}] = spdy_server:stop(ServerPid).
-
-streamid_is_odd(_) ->
- doc("Client-initiated Stream-ID must be an odd number. (spdy-protocol-draft3-1 2.3.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- [do_req_resp(ConnPid, ServerPid, N) || N <- lists:seq(1, 5, 2)],
- Rec = spdy_server:stop(ServerPid),
- true = length(Rec) =:= length([ok || {syn_stream, StreamID, _, _, _, _, _, _, _, _, _, _} <- Rec, StreamID rem 2 =:= 1]).
-
-reject_streamid_0(_) ->
- doc("The Stream-ID 0 is not valid and must be rejected with a PROTOCOL_ERROR session error. (spdy-protocol-draft3-1 2.3.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- _ = gun:get(ConnPid, "/"),
- spdy_server:send(ServerPid, [
- {syn_stream, 0, 1, true, true, 0, <<"GET">>, <<"https">>, ["localhost:", integer_to_binary(Port)], "/a", <<"HTTP/1.1">>, []},
- {syn_reply, 1, true, <<"200">>, <<"HTTP/1.1">>, []}
- ]),
- wait(),
- [_, {goaway, 0, protocol_error}] = spdy_server:stop(ServerPid),
- down().
-
-streamid_increases_monotonically(_) ->
- doc("The Stream-ID must increase monotonically. (spdy-protocol-draft3-1 2.3.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- Expected = [1, 3, 5, 7, 9],
- [do_req_resp(ConnPid, ServerPid, N) || N <- Expected],
- wait(),
- Rec = spdy_server:stop(ServerPid),
- Expected = [StreamID || {syn_stream, StreamID, _, _, _, _, _, _, _, _, _, _} <- Rec].
-
-streamid_does_not_wrap(_) ->
- doc("Stream-ID must not wrap. Reconnect when all Stream-IDs are exhausted. (spdy-protocol-draft3-1 2.3.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- MaxClientStreamID = 2147483647,
- sys:replace_state(ConnPid, fun({loop, State}) ->
- %% Replace the next stream_id value to the maximum allowed value.
- {loop, setelement(11, State, setelement(9, element(11, State), MaxClientStreamID))}
- end),
- do_req_resp(ConnPid, ServerPid, MaxClientStreamID),
- %% Gun has exhausted all Stream-IDs and should now reconnect.
- {ok, spdy} = gun:await_up(ConnPid),
- %% Check that the next request is on a new connection.
- _ = gun:get(ConnPid, "/"),
- [{syn_stream, 1, _, _, _, _, _, _, _, _, _, _}] = spdy_server:stop(ServerPid).
-
-reject_syn_stream_decreasing_streamid(_) ->
- doc("Reject a decreasing Stream-ID with a PROTOCOL_ERROR session error. (spdy-protocol-draft3-1 2.3.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- _ = gun:get(ConnPid, "/"),
- Host = ["localhost:", integer_to_binary(Port)],
- spdy_server:send(ServerPid, [
- {syn_stream, 2, 1, true, true, 0, <<"GET">>, <<"https">>, Host, "/a", <<"HTTP/1.1">>, []},
- {syn_stream, 6, 1, true, true, 0, <<"GET">>, <<"https">>, Host, "/b", <<"HTTP/1.1">>, []},
- {syn_stream, 4, 1, true, true, 0, <<"GET">>, <<"https">>, Host, "/c", <<"HTTP/1.1">>, []},
- {syn_reply, 1, true, <<"200">>, <<"HTTP/1.1">>, []}
- ]),
- wait(),
- [_, {goaway, 0, protocol_error}] = spdy_server:stop(ServerPid),
- down().
-
-reject_stream_duplicate_streamid(_) ->
- doc("Reject duplicate Stream-ID with a PROTOCOL_ERROR session error. (spdy-protocol-draft3-1 2.3.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- _ = gun:get(ConnPid, "/"),
- Host = ["localhost:", integer_to_binary(Port)],
- spdy_server:send(ServerPid, [
- {syn_stream, 2, 1, true, true, 0, <<"GET">>, <<"https">>, Host, "/a", <<"HTTP/1.1">>, []},
- {syn_stream, 2, 1, true, true, 0, <<"GET">>, <<"https">>, Host, "/b", <<"HTTP/1.1">>, []},
- {syn_reply, 1, true, <<"200">>, <<"HTTP/1.1">>, []}
- ]),
- wait(),
- [_, {goaway, 2, protocol_error}] = spdy_server:stop(ServerPid),
- down().
-
-dont_send_frames_after_flag_fin(_) ->
- doc("Do not send frames after sending FLAG_FIN. (spdy-protocol-draft3-1 2.3.6)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- %% Send a POST frame with no content header so that Gun sets FLAG_FIN,
- %% then try sending data. Gun should reject this second call.
- StreamRef = gun:post(ConnPid, "/", []),
- gun:data(ConnPid, StreamRef, false, <<"Hello world!">>),
- receive {gun_error, ConnPid, StreamRef, _} ->
- ok
- after 5000 ->
- exit(timeout)
- end,
- wait(),
- [{syn_stream, _, _, _, _, _, _, _, _, _, _, _}] = spdy_server:stop(ServerPid).
-
-allow_window_update_after_flag_fin(_) ->
- doc("WINDOW_UPDATE is allowed when the stream is half-closed. (spdy-protocol-draft3-1 2.3.6)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- _ = gun:get(ConnPid, "/"),
- spdy_server:send(ServerPid, [
- {window_update, 1, 1024}
- ]),
- wait(),
- [{syn_stream, _, _, _, _, _, _, _, _, _, _, _}] = spdy_server:stop(ServerPid).
-
-%% @todo This probably applies to HEADERS frame or SYN_STREAM from server push.
-reject_data_on_half_closed_stream(_) ->
- doc("Data frames sent on a half-closed stream must be rejected "
- "with a STREAM_ALREADY_CLOSED stream error. (spdy-protocol-draft3-1 2.3.6)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- %% Send a POST frame with a content header so that Gun leaves this
- %% stream alive after the server sends the reply.
- _ = gun:post(ConnPid, "/", [{<<"content-length">>, <<"5">>}]),
- spdy_server:send(ServerPid, [
- {syn_reply, 1, true, <<"200">>, <<"HTTP/1.1">>, []},
- {data, 1, true, <<"Hello world!">>}
- ]),
- wait(),
- [_, {rst_stream, 1, stream_already_closed}] = spdy_server:stop(ServerPid).
-
-%% @todo This probably applies to HEADERS frame or SYN_STREAM from server push.
-reject_data_on_closed_stream(_) ->
- doc("Data frames sent on a closed stream must be rejected "
- "with a PROTOCOL_ERROR stream error. (spdy-protocol-draft3-1 2.3.7)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- %% Send a GET frame so that the stream is closed when the server replies.
- _ = gun:get(ConnPid, "/"),
- spdy_server:send(ServerPid, [
- {syn_reply, 1, true, <<"200">>, <<"HTTP/1.1">>, []},
- {data, 1, true, <<"Hello world!">>}
- ]),
- wait(),
- [_, {rst_stream, 1, protocol_error}] = spdy_server:stop(ServerPid).
-
-%% @todo We need to test that we do the right thing when the server sends a GOAWAY.
-goaway_last_good_streamid(_) ->
- doc("The GOAWAY frame must contain the Stream-ID of the last recently "
- "received stream from the remote endpoint. (spdy-protocol-draft3-1 2.4.1)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- _ = gun:get(ConnPid, "/"),
- Host = ["localhost:", integer_to_binary(Port)],
- spdy_server:send(ServerPid, [
- {syn_stream, 2, 1, true, true, 0, <<"GET">>, <<"https">>, Host, "/a", <<"HTTP/1.1">>, []},
- {syn_stream, 4, 1, true, true, 0, <<"GET">>, <<"https">>, Host, "/c", <<"HTTP/1.1">>, []},
- {syn_stream, 6, 1, true, true, 0, <<"GET">>, <<"https">>, Host, "/b", <<"HTTP/1.1">>, []},
- {syn_reply, 0, true, <<"200">>, <<"HTTP/1.1">>, []}
- ]),
- wait(),
- [_, {goaway, 6, protocol_error}] = spdy_server:stop(ServerPid),
- down().
-
-dont_send_rst_stream_on_rst_stream(_) ->
- doc("An endpoint must not send an RST_STREAM in response to an RST_STREAM. (spdy-protocol-draft3-1 2.4.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- _ = gun:get(ConnPid, "/"),
- spdy_server:send(ServerPid, [
- {rst_stream, 1, refused_stream}
- ]),
- wait(),
- %% No RST_STREAM was received; only SYN_STREAM.
- [_] = spdy_server:stop(ServerPid),
- not_down().
-
-coalesce_multiple_identical_rst_stream(_) ->
- doc("Do not send multiple identical RST_STREAM in succession. (spdy-protocol-draft3-1 2.4.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- spdy_server:send(ServerPid, [
- {data, 1, true, <<"Hello ">>},
- {data, 1, true, <<"world!">>}
- ]),
- wait(),
- [{rst_stream, 1, invalid_stream}] = spdy_server:stop(ServerPid).
-
-%% @todo I am not sure how to adequately test that we don't send bad flags.
-syn_stream_ignore_unknown_flags(_) ->
- %% Note: this is not explicitly written in the specification.
- %% However the HTTP/2 draft tells us to ignore unknown flags.
- %% (draft-ietf-httpbis-http2-17 4.1)
- doc("Unknown flags must be ignored. (spdy-protocol-draft3-1 2.6.1)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- _ = gun:get(ConnPid, "/"),
- %% Build a SYN_STREAM frame with all flag bits set to 1.
- << Before:32/bits, _:8, After/bits >> = iolist_to_binary(cow_spdy:syn_stream(cow_spdy:deflate_init(),
- 2, 1, true, true, 0, <<"GET">>, <<"https">>, ["localhost:", integer_to_binary(Port)], "/a", <<"HTTP/1.1">>, [])),
- Frame = << Before/bits, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, After/bits >>,
- spdy_server:send_raw(ServerPid, Frame),
- wait(),
- [_] = spdy_server:stop(ServerPid).
-
-reject_associated_to_streamid_0(_) ->
- doc("A non-zero Associated-To-Stream-ID sent by the server must "
- "be rejected with a PROTOCOL_ERROR session error. (spdy-protocol-draft3-1 2.6.1)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- _ = gun:get(ConnPid, "/"),
- Host = ["localhost:", integer_to_binary(Port)],
- spdy_server:send(ServerPid, [
- {syn_stream, 2, 1, true, true, 0, <<"GET">>, <<"https">>, Host, "/a", <<"HTTP/1.1">>, []},
- {syn_stream, 4, 1, true, true, 0, <<"GET">>, <<"https">>, Host, "/c", <<"HTTP/1.1">>, []},
- {syn_stream, 6, 0, true, true, 0, <<"GET">>, <<"https">>, Host, "/b", <<"HTTP/1.1">>, []},
- {syn_reply, 1, true, <<"200">>, <<"HTTP/1.1">>, []}
- ]),
- wait(),
- [_, {goaway, 4, protocol_error}] = spdy_server:stop(ServerPid),
- down().
-
-syn_reply_ignore_unknown_flags(_) ->
- %% Note: this is not explicitly written in the specification.
- %% However the HTTP/2 draft tells us to ignore unknown flags.
- %% (draft-ietf-httpbis-http2-17 4.1)
- doc("Unknown flags must be ignored. (spdy-protocol-draft3-1 2.6.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- _ = gun:get(ConnPid, "/"),
- %% Build a SYN_REPLY frame with all flag bits set to 1.
- << Before:32/bits, _:8, After/bits >> = iolist_to_binary(cow_spdy:syn_reply(cow_spdy:deflate_init(),
- 1, true, <<"200">>, <<"HTTP/1.1">>, [])),
- Frame = << Before/bits, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, After/bits >>,
- spdy_server:send_raw(ServerPid, Frame),
- wait(),
- [_] = spdy_server:stop(ServerPid).
-
-reject_duplicate_syn_reply(_) ->
- doc("Reception of multiple SYN_REPLY for the same Stream-ID must "
- "be rejected with a STREAM_IN_USE stream error. (spdy-protocol-draft3-1 2.6.2)"),
- {ok, ServerPid, Port} = spdy_server:start_link(),
- {ok, ConnPid} = gun:open("localhost", Port, #{transport=>ssl}),
- {ok, spdy} = gun:await_up(ConnPid),
- _ = gun:get(ConnPid, "/"),
- Host = ["localhost:", integer_to_binary(Port)],
- spdy_server:send(ServerPid, [
- {syn_reply, 1, false, <<"200">>, <<"HTTP/1.1">>, []},
- {syn_reply, 1, false, <<"200">>, <<"HTTP/1.1">>, []}
- ]),
- wait(),
- [_, {rst_stream, 1, stream_in_use}] = spdy_server:stop(ServerPid).
diff --git a/test/spdy_server.erl b/test/spdy_server.erl
deleted file mode 100644
index deec356..0000000
--- a/test/spdy_server.erl
+++ /dev/null
@@ -1,140 +0,0 @@
-%% Copyright (c) 2015, 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_server).
--behaviour(gen_server).
-
-%% API.
--export([start_link/0]).
--export([stop/1]).
--export([send/2]).
--export([send_raw/2]).
-
-%% gen_server.
--export([init/1]).
--export([handle_call/3]).
--export([handle_cast/2]).
--export([handle_info/2]).
--export([terminate/2]).
--export([code_change/3]).
-
--type recording() :: [tuple()].
-
--record(state, {
- owner = undefined :: pid(),
- recording = [] :: recording(),
- state_name = listen :: listen | record,
- socket = undefined :: ssl:sslsocket(),
- zdef = undefined :: zlib:zstream(),
- zinf = undefined :: zlib:zstream(),
- buffer = <<>> :: binary()
-}).
-
-%% API.
-
--spec start_link() -> {ok, pid()}.
-start_link() ->
- {ok, Pid} = gen_server:start_link(?MODULE, [self()], []),
- receive {port, Pid, Port} ->
- {ok, Pid, Port}
- after 5000 ->
- exit(timeout)
- end.
-
--spec stop(pid()) -> recording().
-stop(Pid) ->
- gen_server:call(Pid, stop).
-
-send(Pid, Frames) ->
- gen_server:call(Pid, {send, Frames}).
-
-send_raw(Pid, Data) ->
- gen_server:call(Pid, {send_raw, Data}).
-
-%% gen_server.
-
-init([Owner]) ->
- Opts = ct_helper:get_certs_from_ets(),
- {ok, LSocket} = ssl:listen(0, [binary, {active, false}, {nodelay, true},
- {next_protocols_advertised, [<<"spdy/3.1">>, <<"spdy/3">>]}|Opts]),
- {ok, {_, Port}} = ssl:sockname(LSocket),
- Owner ! {port, self(), Port},
- self() ! listen,
- Zdef = cow_spdy:deflate_init(),
- Zinf = cow_spdy:inflate_init(),
- {ok, #state{owner=Owner, socket=LSocket, zdef=Zdef, zinf=Zinf}}.
-
-handle_call({send, Frames}, {Owner, _}, State=#state{owner=Owner, socket=Socket, zdef=Zdef}) ->
- do_send(Frames, Socket, Zdef),
- {reply, ok, State};
-handle_call({send_raw, Data}, {Owner, _}, State=#state{owner=Owner, socket=Socket}) ->
- ssl:send(Socket, Data),
- {reply, ok, State};
-handle_call(stop, {Owner, _}, State=#state{owner=Owner, recording=Recording}) ->
- {stop, normal, lists:reverse(Recording), State};
-handle_call(_Request, _From, State) ->
- {reply, ignored, State}.
-
-handle_cast(_Msg, State) ->
- {noreply, State}.
-
-handle_info(listen, State=#state{state_name=listen, socket=LSocket}) ->
- {ok, CSocket} = ssl:transport_accept(LSocket, 5000),
- ok = ssl:ssl_accept(CSocket, 5000),
- ok = ssl:setopts(CSocket, [{active, once}]),
- {noreply, State#state{state_name=record, socket=CSocket}};
-handle_info({ssl, Socket, Data}, State=#state{state_name=record, socket=Socket, buffer=Buffer}) ->
- ok = ssl:setopts(Socket, [{active, once}]),
- State2 = handle_data(<< Buffer/binary, Data/binary >>, State),
- {noreply, State2};
-%% @todo ssl_closed ssl_error
-handle_info(_Info, State) ->
- {noreply, State}.
-
-terminate(_Reason, _State) ->
- ok.
-
-code_change(_OldVsn, State, _Extra) ->
- {ok, State}.
-
-%% Internal.
-
-do_send([], _, _) ->
- ok;
-do_send([{syn_stream, StreamID, AssocToStreamID, IsFin, IsUnidirectional, Priority,
- Method, Scheme, Host, Path, Version, Headers}|Tail], Socket, Zdef) ->
- ssl:send(Socket, cow_spdy:syn_stream(Zdef, StreamID, AssocToStreamID, IsFin, IsUnidirectional, Priority,
- Method, Scheme, Host, Path, Version, Headers)),
- do_send(Tail, Socket, Zdef);
-do_send([{syn_reply, StreamID, IsFin, Status, Version, Headers}|Tail], Socket, Zdef) ->
- ssl:send(Socket, cow_spdy:syn_reply(Zdef, StreamID, IsFin, Status, Version, Headers)),
- do_send(Tail, Socket, Zdef);
-do_send([{rst_stream, StreamID, Status}|Tail], Socket, Zdef) ->
- ssl:send(Socket, cow_spdy:rst_stream(StreamID, Status)),
- do_send(Tail, Socket, Zdef);
-do_send([{window_update, StreamID, DeltaWindowSize}|Tail], Socket, Zdef) ->
-%% @todo ssl:send(Socket, cow_spdy:window_update(StreamID, DeltaWindowSize)),
- do_send(Tail, Socket, Zdef);
-do_send([{data, StreamID, IsFin, Data}|Tail], Socket, Zdef) ->
- ssl:send(Socket, cow_spdy:data(StreamID, IsFin, Data)),
- do_send(Tail, Socket, Zdef).
-
-handle_data(Data, State=#state{recording=Recording, zinf=Zinf}) ->
- case cow_spdy:split(Data) of
- {true, ParsedFrame, Rest} ->
- Frame = cow_spdy:parse(ParsedFrame, Zinf),
- handle_data(Rest, State#state{recording=[Frame|Recording]});
- false ->
- State#state{buffer=Data}
- end.
diff --git a/test/twitter_SUITE.erl b/test/twitter_SUITE.erl
index 6cd53c4..84307a2 100644
--- a/test/twitter_SUITE.erl
+++ b/test/twitter_SUITE.erl
@@ -16,7 +16,7 @@
-compile(export_all).
all() ->
- [http, http2, spdy].
+ [http, http2].
http(_) ->
{ok, Pid} = gun:open("twitter.com", 443, #{protocols => [http]}),
@@ -28,11 +28,6 @@ http2(_) ->
{ok, http2} = gun:await_up(Pid),
common(Pid).
-spdy(_) ->
- {ok, Pid} = gun:open("twitter.com", 443, #{protocols => [spdy]}),
- {ok, spdy} = gun:await_up(Pid),
- common(Pid).
-
common(Pid) ->
Ref = gun:get(Pid, "/"),
receive