0
Fork 0
mirror of https://github.com/ninenines/cowboy.git synced 2025-07-14 12:20:24 +00:00

AcceptCallback may now return created/see_other tuples for POST

They replace and deprecate the {true,URI} return value.
This commit is contained in:
Martin Björklund 2020-09-11 12:35:36 +02:00 committed by Loïc Hoguin
parent 63a6b86fba
commit 8795233c57
No known key found for this signature in database
GPG key ID: 8A9DF795F6FED764
4 changed files with 73 additions and 4 deletions

View file

@ -86,7 +86,10 @@ normal::
----
AcceptCallback(Req, State) -> {Result, Req, State}
Result :: true | {true, URI :: iodata()} | false}
Result :: true
| {created, URI :: iodata()}
| {see_other, URI :: iodata()}
| false
Default - crash
----
@ -99,11 +102,14 @@ For PUT requests, the body is a representation of the resource
that is being created or replaced.
For POST requests, the body is typically application-specific
instructions on how to process the request, but it may also
be a representation of the resource. When creating a new
resource with POST at a different location, return `{true, URI}`
instructions on how to process the request, but it may also be a
representation of the resource. When creating a new resource with POST
at a different location, return `{created, URI}` or `{see_other, URI}`
with `URI` the new location.
The `see_other` tuple will redirect the client to the new location
automatically.
For PATCH requests, the body is a series of instructions on
how to update the resource. Patch files or JSON Patch are
examples of such media types.
@ -724,6 +730,9 @@ listed here, like the authorization header.
== Changelog
* *2.9*: An `AcceptCallback` can now return `{created, URI}` or
`{see_other, URI}`. The return value `{true, URI}`
is deprecated.
* *2.7*: The media type wildcard in `content_types_accepted`
is now documented.
* *2.6*: The callback `rate_limited` was added.

View file

@ -1104,6 +1104,14 @@ process_content_type(Req, State=#state{method=Method, exists=Exists}, Fun) ->
next(Req2, State2, fun maybe_created/2);
{false, Req2, State2} ->
respond(Req2, State2, 400);
{{created, ResURL}, Req2, State2} when Method =:= <<"POST">> ->
Req3 = cowboy_req:set_resp_header(
<<"location">>, ResURL, Req2),
respond(Req3, State2, 201);
{{see_other, ResURL}, Req2, State2} when Method =:= <<"POST">> ->
Req3 = cowboy_req:set_resp_header(
<<"location">>, ResURL, Req2),
respond(Req3, State2, 303);
{{true, ResURL}, Req2, State2} when Method =:= <<"POST">> ->
Req3 = cowboy_req:set_resp_header(
<<"location">>, ResURL, Req2),

View file

@ -0,0 +1,28 @@
-module(create_resource_h).
-export([init/2]).
-export([allowed_methods/2]).
-export([resource_exists/2]).
-export([content_types_accepted/2]).
-export([from_text/2]).
init(Req, Opts) ->
{cowboy_rest, Req, Opts}.
allowed_methods(Req, State) ->
{[<<"POST">>], Req, State}.
resource_exists(Req, State) ->
{true, Req, State}.
content_types_accepted(Req, State) ->
{[{{<<"application">>, <<"text">>, []}, from_text}], Req, State}.
from_text(Req=#{qs := Qs}, State) ->
NewURI = [cowboy_req:uri(Req), "/foo"],
case Qs of
<<"created">> ->
{{created, NewURI}, Req, State};
<<"see_other">> ->
{{see_other, NewURI}, Req, State}
end.

View file

@ -52,6 +52,7 @@ init_dispatch(_) ->
{"/content_types_accepted", content_types_accepted_h, []},
{"/content_types_provided", content_types_provided_h, []},
{"/delete_resource", delete_resource_h, []},
{"/create_resource", create_resource_h, []},
{"/expires", expires_h, []},
{"/generate_etag", generate_etag_h, []},
{"/if_range", if_range_h, []},
@ -474,6 +475,29 @@ delete_resource_missing(Config) ->
{response, _, 500, _} = gun:await(ConnPid, Ref),
ok.
create_resource_created(Config) ->
doc("POST to an existing resource to create a new resource. "
"When the accept callback returns {created, NewURI}, "
"the expected reply is 201 Created."),
ConnPid = gun_open(Config),
Ref = gun:post(ConnPid, "/create_resource?created", [
{<<"content-type">>, <<"application/text">>}
], <<"hello">>, #{}),
{response, _, 201, _} = gun:await(ConnPid, Ref),
ok.
create_resource_see_other(Config) ->
doc("POST to an existing resource to create a new resource. "
"When the accept callback returns {see_other, NewURI}, "
"the expected reply is 303 See Other with a location header set."),
ConnPid = gun_open(Config),
Ref = gun:post(ConnPid, "/create_resource?see_other", [
{<<"content-type">>, <<"application/text">>}
], <<"hello">>, #{}),
{response, _, 303, RespHeaders} = gun:await(ConnPid, Ref),
{_, _} = lists:keyfind(<<"location">>, 1, RespHeaders),
ok.
error_on_malformed_accept(Config) ->
doc("A malformed Accept header must result in a 400 response."),
do_error_on_malformed_header(Config, <<"accept">>).