cowboy_rest.erl (59826B)
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 15 %% Originally based on the Webmachine Diagram from Alan Dean and 16 %% Justin Sheehy. 17 -module(cowboy_rest). 18 -behaviour(cowboy_sub_protocol). 19 20 -export([upgrade/4]). 21 -export([upgrade/5]). 22 23 -type switch_handler() :: {switch_handler, module()} 24 | {switch_handler, module(), any()}. 25 26 %% Common handler callbacks. 27 28 -callback init(Req, any()) 29 -> {ok | module(), Req, any()} 30 | {module(), Req, any(), any()} 31 when Req::cowboy_req:req(). 32 33 -callback terminate(any(), cowboy_req:req(), any()) -> ok. 34 -optional_callbacks([terminate/3]). 35 36 %% REST handler callbacks. 37 38 -callback allowed_methods(Req, State) 39 -> {[binary()], Req, State} 40 | {stop, Req, State} 41 | {switch_handler(), Req, State} 42 when Req::cowboy_req:req(), State::any(). 43 -optional_callbacks([allowed_methods/2]). 44 45 -callback allow_missing_post(Req, State) 46 -> {boolean(), Req, State} 47 | {stop, Req, State} 48 | {switch_handler(), Req, State} 49 when Req::cowboy_req:req(), State::any(). 50 -optional_callbacks([allow_missing_post/2]). 51 52 -callback charsets_provided(Req, State) 53 -> {[binary()], Req, State} 54 | {stop, Req, State} 55 | {switch_handler(), Req, State} 56 when Req::cowboy_req:req(), State::any(). 57 -optional_callbacks([charsets_provided/2]). 58 59 -callback content_types_accepted(Req, State) 60 -> {[{'*' | binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State} 61 | {stop, Req, State} 62 | {switch_handler(), Req, State} 63 when Req::cowboy_req:req(), State::any(). 64 -optional_callbacks([content_types_accepted/2]). 65 66 -callback content_types_provided(Req, State) 67 -> {[{binary() | {binary(), binary(), '*' | [{binary(), binary()}]}, atom()}], Req, State} 68 | {stop, Req, State} 69 | {switch_handler(), Req, State} 70 when Req::cowboy_req:req(), State::any(). 71 -optional_callbacks([content_types_provided/2]). 72 73 -callback delete_completed(Req, State) 74 -> {boolean(), Req, State} 75 | {stop, Req, State} 76 | {switch_handler(), Req, State} 77 when Req::cowboy_req:req(), State::any(). 78 -optional_callbacks([delete_completed/2]). 79 80 -callback delete_resource(Req, State) 81 -> {boolean(), Req, State} 82 | {stop, Req, State} 83 | {switch_handler(), Req, State} 84 when Req::cowboy_req:req(), State::any(). 85 -optional_callbacks([delete_resource/2]). 86 87 -callback expires(Req, State) 88 -> {calendar:datetime() | binary() | undefined, Req, State} 89 when Req::cowboy_req:req(), State::any(). 90 -optional_callbacks([expires/2]). 91 92 -callback forbidden(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([forbidden/2]). 98 99 -callback generate_etag(Req, State) 100 -> {binary() | {weak | strong, binary()}, Req, State} 101 when Req::cowboy_req:req(), State::any(). 102 -optional_callbacks([generate_etag/2]). 103 104 -callback is_authorized(Req, State) 105 -> {true | {false, iodata()}, Req, State} 106 | {stop, Req, State} 107 | {switch_handler(), Req, State} 108 when Req::cowboy_req:req(), State::any(). 109 -optional_callbacks([is_authorized/2]). 110 111 -callback is_conflict(Req, State) 112 -> {boolean(), Req, State} 113 | {stop, Req, State} 114 | {switch_handler(), Req, State} 115 when Req::cowboy_req:req(), State::any(). 116 -optional_callbacks([is_conflict/2]). 117 118 -callback known_methods(Req, State) 119 -> {[binary()], Req, State} 120 | {stop, Req, State} 121 | {switch_handler(), Req, State} 122 when Req::cowboy_req:req(), State::any(). 123 -optional_callbacks([known_methods/2]). 124 125 -callback languages_provided(Req, State) 126 -> {[binary()], Req, State} 127 | {stop, Req, State} 128 | {switch_handler(), Req, State} 129 when Req::cowboy_req:req(), State::any(). 130 -optional_callbacks([languages_provided/2]). 131 132 -callback last_modified(Req, State) 133 -> {calendar:datetime(), Req, State} 134 when Req::cowboy_req:req(), State::any(). 135 -optional_callbacks([last_modified/2]). 136 137 -callback malformed_request(Req, State) 138 -> {boolean(), Req, State} 139 | {stop, Req, State} 140 | {switch_handler(), Req, State} 141 when Req::cowboy_req:req(), State::any(). 142 -optional_callbacks([malformed_request/2]). 143 144 -callback moved_permanently(Req, State) 145 -> {{true, iodata()} | false, Req, State} 146 | {stop, Req, State} 147 | {switch_handler(), Req, State} 148 when Req::cowboy_req:req(), State::any(). 149 -optional_callbacks([moved_permanently/2]). 150 151 -callback moved_temporarily(Req, State) 152 -> {{true, iodata()} | false, Req, State} 153 | {stop, Req, State} 154 | {switch_handler(), Req, State} 155 when Req::cowboy_req:req(), State::any(). 156 -optional_callbacks([moved_temporarily/2]). 157 158 -callback multiple_choices(Req, State) 159 -> {boolean(), Req, State} 160 | {stop, Req, State} 161 | {switch_handler(), Req, State} 162 when Req::cowboy_req:req(), State::any(). 163 -optional_callbacks([multiple_choices/2]). 164 165 -callback options(Req, State) 166 -> {ok, Req, State} 167 | {stop, Req, State} 168 | {switch_handler(), Req, State} 169 when Req::cowboy_req:req(), State::any(). 170 -optional_callbacks([options/2]). 171 172 -callback previously_existed(Req, State) 173 -> {boolean(), Req, State} 174 | {stop, Req, State} 175 | {switch_handler(), Req, State} 176 when Req::cowboy_req:req(), State::any(). 177 -optional_callbacks([previously_existed/2]). 178 179 -callback range_satisfiable(Req, State) 180 -> {boolean() | {false, non_neg_integer() | iodata()}, Req, State} 181 | {stop, Req, State} 182 | {switch_handler(), Req, State} 183 when Req::cowboy_req:req(), State::any(). 184 -optional_callbacks([range_satisfiable/2]). 185 186 -callback ranges_provided(Req, State) 187 -> {[{binary(), atom()}], Req, State} 188 | {stop, Req, State} 189 | {switch_handler(), Req, State} 190 when Req::cowboy_req:req(), State::any(). 191 -optional_callbacks([ranges_provided/2]). 192 193 -callback rate_limited(Req, State) 194 -> {{true, non_neg_integer() | calendar:datetime()} | false, Req, State} 195 | {stop, Req, State} 196 | {switch_handler(), Req, State} 197 when Req::cowboy_req:req(), State::any(). 198 -optional_callbacks([rate_limited/2]). 199 200 -callback resource_exists(Req, State) 201 -> {boolean(), Req, State} 202 | {stop, Req, State} 203 | {switch_handler(), Req, State} 204 when Req::cowboy_req:req(), State::any(). 205 -optional_callbacks([resource_exists/2]). 206 207 -callback service_available(Req, State) 208 -> {boolean(), Req, State} 209 | {stop, Req, State} 210 | {switch_handler(), Req, State} 211 when Req::cowboy_req:req(), State::any(). 212 -optional_callbacks([service_available/2]). 213 214 -callback uri_too_long(Req, State) 215 -> {boolean(), Req, State} 216 | {stop, Req, State} 217 | {switch_handler(), Req, State} 218 when Req::cowboy_req:req(), State::any(). 219 -optional_callbacks([uri_too_long/2]). 220 221 -callback valid_content_headers(Req, State) 222 -> {boolean(), Req, State} 223 | {stop, Req, State} 224 | {switch_handler(), Req, State} 225 when Req::cowboy_req:req(), State::any(). 226 -optional_callbacks([valid_content_headers/2]). 227 228 -callback valid_entity_length(Req, State) 229 -> {boolean(), Req, State} 230 | {stop, Req, State} 231 | {switch_handler(), Req, State} 232 when Req::cowboy_req:req(), State::any(). 233 -optional_callbacks([valid_entity_length/2]). 234 235 -callback variances(Req, State) 236 -> {[binary()], Req, State} 237 when Req::cowboy_req:req(), State::any(). 238 -optional_callbacks([variances/2]). 239 240 %% End of REST callbacks. Whew! 241 242 -record(state, { 243 method = undefined :: binary(), 244 245 %% Handler. 246 handler :: atom(), 247 handler_state :: any(), 248 249 %% Allowed methods. Only used for OPTIONS requests. 250 allowed_methods :: [binary()] | undefined, 251 252 %% Media type. 253 content_types_p = [] :: 254 [{binary() | {binary(), binary(), [{binary(), binary()}] | '*'}, 255 atom()}], 256 content_type_a :: undefined 257 | {binary() | {binary(), binary(), [{binary(), binary()}] | '*'}, 258 atom()}, 259 260 %% Language. 261 languages_p = [] :: [binary()], 262 language_a :: undefined | binary(), 263 264 %% Charset. 265 charsets_p = undefined :: undefined | [binary()], 266 charset_a :: undefined | binary(), 267 268 %% Range units. 269 ranges_a = [] :: [{binary(), atom()}], 270 271 %% Whether the resource exists. 272 exists = false :: boolean(), 273 274 %% Cached resource calls. 275 etag :: undefined | no_call | {strong | weak, binary()}, 276 last_modified :: undefined | no_call | calendar:datetime(), 277 expires :: undefined | no_call | calendar:datetime() | binary() 278 }). 279 280 -spec upgrade(Req, Env, module(), any()) 281 -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). 282 upgrade(Req0, Env, Handler, HandlerState0) -> 283 Method = cowboy_req:method(Req0), 284 case service_available(Req0, #state{method=Method, 285 handler=Handler, handler_state=HandlerState0}) of 286 {ok, Req, Result} -> 287 {ok, Req, Env#{result => Result}}; 288 {Mod, Req, HandlerState} -> 289 Mod:upgrade(Req, Env, Handler, HandlerState); 290 {Mod, Req, HandlerState, Opts} -> 291 Mod:upgrade(Req, Env, Handler, HandlerState, Opts) 292 end. 293 294 -spec upgrade(Req, Env, module(), any(), any()) 295 -> {ok, Req, Env} when Req::cowboy_req:req(), Env::cowboy_middleware:env(). 296 %% cowboy_rest takes no options. 297 upgrade(Req, Env, Handler, HandlerState, _Opts) -> 298 upgrade(Req, Env, Handler, HandlerState). 299 300 service_available(Req, State) -> 301 expect(Req, State, service_available, true, fun known_methods/2, 503). 302 303 %% known_methods/2 should return a list of binary methods. 304 known_methods(Req, State=#state{method=Method}) -> 305 case call(Req, State, known_methods) of 306 no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">>; 307 Method =:= <<"POST">>; Method =:= <<"PUT">>; 308 Method =:= <<"PATCH">>; Method =:= <<"DELETE">>; 309 Method =:= <<"OPTIONS">> -> 310 next(Req, State, fun uri_too_long/2); 311 no_call -> 312 next(Req, State, 501); 313 {stop, Req2, State2} -> 314 terminate(Req2, State2); 315 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 316 switch_handler(Switch, Req2, State2); 317 {List, Req2, State2} -> 318 case lists:member(Method, List) of 319 true -> next(Req2, State2, fun uri_too_long/2); 320 false -> next(Req2, State2, 501) 321 end 322 end. 323 324 uri_too_long(Req, State) -> 325 expect(Req, State, uri_too_long, false, fun allowed_methods/2, 414). 326 327 %% allowed_methods/2 should return a list of binary methods. 328 allowed_methods(Req, State=#state{method=Method}) -> 329 case call(Req, State, allowed_methods) of 330 no_call when Method =:= <<"HEAD">>; Method =:= <<"GET">> -> 331 next(Req, State, fun malformed_request/2); 332 no_call when Method =:= <<"OPTIONS">> -> 333 next(Req, State#state{allowed_methods= 334 [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]}, 335 fun malformed_request/2); 336 no_call -> 337 method_not_allowed(Req, State, 338 [<<"HEAD">>, <<"GET">>, <<"OPTIONS">>]); 339 {stop, Req2, State2} -> 340 terminate(Req2, State2); 341 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 342 switch_handler(Switch, Req2, State2); 343 {List, Req2, State2} -> 344 case lists:member(Method, List) of 345 true when Method =:= <<"OPTIONS">> -> 346 next(Req2, State2#state{allowed_methods=List}, 347 fun malformed_request/2); 348 true -> 349 next(Req2, State2, fun malformed_request/2); 350 false -> 351 method_not_allowed(Req2, State2, List) 352 end 353 end. 354 355 method_not_allowed(Req, State, []) -> 356 Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req), 357 respond(Req2, State, 405); 358 method_not_allowed(Req, State, Methods) -> 359 << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>, 360 Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), 361 respond(Req2, State, 405). 362 363 malformed_request(Req, State) -> 364 expect(Req, State, malformed_request, false, fun is_authorized/2, 400). 365 366 %% is_authorized/2 should return true or {false, WwwAuthenticateHeader}. 367 is_authorized(Req, State) -> 368 case call(Req, State, is_authorized) of 369 no_call -> 370 forbidden(Req, State); 371 {stop, Req2, State2} -> 372 terminate(Req2, State2); 373 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 374 switch_handler(Switch, Req2, State2); 375 {true, Req2, State2} -> 376 forbidden(Req2, State2); 377 {{false, AuthHead}, Req2, State2} -> 378 Req3 = cowboy_req:set_resp_header( 379 <<"www-authenticate">>, AuthHead, Req2), 380 respond(Req3, State2, 401) 381 end. 382 383 forbidden(Req, State) -> 384 expect(Req, State, forbidden, false, fun rate_limited/2, 403). 385 386 rate_limited(Req, State) -> 387 case call(Req, State, rate_limited) of 388 no_call -> 389 valid_content_headers(Req, State); 390 {stop, Req2, State2} -> 391 terminate(Req2, State2); 392 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 393 switch_handler(Switch, Req2, State2); 394 {false, Req2, State2} -> 395 valid_content_headers(Req2, State2); 396 {{true, RetryAfter0}, Req2, State2} -> 397 RetryAfter = if 398 is_integer(RetryAfter0), RetryAfter0 >= 0 -> 399 integer_to_binary(RetryAfter0); 400 is_tuple(RetryAfter0) -> 401 cowboy_clock:rfc1123(RetryAfter0) 402 end, 403 Req3 = cowboy_req:set_resp_header(<<"retry-after">>, RetryAfter, Req2), 404 respond(Req3, State2, 429) 405 end. 406 407 valid_content_headers(Req, State) -> 408 expect(Req, State, valid_content_headers, true, 409 fun valid_entity_length/2, 501). 410 411 valid_entity_length(Req, State) -> 412 expect(Req, State, valid_entity_length, true, fun options/2, 413). 413 414 %% If you need to add additional headers to the response at this point, 415 %% you should do it directly in the options/2 call using set_resp_headers. 416 options(Req, State=#state{allowed_methods=Methods, method= <<"OPTIONS">>}) -> 417 case call(Req, State, options) of 418 no_call when Methods =:= [] -> 419 Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req), 420 respond(Req2, State, 200); 421 no_call -> 422 << ", ", Allow/binary >> 423 = << << ", ", M/binary >> || M <- Methods >>, 424 Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), 425 respond(Req2, State, 200); 426 {stop, Req2, State2} -> 427 terminate(Req2, State2); 428 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 429 switch_handler(Switch, Req2, State2); 430 {ok, Req2, State2} -> 431 respond(Req2, State2, 200) 432 end; 433 options(Req, State) -> 434 content_types_provided(Req, State). 435 436 %% content_types_provided/2 should return a list of content types and their 437 %% associated callback function as a tuple: {{Type, SubType, Params}, Fun}. 438 %% Type and SubType are the media type as binary. Params is a list of 439 %% Key/Value tuple, with Key and Value a binary. Fun is the name of the 440 %% callback that will be used to return the content of the response. It is 441 %% given as an atom. 442 %% 443 %% An example of such return value would be: 444 %% {{<<"text">>, <<"html">>, []}, to_html} 445 %% 446 %% Note that it is also possible to return a binary content type that will 447 %% then be parsed by Cowboy. However note that while this may make your 448 %% resources a little more readable, this is a lot less efficient. 449 %% 450 %% An example of such return value would be: 451 %% {<<"text/html">>, to_html} 452 content_types_provided(Req, State) -> 453 case call(Req, State, content_types_provided) of 454 no_call -> 455 State2 = State#state{ 456 content_types_p=[{{<<"text">>, <<"html">>, '*'}, to_html}]}, 457 try cowboy_req:parse_header(<<"accept">>, Req) of 458 undefined -> 459 languages_provided( 460 Req#{media_type => {<<"text">>, <<"html">>, []}}, 461 State2#state{content_type_a={{<<"text">>, <<"html">>, []}, to_html}}); 462 Accept -> 463 choose_media_type(Req, State2, prioritize_accept(Accept)) 464 catch _:_ -> 465 respond(Req, State2, 400) 466 end; 467 {stop, Req2, State2} -> 468 terminate(Req2, State2); 469 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 470 switch_handler(Switch, Req2, State2); 471 {[], Req2, State2} -> 472 not_acceptable(Req2, State2); 473 {CTP, Req2, State2} -> 474 CTP2 = [normalize_content_types(P) || P <- CTP], 475 State3 = State2#state{content_types_p=CTP2}, 476 try cowboy_req:parse_header(<<"accept">>, Req2) of 477 undefined -> 478 {PMT0, _Fun} = HeadCTP = hd(CTP2), 479 %% We replace the wildcard by an empty list of parameters. 480 PMT = case PMT0 of 481 {Type, SubType, '*'} -> {Type, SubType, []}; 482 _ -> PMT0 483 end, 484 languages_provided( 485 Req2#{media_type => PMT}, 486 State3#state{content_type_a=HeadCTP}); 487 Accept -> 488 choose_media_type(Req2, State3, prioritize_accept(Accept)) 489 catch _:_ -> 490 respond(Req2, State3, 400) 491 end 492 end. 493 494 normalize_content_types({ContentType, Callback}) 495 when is_binary(ContentType) -> 496 {cow_http_hd:parse_content_type(ContentType), Callback}; 497 normalize_content_types(Normalized) -> 498 Normalized. 499 500 prioritize_accept(Accept) -> 501 lists:sort( 502 fun ({MediaTypeA, Quality, _AcceptParamsA}, 503 {MediaTypeB, Quality, _AcceptParamsB}) -> 504 %% Same quality, check precedence in more details. 505 prioritize_mediatype(MediaTypeA, MediaTypeB); 506 ({_MediaTypeA, QualityA, _AcceptParamsA}, 507 {_MediaTypeB, QualityB, _AcceptParamsB}) -> 508 %% Just compare the quality. 509 QualityA > QualityB 510 end, Accept). 511 512 %% Media ranges can be overridden by more specific media ranges or 513 %% specific media types. If more than one media range applies to a given 514 %% type, the most specific reference has precedence. 515 %% 516 %% We always choose B over A when we can't decide between the two. 517 prioritize_mediatype({TypeA, SubTypeA, ParamsA}, {TypeB, SubTypeB, ParamsB}) -> 518 case TypeB of 519 TypeA -> 520 case SubTypeB of 521 SubTypeA -> length(ParamsA) > length(ParamsB); 522 <<"*">> -> true; 523 _Any -> false 524 end; 525 <<"*">> -> true; 526 _Any -> false 527 end. 528 529 %% Ignoring the rare AcceptParams. Not sure what should be done about them. 530 choose_media_type(Req, State, []) -> 531 not_acceptable(Req, State); 532 choose_media_type(Req, State=#state{content_types_p=CTP}, 533 [MediaType|Tail]) -> 534 match_media_type(Req, State, Tail, CTP, MediaType). 535 536 match_media_type(Req, State, Accept, [], _MediaType) -> 537 choose_media_type(Req, State, Accept); 538 match_media_type(Req, State, Accept, CTP, 539 MediaType = {{<<"*">>, <<"*">>, _Params_A}, _QA, _APA}) -> 540 match_media_type_params(Req, State, Accept, CTP, MediaType); 541 match_media_type(Req, State, Accept, 542 CTP = [{{Type, SubType_P, _PP}, _Fun}|_Tail], 543 MediaType = {{Type, SubType_A, _PA}, _QA, _APA}) 544 when SubType_P =:= SubType_A; SubType_A =:= <<"*">> -> 545 match_media_type_params(Req, State, Accept, CTP, MediaType); 546 match_media_type(Req, State, Accept, [_Any|Tail], MediaType) -> 547 match_media_type(Req, State, Accept, Tail, MediaType). 548 549 match_media_type_params(Req, State, Accept, 550 [Provided = {{TP, STP, '*'}, _Fun}|Tail], 551 MediaType = {{TA, _STA, Params_A0}, _QA, _APA}) -> 552 case lists:keytake(<<"charset">>, 1, Params_A0) of 553 {value, {_, Charset}, Params_A} when TA =:= <<"text">> -> 554 %% When we match against a wildcard, the media type is text 555 %% and has a charset parameter, we call charsets_provided 556 %% and check that the charset is provided. If the callback 557 %% is not exported, we accept inconditionally but ignore 558 %% the given charset so as to not send a wrong value back. 559 case call(Req, State, charsets_provided) of 560 no_call -> 561 languages_provided(Req#{media_type => {TP, STP, Params_A0}}, 562 State#state{content_type_a=Provided}); 563 {stop, Req2, State2} -> 564 terminate(Req2, State2); 565 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 566 switch_handler(Switch, Req2, State2); 567 {CP, Req2, State2} -> 568 State3 = State2#state{charsets_p=CP}, 569 case lists:member(Charset, CP) of 570 false -> 571 match_media_type(Req2, State3, Accept, Tail, MediaType); 572 true -> 573 languages_provided(Req2#{media_type => {TP, STP, Params_A}}, 574 State3#state{content_type_a=Provided, 575 charset_a=Charset}) 576 end 577 end; 578 _ -> 579 languages_provided(Req#{media_type => {TP, STP, Params_A0}}, 580 State#state{content_type_a=Provided}) 581 end; 582 match_media_type_params(Req, State, Accept, 583 [Provided = {PMT = {TP, STP, Params_P0}, Fun}|Tail], 584 MediaType = {{_TA, _STA, Params_A}, _QA, _APA}) -> 585 case lists:sort(Params_P0) =:= lists:sort(Params_A) of 586 true when TP =:= <<"text">> -> 587 %% When a charset was provided explicitly in both the charset header 588 %% and the media types provided and the negotiation is successful, 589 %% we keep the charset and don't call charsets_provided. This only 590 %% applies to text media types, however. 591 {Charset, Params_P} = case lists:keytake(<<"charset">>, 1, Params_P0) of 592 false -> {undefined, Params_P0}; 593 {value, {_, Charset0}, Params_P1} -> {Charset0, Params_P1} 594 end, 595 languages_provided(Req#{media_type => {TP, STP, Params_P}}, 596 State#state{content_type_a={{TP, STP, Params_P}, Fun}, 597 charset_a=Charset}); 598 true -> 599 languages_provided(Req#{media_type => PMT}, 600 State#state{content_type_a=Provided}); 601 false -> 602 match_media_type(Req, State, Accept, Tail, MediaType) 603 end. 604 605 %% languages_provided should return a list of binary values indicating 606 %% which languages are accepted by the resource. 607 %% 608 %% @todo I suppose we should also ask the resource if it wants to 609 %% set a language itself or if it wants it to be automatically chosen. 610 languages_provided(Req, State) -> 611 case call(Req, State, languages_provided) of 612 no_call -> 613 charsets_provided(Req, State); 614 {stop, Req2, State2} -> 615 terminate(Req2, State2); 616 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 617 switch_handler(Switch, Req2, State2); 618 {[], Req2, State2} -> 619 not_acceptable(Req2, State2); 620 {LP, Req2, State2} -> 621 State3 = State2#state{languages_p=LP}, 622 case cowboy_req:parse_header(<<"accept-language">>, Req2) of 623 undefined -> 624 set_language(Req2, State3#state{language_a=hd(LP)}); 625 AcceptLanguage -> 626 AcceptLanguage2 = prioritize_languages(AcceptLanguage), 627 choose_language(Req2, State3, AcceptLanguage2) 628 end 629 end. 630 631 %% A language-range matches a language-tag if it exactly equals the tag, 632 %% or if it exactly equals a prefix of the tag such that the first tag 633 %% character following the prefix is "-". The special range "*", if 634 %% present in the Accept-Language field, matches every tag not matched 635 %% by any other range present in the Accept-Language field. 636 %% 637 %% @todo The last sentence probably means we should always put '*' 638 %% at the end of the list. 639 prioritize_languages(AcceptLanguages) -> 640 lists:sort( 641 fun ({_TagA, QualityA}, {_TagB, QualityB}) -> 642 QualityA > QualityB 643 end, AcceptLanguages). 644 645 choose_language(Req, State, []) -> 646 not_acceptable(Req, State); 647 choose_language(Req, State=#state{languages_p=LP}, [Language|Tail]) -> 648 match_language(Req, State, Tail, LP, Language). 649 650 match_language(Req, State, Accept, [], _Language) -> 651 choose_language(Req, State, Accept); 652 match_language(Req, State, _Accept, [Provided|_Tail], {'*', _Quality}) -> 653 set_language(Req, State#state{language_a=Provided}); 654 match_language(Req, State, _Accept, [Provided|_Tail], {Provided, _Quality}) -> 655 set_language(Req, State#state{language_a=Provided}); 656 match_language(Req, State, Accept, [Provided|Tail], 657 Language = {Tag, _Quality}) -> 658 Length = byte_size(Tag), 659 case Provided of 660 << Tag:Length/binary, $-, _Any/bits >> -> 661 set_language(Req, State#state{language_a=Provided}); 662 _Any -> 663 match_language(Req, State, Accept, Tail, Language) 664 end. 665 666 set_language(Req, State=#state{language_a=Language}) -> 667 Req2 = cowboy_req:set_resp_header(<<"content-language">>, Language, Req), 668 charsets_provided(Req2#{language => Language}, State). 669 670 %% charsets_provided should return a list of binary values indicating 671 %% which charsets are accepted by the resource. 672 %% 673 %% A charset may have been selected while negotiating the accept header. 674 %% There's no need to select one again. 675 charsets_provided(Req, State=#state{charset_a=Charset}) 676 when Charset =/= undefined -> 677 set_content_type(Req, State); 678 %% If charsets_p is defined, use it instead of calling charsets_provided 679 %% again. We also call this clause during normal execution to avoid 680 %% duplicating code. 681 charsets_provided(Req, State=#state{charsets_p=[]}) -> 682 not_acceptable(Req, State); 683 charsets_provided(Req, State=#state{charsets_p=CP}) 684 when CP =/= undefined -> 685 case cowboy_req:parse_header(<<"accept-charset">>, Req) of 686 undefined -> 687 set_content_type(Req, State#state{charset_a=hd(CP)}); 688 AcceptCharset0 -> 689 AcceptCharset = prioritize_charsets(AcceptCharset0), 690 choose_charset(Req, State, AcceptCharset) 691 end; 692 charsets_provided(Req, State) -> 693 case call(Req, State, charsets_provided) of 694 no_call -> 695 set_content_type(Req, State); 696 {stop, Req2, State2} -> 697 terminate(Req2, State2); 698 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 699 switch_handler(Switch, Req2, State2); 700 {CP, Req2, State2} -> 701 charsets_provided(Req2, State2#state{charsets_p=CP}) 702 end. 703 704 prioritize_charsets(AcceptCharsets) -> 705 lists:sort( 706 fun ({_CharsetA, QualityA}, {_CharsetB, QualityB}) -> 707 QualityA > QualityB 708 end, AcceptCharsets). 709 710 choose_charset(Req, State, []) -> 711 not_acceptable(Req, State); 712 %% A q-value of 0 means not acceptable. 713 choose_charset(Req, State, [{_, 0}|Tail]) -> 714 choose_charset(Req, State, Tail); 715 choose_charset(Req, State=#state{charsets_p=CP}, [Charset|Tail]) -> 716 match_charset(Req, State, Tail, CP, Charset). 717 718 match_charset(Req, State, Accept, [], _Charset) -> 719 choose_charset(Req, State, Accept); 720 match_charset(Req, State, _Accept, [Provided|_], {<<"*">>, _}) -> 721 set_content_type(Req, State#state{charset_a=Provided}); 722 match_charset(Req, State, _Accept, [Provided|_], {Provided, _}) -> 723 set_content_type(Req, State#state{charset_a=Provided}); 724 match_charset(Req, State, Accept, [_|Tail], Charset) -> 725 match_charset(Req, State, Accept, Tail, Charset). 726 727 set_content_type(Req, State=#state{ 728 content_type_a={{Type, SubType, Params}, _Fun}, 729 charset_a=Charset}) -> 730 ParamsBin = set_content_type_build_params(Params, []), 731 ContentType = [Type, <<"/">>, SubType, ParamsBin], 732 ContentType2 = case {Type, Charset} of 733 {<<"text">>, Charset} when Charset =/= undefined -> 734 [ContentType, <<"; charset=">>, Charset]; 735 _ -> 736 ContentType 737 end, 738 Req2 = cowboy_req:set_resp_header(<<"content-type">>, ContentType2, Req), 739 encodings_provided(Req2#{charset => Charset}, State). 740 741 set_content_type_build_params('*', []) -> 742 <<>>; 743 set_content_type_build_params([], []) -> 744 <<>>; 745 set_content_type_build_params([], Acc) -> 746 lists:reverse(Acc); 747 set_content_type_build_params([{Attr, Value}|Tail], Acc) -> 748 set_content_type_build_params(Tail, [[Attr, <<"=">>, Value], <<";">>|Acc]). 749 750 %% @todo Match for identity as we provide nothing else for now. 751 %% @todo Don't forget to set the Content-Encoding header when we reply a body 752 %% and the found encoding is something other than identity. 753 encodings_provided(Req, State) -> 754 ranges_provided(Req, State). 755 756 not_acceptable(Req, State) -> 757 respond(Req, State, 406). 758 759 ranges_provided(Req, State) -> 760 case call(Req, State, ranges_provided) of 761 no_call -> 762 variances(Req, State); 763 {stop, Req2, State2} -> 764 terminate(Req2, State2); 765 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 766 switch_handler(Switch, Req2, State2); 767 {[], Req2, State2} -> 768 Req3 = cowboy_req:set_resp_header(<<"accept-ranges">>, <<"none">>, Req2), 769 variances(Req3, State2#state{ranges_a=[]}); 770 {RP, Req2, State2} -> 771 <<", ", AcceptRanges/binary>> = <<<<", ", R/binary>> || {R, _} <- RP>>, 772 Req3 = cowboy_req:set_resp_header(<<"accept-ranges">>, AcceptRanges, Req2), 773 variances(Req3, State2#state{ranges_a=RP}) 774 end. 775 776 %% variances/2 should return a list of headers that will be added 777 %% to the Vary response header. The Accept, Accept-Language, 778 %% Accept-Charset and Accept-Encoding headers do not need to be 779 %% specified. 780 %% 781 %% @todo Do Accept-Encoding too when we handle it. 782 %% @todo Does the order matter? 783 variances(Req, State=#state{content_types_p=CTP, 784 languages_p=LP, charsets_p=CP}) -> 785 Variances = case CTP of 786 [] -> []; 787 [_] -> []; 788 [_|_] -> [<<"accept">>] 789 end, 790 Variances2 = case LP of 791 [] -> Variances; 792 [_] -> Variances; 793 [_|_] -> [<<"accept-language">>|Variances] 794 end, 795 Variances3 = case CP of 796 undefined -> Variances2; 797 [] -> Variances2; 798 [_] -> Variances2; 799 [_|_] -> [<<"accept-charset">>|Variances2] 800 end, 801 try variances(Req, State, Variances3) of 802 {Variances4, Req2, State2} -> 803 case [[<<", ">>, V] || V <- Variances4] of 804 [] -> 805 resource_exists(Req2, State2); 806 [[<<", ">>, H]|Variances5] -> 807 Req3 = cowboy_req:set_resp_header( 808 <<"vary">>, [H|Variances5], Req2), 809 resource_exists(Req3, State2) 810 end 811 catch Class:Reason:Stacktrace -> 812 error_terminate(Req, State, Class, Reason, Stacktrace) 813 end. 814 815 variances(Req, State, Variances) -> 816 case unsafe_call(Req, State, variances) of 817 no_call -> 818 {Variances, Req, State}; 819 {HandlerVariances, Req2, State2} -> 820 {Variances ++ HandlerVariances, Req2, State2} 821 end. 822 823 resource_exists(Req, State) -> 824 expect(Req, State, resource_exists, true, 825 fun if_match_exists/2, fun if_match_must_not_exist/2). 826 827 if_match_exists(Req, State) -> 828 State2 = State#state{exists=true}, 829 case cowboy_req:parse_header(<<"if-match">>, Req) of 830 undefined -> 831 if_unmodified_since_exists(Req, State2); 832 '*' -> 833 if_unmodified_since_exists(Req, State2); 834 ETagsList -> 835 if_match(Req, State2, ETagsList) 836 end. 837 838 if_match(Req, State, EtagsList) -> 839 try generate_etag(Req, State) of 840 %% Strong Etag comparison: weak Etag never matches. 841 {{weak, _}, Req2, State2} -> 842 precondition_failed(Req2, State2); 843 {Etag, Req2, State2} -> 844 case lists:member(Etag, EtagsList) of 845 true -> if_none_match_exists(Req2, State2); 846 %% Etag may be `undefined' which cannot be a member. 847 false -> precondition_failed(Req2, State2) 848 end 849 catch Class:Reason:Stacktrace -> 850 error_terminate(Req, State, Class, Reason, Stacktrace) 851 end. 852 853 if_match_must_not_exist(Req, State) -> 854 case cowboy_req:header(<<"if-match">>, Req) of 855 undefined -> is_put_to_missing_resource(Req, State); 856 _ -> precondition_failed(Req, State) 857 end. 858 859 if_unmodified_since_exists(Req, State) -> 860 try cowboy_req:parse_header(<<"if-unmodified-since">>, Req) of 861 undefined -> 862 if_none_match_exists(Req, State); 863 IfUnmodifiedSince -> 864 if_unmodified_since(Req, State, IfUnmodifiedSince) 865 catch _:_ -> 866 if_none_match_exists(Req, State) 867 end. 868 869 %% If LastModified is the atom 'no_call', we continue. 870 if_unmodified_since(Req, State, IfUnmodifiedSince) -> 871 try last_modified(Req, State) of 872 {LastModified, Req2, State2} -> 873 case LastModified > IfUnmodifiedSince of 874 true -> precondition_failed(Req2, State2); 875 false -> if_none_match_exists(Req2, State2) 876 end 877 catch Class:Reason:Stacktrace -> 878 error_terminate(Req, State, Class, Reason, Stacktrace) 879 end. 880 881 if_none_match_exists(Req, State) -> 882 case cowboy_req:parse_header(<<"if-none-match">>, Req) of 883 undefined -> 884 if_modified_since_exists(Req, State); 885 '*' -> 886 precondition_is_head_get(Req, State); 887 EtagsList -> 888 if_none_match(Req, State, EtagsList) 889 end. 890 891 if_none_match(Req, State, EtagsList) -> 892 try generate_etag(Req, State) of 893 {Etag, Req2, State2} -> 894 case Etag of 895 undefined -> 896 precondition_failed(Req2, State2); 897 Etag -> 898 case is_weak_match(Etag, EtagsList) of 899 true -> precondition_is_head_get(Req2, State2); 900 false -> method(Req2, State2) 901 end 902 end 903 catch Class:Reason:Stacktrace -> 904 error_terminate(Req, State, Class, Reason, Stacktrace) 905 end. 906 907 %% Weak Etag comparison: only check the opaque tag. 908 is_weak_match(_, []) -> 909 false; 910 is_weak_match({_, Tag}, [{_, Tag}|_]) -> 911 true; 912 is_weak_match(Etag, [_|Tail]) -> 913 is_weak_match(Etag, Tail). 914 915 precondition_is_head_get(Req, State=#state{method=Method}) 916 when Method =:= <<"HEAD">>; Method =:= <<"GET">> -> 917 not_modified(Req, State); 918 precondition_is_head_get(Req, State) -> 919 precondition_failed(Req, State). 920 921 if_modified_since_exists(Req, State) -> 922 try cowboy_req:parse_header(<<"if-modified-since">>, Req) of 923 undefined -> 924 method(Req, State); 925 IfModifiedSince -> 926 if_modified_since_now(Req, State, IfModifiedSince) 927 catch _:_ -> 928 method(Req, State) 929 end. 930 931 if_modified_since_now(Req, State, IfModifiedSince) -> 932 case IfModifiedSince > erlang:universaltime() of 933 true -> method(Req, State); 934 false -> if_modified_since(Req, State, IfModifiedSince) 935 end. 936 937 if_modified_since(Req, State, IfModifiedSince) -> 938 try last_modified(Req, State) of 939 {undefined, Req2, State2} -> 940 method(Req2, State2); 941 {LastModified, Req2, State2} -> 942 case LastModified > IfModifiedSince of 943 true -> method(Req2, State2); 944 false -> not_modified(Req2, State2) 945 end 946 catch Class:Reason:Stacktrace -> 947 error_terminate(Req, State, Class, Reason, Stacktrace) 948 end. 949 950 not_modified(Req, State) -> 951 Req2 = cowboy_req:delete_resp_header(<<"content-type">>, Req), 952 try set_resp_etag(Req2, State) of 953 {Req3, State2} -> 954 try set_resp_expires(Req3, State2) of 955 {Req4, State3} -> 956 respond(Req4, State3, 304) 957 catch Class:Reason:Stacktrace -> 958 error_terminate(Req, State2, Class, Reason, Stacktrace) 959 end 960 catch Class:Reason:Stacktrace -> 961 error_terminate(Req, State, Class, Reason, Stacktrace) 962 end. 963 964 precondition_failed(Req, State) -> 965 respond(Req, State, 412). 966 967 is_put_to_missing_resource(Req, State=#state{method= <<"PUT">>}) -> 968 moved_permanently(Req, State, fun is_conflict/2); 969 is_put_to_missing_resource(Req, State) -> 970 previously_existed(Req, State). 971 972 %% moved_permanently/2 should return either false or {true, Location} 973 %% with Location the full new URI of the resource. 974 moved_permanently(Req, State, OnFalse) -> 975 case call(Req, State, moved_permanently) of 976 {{true, Location}, Req2, State2} -> 977 Req3 = cowboy_req:set_resp_header( 978 <<"location">>, Location, Req2), 979 respond(Req3, State2, 301); 980 {false, Req2, State2} -> 981 OnFalse(Req2, State2); 982 {stop, Req2, State2} -> 983 terminate(Req2, State2); 984 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 985 switch_handler(Switch, Req2, State2); 986 no_call -> 987 OnFalse(Req, State) 988 end. 989 990 previously_existed(Req, State) -> 991 expect(Req, State, previously_existed, false, 992 fun (R, S) -> is_post_to_missing_resource(R, S, 404) end, 993 fun (R, S) -> moved_permanently(R, S, fun moved_temporarily/2) end). 994 995 %% moved_temporarily/2 should return either false or {true, Location} 996 %% with Location the full new URI of the resource. 997 moved_temporarily(Req, State) -> 998 case call(Req, State, moved_temporarily) of 999 {{true, Location}, Req2, State2} -> 1000 Req3 = cowboy_req:set_resp_header( 1001 <<"location">>, Location, Req2), 1002 respond(Req3, State2, 307); 1003 {false, Req2, State2} -> 1004 is_post_to_missing_resource(Req2, State2, 410); 1005 {stop, Req2, State2} -> 1006 terminate(Req2, State2); 1007 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 1008 switch_handler(Switch, Req2, State2); 1009 no_call -> 1010 is_post_to_missing_resource(Req, State, 410) 1011 end. 1012 1013 is_post_to_missing_resource(Req, State=#state{method= <<"POST">>}, OnFalse) -> 1014 allow_missing_post(Req, State, OnFalse); 1015 is_post_to_missing_resource(Req, State, OnFalse) -> 1016 respond(Req, State, OnFalse). 1017 1018 allow_missing_post(Req, State, OnFalse) -> 1019 expect(Req, State, allow_missing_post, true, fun accept_resource/2, OnFalse). 1020 1021 method(Req, State=#state{method= <<"DELETE">>}) -> 1022 delete_resource(Req, State); 1023 method(Req, State=#state{method= <<"PUT">>}) -> 1024 is_conflict(Req, State); 1025 method(Req, State=#state{method=Method}) 1026 when Method =:= <<"POST">>; Method =:= <<"PATCH">> -> 1027 accept_resource(Req, State); 1028 method(Req, State=#state{method=Method}) 1029 when Method =:= <<"GET">>; Method =:= <<"HEAD">> -> 1030 set_resp_body_etag(Req, State); 1031 method(Req, State) -> 1032 multiple_choices(Req, State). 1033 1034 %% delete_resource/2 should start deleting the resource and return. 1035 delete_resource(Req, State) -> 1036 expect(Req, State, delete_resource, false, 500, fun delete_completed/2). 1037 1038 %% delete_completed/2 indicates whether the resource has been deleted yet. 1039 delete_completed(Req, State) -> 1040 expect(Req, State, delete_completed, true, fun has_resp_body/2, 202). 1041 1042 is_conflict(Req, State) -> 1043 expect(Req, State, is_conflict, false, fun accept_resource/2, 409). 1044 1045 %% content_types_accepted should return a list of media types and their 1046 %% associated callback functions in the same format as content_types_provided. 1047 %% 1048 %% The callback will then be called and is expected to process the content 1049 %% pushed to the resource in the request body. 1050 %% 1051 %% content_types_accepted SHOULD return a different list 1052 %% for each HTTP method. 1053 accept_resource(Req, State) -> 1054 case call(Req, State, content_types_accepted) of 1055 no_call -> 1056 respond(Req, State, 415); 1057 {stop, Req2, State2} -> 1058 terminate(Req2, State2); 1059 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 1060 switch_handler(Switch, Req2, State2); 1061 {CTA, Req2, State2} -> 1062 CTA2 = [normalize_content_types(P) || P <- CTA], 1063 try cowboy_req:parse_header(<<"content-type">>, Req2) of 1064 %% We do not match against the boundary parameter for multipart. 1065 {Type = <<"multipart">>, SubType, Params} -> 1066 ContentType = {Type, SubType, lists:keydelete(<<"boundary">>, 1, Params)}, 1067 choose_content_type(Req2, State2, ContentType, CTA2); 1068 ContentType -> 1069 choose_content_type(Req2, State2, ContentType, CTA2) 1070 catch _:_ -> 1071 respond(Req2, State2, 415) 1072 end 1073 end. 1074 1075 %% The special content type '*' will always match. It can be used as a 1076 %% catch-all content type for accepting any kind of request content. 1077 %% Note that because it will always match, it should be the last of the 1078 %% list of content types, otherwise it'll shadow the ones following. 1079 choose_content_type(Req, State, _ContentType, []) -> 1080 respond(Req, State, 415); 1081 choose_content_type(Req, State, ContentType, [{Accepted, Fun}|_Tail]) 1082 when Accepted =:= '*'; Accepted =:= ContentType -> 1083 process_content_type(Req, State, Fun); 1084 %% The special parameter '*' will always match any kind of content type 1085 %% parameters. 1086 %% Note that because it will always match, it should be the last of the 1087 %% list for specific content type, otherwise it'll shadow the ones following. 1088 choose_content_type(Req, State, {Type, SubType, Param}, 1089 [{{Type, SubType, AcceptedParam}, Fun}|_Tail]) 1090 when AcceptedParam =:= '*'; AcceptedParam =:= Param -> 1091 process_content_type(Req, State, Fun); 1092 choose_content_type(Req, State, ContentType, [_Any|Tail]) -> 1093 choose_content_type(Req, State, ContentType, Tail). 1094 1095 process_content_type(Req, State=#state{method=Method, exists=Exists}, Fun) -> 1096 try case call(Req, State, Fun) of 1097 {stop, Req2, State2} -> 1098 terminate(Req2, State2); 1099 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 1100 switch_handler(Switch, Req2, State2); 1101 {true, Req2, State2} when Exists -> 1102 next(Req2, State2, fun has_resp_body/2); 1103 {true, Req2, State2} -> 1104 next(Req2, State2, fun maybe_created/2); 1105 {false, Req2, State2} -> 1106 respond(Req2, State2, 400); 1107 {{created, ResURL}, Req2, State2} when Method =:= <<"POST">> -> 1108 Req3 = cowboy_req:set_resp_header( 1109 <<"location">>, ResURL, Req2), 1110 respond(Req3, State2, 201); 1111 {{see_other, ResURL}, Req2, State2} when Method =:= <<"POST">> -> 1112 Req3 = cowboy_req:set_resp_header( 1113 <<"location">>, ResURL, Req2), 1114 respond(Req3, State2, 303); 1115 {{true, ResURL}, Req2, State2} when Method =:= <<"POST">> -> 1116 Req3 = cowboy_req:set_resp_header( 1117 <<"location">>, ResURL, Req2), 1118 if 1119 Exists -> respond(Req3, State2, 303); 1120 true -> respond(Req3, State2, 201) 1121 end 1122 end catch Class:Reason = {case_clause, no_call}:Stacktrace -> 1123 error_terminate(Req, State, Class, Reason, Stacktrace) 1124 end. 1125 1126 %% If PUT was used then the resource has been created at the current URL. 1127 %% Otherwise, if a location header has been set then the resource has been 1128 %% created at a new URL. If not, send a 200 or 204 as expected from a 1129 %% POST or PATCH request. 1130 maybe_created(Req, State=#state{method= <<"PUT">>}) -> 1131 respond(Req, State, 201); 1132 maybe_created(Req, State) -> 1133 case cowboy_req:has_resp_header(<<"location">>, Req) of 1134 true -> respond(Req, State, 201); 1135 false -> has_resp_body(Req, State) 1136 end. 1137 1138 has_resp_body(Req, State) -> 1139 case cowboy_req:has_resp_body(Req) of 1140 true -> multiple_choices(Req, State); 1141 false -> respond(Req, State, 204) 1142 end. 1143 1144 %% Set the Etag header if any for the response provided. 1145 set_resp_body_etag(Req, State) -> 1146 try set_resp_etag(Req, State) of 1147 {Req2, State2} -> 1148 set_resp_body_last_modified(Req2, State2) 1149 catch Class:Reason:Stacktrace -> 1150 error_terminate(Req, State, Class, Reason, Stacktrace) 1151 end. 1152 1153 %% Set the Last-Modified header if any for the response provided. 1154 set_resp_body_last_modified(Req, State) -> 1155 try last_modified(Req, State) of 1156 {LastModified, Req2, State2} -> 1157 case LastModified of 1158 LastModified when is_atom(LastModified) -> 1159 set_resp_body_expires(Req2, State2); 1160 LastModified -> 1161 LastModifiedBin = cowboy_clock:rfc1123(LastModified), 1162 Req3 = cowboy_req:set_resp_header( 1163 <<"last-modified">>, LastModifiedBin, Req2), 1164 set_resp_body_expires(Req3, State2) 1165 end 1166 catch Class:Reason:Stacktrace -> 1167 error_terminate(Req, State, Class, Reason, Stacktrace) 1168 end. 1169 1170 %% Set the Expires header if any for the response provided. 1171 set_resp_body_expires(Req, State) -> 1172 try set_resp_expires(Req, State) of 1173 {Req2, State2} -> 1174 if_range(Req2, State2) 1175 catch Class:Reason:Stacktrace -> 1176 error_terminate(Req, State, Class, Reason, Stacktrace) 1177 end. 1178 1179 %% When both the if-range and range headers are set, we perform 1180 %% a strong comparison. If it fails, we send a full response. 1181 if_range(Req=#{headers := #{<<"if-range">> := _, <<"range">> := _}}, 1182 State=#state{etag=Etag}) -> 1183 try cowboy_req:parse_header(<<"if-range">>, Req) of 1184 %% Strong etag comparison is an exact match with the generate_etag result. 1185 Etag={strong, _} -> 1186 range(Req, State); 1187 %% We cannot do a strong date comparison because we have 1188 %% no way of knowing whether the representation changed 1189 %% twice during the second covered by the presented 1190 %% validator. (RFC7232 2.2.2) 1191 _ -> 1192 set_resp_body(Req, State) 1193 catch _:_ -> 1194 set_resp_body(Req, State) 1195 end; 1196 if_range(Req, State) -> 1197 range(Req, State). 1198 1199 range(Req, State=#state{ranges_a=[]}) -> 1200 set_resp_body(Req, State); 1201 range(Req, State) -> 1202 try cowboy_req:parse_header(<<"range">>, Req) of 1203 undefined -> 1204 set_resp_body(Req, State); 1205 %% @todo Maybe change parse_header to return <<"bytes">> in 3.0. 1206 {bytes, BytesRange} -> 1207 choose_range(Req, State, {<<"bytes">>, BytesRange}); 1208 Range -> 1209 choose_range(Req, State, Range) 1210 catch _:_ -> 1211 %% We send a 416 response back when we can't parse the 1212 %% range header at all. I'm not sure this is the right 1213 %% way to go but at least this can help clients identify 1214 %% what went wrong when their range requests never work. 1215 range_not_satisfiable(Req, State, undefined) 1216 end. 1217 1218 choose_range(Req, State=#state{ranges_a=RangesAccepted}, Range={RangeUnit, _}) -> 1219 case lists:keyfind(RangeUnit, 1, RangesAccepted) of 1220 {_, Callback} -> 1221 %% We pass the selected range onward in the Req. 1222 range_satisfiable(Req#{range => Range}, State, Callback); 1223 false -> 1224 set_resp_body(Req, State) 1225 end. 1226 1227 range_satisfiable(Req, State, Callback) -> 1228 case call(Req, State, range_satisfiable) of 1229 no_call -> 1230 set_ranged_body(Req, State, Callback); 1231 {stop, Req2, State2} -> 1232 terminate(Req2, State2); 1233 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 1234 switch_handler(Switch, Req2, State2); 1235 {true, Req2, State2} -> 1236 set_ranged_body(Req2, State2, Callback); 1237 {false, Req2, State2} -> 1238 range_not_satisfiable(Req2, State2, undefined); 1239 {{false, Int}, Req2, State2} when is_integer(Int) -> 1240 range_not_satisfiable(Req2, State2, [<<"*/">>, integer_to_binary(Int)]); 1241 {{false, Iodata}, Req2, State2} when is_binary(Iodata); is_list(Iodata) -> 1242 range_not_satisfiable(Req2, State2, Iodata) 1243 end. 1244 1245 %% When the callback selected is 'auto' and the range unit 1246 %% is bytes, we call the normal provide callback and split 1247 %% the content automatically. 1248 set_ranged_body(Req=#{range := {<<"bytes">>, _}}, State, auto) -> 1249 set_ranged_body_auto(Req, State); 1250 set_ranged_body(Req, State, Callback) -> 1251 set_ranged_body_callback(Req, State, Callback). 1252 1253 set_ranged_body_auto(Req, State=#state{handler=Handler, content_type_a={_, Callback}}) -> 1254 try case call(Req, State, Callback) of 1255 {stop, Req2, State2} -> 1256 terminate(Req2, State2); 1257 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 1258 switch_handler(Switch, Req2, State2); 1259 {Body, Req2, State2} -> 1260 maybe_set_ranged_body_auto(Req2, State2, Body) 1261 end catch Class:{case_clause, no_call}:Stacktrace -> 1262 error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}}, 1263 'A callback specified in content_types_provided/2 is not exported.'}, 1264 Stacktrace) 1265 end. 1266 1267 maybe_set_ranged_body_auto(Req=#{range := {_, Ranges}}, State, Body) -> 1268 Size = case Body of 1269 {sendfile, _, Bytes, _} -> Bytes; 1270 _ -> iolist_size(Body) 1271 end, 1272 Checks = [case Range of 1273 {From, infinity} -> From < Size; 1274 {From, To} -> (From < Size) andalso (From =< To) andalso (To =< Size); 1275 Neg -> (Neg =/= 0) andalso (-Neg < Size) 1276 end || Range <- Ranges], 1277 case lists:usort(Checks) of 1278 [true] -> set_ranged_body_auto(Req, State, Body); 1279 _ -> range_not_satisfiable(Req, State, [<<"*/">>, integer_to_binary(Size)]) 1280 end. 1281 1282 %% We might also want to have some checks about range order, 1283 %% number of ranges, and perhaps also join ranges that are 1284 %% too close into one contiguous range. Some of these can 1285 %% be done before calling the ProvideCallback. 1286 1287 set_ranged_body_auto(Req=#{range := {_, Ranges}}, State, Body) -> 1288 Parts = [ranged_partition(Range, Body) || Range <- Ranges], 1289 case Parts of 1290 [OnePart] -> set_one_ranged_body(Req, State, OnePart); 1291 _ when is_tuple(Body) -> send_multipart_ranged_body(Req, State, Parts); 1292 _ -> set_multipart_ranged_body(Req, State, Parts) 1293 end. 1294 1295 ranged_partition(Range, {sendfile, Offset0, Bytes0, Path}) -> 1296 {From, To, Offset, Bytes} = case Range of 1297 {From0, infinity} -> {From0, Bytes0 - 1, Offset0 + From0, Bytes0 - From0}; 1298 {From0, To0} -> {From0, To0, Offset0 + From0, 1 + To0 - From0}; 1299 Neg -> {Bytes0 + Neg, Bytes0 - 1, Offset0 + Bytes0 + Neg, -Neg} 1300 end, 1301 {{From, To, Bytes0}, {sendfile, Offset, Bytes, Path}}; 1302 ranged_partition(Range, Data0) -> 1303 Total = iolist_size(Data0), 1304 {From, To, Data} = case Range of 1305 {From0, infinity} -> 1306 {_, Data1} = cow_iolists:split(From0, Data0), 1307 {From0, Total - 1, Data1}; 1308 {From0, To0} -> 1309 {_, Data1} = cow_iolists:split(From0, Data0), 1310 {Data2, _} = cow_iolists:split(To0 - From0 + 1, Data1), 1311 {From0, To0, Data2}; 1312 Neg -> 1313 {_, Data1} = cow_iolists:split(Total + Neg, Data0), 1314 {Total + Neg, Total - 1, Data1} 1315 end, 1316 {{From, To, Total}, Data}. 1317 1318 -ifdef(TEST). 1319 ranged_partition_test_() -> 1320 Tests = [ 1321 %% Sendfile with open-ended range. 1322 {{0, infinity}, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}}, 1323 {{6, infinity}, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}}, 1324 {{11, infinity}, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}}, 1325 %% Sendfile with open-ended range. Sendfile tuple has an offset originally. 1326 {{0, infinity}, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}}, 1327 {{6, infinity}, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}}, 1328 {{11, infinity}, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}}, 1329 %% Sendfile with a specific range. 1330 {{0, 11}, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}}, 1331 {{6, 11}, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}}, 1332 {{11, 11}, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}}, 1333 {{1, 10}, {sendfile, 0, 12, "t"}, {{1, 10, 12}, {sendfile, 1, 10, "t"}}}, 1334 %% Sendfile with a specific range. Sendfile tuple has an offset originally. 1335 {{0, 11}, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}}, 1336 {{6, 11}, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}}, 1337 {{11, 11}, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}}, 1338 {{1, 10}, {sendfile, 3, 12, "t"}, {{1, 10, 12}, {sendfile, 4, 10, "t"}}}, 1339 %% Sendfile with negative range. 1340 {-12, {sendfile, 0, 12, "t"}, {{0, 11, 12}, {sendfile, 0, 12, "t"}}}, 1341 {-6, {sendfile, 0, 12, "t"}, {{6, 11, 12}, {sendfile, 6, 6, "t"}}}, 1342 {-1, {sendfile, 0, 12, "t"}, {{11, 11, 12}, {sendfile, 11, 1, "t"}}}, 1343 %% Sendfile with negative range. Sendfile tuple has an offset originally. 1344 {-12, {sendfile, 3, 12, "t"}, {{0, 11, 12}, {sendfile, 3, 12, "t"}}}, 1345 {-6, {sendfile, 3, 12, "t"}, {{6, 11, 12}, {sendfile, 9, 6, "t"}}}, 1346 {-1, {sendfile, 3, 12, "t"}, {{11, 11, 12}, {sendfile, 14, 1, "t"}}}, 1347 %% Iodata with open-ended range. 1348 {{0, infinity}, <<"Hello world!">>, {{0, 11, 12}, <<"Hello world!">>}}, 1349 {{6, infinity}, <<"Hello world!">>, {{6, 11, 12}, <<"world!">>}}, 1350 {{11, infinity}, <<"Hello world!">>, {{11, 11, 12}, <<"!">>}}, 1351 %% Iodata with a specific range. The resulting data is 1352 %% wrapped in a list because of how cow_iolists:split/2 works. 1353 {{0, 11}, <<"Hello world!">>, {{0, 11, 12}, [<<"Hello world!">>]}}, 1354 {{6, 11}, <<"Hello world!">>, {{6, 11, 12}, [<<"world!">>]}}, 1355 {{11, 11}, <<"Hello world!">>, {{11, 11, 12}, [<<"!">>]}}, 1356 {{1, 10}, <<"Hello world!">>, {{1, 10, 12}, [<<"ello world">>]}}, 1357 %% Iodata with negative range. 1358 {-12, <<"Hello world!">>, {{0, 11, 12}, <<"Hello world!">>}}, 1359 {-6, <<"Hello world!">>, {{6, 11, 12}, <<"world!">>}}, 1360 {-1, <<"Hello world!">>, {{11, 11, 12}, <<"!">>}} 1361 ], 1362 [{iolist_to_binary(io_lib:format("range ~p data ~p", [VR, VD])), 1363 fun() -> R = ranged_partition(VR, VD) end} || {VR, VD, R} <- Tests]. 1364 -endif. 1365 1366 set_ranged_body_callback(Req, State=#state{handler=Handler}, Callback) -> 1367 try case call(Req, State, Callback) of 1368 {stop, Req2, State2} -> 1369 terminate(Req2, State2); 1370 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 1371 switch_handler(Switch, Req2, State2); 1372 %% When we receive a single range, we send it directly. 1373 {[OneRange], Req2, State2} -> 1374 set_one_ranged_body(Req2, State2, OneRange); 1375 %% When we receive multiple ranges we have to send them as multipart/byteranges. 1376 %% This also applies to non-bytes units. (RFC7233 A) If users don't want to use 1377 %% this for non-bytes units they can always return a single range with a binary 1378 %% content-range information. 1379 {Ranges, Req2, State2} when length(Ranges) > 1 -> 1380 %% We have to check whether there are sendfile tuples in the 1381 %% ranges to be sent. If there are we must use stream_reply. 1382 HasSendfile = [] =/= [true || {_, {sendfile, _, _, _}} <- Ranges], 1383 case HasSendfile of 1384 true -> send_multipart_ranged_body(Req2, State2, Ranges); 1385 false -> set_multipart_ranged_body(Req2, State2, Ranges) 1386 end 1387 end catch Class:{case_clause, no_call}:Stacktrace -> 1388 error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}}, 1389 'A callback specified in ranges_provided/2 is not exported.'}, 1390 Stacktrace) 1391 end. 1392 1393 set_one_ranged_body(Req0, State, OneRange) -> 1394 {ContentRange, Body} = prepare_range(Req0, OneRange), 1395 Req1 = cowboy_req:set_resp_header(<<"content-range">>, ContentRange, Req0), 1396 Req = cowboy_req:set_resp_body(Body, Req1), 1397 respond(Req, State, 206). 1398 1399 set_multipart_ranged_body(Req, State, [FirstRange|MoreRanges]) -> 1400 Boundary = cow_multipart:boundary(), 1401 ContentType = cowboy_req:resp_header(<<"content-type">>, Req), 1402 {FirstContentRange, FirstPartBody} = prepare_range(Req, FirstRange), 1403 FirstPartHead = cow_multipart:first_part(Boundary, [ 1404 {<<"content-type">>, ContentType}, 1405 {<<"content-range">>, FirstContentRange} 1406 ]), 1407 MoreParts = [begin 1408 {NextContentRange, NextPartBody} = prepare_range(Req, NextRange), 1409 NextPartHead = cow_multipart:part(Boundary, [ 1410 {<<"content-type">>, ContentType}, 1411 {<<"content-range">>, NextContentRange} 1412 ]), 1413 [NextPartHead, NextPartBody] 1414 end || NextRange <- MoreRanges], 1415 Body = [FirstPartHead, FirstPartBody, MoreParts, cow_multipart:close(Boundary)], 1416 Req2 = cowboy_req:set_resp_header(<<"content-type">>, 1417 [<<"multipart/byteranges; boundary=">>, Boundary], Req), 1418 Req3 = cowboy_req:set_resp_body(Body, Req2), 1419 respond(Req3, State, 206). 1420 1421 %% Similar to set_multipart_ranged_body except we have to stream 1422 %% the data because the parts contain sendfile tuples. 1423 send_multipart_ranged_body(Req, State, [FirstRange|MoreRanges]) -> 1424 Boundary = cow_multipart:boundary(), 1425 ContentType = cowboy_req:resp_header(<<"content-type">>, Req), 1426 Req2 = cowboy_req:set_resp_header(<<"content-type">>, 1427 [<<"multipart/byteranges; boundary=">>, Boundary], Req), 1428 Req3 = cowboy_req:stream_reply(206, Req2), 1429 {FirstContentRange, FirstPartBody} = prepare_range(Req, FirstRange), 1430 FirstPartHead = cow_multipart:first_part(Boundary, [ 1431 {<<"content-type">>, ContentType}, 1432 {<<"content-range">>, FirstContentRange} 1433 ]), 1434 cowboy_req:stream_body(FirstPartHead, nofin, Req3), 1435 cowboy_req:stream_body(FirstPartBody, nofin, Req3), 1436 _ = [begin 1437 {NextContentRange, NextPartBody} = prepare_range(Req, NextRange), 1438 NextPartHead = cow_multipart:part(Boundary, [ 1439 {<<"content-type">>, ContentType}, 1440 {<<"content-range">>, NextContentRange} 1441 ]), 1442 cowboy_req:stream_body(NextPartHead, nofin, Req3), 1443 cowboy_req:stream_body(NextPartBody, nofin, Req3), 1444 [NextPartHead, NextPartBody] 1445 end || NextRange <- MoreRanges], 1446 cowboy_req:stream_body(cow_multipart:close(Boundary), fin, Req3), 1447 terminate(Req3, State). 1448 1449 prepare_range(#{range := {RangeUnit, _}}, {{From, To, Total0}, Body}) -> 1450 Total = case Total0 of 1451 '*' -> <<"*">>; 1452 _ -> integer_to_binary(Total0) 1453 end, 1454 ContentRange = [RangeUnit, $\s, integer_to_binary(From), 1455 $-, integer_to_binary(To), $/, Total], 1456 {ContentRange, Body}; 1457 prepare_range(#{range := {RangeUnit, _}}, {RangeData, Body}) -> 1458 {[RangeUnit, $\s, RangeData], Body}. 1459 1460 %% We send the content-range header when we can on error. 1461 range_not_satisfiable(Req, State, undefined) -> 1462 respond(Req, State, 416); 1463 range_not_satisfiable(Req0=#{range := {RangeUnit, _}}, State, RangeData) -> 1464 Req = cowboy_req:set_resp_header(<<"content-range">>, 1465 [RangeUnit, $\s, RangeData], Req0), 1466 respond(Req, State, 416). 1467 1468 %% Set the response headers and call the callback found using 1469 %% content_types_provided/2 to obtain the request body and add 1470 %% it to the response. 1471 set_resp_body(Req, State=#state{handler=Handler, content_type_a={_, Callback}}) -> 1472 try case call(Req, State, Callback) of 1473 {stop, Req2, State2} -> 1474 terminate(Req2, State2); 1475 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 1476 switch_handler(Switch, Req2, State2); 1477 {Body, Req2, State2} -> 1478 Req3 = cowboy_req:set_resp_body(Body, Req2), 1479 multiple_choices(Req3, State2) 1480 end catch Class:{case_clause, no_call}:Stacktrace -> 1481 error_terminate(Req, State, Class, {error, {missing_callback, {Handler, Callback, 2}}, 1482 'A callback specified in content_types_provided/2 is not exported.'}, 1483 Stacktrace) 1484 end. 1485 1486 multiple_choices(Req, State) -> 1487 expect(Req, State, multiple_choices, false, 200, 300). 1488 1489 %% Response utility functions. 1490 1491 set_resp_etag(Req, State) -> 1492 {Etag, Req2, State2} = generate_etag(Req, State), 1493 case Etag of 1494 undefined -> 1495 {Req2, State2}; 1496 Etag -> 1497 Req3 = cowboy_req:set_resp_header( 1498 <<"etag">>, encode_etag(Etag), Req2), 1499 {Req3, State2} 1500 end. 1501 1502 -spec encode_etag({strong | weak, binary()}) -> iolist(). 1503 encode_etag({strong, Etag}) -> [$",Etag,$"]; 1504 encode_etag({weak, Etag}) -> ["W/\"",Etag,$"]. 1505 1506 set_resp_expires(Req, State) -> 1507 {Expires, Req2, State2} = expires(Req, State), 1508 case Expires of 1509 Expires when is_atom(Expires) -> 1510 {Req2, State2}; 1511 Expires when is_binary(Expires) -> 1512 Req3 = cowboy_req:set_resp_header( 1513 <<"expires">>, Expires, Req2), 1514 {Req3, State2}; 1515 Expires -> 1516 ExpiresBin = cowboy_clock:rfc1123(Expires), 1517 Req3 = cowboy_req:set_resp_header( 1518 <<"expires">>, ExpiresBin, Req2), 1519 {Req3, State2} 1520 end. 1521 1522 %% Info retrieval. No logic. 1523 1524 generate_etag(Req, State=#state{etag=no_call}) -> 1525 {undefined, Req, State}; 1526 generate_etag(Req, State=#state{etag=undefined}) -> 1527 case unsafe_call(Req, State, generate_etag) of 1528 no_call -> 1529 {undefined, Req, State#state{etag=no_call}}; 1530 {Etag, Req2, State2} when is_binary(Etag) -> 1531 Etag2 = cow_http_hd:parse_etag(Etag), 1532 {Etag2, Req2, State2#state{etag=Etag2}}; 1533 {Etag, Req2, State2} -> 1534 {Etag, Req2, State2#state{etag=Etag}} 1535 end; 1536 generate_etag(Req, State=#state{etag=Etag}) -> 1537 {Etag, Req, State}. 1538 1539 last_modified(Req, State=#state{last_modified=no_call}) -> 1540 {undefined, Req, State}; 1541 last_modified(Req, State=#state{last_modified=undefined}) -> 1542 case unsafe_call(Req, State, last_modified) of 1543 no_call -> 1544 {undefined, Req, State#state{last_modified=no_call}}; 1545 {LastModified, Req2, State2} -> 1546 {LastModified, Req2, State2#state{last_modified=LastModified}} 1547 end; 1548 last_modified(Req, State=#state{last_modified=LastModified}) -> 1549 {LastModified, Req, State}. 1550 1551 expires(Req, State=#state{expires=no_call}) -> 1552 {undefined, Req, State}; 1553 expires(Req, State=#state{expires=undefined}) -> 1554 case unsafe_call(Req, State, expires) of 1555 no_call -> 1556 {undefined, Req, State#state{expires=no_call}}; 1557 {Expires, Req2, State2} -> 1558 {Expires, Req2, State2#state{expires=Expires}} 1559 end; 1560 expires(Req, State=#state{expires=Expires}) -> 1561 {Expires, Req, State}. 1562 1563 %% REST primitives. 1564 1565 expect(Req, State, Callback, Expected, OnTrue, OnFalse) -> 1566 case call(Req, State, Callback) of 1567 no_call -> 1568 next(Req, State, OnTrue); 1569 {stop, Req2, State2} -> 1570 terminate(Req2, State2); 1571 {Switch, Req2, State2} when element(1, Switch) =:= switch_handler -> 1572 switch_handler(Switch, Req2, State2); 1573 {Expected, Req2, State2} -> 1574 next(Req2, State2, OnTrue); 1575 {_Unexpected, Req2, State2} -> 1576 next(Req2, State2, OnFalse) 1577 end. 1578 1579 call(Req0, State=#state{handler=Handler, 1580 handler_state=HandlerState0}, Callback) -> 1581 case erlang:function_exported(Handler, Callback, 2) of 1582 true -> 1583 try Handler:Callback(Req0, HandlerState0) of 1584 no_call -> 1585 no_call; 1586 {Result, Req, HandlerState} -> 1587 {Result, Req, State#state{handler_state=HandlerState}} 1588 catch Class:Reason:Stacktrace -> 1589 error_terminate(Req0, State, Class, Reason, Stacktrace) 1590 end; 1591 false -> 1592 no_call 1593 end. 1594 1595 unsafe_call(Req0, State=#state{handler=Handler, 1596 handler_state=HandlerState0}, Callback) -> 1597 case erlang:function_exported(Handler, Callback, 2) of 1598 false -> 1599 no_call; 1600 true -> 1601 case Handler:Callback(Req0, HandlerState0) of 1602 no_call -> 1603 no_call; 1604 {Result, Req, HandlerState} -> 1605 {Result, Req, State#state{handler_state=HandlerState}} 1606 end 1607 end. 1608 1609 next(Req, State, Next) when is_function(Next) -> 1610 Next(Req, State); 1611 next(Req, State, StatusCode) when is_integer(StatusCode) -> 1612 respond(Req, State, StatusCode). 1613 1614 respond(Req0, State, StatusCode) -> 1615 %% We remove the content-type header when there is no body, 1616 %% except when the status code is 200 because it might have 1617 %% been intended (for example sending an empty file). 1618 Req = case cowboy_req:has_resp_body(Req0) of 1619 true when StatusCode =:= 200 -> Req0; 1620 true -> Req0; 1621 false -> cowboy_req:delete_resp_header(<<"content-type">>, Req0) 1622 end, 1623 terminate(cowboy_req:reply(StatusCode, Req), State). 1624 1625 switch_handler({switch_handler, Mod}, Req, #state{handler_state=HandlerState}) -> 1626 {Mod, Req, HandlerState}; 1627 switch_handler({switch_handler, Mod, Opts}, Req, #state{handler_state=HandlerState}) -> 1628 {Mod, Req, HandlerState, Opts}. 1629 1630 -spec error_terminate(cowboy_req:req(), #state{}, atom(), any(), any()) -> no_return(). 1631 error_terminate(Req, #state{handler=Handler, handler_state=HandlerState}, Class, Reason, Stacktrace) -> 1632 cowboy_handler:terminate({crash, Class, Reason}, Req, HandlerState, Handler), 1633 erlang:raise(Class, Reason, Stacktrace). 1634 1635 terminate(Req, #state{handler=Handler, handler_state=HandlerState}) -> 1636 Result = cowboy_handler:terminate(normal, Req, HandlerState, Handler), 1637 {ok, Req, Result}.