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}).
|
||||
----
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
When the body is too large, the first call will return
|
||||
|
|
|
@ -120,8 +120,8 @@ request's URI.
|
|||
[source,erlang]
|
||||
----
|
||||
read_body_opts() :: #{
|
||||
length => non_neg_integer(),
|
||||
period => non_neg_integer(),
|
||||
length => non_neg_integer() | auto,
|
||||
period => non_neg_integer() | infinity,
|
||||
timeout => timeout()
|
||||
}
|
||||
----
|
||||
|
@ -130,6 +130,10 @@ Body reading options.
|
|||
|
||||
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()
|
||||
|
||||
[source,erlang]
|
||||
|
|
|
@ -36,6 +36,22 @@ The atom `ok` is always returned. It can be safely ignored.
|
|||
|
||||
== 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
|
||||
[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
|
||||
message was received in that interval. The timeout should be
|
||||
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
|
||||
|
||||
|
@ -86,6 +93,9 @@ body has been read.
|
|||
|
||||
== 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`.
|
||||
|
||||
== Examples
|
||||
|
|
|
@ -45,8 +45,49 @@ The default stream handler spawns the request process
|
|||
and receives its exit signal when it terminates. It
|
||||
will stop the stream once its receives it.
|
||||
|
||||
// @todo It also implements the read_body mechanism.
|
||||
// Note that cowboy_stream_h sends the 100-continue automatically.
|
||||
Because this stream handler converts events from the
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
* *2.11*: Introduce body reading using auto mode.
|
||||
* *2.0*: Module introduced.
|
||||
|
||||
== 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_decompress_h(3)[cowboy_decompress_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) ->
|
||||
Length = maps:get(length, Opts, 8000000),
|
||||
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(),
|
||||
cast({read_body, self(), Ref, Length, Period}, Req),
|
||||
receive
|
||||
|
|
|
@ -25,6 +25,8 @@ echo(<<"read_body">>, Req0, Opts) ->
|
|||
timer:sleep(500),
|
||||
cowboy_req: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>> ->
|
||||
{_, _, Req1} = read_body(Req0, <<>>),
|
||||
Length = cowboy_req:body_length(Req1),
|
||||
|
@ -122,6 +124,25 @@ read_body(Req0, Acc) ->
|
|||
{more, Data, Req} -> read_body(Req, << Acc/binary, Data/binary >>)
|
||||
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_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]);
|
||||
|
|
|
@ -64,6 +64,8 @@ init_dispatch(Config) ->
|
|||
{"/opts/:key/timeout", echo_h, #{timeout => 1000, crash => true}},
|
||||
{"/100-continue/:key", echo_h, []},
|
||||
{"/full/:key", echo_h, []},
|
||||
{"/auto-sync/:key", echo_h, []},
|
||||
{"/auto-async/:key", echo_h, []},
|
||||
{"/spawn/:key", echo_h, []},
|
||||
{"/no/: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),
|
||||
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) ->
|
||||
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),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue