mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +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
|
||||
[Sameroom](https://sameroom.io).
|
||||
|
||||
The SPDY implementation was sponsored by
|
||||
The original SPDY implementation was sponsored by
|
||||
[LeoFS Cloud Storage](http://leo-project.net/leofs/).
|
||||
It has since been superseded by HTTP/2.
|
||||
|
||||
Online documentation
|
||||
--------------------
|
||||
|
|
|
@ -33,7 +33,7 @@ capitalize_hook(Status, Headers, Body, Req) ->
|
|||
cowboy_req:reply(Status, Headers2, Body, Req).
|
||||
----
|
||||
|
||||
Note that SPDY clients do not have that particular issue
|
||||
Note that HTTP/2 clients do not have that particular issue
|
||||
because the specification explicitly says all headers are
|
||||
lowercase, unlike HTTP which allows any case but treats
|
||||
them as case insensitive.
|
||||
|
|
|
@ -106,7 +106,7 @@ new messages, and perform the operations required by only activating
|
|||
the required parts of the system.
|
||||
|
||||
The more recent Web technologies, like Websocket of course, but also
|
||||
SPDY and HTTP/2.0, are all fully asynchronous protocols. The concept
|
||||
HTTP/2.0, are all fully asynchronous protocols. The concept
|
||||
of requests and responses is retained of course, but anything could
|
||||
be sent in between, by both the client or the browser, and the
|
||||
responses could also be received in a completely different order.
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
Cowboy is a small, fast and modular HTTP server written in Erlang.
|
||||
|
||||
Cowboy aims to provide a complete HTTP stack, including its derivatives
|
||||
SPDY, Websocket and REST. Cowboy currently supports HTTP/1.0, HTTP/1.1,
|
||||
Websocket and REST. Cowboy currently supports HTTP/1.0, HTTP/1.1, HTTP/2,
|
||||
Websocket (all implemented drafts + standard) and Webmachine-based REST.
|
||||
|
||||
Cowboy is a high quality project. It has a small code base, is very
|
||||
|
|
|
@ -180,35 +180,21 @@ A Websocket connection can be used to transfer any kind of data,
|
|||
small or big, text or binary. Because of this Websocket is
|
||||
sometimes used for communication between systems.
|
||||
|
||||
=== SPDY
|
||||
=== HTTP/2
|
||||
|
||||
SPDY is an attempt to reduce page loading time by opening a
|
||||
HTTP/2 is an attempt to reduce page loading time by opening a
|
||||
single connection per server, keeping it open for subsequent
|
||||
requests, and also by compressing the HTTP headers to reduce
|
||||
the size of requests.
|
||||
|
||||
SPDY is compatible with HTTP/1.1 semantics, and is actually
|
||||
HTTP/2 is compatible with HTTP/1.1 semantics, and is actually
|
||||
just a different way of performing HTTP requests and responses,
|
||||
by using binary frames instead of a text-based protocol.
|
||||
SPDY also allows the server to send extra responses following
|
||||
HTTP/2 also allows the server to send extra responses following
|
||||
a request. This is meant to allow sending the resources
|
||||
associated with the request before the client requests them,
|
||||
saving latency when loading websites.
|
||||
|
||||
SPDY is an experiment that has proven successful and is used
|
||||
as the basis for the HTTP/2.0 standard.
|
||||
|
||||
Browsers make use of TLS Next Protocol Negotiation to upgrade
|
||||
to a SPDY connection seamlessly if the protocol supports it.
|
||||
|
||||
The protocol itself has a few shortcomings which are being
|
||||
fixed in HTTP/2.0.
|
||||
|
||||
=== HTTP/2.0
|
||||
|
||||
HTTP/2.0 is the long-awaited update to the HTTP/1.1 protocol.
|
||||
It is based on SPDY although a lot has been improved at the
|
||||
time of writing.
|
||||
|
||||
HTTP/2.0 is an asynchronous two-ways communication channel
|
||||
between two endpoints.
|
||||
Browsers make use of TLS Application-Layer Protocol Negotiation
|
||||
extension to upgrade to an HTTP/2 connection seamlessly if the
|
||||
server supports it.
|
||||
|
|
|
@ -55,7 +55,7 @@ HTTP/1.1 allows the client to request that the server
|
|||
keeps the connection alive. This mechanism is described
|
||||
in the next section.
|
||||
|
||||
SPDY is designed to allow sending multiple requests
|
||||
HTTP/2 is designed to allow sending multiple requests
|
||||
asynchronously on the same connection. Details on what
|
||||
this means for your application is described in this
|
||||
chapter.
|
||||
|
@ -126,9 +126,9 @@ static files for example.
|
|||
|
||||
This is handled automatically by the server.
|
||||
|
||||
=== Asynchronous requests (SPDY)
|
||||
=== Asynchronous requests (HTTP/2)
|
||||
|
||||
In SPDY, the client can send a request at any time.
|
||||
In HTTP/2, the client can send a request at any time.
|
||||
And the server can send a response at any time too.
|
||||
|
||||
This means for example that the client does not need
|
||||
|
@ -142,7 +142,7 @@ Cowboy creates a new process for each request, and these
|
|||
processes are managed by another process that handles the
|
||||
connection itself.
|
||||
|
||||
SPDY servers may also decide to send resources to the
|
||||
HTTP/2 servers may also decide to send resources to the
|
||||
client before the client requests them. This is especially
|
||||
useful for sending static files associated with the HTML
|
||||
page requested, as this reduces the latency of the overall
|
||||
|
|
|
@ -67,16 +67,6 @@ ProtoOpts = cowboy_protocol:opts():: HTTP protocol options.
|
|||
Start listening for HTTPS connections. Returns the pid for this
|
||||
listener's supervisor.
|
||||
|
||||
=== start_spdy(Ref, NbAcceptors, TransOpts, ProtoOpts) -> {ok, pid()}
|
||||
|
||||
Ref = ranch:ref():: Listener name.
|
||||
NbAcceptors = non_neg_integer():: Number of acceptor processes.
|
||||
TransOpts = ranch_ssl:opts():: SSL transport options.
|
||||
ProtoOpts = cowboy_spdy:opts():: SPDY protocol options.
|
||||
|
||||
Start listening for SPDY connections. Returns the pid for this
|
||||
listener's supervisor.
|
||||
|
||||
=== stop_listener(Ref) -> ok | {error, not_found}
|
||||
|
||||
Ref = ranch:ref():: Listener name.
|
||||
|
|
|
@ -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, [
|
||||
{description, "Small, fast, modular HTTP server."},
|
||||
{vsn, "2.0.0-pre.2"},
|
||||
{modules, ['cowboy','cowboy_app','cowboy_bstr','cowboy_clear','cowboy_clock','cowboy_constraints','cowboy_handler','cowboy_http','cowboy_http2','cowboy_loop','cowboy_middleware','cowboy_protocol','cowboy_req','cowboy_rest','cowboy_router','cowboy_spdy','cowboy_static','cowboy_stream','cowboy_stream_h','cowboy_sub_protocol','cowboy_sup','cowboy_tls','cowboy_websocket']},
|
||||
{modules, ['cowboy','cowboy_app','cowboy_bstr','cowboy_clear','cowboy_clock','cowboy_constraints','cowboy_handler','cowboy_http','cowboy_http2','cowboy_loop','cowboy_middleware','cowboy_req','cowboy_rest','cowboy_router','cowboy_static','cowboy_stream','cowboy_stream_h','cowboy_sub_protocol','cowboy_sup','cowboy_tls','cowboy_websocket']},
|
||||
{registered, [cowboy_sup,cowboy_clock]},
|
||||
{applications, [kernel,stdlib,crypto,cowlib,ranch]},
|
||||
{mod, {cowboy_app, []}}
|
||||
|
|
|
@ -52,8 +52,8 @@ start_tls(Ref, NbAcceptors, TransOpts0, ProtoOpts)
|
|||
when is_integer(NbAcceptors), NbAcceptors > 0 ->
|
||||
TransOpts = [
|
||||
connection_type(ProtoOpts),
|
||||
{next_protocols_advertised, [<<"h2">>, <<"spdy/3">>, <<"http/1.1">>]},
|
||||
{alpn_preferred_protocols, [<<"h2">>, <<"spdy/3">>, <<"http/1.1">>]}
|
||||
{next_protocols_advertised, [<<"h2">>, <<"http/1.1">>]},
|
||||
{alpn_preferred_protocols, [<<"h2">>, <<"http/1.1">>]}
|
||||
|TransOpts0],
|
||||
ranch:start_listener(Ref, NbAcceptors, ranch_ssl, TransOpts, cowboy_tls, ProtoOpts).
|
||||
|
||||
|
|
|
@ -769,11 +769,6 @@ response_headers(Headers, Req) ->
|
|||
% %% We stream the response body until we close the connection.
|
||||
% RespConn = close,
|
||||
% {RespType, Req2} = if
|
||||
% Transport =:= cowboy_spdy ->
|
||||
% response(Status, Headers, RespHeaders, [
|
||||
% {<<"date">>, cowboy_clock:rfc1123()},
|
||||
% {<<"server">>, <<"Cowboy">>}
|
||||
% ], stream, Req);
|
||||
% true ->
|
||||
% response(Status, Headers, RespHeaders, [
|
||||
% {<<"connection">>, <<"close">>},
|
||||
|
@ -896,9 +891,6 @@ chunk(Data, #{pid := Pid, streamid := StreamID}) ->
|
|||
|
||||
%% If ever made public, need to send nothing if HEAD.
|
||||
-spec last_chunk(Req) -> Req when Req::req().
|
||||
last_chunk(Req=#http_req{socket=Socket, transport=cowboy_spdy}) ->
|
||||
_ = cowboy_spdy:stream_close(Socket),
|
||||
Req#http_req{resp_state=done};
|
||||
last_chunk(Req=#http_req{socket=Socket, transport=Transport}) ->
|
||||
_ = Transport:send(Socket, <<"0\r\n\r\n">>),
|
||||
Req#http_req{resp_state=done}.
|
||||
|
@ -1028,15 +1020,6 @@ to_list(Req) ->
|
|||
%-spec chunked_response(cowboy:http_status(), cowboy:http_headers(), Req) ->
|
||||
% {normal | hook, Req} when Req::req().
|
||||
%chunked_response(Status, Headers, Req=#http_req{
|
||||
% transport=cowboy_spdy, resp_state=waiting,
|
||||
% resp_headers=RespHeaders}) ->
|
||||
% {RespType, Req2} = response(Status, Headers, RespHeaders, [
|
||||
% {<<"date">>, cowboy_clock:rfc1123()},
|
||||
% {<<"server">>, <<"Cowboy">>}
|
||||
% ], stream, Req),
|
||||
% {RespType, Req2#http_req{resp_state=chunks,
|
||||
% resp_headers=[], resp_body= <<>>}};
|
||||
%chunked_response(Status, Headers, Req=#http_req{
|
||||
% version=Version, connection=Connection,
|
||||
% resp_state=RespState, resp_headers=RespHeaders})
|
||||
% when RespState =:= waiting; RespState =:= waiting_stream ->
|
||||
|
@ -1094,14 +1077,6 @@ response(Status, Headers, RespHeaders, DefaultHeaders, Body, Req=#http_req{
|
|||
end
|
||||
end,
|
||||
ReplyType = case Req2#http_req.resp_state of
|
||||
waiting when Transport =:= cowboy_spdy, Body =:= stream ->
|
||||
cowboy_spdy:stream_reply(Socket, status(Status2), FullHeaders2),
|
||||
ReqPid ! {?MODULE, resp_sent},
|
||||
normal;
|
||||
waiting when Transport =:= cowboy_spdy ->
|
||||
cowboy_spdy:reply(Socket, status(Status2), FullHeaders2, Body),
|
||||
ReqPid ! {?MODULE, resp_sent},
|
||||
normal;
|
||||
RespState when RespState =:= waiting; RespState =:= waiting_stream ->
|
||||
HTTPVer = atom_to_binary(Version, latin1),
|
||||
StatusLine = << HTTPVer/binary, " ",
|
||||
|
|
|
@ -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
|
||||
{ok, <<"h2">>} ->
|
||||
init(Parent, Ref, Socket, Transport, Opts, cowboy_http2);
|
||||
%% @todo Implement cowboy_spdy and cowboy_http.
|
||||
{ok, <<"spdy/3">>} ->
|
||||
init(Parent, Ref, Socket, Transport, Opts, cowboy_spdy);
|
||||
_ -> %% http/1.1 or no protocol negotiated.
|
||||
init(Parent, Ref, Socket, Transport, Opts, cowboy_http)
|
||||
end.
|
||||
|
|
|
@ -30,40 +30,28 @@ init_https(Ref, ProtoOpts, Config) ->
|
|||
Port = ranch:get_port(Ref),
|
||||
[{type, ssl}, {protocol, http}, {port, Port}, {opts, Opts}|Config].
|
||||
|
||||
init_spdy(Ref, ProtoOpts, Config) ->
|
||||
Opts = ct_helper:get_certs_from_ets(),
|
||||
{ok, _} = cowboy:start_tls(Ref, 100, Opts ++ [{port, 0}], ProtoOpts),
|
||||
Port = ranch:get_port(Ref),
|
||||
[{type, ssl}, {protocol, spdy}, {port, Port}, {opts, Opts}|Config].
|
||||
|
||||
%% Common group of listeners used by most suites.
|
||||
|
||||
common_all() ->
|
||||
[
|
||||
{group, http},
|
||||
{group, https},
|
||||
{group, spdy},
|
||||
{group, http_compress},
|
||||
{group, https_compress},
|
||||
{group, spdy_compress}
|
||||
{group, https_compress}
|
||||
].
|
||||
|
||||
common_groups(Tests) ->
|
||||
[
|
||||
{http, [parallel], Tests},
|
||||
{https, [parallel], Tests},
|
||||
{spdy, [parallel], Tests},
|
||||
{http_compress, [parallel], Tests},
|
||||
{https_compress, [parallel], Tests},
|
||||
{spdy_compress, [parallel], Tests}
|
||||
{https_compress, [parallel], Tests}
|
||||
].
|
||||
|
||||
init_common_groups(Name = http, Config, Mod) ->
|
||||
init_http(Name, #{env => #{dispatch => Mod:init_dispatch(Config)}}, Config);
|
||||
init_common_groups(Name = https, Config, Mod) ->
|
||||
init_https(Name, #{env => #{dispatch => Mod:init_dispatch(Config)}}, Config);
|
||||
init_common_groups(Name = spdy, Config, Mod) ->
|
||||
init_https(Name, #{env => #{dispatch => Mod:init_dispatch(Config)}}, Config);
|
||||
init_common_groups(Name = http_compress, Config, Mod) ->
|
||||
init_http(Name, #{
|
||||
env => #{dispatch => Mod:init_dispatch(Config)},
|
||||
|
@ -73,11 +61,6 @@ init_common_groups(Name = https_compress, Config, Mod) ->
|
|||
init_https(Name, #{
|
||||
env => #{dispatch => Mod:init_dispatch(Config)},
|
||||
compress => true
|
||||
}, Config);
|
||||
init_common_groups(Name = spdy_compress, Config, Mod) ->
|
||||
init_spdy(Name, #{
|
||||
env => #{dispatch => Mod:init_dispatch(Config)},
|
||||
compress => true
|
||||
}, Config).
|
||||
|
||||
%% Support functions for testing using Gun.
|
||||
|
|
|
@ -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