cowboy_rest.erl 39 KB

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