cowboy_rest.erl 42 KB

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