aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/src/guide/req_body.asciidoc30
-rw-r--r--doc/src/manual/cowboy_req.asciidoc8
-rw-r--r--doc/src/manual/cowboy_req.cast.asciidoc16
-rw-r--r--doc/src/manual/cowboy_req.read_body.asciidoc10
-rw-r--r--doc/src/manual/cowboy_stream_h.asciidoc55
-rw-r--r--src/cowboy_req.erl6
-rw-r--r--test/handlers/echo_h.erl21
-rw-r--r--test/req_SUITE.erl8
8 files changed, 135 insertions, 19 deletions
diff --git a/doc/src/guide/req_body.asciidoc b/doc/src/guide/req_body.asciidoc
index 4906811..88389f6 100644
--- a/doc/src/guide/req_body.asciidoc
+++ b/doc/src/guide/req_body.asciidoc
@@ -74,17 +74,33 @@ only up to 1MB for up to 5 seconds:
#{length => 1000000, period => 5000}).
----
-You may also disable the length limit:
+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]
-{ok, Data, Req} = cowboy_req:read_body(Req0, #{length => infinity}).
+----
+Ref = make_ref(),
+cowboy_req:cast({read_body, self(), Ref, auto, infinity}, Req).
+----
-This makes the function wait 15 seconds and return with
-whatever arrived during that period. This is not
-recommended for public facing applications.
+Cowboy will wait indefinitely for data and then send a
+`request_body` message as soon as it has data available,
+regardless of length.
-These two options can effectively be used to control
-the rate of transmission of the request body.
+[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
diff --git a/doc/src/manual/cowboy_req.asciidoc b/doc/src/manual/cowboy_req.asciidoc
index 0a1ca1b..0367836 100644
--- a/doc/src/manual/cowboy_req.asciidoc
+++ b/doc/src/manual/cowboy_req.asciidoc
@@ -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]
diff --git a/doc/src/manual/cowboy_req.cast.asciidoc b/doc/src/manual/cowboy_req.cast.asciidoc
index d6e018f..b12157f 100644
--- a/doc/src/manual/cowboy_req.cast.asciidoc
+++ b/doc/src/manual/cowboy_req.cast.asciidoc
@@ -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]
----
diff --git a/doc/src/manual/cowboy_req.read_body.asciidoc b/doc/src/manual/cowboy_req.read_body.asciidoc
index 2b87405..7da76ef 100644
--- a/doc/src/manual/cowboy_req.read_body.asciidoc
+++ b/doc/src/manual/cowboy_req.read_body.asciidoc
@@ -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
diff --git a/doc/src/manual/cowboy_stream_h.asciidoc b/doc/src/manual/cowboy_stream_h.asciidoc
index 588346e..7e0af89 100644
--- a/doc/src/manual/cowboy_stream_h.asciidoc
+++ b/doc/src/manual/cowboy_stream_h.asciidoc
@@ -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)]
diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl
index 90c5a3a..312862d 100644
--- a/src/cowboy_req.erl
+++ b/src/cowboy_req.erl
@@ -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
diff --git a/test/handlers/echo_h.erl b/test/handlers/echo_h.erl
index 1b672d1..f50bc79 100644
--- a/test/handlers/echo_h.erl
+++ b/test/handlers/echo_h.erl
@@ -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]);
diff --git a/test/req_SUITE.erl b/test/req_SUITE.erl
index d608767..92c8092 100644
--- a/test/req_SUITE.erl
+++ b/test/req_SUITE.erl
@@ -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),