cowboy_rest.erl 39 KB

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