cowboy_rest.erl 35 KB

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