mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Document body reading in auto mode
It is now tested both via cowboy_req:read_body and via cowboy_req:cast. Removes a bad example from the guide of body reading with period of infinity, which does not work.
This commit is contained in:
parent
c1490d7d55
commit
e4a78aaeb1
8 changed files with 137 additions and 21 deletions
|
@ -74,18 +74,34 @@ only up to 1MB for up to 5 seconds:
|
||||||
#{length => 1000000, period => 5000}).
|
#{length => 1000000, period => 5000}).
|
||||||
----
|
----
|
||||||
|
|
||||||
You may also disable the length limit:
|
|
||||||
|
|
||||||
[source,erlang]
|
|
||||||
{ok, Data, Req} = cowboy_req:read_body(Req0, #{length => infinity}).
|
|
||||||
|
|
||||||
This makes the function wait 15 seconds and return with
|
|
||||||
whatever arrived during that period. This is not
|
|
||||||
recommended for public facing applications.
|
|
||||||
|
|
||||||
These two options can effectively be used to control
|
These two options can effectively be used to control
|
||||||
the rate of transmission of the request body.
|
the rate of transmission of the request body.
|
||||||
|
|
||||||
|
It is also possible to asynchronously read the request
|
||||||
|
body using auto mode:
|
||||||
|
|
||||||
|
[source,erlang]
|
||||||
|
----
|
||||||
|
Ref = make_ref(),
|
||||||
|
cowboy_req:cast({read_body, self(), Ref, auto, infinity}, Req).
|
||||||
|
----
|
||||||
|
|
||||||
|
Cowboy will wait indefinitely for data and then send a
|
||||||
|
`request_body` message as soon as it has data available,
|
||||||
|
regardless of length.
|
||||||
|
|
||||||
|
[source,erlang]
|
||||||
|
----
|
||||||
|
receive
|
||||||
|
{request_body, Ref, nofin, Data} ->
|
||||||
|
do_something(Data);
|
||||||
|
{request_body, Ref, fin, _BodyLen, Data} ->
|
||||||
|
do_something(Data)
|
||||||
|
end.
|
||||||
|
----
|
||||||
|
|
||||||
|
Asynchronous reading of data pairs well with loop handlers.
|
||||||
|
|
||||||
=== Streaming the body
|
=== Streaming the body
|
||||||
|
|
||||||
When the body is too large, the first call will return
|
When the body is too large, the first call will return
|
||||||
|
|
|
@ -120,8 +120,8 @@ request's URI.
|
||||||
[source,erlang]
|
[source,erlang]
|
||||||
----
|
----
|
||||||
read_body_opts() :: #{
|
read_body_opts() :: #{
|
||||||
length => non_neg_integer(),
|
length => non_neg_integer() | auto,
|
||||||
period => non_neg_integer(),
|
period => non_neg_integer() | infinity,
|
||||||
timeout => timeout()
|
timeout => timeout()
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
@ -130,6 +130,10 @@ Body reading options.
|
||||||
|
|
||||||
The defaults are function-specific.
|
The defaults are function-specific.
|
||||||
|
|
||||||
|
Auto mode can be enabled by setting `length` to `auto`
|
||||||
|
and `period` to `infinity`. The period cannot be set
|
||||||
|
to `infinity` when auto mode isn't used.
|
||||||
|
|
||||||
=== req()
|
=== req()
|
||||||
|
|
||||||
[source,erlang]
|
[source,erlang]
|
||||||
|
|
|
@ -36,6 +36,22 @@ The atom `ok` is always returned. It can be safely ignored.
|
||||||
|
|
||||||
== Examples
|
== Examples
|
||||||
|
|
||||||
|
.Read the body using auto mode
|
||||||
|
[source,erlang]
|
||||||
|
----
|
||||||
|
read_body_auto_async(Req) ->
|
||||||
|
read_body_auto_async(Req, make_ref(), <<>>).
|
||||||
|
|
||||||
|
read_body_auto_async(Req, Ref, Acc) ->
|
||||||
|
cowboy_req:cast({read_body, self(), Ref, auto, infinity}, Req),
|
||||||
|
receive
|
||||||
|
{request_body, Ref, nofin, Data} ->
|
||||||
|
read_body_auto_async(Req, Ref, <<Acc/binary, Data/binary>>);
|
||||||
|
{request_body, Ref, fin, _BodyLen, Data} ->
|
||||||
|
{ok, <<Acc/binary, Data/binary>>, Req}
|
||||||
|
end.
|
||||||
|
----
|
||||||
|
|
||||||
.Increase the HTTP/1.1 idle timeout
|
.Increase the HTTP/1.1 idle timeout
|
||||||
[source,erlang]
|
[source,erlang]
|
||||||
----
|
----
|
||||||
|
|
|
@ -68,6 +68,13 @@ The `timeout` option is a safeguard in case the connection
|
||||||
process becomes unresponsive. The function will crash if no
|
process becomes unresponsive. The function will crash if no
|
||||||
message was received in that interval. The timeout should be
|
message was received in that interval. The timeout should be
|
||||||
larger than the period. It defaults to the period + 1 second.
|
larger than the period. It defaults to the period + 1 second.
|
||||||
|
+
|
||||||
|
Auto mode can be enabled by setting the `length` to `auto` and
|
||||||
|
the `period` to `infinity`. When auto mode is used, Cowboy will
|
||||||
|
send data to the handler as soon as it receives it, regardless
|
||||||
|
of its size. It will wait indefinitely until data is available.
|
||||||
|
Auto mode's main purpose is asynchronous body reading using
|
||||||
|
link:man:cowboy_req:cast(3)[cowboy_req:cast(3)].
|
||||||
|
|
||||||
== Return value
|
== Return value
|
||||||
|
|
||||||
|
@ -86,6 +93,9 @@ body has been read.
|
||||||
|
|
||||||
== Changelog
|
== Changelog
|
||||||
|
|
||||||
|
* *2.11*: The `length` option now accepts `auto` and the
|
||||||
|
period now accepts `infinity`. This adds support for
|
||||||
|
reading the body in auto mode.
|
||||||
* *2.0*: Function introduced. Replaces `body/1,2`.
|
* *2.0*: Function introduced. Replaces `body/1,2`.
|
||||||
|
|
||||||
== Examples
|
== Examples
|
||||||
|
|
|
@ -45,8 +45,49 @@ The default stream handler spawns the request process
|
||||||
and receives its exit signal when it terminates. It
|
and receives its exit signal when it terminates. It
|
||||||
will stop the stream once its receives it.
|
will stop the stream once its receives it.
|
||||||
|
|
||||||
// @todo It also implements the read_body mechanism.
|
Because this stream handler converts events from the
|
||||||
// Note that cowboy_stream_h sends the 100-continue automatically.
|
request process into commands, other stream handlers
|
||||||
|
may not work properly if they are executed after the
|
||||||
|
default stream handler. Always be mindful of in which
|
||||||
|
order stream handlers will get executed.
|
||||||
|
|
||||||
|
=== Request body
|
||||||
|
|
||||||
|
The default stream handler implements the `read_body`
|
||||||
|
mechanism. In addition to reading the body, the handler
|
||||||
|
will automatically handle the `expect: 100-continue`
|
||||||
|
header and send a 100 Continue response.
|
||||||
|
|
||||||
|
Normally one would use
|
||||||
|
link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)]
|
||||||
|
to read the request body. The default stream handler
|
||||||
|
will buffer data until the amount gets larger than the
|
||||||
|
requested length before sending it. Alternatively, it
|
||||||
|
will send whatever data it has when the period timeout
|
||||||
|
triggers. Depending on the protocol, the flow control
|
||||||
|
window is updated to allow receiving data for the
|
||||||
|
requested length.
|
||||||
|
|
||||||
|
The default stream handler also comes with an automatic
|
||||||
|
mode for reading the request body. This can be used by
|
||||||
|
sending the event message `{read_body, Pid, Ref, auto, infinity}`
|
||||||
|
using link:man:cowboy_req:cast(3)[cowboy_req:cast(3)].
|
||||||
|
The default stream handler will then send data as soon
|
||||||
|
as some becomes available using one of these two
|
||||||
|
messages depending on whether body reading was completed:
|
||||||
|
|
||||||
|
* `{request_body, Ref, nofin, Data}`
|
||||||
|
* `{request_body, Ref, fin, BodyLen, Data}`
|
||||||
|
|
||||||
|
Depending on the protocol, Cowboy will update the flow
|
||||||
|
control window using the size of the data that was read.
|
||||||
|
|
||||||
|
Auto mode automatically gets disabled after data has
|
||||||
|
been sent to the handler. Therefore in order to continue
|
||||||
|
reading data a `read_body` event message must be sent
|
||||||
|
after each `request_body` message.
|
||||||
|
|
||||||
|
=== Response
|
||||||
|
|
||||||
In addition it returns a command for any event message
|
In addition it returns a command for any event message
|
||||||
looking like one of the following commands: `inform`,
|
looking like one of the following commands: `inform`,
|
||||||
|
@ -54,14 +95,9 @@ looking like one of the following commands: `inform`,
|
||||||
`switch_protocol`. This is what allows the request
|
`switch_protocol`. This is what allows the request
|
||||||
process to send a response.
|
process to send a response.
|
||||||
|
|
||||||
// @todo Add set_options, which updates options dynamically.
|
|
||||||
|
|
||||||
Because this stream handler converts events from the
|
|
||||||
request process into commands, other stream handlers
|
|
||||||
may not work properly if they are executed
|
|
||||||
|
|
||||||
== Changelog
|
== Changelog
|
||||||
|
|
||||||
|
* *2.11*: Introduce body reading using auto mode.
|
||||||
* *2.0*: Module introduced.
|
* *2.0*: Module introduced.
|
||||||
|
|
||||||
== See also
|
== See also
|
||||||
|
@ -71,4 +107,5 @@ link:man:cowboy_stream(3)[cowboy_stream(3)],
|
||||||
link:man:cowboy_compress_h(3)[cowboy_compress_h(3)],
|
link:man:cowboy_compress_h(3)[cowboy_compress_h(3)],
|
||||||
link:man:cowboy_decompress_h(3)[cowboy_decompress_h(3)],
|
link:man:cowboy_decompress_h(3)[cowboy_decompress_h(3)],
|
||||||
link:man:cowboy_metrics_h(3)[cowboy_metrics_h(3)],
|
link:man:cowboy_metrics_h(3)[cowboy_metrics_h(3)],
|
||||||
link:man:cowboy_tracer_h(3)[cowboy_tracer_h(3)]
|
link:man:cowboy_tracer_h(3)[cowboy_tracer_h(3)],
|
||||||
|
link:man:cowboy_req:cast(3)[cowboy_req:cast(3)]
|
||||||
|
|
|
@ -521,7 +521,11 @@ read_body(Req=#{has_read_body := true}, _) ->
|
||||||
read_body(Req, Opts) ->
|
read_body(Req, Opts) ->
|
||||||
Length = maps:get(length, Opts, 8000000),
|
Length = maps:get(length, Opts, 8000000),
|
||||||
Period = maps:get(period, Opts, 15000),
|
Period = maps:get(period, Opts, 15000),
|
||||||
Timeout = maps:get(timeout, Opts, Period + 1000),
|
DefaultTimeout = case Period of
|
||||||
|
infinity -> infinity; %% infinity + 1000 = infinity.
|
||||||
|
_ -> Period + 1000
|
||||||
|
end,
|
||||||
|
Timeout = maps:get(timeout, Opts, DefaultTimeout),
|
||||||
Ref = make_ref(),
|
Ref = make_ref(),
|
||||||
cast({read_body, self(), Ref, Length, Period}, Req),
|
cast({read_body, self(), Ref, Length, Period}, Req),
|
||||||
receive
|
receive
|
||||||
|
|
|
@ -25,6 +25,8 @@ echo(<<"read_body">>, Req0, Opts) ->
|
||||||
timer:sleep(500),
|
timer:sleep(500),
|
||||||
cowboy_req:read_body(Req0);
|
cowboy_req:read_body(Req0);
|
||||||
<<"/full", _/bits>> -> read_body(Req0, <<>>);
|
<<"/full", _/bits>> -> read_body(Req0, <<>>);
|
||||||
|
<<"/auto-sync", _/bits>> -> read_body_auto_sync(Req0, <<>>);
|
||||||
|
<<"/auto-async", _/bits>> -> read_body_auto_async(Req0, <<>>);
|
||||||
<<"/length", _/bits>> ->
|
<<"/length", _/bits>> ->
|
||||||
{_, _, Req1} = read_body(Req0, <<>>),
|
{_, _, Req1} = read_body(Req0, <<>>),
|
||||||
Length = cowboy_req:body_length(Req1),
|
Length = cowboy_req:body_length(Req1),
|
||||||
|
@ -122,6 +124,25 @@ read_body(Req0, Acc) ->
|
||||||
{more, Data, Req} -> read_body(Req, << Acc/binary, Data/binary >>)
|
{more, Data, Req} -> read_body(Req, << Acc/binary, Data/binary >>)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
read_body_auto_sync(Req0, Acc) ->
|
||||||
|
Opts = #{length => auto, period => infinity},
|
||||||
|
case cowboy_req:read_body(Req0, Opts) of
|
||||||
|
{ok, Data, Req} -> {ok, << Acc/binary, Data/binary >>, Req};
|
||||||
|
{more, Data, Req} -> read_body_auto_sync(Req, << Acc/binary, Data/binary >>)
|
||||||
|
end.
|
||||||
|
|
||||||
|
read_body_auto_async(Req, Acc) ->
|
||||||
|
read_body_auto_async(Req, make_ref(), Acc).
|
||||||
|
|
||||||
|
read_body_auto_async(Req, ReadBodyRef, Acc) ->
|
||||||
|
cowboy_req:cast({read_body, self(), ReadBodyRef, auto, infinity}, Req),
|
||||||
|
receive
|
||||||
|
{request_body, ReadBodyRef, nofin, Data} ->
|
||||||
|
read_body_auto_async(Req, ReadBodyRef, <<Acc/binary, Data/binary>>);
|
||||||
|
{request_body, ReadBodyRef, fin, _, Data} ->
|
||||||
|
{ok, <<Acc/binary, Data/binary>>, Req}
|
||||||
|
end.
|
||||||
|
|
||||||
value_to_iodata(V) when is_integer(V) -> integer_to_binary(V);
|
value_to_iodata(V) when is_integer(V) -> integer_to_binary(V);
|
||||||
value_to_iodata(V) when is_atom(V) -> atom_to_binary(V, latin1);
|
value_to_iodata(V) when is_atom(V) -> atom_to_binary(V, latin1);
|
||||||
value_to_iodata(V) when is_list(V); is_tuple(V); is_map(V) -> io_lib:format("~999999p", [V]);
|
value_to_iodata(V) when is_list(V); is_tuple(V); is_map(V) -> io_lib:format("~999999p", [V]);
|
||||||
|
|
|
@ -64,6 +64,8 @@ init_dispatch(Config) ->
|
||||||
{"/opts/:key/timeout", echo_h, #{timeout => 1000, crash => true}},
|
{"/opts/:key/timeout", echo_h, #{timeout => 1000, crash => true}},
|
||||||
{"/100-continue/:key", echo_h, []},
|
{"/100-continue/:key", echo_h, []},
|
||||||
{"/full/:key", echo_h, []},
|
{"/full/:key", echo_h, []},
|
||||||
|
{"/auto-sync/:key", echo_h, []},
|
||||||
|
{"/auto-async/:key", echo_h, []},
|
||||||
{"/spawn/:key", echo_h, []},
|
{"/spawn/:key", echo_h, []},
|
||||||
{"/no/:key", echo_h, []},
|
{"/no/:key", echo_h, []},
|
||||||
{"/direct/:key/[...]", echo_h, []},
|
{"/direct/:key/[...]", echo_h, []},
|
||||||
|
@ -524,6 +526,12 @@ do_read_body_timeout(Path, Body, Config) ->
|
||||||
{response, _, 500, _} = gun:await(ConnPid, Ref, infinity),
|
{response, _, 500, _} = gun:await(ConnPid, Ref, infinity),
|
||||||
gun:close(ConnPid).
|
gun:close(ConnPid).
|
||||||
|
|
||||||
|
read_body_auto(Config) ->
|
||||||
|
doc("Read the request body using auto mode."),
|
||||||
|
<<0:80000000>> = do_body("POST", "/auto-sync/read_body", [], <<0:80000000>>, Config),
|
||||||
|
<<0:80000000>> = do_body("POST", "/auto-async/read_body", [], <<0:80000000>>, Config),
|
||||||
|
ok.
|
||||||
|
|
||||||
read_body_spawn(Config) ->
|
read_body_spawn(Config) ->
|
||||||
doc("Confirm we can use cowboy_req:read_body/1,2 from another process."),
|
doc("Confirm we can use cowboy_req:read_body/1,2 from another process."),
|
||||||
<<"hello world!">> = do_body("POST", "/spawn/read_body", [], "hello world!", Config),
|
<<"hello world!">> = do_body("POST", "/spawn/read_body", [], "hello world!", Config),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue