cowboy_rest.erl 38 KB

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