mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Move body length count to cowboy_stream_h instead of protocols
The documentation was correct, the code was not. This should make it easier to implement new protocols. Note that for HTTP/2 we will need to add some form of counting later on to check for malformed requests, but we can do simpler and just reduce from the expected length and then check if that's 0 when IsFin=fin.
This commit is contained in:
parent
a6126306a2
commit
c09b10190b
4 changed files with 48 additions and 37 deletions
|
@ -709,11 +709,13 @@ parse_body(Buffer, State=#state{in_streamid=StreamID, in_state=
|
||||||
%% @todo Asks for 0 or more bytes.
|
%% @todo Asks for 0 or more bytes.
|
||||||
{data, StreamID, nofin, Data, State#state{in_state=
|
{data, StreamID, nofin, Data, State#state{in_state=
|
||||||
PS#ps_body{transfer_decode_state=TState}}, Rest};
|
PS#ps_body{transfer_decode_state=TState}}, Rest};
|
||||||
{done, TotalLength, Rest} ->
|
%% @todo We probably want to confirm that the total length
|
||||||
{data, StreamID, {fin, TotalLength}, <<>>, set_timeout(
|
%% is the same as the content-length, if one was provided.
|
||||||
|
{done, _TotalLength, Rest} ->
|
||||||
|
{data, StreamID, fin, <<>>, set_timeout(
|
||||||
State#state{in_streamid=StreamID + 1, in_state=#ps_request_line{}}), Rest};
|
State#state{in_streamid=StreamID + 1, in_state=#ps_request_line{}}), Rest};
|
||||||
{done, Data, TotalLength, Rest} ->
|
{done, Data, _TotalLength, Rest} ->
|
||||||
{data, StreamID, {fin, TotalLength}, Data, set_timeout(
|
{data, StreamID, fin, Data, set_timeout(
|
||||||
State#state{in_streamid=StreamID + 1, in_state=#ps_request_line{}}), Rest}
|
State#state{in_streamid=StreamID + 1, in_state=#ps_request_line{}}), Rest}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
|
|
@ -49,9 +49,7 @@
|
||||||
%% Whether we finished receiving data.
|
%% Whether we finished receiving data.
|
||||||
remote = nofin :: cowboy_stream:fin(),
|
remote = nofin :: cowboy_stream:fin(),
|
||||||
%% Remote flow control window (how much we accept to receive).
|
%% Remote flow control window (how much we accept to receive).
|
||||||
remote_window :: integer(),
|
remote_window :: integer()
|
||||||
%% Request body length.
|
|
||||||
body_length = 0 :: non_neg_integer()
|
|
||||||
}).
|
}).
|
||||||
|
|
||||||
-type stream() :: #stream{}.
|
-type stream() :: #stream{}.
|
||||||
|
@ -289,22 +287,15 @@ frame(State=#state{client_streamid=LastStreamID}, {data, StreamID, _, _})
|
||||||
terminate(State, {connection_error, protocol_error,
|
terminate(State, {connection_error, protocol_error,
|
||||||
'DATA frame received on a stream in idle state. (RFC7540 5.1)'});
|
'DATA frame received on a stream in idle state. (RFC7540 5.1)'});
|
||||||
frame(State0=#state{remote_window=ConnWindow, streams=Streams},
|
frame(State0=#state{remote_window=ConnWindow, streams=Streams},
|
||||||
{data, StreamID, IsFin0, Data}) ->
|
{data, StreamID, IsFin, Data}) ->
|
||||||
DataLen = byte_size(Data),
|
DataLen = byte_size(Data),
|
||||||
State = State0#state{remote_window=ConnWindow - DataLen},
|
State = State0#state{remote_window=ConnWindow - DataLen},
|
||||||
case lists:keyfind(StreamID, #stream.id, Streams) of
|
case lists:keyfind(StreamID, #stream.id, Streams) of
|
||||||
Stream = #stream{state=StreamState0, remote=nofin,
|
Stream = #stream{state=StreamState0, remote=nofin, remote_window=StreamWindow} ->
|
||||||
remote_window=StreamWindow, body_length=Len0} ->
|
|
||||||
Len = Len0 + DataLen,
|
|
||||||
IsFin = case IsFin0 of
|
|
||||||
fin -> {fin, Len};
|
|
||||||
nofin -> nofin
|
|
||||||
end,
|
|
||||||
try cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of
|
try cowboy_stream:data(StreamID, IsFin, Data, StreamState0) of
|
||||||
{Commands, StreamState} ->
|
{Commands, StreamState} ->
|
||||||
commands(State,
|
commands(State, Stream#stream{state=StreamState, remote=IsFin,
|
||||||
Stream#stream{state=StreamState, remote_window=StreamWindow - DataLen,
|
remote_window=StreamWindow - DataLen}, Commands)
|
||||||
body_length=Len}, Commands)
|
|
||||||
catch Class:Exception ->
|
catch Class:Exception ->
|
||||||
cowboy_stream:report_error(data,
|
cowboy_stream:report_error(data,
|
||||||
[StreamID, IsFin, Data, StreamState0],
|
[StreamID, IsFin, Data, StreamState0],
|
||||||
|
|
|
@ -440,7 +440,7 @@ read_body(Req=#{pid := Pid, streamid := StreamID}, Opts) ->
|
||||||
receive
|
receive
|
||||||
{request_body, Ref, nofin, Body} ->
|
{request_body, Ref, nofin, Body} ->
|
||||||
{more, Body, Req};
|
{more, Body, Req};
|
||||||
{request_body, Ref, {fin, BodyLength}, Body} ->
|
{request_body, Ref, fin, BodyLength, Body} ->
|
||||||
{ok, Body, set_body_length(Req, BodyLength)}
|
{ok, Body, set_body_length(Req, BodyLength)}
|
||||||
after Timeout ->
|
after Timeout ->
|
||||||
exit(timeout)
|
exit(timeout)
|
||||||
|
|
|
@ -34,7 +34,8 @@
|
||||||
read_body_timer_ref = undefined :: reference() | undefined,
|
read_body_timer_ref = undefined :: reference() | undefined,
|
||||||
read_body_length = 0 :: non_neg_integer() | infinity,
|
read_body_length = 0 :: non_neg_integer() | infinity,
|
||||||
read_body_is_fin = nofin :: nofin | {fin, non_neg_integer()},
|
read_body_is_fin = nofin :: nofin | {fin, non_neg_integer()},
|
||||||
read_body_buffer = <<>> :: binary()
|
read_body_buffer = <<>> :: binary(),
|
||||||
|
body_length = 0 :: non_neg_integer()
|
||||||
}).
|
}).
|
||||||
|
|
||||||
%% @todo For shutting down children we need to have a timeout before we terminate
|
%% @todo For shutting down children we need to have a timeout before we terminate
|
||||||
|
@ -54,17 +55,31 @@ init(_StreamID, Req=#{ref := Ref}, Opts) ->
|
||||||
%% If we accumulated enough data or IsFin=fin, send it.
|
%% If we accumulated enough data or IsFin=fin, send it.
|
||||||
%% If not, buffer it.
|
%% If not, buffer it.
|
||||||
%% If not, buffer it.
|
%% If not, buffer it.
|
||||||
|
|
||||||
-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
|
-spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
|
||||||
-> {cowboy_stream:commands(), State} when State::#state{}.
|
-> {cowboy_stream:commands(), State} when State::#state{}.
|
||||||
data(_StreamID, IsFin, Data, State=#state{read_body_ref=undefined, read_body_buffer=Buffer}) ->
|
data(_StreamID, IsFin, Data, State=#state{
|
||||||
{[], State#state{read_body_is_fin=IsFin, read_body_buffer= << Buffer/binary, Data/binary >>}};
|
read_body_ref=undefined, read_body_buffer=Buffer, body_length=BodyLen}) ->
|
||||||
data(_StreamID, nofin, Data, State=#state{read_body_length=Length, read_body_buffer=Buffer}) when byte_size(Data) + byte_size(Buffer) < Length ->
|
{[], State#state{
|
||||||
{[], State#state{read_body_buffer= << Buffer/binary, Data/binary >>}};
|
read_body_is_fin=IsFin,
|
||||||
|
read_body_buffer= << Buffer/binary, Data/binary >>,
|
||||||
|
body_length=BodyLen + byte_size(Data)}};
|
||||||
|
data(_StreamID, nofin, Data, State=#state{
|
||||||
|
read_body_length=ReadLen, read_body_buffer=Buffer, body_length=BodyLen})
|
||||||
|
when byte_size(Data) + byte_size(Buffer) < ReadLen ->
|
||||||
|
{[], State#state{
|
||||||
|
read_body_buffer= << Buffer/binary, Data/binary >>,
|
||||||
|
body_length=BodyLen + byte_size(Data)}};
|
||||||
data(_StreamID, IsFin, Data, State=#state{pid=Pid, read_body_ref=Ref,
|
data(_StreamID, IsFin, Data, State=#state{pid=Pid, read_body_ref=Ref,
|
||||||
read_body_timer_ref=TRef, read_body_buffer=Buffer}) ->
|
read_body_timer_ref=TRef, read_body_buffer=Buffer, body_length=BodyLen0}) ->
|
||||||
|
BodyLen = BodyLen0 + byte_size(Data),
|
||||||
ok = erlang:cancel_timer(TRef, [{async, true}, {info, false}]),
|
ok = erlang:cancel_timer(TRef, [{async, true}, {info, false}]),
|
||||||
Pid ! {request_body, Ref, IsFin, << Buffer/binary, Data/binary >>},
|
send_request_body(Pid, Ref, IsFin, BodyLen, <<Buffer/binary, Data/binary>>),
|
||||||
{[], State#state{read_body_ref=undefined, read_body_timer_ref=undefined, read_body_buffer= <<>>}}.
|
{[], State#state{
|
||||||
|
read_body_ref=undefined,
|
||||||
|
read_body_timer_ref=undefined,
|
||||||
|
read_body_buffer= <<>>,
|
||||||
|
body_length=BodyLen}}.
|
||||||
|
|
||||||
-spec info(cowboy_stream:streamid(), any(), State)
|
-spec info(cowboy_stream:streamid(), any(), State)
|
||||||
-> {cowboy_stream:commands(), State} when State::#state{}.
|
-> {cowboy_stream:commands(), State} when State::#state{}.
|
||||||
|
@ -86,15 +101,11 @@ info(StreamID, Exit = {'EXIT', Pid, {Reason, Stacktrace}}, State=#state{ref=Ref,
|
||||||
{error_response, 500, #{<<"content-length">> => <<"0">>}, <<>>},
|
{error_response, 500, #{<<"content-length">> => <<"0">>}, <<>>},
|
||||||
{internal_error, Exit, 'Stream process crashed.'}
|
{internal_error, Exit, 'Stream process crashed.'}
|
||||||
], State};
|
], State};
|
||||||
%% Request body, no body buffer but IsFin=fin.
|
|
||||||
%info(_StreamID, {read_body, Ref, _, _}, State=#state{pid=Pid, read_body_is_fin=fin, read_body_buffer= <<>>}) ->
|
|
||||||
% Pid ! {request_body, Ref, fin, <<>>},
|
|
||||||
% {[], State};
|
|
||||||
%% Request body, body buffered large enough or complete.
|
%% Request body, body buffered large enough or complete.
|
||||||
info(_StreamID, {read_body, Ref, Length, _},
|
info(_StreamID, {read_body, Ref, Length, _}, State=#state{pid=Pid,
|
||||||
State=#state{pid=Pid, read_body_is_fin=IsFin, read_body_buffer=Data})
|
read_body_is_fin=IsFin, read_body_buffer=Buffer, body_length=BodyLen})
|
||||||
when element(1, IsFin) =:= fin; byte_size(Data) >= Length ->
|
when IsFin =:= fin; byte_size(Buffer) >= Length ->
|
||||||
Pid ! {request_body, Ref, IsFin, Data},
|
send_request_body(Pid, Ref, IsFin, BodyLen, Buffer),
|
||||||
{[], State#state{read_body_buffer= <<>>}};
|
{[], State#state{read_body_buffer= <<>>}};
|
||||||
%% Request body, not enough to send yet.
|
%% Request body, not enough to send yet.
|
||||||
info(StreamID, {read_body, Ref, Length, Period}, State) ->
|
info(StreamID, {read_body, Ref, Length, Period}, State) ->
|
||||||
|
@ -102,8 +113,8 @@ info(StreamID, {read_body, Ref, Length, Period}, State) ->
|
||||||
{[{flow, Length}], State#state{read_body_ref=Ref, read_body_timer_ref=TRef, read_body_length=Length}};
|
{[{flow, Length}], State#state{read_body_ref=Ref, read_body_timer_ref=TRef, read_body_length=Length}};
|
||||||
%% Request body reading timeout; send what we got.
|
%% Request body reading timeout; send what we got.
|
||||||
info(_StreamID, {read_body_timeout, Ref}, State=#state{pid=Pid, read_body_ref=Ref,
|
info(_StreamID, {read_body_timeout, Ref}, State=#state{pid=Pid, read_body_ref=Ref,
|
||||||
read_body_is_fin=IsFin, read_body_buffer=Buffer}) ->
|
read_body_is_fin=IsFin, read_body_buffer=Buffer, body_length=BodyLen}) ->
|
||||||
Pid ! {request_body, Ref, IsFin, Buffer},
|
send_request_body(Pid, Ref, IsFin, BodyLen, Buffer),
|
||||||
{[], State#state{read_body_ref=undefined, read_body_timer_ref=undefined, read_body_buffer= <<>>}};
|
{[], State#state{read_body_ref=undefined, read_body_timer_ref=undefined, read_body_buffer= <<>>}};
|
||||||
info(_StreamID, {read_body_timeout, _}, State) ->
|
info(_StreamID, {read_body_timeout, _}, State) ->
|
||||||
{[], State};
|
{[], State};
|
||||||
|
@ -132,6 +143,13 @@ terminate(_StreamID, _Reason, _State) ->
|
||||||
early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
|
early_error(StreamID, Reason, PartialReq, Resp, Opts) ->
|
||||||
cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).
|
cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp, Opts).
|
||||||
|
|
||||||
|
send_request_body(Pid, Ref, nofin, _, Data) ->
|
||||||
|
Pid ! {request_body, Ref, nofin, Data},
|
||||||
|
ok;
|
||||||
|
send_request_body(Pid, Ref, fin, BodyLen, Data) ->
|
||||||
|
Pid ! {request_body, Ref, fin, BodyLen, Data},
|
||||||
|
ok.
|
||||||
|
|
||||||
%% We use ~999999p here instead of ~w because the latter doesn't
|
%% We use ~999999p here instead of ~w because the latter doesn't
|
||||||
%% support printable strings.
|
%% support printable strings.
|
||||||
report_crash(_, _, _, normal, _) ->
|
report_crash(_, _, _, normal, _) ->
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue