mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Add cowboy_req:read_and_match_urlencoded_body/2,3
This commit is contained in:
parent
dcc6f9326f
commit
4b385749f2
9 changed files with 218 additions and 2 deletions
|
@ -66,6 +66,7 @@ Request body:
|
||||||
* link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)] - Body length
|
* link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)] - Body length
|
||||||
* link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)] - Read the request body
|
* link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)] - Read the request body
|
||||||
* link:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)] - Read and parse a urlencoded request body
|
* link:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)] - Read and parse a urlencoded request body
|
||||||
|
* link:man:cowboy_req:read_and_match_urlencoded_body(3)[cowboy_req:read_and_match_urlencoded_body(3)] - Read, parse and match a urlencoded request body against constraints
|
||||||
* link:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)] - Read the next multipart headers
|
* link:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)] - Read the next multipart headers
|
||||||
* link:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)] - Read the current part's body
|
* link:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)] - Read the current part's body
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,148 @@
|
||||||
|
= cowboy_req:read_and_match_urlencoded_body(3)
|
||||||
|
|
||||||
|
== Name
|
||||||
|
|
||||||
|
cowboy_req:read_and_match_urlencoded_body - Read, parse
|
||||||
|
and match a urlencoded request body against constraints
|
||||||
|
|
||||||
|
== Description
|
||||||
|
|
||||||
|
[source,erlang]
|
||||||
|
----
|
||||||
|
read_and_match_urlencoded_body(Fields, Req)
|
||||||
|
-> read_and_match_urlencoded_body(Fields, Req, #{})
|
||||||
|
|
||||||
|
read_and_match_urlencoded_body(Fields, Req, Opts)
|
||||||
|
-> {ok, Body, Req}
|
||||||
|
|
||||||
|
Fields :: cowboy:fields()
|
||||||
|
Req :: cowboy_req:req()
|
||||||
|
Opts :: cowboy_req:read_body_opts()
|
||||||
|
Body :: #{atom() => any()}
|
||||||
|
----
|
||||||
|
|
||||||
|
Read, parse and match a urlencoded request body against
|
||||||
|
constraints.
|
||||||
|
|
||||||
|
This function reads the request body and parses it as
|
||||||
|
`application/x-www-form-urlencoded`. It then applies
|
||||||
|
the given field constraints to the urlencoded data
|
||||||
|
and returns the result as a map.
|
||||||
|
|
||||||
|
The urlencoded media type is used by Web browsers when
|
||||||
|
submitting HTML forms using the POST method.
|
||||||
|
|
||||||
|
Cowboy will only return the values specified
|
||||||
|
in the fields list, and ignore all others. Fields can be
|
||||||
|
either the key requested; the key along with a list of
|
||||||
|
constraints; or the key, a list of constraints and a
|
||||||
|
default value in case the key is missing.
|
||||||
|
|
||||||
|
This function will crash if the key is missing and no
|
||||||
|
default value is provided. This function will also crash
|
||||||
|
if a constraint fails.
|
||||||
|
|
||||||
|
The key must be provided as an atom. The key of the
|
||||||
|
returned map will be that atom. The value may be converted
|
||||||
|
through the use of constraints, making this function able
|
||||||
|
to extract, validate and convert values all in one step.
|
||||||
|
|
||||||
|
Cowboy needs to read the full body before parsing. By default
|
||||||
|
it will read bodies of size up to 64KB. It is possible to
|
||||||
|
provide options to read larger bodies if required.
|
||||||
|
|
||||||
|
Cowboy will automatically handle protocol details including
|
||||||
|
the expect header, chunked transfer-encoding and others.
|
||||||
|
|
||||||
|
Once the body has been read, Cowboy sets the content-length
|
||||||
|
header if it was not previously provided.
|
||||||
|
|
||||||
|
This function can only be called once. Calling it again will
|
||||||
|
result in undefined behavior.
|
||||||
|
|
||||||
|
== Arguments
|
||||||
|
|
||||||
|
Fields::
|
||||||
|
|
||||||
|
Fields to retrieve from the urlencoded body.
|
||||||
|
+
|
||||||
|
See link:man:cowboy(3)[cowboy(3)] for a complete description.
|
||||||
|
|
||||||
|
Req::
|
||||||
|
|
||||||
|
The Req object.
|
||||||
|
|
||||||
|
Opts::
|
||||||
|
|
||||||
|
A map of body reading options. Please refer to
|
||||||
|
link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)]
|
||||||
|
for details about each option.
|
||||||
|
+
|
||||||
|
This function defaults the `length` to 64KB and the `period`
|
||||||
|
to 5 seconds.
|
||||||
|
|
||||||
|
== Return value
|
||||||
|
|
||||||
|
An `ok` tuple is returned.
|
||||||
|
|
||||||
|
Desired values are returned as a map. The key is the atom
|
||||||
|
that was given in the list of fields, and the value is the
|
||||||
|
optionally converted value after applying constraints.
|
||||||
|
|
||||||
|
The map contains the same keys that were given in the fields.
|
||||||
|
|
||||||
|
An exception is triggered when the match fails.
|
||||||
|
|
||||||
|
The Req object returned in the tuple must be used from that point
|
||||||
|
onward. It contains a more up to date representation of the request.
|
||||||
|
For example it may have an added content-length header once the
|
||||||
|
body has been read.
|
||||||
|
|
||||||
|
== Changelog
|
||||||
|
|
||||||
|
* *2.5*: Function introduced.
|
||||||
|
|
||||||
|
== Examples
|
||||||
|
|
||||||
|
.Match fields
|
||||||
|
[source,erlang]
|
||||||
|
----
|
||||||
|
%% ID and Lang are binaries.
|
||||||
|
#{id := ID, lang := Lang}
|
||||||
|
= cowboy_req:read_and_match_urlencoded_body(
|
||||||
|
[id, lang], Req).
|
||||||
|
----
|
||||||
|
|
||||||
|
.Match fields and apply constraints
|
||||||
|
[source,erlang]
|
||||||
|
----
|
||||||
|
%% ID is an integer and Lang a non-empty binary.
|
||||||
|
#{id := ID, lang := Lang}
|
||||||
|
= cowboy_req:read_and_match_urlencoded_body(
|
||||||
|
[{id, int}, {lang, nonempty}], Req).
|
||||||
|
----
|
||||||
|
|
||||||
|
.Match fields with default values
|
||||||
|
[source,erlang]
|
||||||
|
----
|
||||||
|
#{lang := Lang}
|
||||||
|
= cowboy_req:read_and_match_urlencoded_body(
|
||||||
|
[{lang, [], <<"en-US">>}], Req).
|
||||||
|
----
|
||||||
|
|
||||||
|
.Allow large urlencoded bodies
|
||||||
|
[source,erlang]
|
||||||
|
----
|
||||||
|
{ok, Body, Req} = cowboy_req:read_and_match_urlencoded_body(
|
||||||
|
Fields, Req0, #{length => 1000000}).
|
||||||
|
----
|
||||||
|
|
||||||
|
== See also
|
||||||
|
|
||||||
|
link:man:cowboy_req(3)[cowboy_req(3)],
|
||||||
|
link:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],
|
||||||
|
link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],
|
||||||
|
link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],
|
||||||
|
link:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],
|
||||||
|
link:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)],
|
||||||
|
link:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]
|
|
@ -112,5 +112,6 @@ link:man:cowboy_req(3)[cowboy_req(3)],
|
||||||
link:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],
|
link:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],
|
||||||
link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],
|
link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],
|
||||||
link:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],
|
link:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],
|
||||||
|
link:man:cowboy_req:read_and_match_urlencoded_body(3)[cowboy_req:read_and_match_urlencoded_body(3)],
|
||||||
link:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)],
|
link:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)],
|
||||||
link:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]
|
link:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]
|
||||||
|
|
|
@ -131,4 +131,5 @@ link:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],
|
||||||
link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],
|
link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],
|
||||||
link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],
|
link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],
|
||||||
link:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],
|
link:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],
|
||||||
|
link:man:cowboy_req:read_and_match_urlencoded_body(3)[cowboy_req:read_and_match_urlencoded_body(3)],
|
||||||
link:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]
|
link:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]
|
||||||
|
|
|
@ -97,4 +97,5 @@ link:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],
|
||||||
link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],
|
link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],
|
||||||
link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],
|
link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],
|
||||||
link:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],
|
link:man:cowboy_req:read_urlencoded_body(3)[cowboy_req:read_urlencoded_body(3)],
|
||||||
|
link:man:cowboy_req:read_and_match_urlencoded_body(3)[cowboy_req:read_and_match_urlencoded_body(3)],
|
||||||
link:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)]
|
link:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)]
|
||||||
|
|
|
@ -90,5 +90,6 @@ link:man:cowboy_req(3)[cowboy_req(3)],
|
||||||
link:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],
|
link:man:cowboy_req:has_body(3)[cowboy_req:has_body(3)],
|
||||||
link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],
|
link:man:cowboy_req:body_length(3)[cowboy_req:body_length(3)],
|
||||||
link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],
|
link:man:cowboy_req:read_body(3)[cowboy_req:read_body(3)],
|
||||||
|
link:man:cowboy_req:read_and_match_urlencoded_body(3)[cowboy_req:read_and_match_urlencoded_body(3)],
|
||||||
link:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)],
|
link:man:cowboy_req:read_part(3)[cowboy_req:read_part(3)],
|
||||||
link:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]
|
link:man:cowboy_req:read_part_body(3)[cowboy_req:read_part_body(3)]
|
||||||
|
|
|
@ -54,7 +54,8 @@
|
||||||
-export([read_body/2]).
|
-export([read_body/2]).
|
||||||
-export([read_urlencoded_body/1]).
|
-export([read_urlencoded_body/1]).
|
||||||
-export([read_urlencoded_body/2]).
|
-export([read_urlencoded_body/2]).
|
||||||
%% @todo read_and_match_urlencoded_body?
|
-export([read_and_match_urlencoded_body/2]).
|
||||||
|
-export([read_and_match_urlencoded_body/3]).
|
||||||
|
|
||||||
%% Multipart.
|
%% Multipart.
|
||||||
-export([read_part/1]).
|
-export([read_part/1]).
|
||||||
|
@ -513,6 +514,23 @@ read_urlencoded_body(Req0, Opts) ->
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
-spec read_and_match_urlencoded_body(cowboy:fields(), Req)
|
||||||
|
-> {ok, map(), Req} when Req::req().
|
||||||
|
read_and_match_urlencoded_body(Fields, Req) ->
|
||||||
|
read_and_match_urlencoded_body(Fields, Req, #{length => 64000, period => 5000}).
|
||||||
|
|
||||||
|
-spec read_and_match_urlencoded_body(cowboy:fields(), Req, read_body_opts())
|
||||||
|
-> {ok, map(), Req} when Req::req().
|
||||||
|
read_and_match_urlencoded_body(Fields, Req0, Opts) ->
|
||||||
|
{ok, Qs, Req} = read_urlencoded_body(Req0, Opts),
|
||||||
|
case filter(Fields, kvlist_to_map(Fields, Qs)) of
|
||||||
|
{ok, Map} ->
|
||||||
|
{ok, Map, Req};
|
||||||
|
{error, Errors} ->
|
||||||
|
exit({request_error, {read_and_match_urlencoded_body, Errors},
|
||||||
|
'Urlencoded request body validation constraints failed for the reasons provided.'})
|
||||||
|
end.
|
||||||
|
|
||||||
%% Multipart.
|
%% Multipart.
|
||||||
|
|
||||||
-spec read_part(Req)
|
-spec read_part(Req)
|
||||||
|
|
|
@ -46,6 +46,19 @@ echo(<<"read_urlencoded_body">>, Req0, Opts) ->
|
||||||
_ -> cowboy_req:read_urlencoded_body(Req0)
|
_ -> cowboy_req:read_urlencoded_body(Req0)
|
||||||
end,
|
end,
|
||||||
{ok, cowboy_req:reply(200, #{}, value_to_iodata(Body), Req), Opts};
|
{ok, cowboy_req:reply(200, #{}, value_to_iodata(Body), Req), Opts};
|
||||||
|
echo(<<"read_and_match_urlencoded_body">>, Req0, Opts) ->
|
||||||
|
Path = cowboy_req:path(Req0),
|
||||||
|
case {Path, Opts} of
|
||||||
|
{<<"/opts", _/bits>>, #{crash := true}} -> ct_helper:ignore(cowboy_req, read_body, 2);
|
||||||
|
{_, #{crash := true}} -> ct_helper:ignore(cowboy_req, read_urlencoded_body, 2);
|
||||||
|
_ -> ok
|
||||||
|
end,
|
||||||
|
{ok, Body, Req} = case Path of
|
||||||
|
<<"/opts", _/bits>> -> cowboy_req:read_and_match_urlencoded_body([], Req0, Opts);
|
||||||
|
<<"/crash", _/bits>> -> cowboy_req:read_and_match_urlencoded_body([], Req0, Opts);
|
||||||
|
_ -> cowboy_req:read_and_match_urlencoded_body([], Req0)
|
||||||
|
end,
|
||||||
|
{ok, cowboy_req:reply(200, #{}, value_to_iodata(Body), Req), Opts};
|
||||||
echo(<<"uri">>, Req, Opts) ->
|
echo(<<"uri">>, Req, Opts) ->
|
||||||
Value = case cowboy_req:path_info(Req) of
|
Value = case cowboy_req:path_info(Req) of
|
||||||
[<<"origin">>] -> cowboy_req:uri(Req, #{host => undefined});
|
[<<"origin">>] -> cowboy_req:uri(Req, #{host => undefined});
|
||||||
|
@ -61,7 +74,12 @@ echo(<<"match">>, Req, Opts) ->
|
||||||
Fields = [binary_to_atom(F, latin1) || F <- Fields0],
|
Fields = [binary_to_atom(F, latin1) || F <- Fields0],
|
||||||
Value = case Type of
|
Value = case Type of
|
||||||
<<"qs">> -> cowboy_req:match_qs(Fields, Req);
|
<<"qs">> -> cowboy_req:match_qs(Fields, Req);
|
||||||
<<"cookies">> -> cowboy_req:match_cookies(Fields, Req)
|
<<"cookies">> -> cowboy_req:match_cookies(Fields, Req);
|
||||||
|
<<"body_qs">> ->
|
||||||
|
%% Note that the Req should not be discarded but for the
|
||||||
|
%% purpose of this test this has no ill impacts.
|
||||||
|
{ok, Match, _} = cowboy_req:read_and_match_urlencoded_body(Fields, Req),
|
||||||
|
Match
|
||||||
end,
|
end,
|
||||||
{ok, cowboy_req:reply(200, #{}, value_to_iodata(Value), Req), Opts};
|
{ok, cowboy_req:reply(200, #{}, value_to_iodata(Value), Req), Opts};
|
||||||
echo(What, Req, Opts) ->
|
echo(What, Req, Opts) ->
|
||||||
|
|
|
@ -564,6 +564,33 @@ do_read_urlencoded_body_too_long(Path, Body, Config) ->
|
||||||
end,
|
end,
|
||||||
gun:close(ConnPid).
|
gun:close(ConnPid).
|
||||||
|
|
||||||
|
read_and_match_urlencoded_body(Config) ->
|
||||||
|
doc("Read and match an application/x-www-form-urlencoded request body."),
|
||||||
|
<<"#{}">> = do_body("POST", "/match/body_qs", [], "a=b&c=d", Config),
|
||||||
|
<<"#{a => <<\"b\">>}">> = do_body("POST", "/match/body_qs/a", [], "a=b&c=d", Config),
|
||||||
|
<<"#{c => <<\"d\">>}">> = do_body("POST", "/match/body_qs/c", [], "a=b&c=d", Config),
|
||||||
|
<<"#{a => <<\"b\">>,c => <<\"d\">>}">>
|
||||||
|
= do_body("POST", "/match/body_qs/a/c", [], "a=b&c=d", Config),
|
||||||
|
<<"#{a => <<\"b\">>,c => true}">> = do_body("POST", "/match/body_qs/a/c", [], "a=b&c", Config),
|
||||||
|
<<"#{a => true,c => <<\"d\">>}">> = do_body("POST", "/match/body_qs/a/c", [], "a&c=d", Config),
|
||||||
|
%% Ensure match errors result in a 400 response.
|
||||||
|
{400, _} = do_body_error("POST", "/match/body_qs/a/c", [], "a=b", Config),
|
||||||
|
%% Ensure parse errors result in a 400 response.
|
||||||
|
{400, _} = do_body_error("POST", "/match/body_qs", [], "%%%%%", Config),
|
||||||
|
%% Send a 10MB body, larger than the default length, to ensure a crash occurs.
|
||||||
|
ok = do_read_urlencoded_body_too_large(
|
||||||
|
"/no-opts/read_and_match_urlencoded_body",
|
||||||
|
string:chars($a, 10000000), Config),
|
||||||
|
%% We read any length for at most 1 second.
|
||||||
|
%%
|
||||||
|
%% The body is sent twice, first with nofin, then wait 1.1 second, then again with fin.
|
||||||
|
%% We expect the handler to crash because read_and_match_urlencoded_body expects the full body.
|
||||||
|
ok = do_read_urlencoded_body_too_long(
|
||||||
|
"/crash/read_and_match_urlencoded_body/period", <<"abc">>, Config),
|
||||||
|
%% The timeout value is set too low on purpose to ensure a crash occurs.
|
||||||
|
ok = do_read_body_timeout("/opts/read_and_match_urlencoded_body/timeout", <<"abc">>, Config),
|
||||||
|
ok.
|
||||||
|
|
||||||
multipart(Config) ->
|
multipart(Config) ->
|
||||||
doc("Multipart request body."),
|
doc("Multipart request body."),
|
||||||
do_multipart("/multipart", Config).
|
do_multipart("/multipart", Config).
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue