2012-05-23 14:53:48 +02:00
|
|
|
%% Copyright (c) 2011-2012, 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.
|
|
|
|
|
|
|
|
%% @doc Experimental REST protocol implementation.
|
|
|
|
%%
|
|
|
|
%% Based on the Webmachine Diagram from Alan Dean and Justin Sheehy, which
|
|
|
|
%% can be found in the Webmachine source tree, and on the Webmachine
|
|
|
|
%% documentation available at http://wiki.basho.com/Webmachine.html
|
|
|
|
%% at the time of writing.
|
2012-08-27 13:39:59 +02:00
|
|
|
-module(cowboy_rest).
|
2012-08-27 12:16:07 +02:00
|
|
|
|
2011-12-05 22:53:59 +01:00
|
|
|
-export([upgrade/4]).
|
|
|
|
|
|
|
|
-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(),
|
|
|
|
|
|
|
|
%% Media type.
|
|
|
|
content_types_p = [] ::
|
2012-07-24 01:07:31 +02:00
|
|
|
[{binary() | {binary(), binary(), [{binary(), binary()}]}, atom()}],
|
2011-12-05 22:53:59 +01:00
|
|
|
content_type_a :: undefined
|
2012-07-24 01:07:31 +02:00
|
|
|
| {binary() | {binary(), binary(), [{binary(), binary()}]}, atom()},
|
2011-12-05 22:53:59 +01:00
|
|
|
|
|
|
|
%% Language.
|
|
|
|
languages_p = [] :: [binary()],
|
|
|
|
language_a :: undefined | binary(),
|
|
|
|
|
|
|
|
%% Charset.
|
2012-10-26 23:54:36 -04:00
|
|
|
charsets_p = [] :: [binary()],
|
2011-12-05 22:53:59 +01:00
|
|
|
charset_a :: undefined | binary(),
|
|
|
|
|
|
|
|
%% 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(),
|
|
|
|
expires :: undefined | no_call | calendar:datetime()
|
2011-12-05 22:53:59 +01:00
|
|
|
}).
|
|
|
|
|
|
|
|
%% @doc Upgrade a HTTP request to the REST protocol.
|
|
|
|
%%
|
|
|
|
%% You do not need to call this function manually. To upgrade to the REST
|
|
|
|
%% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em>
|
|
|
|
%% in your <em>cowboy_http_handler:init/3</em> handler function.
|
2012-09-15 01:31:51 +02:00
|
|
|
-spec upgrade(pid(), module(), any(), Req)
|
|
|
|
-> {ok, Req} | close when Req::cowboy_req:req().
|
2011-12-05 22:53:59 +01:00
|
|
|
upgrade(_ListenerPid, Handler, Opts, Req) ->
|
|
|
|
try
|
2012-09-29 11:08:59 +02:00
|
|
|
Method = cowboy_req:get(method, Req),
|
2011-12-05 22:53:59 +01:00
|
|
|
case erlang:function_exported(Handler, rest_init, 2) of
|
|
|
|
true ->
|
2012-09-29 11:08:59 +02:00
|
|
|
case Handler:rest_init(Req, Opts) of
|
2011-12-05 22:53:59 +01:00
|
|
|
{ok, Req2, HandlerState} ->
|
2012-09-15 23:53:30 +02:00
|
|
|
service_available(Req2, #state{method=Method,
|
|
|
|
handler=Handler, handler_state=HandlerState})
|
2011-12-05 22:53:59 +01:00
|
|
|
end;
|
|
|
|
false ->
|
2012-09-29 11:08:59 +02:00
|
|
|
service_available(Req, #state{method=Method,
|
2012-09-15 23:53:30 +02:00
|
|
|
handler=Handler})
|
2011-12-05 22:53:59 +01:00
|
|
|
end
|
|
|
|
catch Class:Reason ->
|
2012-09-15 20:33:57 +02:00
|
|
|
PLReq = cowboy_req:to_list(Req),
|
2011-12-05 22:53:59 +01:00
|
|
|
error_logger:error_msg(
|
2012-11-28 18:30:53 +01:00
|
|
|
"** Cowboy handler ~p terminating in rest_init/2~n"
|
2011-12-05 22:53:59 +01:00
|
|
|
" for the reason ~p:~p~n** Options were ~p~n"
|
|
|
|
"** Request was ~p~n** Stacktrace: ~p~n~n",
|
2012-03-31 11:04:52 -07:00
|
|
|
[Handler, Class, Reason, Opts, PLReq, erlang:get_stacktrace()]),
|
2012-08-27 13:28:57 +02:00
|
|
|
{ok, _Req2} = cowboy_req:reply(500, Req),
|
2012-01-23 07:55:08 +01:00
|
|
|
close
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
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">>;
|
|
|
|
Method =:= <<"DELETE">>; Method =:= <<"TRACE">>;
|
|
|
|
Method =:= <<"CONNECT">>; 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);
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
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);
|
2011-12-08 18:54:20 +01:00
|
|
|
no_call ->
|
2012-09-20 06:22:51 +02:00
|
|
|
method_not_allowed(Req, State, [<<"GET">>, <<"HEAD">>]);
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
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 malformed_request/2);
|
|
|
|
false -> method_not_allowed(Req2, State2, List)
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
|
|
|
method_not_allowed(Req, State, Methods) ->
|
2012-09-16 03:51:07 +02:00
|
|
|
Req2 = cowboy_req:set_resp_header(
|
2012-11-10 17:24:25 -05:00
|
|
|
<<"allow">>, method_not_allowed_build(Methods, []), Req),
|
2011-12-05 22:53:59 +01:00
|
|
|
respond(Req2, State, 405).
|
|
|
|
|
|
|
|
method_not_allowed_build([], []) ->
|
|
|
|
<<>>;
|
|
|
|
method_not_allowed_build([], [_Ignore|Acc]) ->
|
|
|
|
lists:reverse(Acc);
|
2011-12-08 20:16:04 +01:00
|
|
|
method_not_allowed_build([Method|Tail], Acc) when is_atom(Method) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
Method2 = list_to_binary(atom_to_list(Method)),
|
2011-12-08 20:16:04 +01:00
|
|
|
method_not_allowed_build(Tail, [<<", ">>, Method2|Acc]);
|
|
|
|
method_not_allowed_build([Method|Tail], Acc) ->
|
|
|
|
method_not_allowed_build(Tail, [<<", ">>, Method|Acc]).
|
2011-12-05 22:53:59 +01:00
|
|
|
|
|
|
|
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);
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
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,
|
|
|
|
fun known_content_type/2, 501).
|
|
|
|
|
|
|
|
known_content_type(Req, State) ->
|
|
|
|
expect(Req, State, known_content_type, true,
|
2012-11-16 14:02:38 +01:00
|
|
|
fun valid_entity_length/2, 415).
|
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.
|
2012-09-20 06:22:51 +02:00
|
|
|
options(Req, State=#state{method= <<"OPTIONS">>}) ->
|
2012-01-23 08:24:15 +01:00
|
|
|
case call(Req, State, options) of
|
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
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 ->
|
|
|
|
not_acceptable(Req, State);
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
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} ->
|
2012-07-21 19:02:01 +02: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},
|
2012-11-30 16:44:57 +01:00
|
|
|
case cowboy_req:parse_header(<<"accept">>, Req2) of
|
|
|
|
{error, badarg} ->
|
|
|
|
respond(Req2, State2, 400);
|
|
|
|
{ok, undefined, Req3} ->
|
2011-12-19 09:44:24 +01:00
|
|
|
{PMT, _Fun} = HeadCTP = hd(CTP2),
|
|
|
|
languages_provided(
|
2012-09-15 22:51:37 +02:00
|
|
|
cowboy_req:set_meta(media_type, PMT, Req3),
|
2011-12-19 09:44:24 +01:00
|
|
|
State2#state{content_type_a=HeadCTP});
|
2012-11-30 16:44:57 +01:00
|
|
|
{ok, Accept, Req3} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
Accept2 = prioritize_accept(Accept),
|
|
|
|
choose_media_type(Req3, State2, Accept2)
|
|
|
|
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) ->
|
2012-07-21 19:02:01 +02:00
|
|
|
{cowboy_http:content_type(ContentType), Callback};
|
|
|
|
normalize_content_types(Provided) ->
|
2011-12-09 22:58:15 -08:00
|
|
|
Provided.
|
|
|
|
|
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).
|
|
|
|
|
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 ->
|
2012-09-15 22:51:37 +02:00
|
|
|
languages_provided(cowboy_req:set_meta(media_type, PMT, Req),
|
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);
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
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},
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, AcceptLanguage, Req3} =
|
2012-09-21 09:18:56 +02:00
|
|
|
cowboy_req:parse_header(<<"accept-language">>, Req2),
|
2011-12-05 22:53:59 +01:00
|
|
|
case AcceptLanguage of
|
|
|
|
undefined ->
|
|
|
|
set_language(Req3, State2#state{language_a=hd(LP)});
|
|
|
|
AcceptLanguage ->
|
|
|
|
AcceptLanguage2 = prioritize_languages(AcceptLanguage),
|
|
|
|
choose_language(Req3, State2, AcceptLanguage2)
|
|
|
|
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),
|
2012-09-15 22:51:37 +02:00
|
|
|
charsets_provided(cowboy_req:set_meta(language, Language, Req2), 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);
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
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},
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, AcceptCharset, Req3} =
|
2012-09-21 09:18:56 +02:00
|
|
|
cowboy_req:parse_header(<<"accept-charset">>, Req2),
|
2011-12-05 22:53:59 +01:00
|
|
|
case AcceptCharset of
|
|
|
|
undefined ->
|
2012-07-21 19:00:52 +02:00
|
|
|
set_content_type(Req3, State2#state{
|
2012-10-26 23:54:36 -04:00
|
|
|
charset_a=hd(CP)});
|
2011-12-05 22:53:59 +01:00
|
|
|
AcceptCharset ->
|
|
|
|
AcceptCharset2 = prioritize_charsets(AcceptCharset),
|
|
|
|
choose_charset(Req3, State2, AcceptCharset2)
|
|
|
|
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;
|
|
|
|
false -> [{<<"iso-8859-1">>, 1000}|AcceptCharsets2]
|
|
|
|
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);
|
2012-10-26 23:54:36 -04: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),
|
2012-09-15 22:51:37 +02:00
|
|
|
encodings_provided(cowboy_req:set_meta(charset, Charset, Req2), State).
|
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,
|
|
|
|
{Variances4, Req3, State2} = case call(Req, State, variances) of
|
|
|
|
no_call ->
|
|
|
|
{Variances3, Req, State};
|
|
|
|
{HandlerVariances, Req2, HandlerState} ->
|
|
|
|
{Variances3 ++ HandlerVariances, Req2,
|
|
|
|
State#state{handler_state=HandlerState}}
|
|
|
|
end,
|
2011-12-11 19:57:07 +01:00
|
|
|
case [[<<", ">>, V] || V <- Variances4] of
|
2011-12-05 22:53:59 +01:00
|
|
|
[] ->
|
|
|
|
resource_exists(Req3, State2);
|
2011-12-11 19:57:07 +01:00
|
|
|
[[<<", ">>, H]|Variances5] ->
|
2012-09-16 03:51:07 +02:00
|
|
|
Req4 = cowboy_req:set_resp_header(
|
2012-11-10 17:24:25 -05:00
|
|
|
<<"vary">>, [H|Variances5], Req3),
|
2011-12-05 22:53:59 +01:00
|
|
|
resource_exists(Req4, State2)
|
|
|
|
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) ->
|
2012-09-21 09:18:56 +02:00
|
|
|
case cowboy_req:parse_header(<<"if-match">>, Req) of
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, undefined, Req2} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
if_unmodified_since_exists(Req2, State);
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, '*', Req2} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
if_unmodified_since_exists(Req2, State);
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, ETagsList, Req2} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
if_match(Req2, State, ETagsList)
|
|
|
|
end.
|
|
|
|
|
|
|
|
if_match(Req, State, EtagsList) ->
|
|
|
|
{Etag, Req2, State2} = generate_etag(Req, State),
|
2012-02-28 18:35:26 +01:00
|
|
|
case lists:member(Etag, EtagsList) of
|
|
|
|
true -> if_unmodified_since_exists(Req2, State2);
|
|
|
|
%% Etag may be `undefined' which cannot be a member.
|
|
|
|
false -> precondition_failed(Req2, State2)
|
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
|
2011-12-05 22:53:59 +01:00
|
|
|
{undefined, Req2} -> is_put_to_missing_resource(Req2, State);
|
|
|
|
{_Any, Req2} -> precondition_failed(Req2, State)
|
|
|
|
end.
|
|
|
|
|
|
|
|
if_unmodified_since_exists(Req, State) ->
|
2012-09-21 09:18:56 +02:00
|
|
|
case cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, undefined, Req2} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
if_none_match_exists(Req2, State);
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, IfUnmodifiedSince, Req2} ->
|
|
|
|
if_unmodified_since(Req2, State, IfUnmodifiedSince);
|
|
|
|
{error, badarg} ->
|
|
|
|
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) ->
|
|
|
|
{LastModified, Req2, State2} = last_modified(Req, State),
|
|
|
|
case LastModified > IfUnmodifiedSince of
|
|
|
|
true -> precondition_failed(Req2, State2);
|
|
|
|
false -> if_none_match_exists(Req2, State2)
|
|
|
|
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
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, undefined, Req2} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
if_modified_since_exists(Req2, State);
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, '*', Req2} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
precondition_is_head_get(Req2, State);
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, EtagsList, Req2} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
if_none_match(Req2, State, EtagsList)
|
|
|
|
end.
|
|
|
|
|
|
|
|
if_none_match(Req, State, EtagsList) ->
|
|
|
|
{Etag, Req2, State2} = generate_etag(Req, State),
|
|
|
|
case Etag of
|
2012-02-28 18:35:26 +01:00
|
|
|
undefined ->
|
2011-12-05 22:53:59 +01:00
|
|
|
precondition_failed(Req2, State2);
|
|
|
|
Etag ->
|
|
|
|
case lists:member(Etag, EtagsList) of
|
|
|
|
true -> precondition_is_head_get(Req2, State2);
|
|
|
|
false -> if_modified_since_exists(Req2, State2)
|
|
|
|
end
|
|
|
|
end.
|
|
|
|
|
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) ->
|
2012-09-21 09:18:56 +02:00
|
|
|
case cowboy_req:parse_header(<<"if-modified-since">>, Req) of
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, undefined, Req2} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
method(Req2, State);
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, IfModifiedSince, Req2} ->
|
|
|
|
if_modified_since_now(Req2, State, IfModifiedSince);
|
|
|
|
{error, badarg} ->
|
|
|
|
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) ->
|
|
|
|
{LastModified, Req2, State2} = last_modified(Req, State),
|
|
|
|
case LastModified of
|
|
|
|
no_call ->
|
|
|
|
method(Req2, State2);
|
|
|
|
LastModified ->
|
|
|
|
case LastModified > IfModifiedSince of
|
|
|
|
true -> method(Req2, State2);
|
|
|
|
false -> not_modified(Req2, State2)
|
|
|
|
end
|
|
|
|
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),
|
2011-12-05 22:53:59 +01:00
|
|
|
{Req3, State2} = set_resp_etag(Req2, State),
|
|
|
|
{Req4, State3} = set_resp_expires(Req3, State2),
|
|
|
|
respond(Req4, State3, 304).
|
|
|
|
|
|
|
|
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});
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
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);
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
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) ->
|
|
|
|
expect(Req, State, allow_missing_post, true, fun post_is_create/2, OnFalse).
|
|
|
|
|
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= <<"POST">>}) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
post_is_create(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);
|
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">> ->
|
2012-09-15 23:53:30 +02:00
|
|
|
set_resp_body(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).
|
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% post_is_create/2 indicates whether the POST method can create new resources.
|
2011-12-05 22:53:59 +01:00
|
|
|
post_is_create(Req, State) ->
|
|
|
|
expect(Req, State, post_is_create, false, fun process_post/2, fun create_path/2).
|
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% When the POST method can create new resources, create_path/2 will be called
|
2011-12-19 10:29:44 +01:00
|
|
|
%% and is expected to return the full path to the new resource
|
|
|
|
%% (including the leading /).
|
2012-09-15 22:51:37 +02:00
|
|
|
create_path(Req, State) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
case call(Req, State, create_path) of
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2011-12-19 10:29:44 +01:00
|
|
|
{Path, Req2, HandlerState} ->
|
2012-09-16 01:13:44 +02:00
|
|
|
{HostURL, Req3} = cowboy_req:host_url(Req2),
|
2011-12-05 22:53:59 +01:00
|
|
|
State2 = State#state{handler_state=HandlerState},
|
2012-09-16 03:51:07 +02:00
|
|
|
Req4 = cowboy_req:set_resp_header(
|
2012-11-10 17:24:25 -05:00
|
|
|
<<"location">>, << HostURL/binary, Path/binary >>, Req3),
|
2012-09-16 01:13:44 +02:00
|
|
|
put_resource(cowboy_req:set_meta(put_path, Path, Req4),
|
2011-12-19 10:29:44 +01:00
|
|
|
State2, 303)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
2012-01-06 21:05:58 +01:00
|
|
|
%% process_post should return true when the POST body could be processed
|
|
|
|
%% and false when it hasn't, in which case a 500 error is sent.
|
2011-12-05 22:53:59 +01:00
|
|
|
process_post(Req, State) ->
|
|
|
|
case call(Req, State, process_post) of
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2012-01-06 21:05:58 +01:00
|
|
|
{true, Req2, HandlerState} ->
|
2011-12-08 20:21:50 +01:00
|
|
|
State2 = State#state{handler_state=HandlerState},
|
2012-02-20 08:33:54 +01:00
|
|
|
next(Req2, State2, fun is_new_resource/2);
|
2012-01-06 21:05:58 +01:00
|
|
|
{false, Req2, HandlerState} ->
|
|
|
|
State2 = State#state{handler_state=HandlerState},
|
|
|
|
respond(Req2, State2, 500)
|
2011-12-05 22:53:59 +01:00
|
|
|
end.
|
|
|
|
|
|
|
|
is_conflict(Req, State) ->
|
|
|
|
expect(Req, State, is_conflict, false, fun put_resource/2, 409).
|
|
|
|
|
2012-09-16 14:02:07 +02:00
|
|
|
put_resource(Req, State) ->
|
2012-09-29 11:08:59 +02:00
|
|
|
Path = cowboy_req:get(path, Req),
|
|
|
|
put_resource(cowboy_req:set_meta(put_path, Path, Req),
|
2012-09-15 22:51:37 +02:00
|
|
|
State, fun is_new_resource/2).
|
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
|
2011-12-19 10:29:44 +01:00
|
|
|
%% pushed to the resource in the request body. The path to the new resource
|
|
|
|
%% may be different from the request path, and is stored as request metadata.
|
|
|
|
%% It is always defined past this point. It can be retrieved as demonstrated:
|
2012-08-27 13:28:57 +02:00
|
|
|
%% {PutPath, Req2} = cowboy_req:meta(put_path, Req)
|
2011-12-05 22:53:59 +01:00
|
|
|
put_resource(Req, State, OnTrue) ->
|
|
|
|
case call(Req, State, content_types_accepted) of
|
|
|
|
no_call ->
|
|
|
|
respond(Req, State, 415);
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2012-01-23 08:11:29 +01:00
|
|
|
{CTA, Req2, HandlerState} ->
|
2012-07-21 19:02:01 +02:00
|
|
|
CTA2 = [normalize_content_types(P) || P <- CTA],
|
2012-01-23 08:11:29 +01:00
|
|
|
State2 = State#state{handler_state=HandlerState},
|
2012-09-16 03:51:07 +02:00
|
|
|
{ok, ContentType, Req3}
|
2012-09-21 09:18:56 +02:00
|
|
|
= cowboy_req:parse_header(<<"content-type">>, Req2),
|
2012-07-21 19:02:01 +02:00
|
|
|
choose_content_type(Req3, State2, OnTrue, ContentType, CTA2)
|
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.
|
2011-12-05 22:53:59 +01:00
|
|
|
choose_content_type(Req, State, _OnTrue, _ContentType, []) ->
|
|
|
|
respond(Req, State, 415);
|
2012-11-29 14:33:45 +01:00
|
|
|
choose_content_type(Req,
|
|
|
|
State=#state{handler=Handler, handler_state=HandlerState},
|
|
|
|
OnTrue, ContentType, [{Accepted, Fun}|_Tail])
|
2012-03-13 00:46:56 +01:00
|
|
|
when Accepted =:= '*' orelse Accepted =:= ContentType ->
|
2011-12-05 22:53:59 +01:00
|
|
|
case call(Req, State, Fun) of
|
2012-11-29 14:33:45 +01:00
|
|
|
no_call ->
|
|
|
|
error_logger:error_msg(
|
|
|
|
"** Cowboy handler ~p terminating; "
|
|
|
|
"function ~p was not exported~n"
|
|
|
|
"** Request was ~p~n** State was ~p~n~n",
|
|
|
|
[Handler, Fun, cowboy_req:to_list(Req), HandlerState]),
|
|
|
|
{ok, _} = cowboy_req:reply(500, Req),
|
|
|
|
close;
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
terminate(Req2, State#state{handler_state=HandlerState});
|
2012-01-09 08:06:05 +01:00
|
|
|
{true, Req2, HandlerState} ->
|
|
|
|
State2 = State#state{handler_state=HandlerState},
|
|
|
|
next(Req2, State2, OnTrue);
|
|
|
|
{false, Req2, HandlerState} ->
|
2011-12-05 22:53:59 +01:00
|
|
|
State2 = State#state{handler_state=HandlerState},
|
2012-01-09 08:06:05 +01:00
|
|
|
respond(Req2, State2, 500)
|
2011-12-05 22:53:59 +01:00
|
|
|
end;
|
|
|
|
choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) ->
|
|
|
|
choose_content_type(Req, State, OnTrue, ContentType, Tail).
|
|
|
|
|
2011-12-08 20:16:04 +01:00
|
|
|
%% Whether we created a new resource, either through PUT or POST.
|
|
|
|
%% This is easily testable because we would have set the Location
|
|
|
|
%% header by this point if we did so.
|
2011-12-05 22:53:59 +01:00
|
|
|
is_new_resource(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.
|
|
|
|
|
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.
|
2012-11-29 14:33:45 +01:00
|
|
|
set_resp_body(Req, State=#state{handler=Handler, handler_state=HandlerState,
|
|
|
|
content_type_a={_Type, Fun}}) ->
|
2011-12-05 22:53:59 +01:00
|
|
|
{Req2, State2} = set_resp_etag(Req, State),
|
|
|
|
{LastModified, Req3, State3} = last_modified(Req2, State2),
|
2012-11-29 14:33:45 +01:00
|
|
|
Req4 = case LastModified of
|
2011-12-05 22:53:59 +01:00
|
|
|
LastModified when is_atom(LastModified) ->
|
2012-11-29 14:33:45 +01:00
|
|
|
Req3;
|
2011-12-05 22:53:59 +01:00
|
|
|
LastModified ->
|
2012-12-03 15:57:27 +01:00
|
|
|
LastModifiedBin = cowboy_clock:rfc1123(LastModified),
|
2012-11-29 14:33:45 +01:00
|
|
|
cowboy_req:set_resp_header(
|
2012-12-03 15:57:27 +01:00
|
|
|
<<"last-modified">>, LastModifiedBin, Req3)
|
2011-12-05 22:53:59 +01:00
|
|
|
end,
|
|
|
|
{Req5, State4} = set_resp_expires(Req4, State3),
|
|
|
|
case call(Req5, State4, Fun) of
|
2012-11-29 14:33:45 +01:00
|
|
|
no_call ->
|
|
|
|
error_logger:error_msg(
|
|
|
|
"** Cowboy handler ~p terminating; "
|
|
|
|
"function ~p was not exported~n"
|
|
|
|
"** Request was ~p~n** State was ~p~n~n",
|
|
|
|
[Handler, Fun, cowboy_req:to_list(Req5), HandlerState]),
|
|
|
|
{ok, _} = cowboy_req:reply(500, Req5),
|
|
|
|
close;
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req6, HandlerState} ->
|
|
|
|
terminate(Req6, State4#state{handler_state=HandlerState});
|
2011-12-05 22:53:59 +01:00
|
|
|
{Body, Req6, HandlerState} ->
|
|
|
|
State5 = State4#state{handler_state=HandlerState},
|
2012-09-16 03:51:07 +02:00
|
|
|
Req7 = case Body of
|
2011-12-28 18:01:21 +01:00
|
|
|
{stream, Len, Fun1} ->
|
2012-08-27 13:28:57 +02:00
|
|
|
cowboy_req:set_resp_body_fun(Len, Fun1, Req6);
|
2011-12-28 18:01:21 +01:00
|
|
|
_Contents ->
|
2012-08-27 13:28:57 +02:00
|
|
|
cowboy_req:set_resp_body(Body, Req6)
|
2011-12-28 18:01:21 +01:00
|
|
|
end,
|
2011-12-05 22:53:59 +01:00
|
|
|
multiple_choices(Req7, State5)
|
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};
|
|
|
|
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}) ->
|
|
|
|
case call(Req, State, generate_etag) of
|
|
|
|
no_call ->
|
|
|
|
{undefined, Req, State#state{etag=no_call}};
|
2012-02-28 18:35:26 +01:00
|
|
|
%% Previously the return value from the generate_etag/2 callback was set
|
|
|
|
%% as the value of the ETag header in the response. Therefore the only
|
|
|
|
%% valid return type was `binary()'. If a handler returns a `binary()'
|
|
|
|
%% it must be mapped to the expected type or it'll always fail to
|
|
|
|
%% compare equal to any entity tags present in the request headers.
|
|
|
|
%% @todo Remove support for binary return values after 0.6.
|
|
|
|
{Etag, Req2, HandlerState} when is_binary(Etag) ->
|
|
|
|
[Etag2] = cowboy_http:entity_tag_match(Etag),
|
|
|
|
{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}) ->
|
|
|
|
case call(Req, State, last_modified) of
|
|
|
|
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}) ->
|
|
|
|
case call(Req, State, expires) of
|
|
|
|
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);
|
2012-01-23 08:24:15 +01:00
|
|
|
{halt, Req2, HandlerState} ->
|
|
|
|
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.
|
|
|
|
|
|
|
|
call(Req, #state{handler=Handler, handler_state=HandlerState}, Fun) ->
|
|
|
|
case erlang:function_exported(Handler, Fun, 2) of
|
|
|
|
true -> Handler:Fun(Req, HandlerState);
|
|
|
|
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) ->
|
2012-08-27 13:28:57 +02:00
|
|
|
{ok, Req2} = cowboy_req:reply(StatusCode, Req),
|
2011-12-05 22:53:59 +01:00
|
|
|
terminate(Req2, State).
|
|
|
|
|
|
|
|
terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
|
|
|
|
case erlang:function_exported(Handler, rest_terminate, 2) of
|
2011-12-08 18:30:13 +01:00
|
|
|
true -> ok = Handler:rest_terminate(
|
2012-09-16 13:57:27 +02:00
|
|
|
cowboy_req:lock(Req), HandlerState);
|
2011-12-05 22:53:59 +01:00
|
|
|
false -> ok
|
2011-12-08 18:30:13 +01:00
|
|
|
end,
|
|
|
|
{ok, Req}.
|