1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141 |
- %% Copyright (c) 2011-2013, Loïc Hoguin <essen@ninenines.eu>
- %%
- %% 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.
- -module(cowboy_rest).
- -behaviour(cowboy_sub_protocol).
- -export([upgrade/4]).
- -record(state, {
- env :: cowboy_middleware:env(),
- method = undefined :: binary(),
- %% Handler.
- handler :: atom(),
- handler_state :: any(),
- %% Media type.
- content_types_p = [] ::
- [{binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
- atom()}],
- content_type_a :: undefined
- | {binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
- atom()},
- %% Language.
- languages_p = [] :: [binary()],
- language_a :: undefined | binary(),
- %% Charset.
- charsets_p = [] :: [{binary(), integer()}],
- charset_a :: undefined | binary(),
- %% Cached resource calls.
- etag :: undefined | no_call | {strong | weak, binary()},
- last_modified :: undefined | no_call | calendar:datetime(),
- expires :: undefined | no_call | calendar:datetime()
- }).
- %% @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.
- -spec upgrade(Req, Env, module(), any())
- -> {ok, Req, Env} | {error, 500, Req}
- when Req::cowboy_req:req(), Env::cowboy_middleware:env().
- upgrade(Req, Env, Handler, HandlerOpts) ->
- try
- Method = cowboy_req:get(method, Req),
- case erlang:function_exported(Handler, rest_init, 2) of
- true ->
- try Handler:rest_init(Req, HandlerOpts) of
- {ok, Req2, HandlerState} ->
- service_available(Req2, #state{env=Env, method=Method,
- handler=Handler, handler_state=HandlerState})
- catch Class:Reason ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Options were ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [Handler, rest_init, 2, Class, Reason, HandlerOpts,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- {error, 500, Req}
- end;
- false ->
- service_available(Req, #state{env=Env, method=Method,
- handler=Handler})
- end
- catch
- throw:{?MODULE, error} ->
- {error, 500, Req}
- end.
- service_available(Req, State) ->
- expect(Req, State, service_available, true, fun known_methods/2, 503).
- %% known_methods/2 should return a list of binary methods.
- known_methods(Req, State=#state{method=Method}) ->
- case call(Req, State, known_methods) of
- no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>;
- Method =:= <<"POST">>; Method =:= <<"PUT">>;
- Method =:= <<"DELETE">>; Method =:= <<"TRACE">>;
- Method =:= <<"CONNECT">>; Method =:= <<"OPTIONS">>;
- Method =:= <<"PATCH">> ->
- next(Req, State, fun uri_too_long/2);
- no_call ->
- next(Req, State, 501);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {List, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState},
- 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).
- %% allowed_methods/2 should return a list of binary methods.
- allowed_methods(Req, State=#state{method=Method}) ->
- case call(Req, State, allowed_methods) of
- no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
- next(Req, State, fun malformed_request/2);
- no_call ->
- method_not_allowed(Req, State, [<<"GET">>, <<"HEAD">>]);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {List, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState},
- 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) ->
- Req2 = cowboy_req:set_resp_header(
- <<"allow">>, method_not_allowed_build(Methods, []), Req),
- respond(Req2, State, 405).
- method_not_allowed_build([], []) ->
- <<>>;
- method_not_allowed_build([], [_Ignore|Acc]) ->
- lists:reverse(Acc);
- method_not_allowed_build([Method|Tail], Acc) when is_atom(Method) ->
- Method2 = list_to_binary(atom_to_list(Method)),
- method_not_allowed_build(Tail, [<<", ">>, Method2|Acc]);
- method_not_allowed_build([Method|Tail], Acc) ->
- method_not_allowed_build(Tail, [<<", ">>, Method|Acc]).
- malformed_request(Req, State) ->
- expect(Req, State, malformed_request, false, fun is_authorized/2, 400).
- %% is_authorized/2 should return true or {false, WwwAuthenticateHeader}.
- is_authorized(Req, State) ->
- case call(Req, State, is_authorized) of
- no_call ->
- forbidden(Req, State);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {true, Req2, HandlerState} ->
- forbidden(Req2, State#state{handler_state=HandlerState});
- {{false, AuthHead}, Req2, HandlerState} ->
- Req3 = cowboy_req:set_resp_header(
- <<"www-authenticate">>, AuthHead, Req2),
- respond(Req3, State#state{handler_state=HandlerState}, 401)
- 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,
- fun valid_entity_length/2, 415).
- valid_entity_length(Req, State) ->
- expect(Req, State, valid_entity_length, true, fun options/2, 413).
- %% 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.
- options(Req, State=#state{method= <<"OPTIONS">>}) ->
- 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;
- options(Req, State) ->
- content_types_provided(Req, State).
- %% 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}
- %%
- %% 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
- %% resources a little more readable, this is a lot less efficient.
- %%
- %% An example of such return value would be:
- %% {<<"text/html">>, to_html}
- content_types_provided(Req, State) ->
- case call(Req, State, content_types_provided) of
- no_call ->
- not_acceptable(Req, State);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {[], Req2, HandlerState} ->
- not_acceptable(Req2, State#state{handler_state=HandlerState});
- {CTP, Req2, HandlerState} ->
- CTP2 = [normalize_content_types(P) || P <- CTP],
- State2 = State#state{
- handler_state=HandlerState, content_types_p=CTP2},
- case cowboy_req:parse_header(<<"accept">>, Req2) of
- {error, badarg} ->
- respond(Req2, State2, 400);
- {ok, undefined, Req3} ->
- {PMT, _Fun} = HeadCTP = hd(CTP2),
- languages_provided(
- cowboy_req:set_meta(media_type, PMT, Req3),
- State2#state{content_type_a=HeadCTP});
- {ok, Accept, Req3} ->
- Accept2 = prioritize_accept(Accept),
- choose_media_type(Req3, State2, Accept2)
- end
- end.
- normalize_content_types({ContentType, Callback})
- when is_binary(ContentType) ->
- {cowboy_http:content_type(ContentType), Callback};
- normalize_content_types(Provided) ->
- Provided.
- 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);
- match_media_type(Req, State, Accept, CTP,
- MediaType = {{<<"*">>, <<"*">>, _Params_A}, _QA, _APA}) ->
- match_media_type_params(Req, State, Accept, CTP, MediaType);
- match_media_type(Req, State, Accept,
- CTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail],
- MediaType = {{Type, SubType_A, _PA}, _QA, _APA})
- when SubType_P =:= SubType_A; SubType_A =:= <<"*">> ->
- 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).
- match_media_type_params(Req, State, _Accept,
- [Provided = {{TP, STP, '*'}, _Fun}|_Tail],
- {{_TA, _STA, Params_A}, _QA, _APA}) ->
- PMT = {TP, STP, Params_A},
- languages_provided(cowboy_req:set_meta(media_type, PMT, Req),
- State#state{content_type_a=Provided});
- match_media_type_params(Req, State, Accept,
- [Provided = {PMT = {_TP, _STP, Params_P}, _Fun}|Tail],
- MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) ->
- case lists:sort(Params_P) =:= lists:sort(Params_A) of
- true ->
- languages_provided(cowboy_req:set_meta(media_type, PMT, Req),
- State#state{content_type_a=Provided});
- false ->
- match_media_type(Req, State, Accept, Tail, MediaType)
- end.
- %% languages_provided should return a list of binary values indicating
- %% which languages are accepted by the resource.
- %%
- %% @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);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {[], Req2, HandlerState} ->
- not_acceptable(Req2, State#state{handler_state=HandlerState});
- {LP, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState, languages_p=LP},
- {ok, AcceptLanguage, Req3} =
- cowboy_req:parse_header(<<"accept-language">>, Req2),
- 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.
- set_language(Req, State=#state{language_a=Language}) ->
- Req2 = cowboy_req:set_resp_header(<<"content-language">>, Language, Req),
- charsets_provided(cowboy_req:set_meta(language, Language, Req2), State).
- %% charsets_provided should return a list of binary values indicating
- %% which charsets are accepted by the resource.
- charsets_provided(Req, State) ->
- case call(Req, State, charsets_provided) of
- no_call ->
- set_content_type(Req, State);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {[], Req2, HandlerState} ->
- not_acceptable(Req2, State#state{handler_state=HandlerState});
- {CP, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState, charsets_p=CP},
- {ok, AcceptCharset, Req3} =
- cowboy_req:parse_header(<<"accept-charset">>, Req2),
- case AcceptCharset of
- undefined ->
- set_content_type(Req3, State2#state{
- charset_a=element(1, hd(CP))});
- 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);
- match_charset(Req, State, _Accept, [{Provided, _}|_], {Provided, _}) ->
- set_content_type(Req, State#state{charset_a=Provided});
- match_charset(Req, State, Accept, [_|Tail], Charset) ->
- match_charset(Req, State, Accept, Tail, Charset).
- set_content_type(Req, State=#state{
- 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,
- Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req),
- encodings_provided(cowboy_req:set_meta(charset, Charset, Req2), State).
- set_content_type_build_params('*', []) ->
- <<>>;
- 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).
- %% 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.
- %%
- %% @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}) ->
- Variances = case CTP of
- [] -> [];
- [_] -> [];
- [_|_] -> [<<"accept">>]
- end,
- Variances2 = case LP of
- [] -> Variances;
- [_] -> Variances;
- [_|_] -> [<<"accept-language">>|Variances]
- end,
- Variances3 = case CP of
- [] -> Variances2;
- [_] -> Variances2;
- [_|_] -> [<<"accept-charset">>|Variances2]
- end,
- 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 ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Handler state was ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [State#state.handler, variances, 2,
- Class, Reason, State#state.handler_state,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- error_terminate(Req, State)
- end.
- variances(Req, State, Variances) ->
- case unsafe_call(Req, State, variances) of
- no_call ->
- {Variances, Req, State};
- {HandlerVariances, Req2, HandlerState} ->
- {Variances ++ HandlerVariances, Req2,
- State#state{handler_state=HandlerState}}
- end.
- resource_exists(Req, State) ->
- expect(Req, State, resource_exists, true,
- fun if_match_exists/2, fun if_match_must_not_exist/2).
- if_match_exists(Req, State) ->
- case cowboy_req:parse_header(<<"if-match">>, Req) of
- {ok, undefined, Req2} ->
- if_unmodified_since_exists(Req2, State);
- {ok, '*', Req2} ->
- if_unmodified_since_exists(Req2, State);
- {ok, ETagsList, Req2} ->
- if_match(Req2, State, ETagsList)
- end.
- if_match(Req, State, EtagsList) ->
- try generate_etag(Req, State) of
- {Etag, Req2, State2} ->
- 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)
- end
- catch Class:Reason ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Handler state was ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [State#state.handler, generate_etag, 2,
- Class, Reason, State#state.handler_state,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- error_terminate(Req, State)
- end.
- if_match_must_not_exist(Req, State) ->
- case cowboy_req:header(<<"if-match">>, Req) of
- {undefined, Req2} -> is_put_to_missing_resource(Req2, State);
- {_Any, Req2} -> precondition_failed(Req2, State)
- end.
- if_unmodified_since_exists(Req, State) ->
- case cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of
- {ok, undefined, Req2} ->
- if_none_match_exists(Req2, State);
- {ok, IfUnmodifiedSince, Req2} ->
- if_unmodified_since(Req2, State, IfUnmodifiedSince);
- {error, badarg} ->
- if_none_match_exists(Req, State)
- end.
- %% If LastModified is the atom 'no_call', we continue.
- if_unmodified_since(Req, State, IfUnmodifiedSince) ->
- 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 ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Handler state was ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [State#state.handler, last_modified, 2,
- Class, Reason, State#state.handler_state,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- error_terminate(Req, State)
- end.
- if_none_match_exists(Req, State) ->
- case cowboy_req:parse_header(<<"if-none-match">>, Req) of
- {ok, undefined, Req2} ->
- if_modified_since_exists(Req2, State);
- {ok, '*', Req2} ->
- precondition_is_head_get(Req2, State);
- {ok, EtagsList, Req2} ->
- if_none_match(Req2, State, EtagsList)
- end.
- if_none_match(Req, State, EtagsList) ->
- try generate_etag(Req, State) of
- {Etag, Req2, State2} ->
- case Etag of
- undefined ->
- 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
- catch Class:Reason ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Handler state was ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [State#state.handler, generate_etag, 2,
- Class, Reason, State#state.handler_state,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- error_terminate(Req, State)
- end.
- precondition_is_head_get(Req, State=#state{method=Method})
- when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
- not_modified(Req, State);
- precondition_is_head_get(Req, State) ->
- precondition_failed(Req, State).
- if_modified_since_exists(Req, State) ->
- case cowboy_req:parse_header(<<"if-modified-since">>, Req) of
- {ok, undefined, Req2} ->
- method(Req2, State);
- {ok, IfModifiedSince, Req2} ->
- if_modified_since_now(Req2, State, IfModifiedSince);
- {error, badarg} ->
- method(Req, State)
- 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) ->
- try last_modified(Req, State) of
- {no_call, Req2, State2} ->
- method(Req2, State2);
- {LastModified, Req2, State2} ->
- case LastModified > IfModifiedSince of
- true -> method(Req2, State2);
- false -> not_modified(Req2, State2)
- end
- catch Class:Reason ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Handler state was ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [State#state.handler, last_modified, 2,
- Class, Reason, State#state.handler_state,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- error_terminate(Req, State)
- end.
- not_modified(Req, State) ->
- Req2 = cowboy_req:delete_resp_header(<<"content-type">>, Req),
- 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 ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Handler state was ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [State#state.handler, expires, 2,
- Class, Reason, State#state.handler_state,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- error_terminate(Req2, State)
- end
- catch Class:Reason ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Handler state was ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [State#state.handler, generate_etag, 2,
- Class, Reason, State#state.handler_state,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- error_terminate(Req2, State)
- end.
- precondition_failed(Req, State) ->
- respond(Req, State, 412).
- is_put_to_missing_resource(Req, State=#state{method= <<"PUT">>}) ->
- moved_permanently(Req, State, fun is_conflict/2);
- is_put_to_missing_resource(Req, State) ->
- previously_existed(Req, State).
- %% moved_permanently/2 should return either false or {true, Location}
- %% with Location the full new URI of the resource.
- moved_permanently(Req, State, OnFalse) ->
- case call(Req, State, moved_permanently) of
- {{true, Location}, Req2, HandlerState} ->
- Req3 = cowboy_req:set_resp_header(
- <<"location">>, Location, Req2),
- respond(Req3, State#state{handler_state=HandlerState}, 301);
- {false, Req2, HandlerState} ->
- OnFalse(Req2, State#state{handler_state=HandlerState});
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- 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).
- %% moved_temporarily/2 should return either false or {true, Location}
- %% with Location the full new URI of the resource.
- moved_temporarily(Req, State) ->
- case call(Req, State, moved_temporarily) of
- {{true, Location}, Req2, HandlerState} ->
- Req3 = cowboy_req:set_resp_header(
- <<"location">>, Location, Req2),
- respond(Req3, State#state{handler_state=HandlerState}, 307);
- {false, Req2, HandlerState} ->
- is_post_to_missing_resource(Req2, State#state{handler_state=HandlerState}, 410);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- no_call ->
- is_post_to_missing_resource(Req, State, 410)
- end.
- is_post_to_missing_resource(Req, State=#state{method= <<"POST">>}, OnFalse) ->
- 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).
- method(Req, State=#state{method= <<"DELETE">>}) ->
- delete_resource(Req, State);
- method(Req, State=#state{method= <<"POST">>}) ->
- post_is_create(Req, State);
- method(Req, State=#state{method= <<"PUT">>}) ->
- is_conflict(Req, State);
- method(Req, State=#state{method= <<"PATCH">>}) ->
- patch_resource(Req, State);
- method(Req, State=#state{method=Method})
- when Method =:= <<"GET">>; Method =:= <<"HEAD">> ->
- set_resp_body_etag(Req, State);
- method(Req, State) ->
- multiple_choices(Req, State).
- %% delete_resource/2 should start deleting the resource and return.
- delete_resource(Req, State) ->
- expect(Req, State, delete_resource, false, 500, fun delete_completed/2).
- %% delete_completed/2 indicates whether the resource has been deleted yet.
- delete_completed(Req, State) ->
- expect(Req, State, delete_completed, true, fun has_resp_body/2, 202).
- %% post_is_create/2 indicates whether the POST method can create new resources.
- post_is_create(Req, State) ->
- expect(Req, State, post_is_create, false, fun process_post/2, fun create_path/2).
- %% When the POST method can create new resources, create_path/2 will be called
- %% and is expected to return the full path to the new resource
- %% (including the leading /).
- create_path(Req, State) ->
- case call(Req, State, create_path) of
- no_call ->
- put_resource(Req, State, fun created_path/2);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {Path, Req2, HandlerState} ->
- {HostURL, Req3} = cowboy_req:host_url(Req2),
- State2 = State#state{handler_state=HandlerState},
- Req4 = cowboy_req:set_resp_header(
- <<"location">>, << HostURL/binary, Path/binary >>, Req3),
- put_resource(cowboy_req:set_meta(put_path, Path, Req4),
- State2, 303)
- end.
- %% Called after content_types_accepted is called for POST methods
- %% when create_path did not exist. Expects the full path to
- %% be returned and MUST exist in the case that create_path
- %% does not.
- created_path(Req, State) ->
- case call(Req, State, created_path) of
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {Path, Req2, HandlerState} ->
- {HostURL, Req3} = cowboy_req:host_url(Req2),
- State2 = State#state{handler_state=HandlerState},
- Req4 = cowboy_req:set_resp_header(
- <<"location">>, << HostURL/binary, Path/binary >>, Req3),
- respond(cowboy_req:set_meta(put_path, Path, Req4),
- State2, 303)
- end.
- %% 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.
- process_post(Req, State) ->
- case call(Req, State, process_post) of
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {true, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState},
- next(Req2, State2, fun is_new_resource/2);
- {false, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState},
- respond(Req2, State2, 500)
- end.
- is_conflict(Req, State) ->
- expect(Req, State, is_conflict, false, fun put_resource/2, 409).
- put_resource(Req, State) ->
- Path = cowboy_req:get(path, Req),
- put_resource(cowboy_req:set_meta(put_path, Path, Req),
- State, fun is_new_resource/2).
- %% 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
- %% 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:
- %% {PutPath, Req2} = cowboy_req:meta(put_path, Req)
- %%
- %%content_types_accepted SHOULD return a different list
- %% for each HTTP method.
- put_resource(Req, State, OnTrue) ->
- case call(Req, State, content_types_accepted) of
- no_call ->
- respond(Req, State, 415);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {CTA, Req2, HandlerState} ->
- CTA2 = [normalize_content_types(P) || P <- CTA],
- State2 = State#state{handler_state=HandlerState},
- {ok, ContentType, Req3}
- = cowboy_req:parse_header(<<"content-type">>, Req2),
- choose_content_type(Req3, State2, OnTrue, ContentType, CTA2)
- end.
- %% 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
- %% pushed to the resource in the request body.
- %%
- %% content_types_accepted SHOULD return a different list
- %% for each HTTP method.
- patch_resource(Req, State) ->
- case call(Req, State, content_types_accepted) of
- no_call ->
- respond(Req, State, 415);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {CTM, Req2, HandlerState} ->
- State2 = State#state{handler_state=HandlerState},
- {ok, ContentType, Req3}
- = cowboy_req:parse_header(<<"content-type">>, Req2),
- choose_content_type(Req3, State2, 204, ContentType, CTM)
- end.
- %% 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.
- choose_content_type(Req, State, _OnTrue, _ContentType, []) ->
- respond(Req, State, 415);
- choose_content_type(Req, State, OnTrue, ContentType, [{Accepted, Fun}|_Tail])
- when Accepted =:= '*'; Accepted =:= ContentType ->
- process_content_type(Req, State, OnTrue, Fun);
- %% 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.
- choose_content_type(Req, State, OnTrue,
- {Type, SubType, Param},
- [{{Type, SubType, AcceptedParam}, Fun}|_Tail])
- when AcceptedParam =:= '*'; AcceptedParam =:= Param ->
- process_content_type(Req, State, OnTrue, Fun);
- choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) ->
- choose_content_type(Req, State, OnTrue, ContentType, Tail).
- process_content_type(Req,
- State=#state{handler=Handler, handler_state=HandlerState},
- OnTrue, Fun) ->
- case call(Req, State, Fun) of
- no_call ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating; "
- "function ~p/~p was not exported~n"
- "** Request was ~p~n** State was ~p~n~n",
- [Handler, Fun, 2, cowboy_req:to_list(Req), HandlerState]),
- {error, 500, Req};
- {halt, Req2, HandlerState2} ->
- terminate(Req2, State#state{handler_state=HandlerState2});
- {true, Req2, HandlerState2} ->
- State2 = State#state{handler_state=HandlerState2},
- next(Req2, State2, OnTrue);
- {false, Req2, HandlerState2} ->
- State2 = State#state{handler_state=HandlerState2},
- respond(Req2, State2, 422)
- end.
- %% 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.
- is_new_resource(Req, State) ->
- case cowboy_req:has_resp_header(<<"location">>, Req) of
- true -> respond(Req, State, 201);
- false -> has_resp_body(Req, State)
- end.
- has_resp_body(Req, State) ->
- case cowboy_req:has_resp_body(Req) of
- true -> multiple_choices(Req, State);
- false -> respond(Req, State, 204)
- end.
- %% 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 ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Handler state was ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [State#state.handler, generate_etag, 2,
- Class, Reason, State#state.handler_state,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- error_terminate(Req, State)
- 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 ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Handler state was ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [State#state.handler, last_modified, 2,
- Class, Reason, State#state.handler_state,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- error_terminate(Req, State)
- 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 ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Handler state was ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [State#state.handler, expires, 2,
- Class, Reason, State#state.handler_state,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- error_terminate(Req, State)
- end.
- %% 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.
- set_resp_body(Req, State=#state{handler=Handler, handler_state=HandlerState,
- content_type_a={_Type, Callback}}) ->
- case call(Req, State, Callback) of
- no_call ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating; "
- "function ~p/~p was not exported~n"
- "** Request was ~p~n** State was ~p~n~n",
- [Handler, Callback, 2, cowboy_req:to_list(Req), HandlerState]),
- {error, 500, Req};
- {halt, Req2, HandlerState2} ->
- terminate(Req2, State#state{handler_state=HandlerState2});
- {Body, Req2, HandlerState2} ->
- State2 = State#state{handler_state=HandlerState2},
- Req3 = case Body of
- {stream, StreamFun} ->
- cowboy_req:set_resp_body_fun(StreamFun, Req2);
- {stream, Len, StreamFun} ->
- cowboy_req:set_resp_body_fun(Len, StreamFun, Req2);
- _Contents ->
- cowboy_req:set_resp_body(Body, Req2)
- end,
- multiple_choices(Req3, State2)
- end.
- 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 ->
- Req3 = cowboy_req:set_resp_header(
- <<"etag">>, encode_etag(Etag), Req2),
- {Req3, State2}
- end.
- -spec encode_etag({strong | weak, binary()}) -> iolist().
- encode_etag({strong, Etag}) -> [$",Etag,$"];
- encode_etag({weak, Etag}) -> ["W/\"",Etag,$"].
- set_resp_expires(Req, State) ->
- {Expires, Req2, State2} = expires(Req, State),
- case Expires of
- Expires when is_atom(Expires) ->
- {Req2, State2};
- Expires ->
- ExpiresBin = cowboy_clock:rfc1123(Expires),
- Req3 = cowboy_req:set_resp_header(
- <<"expires">>, ExpiresBin, Req2),
- {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 unsafe_call(Req, State, generate_etag) of
- no_call ->
- {undefined, Req, State#state{etag=no_call}};
- {Etag, Req2, HandlerState} when is_binary(Etag) ->
- [Etag2] = cowboy_http:entity_tag_match(Etag),
- {Etag2, Req2, State#state{handler_state=HandlerState, etag=Etag2}};
- {Etag, Req2, HandlerState} ->
- {Etag, Req2, State#state{handler_state=HandlerState, etag=Etag}}
- 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 unsafe_call(Req, State, last_modified) of
- no_call ->
- {undefined, Req, State#state{last_modified=no_call}};
- {LastModified, Req2, HandlerState} ->
- {LastModified, Req2, State#state{handler_state=HandlerState,
- 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 unsafe_call(Req, State, expires) of
- no_call ->
- {undefined, Req, State#state{expires=no_call}};
- {Expires, Req2, HandlerState} ->
- {Expires, Req2, State#state{handler_state=HandlerState,
- 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);
- {halt, Req2, HandlerState} ->
- terminate(Req2, State#state{handler_state=HandlerState});
- {Expected, Req2, HandlerState} ->
- next(Req2, State#state{handler_state=HandlerState}, OnTrue);
- {_Unexpected, Req2, HandlerState} ->
- next(Req2, State#state{handler_state=HandlerState}, OnFalse)
- end.
- 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 ->
- error_logger:error_msg(
- "** Cowboy handler ~p terminating in ~p/~p~n"
- " for the reason ~p:~p~n** Handler state was ~p~n"
- "** Request was ~p~n** Stacktrace: ~p~n~n",
- [Handler, Callback, 2, Class, Reason, HandlerState,
- cowboy_req:to_list(Req), erlang:get_stacktrace()]),
- error_terminate(Req, State)
- 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);
- 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) ->
- {ok, Req2} = cowboy_req:reply(StatusCode, Req),
- terminate(Req2, State).
- terminate(Req, State=#state{env=Env}) ->
- rest_terminate(Req, State),
- {ok, Req, [{result, ok}|Env]}.
- -spec error_terminate(cowboy_req:req(), #state{}) -> no_return().
- error_terminate(Req, State) ->
- rest_terminate(Req, State),
- erlang:throw({?MODULE, error}).
- rest_terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
- case erlang:function_exported(Handler, rest_terminate, 2) of
- true -> ok = Handler:rest_terminate(
- cowboy_req:lock(Req), HandlerState);
- false -> ok
- end.
|