cowboy_rest.erl 35 KB

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