cowboy_rest.erl 40 KB

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