cowboy_http_rest.erl 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918
  1. %% Copyright (c) 2011, Loïc Hoguin <essen@dev-extend.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_http_rest).
  21. -export([upgrade/4]).
  22. -record(state, {
  23. %% Handler.
  24. handler :: atom(),
  25. handler_state :: any(),
  26. %% Media type.
  27. content_types_p = [] ::
  28. [{{binary(), binary(), [{binary(), binary()}]}, atom()}],
  29. content_type_a :: undefined
  30. | {{binary(), binary(), [{binary(), binary()}]}, atom()},
  31. %% Language.
  32. languages_p = [] :: [binary()],
  33. language_a :: undefined | binary(),
  34. %% Charset.
  35. charsets_p = [] :: [binary()],
  36. charset_a :: undefined | binary(),
  37. %% Cached resource calls.
  38. etag :: undefined | no_call | {strong | weak, binary()},
  39. last_modified :: undefined | no_call | calendar:datetime(),
  40. expires :: undefined | no_call | calendar:datetime()
  41. }).
  42. -include("http.hrl").
  43. %% @doc Upgrade a HTTP request to the REST protocol.
  44. %%
  45. %% You do not need to call this function manually. To upgrade to the REST
  46. %% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em>
  47. %% in your <em>cowboy_http_handler:init/3</em> handler function.
  48. -spec upgrade(pid(), module(), any(), #http_req{})
  49. -> {ok, #http_req{}} | close.
  50. upgrade(_ListenerPid, Handler, Opts, Req) ->
  51. try
  52. case erlang:function_exported(Handler, rest_init, 2) of
  53. true ->
  54. case Handler:rest_init(Req, Opts) of
  55. {ok, Req2, HandlerState} ->
  56. service_available(Req2, #state{handler=Handler,
  57. handler_state=HandlerState})
  58. end;
  59. false ->
  60. service_available(Req, #state{handler=Handler})
  61. end
  62. catch Class:Reason ->
  63. error_logger:error_msg(
  64. "** Handler ~p terminating in rest_init/3~n"
  65. " for the reason ~p:~p~n** Options were ~p~n"
  66. "** Request was ~p~n** Stacktrace: ~p~n~n",
  67. [Handler, Class, Reason, Opts, Req, erlang:get_stacktrace()]),
  68. {ok, _Req2} = cowboy_http_req:reply(500, Req),
  69. close
  70. end.
  71. service_available(Req, State) ->
  72. expect(Req, State, service_available, true, fun known_methods/2, 503).
  73. %% known_methods/2 should return a list of atoms or binary methods.
  74. known_methods(Req=#http_req{method=Method}, State) ->
  75. case call(Req, State, known_methods) of
  76. no_call when Method =:= 'HEAD'; Method =:= 'GET'; Method =:= 'POST';
  77. Method =:= 'PUT'; Method =:= 'DELETE'; Method =:= 'TRACE';
  78. Method =:= 'CONNECT'; Method =:= 'OPTIONS' ->
  79. next(Req, State, fun uri_too_long/2);
  80. no_call ->
  81. next(Req, State, 501);
  82. {halt, Req2, HandlerState} ->
  83. terminate(Req2, State#state{handler_state=HandlerState});
  84. {List, Req2, HandlerState} ->
  85. State2 = State#state{handler_state=HandlerState},
  86. case lists:member(Method, List) of
  87. true -> next(Req2, State2, fun uri_too_long/2);
  88. false -> next(Req2, State2, 501)
  89. end
  90. end.
  91. uri_too_long(Req, State) ->
  92. expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414).
  93. %% allowed_methods/2 should return a list of atoms or binary methods.
  94. allowed_methods(Req=#http_req{method=Method}, State) ->
  95. case call(Req, State, allowed_methods) of
  96. no_call when Method =:= 'HEAD'; Method =:= 'GET' ->
  97. next(Req, State, fun malformed_request/2);
  98. no_call ->
  99. method_not_allowed(Req, State, ['GET', 'HEAD']);
  100. {halt, Req2, HandlerState} ->
  101. terminate(Req2, State#state{handler_state=HandlerState});
  102. {List, Req2, HandlerState} ->
  103. State2 = State#state{handler_state=HandlerState},
  104. case lists:member(Method, List) of
  105. true -> next(Req2, State2, fun malformed_request/2);
  106. false -> method_not_allowed(Req2, State2, List)
  107. end
  108. end.
  109. method_not_allowed(Req, State, Methods) ->
  110. {ok, Req2} = cowboy_http_req:set_resp_header(
  111. <<"Allow">>, method_not_allowed_build(Methods, []), Req),
  112. respond(Req2, State, 405).
  113. method_not_allowed_build([], []) ->
  114. <<>>;
  115. method_not_allowed_build([], [_Ignore|Acc]) ->
  116. lists:reverse(Acc);
  117. method_not_allowed_build([Method|Tail], Acc) when is_atom(Method) ->
  118. Method2 = list_to_binary(atom_to_list(Method)),
  119. method_not_allowed_build(Tail, [<<", ">>, Method2|Acc]);
  120. method_not_allowed_build([Method|Tail], Acc) ->
  121. method_not_allowed_build(Tail, [<<", ">>, Method|Acc]).
  122. malformed_request(Req, State) ->
  123. expect(Req, State, malformed_request, false, fun is_authorized/2, 400).
  124. %% is_authorized/2 should return true or {false, WwwAuthenticateHeader}.
  125. is_authorized(Req, State) ->
  126. case call(Req, State, is_authorized) of
  127. no_call ->
  128. forbidden(Req, State);
  129. {halt, Req2, HandlerState} ->
  130. terminate(Req2, State#state{handler_state=HandlerState});
  131. {true, Req2, HandlerState} ->
  132. forbidden(Req2, State#state{handler_state=HandlerState});
  133. {{false, AuthHead}, Req2, HandlerState} ->
  134. {ok, Req3} = cowboy_http_req:set_resp_header(
  135. <<"Www-Authenticate">>, AuthHead, Req2),
  136. respond(Req3, State#state{handler_state=HandlerState}, 401)
  137. end.
  138. forbidden(Req, State) ->
  139. expect(Req, State, forbidden, false, fun valid_content_headers/2, 403).
  140. valid_content_headers(Req, State) ->
  141. expect(Req, State, valid_content_headers, true,
  142. fun known_content_type/2, 501).
  143. known_content_type(Req, State) ->
  144. expect(Req, State, known_content_type, true,
  145. fun valid_entity_length/2, 413).
  146. valid_entity_length(Req, State) ->
  147. expect(Req, State, valid_entity_length, true, fun options/2, 413).
  148. %% If you need to add additional headers to the response at this point,
  149. %% you should do it directly in the options/2 call using set_resp_headers.
  150. options(Req=#http_req{method='OPTIONS'}, State) ->
  151. case call(Req, State, options) of
  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. An example
  172. %% of such a return value would be:
  173. %% {<<"text/html">>, to_html}
  174. content_types_provided(Req=#http_req{meta=Meta}, State) ->
  175. case call(Req, State, content_types_provided) of
  176. no_call ->
  177. not_acceptable(Req, State);
  178. {halt, Req2, HandlerState} ->
  179. terminate(Req2, State#state{handler_state=HandlerState});
  180. {[], Req2, HandlerState} ->
  181. not_acceptable(Req2, State#state{handler_state=HandlerState});
  182. {CTP, Req2, HandlerState} ->
  183. CTP2 = [normalize_content_types_provided(P) || P <- CTP],
  184. State2 = State#state{
  185. handler_state=HandlerState, content_types_p=CTP2},
  186. {Accept, Req3} = cowboy_http_req:parse_header('Accept', Req2),
  187. case Accept of
  188. undefined ->
  189. {PMT, _Fun} = HeadCTP = hd(CTP2),
  190. languages_provided(
  191. Req3#http_req{meta=[{media_type, PMT}|Meta]},
  192. State2#state{content_type_a=HeadCTP});
  193. Accept ->
  194. Accept2 = prioritize_accept(Accept),
  195. choose_media_type(Req3, State2, Accept2)
  196. end
  197. end.
  198. normalize_content_types_provided({ContentType, Handler})
  199. when is_binary(ContentType) ->
  200. {cowboy_http:content_type(ContentType), Handler};
  201. normalize_content_types_provided(Provided) ->
  202. Provided.
  203. prioritize_accept(Accept) ->
  204. lists:sort(
  205. fun ({MediaTypeA, Quality, _AcceptParamsA},
  206. {MediaTypeB, Quality, _AcceptParamsB}) ->
  207. %% Same quality, check precedence in more details.
  208. prioritize_mediatype(MediaTypeA, MediaTypeB);
  209. ({_MediaTypeA, QualityA, _AcceptParamsA},
  210. {_MediaTypeB, QualityB, _AcceptParamsB}) ->
  211. %% Just compare the quality.
  212. QualityA > QualityB
  213. end, Accept).
  214. %% Media ranges can be overridden by more specific media ranges or
  215. %% specific media types. If more than one media range applies to a given
  216. %% type, the most specific reference has precedence.
  217. %%
  218. %% We always choose B over A when we can't decide between the two.
  219. prioritize_mediatype({TypeA, SubTypeA, ParamsA}, {TypeB, SubTypeB, ParamsB}) ->
  220. case TypeB of
  221. TypeA ->
  222. case SubTypeB of
  223. SubTypeA -> length(ParamsA) > length(ParamsB);
  224. <<"*">> -> true;
  225. _Any -> false
  226. end;
  227. <<"*">> -> true;
  228. _Any -> false
  229. end.
  230. %% Ignoring the rare AcceptParams. Not sure what should be done about them.
  231. choose_media_type(Req, State, []) ->
  232. not_acceptable(Req, State);
  233. choose_media_type(Req, State=#state{content_types_p=CTP},
  234. [MediaType|Tail]) ->
  235. match_media_type(Req, State, Tail, CTP, MediaType).
  236. match_media_type(Req, State, Accept, [], _MediaType) ->
  237. choose_media_type(Req, State, Accept);
  238. match_media_type(Req, State, Accept, CTP,
  239. MediaType = {{<<"*">>, <<"*">>, _Params_A}, _QA, _APA}) ->
  240. match_media_type_params(Req, State, Accept, CTP, MediaType);
  241. match_media_type(Req, State, Accept,
  242. CTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail],
  243. MediaType = {{Type, SubType_A, _PA}, _QA, _APA})
  244. when SubType_P =:= SubType_A; SubType_A =:= <<"*">> ->
  245. match_media_type_params(Req, State, Accept, CTP, MediaType);
  246. match_media_type(Req, State, Accept, [_Any|Tail], MediaType) ->
  247. match_media_type(Req, State, Accept, Tail, MediaType).
  248. match_media_type_params(Req=#http_req{meta=Meta}, State, Accept,
  249. [Provided = {PMT = {_TP, _STP, Params_P}, _Fun}|Tail],
  250. MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) ->
  251. case lists:sort(Params_P) =:= lists:sort(Params_A) of
  252. true ->
  253. languages_provided(Req#http_req{meta=[{media_type, PMT}|Meta]},
  254. State#state{content_type_a=Provided});
  255. false ->
  256. match_media_type(Req, State, Accept, Tail, MediaType)
  257. end.
  258. %% languages_provided should return a list of binary values indicating
  259. %% which languages are accepted by the resource.
  260. %%
  261. %% @todo I suppose we should also ask the resource if it wants to
  262. %% set a language itself or if it wants it to be automatically chosen.
  263. languages_provided(Req, State) ->
  264. case call(Req, State, languages_provided) of
  265. no_call ->
  266. charsets_provided(Req, State);
  267. {halt, Req2, HandlerState} ->
  268. terminate(Req2, State#state{handler_state=HandlerState});
  269. {[], Req2, HandlerState} ->
  270. not_acceptable(Req2, State#state{handler_state=HandlerState});
  271. {LP, Req2, HandlerState} ->
  272. State2 = State#state{handler_state=HandlerState, languages_p=LP},
  273. {AcceptLanguage, Req3} =
  274. cowboy_http_req:parse_header('Accept-Language', Req2),
  275. case AcceptLanguage of
  276. undefined ->
  277. set_language(Req3, State2#state{language_a=hd(LP)});
  278. AcceptLanguage ->
  279. AcceptLanguage2 = prioritize_languages(AcceptLanguage),
  280. choose_language(Req3, State2, AcceptLanguage2)
  281. end
  282. end.
  283. %% A language-range matches a language-tag if it exactly equals the tag,
  284. %% or if it exactly equals a prefix of the tag such that the first tag
  285. %% character following the prefix is "-". The special range "*", if
  286. %% present in the Accept-Language field, matches every tag not matched
  287. %% by any other range present in the Accept-Language field.
  288. %%
  289. %% @todo The last sentence probably means we should always put '*'
  290. %% at the end of the list.
  291. prioritize_languages(AcceptLanguages) ->
  292. lists:sort(
  293. fun ({_TagA, QualityA}, {_TagB, QualityB}) ->
  294. QualityA > QualityB
  295. end, AcceptLanguages).
  296. choose_language(Req, State, []) ->
  297. not_acceptable(Req, State);
  298. choose_language(Req, State=#state{languages_p=LP}, [Language|Tail]) ->
  299. match_language(Req, State, Tail, LP, Language).
  300. match_language(Req, State, Accept, [], _Language) ->
  301. choose_language(Req, State, Accept);
  302. match_language(Req, State, _Accept, [Provided|_Tail], {'*', _Quality}) ->
  303. set_language(Req, State#state{language_a=Provided});
  304. match_language(Req, State, _Accept, [Provided|_Tail], {Provided, _Quality}) ->
  305. set_language(Req, State#state{language_a=Provided});
  306. match_language(Req, State, Accept, [Provided|Tail],
  307. Language = {Tag, _Quality}) ->
  308. Length = byte_size(Tag),
  309. case Provided of
  310. << Tag:Length/binary, $-, _Any/bits >> ->
  311. set_language(Req, State#state{language_a=Provided});
  312. _Any ->
  313. match_language(Req, State, Accept, Tail, Language)
  314. end.
  315. set_language(Req=#http_req{meta=Meta}, State=#state{language_a=Language}) ->
  316. {ok, Req2} = cowboy_http_req:set_resp_header(
  317. <<"Content-Language">>, Language, Req),
  318. charsets_provided(Req2#http_req{meta=[{language, Language}|Meta]}, State).
  319. %% charsets_provided should return a list of binary values indicating
  320. %% which charsets are accepted by the resource.
  321. charsets_provided(Req, State) ->
  322. case call(Req, State, charsets_provided) of
  323. no_call ->
  324. set_content_type(Req, State);
  325. {halt, Req2, HandlerState} ->
  326. terminate(Req2, State#state{handler_state=HandlerState});
  327. {[], Req2, HandlerState} ->
  328. not_acceptable(Req2, State#state{handler_state=HandlerState});
  329. {CP, Req2, HandlerState} ->
  330. State2 = State#state{handler_state=HandlerState, charsets_p=CP},
  331. {AcceptCharset, Req3} =
  332. cowboy_http_req:parse_header('Accept-Charset', Req2),
  333. case AcceptCharset of
  334. undefined ->
  335. set_content_type(Req3, State2#state{charset_a=hd(CP)});
  336. AcceptCharset ->
  337. AcceptCharset2 = prioritize_charsets(AcceptCharset),
  338. choose_charset(Req3, State2, AcceptCharset2)
  339. end
  340. end.
  341. %% The special value "*", if present in the Accept-Charset field,
  342. %% matches every character set (including ISO-8859-1) which is not
  343. %% mentioned elsewhere in the Accept-Charset field. If no "*" is present
  344. %% in an Accept-Charset field, then all character sets not explicitly
  345. %% mentioned get a quality value of 0, except for ISO-8859-1, which gets
  346. %% a quality value of 1 if not explicitly mentioned.
  347. prioritize_charsets(AcceptCharsets) ->
  348. AcceptCharsets2 = lists:sort(
  349. fun ({_CharsetA, QualityA}, {_CharsetB, QualityB}) ->
  350. QualityA > QualityB
  351. end, AcceptCharsets),
  352. case lists:keymember(<<"*">>, 1, AcceptCharsets2) of
  353. true -> AcceptCharsets2;
  354. false -> [{<<"iso-8859-1">>, 1000}|AcceptCharsets2]
  355. end.
  356. choose_charset(Req, State, []) ->
  357. not_acceptable(Req, State);
  358. choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) ->
  359. match_charset(Req, State, Tail, CP, Charset).
  360. match_charset(Req, State, Accept, [], _Charset) ->
  361. choose_charset(Req, State, Accept);
  362. match_charset(Req, State, _Accept, [Provided|_Tail],
  363. {Provided, _Quality}) ->
  364. set_content_type(Req, State#state{charset_a=Provided});
  365. match_charset(Req, State, Accept, [_Provided|Tail], Charset) ->
  366. match_charset(Req, State, Accept, Tail, Charset).
  367. set_content_type(Req=#http_req{meta=Meta}, State=#state{
  368. content_type_a={{Type, SubType, Params}, _Fun},
  369. charset_a=Charset}) ->
  370. ParamsBin = set_content_type_build_params(Params, []),
  371. ContentType = [Type, <<"/">>, SubType, ParamsBin],
  372. ContentType2 = case Charset of
  373. undefined -> ContentType;
  374. Charset -> [ContentType, <<"; charset=">>, Charset]
  375. end,
  376. {ok, Req2} = cowboy_http_req:set_resp_header(
  377. <<"Content-Type">>, ContentType2, Req),
  378. encodings_provided(Req2#http_req{meta=[{charset, Charset}|Meta]}, State).
  379. set_content_type_build_params([], []) ->
  380. <<>>;
  381. set_content_type_build_params([], Acc) ->
  382. lists:reverse(Acc);
  383. set_content_type_build_params([{Attr, Value}|Tail], Acc) ->
  384. set_content_type_build_params(Tail, [[Attr, <<"=">>, Value], <<";">>|Acc]).
  385. %% @todo Match for identity as we provide nothing else for now.
  386. %% @todo Don't forget to set the Content-Encoding header when we reply a body
  387. %% and the found encoding is something other than identity.
  388. encodings_provided(Req, State) ->
  389. variances(Req, State).
  390. not_acceptable(Req, State) ->
  391. respond(Req, State, 406).
  392. %% variances/2 should return a list of headers that will be added
  393. %% to the Vary response header. The Accept, Accept-Language,
  394. %% Accept-Charset and Accept-Encoding headers do not need to be
  395. %% specified.
  396. %%
  397. %% @todo Do Accept-Encoding too when we handle it.
  398. %% @todo Does the order matter?
  399. variances(Req, State=#state{content_types_p=CTP,
  400. languages_p=LP, charsets_p=CP}) ->
  401. Variances = case CTP of
  402. [] -> [];
  403. [_] -> [];
  404. [_|_] -> [<<"Accept">>]
  405. end,
  406. Variances2 = case LP of
  407. [] -> Variances;
  408. [_] -> Variances;
  409. [_|_] -> [<<"Accept-Language">>|Variances]
  410. end,
  411. Variances3 = case CP of
  412. [] -> Variances2;
  413. [_] -> Variances2;
  414. [_|_] -> [<<"Accept-Charset">>|Variances2]
  415. end,
  416. {Variances4, Req3, State2} = case call(Req, State, variances) of
  417. no_call ->
  418. {Variances3, Req, State};
  419. {HandlerVariances, Req2, HandlerState} ->
  420. {Variances3 ++ HandlerVariances, Req2,
  421. State#state{handler_state=HandlerState}}
  422. end,
  423. case [[<<", ">>, V] || V <- Variances4] of
  424. [] ->
  425. resource_exists(Req3, State2);
  426. [[<<", ">>, H]|Variances5] ->
  427. {ok, Req4} = cowboy_http_req:set_resp_header(
  428. <<"Variances">>, [H|Variances5], Req3),
  429. resource_exists(Req4, State2)
  430. end.
  431. resource_exists(Req, State) ->
  432. expect(Req, State, resource_exists, true,
  433. fun if_match_exists/2, fun if_match_musnt_exist/2).
  434. if_match_exists(Req, State) ->
  435. case cowboy_http_req:parse_header('If-Match', Req) of
  436. {undefined, Req2} ->
  437. if_unmodified_since_exists(Req2, State);
  438. {'*', Req2} ->
  439. if_unmodified_since_exists(Req2, State);
  440. {ETagsList, Req2} ->
  441. if_match(Req2, State, ETagsList)
  442. end.
  443. if_match(Req, State, EtagsList) ->
  444. {Etag, Req2, State2} = generate_etag(Req, State),
  445. case lists:member(Etag, EtagsList) of
  446. true -> if_unmodified_since_exists(Req2, State2);
  447. %% Etag may be `undefined' which cannot be a member.
  448. false -> precondition_failed(Req2, State2)
  449. end.
  450. if_match_musnt_exist(Req, State) ->
  451. case cowboy_http_req:header('If-Match', Req) of
  452. {undefined, Req2} -> is_put_to_missing_resource(Req2, State);
  453. {_Any, Req2} -> precondition_failed(Req2, State)
  454. end.
  455. if_unmodified_since_exists(Req, State) ->
  456. case cowboy_http_req:parse_header('If-Unmodified-Since', Req) of
  457. {undefined, Req2} ->
  458. if_none_match_exists(Req2, State);
  459. {{error, badarg}, Req2} ->
  460. if_none_match_exists(Req2, State);
  461. {IfUnmodifiedSince, Req2} ->
  462. if_unmodified_since(Req2, State, IfUnmodifiedSince)
  463. end.
  464. %% If LastModified is the atom 'no_call', we continue.
  465. if_unmodified_since(Req, State, IfUnmodifiedSince) ->
  466. {LastModified, Req2, State2} = last_modified(Req, State),
  467. case LastModified > IfUnmodifiedSince of
  468. true -> precondition_failed(Req2, State2);
  469. false -> if_none_match_exists(Req2, State2)
  470. end.
  471. if_none_match_exists(Req, State) ->
  472. case cowboy_http_req:parse_header('If-None-Match', Req) of
  473. {undefined, Req2} ->
  474. if_modified_since_exists(Req2, State);
  475. {'*', Req2} ->
  476. precondition_is_head_get(Req2, State);
  477. {EtagsList, Req2} ->
  478. if_none_match(Req2, State, EtagsList)
  479. end.
  480. if_none_match(Req, State, EtagsList) ->
  481. {Etag, Req2, State2} = generate_etag(Req, State),
  482. case Etag of
  483. undefined ->
  484. precondition_failed(Req2, State2);
  485. Etag ->
  486. case lists:member(Etag, EtagsList) of
  487. true -> precondition_is_head_get(Req2, State2);
  488. false -> if_modified_since_exists(Req2, State2)
  489. end
  490. end.
  491. precondition_is_head_get(Req=#http_req{method=Method}, State)
  492. when Method =:= 'HEAD'; Method =:= 'GET' ->
  493. not_modified(Req, State);
  494. precondition_is_head_get(Req, State) ->
  495. precondition_failed(Req, State).
  496. if_modified_since_exists(Req, State) ->
  497. case cowboy_http_req:parse_header('If-Modified-Since', Req) of
  498. {undefined, Req2} ->
  499. method(Req2, State);
  500. {{error, badarg}, Req2} ->
  501. method(Req2, State);
  502. {IfModifiedSince, Req2} ->
  503. if_modified_since_now(Req2, State, IfModifiedSince)
  504. end.
  505. if_modified_since_now(Req, State, IfModifiedSince) ->
  506. case IfModifiedSince > erlang:universaltime() of
  507. true -> method(Req, State);
  508. false -> if_modified_since(Req, State, IfModifiedSince)
  509. end.
  510. if_modified_since(Req, State, IfModifiedSince) ->
  511. {LastModified, Req2, State2} = last_modified(Req, State),
  512. case LastModified of
  513. no_call ->
  514. method(Req2, State2);
  515. LastModified ->
  516. case LastModified > IfModifiedSince of
  517. true -> method(Req2, State2);
  518. false -> not_modified(Req2, State2)
  519. end
  520. end.
  521. not_modified(Req=#http_req{resp_headers=RespHeaders}, State) ->
  522. RespHeaders2 = lists:keydelete(<<"Content-Type">>, 1, RespHeaders),
  523. Req2 = Req#http_req{resp_headers=RespHeaders2},
  524. {Req3, State2} = set_resp_etag(Req2, State),
  525. {Req4, State3} = set_resp_expires(Req3, State2),
  526. respond(Req4, State3, 304).
  527. precondition_failed(Req, State) ->
  528. respond(Req, State, 412).
  529. is_put_to_missing_resource(Req=#http_req{method='PUT'}, State) ->
  530. moved_permanently(Req, State, fun is_conflict/2);
  531. is_put_to_missing_resource(Req, State) ->
  532. previously_existed(Req, State).
  533. %% moved_permanently/2 should return either false or {true, Location}
  534. %% with Location the full new URI of the resource.
  535. moved_permanently(Req, State, OnFalse) ->
  536. case call(Req, State, moved_permanently) of
  537. {{true, Location}, Req2, HandlerState} ->
  538. {ok, Req3} = cowboy_http_req:set_resp_header(
  539. <<"Location">>, Location, Req2),
  540. respond(Req3, State#state{handler_state=HandlerState}, 301);
  541. {false, Req2, HandlerState} ->
  542. OnFalse(Req2, State#state{handler_state=HandlerState});
  543. {halt, Req2, HandlerState} ->
  544. terminate(Req2, State#state{handler_state=HandlerState});
  545. no_call ->
  546. OnFalse(Req, State)
  547. end.
  548. previously_existed(Req, State) ->
  549. expect(Req, State, previously_existed, false,
  550. fun (R, S) -> is_post_to_missing_resource(R, S, 404) end,
  551. fun (R, S) -> moved_permanently(R, S, fun moved_temporarily/2) end).
  552. %% moved_temporarily/2 should return either false or {true, Location}
  553. %% with Location the full new URI of the resource.
  554. moved_temporarily(Req, State) ->
  555. case call(Req, State, moved_temporarily) of
  556. {{true, Location}, Req2, HandlerState} ->
  557. {ok, Req3} = cowboy_http_req:set_resp_header(
  558. <<"Location">>, Location, Req2),
  559. respond(Req3, State#state{handler_state=HandlerState}, 307);
  560. {false, Req2, HandlerState} ->
  561. is_post_to_missing_resource(Req2, State#state{handler_state=HandlerState}, 410);
  562. {halt, Req2, HandlerState} ->
  563. terminate(Req2, State#state{handler_state=HandlerState});
  564. no_call ->
  565. is_post_to_missing_resource(Req, State, 410)
  566. end.
  567. is_post_to_missing_resource(Req=#http_req{method='POST'}, State, OnFalse) ->
  568. allow_missing_post(Req, State, OnFalse);
  569. is_post_to_missing_resource(Req, State, OnFalse) ->
  570. respond(Req, State, OnFalse).
  571. allow_missing_post(Req, State, OnFalse) ->
  572. expect(Req, State, allow_missing_post, true, fun post_is_create/2, OnFalse).
  573. method(Req=#http_req{method='DELETE'}, State) ->
  574. delete_resource(Req, State);
  575. method(Req=#http_req{method='POST'}, State) ->
  576. post_is_create(Req, State);
  577. method(Req=#http_req{method='PUT'}, State) ->
  578. is_conflict(Req, State);
  579. method(Req, State) ->
  580. set_resp_body(Req, State).
  581. %% delete_resource/2 should start deleting the resource and return.
  582. delete_resource(Req, State) ->
  583. expect(Req, State, delete_resource, false, 500, fun delete_completed/2).
  584. %% delete_completed/2 indicates whether the resource has been deleted yet.
  585. delete_completed(Req, State) ->
  586. expect(Req, State, delete_completed, true, fun has_resp_body/2, 202).
  587. %% post_is_create/2 indicates whether the POST method can create new resources.
  588. post_is_create(Req, State) ->
  589. expect(Req, State, post_is_create, false, fun process_post/2, fun create_path/2).
  590. %% When the POST method can create new resources, create_path/2 will be called
  591. %% and is expected to return the full path to the new resource
  592. %% (including the leading /).
  593. create_path(Req=#http_req{meta=Meta}, State) ->
  594. case call(Req, State, create_path) of
  595. {halt, Req2, HandlerState} ->
  596. terminate(Req2, State#state{handler_state=HandlerState});
  597. {Path, Req2, HandlerState} ->
  598. Location = create_path_location(Req2, Path),
  599. State2 = State#state{handler_state=HandlerState},
  600. {ok, Req3} = cowboy_http_req:set_resp_header(
  601. <<"Location">>, Location, Req2),
  602. put_resource(Req3#http_req{meta=[{put_path, Path}|Meta]},
  603. State2, 303)
  604. end.
  605. create_path_location(#http_req{transport=Transport, raw_host=Host,
  606. port=Port}, Path) ->
  607. TransportName = Transport:name(),
  608. << (create_path_location_protocol(TransportName))/binary, "://",
  609. Host/binary, (create_path_location_port(TransportName, Port))/binary,
  610. Path/binary >>.
  611. create_path_location_protocol(ssl) -> <<"https">>;
  612. create_path_location_protocol(_) -> <<"http">>.
  613. create_path_location_port(ssl, 443) ->
  614. <<>>;
  615. create_path_location_port(tcp, 80) ->
  616. <<>>;
  617. create_path_location_port(_, Port) ->
  618. <<":", (list_to_binary(integer_to_list(Port)))/binary>>.
  619. %% process_post should return true when the POST body could be processed
  620. %% and false when it hasn't, in which case a 500 error is sent.
  621. process_post(Req, State) ->
  622. case call(Req, State, process_post) of
  623. {halt, Req2, HandlerState} ->
  624. terminate(Req2, State#state{handler_state=HandlerState});
  625. {true, Req2, HandlerState} ->
  626. State2 = State#state{handler_state=HandlerState},
  627. next(Req2, State2, fun is_new_resource/2);
  628. {false, Req2, HandlerState} ->
  629. State2 = State#state{handler_state=HandlerState},
  630. respond(Req2, State2, 500)
  631. end.
  632. is_conflict(Req, State) ->
  633. expect(Req, State, is_conflict, false, fun put_resource/2, 409).
  634. put_resource(Req=#http_req{raw_path=RawPath, meta=Meta}, State) ->
  635. Req2 = Req#http_req{meta=[{put_path, RawPath}|Meta]},
  636. put_resource(Req2, State, fun is_new_resource/2).
  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. The path to the new resource
  642. %% may be different from the request path, and is stored as request metadata.
  643. %% It is always defined past this point. It can be retrieved as demonstrated:
  644. %% {PutPath, Req2} = cowboy_http_req:meta(put_path, Req)
  645. put_resource(Req, State, OnTrue) ->
  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. State2 = State#state{handler_state=HandlerState},
  653. {ContentType, Req3}
  654. = cowboy_http_req:parse_header('Content-Type', Req2),
  655. choose_content_type(Req3, State2, OnTrue, ContentType, CTA)
  656. end.
  657. %% The special content type '*' will always match. It can be used as a
  658. %% catch-all content type for accepting any kind of request content.
  659. %% Note that because it will always match, it should be the last of the
  660. %% list of content types, otherwise it'll shadow the ones following.
  661. choose_content_type(Req, State, _OnTrue, _ContentType, []) ->
  662. respond(Req, State, 415);
  663. choose_content_type(Req, State, OnTrue, ContentType, [{Accepted, Fun}|_Tail])
  664. when Accepted =:= '*' orelse Accepted =:= ContentType ->
  665. case call(Req, State, Fun) of
  666. {halt, Req2, HandlerState} ->
  667. terminate(Req2, State#state{handler_state=HandlerState});
  668. {true, Req2, HandlerState} ->
  669. State2 = State#state{handler_state=HandlerState},
  670. next(Req2, State2, OnTrue);
  671. {false, Req2, HandlerState} ->
  672. State2 = State#state{handler_state=HandlerState},
  673. respond(Req2, State2, 500)
  674. end;
  675. choose_content_type(Req, State, OnTrue, ContentType, [_Any|Tail]) ->
  676. choose_content_type(Req, State, OnTrue, ContentType, Tail).
  677. %% Whether we created a new resource, either through PUT or POST.
  678. %% This is easily testable because we would have set the Location
  679. %% header by this point if we did so.
  680. is_new_resource(Req, State) ->
  681. case cowboy_http_req:has_resp_header(<<"Location">>, Req) of
  682. true -> respond(Req, State, 201);
  683. false -> has_resp_body(Req, State)
  684. end.
  685. has_resp_body(Req, State) ->
  686. case cowboy_http_req:has_resp_body(Req) of
  687. true -> multiple_choices(Req, State);
  688. false -> respond(Req, State, 204)
  689. end.
  690. %% Set the response headers and call the callback found using
  691. %% content_types_provided/2 to obtain the request body and add
  692. %% it to the response.
  693. set_resp_body(Req=#http_req{method=Method},
  694. State=#state{content_type_a={_Type, Fun}})
  695. when Method =:= 'GET'; Method =:= 'HEAD' ->
  696. {Req2, State2} = set_resp_etag(Req, State),
  697. {LastModified, Req3, State3} = last_modified(Req2, State2),
  698. case LastModified of
  699. LastModified when is_atom(LastModified) ->
  700. Req4 = Req3;
  701. LastModified ->
  702. LastModifiedStr = httpd_util:rfc1123_date(LastModified),
  703. {ok, Req4} = cowboy_http_req:set_resp_header(
  704. <<"Last-Modified">>, LastModifiedStr, Req3)
  705. end,
  706. {Req5, State4} = set_resp_expires(Req4, State3),
  707. case call(Req5, State4, Fun) of
  708. {halt, Req6, HandlerState} ->
  709. terminate(Req6, State4#state{handler_state=HandlerState});
  710. {Body, Req6, HandlerState} ->
  711. State5 = State4#state{handler_state=HandlerState},
  712. {ok, Req7} = case Body of
  713. {stream, Len, Fun1} ->
  714. cowboy_http_req:set_resp_body_fun(Len, Fun1, Req6);
  715. _Contents ->
  716. cowboy_http_req:set_resp_body(Body, Req6)
  717. end,
  718. multiple_choices(Req7, State5)
  719. end;
  720. set_resp_body(Req, State) ->
  721. multiple_choices(Req, State).
  722. multiple_choices(Req, State) ->
  723. expect(Req, State, multiple_choices, false, 200, 300).
  724. %% Response utility functions.
  725. set_resp_etag(Req, State) ->
  726. {Etag, Req2, State2} = generate_etag(Req, State),
  727. case Etag of
  728. undefined ->
  729. {Req2, State2};
  730. Etag ->
  731. {ok, Req3} = cowboy_http_req:set_resp_header(
  732. <<"ETag">>, encode_etag(Etag), Req2),
  733. {Req3, State2}
  734. end.
  735. -spec encode_etag({strong | weak, binary()}) -> iolist().
  736. encode_etag({strong, Etag}) -> [$",Etag,$"];
  737. encode_etag({weak, Etag}) -> ["W/\"",Etag,$"].
  738. set_resp_expires(Req, State) ->
  739. {Expires, Req2, State2} = expires(Req, State),
  740. case Expires of
  741. Expires when is_atom(Expires) ->
  742. {Req2, State2};
  743. Expires ->
  744. ExpiresStr = httpd_util:rfc1123_date(Expires),
  745. {ok, Req3} = cowboy_http_req:set_resp_header(
  746. <<"Expires">>, ExpiresStr, Req2),
  747. {Req3, State2}
  748. end.
  749. %% Info retrieval. No logic.
  750. generate_etag(Req, State=#state{etag=no_call}) ->
  751. {undefined, Req, State};
  752. generate_etag(Req, State=#state{etag=undefined}) ->
  753. case call(Req, State, generate_etag) of
  754. no_call ->
  755. {undefined, Req, State#state{etag=no_call}};
  756. %% Previously the return value from the generate_etag/2 callback was set
  757. %% as the value of the ETag header in the response. Therefore the only
  758. %% valid return type was `binary()'. If a handler returns a `binary()'
  759. %% it must be mapped to the expected type or it'll always fail to
  760. %% compare equal to any entity tags present in the request headers.
  761. %% @todo Remove support for binary return values after 0.6.
  762. {Etag, Req2, HandlerState} when is_binary(Etag) ->
  763. [Etag2] = cowboy_http:entity_tag_match(Etag),
  764. {Etag2, Req2, State#state{handler_state=HandlerState, etag=Etag2}};
  765. {Etag, Req2, HandlerState} ->
  766. {Etag, Req2, State#state{handler_state=HandlerState, etag=Etag}}
  767. end;
  768. generate_etag(Req, State=#state{etag=Etag}) ->
  769. {Etag, Req, State}.
  770. last_modified(Req, State=#state{last_modified=no_call}) ->
  771. {undefined, Req, State};
  772. last_modified(Req, State=#state{last_modified=undefined}) ->
  773. case call(Req, State, last_modified) of
  774. no_call ->
  775. {undefined, Req, State#state{last_modified=no_call}};
  776. {LastModified, Req2, HandlerState} ->
  777. {LastModified, Req2, State#state{handler_state=HandlerState,
  778. last_modified=LastModified}}
  779. end;
  780. last_modified(Req, State=#state{last_modified=LastModified}) ->
  781. {LastModified, Req, State}.
  782. expires(Req, State=#state{expires=no_call}) ->
  783. {undefined, Req, State};
  784. expires(Req, State=#state{expires=undefined}) ->
  785. case call(Req, State, expires) of
  786. no_call ->
  787. {undefined, Req, State#state{expires=no_call}};
  788. {Expires, Req2, HandlerState} ->
  789. {Expires, Req2, State#state{handler_state=HandlerState,
  790. expires=Expires}}
  791. end;
  792. expires(Req, State=#state{expires=Expires}) ->
  793. {Expires, Req, State}.
  794. %% REST primitives.
  795. expect(Req, State, Callback, Expected, OnTrue, OnFalse) ->
  796. case call(Req, State, Callback) of
  797. no_call ->
  798. next(Req, State, OnTrue);
  799. {halt, Req2, HandlerState} ->
  800. terminate(Req2, State#state{handler_state=HandlerState});
  801. {Expected, Req2, HandlerState} ->
  802. next(Req2, State#state{handler_state=HandlerState}, OnTrue);
  803. {_Unexpected, Req2, HandlerState} ->
  804. next(Req2, State#state{handler_state=HandlerState}, OnFalse)
  805. end.
  806. call(Req, #state{handler=Handler, handler_state=HandlerState}, Fun) ->
  807. case erlang:function_exported(Handler, Fun, 2) of
  808. true -> Handler:Fun(Req, HandlerState);
  809. false -> no_call
  810. end.
  811. next(Req, State, Next) when is_function(Next) ->
  812. Next(Req, State);
  813. next(Req, State, StatusCode) when is_integer(StatusCode) ->
  814. respond(Req, State, StatusCode).
  815. %% @todo Allow some sort of callback for custom error pages.
  816. respond(Req, State, StatusCode) ->
  817. {ok, Req2} = cowboy_http_req:reply(StatusCode, Req),
  818. terminate(Req2, State).
  819. terminate(Req, #state{handler=Handler, handler_state=HandlerState}) ->
  820. case erlang:function_exported(Handler, rest_terminate, 2) of
  821. true -> ok = Handler:rest_terminate(
  822. Req#http_req{resp_state=locked}, HandlerState);
  823. false -> ok
  824. end,
  825. {ok, Req}.