cowboy_rest.erl 39 KB

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