cowboy_rest.erl 35 KB

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