cowboy_rest.erl 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141
  1. %% Copyright (c) 2011-2013, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. %% @doc Experimental REST protocol implementation.
  15. %%
  16. %% Based on the Webmachine Diagram from Alan Dean and Justin Sheehy, which
  17. %% can be found in the Webmachine source tree, and on the Webmachine
  18. %% documentation available at http://wiki.basho.com/Webmachine.html
  19. %% at the time of writing.
  20. -module(cowboy_rest).
  21. -behaviour(cowboy_sub_protocol).
  22. -export([upgrade/4]).
  23. -record(state, {
  24. env :: cowboy_middleware:env(),
  25. method = undefined :: binary(),
  26. %% Handler.
  27. handler :: atom(),
  28. handler_state :: any(),
  29. %% Media type.
  30. content_types_p = [] ::
  31. [{binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
  32. atom()}],
  33. content_type_a :: undefined
  34. | {binary() | {binary(), binary(), [{binary(), binary()}] | '*'},
  35. atom()},
  36. %% Language.
  37. languages_p = [] :: [binary()],
  38. language_a :: undefined | binary(),
  39. %% Charset.
  40. charsets_p = [] :: [{binary(), integer()}],
  41. charset_a :: undefined | binary(),
  42. %% Cached resource calls.
  43. etag :: undefined | no_call | {strong | weak, binary()},
  44. last_modified :: undefined | no_call | calendar:datetime(),
  45. expires :: undefined | no_call | calendar:datetime()
  46. }).
  47. %% @doc Upgrade a HTTP request to the REST protocol.
  48. %%
  49. %% You do not need to call this function manually. To upgrade to the REST
  50. %% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em>
  51. %% in your <em>cowboy_http_handler:init/3</em> handler function.
  52. -spec upgrade(Req, Env, module(), any())
  53. -> {ok, Req, Env} | {error, 500, Req}
  54. when Req::cowboy_req:req(), Env::cowboy_middleware:env().
  55. upgrade(Req, Env, Handler, HandlerOpts) ->
  56. try
  57. Method = cowboy_req:get(method, Req),
  58. case erlang:function_exported(Handler, rest_init, 2) of
  59. true ->
  60. try Handler:rest_init(Req, HandlerOpts) of
  61. {ok, Req2, HandlerState} ->
  62. service_available(Req2, #state{env=Env, method=Method,
  63. handler=Handler, handler_state=HandlerState})
  64. catch Class:Reason ->
  65. error_logger:error_msg(
  66. "** Cowboy handler ~p terminating in ~p/~p~n"
  67. " for the reason ~p:~p~n** Options were ~p~n"
  68. "** Request was ~p~n** Stacktrace: ~p~n~n",
  69. [Handler, rest_init, 2, Class, Reason, HandlerOpts,
  70. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  71. {error, 500, Req}
  72. end;
  73. false ->
  74. service_available(Req, #state{env=Env, method=Method,
  75. handler=Handler})
  76. end
  77. catch
  78. throw:{?MODULE, error} ->
  79. {error, 500, Req}
  80. end.
  81. service_available(Req, State) ->
  82. expect(Req, State, service_available, true, fun known_methods/2, 503).
  83. %% known_methods/2 should return a list of binary methods.
  84. known_methods(Req, State=#state{method=Method}) ->
  85. case call(Req, State, known_methods) of
  86. no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>;
  87. Method =:= <<"POST">>; Method =:= <<"PUT">>;
  88. Method =:= <<"DELETE">>; Method =:= <<"TRACE">>;
  89. Method =:= <<"CONNECT">>; Method =:= <<"OPTIONS">>;
  90. Method =:= <<"PATCH">> ->
  91. next(Req, State, fun uri_too_long/2);
  92. no_call ->
  93. next(Req, State, 501);
  94. {halt, Req2, HandlerState} ->
  95. terminate(Req2, State#state{handler_state=HandlerState});
  96. {List, Req2, HandlerState} ->
  97. State2 = State#state{handler_state=HandlerState},
  98. case lists:member(Method, List) of
  99. true -> next(Req2, State2, fun uri_too_long/2);
  100. false -> next(Req2, State2, 501)
  101. end
  102. end.
  103. uri_too_long(Req, State) ->
  104. expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414).
  105. %% allowed_methods/2 should return a list of binary methods.
  106. allowed_methods(Req, State=#state{method=Method}) ->
  107. case call(Req, State, allowed_methods) of
  108. no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
  109. next(Req, State, fun malformed_request/2);
  110. no_call ->
  111. method_not_allowed(Req, State, [<<"GET">>, <<"HEAD">>]);
  112. {halt, Req2, HandlerState} ->
  113. terminate(Req2, State#state{handler_state=HandlerState});
  114. {List, Req2, HandlerState} ->
  115. State2 = State#state{handler_state=HandlerState},
  116. case lists:member(Method, List) of
  117. true -> next(Req2, State2, fun malformed_request/2);
  118. false -> method_not_allowed(Req2, State2, List)
  119. end
  120. end.
  121. method_not_allowed(Req, State, Methods) ->
  122. Req2 = cowboy_req:set_resp_header(
  123. <<"allow">>, method_not_allowed_build(Methods, []), Req),
  124. respond(Req2, State, 405).
  125. method_not_allowed_build([], []) ->
  126. <<>>;
  127. method_not_allowed_build([], [_Ignore|Acc]) ->
  128. lists:reverse(Acc);
  129. method_not_allowed_build([Method|Tail], Acc) when is_atom(Method) ->
  130. Method2 = list_to_binary(atom_to_list(Method)),
  131. method_not_allowed_build(Tail, [<<", ">>, Method2|Acc]);
  132. method_not_allowed_build([Method|Tail], Acc) ->
  133. method_not_allowed_build(Tail, [<<", ">>, Method|Acc]).
  134. malformed_request(Req, State) ->
  135. expect(Req, State, malformed_request, false, fun is_authorized/2, 400).
  136. %% is_authorized/2 should return true or {false, WwwAuthenticateHeader}.
  137. is_authorized(Req, State) ->
  138. case call(Req, State, is_authorized) of
  139. no_call ->
  140. forbidden(Req, State);
  141. {halt, Req2, HandlerState} ->
  142. terminate(Req2, State#state{handler_state=HandlerState});
  143. {true, Req2, HandlerState} ->
  144. forbidden(Req2, State#state{handler_state=HandlerState});
  145. {{false, AuthHead}, Req2, HandlerState} ->
  146. Req3 = cowboy_req:set_resp_header(
  147. <<"www-authenticate">>, AuthHead, Req2),
  148. respond(Req3, State#state{handler_state=HandlerState}, 401)
  149. end.
  150. forbidden(Req, State) ->
  151. expect(Req, State, forbidden, false, fun valid_content_headers/2, 403).
  152. valid_content_headers(Req, State) ->
  153. expect(Req, State, valid_content_headers, true,
  154. fun known_content_type/2, 501).
  155. known_content_type(Req, State) ->
  156. expect(Req, State, known_content_type, true,
  157. fun valid_entity_length/2, 415).
  158. valid_entity_length(Req, State) ->
  159. expect(Req, State, valid_entity_length, true, fun options/2, 413).
  160. %% If you need to add additional headers to the response at this point,
  161. %% you should do it directly in the options/2 call using set_resp_headers.
  162. options(Req, State=#state{method= <<"OPTIONS">>}) ->
  163. case call(Req, State, options) of
  164. {halt, Req2, HandlerState} ->
  165. terminate(Req2, State#state{handler_state=HandlerState});
  166. {ok, Req2, HandlerState} ->
  167. respond(Req2, State#state{handler_state=HandlerState}, 200)
  168. end;
  169. options(Req, State) ->
  170. content_types_provided(Req, State).
  171. %% content_types_provided/2 should return a list of content types and their
  172. %% associated callback function as a tuple: {{Type, SubType, Params}, Fun}.
  173. %% Type and SubType are the media type as binary. Params is a list of
  174. %% Key/Value tuple, with Key and Value a binary. Fun is the name of the
  175. %% callback that will be used to return the content of the response. It is
  176. %% given as an atom.
  177. %%
  178. %% An example of such return value would be:
  179. %% {{<<"text">>, <<"html">>, []}, to_html}
  180. %%
  181. %% Note that it is also possible to return a binary content type that will
  182. %% then be parsed by Cowboy. However note that while this may make your
  183. %% resources a little more readable, this is a lot less efficient.
  184. %%
  185. %% An example of such return value would be:
  186. %% {<<"text/html">>, to_html}
  187. content_types_provided(Req, State) ->
  188. case call(Req, State, content_types_provided) of
  189. no_call ->
  190. not_acceptable(Req, State);
  191. {halt, Req2, HandlerState} ->
  192. terminate(Req2, State#state{handler_state=HandlerState});
  193. {[], Req2, HandlerState} ->
  194. not_acceptable(Req2, State#state{handler_state=HandlerState});
  195. {CTP, Req2, HandlerState} ->
  196. CTP2 = [normalize_content_types(P) || P <- CTP],
  197. State2 = State#state{
  198. handler_state=HandlerState, content_types_p=CTP2},
  199. case cowboy_req:parse_header(<<"accept">>, Req2) of
  200. {error, badarg} ->
  201. respond(Req2, State2, 400);
  202. {ok, undefined, Req3} ->
  203. {PMT, _Fun} = HeadCTP = hd(CTP2),
  204. languages_provided(
  205. cowboy_req:set_meta(media_type, PMT, Req3),
  206. State2#state{content_type_a=HeadCTP});
  207. {ok, Accept, Req3} ->
  208. Accept2 = prioritize_accept(Accept),
  209. choose_media_type(Req3, State2, Accept2)
  210. end
  211. end.
  212. normalize_content_types({ContentType, Callback})
  213. when is_binary(ContentType) ->
  214. {cowboy_http:content_type(ContentType), Callback};
  215. normalize_content_types(Provided) ->
  216. Provided.
  217. prioritize_accept(Accept) ->
  218. lists:sort(
  219. fun ({MediaTypeA, Quality, _AcceptParamsA},
  220. {MediaTypeB, Quality, _AcceptParamsB}) ->
  221. %% Same quality, check precedence in more details.
  222. prioritize_mediatype(MediaTypeA, MediaTypeB);
  223. ({_MediaTypeA, QualityA, _AcceptParamsA},
  224. {_MediaTypeB, QualityB, _AcceptParamsB}) ->
  225. %% Just compare the quality.
  226. QualityA > QualityB
  227. end, Accept).
  228. %% Media ranges can be overridden by more specific media ranges or
  229. %% specific media types. If more than one media range applies to a given
  230. %% type, the most specific reference has precedence.
  231. %%
  232. %% We always choose B over A when we can't decide between the two.
  233. prioritize_mediatype({TypeA, SubTypeA, ParamsA}, {TypeB, SubTypeB, ParamsB}) ->
  234. case TypeB of
  235. TypeA ->
  236. case SubTypeB of
  237. SubTypeA -> length(ParamsA) > length(ParamsB);
  238. <<"*">> -> true;
  239. _Any -> false
  240. end;
  241. <<"*">> -> true;
  242. _Any -> false
  243. end.
  244. %% Ignoring the rare AcceptParams. Not sure what should be done about them.
  245. choose_media_type(Req, State, []) ->
  246. not_acceptable(Req, State);
  247. choose_media_type(Req, State=#state{content_types_p=CTP},
  248. [MediaType|Tail]) ->
  249. match_media_type(Req, State, Tail, CTP, MediaType).
  250. match_media_type(Req, State, Accept, [], _MediaType) ->
  251. choose_media_type(Req, State, Accept);
  252. match_media_type(Req, State, Accept, CTP,
  253. MediaType = {{<<"*">>, <<"*">>, _Params_A}, _QA, _APA}) ->
  254. match_media_type_params(Req, State, Accept, CTP, MediaType);
  255. match_media_type(Req, State, Accept,
  256. CTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail],
  257. MediaType = {{Type, SubType_A, _PA}, _QA, _APA})
  258. when SubType_P =:= SubType_A; SubType_A =:= <<"*">> ->
  259. match_media_type_params(Req, State, Accept, CTP, MediaType);
  260. match_media_type(Req, State, Accept, [_Any|Tail], MediaType) ->
  261. match_media_type(Req, State, Accept, Tail, MediaType).
  262. match_media_type_params(Req, State, _Accept,
  263. [Provided = {{TP, STP, '*'}, _Fun}|_Tail],
  264. {{_TA, _STA, Params_A}, _QA, _APA}) ->
  265. PMT = {TP, STP, Params_A},
  266. languages_provided(cowboy_req:set_meta(media_type, PMT, Req),
  267. State#state{content_type_a=Provided});
  268. match_media_type_params(Req, State, Accept,
  269. [Provided = {PMT = {_TP, _STP, Params_P}, _Fun}|Tail],
  270. MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) ->
  271. case lists:sort(Params_P) =:= lists:sort(Params_A) of
  272. true ->
  273. languages_provided(cowboy_req:set_meta(media_type, PMT, Req),
  274. State#state{content_type_a=Provided});
  275. false ->
  276. match_media_type(Req, State, Accept, Tail, MediaType)
  277. end.
  278. %% languages_provided should return a list of binary values indicating
  279. %% which languages are accepted by the resource.
  280. %%
  281. %% @todo I suppose we should also ask the resource if it wants to
  282. %% set a language itself or if it wants it to be automatically chosen.
  283. languages_provided(Req, State) ->
  284. case call(Req, State, languages_provided) of
  285. no_call ->
  286. charsets_provided(Req, State);
  287. {halt, Req2, HandlerState} ->
  288. terminate(Req2, State#state{handler_state=HandlerState});
  289. {[], Req2, HandlerState} ->
  290. not_acceptable(Req2, State#state{handler_state=HandlerState});
  291. {LP, Req2, HandlerState} ->
  292. State2 = State#state{handler_state=HandlerState, languages_p=LP},
  293. {ok, AcceptLanguage, Req3} =
  294. cowboy_req:parse_header(<<"accept-language">>, Req2),
  295. case AcceptLanguage of
  296. undefined ->
  297. set_language(Req3, State2#state{language_a=hd(LP)});
  298. AcceptLanguage ->
  299. AcceptLanguage2 = prioritize_languages(AcceptLanguage),
  300. choose_language(Req3, State2, AcceptLanguage2)
  301. end
  302. end.
  303. %% A language-range matches a language-tag if it exactly equals the tag,
  304. %% or if it exactly equals a prefix of the tag such that the first tag
  305. %% character following the prefix is "-". The special range "*", if
  306. %% present in the Accept-Language field, matches every tag not matched
  307. %% by any other range present in the Accept-Language field.
  308. %%
  309. %% @todo The last sentence probably means we should always put '*'
  310. %% at the end of the list.
  311. prioritize_languages(AcceptLanguages) ->
  312. lists:sort(
  313. fun ({_TagA, QualityA}, {_TagB, QualityB}) ->
  314. QualityA > QualityB
  315. end, AcceptLanguages).
  316. choose_language(Req, State, []) ->
  317. not_acceptable(Req, State);
  318. choose_language(Req, State=#state{languages_p=LP}, [Language|Tail]) ->
  319. match_language(Req, State, Tail, LP, Language).
  320. match_language(Req, State, Accept, [], _Language) ->
  321. choose_language(Req, State, Accept);
  322. match_language(Req, State, _Accept, [Provided|_Tail], {'*', _Quality}) ->
  323. set_language(Req, State#state{language_a=Provided});
  324. match_language(Req, State, _Accept, [Provided|_Tail], {Provided, _Quality}) ->
  325. set_language(Req, State#state{language_a=Provided});
  326. match_language(Req, State, Accept, [Provided|Tail],
  327. Language = {Tag, _Quality}) ->
  328. Length = byte_size(Tag),
  329. case Provided of
  330. << Tag:Length/binary, $-, _Any/bits >> ->
  331. set_language(Req, State#state{language_a=Provided});
  332. _Any ->
  333. match_language(Req, State, Accept, Tail, Language)
  334. end.
  335. set_language(Req, State=#state{language_a=Language}) ->
  336. Req2 = cowboy_req:set_resp_header(<<"content-language">>, Language, Req),
  337. charsets_provided(cowboy_req:set_meta(language, Language, Req2), State).
  338. %% charsets_provided should return a list of binary values indicating
  339. %% which charsets are accepted by the resource.
  340. charsets_provided(Req, State) ->
  341. case call(Req, State, charsets_provided) of
  342. no_call ->
  343. set_content_type(Req, State);
  344. {halt, Req2, HandlerState} ->
  345. terminate(Req2, State#state{handler_state=HandlerState});
  346. {[], Req2, HandlerState} ->
  347. not_acceptable(Req2, State#state{handler_state=HandlerState});
  348. {CP, Req2, HandlerState} ->
  349. State2 = State#state{handler_state=HandlerState, charsets_p=CP},
  350. {ok, AcceptCharset, Req3} =
  351. cowboy_req:parse_header(<<"accept-charset">>, Req2),
  352. case AcceptCharset of
  353. undefined ->
  354. set_content_type(Req3, State2#state{
  355. charset_a=element(1, hd(CP))});
  356. AcceptCharset ->
  357. AcceptCharset2 = prioritize_charsets(AcceptCharset),
  358. choose_charset(Req3, State2, AcceptCharset2)
  359. end
  360. end.
  361. %% The special value "*", if present in the Accept-Charset field,
  362. %% matches every character set (including ISO-8859-1) which is not
  363. %% mentioned elsewhere in the Accept-Charset field. If no "*" is present
  364. %% in an Accept-Charset field, then all character sets not explicitly
  365. %% mentioned get a quality value of 0, except for ISO-8859-1, which gets
  366. %% a quality value of 1 if not explicitly mentioned.
  367. prioritize_charsets(AcceptCharsets) ->
  368. AcceptCharsets2 = lists:sort(
  369. fun ({_CharsetA, QualityA}, {_CharsetB, QualityB}) ->
  370. QualityA > QualityB
  371. end, AcceptCharsets),
  372. case lists:keymember(<<"*">>, 1, AcceptCharsets2) of
  373. true -> AcceptCharsets2;
  374. false -> [{<<"iso-8859-1">>, 1000}|AcceptCharsets2]
  375. end.
  376. choose_charset(Req, State, []) ->
  377. not_acceptable(Req, State);
  378. choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) ->
  379. match_charset(Req, State, Tail, CP, Charset).
  380. match_charset(Req, State, Accept, [], _Charset) ->
  381. choose_charset(Req, State, Accept);
  382. match_charset(Req, State, _Accept, [{Provided, _}|_], {Provided, _}) ->
  383. set_content_type(Req, State#state{charset_a=Provided});
  384. match_charset(Req, State, Accept, [_|Tail], Charset) ->
  385. match_charset(Req, State, Accept, Tail, Charset).
  386. set_content_type(Req, State=#state{
  387. content_type_a={{Type, SubType, Params}, _Fun},
  388. charset_a=Charset}) ->
  389. ParamsBin = set_content_type_build_params(Params, []),
  390. ContentType = [Type, <<"/">>, SubType, ParamsBin],
  391. ContentType2 = case Charset of
  392. undefined -> ContentType;
  393. Charset -> [ContentType, <<"; charset=">>, Charset]
  394. end,
  395. Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req),
  396. encodings_provided(cowboy_req:set_meta(charset, Charset, Req2), State).
  397. set_content_type_build_params('*', []) ->
  398. <<>>;
  399. set_content_type_build_params([], []) ->
  400. <<>>;
  401. set_content_type_build_params([], Acc) ->
  402. lists:reverse(Acc);
  403. set_content_type_build_params([{Attr, Value}|Tail], Acc) ->
  404. set_content_type_build_params(Tail, [[Attr, <<"=">>, Value], <<";">>|Acc]).
  405. %% @todo Match for identity as we provide nothing else for now.
  406. %% @todo Don't forget to set the Content-Encoding header when we reply a body
  407. %% and the found encoding is something other than identity.
  408. encodings_provided(Req, State) ->
  409. variances(Req, State).
  410. not_acceptable(Req, State) ->
  411. respond(Req, State, 406).
  412. %% variances/2 should return a list of headers that will be added
  413. %% to the Vary response header. The Accept, Accept-Language,
  414. %% Accept-Charset and Accept-Encoding headers do not need to be
  415. %% specified.
  416. %%
  417. %% @todo Do Accept-Encoding too when we handle it.
  418. %% @todo Does the order matter?
  419. variances(Req, State=#state{content_types_p=CTP,
  420. languages_p=LP, charsets_p=CP}) ->
  421. Variances = case CTP of
  422. [] -> [];
  423. [_] -> [];
  424. [_|_] -> [<<"accept">>]
  425. end,
  426. Variances2 = case LP of
  427. [] -> Variances;
  428. [_] -> Variances;
  429. [_|_] -> [<<"accept-language">>|Variances]
  430. end,
  431. Variances3 = case CP of
  432. [] -> Variances2;
  433. [_] -> Variances2;
  434. [_|_] -> [<<"accept-charset">>|Variances2]
  435. end,
  436. try variances(Req, State, Variances3) of
  437. {Variances4, Req2, State2} ->
  438. case [[<<", ">>, V] || V <- Variances4] of
  439. [] ->
  440. resource_exists(Req2, State2);
  441. [[<<", ">>, H]|Variances5] ->
  442. Req3 = cowboy_req:set_resp_header(
  443. <<"vary">>, [H|Variances5], Req2),
  444. resource_exists(Req3, State2)
  445. end
  446. catch Class:Reason ->
  447. error_logger:error_msg(
  448. "** Cowboy handler ~p terminating in ~p/~p~n"
  449. " for the reason ~p:~p~n** Handler state was ~p~n"
  450. "** Request was ~p~n** Stacktrace: ~p~n~n",
  451. [State#state.handler, variances, 2,
  452. Class, Reason, State#state.handler_state,
  453. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  454. error_terminate(Req, State)
  455. end.
  456. variances(Req, State, Variances) ->
  457. case unsafe_call(Req, State, variances) of
  458. no_call ->
  459. {Variances, Req, State};
  460. {HandlerVariances, Req2, HandlerState} ->
  461. {Variances ++ HandlerVariances, Req2,
  462. State#state{handler_state=HandlerState}}
  463. end.
  464. resource_exists(Req, State) ->
  465. expect(Req, State, resource_exists, true,
  466. fun if_match_exists/2, fun if_match_must_not_exist/2).
  467. if_match_exists(Req, State) ->
  468. case cowboy_req:parse_header(<<"if-match">>, Req) of
  469. {ok, undefined, Req2} ->
  470. if_unmodified_since_exists(Req2, State);
  471. {ok, '*', Req2} ->
  472. if_unmodified_since_exists(Req2, State);
  473. {ok, ETagsList, Req2} ->
  474. if_match(Req2, State, ETagsList)
  475. end.
  476. if_match(Req, State, EtagsList) ->
  477. try generate_etag(Req, State) of
  478. {Etag, Req2, State2} ->
  479. case lists:member(Etag, EtagsList) of
  480. true -> if_unmodified_since_exists(Req2, State2);
  481. %% Etag may be `undefined' which cannot be a member.
  482. false -> precondition_failed(Req2, State2)
  483. end
  484. catch Class:Reason ->
  485. error_logger:error_msg(
  486. "** Cowboy handler ~p terminating in ~p/~p~n"
  487. " for the reason ~p:~p~n** Handler state was ~p~n"
  488. "** Request was ~p~n** Stacktrace: ~p~n~n",
  489. [State#state.handler, generate_etag, 2,
  490. Class, Reason, State#state.handler_state,
  491. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  492. error_terminate(Req, State)
  493. end.
  494. if_match_must_not_exist(Req, State) ->
  495. case cowboy_req:header(<<"if-match">>, Req) of
  496. {undefined, Req2} -> is_put_to_missing_resource(Req2, State);
  497. {_Any, Req2} -> precondition_failed(Req2, State)
  498. end.
  499. if_unmodified_since_exists(Req, State) ->
  500. case cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of
  501. {ok, undefined, Req2} ->
  502. if_none_match_exists(Req2, State);
  503. {ok, IfUnmodifiedSince, Req2} ->
  504. if_unmodified_since(Req2, State, IfUnmodifiedSince);
  505. {error, badarg} ->
  506. if_none_match_exists(Req, State)
  507. end.
  508. %% If LastModified is the atom 'no_call', we continue.
  509. if_unmodified_since(Req, State, IfUnmodifiedSince) ->
  510. try last_modified(Req, State) of
  511. {LastModified, Req2, State2} ->
  512. case LastModified > IfUnmodifiedSince of
  513. true -> precondition_failed(Req2, State2);
  514. false -> if_none_match_exists(Req2, State2)
  515. end
  516. catch Class:Reason ->
  517. error_logger:error_msg(
  518. "** Cowboy handler ~p terminating in ~p/~p~n"
  519. " for the reason ~p:~p~n** Handler state was ~p~n"
  520. "** Request was ~p~n** Stacktrace: ~p~n~n",
  521. [State#state.handler, last_modified, 2,
  522. Class, Reason, State#state.handler_state,
  523. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  524. error_terminate(Req, State)
  525. end.
  526. if_none_match_exists(Req, State) ->
  527. case cowboy_req:parse_header(<<"if-none-match">>, Req) of
  528. {ok, undefined, Req2} ->
  529. if_modified_since_exists(Req2, State);
  530. {ok, '*', Req2} ->
  531. precondition_is_head_get(Req2, State);
  532. {ok, EtagsList, Req2} ->
  533. if_none_match(Req2, State, EtagsList)
  534. end.
  535. if_none_match(Req, State, EtagsList) ->
  536. try generate_etag(Req, State) of
  537. {Etag, Req2, State2} ->
  538. case Etag of
  539. undefined ->
  540. precondition_failed(Req2, State2);
  541. Etag ->
  542. case lists:member(Etag, EtagsList) of
  543. true -> precondition_is_head_get(Req2, State2);
  544. false -> if_modified_since_exists(Req2, State2)
  545. end
  546. end
  547. catch Class:Reason ->
  548. error_logger:error_msg(
  549. "** Cowboy handler ~p terminating in ~p/~p~n"
  550. " for the reason ~p:~p~n** Handler state was ~p~n"
  551. "** Request was ~p~n** Stacktrace: ~p~n~n",
  552. [State#state.handler, generate_etag, 2,
  553. Class, Reason, State#state.handler_state,
  554. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  555. error_terminate(Req, State)
  556. end.
  557. precondition_is_head_get(Req, State=#state{method=Method})
  558. when Method =:= <<"HEAD">>; Method =:= <<"GET">> ->
  559. not_modified(Req, State);
  560. precondition_is_head_get(Req, State) ->
  561. precondition_failed(Req, State).
  562. if_modified_since_exists(Req, State) ->
  563. case cowboy_req:parse_header(<<"if-modified-since">>, Req) of
  564. {ok, undefined, Req2} ->
  565. method(Req2, State);
  566. {ok, IfModifiedSince, Req2} ->
  567. if_modified_since_now(Req2, State, IfModifiedSince);
  568. {error, badarg} ->
  569. method(Req, State)
  570. end.
  571. if_modified_since_now(Req, State, IfModifiedSince) ->
  572. case IfModifiedSince > erlang:universaltime() of
  573. true -> method(Req, State);
  574. false -> if_modified_since(Req, State, IfModifiedSince)
  575. end.
  576. if_modified_since(Req, State, IfModifiedSince) ->
  577. try last_modified(Req, State) of
  578. {no_call, Req2, State2} ->
  579. method(Req2, State2);
  580. {LastModified, Req2, State2} ->
  581. case LastModified > IfModifiedSince of
  582. true -> method(Req2, State2);
  583. false -> not_modified(Req2, State2)
  584. end
  585. catch Class:Reason ->
  586. error_logger:error_msg(
  587. "** Cowboy handler ~p terminating in ~p/~p~n"
  588. " for the reason ~p:~p~n** Handler state was ~p~n"
  589. "** Request was ~p~n** Stacktrace: ~p~n~n",
  590. [State#state.handler, last_modified, 2,
  591. Class, Reason, State#state.handler_state,
  592. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  593. error_terminate(Req, State)
  594. end.
  595. not_modified(Req, State) ->
  596. Req2 = cowboy_req:delete_resp_header(<<"content-type">>, Req),
  597. try set_resp_etag(Req2, State) of
  598. {Req3, State2} ->
  599. try set_resp_expires(Req3, State2) of
  600. {Req4, State3} ->
  601. respond(Req4, State3, 304)
  602. catch Class:Reason ->
  603. error_logger:error_msg(
  604. "** Cowboy handler ~p terminating in ~p/~p~n"
  605. " for the reason ~p:~p~n** Handler state was ~p~n"
  606. "** Request was ~p~n** Stacktrace: ~p~n~n",
  607. [State#state.handler, expires, 2,
  608. Class, Reason, State#state.handler_state,
  609. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  610. error_terminate(Req2, State)
  611. end
  612. catch Class:Reason ->
  613. error_logger:error_msg(
  614. "** Cowboy handler ~p terminating in ~p/~p~n"
  615. " for the reason ~p:~p~n** Handler state was ~p~n"
  616. "** Request was ~p~n** Stacktrace: ~p~n~n",
  617. [State#state.handler, generate_etag, 2,
  618. Class, Reason, State#state.handler_state,
  619. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  620. error_terminate(Req2, State)
  621. end.
  622. precondition_failed(Req, State) ->
  623. respond(Req, State, 412).
  624. is_put_to_missing_resource(Req, State=#state{method= <<"PUT">>}) ->
  625. moved_permanently(Req, State, fun is_conflict/2);
  626. is_put_to_missing_resource(Req, State) ->
  627. previously_existed(Req, State).
  628. %% moved_permanently/2 should return either false or {true, Location}
  629. %% with Location the full new URI of the resource.
  630. moved_permanently(Req, State, OnFalse) ->
  631. case call(Req, State, moved_permanently) of
  632. {{true, Location}, Req2, HandlerState} ->
  633. Req3 = cowboy_req:set_resp_header(
  634. <<"location">>, Location, Req2),
  635. respond(Req3, State#state{handler_state=HandlerState}, 301);
  636. {false, Req2, HandlerState} ->
  637. OnFalse(Req2, State#state{handler_state=HandlerState});
  638. {halt, Req2, HandlerState} ->
  639. terminate(Req2, State#state{handler_state=HandlerState});
  640. no_call ->
  641. OnFalse(Req, State)
  642. end.
  643. previously_existed(Req, State) ->
  644. expect(Req, State, previously_existed, false,
  645. fun (R, S) -> is_post_to_missing_resource(R, S, 404) end,
  646. fun (R, S) -> moved_permanently(R, S, fun moved_temporarily/2) end).
  647. %% moved_temporarily/2 should return either false or {true, Location}
  648. %% with Location the full new URI of the resource.
  649. moved_temporarily(Req, State) ->
  650. case call(Req, State, moved_temporarily) of
  651. {{true, Location}, Req2, HandlerState} ->
  652. Req3 = cowboy_req:set_resp_header(
  653. <<"location">>, Location, Req2),
  654. respond(Req3, State#state{handler_state=HandlerState}, 307);
  655. {false, Req2, HandlerState} ->
  656. is_post_to_missing_resource(Req2, State#state{handler_state=HandlerState}, 410);
  657. {halt, Req2, HandlerState} ->
  658. terminate(Req2, State#state{handler_state=HandlerState});
  659. no_call ->
  660. is_post_to_missing_resource(Req, State, 410)
  661. end.
  662. is_post_to_missing_resource(Req, State=#state{method= <<"POST">>}, OnFalse) ->
  663. allow_missing_post(Req, State, OnFalse);
  664. is_post_to_missing_resource(Req, State, OnFalse) ->
  665. respond(Req, State, OnFalse).
  666. allow_missing_post(Req, State, OnFalse) ->
  667. expect(Req, State, allow_missing_post, true, fun post_is_create/2, OnFalse).
  668. method(Req, State=#state{method= <<"DELETE">>}) ->
  669. delete_resource(Req, State);
  670. method(Req, State=#state{method= <<"POST">>}) ->
  671. post_is_create(Req, State);
  672. method(Req, State=#state{method= <<"PUT">>}) ->
  673. is_conflict(Req, State);
  674. method(Req, State=#state{method= <<"PATCH">>}) ->
  675. patch_resource(Req, State);
  676. method(Req, State=#state{method=Method})
  677. when Method =:= <<"GET">>; Method =:= <<"HEAD">> ->
  678. set_resp_body_etag(Req, State);
  679. method(Req, State) ->
  680. multiple_choices(Req, State).
  681. %% delete_resource/2 should start deleting the resource and return.
  682. delete_resource(Req, State) ->
  683. expect(Req, State, delete_resource, false, 500, fun delete_completed/2).
  684. %% delete_completed/2 indicates whether the resource has been deleted yet.
  685. delete_completed(Req, State) ->
  686. expect(Req, State, delete_completed, true, fun has_resp_body/2, 202).
  687. %% post_is_create/2 indicates whether the POST method can create new resources.
  688. post_is_create(Req, State) ->
  689. expect(Req, State, post_is_create, false, fun process_post/2, fun create_path/2).
  690. %% When the POST method can create new resources, create_path/2 will be called
  691. %% and is expected to return the full path to the new resource
  692. %% (including the leading /).
  693. create_path(Req, State) ->
  694. case call(Req, State, create_path) of
  695. no_call ->
  696. put_resource(Req, State, fun created_path/2);
  697. {halt, Req2, HandlerState} ->
  698. terminate(Req2, State#state{handler_state=HandlerState});
  699. {Path, Req2, HandlerState} ->
  700. {HostURL, Req3} = cowboy_req:host_url(Req2),
  701. State2 = State#state{handler_state=HandlerState},
  702. Req4 = cowboy_req:set_resp_header(
  703. <<"location">>, << HostURL/binary, Path/binary >>, Req3),
  704. put_resource(cowboy_req:set_meta(put_path, Path, Req4),
  705. State2, 303)
  706. end.
  707. %% Called after content_types_accepted is called for POST methods
  708. %% when create_path did not exist. Expects the full path to
  709. %% be returned and MUST exist in the case that create_path
  710. %% does not.
  711. created_path(Req, State) ->
  712. case call(Req, State, created_path) of
  713. {halt, Req2, HandlerState} ->
  714. terminate(Req2, State#state{handler_state=HandlerState});
  715. {Path, Req2, HandlerState} ->
  716. {HostURL, Req3} = cowboy_req:host_url(Req2),
  717. State2 = State#state{handler_state=HandlerState},
  718. Req4 = cowboy_req:set_resp_header(
  719. <<"location">>, << HostURL/binary, Path/binary >>, Req3),
  720. respond(cowboy_req:set_meta(put_path, Path, Req4),
  721. State2, 303)
  722. end.
  723. %% process_post should return true when the POST body could be processed
  724. %% and false when it hasn't, in which case a 500 error is sent.
  725. process_post(Req, State) ->
  726. case call(Req, State, process_post) of
  727. {halt, Req2, HandlerState} ->
  728. terminate(Req2, State#state{handler_state=HandlerState});
  729. {true, Req2, HandlerState} ->
  730. State2 = State#state{handler_state=HandlerState},
  731. next(Req2, State2, fun is_new_resource/2);
  732. {false, Req2, HandlerState} ->
  733. State2 = State#state{handler_state=HandlerState},
  734. respond(Req2, State2, 500)
  735. end.
  736. is_conflict(Req, State) ->
  737. expect(Req, State, is_conflict, false, fun put_resource/2, 409).
  738. put_resource(Req, State) ->
  739. Path = cowboy_req:get(path, Req),
  740. put_resource(cowboy_req:set_meta(put_path, Path, Req),
  741. State, fun is_new_resource/2).
  742. %% content_types_accepted should return a list of media types and their
  743. %% associated callback functions in the same format as content_types_provided.
  744. %%
  745. %% The callback will then be called and is expected to process the content
  746. %% pushed to the resource in the request body. The path to the new resource
  747. %% may be different from the request path, and is stored as request metadata.
  748. %% It is always defined past this point. It can be retrieved as demonstrated:
  749. %% {PutPath, Req2} = cowboy_req:meta(put_path, Req)
  750. %%
  751. %%content_types_accepted SHOULD return a different list
  752. %% for each HTTP method.
  753. put_resource(Req, State, OnTrue) ->
  754. case call(Req, State, content_types_accepted) of
  755. no_call ->
  756. respond(Req, State, 415);
  757. {halt, Req2, HandlerState} ->
  758. terminate(Req2, State#state{handler_state=HandlerState});
  759. {CTA, Req2, HandlerState} ->
  760. CTA2 = [normalize_content_types(P) || P <- CTA],
  761. State2 = State#state{handler_state=HandlerState},
  762. {ok, ContentType, Req3}
  763. = cowboy_req:parse_header(<<"content-type">>, Req2),
  764. choose_content_type(Req3, State2, OnTrue, ContentType, CTA2)
  765. end.
  766. %% content_types_accepted should return a list of media types and their
  767. %% associated callback functions in the same format as content_types_provided.
  768. %%
  769. %% The callback will then be called and is expected to process the content
  770. %% pushed to the resource in the request body.
  771. %%
  772. %% content_types_accepted SHOULD return a different list
  773. %% for each HTTP method.
  774. patch_resource(Req, State) ->
  775. case call(Req, State, content_types_accepted) of
  776. no_call ->
  777. respond(Req, State, 415);
  778. {halt, Req2, HandlerState} ->
  779. terminate(Req2, State#state{handler_state=HandlerState});
  780. {CTM, Req2, HandlerState} ->
  781. State2 = State#state{handler_state=HandlerState},
  782. {ok, ContentType, Req3}
  783. = cowboy_req:parse_header(<<"content-type">>, Req2),
  784. choose_content_type(Req3, State2, 204, ContentType, CTM)
  785. end.
  786. %% The special content type '*' will always match. It can be used as a
  787. %% catch-all content type for accepting any kind of request content.
  788. %% Note that because it will always match, it should be the last of the
  789. %% list of content types, otherwise it'll shadow the ones following.
  790. choose_content_type(Req, State, _OnTrue, _ContentType, []) ->
  791. respond(Req, State, 415);
  792. choose_content_type(Req, State, OnTrue, ContentType, [{Accepted, Fun}|_Tail])
  793. when Accepted =:= '*'; Accepted =:= ContentType ->
  794. process_content_type(Req, State, OnTrue, Fun);
  795. %% The special parameter '*' will always match any kind of content type
  796. %% parameters.
  797. %% Note that because it will always match, it should be the last of the
  798. %% list for specific content type, otherwise it'll shadow the ones following.
  799. choose_content_type(Req, State, OnTrue,
  800. {Type, SubType, Param},
  801. [{{Type, SubType, AcceptedParam}, Fun}|_Tail])
  802. when AcceptedParam =:= '*'; AcceptedParam =:= Param ->
  803. process_content_type(Req, State, OnTrue, Fun);
  804. choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) ->
  805. choose_content_type(Req, State, OnTrue, ContentType, Tail).
  806. process_content_type(Req,
  807. State=#state{handler=Handler, handler_state=HandlerState},
  808. OnTrue, Fun) ->
  809. case call(Req, State, Fun) of
  810. no_call ->
  811. error_logger:error_msg(
  812. "** Cowboy handler ~p terminating; "
  813. "function ~p/~p was not exported~n"
  814. "** Request was ~p~n** State was ~p~n~n",
  815. [Handler, Fun, 2, cowboy_req:to_list(Req), HandlerState]),
  816. {error, 500, Req};
  817. {halt, Req2, HandlerState2} ->
  818. terminate(Req2, State#state{handler_state=HandlerState2});
  819. {true, Req2, HandlerState2} ->
  820. State2 = State#state{handler_state=HandlerState2},
  821. next(Req2, State2, OnTrue);
  822. {false, Req2, HandlerState2} ->
  823. State2 = State#state{handler_state=HandlerState2},
  824. respond(Req2, State2, 422)
  825. end.
  826. %% Whether we created a new resource, either through PUT or POST.
  827. %% This is easily testable because we would have set the Location
  828. %% header by this point if we did so.
  829. is_new_resource(Req, State) ->
  830. case cowboy_req:has_resp_header(<<"location">>, Req) of
  831. true -> respond(Req, State, 201);
  832. false -> has_resp_body(Req, State)
  833. end.
  834. has_resp_body(Req, State) ->
  835. case cowboy_req:has_resp_body(Req) of
  836. true -> multiple_choices(Req, State);
  837. false -> respond(Req, State, 204)
  838. end.
  839. %% Set the Etag header if any for the response provided.
  840. set_resp_body_etag(Req, State) ->
  841. try set_resp_etag(Req, State) of
  842. {Req2, State2} ->
  843. set_resp_body_last_modified(Req2, State2)
  844. catch Class:Reason ->
  845. error_logger:error_msg(
  846. "** Cowboy handler ~p terminating in ~p/~p~n"
  847. " for the reason ~p:~p~n** Handler state was ~p~n"
  848. "** Request was ~p~n** Stacktrace: ~p~n~n",
  849. [State#state.handler, generate_etag, 2,
  850. Class, Reason, State#state.handler_state,
  851. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  852. error_terminate(Req, State)
  853. end.
  854. %% Set the Last-Modified header if any for the response provided.
  855. set_resp_body_last_modified(Req, State) ->
  856. try last_modified(Req, State) of
  857. {LastModified, Req2, State2} ->
  858. case LastModified of
  859. LastModified when is_atom(LastModified) ->
  860. set_resp_body_expires(Req2, State2);
  861. LastModified ->
  862. LastModifiedBin = cowboy_clock:rfc1123(LastModified),
  863. Req3 = cowboy_req:set_resp_header(
  864. <<"last-modified">>, LastModifiedBin, Req2),
  865. set_resp_body_expires(Req3, State2)
  866. end
  867. catch Class:Reason ->
  868. error_logger:error_msg(
  869. "** Cowboy handler ~p terminating in ~p/~p~n"
  870. " for the reason ~p:~p~n** Handler state was ~p~n"
  871. "** Request was ~p~n** Stacktrace: ~p~n~n",
  872. [State#state.handler, last_modified, 2,
  873. Class, Reason, State#state.handler_state,
  874. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  875. error_terminate(Req, State)
  876. end.
  877. %% Set the Expires header if any for the response provided.
  878. set_resp_body_expires(Req, State) ->
  879. try set_resp_expires(Req, State) of
  880. {Req2, State2} ->
  881. set_resp_body(Req2, State2)
  882. catch Class:Reason ->
  883. error_logger:error_msg(
  884. "** Cowboy handler ~p terminating in ~p/~p~n"
  885. " for the reason ~p:~p~n** Handler state was ~p~n"
  886. "** Request was ~p~n** Stacktrace: ~p~n~n",
  887. [State#state.handler, expires, 2,
  888. Class, Reason, State#state.handler_state,
  889. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  890. error_terminate(Req, State)
  891. end.
  892. %% Set the response headers and call the callback found using
  893. %% content_types_provided/2 to obtain the request body and add
  894. %% it to the response.
  895. set_resp_body(Req, State=#state{handler=Handler, handler_state=HandlerState,
  896. content_type_a={_Type, Callback}}) ->
  897. case call(Req, State, Callback) of
  898. no_call ->
  899. error_logger:error_msg(
  900. "** Cowboy handler ~p terminating; "
  901. "function ~p/~p was not exported~n"
  902. "** Request was ~p~n** State was ~p~n~n",
  903. [Handler, Callback, 2, cowboy_req:to_list(Req), HandlerState]),
  904. {error, 500, Req};
  905. {halt, Req2, HandlerState2} ->
  906. terminate(Req2, State#state{handler_state=HandlerState2});
  907. {Body, Req2, HandlerState2} ->
  908. State2 = State#state{handler_state=HandlerState2},
  909. Req3 = case Body of
  910. {stream, StreamFun} ->
  911. cowboy_req:set_resp_body_fun(StreamFun, Req2);
  912. {stream, Len, StreamFun} ->
  913. cowboy_req:set_resp_body_fun(Len, StreamFun, Req2);
  914. _Contents ->
  915. cowboy_req:set_resp_body(Body, Req2)
  916. end,
  917. multiple_choices(Req3, State2)
  918. end.
  919. multiple_choices(Req, State) ->
  920. expect(Req, State, multiple_choices, false, 200, 300).
  921. %% Response utility functions.
  922. set_resp_etag(Req, State) ->
  923. {Etag, Req2, State2} = generate_etag(Req, State),
  924. case Etag of
  925. undefined ->
  926. {Req2, State2};
  927. Etag ->
  928. Req3 = cowboy_req:set_resp_header(
  929. <<"etag">>, encode_etag(Etag), Req2),
  930. {Req3, State2}
  931. end.
  932. -spec encode_etag({strong | weak, binary()}) -> iolist().
  933. encode_etag({strong, Etag}) -> [$",Etag,$"];
  934. encode_etag({weak, Etag}) -> ["W/\"",Etag,$"].
  935. set_resp_expires(Req, State) ->
  936. {Expires, Req2, State2} = expires(Req, State),
  937. case Expires of
  938. Expires when is_atom(Expires) ->
  939. {Req2, State2};
  940. Expires ->
  941. ExpiresBin = cowboy_clock:rfc1123(Expires),
  942. Req3 = cowboy_req:set_resp_header(
  943. <<"expires">>, ExpiresBin, Req2),
  944. {Req3, State2}
  945. end.
  946. %% Info retrieval. No logic.
  947. generate_etag(Req, State=#state{etag=no_call}) ->
  948. {undefined, Req, State};
  949. generate_etag(Req, State=#state{etag=undefined}) ->
  950. case unsafe_call(Req, State, generate_etag) of
  951. no_call ->
  952. {undefined, Req, State#state{etag=no_call}};
  953. {Etag, Req2, HandlerState} when is_binary(Etag) ->
  954. [Etag2] = cowboy_http:entity_tag_match(Etag),
  955. {Etag2, Req2, State#state{handler_state=HandlerState, etag=Etag2}};
  956. {Etag, Req2, HandlerState} ->
  957. {Etag, Req2, State#state{handler_state=HandlerState, etag=Etag}}
  958. end;
  959. generate_etag(Req, State=#state{etag=Etag}) ->
  960. {Etag, Req, State}.
  961. last_modified(Req, State=#state{last_modified=no_call}) ->
  962. {undefined, Req, State};
  963. last_modified(Req, State=#state{last_modified=undefined}) ->
  964. case unsafe_call(Req, State, last_modified) of
  965. no_call ->
  966. {undefined, Req, State#state{last_modified=no_call}};
  967. {LastModified, Req2, HandlerState} ->
  968. {LastModified, Req2, State#state{handler_state=HandlerState,
  969. last_modified=LastModified}}
  970. end;
  971. last_modified(Req, State=#state{last_modified=LastModified}) ->
  972. {LastModified, Req, State}.
  973. expires(Req, State=#state{expires=no_call}) ->
  974. {undefined, Req, State};
  975. expires(Req, State=#state{expires=undefined}) ->
  976. case unsafe_call(Req, State, expires) of
  977. no_call ->
  978. {undefined, Req, State#state{expires=no_call}};
  979. {Expires, Req2, HandlerState} ->
  980. {Expires, Req2, State#state{handler_state=HandlerState,
  981. expires=Expires}}
  982. end;
  983. expires(Req, State=#state{expires=Expires}) ->
  984. {Expires, Req, State}.
  985. %% REST primitives.
  986. expect(Req, State, Callback, Expected, OnTrue, OnFalse) ->
  987. case call(Req, State, Callback) of
  988. no_call ->
  989. next(Req, State, OnTrue);
  990. {halt, Req2, HandlerState} ->
  991. terminate(Req2, State#state{handler_state=HandlerState});
  992. {Expected, Req2, HandlerState} ->
  993. next(Req2, State#state{handler_state=HandlerState}, OnTrue);
  994. {_Unexpected, Req2, HandlerState} ->
  995. next(Req2, State#state{handler_state=HandlerState}, OnFalse)
  996. end.
  997. call(Req, State=#state{handler=Handler, handler_state=HandlerState},
  998. Callback) ->
  999. case erlang:function_exported(Handler, Callback, 2) of
  1000. true ->
  1001. try
  1002. Handler:Callback(Req, HandlerState)
  1003. catch Class:Reason ->
  1004. error_logger:error_msg(
  1005. "** Cowboy handler ~p terminating in ~p/~p~n"
  1006. " for the reason ~p:~p~n** Handler state was ~p~n"
  1007. "** Request was ~p~n** Stacktrace: ~p~n~n",
  1008. [Handler, Callback, 2, Class, Reason, HandlerState,
  1009. cowboy_req:to_list(Req), erlang:get_stacktrace()]),
  1010. error_terminate(Req, State)
  1011. end;
  1012. false ->
  1013. no_call
  1014. end.
  1015. unsafe_call(Req, #state{handler=Handler, handler_state=HandlerState},
  1016. Callback) ->
  1017. case erlang:function_exported(Handler, Callback, 2) of
  1018. true -> Handler:Callback(Req, HandlerState);
  1019. false -> no_call
  1020. end.
  1021. next(Req, State, Next) when is_function(Next) ->
  1022. Next(Req, State);
  1023. next(Req, State, StatusCode) when is_integer(StatusCode) ->
  1024. respond(Req, State, StatusCode).
  1025. respond(Req, State, StatusCode) ->
  1026. {ok, Req2} = cowboy_req:reply(StatusCode, Req),
  1027. terminate(Req2, State).
  1028. terminate(Req, State=#state{env=Env}) ->
  1029. rest_terminate(Req, State),
  1030. {ok, Req, [{result, ok}|Env]}.
  1031. -spec error_terminate(cowboy_req:req(), #state{}) -> no_return().
  1032. error_terminate(Req, State) ->
  1033. rest_terminate(Req, State),
  1034. erlang:throw({?MODULE, error}).
  1035. rest_terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
  1036. case erlang:function_exported(Handler, rest_terminate, 2) of
  1037. true -> ok = Handler:rest_terminate(
  1038. cowboy_req:lock(Req), HandlerState);
  1039. false -> ok
  1040. end.