2017-01-02 19:36:36 +01:00
|
|
|
%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
|
2011-12-05 22:53:59 +01:00
|
|
|
%%
|
|
|
|
%% Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
%% purpose with or without fee is hereby granted, provided that the above
|
|
|
|
%% copyright notice and this permission notice appear in all copies.
|
|
|
|
%%
|
|
|
|
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
2013-04-20 17:46:29 +02:00
|
|
|
%% Originally based on the Webmachine Diagram from Alan Dean and
|
|
|
|
%% Justin Sheehy.
|
2012-08-27 13:39:59 +02:00
|
|
|
-module(cowboy_rest).
|
2013-02-15 00:53:40 +00:00
|
|
|
-behaviour(cowboy_sub_protocol).
|
2012-08-27 12:16:07 +02:00
|
|
|
|
Allow passing options to sub protocols
Before this commit we had an issue where configuring a
Websocket connection was simply not possible without
doing magic, adding callbacks or extra return values.
The init/2 function only allowed setting hibernate
and timeout options.
After this commit, when switching to a different
type of handler you can either return
{module, Req, State}
or
{module, Req, State, Opts}
where Opts is any value (as far as the sub protocol
interface is concerned) and is ultimately checked
by the custom handlers.
A large protocol like Websocket would accept only
a map there, with many different options, while a
small interface like loop handlers would allow
passing hibernate and nothing else.
For Websocket, hibernate must be set from the
websocket_init/1 callback, because init/2 executes
in a separate process.
Sub protocols now have two callbacks: one with the
Opts value, one without.
The loop handler code was largely reworked and
simplified. It does not need to manage a timeout
or read from the socket anymore, it's the job of
the protocol code. A lot of unnecessary stuff was
therefore removed.
Websocket compression must now be enabled from
the handler options instead of per listener. This
means that a project can have two separate Websocket
handlers with different options. Compression is
still disabled by default, and the idle_timeout
value was changed from inifnity to 60000 (60 seconds),
as that's safer and is also a good value for mobile
devices.
2017-02-18 18:26:20 +01:00
|
|
|
-export([upgrade/4]).
|
|
|
|
-export([upgrade/5]).
|
2011-12-05 22:53:59 +01:00
|
|
|
|
2015-07-27 23:58:58 +02:00
|
|
|
%% Common handler callbacks.
|
|
|
|
|
2014-09-30 20:12:13 +03:00
|
|
|
-callback init(Req, any())
|
|
|
|
-> {ok | module(), Req, any()}
|
Allow passing options to sub protocols
Before this commit we had an issue where configuring a
Websocket connection was simply not possible without
doing magic, adding callbacks or extra return values.
The init/2 function only allowed setting hibernate
and timeout options.
After this commit, when switching to a different
type of handler you can either return
{module, Req, State}
or
{module, Req, State, Opts}
where Opts is any value (as far as the sub protocol
interface is concerned) and is ultimately checked
by the custom handlers.
A large protocol like Websocket would accept only
a map there, with many different options, while a
small interface like loop handlers would allow
passing hibernate and nothing else.
For Websocket, hibernate must be set from the
websocket_init/1 callback, because init/2 executes
in a separate process.
Sub protocols now have two callbacks: one with the
Opts value, one without.
The loop handler code was largely reworked and
simplified. It does not need to manage a timeout
or read from the socket anymore, it's the job of
the protocol code. A lot of unnecessary stuff was
therefore removed.
Websocket compression must now be enabled from
the handler options instead of per listener. This
means that a project can have two separate Websocket
handlers with different options. Compression is
still disabled by default, and the idle_timeout
value was changed from inifnity to 60000 (60 seconds),
as that's safer and is also a good value for mobile
devices.
2017-02-18 18:26:20 +01:00
|
|
|
| {module(), Req, any(), any()}
|
2014-09-30 20:12:13 +03:00
|
|
|
when Req::cowboy_req:req().
|
2015-07-27 23:58:58 +02:00
|
|
|
|
|
|
|
-callback terminate(any(), cowboy_req:req(), any()) -> ok.
|
|
|
|
-optional_callbacks([terminate/3]).
|
|
|
|
|
|
|
|
%% REST handler callbacks.
|
|
|
|
|
|
|
|
-callback allowed_methods(Req, State)
|
|
|
|
-> {[binary()], Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([allowed_methods/2]).
|
|
|
|
|
|
|
|
-callback allow_missing_post(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([allow_missing_post/2]).
|
|
|
|
|
|
|
|
-callback charsets_provided(Req, State)
|
|
|
|
-> {[binary()], Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([charsets_provided/2]).
|
|
|
|
|
|
|
|
-callback content_types_accepted(Req, State)
|
|
|
|
-> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([content_types_accepted/2]).
|
|
|
|
|
|
|
|
-callback content_types_provided(Req, State)
|
|
|
|
-> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([content_types_provided/2]).
|
|
|
|
|
|
|
|
-callback delete_completed(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([delete_completed/2]).
|
|
|
|
|
|
|
|
-callback delete_resource(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([delete_resource/2]).
|
|
|
|
|
|
|
|
-callback expires(Req, State)
|
|
|
|
-> {calendar:datetime() | binary() | undefined, Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([expires/2]).
|
|
|
|
|
|
|
|
-callback forbidden(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([forbidden/2]).
|
|
|
|
|
|
|
|
-callback generate_etag(Req, State)
|
|
|
|
-> {binary() | {weak | strong, binary()}, Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([generate_etag/2]).
|
|
|
|
|
|
|
|
-callback is_authorized(Req, State)
|
|
|
|
-> {true | {false, iodata()}, Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([is_authorized/2]).
|
|
|
|
|
|
|
|
-callback is_conflict(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([is_conflict/2]).
|
|
|
|
|
|
|
|
-callback known_methods(Req, State)
|
|
|
|
-> {[binary()], Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([known_methods/2]).
|
|
|
|
|
|
|
|
-callback languages_provided(Req, State)
|
|
|
|
-> {[binary()], Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([languages_provided/2]).
|
|
|
|
|
|
|
|
-callback last_modified(Req, State)
|
|
|
|
-> {calendar:datetime(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([last_modified/2]).
|
|
|
|
|
|
|
|
-callback malformed_request(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([malformed_request/2]).
|
|
|
|
|
|
|
|
-callback moved_permanently(Req, State)
|
|
|
|
-> {{true, iodata()} | false, Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([moved_permanently/2]).
|
|
|
|
|
|
|
|
-callback moved_temporarily(Req, State)
|
|
|
|
-> {{true, iodata()} | false, Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([moved_temporarily/2]).
|
|
|
|
|
|
|
|
-callback multiple_choices(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([multiple_choices/2]).
|
|
|
|
|
|
|
|
-callback options(Req, State)
|
|
|
|
-> {ok, Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([options/2]).
|
|
|
|
|
|
|
|
-callback previously_existed(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([previously_existed/2]).
|
|
|
|
|
|
|
|
-callback resource_exists(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([resource_exists/2]).
|
|
|
|
|
|
|
|
-callback service_available(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([service_available/2]).
|
|
|
|
|
|
|
|
-callback uri_too_long(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([uri_too_long/2]).
|
|
|
|
|
|
|
|
-callback valid_content_headers(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([valid_content_headers/2]).
|
|
|
|
|
|
|
|
-callback valid_entity_length(Req, State)
|
|
|
|
-> {boolean(), Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([valid_entity_length/2]).
|
|
|
|
|
|
|
|
-callback variances(Req, State)
|
|
|
|
-> {[binary()], Req, State}
|
|
|
|
| {stop, Req, State}
|
|
|
|
when Req::cowboy_req:req(), State::any().
|
|
|
|
-optional_callbacks([variances/2]).
|
|
|
|
|
|
|
|
%% End of REST callbacks. Whew!
|
2014-09-30 20:12:13 +03:00
|
|
|
|
2011-12-05 22:53:59 +01:00
|
|
|
-record(state, {
|
2012-09-20 06:22:51 +02:00
|
|
|
method = undefined :: binary(),
|
2012-09-15 23:53:30 +02:00
|
|
|
|
2011-12-05 22:53:59 +01:00
|
|
|
%% Handler.
|
|
|
|
handler :: atom(),
|
|
|
|
handler_state :: any(),
|
|
|
|
|
2013-04-12 18:20:41 +02:00
|
|
|
%% Allowed methods. Only used for OPTIONS requests.
|
2017-01-02 16:47:16 +01:00
|
|
|
allowed_methods :: [binary()] | undefined,
|
2013-04-12 18:20:41 +02:00
|
|
|
|
2011-12-05 22:53:59 +01:00
|
|
|
%% Media type.
|
|
|
|
content_types_p = [] ::
|
2013-02-15 22:41:55 +07:00
|
|
|
[{binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
|
|
|
|
atom()}],
|
2011-12-05 22:53:59 +01:00
|
|
|
content_type_a :: undefined
|
2013-02-15 22:41:55 +07:00
|
|
|
| {binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
|
|
|
|
atom()},
|
2011-12-05 22:53:59 +01:00
|
|
|
|
|
|
|
%% Language.
|
|
|
|
languages_p = [] :: [binary()],
|
|
|
|
language_a :: undefined | binary(),
|
|
|
|
|
|
|
|
%% Charset.
|
2013-05-16 17:32:07 +02:00
|
|
|
charsets_p = [] :: [binary()],
|
2011-12-05 22:53:59 +01:00
|
|
|
charset_a :: undefined | binary(),
|
|
|
|
|
2013-04-20 15:52:31 +02:00
|
|
|
%% Whether the resource exists.
|
|
|
|
exists = false :: boolean(),
|
|
|
|
|
2011-12-05 22:53:59 +01:00
|
|
|
%% Cached resource calls.
|
2012-02-28 18:35:26 +01:00
|
|
|
etag :: undefined | no_call | {strong | weak, binary()},
|
2011-12-26 10:13:30 +01:00
|
|
|
last_modified :: undefined | no_call | calendar:datetime(),
|
2014-06-06 02:37:24 -04:00
|
|
|
expires :: undefined | no_call | calendar:datetime() | binary()
|
2011-12-05 22:53:59 +01:00
|
|
|
}).
|
|
|
|
|
Allow passing options to sub protocols
Before this commit we had an issue where configuring a
Websocket connection was simply not possible without
doing magic, adding callbacks or extra return values.
The init/2 function only allowed setting hibernate
and timeout options.
After this commit, when switching to a different
type of handler you can either return
{module, Req, State}
or
{module, Req, State, Opts}
where Opts is any value (as far as the sub protocol
interface is concerned) and is ultimately checked
by the custom handlers.
A large protocol like Websocket would accept only
a map there, with many different options, while a
small interface like loop handlers would allow
passing hibernate and nothing else.
For Websocket, hibernate must be set from the
websocket_init/1 callback, because init/2 executes
in a separate process.
Sub protocols now have two callbacks: one with the
Opts value, one without.
The loop handler code was largely reworked and
simplified. It does not need to manage a timeout
or read from the socket anymore, it's the job of
the protocol code. A lot of unnecessary stuff was
therefore removed.
Websocket compression must now be enabled from
the handler options instead of per listener. This
means that a project can have two separate Websocket
handlers with different options. Compression is
still disabled by default, and the idle_timeout
value was changed from inifnity to 60000 (60 seconds),
as that's safer and is also a good value for mobile
devices.
2017-02-18 18:26:20 +01:00
|
|
|
-spec upgrade(Req, Env, module(), any())
|
2014-06-30 17:36:43 +02:00
|
|
|
-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
|
Allow passing options to sub protocols
Before this commit we had an issue where configuring a
Websocket connection was simply not possible without
doing magic, adding callbacks or extra return values.
The init/2 function only allowed setting hibernate
and timeout options.
After this commit, when switching to a different
type of handler you can either return
{module, Req, State}
or
{module, Req, State, Opts}
where Opts is any value (as far as the sub protocol
interface is concerned) and is ultimately checked
by the custom handlers.
A large protocol like Websocket would accept only
a map there, with many different options, while a
small interface like loop handlers would allow
passing hibernate and nothing else.
For Websocket, hibernate must be set from the
websocket_init/1 callback, because init/2 executes
in a separate process.
Sub protocols now have two callbacks: one with the
Opts value, one without.
The loop handler code was largely reworked and
simplified. It does not need to manage a timeout
or read from the socket anymore, it's the job of
the protocol code. A lot of unnecessary stuff was
therefore removed.
Websocket compression must now be enabled from
the handler options instead of per listener. This
means that a project can have two separate Websocket
handlers with different options. Compression is
still disabled by default, and the idle_timeout
value was changed from inifnity to 60000 (60 seconds),
as that's safer and is also a good value for mobile
devices.
2017-02-18 18:26:20 +01:00
|
|
|
upgrade(Req0, Env, Handler, HandlerState) ->
|
2016-06-06 17:30:13 +02:00
|
|
|
Method = cowboy_req:method(Req0),
|
|
|
|
{ok, Req, Result} = service_available(Req0, #state{method=Method,
|
|
|
|
handler=Handler, handler_state=HandlerState}),
|
2017-01-02 18:27:03 +01:00
|
|
|
{ok, Req, Env#{result => Result}}.
|
2011-12-05 22:53:59 +01:00
|
|
|
|
Allow passing options to sub protocols
Before this commit we had an issue where configuring a
Websocket connection was simply not possible without
doing magic, adding callbacks or extra return values.
The init/2 function only allowed setting hibernate
and timeout options.
After this commit, when switching to a different
type of handler you can either return
{module, Req, State}
or
{module, Req, State, Opts}
where Opts is any value (as far as the sub protocol
interface is concerned) and is ultimately checked
by the custom handlers.
A large protocol like Websocket would accept only
a map there, with many different options, while a
small interface like loop handlers would allow
passing hibernate and nothing else.
For Websocket, hibernate must be set from the
websocket_init/1 callback, because init/2 executes
in a separate process.
Sub protocols now have two callbacks: one with the
Opts value, one without.
The loop handler code was largely reworked and
simplified. It does not need to manage a timeout
or read from the socket anymore, it's the job of
the protocol code. A lot of unnecessary stuff was
therefore removed.
Websocket compression must now be enabled from
the handler options instead of per listener. This
means that a project can have two separate Websocket
handlers with different options. Compression is
still disabled by default, and the idle_timeout
value was changed from inifnity to 60000 (60 seconds),
as that's safer and is also a good value for mobile
devices.
2017-02-18 18:26:20 +01:00
|
|
|
-spec upgrade(Req, Env, module(), any(), any())
|
|
|
|
-> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env().
|
|
|
|
%% cowboy_rest takes no options.
|
|
|
|
upgrade(Req, Env, Handler, HandlerState, _Opts) ->
|
|
|
|
upgrade(Req, Env, Handler, HandlerState).
|
|
|
|
|
2011-12-05 22:53:59 +01:00
|
|
|
service_available(Req, State) ->
|
|
|
|
expect(Req, State, service_available, true, fun known_methods/2, 503).
|
|
|
|
|
2012-10-13 15:15:15 -07:00
|
|
|
%% known_methods/2 should return a list of binary methods.
|
2012-09-15 23:53:30 +02:00
|
|
|
known_methods(Req, State=#state{method=Method}) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
case call(Req, State, known_methods) of
|
2012-09-20 06:22:51 +02:00
|
|
|
no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>;
|
|
|
|
Method =:= <<"POST">>; Method =:= <<"PUT">>;
|
2013-04-11 21:25:35 +02:00
|
|
|
Method =:= <<"PATCH">>; Method =:= <<"DELETE">>;
|
|
|
|
Method =:= <<"OPTIONS">> ->
|
2011-12-05 22:53:59 +01:00
|
|
|
next(Req, State, fun uri_too_long/2);
|
2011-12-08 18:54:20 +01:00
|
|
|
no_call ->
|
|
|
|
next(Req, State, 501);
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState} ->
|
2012-01-23 08:24:15 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2012-01-23 08:11:29 +01:00
|
|
|
{List, Req2, HandlerState} ->
|
|
|
|
State2 = State#state{handler_state=HandlerState},
|
2011-12-05 22:53:59 +01:00
|
|
|
case lists:member(Method, List) of
|
|
|
|
true -> next(Req2, State2, fun uri_too_long/2);
|
|
|
|
false -> next(Req2, State2, 501)
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
|
|
|
uri_too_long(Req, State) ->
|
|
|
|
expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414).
|
|
|
|
|
2012-10-13 15:15:15 -07:00
|
|
|
%% allowed_methods/2 should return a list of binary methods.
|
2012-09-15 23:53:30 +02:00
|
|
|
allowed_methods(Req, State=#state{method=Method}) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
case call(Req, State, allowed_methods) of
|
2012-09-20 06:22:51 +02:00
|
|
|
no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
|
2011-12-05 22:53:59 +01:00
|
|
|
next(Req, State, fun malformed_request/2);
|
2013-04-12 18:20:41 +02:00
|
|
|
no_call when Method =:= <<"OPTIONS">> ->
|
|
|
|
next(Req, State#state{allowed_methods=
|
|
|
|
[<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]},
|
|
|
|
fun malformed_request/2);
|
2011-12-08 18:54:20 +01:00
|
|
|
no_call ->
|
2013-04-12 18:20:41 +02:00
|
|
|
method_not_allowed(Req, State,
|
|
|
|
[<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]);
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState} ->
|
2012-01-23 08:24:15 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2012-01-23 08:11:29 +01:00
|
|
|
{List, Req2, HandlerState} ->
|
|
|
|
State2 = State#state{handler_state=HandlerState},
|
2011-12-05 22:53:59 +01:00
|
|
|
case lists:member(Method, List) of
|
2013-04-12 18:20:41 +02:00
|
|
|
true when Method =:= <<"OPTIONS">> ->
|
2013-04-29 16:36:54 +02:00
|
|
|
next(Req2, State2#state{allowed_methods=List},
|
2013-04-12 18:20:41 +02:00
|
|
|
fun malformed_request/2);
|
|
|
|
true ->
|
|
|
|
next(Req2, State2, fun malformed_request/2);
|
|
|
|
false ->
|
|
|
|
method_not_allowed(Req2, State2, List)
|
2011-12-05 22:53:59 +01:00
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
2013-04-30 18:22:40 +04:00
|
|
|
method_not_allowed(Req, State, []) ->
|
|
|
|
Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req),
|
|
|
|
respond(Req2, State, 405);
|
2011-12-05 22:53:59 +01:00
|
|
|
method_not_allowed(Req, State, Methods) ->
|
2013-04-15 22:15:45 +02:00
|
|
|
<< ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>,
|
|
|
|
Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req),
|
2011-12-05 22:53:59 +01:00
|
|
|
respond(Req2, State, 405).
|
|
|
|
|
|
|
|
malformed_request(Req, State) ->
|
|
|
|
expect(Req, State, malformed_request, false, fun is_authorized/2, 400).
|
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% is_authorized/2 should return true or {false, WwwAuthenticateHeader}.
|
2011-12-05 22:53:59 +01:00
|
|
|
is_authorized(Req, State) ->
|
|
|
|
case call(Req, State, is_authorized) of
|
|
|
|
no_call ->
|
|
|
|
forbidden(Req, State);
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState} ->
|
2012-01-23 08:24:15 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2012-01-23 08:11:29 +01:00
|
|
|
{true, Req2, HandlerState} ->
|
|
|
|
forbidden(Req2, State#state{handler_state=HandlerState});
|
|
|
|
{{false, AuthHead}, Req2, HandlerState} ->
|
2012-09-16 03:51:07 +02:00
|
|
|
Req3 = cowboy_req:set_resp_header(
|
2012-11-10 17:24:25 -05:00
|
|
|
<<"www-authenticate">>, AuthHead, Req2),
|
2012-01-23 08:11:29 +01:00
|
|
|
respond(Req3, State#state{handler_state=HandlerState}, 401)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
forbidden(Req, State) ->
|
|
|
|
expect(Req, State, forbidden, false, fun valid_content_headers/2, 403).
|
|
|
|
|
|
|
|
valid_content_headers(Req, State) ->
|
|
|
|
expect(Req, State, valid_content_headers, true,
|
2014-10-03 18:52:14 +03:00
|
|
|
fun valid_entity_length/2, 501).
|
2011-12-05 22:53:59 +01:00
|
|
|
|
|
|
|
valid_entity_length(Req, State) ->
|
|
|
|
expect(Req, State, valid_entity_length, true, fun options/2, 413).
|
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% If you need to add additional headers to the response at this point,
|
|
|
|
%% you should do it directly in the options/2 call using set_resp_headers.
|
2013-04-15 22:15:45 +02:00
|
|
|
options(Req, State=#state{allowed_methods=Methods, method= <<"OPTIONS">>}) ->
|
2012-01-23 08:24:15 +01:00
|
|
|
case call(Req, State, options) of
|
2013-04-30 18:22:40 +04:00
|
|
|
no_call when Methods =:= [] ->
|
|
|
|
Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req),
|
|
|
|
respond(Req2, State, 200);
|
2013-04-12 18:20:41 +02:00
|
|
|
no_call ->
|
2013-04-15 22:15:45 +02:00
|
|
|
<< ", ", Allow/binary >>
|
|
|
|
= << << ", ", M/binary >> || M <- Methods >>,
|
|
|
|
Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req),
|
2013-04-12 18:20:41 +02:00
|
|
|
respond(Req2, State, 200);
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState} ->
|
2012-01-23 08:24:15 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
|
|
|
{ok, Req2, HandlerState} ->
|
|
|
|
respond(Req2, State#state{handler_state=HandlerState}, 200)
|
|
|
|
end;
|
2011-12-05 22:53:59 +01:00
|
|
|
options(Req, State) ->
|
|
|
|
content_types_provided(Req, State).
|
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% content_types_provided/2 should return a list of content types and their
|
|
|
|
%% associated callback function as a tuple: {{Type, SubType, Params}, Fun}.
|
|
|
|
%% Type and SubType are the media type as binary. Params is a list of
|
|
|
|
%% Key/Value tuple, with Key and Value a binary. Fun is the name of the
|
|
|
|
%% callback that will be used to return the content of the response. It is
|
|
|
|
%% given as an atom.
|
|
|
|
%%
|
|
|
|
%% An example of such return value would be:
|
|
|
|
%% {{<<"text">>, <<"html">>, []}, to_html}
|
2011-12-09 22:58:15 -08:00
|
|
|
%%
|
|
|
|
%% Note that it is also possible to return a binary content type that will
|
|
|
|
%% then be parsed by Cowboy. However note that while this may make your
|
2012-11-27 16:24:08 +01:00
|
|
|
%% resources a little more readable, this is a lot less efficient.
|
|
|
|
%%
|
|
|
|
%% An example of such return value would be:
|
2011-12-09 22:58:15 -08:00
|
|
|
%% {<<"text/html">>, to_html}
|
2012-09-15 22:51:37 +02:00
|
|
|
content_types_provided(Req, State) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
case call(Req, State, content_types_provided) of
|
|
|
|
no_call ->
|
2013-04-26 18:34:01 +07:00
|
|
|
State2 = State#state{
|
|
|
|
content_types_p=[{{<<"text">>, <<"html">>, '*'}, to_html}]},
|
2014-09-23 16:43:29 +03:00
|
|
|
try cowboy_req:parse_header(<<"accept">>, Req) of
|
|
|
|
undefined ->
|
2013-04-26 18:34:01 +07:00
|
|
|
languages_provided(
|
Initial commit with connection/streams
Breaking changes with previous commit. This is a very large change,
and I am giving up on making a single commit that fixes everything.
More commits will follow slowly adding back features, introducing
new tests and fixing the documentation.
This change contains most of the work toward unifying the interface
for handling both HTTP/1.1 and HTTP/2. HTTP/1.1 connections are now
no longer 1 process per connection; instead by default 1 process per
request is also created. This has a number of pros and cons.
Because it has cons, we also allow users to use a lower-level API
that acts on "streams" (requests/responses) directly at the connection
process-level. If performance is a concern, one can always write a
stream handler. The performance in this case will be even greater
than with Cowboy 1, although all the special handlers are unavailable.
When switching to Websocket, after the handler returns from init/2,
Cowboy stops the stream and the Websocket protocol takes over the
connection process. Websocket then calls websocket_init/2 for any
additional initialization such as timers, because the process is
different in init/2 and websocket_*/* functions. This however would
allow us to use websocket_init/2 for sending messages on connect,
instead of sending ourselves a message and be subject to races.
Note that websocket_init/2 is optional.
This is all a big change and while most of the tests pass, some
functionality currently doesn't. SPDY is broken and will be removed
soon in favor of HTTP/2. Automatic compression is currently disabled.
The cowboy_req interface probably still have a few functions that
need to be updated. The docs and examples do not refer the current
functionality anymore.
Everything will be fixed over time. Feedback is more than welcome.
Open a ticket!
2016-02-10 17:28:32 +01:00
|
|
|
Req#{media_type => {<<"text">>, <<"html">>, []}},
|
2013-04-26 18:34:01 +07:00
|
|
|
State2#state{content_type_a={{<<"text">>, <<"html">>, []}, to_html}});
|
2014-09-23 16:43:29 +03:00
|
|
|
Accept ->
|
|
|
|
choose_media_type(Req, State2, prioritize_accept(Accept))
|
|
|
|
catch _:_ ->
|
|
|
|
respond(Req, State2, 400)
|
2013-04-26 18:34:01 +07:00
|
|
|
end;
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState} ->
|
2012-01-23 08:24:15 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2011-12-09 22:58:15 -08:00
|
|
|
{[], Req2, HandlerState} ->
|
|
|
|
not_acceptable(Req2, State#state{handler_state=HandlerState});
|
|
|
|
{CTP, Req2, HandlerState} ->
|
2013-04-26 18:34:10 +07:00
|
|
|
CTP2 = [normalize_content_types(P) || P <- CTP],
|
2011-12-09 22:58:15 -08:00
|
|
|
State2 = State#state{
|
|
|
|
handler_state=HandlerState, content_types_p=CTP2},
|
2014-09-23 16:43:29 +03:00
|
|
|
try cowboy_req:parse_header(<<"accept">>, Req2) of
|
|
|
|
undefined ->
|
2011-12-19 09:44:24 +01:00
|
|
|
{PMT, _Fun} = HeadCTP = hd(CTP2),
|
|
|
|
languages_provided(
|
Initial commit with connection/streams
Breaking changes with previous commit. This is a very large change,
and I am giving up on making a single commit that fixes everything.
More commits will follow slowly adding back features, introducing
new tests and fixing the documentation.
This change contains most of the work toward unifying the interface
for handling both HTTP/1.1 and HTTP/2. HTTP/1.1 connections are now
no longer 1 process per connection; instead by default 1 process per
request is also created. This has a number of pros and cons.
Because it has cons, we also allow users to use a lower-level API
that acts on "streams" (requests/responses) directly at the connection
process-level. If performance is a concern, one can always write a
stream handler. The performance in this case will be even greater
than with Cowboy 1, although all the special handlers are unavailable.
When switching to Websocket, after the handler returns from init/2,
Cowboy stops the stream and the Websocket protocol takes over the
connection process. Websocket then calls websocket_init/2 for any
additional initialization such as timers, because the process is
different in init/2 and websocket_*/* functions. This however would
allow us to use websocket_init/2 for sending messages on connect,
instead of sending ourselves a message and be subject to races.
Note that websocket_init/2 is optional.
This is all a big change and while most of the tests pass, some
functionality currently doesn't. SPDY is broken and will be removed
soon in favor of HTTP/2. Automatic compression is currently disabled.
The cowboy_req interface probably still have a few functions that
need to be updated. The docs and examples do not refer the current
functionality anymore.
Everything will be fixed over time. Feedback is more than welcome.
Open a ticket!
2016-02-10 17:28:32 +01:00
|
|
|
Req2#{media_type => PMT},
|
2011-12-19 09:44:24 +01:00
|
|
|
State2#state{content_type_a=HeadCTP});
|
2014-09-23 16:43:29 +03:00
|
|
|
Accept ->
|
|
|
|
choose_media_type(Req2, State2, prioritize_accept(Accept))
|
|
|
|
catch _:_ ->
|
|
|
|
respond(Req2, State2, 400)
|
2011-12-05 22:53:59 +01:00
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
2012-07-21 19:02:01 +02:00
|
|
|
normalize_content_types({ContentType, Callback})
|
2011-12-09 22:58:15 -08:00
|
|
|
when is_binary(ContentType) ->
|
2015-02-04 16:18:28 +01:00
|
|
|
{cow_http_hd:parse_content_type(ContentType), Callback};
|
2013-04-11 17:59:54 +02:00
|
|
|
normalize_content_types(Normalized) ->
|
|
|
|
Normalized.
|
2011-12-09 22:58:15 -08:00
|
|
|
|
2011-12-05 22:53:59 +01:00
|
|
|
prioritize_accept(Accept) ->
|
|
|
|
lists:sort(
|
|
|
|
fun ({MediaTypeA, Quality, _AcceptParamsA},
|
|
|
|
{MediaTypeB, Quality, _AcceptParamsB}) ->
|
|
|
|
%% Same quality, check precedence in more details.
|
|
|
|
prioritize_mediatype(MediaTypeA, MediaTypeB);
|
|
|
|
({_MediaTypeA, QualityA, _AcceptParamsA},
|
|
|
|
{_MediaTypeB, QualityB, _AcceptParamsB}) ->
|
|
|
|
%% Just compare the quality.
|
|
|
|
QualityA > QualityB
|
|
|
|
end, Accept).
|
|
|
|
|
|
|
|
%% Media ranges can be overridden by more specific media ranges or
|
|
|
|
%% specific media types. If more than one media range applies to a given
|
|
|
|
%% type, the most specific reference has precedence.
|
|
|
|
%%
|
|
|
|
%% We always choose B over A when we can't decide between the two.
|
|
|
|
prioritize_mediatype({TypeA, SubTypeA, ParamsA}, {TypeB, SubTypeB, ParamsB}) ->
|
|
|
|
case TypeB of
|
|
|
|
TypeA ->
|
|
|
|
case SubTypeB of
|
|
|
|
SubTypeA -> length(ParamsA) > length(ParamsB);
|
|
|
|
<<"*">> -> true;
|
|
|
|
_Any -> false
|
|
|
|
end;
|
|
|
|
<<"*">> -> true;
|
|
|
|
_Any -> false
|
|
|
|
end.
|
|
|
|
|
|
|
|
%% Ignoring the rare AcceptParams. Not sure what should be done about them.
|
|
|
|
choose_media_type(Req, State, []) ->
|
|
|
|
not_acceptable(Req, State);
|
|
|
|
choose_media_type(Req, State=#state{content_types_p=CTP},
|
|
|
|
[MediaType|Tail]) ->
|
|
|
|
match_media_type(Req, State, Tail, CTP, MediaType).
|
|
|
|
|
|
|
|
match_media_type(Req, State, Accept, [], _MediaType) ->
|
|
|
|
choose_media_type(Req, State, Accept);
|
2011-12-12 08:18:38 +01:00
|
|
|
match_media_type(Req, State, Accept, CTP,
|
|
|
|
MediaType = {{<<"*">>, <<"*">>, _Params_A}, _QA, _APA}) ->
|
|
|
|
match_media_type_params(Req, State, Accept, CTP, MediaType);
|
2011-12-05 22:53:59 +01:00
|
|
|
match_media_type(Req, State, Accept,
|
2011-12-12 08:18:38 +01:00
|
|
|
CTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail],
|
|
|
|
MediaType = {{Type, SubType_A, _PA}, _QA, _APA})
|
2011-12-05 22:53:59 +01:00
|
|
|
when SubType_P =:= SubType_A; SubType_A =:= <<"*">> ->
|
2011-12-12 08:18:38 +01:00
|
|
|
match_media_type_params(Req, State, Accept, CTP, MediaType);
|
|
|
|
match_media_type(Req, State, Accept, [_Any|Tail], MediaType) ->
|
|
|
|
match_media_type(Req, State, Accept, Tail, MediaType).
|
|
|
|
|
2013-02-15 22:41:55 +07:00
|
|
|
match_media_type_params(Req, State, _Accept,
|
|
|
|
[Provided = {{TP, STP, '*'}, _Fun}|_Tail],
|
|
|
|
{{_TA, _STA, Params_A}, _QA, _APA}) ->
|
|
|
|
PMT = {TP, STP, Params_A},
|
Initial commit with connection/streams
Breaking changes with previous commit. This is a very large change,
and I am giving up on making a single commit that fixes everything.
More commits will follow slowly adding back features, introducing
new tests and fixing the documentation.
This change contains most of the work toward unifying the interface
for handling both HTTP/1.1 and HTTP/2. HTTP/1.1 connections are now
no longer 1 process per connection; instead by default 1 process per
request is also created. This has a number of pros and cons.
Because it has cons, we also allow users to use a lower-level API
that acts on "streams" (requests/responses) directly at the connection
process-level. If performance is a concern, one can always write a
stream handler. The performance in this case will be even greater
than with Cowboy 1, although all the special handlers are unavailable.
When switching to Websocket, after the handler returns from init/2,
Cowboy stops the stream and the Websocket protocol takes over the
connection process. Websocket then calls websocket_init/2 for any
additional initialization such as timers, because the process is
different in init/2 and websocket_*/* functions. This however would
allow us to use websocket_init/2 for sending messages on connect,
instead of sending ourselves a message and be subject to races.
Note that websocket_init/2 is optional.
This is all a big change and while most of the tests pass, some
functionality currently doesn't. SPDY is broken and will be removed
soon in favor of HTTP/2. Automatic compression is currently disabled.
The cowboy_req interface probably still have a few functions that
need to be updated. The docs and examples do not refer the current
functionality anymore.
Everything will be fixed over time. Feedback is more than welcome.
Open a ticket!
2016-02-10 17:28:32 +01:00
|
|
|
languages_provided(Req#{media_type => PMT},
|
2013-02-15 22:41:55 +07:00
|
|
|
State#state{content_type_a=Provided});
|
2012-09-15 22:51:37 +02:00
|
|
|
match_media_type_params(Req, State, Accept,
|
2011-12-19 09:44:24 +01:00
|
|
|
[Provided = {PMT = {_TP, _STP, Params_P}, _Fun}|Tail],
|
2011-12-12 08:18:38 +01:00
|
|
|
MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
case lists:sort(Params_P) =:= lists:sort(Params_A) of
|
|
|
|
true ->
|
Initial commit with connection/streams
Breaking changes with previous commit. This is a very large change,
and I am giving up on making a single commit that fixes everything.
More commits will follow slowly adding back features, introducing
new tests and fixing the documentation.
This change contains most of the work toward unifying the interface
for handling both HTTP/1.1 and HTTP/2. HTTP/1.1 connections are now
no longer 1 process per connection; instead by default 1 process per
request is also created. This has a number of pros and cons.
Because it has cons, we also allow users to use a lower-level API
that acts on "streams" (requests/responses) directly at the connection
process-level. If performance is a concern, one can always write a
stream handler. The performance in this case will be even greater
than with Cowboy 1, although all the special handlers are unavailable.
When switching to Websocket, after the handler returns from init/2,
Cowboy stops the stream and the Websocket protocol takes over the
connection process. Websocket then calls websocket_init/2 for any
additional initialization such as timers, because the process is
different in init/2 and websocket_*/* functions. This however would
allow us to use websocket_init/2 for sending messages on connect,
instead of sending ourselves a message and be subject to races.
Note that websocket_init/2 is optional.
This is all a big change and while most of the tests pass, some
functionality currently doesn't. SPDY is broken and will be removed
soon in favor of HTTP/2. Automatic compression is currently disabled.
The cowboy_req interface probably still have a few functions that
need to be updated. The docs and examples do not refer the current
functionality anymore.
Everything will be fixed over time. Feedback is more than welcome.
Open a ticket!
2016-02-10 17:28:32 +01:00
|
|
|
languages_provided(Req#{media_type => PMT},
|
2011-12-19 09:44:24 +01:00
|
|
|
State#state{content_type_a=Provided});
|
2011-12-05 22:53:59 +01:00
|
|
|
false ->
|
|
|
|
match_media_type(Req, State, Accept, Tail, MediaType)
|
2011-12-12 08:18:38 +01:00
|
|
|
end.
|
2011-12-05 22:53:59 +01:00
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% languages_provided should return a list of binary values indicating
|
|
|
|
%% which languages are accepted by the resource.
|
|
|
|
%%
|
2011-12-05 22:53:59 +01:00
|
|
|
%% @todo I suppose we should also ask the resource if it wants to
|
|
|
|
%% set a language itself or if it wants it to be automatically chosen.
|
|
|
|
languages_provided(Req, State) ->
|
|
|
|
case call(Req, State, languages_provided) of
|
|
|
|
no_call ->
|
|
|
|
charsets_provided(Req, State);
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState} ->
|
2012-01-23 08:24:15 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2012-01-23 08:11:29 +01:00
|
|
|
{[], Req2, HandlerState} ->
|
|
|
|
not_acceptable(Req2, State#state{handler_state=HandlerState});
|
|
|
|
{LP, Req2, HandlerState} ->
|
|
|
|
State2 = State#state{handler_state=HandlerState, languages_p=LP},
|
2014-09-23 16:43:29 +03:00
|
|
|
case cowboy_req:parse_header(<<"accept-language">>, Req2) of
|
2011-12-05 22:53:59 +01:00
|
|
|
undefined ->
|
2014-09-23 16:43:29 +03:00
|
|
|
set_language(Req2, State2#state{language_a=hd(LP)});
|
2011-12-05 22:53:59 +01:00
|
|
|
AcceptLanguage ->
|
|
|
|
AcceptLanguage2 = prioritize_languages(AcceptLanguage),
|
2014-09-23 16:43:29 +03:00
|
|
|
choose_language(Req2, State2, AcceptLanguage2)
|
2011-12-05 22:53:59 +01:00
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
|
|
|
%% A language-range matches a language-tag if it exactly equals the tag,
|
|
|
|
%% or if it exactly equals a prefix of the tag such that the first tag
|
|
|
|
%% character following the prefix is "-". The special range "*", if
|
|
|
|
%% present in the Accept-Language field, matches every tag not matched
|
|
|
|
%% by any other range present in the Accept-Language field.
|
|
|
|
%%
|
|
|
|
%% @todo The last sentence probably means we should always put '*'
|
|
|
|
%% at the end of the list.
|
|
|
|
prioritize_languages(AcceptLanguages) ->
|
|
|
|
lists:sort(
|
|
|
|
fun ({_TagA, QualityA}, {_TagB, QualityB}) ->
|
|
|
|
QualityA > QualityB
|
|
|
|
end, AcceptLanguages).
|
|
|
|
|
|
|
|
choose_language(Req, State, []) ->
|
|
|
|
not_acceptable(Req, State);
|
|
|
|
choose_language(Req, State=#state{languages_p=LP}, [Language|Tail]) ->
|
|
|
|
match_language(Req, State, Tail, LP, Language).
|
|
|
|
|
|
|
|
match_language(Req, State, Accept, [], _Language) ->
|
|
|
|
choose_language(Req, State, Accept);
|
|
|
|
match_language(Req, State, _Accept, [Provided|_Tail], {'*', _Quality}) ->
|
|
|
|
set_language(Req, State#state{language_a=Provided});
|
|
|
|
match_language(Req, State, _Accept, [Provided|_Tail], {Provided, _Quality}) ->
|
|
|
|
set_language(Req, State#state{language_a=Provided});
|
|
|
|
match_language(Req, State, Accept, [Provided|Tail],
|
|
|
|
Language = {Tag, _Quality}) ->
|
|
|
|
Length = byte_size(Tag),
|
|
|
|
case Provided of
|
|
|
|
<< Tag:Length/binary, $-, _Any/bits >> ->
|
|
|
|
set_language(Req, State#state{language_a=Provided});
|
|
|
|
_Any ->
|
|
|
|
match_language(Req, State, Accept, Tail, Language)
|
|
|
|
end.
|
|
|
|
|
2012-09-15 22:51:37 +02:00
|
|
|
set_language(Req, State=#state{language_a=Language}) ->
|
2012-11-10 17:24:25 -05:00
|
|
|
Req2 = cowboy_req:set_resp_header(<<"content-language">>, Language, Req),
|
Initial commit with connection/streams
Breaking changes with previous commit. This is a very large change,
and I am giving up on making a single commit that fixes everything.
More commits will follow slowly adding back features, introducing
new tests and fixing the documentation.
This change contains most of the work toward unifying the interface
for handling both HTTP/1.1 and HTTP/2. HTTP/1.1 connections are now
no longer 1 process per connection; instead by default 1 process per
request is also created. This has a number of pros and cons.
Because it has cons, we also allow users to use a lower-level API
that acts on "streams" (requests/responses) directly at the connection
process-level. If performance is a concern, one can always write a
stream handler. The performance in this case will be even greater
than with Cowboy 1, although all the special handlers are unavailable.
When switching to Websocket, after the handler returns from init/2,
Cowboy stops the stream and the Websocket protocol takes over the
connection process. Websocket then calls websocket_init/2 for any
additional initialization such as timers, because the process is
different in init/2 and websocket_*/* functions. This however would
allow us to use websocket_init/2 for sending messages on connect,
instead of sending ourselves a message and be subject to races.
Note that websocket_init/2 is optional.
This is all a big change and while most of the tests pass, some
functionality currently doesn't. SPDY is broken and will be removed
soon in favor of HTTP/2. Automatic compression is currently disabled.
The cowboy_req interface probably still have a few functions that
need to be updated. The docs and examples do not refer the current
functionality anymore.
Everything will be fixed over time. Feedback is more than welcome.
Open a ticket!
2016-02-10 17:28:32 +01:00
|
|
|
charsets_provided(Req2#{language => Language}, State).
|
2011-12-05 22:53:59 +01:00
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% charsets_provided should return a list of binary values indicating
|
|
|
|
%% which charsets are accepted by the resource.
|
2011-12-05 22:53:59 +01:00
|
|
|
charsets_provided(Req, State) ->
|
|
|
|
case call(Req, State, charsets_provided) of
|
|
|
|
no_call ->
|
|
|
|
set_content_type(Req, State);
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState} ->
|
2012-01-23 08:24:15 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2012-01-23 08:11:29 +01:00
|
|
|
{[], Req2, HandlerState} ->
|
|
|
|
not_acceptable(Req2, State#state{handler_state=HandlerState});
|
|
|
|
{CP, Req2, HandlerState} ->
|
|
|
|
State2 = State#state{handler_state=HandlerState, charsets_p=CP},
|
2014-09-23 16:43:29 +03:00
|
|
|
case cowboy_req:parse_header(<<"accept-charset">>, Req2) of
|
2011-12-05 22:53:59 +01:00
|
|
|
undefined ->
|
2014-09-23 16:43:29 +03:00
|
|
|
set_content_type(Req2, State2#state{charset_a=hd(CP)});
|
2011-12-05 22:53:59 +01:00
|
|
|
AcceptCharset ->
|
|
|
|
AcceptCharset2 = prioritize_charsets(AcceptCharset),
|
2014-09-23 16:43:29 +03:00
|
|
|
choose_charset(Req2, State2, AcceptCharset2)
|
2011-12-05 22:53:59 +01:00
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
|
|
|
%% The special value "*", if present in the Accept-Charset field,
|
|
|
|
%% matches every character set (including ISO-8859-1) which is not
|
|
|
|
%% mentioned elsewhere in the Accept-Charset field. If no "*" is present
|
|
|
|
%% in an Accept-Charset field, then all character sets not explicitly
|
|
|
|
%% mentioned get a quality value of 0, except for ISO-8859-1, which gets
|
|
|
|
%% a quality value of 1 if not explicitly mentioned.
|
|
|
|
prioritize_charsets(AcceptCharsets) ->
|
|
|
|
AcceptCharsets2 = lists:sort(
|
|
|
|
fun ({_CharsetA, QualityA}, {_CharsetB, QualityB}) ->
|
|
|
|
QualityA > QualityB
|
|
|
|
end, AcceptCharsets),
|
|
|
|
case lists:keymember(<<"*">>, 1, AcceptCharsets2) of
|
|
|
|
true -> AcceptCharsets2;
|
2013-05-16 17:38:29 +02:00
|
|
|
false ->
|
|
|
|
case lists:keymember(<<"iso-8859-1">>, 1, AcceptCharsets2) of
|
|
|
|
true -> AcceptCharsets2;
|
|
|
|
false -> [{<<"iso-8859-1">>, 1000}|AcceptCharsets2]
|
|
|
|
end
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
choose_charset(Req, State, []) ->
|
|
|
|
not_acceptable(Req, State);
|
|
|
|
choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) ->
|
|
|
|
match_charset(Req, State, Tail, CP, Charset).
|
|
|
|
|
|
|
|
match_charset(Req, State, Accept, [], _Charset) ->
|
|
|
|
choose_charset(Req, State, Accept);
|
2013-05-16 17:32:07 +02:00
|
|
|
match_charset(Req, State, _Accept, [Provided|_], {Provided, _}) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
set_content_type(Req, State#state{charset_a=Provided});
|
2012-07-21 19:00:52 +02:00
|
|
|
match_charset(Req, State, Accept, [_|Tail], Charset) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
match_charset(Req, State, Accept, Tail, Charset).
|
|
|
|
|
2012-09-15 22:51:37 +02:00
|
|
|
set_content_type(Req, State=#state{
|
2011-12-05 22:53:59 +01:00
|
|
|
content_type_a={{Type, SubType, Params}, _Fun},
|
|
|
|
charset_a=Charset}) ->
|
|
|
|
ParamsBin = set_content_type_build_params(Params, []),
|
|
|
|
ContentType = [Type, <<"/">>, SubType, ParamsBin],
|
|
|
|
ContentType2 = case Charset of
|
|
|
|
undefined -> ContentType;
|
|
|
|
Charset -> [ContentType, <<"; charset=">>, Charset]
|
|
|
|
end,
|
2012-11-10 17:24:25 -05:00
|
|
|
Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req),
|
Initial commit with connection/streams
Breaking changes with previous commit. This is a very large change,
and I am giving up on making a single commit that fixes everything.
More commits will follow slowly adding back features, introducing
new tests and fixing the documentation.
This change contains most of the work toward unifying the interface
for handling both HTTP/1.1 and HTTP/2. HTTP/1.1 connections are now
no longer 1 process per connection; instead by default 1 process per
request is also created. This has a number of pros and cons.
Because it has cons, we also allow users to use a lower-level API
that acts on "streams" (requests/responses) directly at the connection
process-level. If performance is a concern, one can always write a
stream handler. The performance in this case will be even greater
than with Cowboy 1, although all the special handlers are unavailable.
When switching to Websocket, after the handler returns from init/2,
Cowboy stops the stream and the Websocket protocol takes over the
connection process. Websocket then calls websocket_init/2 for any
additional initialization such as timers, because the process is
different in init/2 and websocket_*/* functions. This however would
allow us to use websocket_init/2 for sending messages on connect,
instead of sending ourselves a message and be subject to races.
Note that websocket_init/2 is optional.
This is all a big change and while most of the tests pass, some
functionality currently doesn't. SPDY is broken and will be removed
soon in favor of HTTP/2. Automatic compression is currently disabled.
The cowboy_req interface probably still have a few functions that
need to be updated. The docs and examples do not refer the current
functionality anymore.
Everything will be fixed over time. Feedback is more than welcome.
Open a ticket!
2016-02-10 17:28:32 +01:00
|
|
|
encodings_provided(Req2#{charset => Charset}, State).
|
2011-12-05 22:53:59 +01:00
|
|
|
|
2013-02-15 22:41:55 +07:00
|
|
|
set_content_type_build_params('*', []) ->
|
|
|
|
<<>>;
|
2011-12-05 22:53:59 +01:00
|
|
|
set_content_type_build_params([], []) ->
|
|
|
|
<<>>;
|
|
|
|
set_content_type_build_params([], Acc) ->
|
|
|
|
lists:reverse(Acc);
|
|
|
|
set_content_type_build_params([{Attr, Value}|Tail], Acc) ->
|
|
|
|
set_content_type_build_params(Tail, [[Attr, <<"=">>, Value], <<";">>|Acc]).
|
|
|
|
|
|
|
|
%% @todo Match for identity as we provide nothing else for now.
|
|
|
|
%% @todo Don't forget to set the Content-Encoding header when we reply a body
|
|
|
|
%% and the found encoding is something other than identity.
|
|
|
|
encodings_provided(Req, State) ->
|
|
|
|
variances(Req, State).
|
|
|
|
|
|
|
|
not_acceptable(Req, State) ->
|
|
|
|
respond(Req, State, 406).
|
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% variances/2 should return a list of headers that will be added
|
|
|
|
%% to the Vary response header. The Accept, Accept-Language,
|
|
|
|
%% Accept-Charset and Accept-Encoding headers do not need to be
|
|
|
|
%% specified.
|
|
|
|
%%
|
2011-12-05 22:53:59 +01:00
|
|
|
%% @todo Do Accept-Encoding too when we handle it.
|
|
|
|
%% @todo Does the order matter?
|
|
|
|
variances(Req, State=#state{content_types_p=CTP,
|
|
|
|
languages_p=LP, charsets_p=CP}) ->
|
2011-12-11 19:57:07 +01:00
|
|
|
Variances = case CTP of
|
|
|
|
[] -> [];
|
|
|
|
[_] -> [];
|
2012-11-10 17:24:25 -05:00
|
|
|
[_|_] -> [<<"accept">>]
|
2011-12-05 22:53:59 +01:00
|
|
|
end,
|
2011-12-11 19:57:07 +01:00
|
|
|
Variances2 = case LP of
|
|
|
|
[] -> Variances;
|
|
|
|
[_] -> Variances;
|
2012-11-10 17:24:25 -05:00
|
|
|
[_|_] -> [<<"accept-language">>|Variances]
|
2011-12-05 22:53:59 +01:00
|
|
|
end,
|
2011-12-11 19:57:07 +01:00
|
|
|
Variances3 = case CP of
|
|
|
|
[] -> Variances2;
|
|
|
|
[_] -> Variances2;
|
2012-11-10 17:24:25 -05:00
|
|
|
[_|_] -> [<<"accept-charset">>|Variances2]
|
2011-12-05 22:53:59 +01:00
|
|
|
end,
|
2013-02-09 16:45:30 +01:00
|
|
|
try variances(Req, State, Variances3) of
|
|
|
|
{Variances4, Req2, State2} ->
|
|
|
|
case [[<<", ">>, V] || V <- Variances4] of
|
|
|
|
[] ->
|
|
|
|
resource_exists(Req2, State2);
|
|
|
|
[[<<", ">>, H]|Variances5] ->
|
|
|
|
Req3 = cowboy_req:set_resp_header(
|
|
|
|
<<"vary">>, [H|Variances5], Req2),
|
|
|
|
resource_exists(Req3, State2)
|
|
|
|
end
|
|
|
|
catch Class:Reason ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2013-02-09 16:45:30 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
variances(Req, State, Variances) ->
|
|
|
|
case unsafe_call(Req, State, variances) of
|
2011-12-05 22:53:59 +01:00
|
|
|
no_call ->
|
2013-02-09 16:45:30 +01:00
|
|
|
{Variances, Req, State};
|
2011-12-05 22:53:59 +01:00
|
|
|
{HandlerVariances, Req2, HandlerState} ->
|
2013-02-09 16:45:30 +01:00
|
|
|
{Variances ++ HandlerVariances, Req2,
|
2011-12-05 22:53:59 +01:00
|
|
|
State#state{handler_state=HandlerState}}
|
|
|
|
end.
|
|
|
|
|
|
|
|
resource_exists(Req, State) ->
|
|
|
|
expect(Req, State, resource_exists, true,
|
2012-10-25 15:29:02 -04:00
|
|
|
fun if_match_exists/2, fun if_match_must_not_exist/2).
|
2011-12-05 22:53:59 +01:00
|
|
|
|
|
|
|
if_match_exists(Req, State) ->
|
2013-04-20 15:52:31 +02:00
|
|
|
State2 = State#state{exists=true},
|
2012-09-21 09:18:56 +02:00
|
|
|
case cowboy_req:parse_header(<<"if-match">>, Req) of
|
2014-09-23 16:43:29 +03:00
|
|
|
undefined ->
|
|
|
|
if_unmodified_since_exists(Req, State2);
|
|
|
|
'*' ->
|
|
|
|
if_unmodified_since_exists(Req, State2);
|
|
|
|
ETagsList ->
|
|
|
|
if_match(Req, State2, ETagsList)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
if_match(Req, State, EtagsList) ->
|
2013-02-09 16:45:30 +01:00
|
|
|
try generate_etag(Req, State) of
|
2016-06-06 17:32:04 +02:00
|
|
|
%% Strong Etag comparison: weak Etag never matches.
|
|
|
|
{{weak, _}, Req2, State2} ->
|
|
|
|
precondition_failed(Req2, State2);
|
2013-02-09 16:45:30 +01:00
|
|
|
{Etag, Req2, State2} ->
|
|
|
|
case lists:member(Etag, EtagsList) of
|
2016-06-06 17:33:46 +02:00
|
|
|
true -> if_none_match_exists(Req2, State2);
|
2013-02-09 16:45:30 +01:00
|
|
|
%% Etag may be `undefined' which cannot be a member.
|
|
|
|
false -> precondition_failed(Req2, State2)
|
|
|
|
end
|
|
|
|
catch Class:Reason ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
2012-10-25 15:29:02 -04:00
|
|
|
if_match_must_not_exist(Req, State) ->
|
2012-09-21 09:18:56 +02:00
|
|
|
case cowboy_req:header(<<"if-match">>, Req) of
|
2014-09-23 16:43:29 +03:00
|
|
|
undefined -> is_put_to_missing_resource(Req, State);
|
|
|
|
_ -> precondition_failed(Req, State)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
if_unmodified_since_exists(Req, State) ->
|
2014-09-23 16:43:29 +03:00
|
|
|
try cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of
|
|
|
|
undefined ->
|
|
|
|
if_none_match_exists(Req, State);
|
|
|
|
IfUnmodifiedSince ->
|
|
|
|
if_unmodified_since(Req, State, IfUnmodifiedSince)
|
|
|
|
catch _:_ ->
|
|
|
|
if_none_match_exists(Req, State)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
%% If LastModified is the atom 'no_call', we continue.
|
|
|
|
if_unmodified_since(Req, State, IfUnmodifiedSince) ->
|
2013-02-09 16:45:30 +01:00
|
|
|
try last_modified(Req, State) of
|
|
|
|
{LastModified, Req2, State2} ->
|
|
|
|
case LastModified > IfUnmodifiedSince of
|
|
|
|
true -> precondition_failed(Req2, State2);
|
|
|
|
false -> if_none_match_exists(Req2, State2)
|
|
|
|
end
|
|
|
|
catch Class:Reason ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
if_none_match_exists(Req, State) ->
|
2012-09-21 09:18:56 +02:00
|
|
|
case cowboy_req:parse_header(<<"if-none-match">>, Req) of
|
2014-09-23 16:43:29 +03:00
|
|
|
undefined ->
|
|
|
|
if_modified_since_exists(Req, State);
|
|
|
|
'*' ->
|
|
|
|
precondition_is_head_get(Req, State);
|
|
|
|
EtagsList ->
|
|
|
|
if_none_match(Req, State, EtagsList)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
if_none_match(Req, State, EtagsList) ->
|
2013-02-09 16:45:30 +01:00
|
|
|
try generate_etag(Req, State) of
|
|
|
|
{Etag, Req2, State2} ->
|
|
|
|
case Etag of
|
|
|
|
undefined ->
|
|
|
|
precondition_failed(Req2, State2);
|
|
|
|
Etag ->
|
2016-06-06 17:33:03 +02:00
|
|
|
case is_weak_match(Etag, EtagsList) of
|
2013-02-09 16:45:30 +01:00
|
|
|
true -> precondition_is_head_get(Req2, State2);
|
2016-06-06 17:33:46 +02:00
|
|
|
false -> method(Req2, State2)
|
2013-02-09 16:45:30 +01:00
|
|
|
end
|
2011-12-05 22:53:59 +01:00
|
|
|
end
|
2013-02-09 16:45:30 +01:00
|
|
|
catch Class:Reason ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
2016-06-06 17:33:03 +02:00
|
|
|
%% Weak Etag comparison: only check the opaque tag.
|
|
|
|
is_weak_match(_, []) ->
|
|
|
|
false;
|
|
|
|
is_weak_match({_, Tag}, [{_, Tag}|_]) ->
|
|
|
|
true;
|
|
|
|
is_weak_match(Etag, [_|Tail]) ->
|
|
|
|
is_weak_match(Etag, Tail).
|
|
|
|
|
2012-09-15 23:53:30 +02:00
|
|
|
precondition_is_head_get(Req, State=#state{method=Method})
|
2012-09-20 06:22:51 +02:00
|
|
|
when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
|
2011-12-05 22:53:59 +01:00
|
|
|
not_modified(Req, State);
|
|
|
|
precondition_is_head_get(Req, State) ->
|
|
|
|
precondition_failed(Req, State).
|
|
|
|
|
|
|
|
if_modified_since_exists(Req, State) ->
|
2014-09-23 16:43:29 +03:00
|
|
|
try cowboy_req:parse_header(<<"if-modified-since">>, Req) of
|
|
|
|
undefined ->
|
|
|
|
method(Req, State);
|
|
|
|
IfModifiedSince ->
|
|
|
|
if_modified_since_now(Req, State, IfModifiedSince)
|
|
|
|
catch _:_ ->
|
|
|
|
method(Req, State)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
if_modified_since_now(Req, State, IfModifiedSince) ->
|
|
|
|
case IfModifiedSince > erlang:universaltime() of
|
|
|
|
true -> method(Req, State);
|
|
|
|
false -> if_modified_since(Req, State, IfModifiedSince)
|
|
|
|
end.
|
|
|
|
|
|
|
|
if_modified_since(Req, State, IfModifiedSince) ->
|
2013-02-09 16:45:30 +01:00
|
|
|
try last_modified(Req, State) of
|
2016-10-06 16:48:34 +08:00
|
|
|
{undefined, Req2, State2} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
method(Req2, State2);
|
2013-02-09 16:45:30 +01:00
|
|
|
{LastModified, Req2, State2} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
case LastModified > IfModifiedSince of
|
|
|
|
true -> method(Req2, State2);
|
|
|
|
false -> not_modified(Req2, State2)
|
|
|
|
end
|
2013-02-09 16:45:30 +01:00
|
|
|
catch Class:Reason ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
2012-09-16 01:55:40 +02:00
|
|
|
not_modified(Req, State) ->
|
2012-11-10 17:24:25 -05:00
|
|
|
Req2 = cowboy_req:delete_resp_header(<<"content-type">>, Req),
|
2013-02-09 16:45:30 +01:00
|
|
|
try set_resp_etag(Req2, State) of
|
|
|
|
{Req3, State2} ->
|
|
|
|
try set_resp_expires(Req3, State2) of
|
|
|
|
{Req4, State3} ->
|
|
|
|
respond(Req4, State3, 304)
|
|
|
|
catch Class:Reason ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State2, Class, Reason)
|
2013-02-09 16:45:30 +01:00
|
|
|
end
|
|
|
|
catch Class:Reason ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2013-02-09 16:45:30 +01:00
|
|
|
end.
|
2011-12-05 22:53:59 +01:00
|
|
|
|
|
|
|
precondition_failed(Req, State) ->
|
|
|
|
respond(Req, State, 412).
|
|
|
|
|
2012-09-20 06:22:51 +02:00
|
|
|
is_put_to_missing_resource(Req, State=#state{method= <<"PUT">>}) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
moved_permanently(Req, State, fun is_conflict/2);
|
|
|
|
is_put_to_missing_resource(Req, State) ->
|
|
|
|
previously_existed(Req, State).
|
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% moved_permanently/2 should return either false or {true, Location}
|
|
|
|
%% with Location the full new URI of the resource.
|
2011-12-05 22:53:59 +01:00
|
|
|
moved_permanently(Req, State, OnFalse) ->
|
|
|
|
case call(Req, State, moved_permanently) of
|
2012-01-23 08:11:29 +01:00
|
|
|
{{true, Location}, Req2, HandlerState} ->
|
2012-09-16 03:51:07 +02:00
|
|
|
Req3 = cowboy_req:set_resp_header(
|
2012-11-10 17:24:25 -05:00
|
|
|
<<"location">>, Location, Req2),
|
2012-01-23 08:11:29 +01:00
|
|
|
respond(Req3, State#state{handler_state=HandlerState}, 301);
|
|
|
|
{false, Req2, HandlerState} ->
|
|
|
|
OnFalse(Req2, State#state{handler_state=HandlerState});
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState} ->
|
2012-01-23 08:24:15 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2011-12-05 22:53:59 +01:00
|
|
|
no_call ->
|
|
|
|
OnFalse(Req, State)
|
|
|
|
end.
|
|
|
|
|
|
|
|
previously_existed(Req, State) ->
|
|
|
|
expect(Req, State, previously_existed, false,
|
|
|
|
fun (R, S) -> is_post_to_missing_resource(R, S, 404) end,
|
|
|
|
fun (R, S) -> moved_permanently(R, S, fun moved_temporarily/2) end).
|
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% moved_temporarily/2 should return either false or {true, Location}
|
|
|
|
%% with Location the full new URI of the resource.
|
2011-12-05 22:53:59 +01:00
|
|
|
moved_temporarily(Req, State) ->
|
|
|
|
case call(Req, State, moved_temporarily) of
|
2012-01-23 08:11:29 +01:00
|
|
|
{{true, Location}, Req2, HandlerState} ->
|
2012-09-16 03:51:07 +02:00
|
|
|
Req3 = cowboy_req:set_resp_header(
|
2012-11-10 17:24:25 -05:00
|
|
|
<<"location">>, Location, Req2),
|
2012-01-23 08:11:29 +01:00
|
|
|
respond(Req3, State#state{handler_state=HandlerState}, 307);
|
|
|
|
{false, Req2, HandlerState} ->
|
|
|
|
is_post_to_missing_resource(Req2, State#state{handler_state=HandlerState}, 410);
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState} ->
|
2012-01-23 08:24:15 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2011-12-05 22:53:59 +01:00
|
|
|
no_call ->
|
|
|
|
is_post_to_missing_resource(Req, State, 410)
|
|
|
|
end.
|
|
|
|
|
2012-09-20 06:22:51 +02:00
|
|
|
is_post_to_missing_resource(Req, State=#state{method= <<"POST">>}, OnFalse) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
allow_missing_post(Req, State, OnFalse);
|
|
|
|
is_post_to_missing_resource(Req, State, OnFalse) ->
|
|
|
|
respond(Req, State, OnFalse).
|
|
|
|
|
|
|
|
allow_missing_post(Req, State, OnFalse) ->
|
2013-04-20 15:52:31 +02:00
|
|
|
expect(Req, State, allow_missing_post, true, fun accept_resource/2, OnFalse).
|
2011-12-05 22:53:59 +01:00
|
|
|
|
2012-09-20 06:22:51 +02:00
|
|
|
method(Req, State=#state{method= <<"DELETE">>}) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
delete_resource(Req, State);
|
2012-09-20 06:22:51 +02:00
|
|
|
method(Req, State=#state{method= <<"PUT">>}) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
is_conflict(Req, State);
|
2013-04-11 17:59:54 +02:00
|
|
|
method(Req, State=#state{method=Method})
|
|
|
|
when Method =:= <<"POST">>; Method =:= <<"PATCH">> ->
|
2013-04-20 15:52:31 +02:00
|
|
|
accept_resource(Req, State);
|
2012-09-15 23:53:30 +02:00
|
|
|
method(Req, State=#state{method=Method})
|
2012-09-20 06:22:51 +02:00
|
|
|
when Method =:= <<"GET">>; Method =:= <<"HEAD">> ->
|
2013-02-09 16:45:30 +01:00
|
|
|
set_resp_body_etag(Req, State);
|
2011-12-05 22:53:59 +01:00
|
|
|
method(Req, State) ->
|
2012-09-15 23:53:30 +02:00
|
|
|
multiple_choices(Req, State).
|
2011-12-05 22:53:59 +01:00
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% delete_resource/2 should start deleting the resource and return.
|
2011-12-05 22:53:59 +01:00
|
|
|
delete_resource(Req, State) ->
|
2012-01-23 16:10:41 -06:00
|
|
|
expect(Req, State, delete_resource, false, 500, fun delete_completed/2).
|
2011-12-05 22:53:59 +01:00
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% delete_completed/2 indicates whether the resource has been deleted yet.
|
2011-12-05 22:53:59 +01:00
|
|
|
delete_completed(Req, State) ->
|
|
|
|
expect(Req, State, delete_completed, true, fun has_resp_body/2, 202).
|
|
|
|
|
|
|
|
is_conflict(Req, State) ->
|
2013-04-20 15:52:31 +02:00
|
|
|
expect(Req, State, is_conflict, false, fun accept_resource/2, 409).
|
2011-12-05 22:53:59 +01:00
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% content_types_accepted should return a list of media types and their
|
|
|
|
%% associated callback functions in the same format as content_types_provided.
|
|
|
|
%%
|
|
|
|
%% The callback will then be called and is expected to process the content
|
2013-04-11 17:59:54 +02:00
|
|
|
%% pushed to the resource in the request body.
|
2012-10-15 17:56:55 -05:00
|
|
|
%%
|
2013-04-11 17:59:54 +02:00
|
|
|
%% content_types_accepted SHOULD return a different list
|
2012-10-15 17:56:55 -05:00
|
|
|
%% for each HTTP method.
|
2013-04-20 15:52:31 +02:00
|
|
|
accept_resource(Req, State) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
case call(Req, State, content_types_accepted) of
|
|
|
|
no_call ->
|
|
|
|
respond(Req, State, 415);
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState} ->
|
2012-01-23 08:24:15 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2012-01-23 08:11:29 +01:00
|
|
|
{CTA, Req2, HandlerState} ->
|
2013-04-26 18:34:10 +07:00
|
|
|
CTA2 = [normalize_content_types(P) || P <- CTA],
|
2012-01-23 08:11:29 +01:00
|
|
|
State2 = State#state{handler_state=HandlerState},
|
2014-09-23 16:43:29 +03:00
|
|
|
try cowboy_req:parse_header(<<"content-type">>, Req2) of
|
|
|
|
ContentType ->
|
|
|
|
choose_content_type(Req2, State2, ContentType, CTA2)
|
|
|
|
catch _:_ ->
|
|
|
|
respond(Req2, State2, 415)
|
2013-04-12 09:51:47 +04:00
|
|
|
end
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
2012-03-13 00:46:56 +01:00
|
|
|
%% The special content type '*' will always match. It can be used as a
|
|
|
|
%% catch-all content type for accepting any kind of request content.
|
|
|
|
%% Note that because it will always match, it should be the last of the
|
|
|
|
%% list of content types, otherwise it'll shadow the ones following.
|
2013-04-20 15:52:31 +02:00
|
|
|
choose_content_type(Req, State, _ContentType, []) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
respond(Req, State, 415);
|
2013-04-20 15:52:31 +02:00
|
|
|
choose_content_type(Req, State, ContentType, [{Accepted, Fun}|_Tail])
|
2013-02-15 22:41:55 +07:00
|
|
|
when Accepted =:= '*'; Accepted =:= ContentType ->
|
2013-04-20 15:52:31 +02:00
|
|
|
process_content_type(Req, State, Fun);
|
2013-02-15 22:41:55 +07:00
|
|
|
%% The special parameter '*' will always match any kind of content type
|
|
|
|
%% parameters.
|
|
|
|
%% Note that because it will always match, it should be the last of the
|
|
|
|
%% list for specific content type, otherwise it'll shadow the ones following.
|
2013-04-20 15:52:31 +02:00
|
|
|
choose_content_type(Req, State, {Type, SubType, Param},
|
2013-02-15 22:41:55 +07:00
|
|
|
[{{Type, SubType, AcceptedParam}, Fun}|_Tail])
|
|
|
|
when AcceptedParam =:= '*'; AcceptedParam =:= Param ->
|
2013-04-20 15:52:31 +02:00
|
|
|
process_content_type(Req, State, Fun);
|
|
|
|
choose_content_type(Req, State, ContentType, [_Any|Tail]) ->
|
|
|
|
choose_content_type(Req, State, ContentType, Tail).
|
2013-02-15 22:41:55 +07:00
|
|
|
|
2013-08-24 20:36:23 +02:00
|
|
|
process_content_type(Req, State=#state{method=Method, exists=Exists}, Fun) ->
|
|
|
|
try case call(Req, State, Fun) of
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState2} ->
|
2013-01-22 19:01:56 +07:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState2});
|
2013-04-20 15:52:31 +02:00
|
|
|
{true, Req2, HandlerState2} when Exists ->
|
|
|
|
State2 = State#state{handler_state=HandlerState2},
|
|
|
|
next(Req2, State2, fun has_resp_body/2);
|
2013-01-22 19:01:56 +07:00
|
|
|
{true, Req2, HandlerState2} ->
|
|
|
|
State2 = State#state{handler_state=HandlerState2},
|
2013-04-20 15:52:31 +02:00
|
|
|
next(Req2, State2, fun maybe_created/2);
|
2013-01-22 19:01:56 +07:00
|
|
|
{false, Req2, HandlerState2} ->
|
|
|
|
State2 = State#state{handler_state=HandlerState2},
|
2014-06-30 17:49:36 +02:00
|
|
|
respond(Req2, State2, 400);
|
2013-05-16 15:14:11 +02:00
|
|
|
{{true, ResURL}, Req2, HandlerState2} when Method =:= <<"POST">> ->
|
2013-04-11 17:59:54 +02:00
|
|
|
State2 = State#state{handler_state=HandlerState2},
|
|
|
|
Req3 = cowboy_req:set_resp_header(
|
|
|
|
<<"location">>, ResURL, Req2),
|
2013-04-20 15:52:31 +02:00
|
|
|
if
|
|
|
|
Exists -> respond(Req3, State2, 303);
|
|
|
|
true -> respond(Req3, State2, 201)
|
|
|
|
end
|
2013-08-24 20:36:23 +02:00
|
|
|
end catch Class:Reason = {case_clause, no_call} ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2013-02-15 22:41:55 +07:00
|
|
|
end.
|
2011-12-05 22:53:59 +01:00
|
|
|
|
2013-11-09 18:26:49 +01:00
|
|
|
%% If PUT was used then the resource has been created at the current URL.
|
|
|
|
%% Otherwise, if a location header has been set then the resource has been
|
|
|
|
%% created at a new URL. If not, send a 200 or 204 as expected from a
|
|
|
|
%% POST or PATCH request.
|
|
|
|
maybe_created(Req, State=#state{method= <<"PUT">>}) ->
|
|
|
|
respond(Req, State, 201);
|
2013-04-20 15:52:31 +02:00
|
|
|
maybe_created(Req, State) ->
|
2012-11-10 17:24:25 -05:00
|
|
|
case cowboy_req:has_resp_header(<<"location">>, Req) of
|
2011-12-05 22:53:59 +01:00
|
|
|
true -> respond(Req, State, 201);
|
|
|
|
false -> has_resp_body(Req, State)
|
|
|
|
end.
|
|
|
|
|
|
|
|
has_resp_body(Req, State) ->
|
2012-08-27 13:28:57 +02:00
|
|
|
case cowboy_req:has_resp_body(Req) of
|
2011-12-05 22:53:59 +01:00
|
|
|
true -> multiple_choices(Req, State);
|
|
|
|
false -> respond(Req, State, 204)
|
|
|
|
end.
|
|
|
|
|
2013-02-09 16:45:30 +01:00
|
|
|
%% Set the Etag header if any for the response provided.
|
|
|
|
set_resp_body_etag(Req, State) ->
|
|
|
|
try set_resp_etag(Req, State) of
|
|
|
|
{Req2, State2} ->
|
|
|
|
set_resp_body_last_modified(Req2, State2)
|
|
|
|
catch Class:Reason ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2013-02-09 16:45:30 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
%% Set the Last-Modified header if any for the response provided.
|
|
|
|
set_resp_body_last_modified(Req, State) ->
|
|
|
|
try last_modified(Req, State) of
|
|
|
|
{LastModified, Req2, State2} ->
|
|
|
|
case LastModified of
|
|
|
|
LastModified when is_atom(LastModified) ->
|
|
|
|
set_resp_body_expires(Req2, State2);
|
|
|
|
LastModified ->
|
|
|
|
LastModifiedBin = cowboy_clock:rfc1123(LastModified),
|
|
|
|
Req3 = cowboy_req:set_resp_header(
|
|
|
|
<<"last-modified">>, LastModifiedBin, Req2),
|
|
|
|
set_resp_body_expires(Req3, State2)
|
|
|
|
end
|
|
|
|
catch Class:Reason ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2013-02-09 16:45:30 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
%% Set the Expires header if any for the response provided.
|
|
|
|
set_resp_body_expires(Req, State) ->
|
|
|
|
try set_resp_expires(Req, State) of
|
|
|
|
{Req2, State2} ->
|
|
|
|
set_resp_body(Req2, State2)
|
|
|
|
catch Class:Reason ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2013-02-09 16:45:30 +01:00
|
|
|
end.
|
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% Set the response headers and call the callback found using
|
|
|
|
%% content_types_provided/2 to obtain the request body and add
|
|
|
|
%% it to the response.
|
2013-08-24 20:36:23 +02:00
|
|
|
set_resp_body(Req, State=#state{content_type_a={_, Callback}}) ->
|
|
|
|
try case call(Req, State, Callback) of
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState2} ->
|
2013-02-09 16:45:30 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState2});
|
|
|
|
{Body, Req2, HandlerState2} ->
|
|
|
|
State2 = State#state{handler_state=HandlerState2},
|
2017-01-02 18:27:03 +01:00
|
|
|
Req3 = cowboy_req:set_resp_body(Body, Req2),
|
2013-02-09 16:45:30 +01:00
|
|
|
multiple_choices(Req3, State2)
|
2013-08-24 20:36:23 +02:00
|
|
|
end catch Class:Reason = {case_clause, no_call} ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2012-09-15 23:53:30 +02:00
|
|
|
end.
|
2011-12-05 22:53:59 +01:00
|
|
|
|
|
|
|
multiple_choices(Req, State) ->
|
|
|
|
expect(Req, State, multiple_choices, false, 200, 300).
|
|
|
|
|
|
|
|
%% Response utility functions.
|
|
|
|
|
|
|
|
set_resp_etag(Req, State) ->
|
|
|
|
{Etag, Req2, State2} = generate_etag(Req, State),
|
|
|
|
case Etag of
|
|
|
|
undefined ->
|
|
|
|
{Req2, State2};
|
|
|
|
Etag ->
|
2012-09-16 03:51:07 +02:00
|
|
|
Req3 = cowboy_req:set_resp_header(
|
2012-11-10 17:24:25 -05:00
|
|
|
<<"etag">>, encode_etag(Etag), Req2),
|
2011-12-05 22:53:59 +01:00
|
|
|
{Req3, State2}
|
|
|
|
end.
|
|
|
|
|
2012-02-28 18:35:26 +01:00
|
|
|
-spec encode_etag({strong | weak, binary()}) -> iolist().
|
|
|
|
encode_etag({strong, Etag}) -> [$",Etag,$"];
|
|
|
|
encode_etag({weak, Etag}) -> ["W/\"",Etag,$"].
|
|
|
|
|
2011-12-05 22:53:59 +01:00
|
|
|
set_resp_expires(Req, State) ->
|
|
|
|
{Expires, Req2, State2} = expires(Req, State),
|
|
|
|
case Expires of
|
|
|
|
Expires when is_atom(Expires) ->
|
|
|
|
{Req2, State2};
|
2014-06-06 02:37:24 -04:00
|
|
|
Expires when is_binary(Expires) ->
|
|
|
|
Req3 = cowboy_req:set_resp_header(
|
2014-06-10 08:38:54 +02:00
|
|
|
<<"expires">>, Expires, Req2),
|
2014-06-06 02:37:24 -04:00
|
|
|
{Req3, State2};
|
2011-12-05 22:53:59 +01:00
|
|
|
Expires ->
|
2012-12-03 15:57:27 +01:00
|
|
|
ExpiresBin = cowboy_clock:rfc1123(Expires),
|
2012-09-16 03:51:07 +02:00
|
|
|
Req3 = cowboy_req:set_resp_header(
|
2012-12-03 15:57:27 +01:00
|
|
|
<<"expires">>, ExpiresBin, Req2),
|
2011-12-05 22:53:59 +01:00
|
|
|
{Req3, State2}
|
|
|
|
end.
|
|
|
|
|
|
|
|
%% Info retrieval. No logic.
|
|
|
|
|
|
|
|
generate_etag(Req, State=#state{etag=no_call}) ->
|
|
|
|
{undefined, Req, State};
|
|
|
|
generate_etag(Req, State=#state{etag=undefined}) ->
|
2013-02-09 16:45:30 +01:00
|
|
|
case unsafe_call(Req, State, generate_etag) of
|
2011-12-05 22:53:59 +01:00
|
|
|
no_call ->
|
|
|
|
{undefined, Req, State#state{etag=no_call}};
|
2012-02-28 18:35:26 +01:00
|
|
|
{Etag, Req2, HandlerState} when is_binary(Etag) ->
|
2015-02-04 16:18:28 +01:00
|
|
|
Etag2 = cow_http_hd:parse_etag(Etag),
|
2012-02-28 18:35:26 +01:00
|
|
|
{Etag2, Req2, State#state{handler_state=HandlerState, etag=Etag2}};
|
2012-01-23 08:11:29 +01:00
|
|
|
{Etag, Req2, HandlerState} ->
|
|
|
|
{Etag, Req2, State#state{handler_state=HandlerState, etag=Etag}}
|
2011-12-05 22:53:59 +01:00
|
|
|
end;
|
|
|
|
generate_etag(Req, State=#state{etag=Etag}) ->
|
|
|
|
{Etag, Req, State}.
|
|
|
|
|
|
|
|
last_modified(Req, State=#state{last_modified=no_call}) ->
|
|
|
|
{undefined, Req, State};
|
|
|
|
last_modified(Req, State=#state{last_modified=undefined}) ->
|
2013-02-09 16:45:30 +01:00
|
|
|
case unsafe_call(Req, State, last_modified) of
|
2011-12-05 22:53:59 +01:00
|
|
|
no_call ->
|
|
|
|
{undefined, Req, State#state{last_modified=no_call}};
|
2012-01-23 08:11:29 +01:00
|
|
|
{LastModified, Req2, HandlerState} ->
|
|
|
|
{LastModified, Req2, State#state{handler_state=HandlerState,
|
2011-12-05 22:53:59 +01:00
|
|
|
last_modified=LastModified}}
|
|
|
|
end;
|
|
|
|
last_modified(Req, State=#state{last_modified=LastModified}) ->
|
|
|
|
{LastModified, Req, State}.
|
|
|
|
|
|
|
|
expires(Req, State=#state{expires=no_call}) ->
|
|
|
|
{undefined, Req, State};
|
|
|
|
expires(Req, State=#state{expires=undefined}) ->
|
2013-02-09 16:45:30 +01:00
|
|
|
case unsafe_call(Req, State, expires) of
|
2011-12-05 22:53:59 +01:00
|
|
|
no_call ->
|
|
|
|
{undefined, Req, State#state{expires=no_call}};
|
2012-01-23 08:11:29 +01:00
|
|
|
{Expires, Req2, HandlerState} ->
|
|
|
|
{Expires, Req2, State#state{handler_state=HandlerState,
|
2011-12-05 22:53:59 +01:00
|
|
|
expires=Expires}}
|
|
|
|
end;
|
|
|
|
expires(Req, State=#state{expires=Expires}) ->
|
|
|
|
{Expires, Req, State}.
|
|
|
|
|
|
|
|
%% REST primitives.
|
|
|
|
|
|
|
|
expect(Req, State, Callback, Expected, OnTrue, OnFalse) ->
|
|
|
|
case call(Req, State, Callback) of
|
|
|
|
no_call ->
|
|
|
|
next(Req, State, OnTrue);
|
2014-11-07 20:19:05 +02:00
|
|
|
{stop, Req2, HandlerState} ->
|
2012-01-23 08:24:15 +01:00
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2012-01-23 08:11:29 +01:00
|
|
|
{Expected, Req2, HandlerState} ->
|
|
|
|
next(Req2, State#state{handler_state=HandlerState}, OnTrue);
|
|
|
|
{_Unexpected, Req2, HandlerState} ->
|
|
|
|
next(Req2, State#state{handler_state=HandlerState}, OnFalse)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
2013-02-09 16:45:30 +01:00
|
|
|
call(Req, State=#state{handler=Handler, handler_state=HandlerState},
|
|
|
|
Callback) ->
|
|
|
|
case erlang:function_exported(Handler, Callback, 2) of
|
|
|
|
true ->
|
|
|
|
try
|
|
|
|
Handler:Callback(Req, HandlerState)
|
|
|
|
catch Class:Reason ->
|
2016-08-10 17:45:28 +02:00
|
|
|
error_terminate(Req, State, Class, Reason)
|
2013-02-09 16:45:30 +01:00
|
|
|
end;
|
|
|
|
false ->
|
|
|
|
no_call
|
|
|
|
end.
|
|
|
|
|
|
|
|
unsafe_call(Req, #state{handler=Handler, handler_state=HandlerState},
|
|
|
|
Callback) ->
|
|
|
|
case erlang:function_exported(Handler, Callback, 2) of
|
|
|
|
true -> Handler:Callback(Req, HandlerState);
|
2011-12-05 22:53:59 +01:00
|
|
|
false -> no_call
|
|
|
|
end.
|
|
|
|
|
|
|
|
next(Req, State, Next) when is_function(Next) ->
|
|
|
|
Next(Req, State);
|
|
|
|
next(Req, State, StatusCode) when is_integer(StatusCode) ->
|
|
|
|
respond(Req, State, StatusCode).
|
|
|
|
|
|
|
|
respond(Req, State, StatusCode) ->
|
2014-09-23 16:43:29 +03:00
|
|
|
terminate(cowboy_req:reply(StatusCode, Req), State).
|
2011-12-05 22:53:59 +01:00
|
|
|
|
2016-08-10 17:45:28 +02:00
|
|
|
-spec error_terminate(cowboy_req:req(), #state{}, atom(), any()) -> no_return().
|
|
|
|
error_terminate(Req, #state{handler=Handler, handler_state=HandlerState}, Class, Reason) ->
|
2014-09-30 20:12:13 +03:00
|
|
|
cowboy_handler:terminate({crash, Class, Reason}, Req, HandlerState, Handler),
|
2016-08-10 17:45:28 +02:00
|
|
|
erlang:raise(Class, Reason, erlang:get_stacktrace()).
|
2013-02-09 16:45:30 +01:00
|
|
|
|
2016-06-06 17:30:13 +02:00
|
|
|
terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
|
2014-09-30 20:12:13 +03:00
|
|
|
Result = cowboy_handler:terminate(normal, Req, HandlerState, Handler),
|
2016-06-06 17:30:13 +02:00
|
|
|
{ok, Req, Result}.
|