mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 20:30:23 +00:00
Completely remove SPDY
This commit is contained in:
parent
b370442a63
commit
7bdd710849
15 changed files with 21 additions and 766 deletions
|
@ -24,8 +24,9 @@ Sponsors
|
||||||
The project is currently sponsored by
|
The project is currently sponsored by
|
||||||
[Sameroom](https://sameroom.io).
|
[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/).
|
[LeoFS Cloud Storage](http://leo-project.net/leofs/).
|
||||||
|
It has since been superseded by HTTP/2.
|
||||||
|
|
||||||
Online documentation
|
Online documentation
|
||||||
--------------------
|
--------------------
|
||||||
|
|
|
@ -33,7 +33,7 @@ capitalize_hook(Status, Headers, Body, Req) ->
|
||||||
cowboy_req:reply(Status, Headers2, 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
|
because the specification explicitly says all headers are
|
||||||
lowercase, unlike HTTP which allows any case but treats
|
lowercase, unlike HTTP which allows any case but treats
|
||||||
them as case insensitive.
|
them as case insensitive.
|
||||||
|
|
|
@ -106,7 +106,7 @@ new messages, and perform the operations required by only activating
|
||||||
the required parts of the system.
|
the required parts of the system.
|
||||||
|
|
||||||
The more recent Web technologies, like Websocket of course, but also
|
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
|
of requests and responses is retained of course, but anything could
|
||||||
be sent in between, by both the client or the browser, and the
|
be sent in between, by both the client or the browser, and the
|
||||||
responses could also be received in a completely different order.
|
responses could also be received in a completely different order.
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
Cowboy is a small, fast and modular HTTP server written in Erlang.
|
Cowboy is a small, fast and modular HTTP server written in Erlang.
|
||||||
|
|
||||||
Cowboy aims to provide a complete HTTP stack, including its derivatives
|
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.
|
Websocket (all implemented drafts + standard) and Webmachine-based REST.
|
||||||
|
|
||||||
Cowboy is a high quality project. It has a small code base, is very
|
Cowboy is a high quality project. It has a small code base, is very
|
||||||
|
|
|
@ -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
|
small or big, text or binary. Because of this Websocket is
|
||||||
sometimes used for communication between systems.
|
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
|
single connection per server, keeping it open for subsequent
|
||||||
requests, and also by compressing the HTTP headers to reduce
|
requests, and also by compressing the HTTP headers to reduce
|
||||||
the size of requests.
|
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,
|
just a different way of performing HTTP requests and responses,
|
||||||
by using binary frames instead of a text-based protocol.
|
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
|
a request. This is meant to allow sending the resources
|
||||||
associated with the request before the client requests them,
|
associated with the request before the client requests them,
|
||||||
saving latency when loading websites.
|
saving latency when loading websites.
|
||||||
|
|
||||||
SPDY is an experiment that has proven successful and is used
|
Browsers make use of TLS Application-Layer Protocol Negotiation
|
||||||
as the basis for the HTTP/2.0 standard.
|
extension to upgrade to an HTTP/2 connection seamlessly if the
|
||||||
|
server supports it.
|
||||||
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.
|
|
||||||
|
|
|
@ -55,7 +55,7 @@ HTTP/1.1 allows the client to request that the server
|
||||||
keeps the connection alive. This mechanism is described
|
keeps the connection alive. This mechanism is described
|
||||||
in the next section.
|
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
|
asynchronously on the same connection. Details on what
|
||||||
this means for your application is described in this
|
this means for your application is described in this
|
||||||
chapter.
|
chapter.
|
||||||
|
@ -126,9 +126,9 @@ static files for example.
|
||||||
|
|
||||||
This is handled automatically by the server.
|
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.
|
And the server can send a response at any time too.
|
||||||
|
|
||||||
This means for example that the client does not need
|
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
|
processes are managed by another process that handles the
|
||||||
connection itself.
|
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
|
client before the client requests them. This is especially
|
||||||
useful for sending static files associated with the HTML
|
useful for sending static files associated with the HTML
|
||||||
page requested, as this reduces the latency of the overall
|
page requested, as this reduces the latency of the overall
|
||||||
|
|
|
@ -67,16 +67,6 @@ ProtoOpts = cowboy_protocol:opts():: HTTP protocol options.
|
||||||
Start listening for HTTPS connections. Returns the pid for this
|
Start listening for HTTPS connections. Returns the pid for this
|
||||||
listener's supervisor.
|
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}
|
=== stop_listener(Ref) -> ok | {error, not_found}
|
||||||
|
|
||||||
Ref = ranch:ref():: Listener name.
|
Ref = ranch:ref():: Listener name.
|
||||||
|
|
|
@ -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.
|
|
|
@ -1,7 +1,7 @@
|
||||||
{application, cowboy, [
|
{application, cowboy, [
|
||||||
{description, "Small, fast, modular HTTP server."},
|
{description, "Small, fast, modular HTTP server."},
|
||||||
{vsn, "2.0.0-pre.2"},
|
{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]},
|
{registered, [cowboy_sup,cowboy_clock]},
|
||||||
{applications, [kernel,stdlib,crypto,cowlib,ranch]},
|
{applications, [kernel,stdlib,crypto,cowlib,ranch]},
|
||||||
{mod, {cowboy_app, []}}
|
{mod, {cowboy_app, []}}
|
||||||
|
|
|
@ -52,8 +52,8 @@ start_tls(Ref, NbAcceptors, TransOpts0, ProtoOpts)
|
||||||
when is_integer(NbAcceptors), NbAcceptors > 0 ->
|
when is_integer(NbAcceptors), NbAcceptors > 0 ->
|
||||||
TransOpts = [
|
TransOpts = [
|
||||||
connection_type(ProtoOpts),
|
connection_type(ProtoOpts),
|
||||||
{next_protocols_advertised, [<<"h2">>, <<"spdy/3">>, <<"http/1.1">>]},
|
{next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]},
|
||||||
{alpn_preferred_protocols, [<<"h2">>, <<"spdy/3">>, <<"http/1.1">>]}
|
{alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]}
|
||||||
|TransOpts0],
|
|TransOpts0],
|
||||||
ranch:start_listener(Ref, NbAcceptors, ranch_ssl, TransOpts, cowboy_tls, ProtoOpts).
|
ranch:start_listener(Ref, NbAcceptors, ranch_ssl, TransOpts, cowboy_tls, ProtoOpts).
|
||||||
|
|
||||||
|
|
|
@ -769,11 +769,6 @@ response_headers(Headers, Req) ->
|
||||||
% %% We stream the response body until we close the connection.
|
% %% We stream the response body until we close the connection.
|
||||||
% RespConn = close,
|
% RespConn = close,
|
||||||
% {RespType, Req2} = if
|
% {RespType, Req2} = if
|
||||||
% Transport =:= cowboy_spdy ->
|
|
||||||
% response(Status, Headers, RespHeaders, [
|
|
||||||
% {<<"date">>, cowboy_clock:rfc1123()},
|
|
||||||
% {<<"server">>, <<"Cowboy">>}
|
|
||||||
% ], stream, Req);
|
|
||||||
% true ->
|
% true ->
|
||||||
% response(Status, Headers, RespHeaders, [
|
% response(Status, Headers, RespHeaders, [
|
||||||
% {<<"connection">>, <<"close">>},
|
% {<<"connection">>, <<"close">>},
|
||||||
|
@ -896,9 +891,6 @@ chunk(Data, #{pid := Pid, streamid := StreamID}) ->
|
||||||
|
|
||||||
%% If ever made public, need to send nothing if HEAD.
|
%% If ever made public, need to send nothing if HEAD.
|
||||||
-spec last_chunk(Req) -> Req when Req::req().
|
-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}) ->
|
last_chunk(Req=#http_req{socket=Socket, transport=Transport}) ->
|
||||||
_ = Transport:send(Socket, <<"0\r\n\r\n">>),
|
_ = Transport:send(Socket, <<"0\r\n\r\n">>),
|
||||||
Req#http_req{resp_state=done}.
|
Req#http_req{resp_state=done}.
|
||||||
|
@ -1028,15 +1020,6 @@ to_list(Req) ->
|
||||||
%-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().
|
% {normal | hook, Req} when Req::req().
|
||||||
%chunked_response(Status, Headers, Req=#http_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,
|
% version=Version, connection=Connection,
|
||||||
% resp_state=RespState, resp_headers=RespHeaders})
|
% resp_state=RespState, resp_headers=RespHeaders})
|
||||||
% when RespState =:= waiting; RespState =:= waiting_stream ->
|
% when RespState =:= waiting; RespState =:= waiting_stream ->
|
||||||
|
@ -1094,14 +1077,6 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
|
||||||
end
|
end
|
||||||
end,
|
end,
|
||||||
ReplyType = case Req2#http_req.resp_state of
|
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 ->
|
RespState when RespState =:= waiting; RespState =:= waiting_stream ->
|
||||||
HTTPVer = atom_to_binary(Version, latin1),
|
HTTPVer = atom_to_binary(Version, latin1),
|
||||||
StatusLine = << HTTPVer/binary, " ",
|
StatusLine = << HTTPVer/binary, " ",
|
||||||
|
|
|
@ -1,488 +0,0 @@
|
||||||
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
|
|
||||||
%%
|
|
||||||
%% 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.
|
|
|
@ -29,9 +29,6 @@ init(Parent, Ref, Socket, Transport, Opts) ->
|
||||||
case ssl:negotiated_protocol(Socket) of
|
case ssl:negotiated_protocol(Socket) of
|
||||||
{ok, <<"h2">>} ->
|
{ok, <<"h2">>} ->
|
||||||
init(Parent, Ref, Socket, Transport, Opts, cowboy_http2);
|
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.
|
_ -> %% http/1.1 or no protocol negotiated.
|
||||||
init(Parent, Ref, Socket, Transport, Opts, cowboy_http)
|
init(Parent, Ref, Socket, Transport, Opts, cowboy_http)
|
||||||
end.
|
end.
|
||||||
|
|
|
@ -30,40 +30,28 @@ init_https(Ref, ProtoOpts, Config) ->
|
||||||
Port = ranch:get_port(Ref),
|
Port = ranch:get_port(Ref),
|
||||||
[{type, ssl}, {protocol, http}, {port, Port}, {opts, Opts}|Config].
|
[{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 group of listeners used by most suites.
|
||||||
|
|
||||||
common_all() ->
|
common_all() ->
|
||||||
[
|
[
|
||||||
{group, http},
|
{group, http},
|
||||||
{group, https},
|
{group, https},
|
||||||
{group, spdy},
|
|
||||||
{group, http_compress},
|
{group, http_compress},
|
||||||
{group, https_compress},
|
{group, https_compress}
|
||||||
{group, spdy_compress}
|
|
||||||
].
|
].
|
||||||
|
|
||||||
common_groups(Tests) ->
|
common_groups(Tests) ->
|
||||||
[
|
[
|
||||||
{http, [parallel], Tests},
|
{http, [parallel], Tests},
|
||||||
{https, [parallel], Tests},
|
{https, [parallel], Tests},
|
||||||
{spdy, [parallel], Tests},
|
|
||||||
{http_compress, [parallel], Tests},
|
{http_compress, [parallel], Tests},
|
||||||
{https_compress, [parallel], Tests},
|
{https_compress, [parallel], Tests}
|
||||||
{spdy_compress, [parallel], Tests}
|
|
||||||
].
|
].
|
||||||
|
|
||||||
init_common_groups(Name = http, Config, Mod) ->
|
init_common_groups(Name = http, Config, Mod) ->
|
||||||
init_http(Name, #{env => #{dispatch => Mod:init_dispatch(Config)}}, Config);
|
init_http(Name, #{env => #{dispatch => Mod:init_dispatch(Config)}}, Config);
|
||||||
init_common_groups(Name = https, Config, Mod) ->
|
init_common_groups(Name = https, Config, Mod) ->
|
||||||
init_https(Name, #{env => #{dispatch => Mod:init_dispatch(Config)}}, Config);
|
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_common_groups(Name = http_compress, Config, Mod) ->
|
||||||
init_http(Name, #{
|
init_http(Name, #{
|
||||||
env => #{dispatch => Mod:init_dispatch(Config)},
|
env => #{dispatch => Mod:init_dispatch(Config)},
|
||||||
|
@ -73,11 +61,6 @@ init_common_groups(Name = https_compress, Config, Mod) ->
|
||||||
init_https(Name, #{
|
init_https(Name, #{
|
||||||
env => #{dispatch => Mod:init_dispatch(Config)},
|
env => #{dispatch => Mod:init_dispatch(Config)},
|
||||||
compress => true
|
compress => true
|
||||||
}, Config);
|
|
||||||
init_common_groups(Name = spdy_compress, Config, Mod) ->
|
|
||||||
init_spdy(Name, #{
|
|
||||||
env => #{dispatch => Mod:init_dispatch(Config)},
|
|
||||||
compress => true
|
|
||||||
}, Config).
|
}, Config).
|
||||||
|
|
||||||
%% Support functions for testing using Gun.
|
%% Support functions for testing using Gun.
|
||||||
|
|
|
@ -1,147 +0,0 @@
|
||||||
%% Copyright (c) 2013-2014, Loïc Hoguin <essen@ninenines.eu>
|
|
||||||
%%
|
|
||||||
%% 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.
|
|
Loading…
Add table
Add a link
Reference in a new issue