mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Move the final old HTTP suite tests and remove it
This commit is contained in:
parent
037b286aa8
commit
0223f69fcd
24 changed files with 241 additions and 591 deletions
23
test/handlers/accept_callback_h.erl
Normal file
23
test/handlers/accept_callback_h.erl
Normal file
|
@ -0,0 +1,23 @@
|
|||
%% This module returns something different in
|
||||
%% AcceptCallback depending on the query string.
|
||||
|
||||
-module(accept_callback_h).
|
||||
|
||||
-export([init/2]).
|
||||
-export([allowed_methods/2]).
|
||||
-export([content_types_accepted/2]).
|
||||
-export([put_text_plain/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
allowed_methods(Req, State) ->
|
||||
{[<<"PUT">>, <<"POST">>, <<"PATCH">>], Req, State}.
|
||||
|
||||
content_types_accepted(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, []}, put_text_plain}], Req, State}.
|
||||
|
||||
put_text_plain(Req=#{qs := <<"false">>}, State) ->
|
||||
{false, Req, State};
|
||||
put_text_plain(Req=#{qs := <<"true">>}, State) ->
|
||||
{true, Req, State}.
|
|
@ -1,5 +1,5 @@
|
|||
%% This module accepts a multipart media type with parameters
|
||||
%% that do not include boundary.
|
||||
%% This module returns something different in
|
||||
%% content_types_accepted depending on the query string.
|
||||
|
||||
-module(content_types_accepted_h).
|
||||
|
||||
|
@ -19,6 +19,8 @@ content_types_accepted(Req=#{qs := <<"multipart">>}, State) ->
|
|||
{[
|
||||
{{<<"multipart">>, <<"mixed">>, [{<<"v">>, <<"1">>}]}, put_multipart_mixed}
|
||||
], Req, State};
|
||||
content_types_accepted(Req=#{qs := <<"param">>}, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, [{<<"charset">>, <<"utf-8">>}]}, put_text_plain}], Req, State};
|
||||
content_types_accepted(Req=#{qs := <<"wildcard-param">>}, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, '*'}, put_text_plain}], Req, State}.
|
||||
|
||||
|
|
17
test/handlers/delete_resource_h.erl
Normal file
17
test/handlers/delete_resource_h.erl
Normal file
|
@ -0,0 +1,17 @@
|
|||
%% This module accepts a multipart media type with parameters
|
||||
%% that do not include boundary.
|
||||
|
||||
-module(delete_resource_h).
|
||||
|
||||
-export([init/2]).
|
||||
-export([allowed_methods/2]).
|
||||
-export([delete_resource/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
allowed_methods(Req, State) ->
|
||||
{[<<"DELETE">>], Req, State}.
|
||||
|
||||
delete_resource(#{qs := <<"missing">>}, _) ->
|
||||
no_call.
|
39
test/handlers/generate_etag_h.erl
Normal file
39
test/handlers/generate_etag_h.erl
Normal file
|
@ -0,0 +1,39 @@
|
|||
%% This module sends a different etag value
|
||||
%% depending on the query string.
|
||||
|
||||
-module(generate_etag_h).
|
||||
|
||||
-export([init/2]).
|
||||
-export([content_types_provided/2]).
|
||||
-export([get_text_plain/2]).
|
||||
-export([generate_etag/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
content_types_provided(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
|
||||
|
||||
get_text_plain(Req, State) ->
|
||||
{<<"This is REST!">>, Req, State}.
|
||||
|
||||
%% Correct return values from generate_etag/2.
|
||||
generate_etag(Req=#{qs := <<"tuple-weak">>}, State) ->
|
||||
{{weak, <<"etag-header-value">>}, Req, State};
|
||||
generate_etag(Req=#{qs := <<"tuple-strong">>}, State) ->
|
||||
{{strong, <<"etag-header-value">>}, Req, State};
|
||||
%% Backwards compatible return values from generate_etag/2.
|
||||
generate_etag(Req=#{qs := <<"binary-weak-quoted">>}, State) ->
|
||||
{<<"W/\"etag-header-value\"">>, Req, State};
|
||||
generate_etag(Req=#{qs := <<"binary-strong-quoted">>}, State) ->
|
||||
{<<"\"etag-header-value\"">>, Req, State};
|
||||
%% Invalid return values from generate_etag/2.
|
||||
generate_etag(Req=#{qs := <<"binary-weak-unquoted">>}, State) ->
|
||||
ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
|
||||
{<<"W/etag-header-value">>, Req, State};
|
||||
generate_etag(Req=#{qs := <<"binary-strong-unquoted">>}, State) ->
|
||||
ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
|
||||
{<<"etag-header-value">>, Req, State};
|
||||
%% Simulate the callback being missing in other cases.
|
||||
generate_etag(#{qs := <<"missing">>}, _) ->
|
||||
no_call.
|
|
@ -1,183 +0,0 @@
|
|||
%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
|
||||
%% Copyright (c) 2011, Anthony Ramine <nox@dev-extend.eu>
|
||||
%%
|
||||
%% Permission to use, copy, modify, and/or distribute this software for any
|
||||
%% purpose with or without fee is hereby granted, provided that the above
|
||||
%% copyright notice and this permission notice appear in all copies.
|
||||
%%
|
||||
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
-module(old_http_SUITE).
|
||||
-compile(export_all).
|
||||
-compile(nowarn_export_all).
|
||||
|
||||
-import(ct_helper, [config/2]).
|
||||
-import(cowboy_test, [gun_open/1]).
|
||||
-import(cowboy_test, [gun_open/2]).
|
||||
-import(cowboy_test, [gun_down/1]).
|
||||
-import(cowboy_test, [raw_open/1]).
|
||||
-import(cowboy_test, [raw_send/2]).
|
||||
-import(cowboy_test, [raw_recv_head/1]).
|
||||
-import(cowboy_test, [raw_expect_recv/2]).
|
||||
|
||||
%% ct.
|
||||
|
||||
all() ->
|
||||
[
|
||||
{group, http},
|
||||
{group, https},
|
||||
{group, http_compress},
|
||||
{group, https_compress}
|
||||
].
|
||||
|
||||
groups() ->
|
||||
Tests = ct_helper:all(?MODULE),
|
||||
[
|
||||
{http, [], Tests}, %% @todo parallel
|
||||
{https, [parallel], Tests},
|
||||
{http_compress, [parallel], Tests},
|
||||
{https_compress, [parallel], Tests}
|
||||
].
|
||||
|
||||
init_per_group(Name = http, Config) ->
|
||||
cowboy_test:init_http(Name, #{env => #{dispatch => init_dispatch(Config)}}, Config);
|
||||
init_per_group(Name = https, Config) ->
|
||||
cowboy_test:init_https(Name, #{env => #{dispatch => init_dispatch(Config)}}, Config);
|
||||
init_per_group(Name = http_compress, Config) ->
|
||||
cowboy_test:init_http(Name, #{
|
||||
env => #{dispatch => init_dispatch(Config)},
|
||||
stream_handlers => [cowboy_compress_h, cowboy_stream_h]
|
||||
}, Config);
|
||||
init_per_group(Name = https_compress, Config) ->
|
||||
cowboy_test:init_https(Name, #{
|
||||
env => #{dispatch => init_dispatch(Config)},
|
||||
stream_handlers => [cowboy_compress_h, cowboy_stream_h]
|
||||
}, Config).
|
||||
|
||||
end_per_group(Name, _) ->
|
||||
ok = cowboy:stop_listener(Name).
|
||||
|
||||
%% Dispatch configuration.
|
||||
|
||||
init_dispatch(_) ->
|
||||
cowboy_router:compile([
|
||||
{"localhost", [
|
||||
{"/chunked_response", http_chunked, []},
|
||||
{"/headers/dupe", http_handler,
|
||||
[{headers, #{<<"connection">> => <<"close">>}}]},
|
||||
{"/set_resp/header", http_set_resp,
|
||||
[{headers, #{<<"vary">> => <<"Accept">>}}]},
|
||||
{"/set_resp/overwrite", http_set_resp,
|
||||
[{headers, #{<<"server">> => <<"DesireDrive/1.0">>}}]},
|
||||
{"/set_resp/body", http_set_resp,
|
||||
[{body, <<"A flameless dance does not equal a cycle">>}]},
|
||||
{"/handler_errors", http_errors, []},
|
||||
{"/echo/body", http_echo_body, []},
|
||||
{"/param_all", rest_param_all, []},
|
||||
{"/bad_accept", rest_simple_resource, []},
|
||||
{"/bad_content_type", rest_patch_resource, []},
|
||||
{"/simple", rest_simple_resource, []},
|
||||
{"/forbidden_post", rest_forbidden_resource, [true]},
|
||||
{"/simple_post", rest_forbidden_resource, [false]},
|
||||
{"/missing_get_callbacks", rest_missing_callbacks, []},
|
||||
{"/missing_put_callbacks", rest_missing_callbacks, []},
|
||||
{"/nodelete", rest_nodelete_resource, []},
|
||||
{"/post_charset", rest_post_charset_resource, []},
|
||||
{"/postonly", rest_postonly_resource, []},
|
||||
{"/patch", rest_patch_resource, []},
|
||||
{"/resetags", rest_resource_etags, []},
|
||||
{"/rest_expires", rest_expires, []},
|
||||
{"/rest_expires_binary", rest_expires_binary, []},
|
||||
{"/rest_empty_resource", rest_empty_resource, []},
|
||||
{"/loop_stream_recv", http_loop_stream_recv, []},
|
||||
{"/", http_handler, []}
|
||||
]}
|
||||
]).
|
||||
|
||||
%% Tests.
|
||||
|
||||
rest_bad_content_type(Config) ->
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:patch(ConnPid, "/bad_content_type",
|
||||
[{<<"content-type">>, <<"text/plain, text/html">>}], <<"Whatever">>),
|
||||
{response, fin, 415, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
rest_nodelete(Config) ->
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:delete(ConnPid, "/nodelete"),
|
||||
{response, fin, 500, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
rest_patch(Config) ->
|
||||
Tests = [
|
||||
{204, [{<<"content-type">>, <<"text/plain">>}], <<"whatever">>},
|
||||
{400, [{<<"content-type">>, <<"text/plain">>}], <<"false">>},
|
||||
{400, [{<<"content-type">>, <<"text/plain">>}], <<"stop">>},
|
||||
{415, [{<<"content-type">>, <<"application/json">>}], <<"bad_content_type">>}
|
||||
],
|
||||
ConnPid = gun_open(Config),
|
||||
_ = [begin
|
||||
Ref = gun:patch(ConnPid, "/patch", Headers, Body),
|
||||
{response, fin, Status, _} = gun:await(ConnPid, Ref)
|
||||
end || {Status, Headers, Body} <- Tests],
|
||||
ok.
|
||||
|
||||
rest_post_charset(Config) ->
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:post(ConnPid, "/post_charset",
|
||||
[{<<"content-type">>, <<"text/plain;charset=UTF-8">>}], "12345"),
|
||||
{response, fin, 204, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
rest_postonly(Config) ->
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:post(ConnPid, "/postonly",
|
||||
[{<<"content-type">>, <<"text/plain">>}], "12345"),
|
||||
{response, fin, 204, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
rest_resource_get_etag(Config, Type) ->
|
||||
rest_resource_get_etag(Config, Type, []).
|
||||
|
||||
rest_resource_get_etag(Config, Type, Headers) ->
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:get(ConnPid, "/resetags?type=" ++ Type, Headers),
|
||||
{response, _, Status, RespHeaders} = gun:await(ConnPid, Ref),
|
||||
case lists:keyfind(<<"etag">>, 1, RespHeaders) of
|
||||
false -> {Status, false};
|
||||
{<<"etag">>, ETag} -> {Status, ETag}
|
||||
end.
|
||||
|
||||
rest_resource_etags(Config) ->
|
||||
Tests = [
|
||||
{200, <<"W/\"etag-header-value\"">>, "tuple-weak"},
|
||||
{200, <<"\"etag-header-value\"">>, "tuple-strong"},
|
||||
{200, <<"W/\"etag-header-value\"">>, "binary-weak-quoted"},
|
||||
{200, <<"\"etag-header-value\"">>, "binary-strong-quoted"},
|
||||
{500, false, "binary-strong-unquoted"},
|
||||
{500, false, "binary-weak-unquoted"}
|
||||
],
|
||||
_ = [{Status, ETag, Type} = begin
|
||||
{Ret, RespETag} = rest_resource_get_etag(Config, Type),
|
||||
{Ret, RespETag, Type}
|
||||
end || {Status, ETag, Type} <- Tests].
|
||||
|
||||
rest_resource_etags_if_none_match(Config) ->
|
||||
Tests = [
|
||||
{304, <<"W/\"etag-header-value\"">>, "tuple-weak"},
|
||||
{304, <<"\"etag-header-value\"">>, "tuple-strong"},
|
||||
{304, <<"W/\"etag-header-value\"">>, "binary-weak-quoted"},
|
||||
{304, <<"\"etag-header-value\"">>, "binary-strong-quoted"}
|
||||
],
|
||||
_ = [{Status, Type} = begin
|
||||
{Ret, _} = rest_resource_get_etag(Config, Type,
|
||||
[{<<"if-none-match">>, ETag}]),
|
||||
{Ret, Type}
|
||||
end || {Status, ETag, Type} <- Tests].
|
|
@ -1,15 +0,0 @@
|
|||
%% Feel free to use, reuse and abuse the code in this file.
|
||||
|
||||
-module(http_chunked).
|
||||
|
||||
-export([init/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
Req2 = cowboy_req:stream_reply(200, Req),
|
||||
%% Try an empty chunk to make sure the stream doesn't get closed.
|
||||
cowboy_req:stream_body([<<>>], nofin, Req2),
|
||||
timer:sleep(100),
|
||||
cowboy_req:stream_body("chunked_handler\r\n", nofin, Req2),
|
||||
timer:sleep(100),
|
||||
cowboy_req:stream_body("works fine!", fin, Req2),
|
||||
{ok, Req2, Opts}.
|
|
@ -1,21 +0,0 @@
|
|||
%% Feel free to use, reuse and abuse the code in this file.
|
||||
|
||||
-module(http_echo_body).
|
||||
|
||||
-export([init/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
true = cowboy_req:has_body(Req),
|
||||
Req3 = case cowboy_req:read_body(Req, #{length => 1000000}) of
|
||||
{ok, Body, Req2} -> handle_body(Req2, Body);
|
||||
{more, _, Req2} -> handle_badlength(Req2)
|
||||
end,
|
||||
{ok, Req3, Opts}.
|
||||
|
||||
handle_badlength(Req) ->
|
||||
cowboy_req:reply(413, #{}, <<"Request entity too large">>, Req).
|
||||
|
||||
handle_body(Req, Body) ->
|
||||
Size = cowboy_req:body_length(Req),
|
||||
Size = byte_size(Body),
|
||||
cowboy_req:reply(200, #{}, Body, Req).
|
|
@ -1,19 +0,0 @@
|
|||
%% Feel free to use, reuse and abuse the code in this file.
|
||||
|
||||
-module(http_errors).
|
||||
|
||||
-export([init/2]).
|
||||
|
||||
-spec init(_, _) -> no_return().
|
||||
init(Req, _Opts) ->
|
||||
#{'case' := Case} = cowboy_req:match_qs(['case'], Req),
|
||||
case_init(Case, Req).
|
||||
|
||||
-spec case_init(_, _) -> no_return().
|
||||
case_init(<<"init_before_reply">> = Case, _Req) ->
|
||||
ct_helper_error_h:ignore(?MODULE, case_init, 2),
|
||||
error(Case);
|
||||
case_init(<<"init_after_reply">> = Case, Req) ->
|
||||
ct_helper_error_h:ignore(?MODULE, case_init, 2),
|
||||
_ = cowboy_req:reply(200, #{}, "http_handler_crashes", Req),
|
||||
error(Case).
|
|
@ -1,10 +0,0 @@
|
|||
%% Feel free to use, reuse and abuse the code in this file.
|
||||
|
||||
-module(http_handler).
|
||||
|
||||
-export([init/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
Headers = proplists:get_value(headers, Opts, #{}),
|
||||
Body = proplists:get_value(body, Opts, "http_handler"),
|
||||
{ok, cowboy_req:reply(200, Headers, Body, Req), Opts}.
|
|
@ -1,34 +0,0 @@
|
|||
%% Feel free to use, reuse and abuse the code in this file.
|
||||
|
||||
-module(http_loop_stream_recv).
|
||||
|
||||
-export([init/2]).
|
||||
-export([info/3]).
|
||||
-export([terminate/3]).
|
||||
|
||||
init(Req, _) ->
|
||||
receive after 100 -> ok end,
|
||||
self() ! stream,
|
||||
{cowboy_loop, Req, undefined}.
|
||||
|
||||
info(stream, Req, undefined) ->
|
||||
stream(Req, 1, <<>>).
|
||||
|
||||
stream(Req, ID, Acc) ->
|
||||
case cowboy_req:read_body(Req) of
|
||||
{ok, <<>>, Req2} ->
|
||||
{stop, cowboy_req:reply(200, Req2), undefined};
|
||||
{_, Data, Req2} ->
|
||||
parse_id(Req2, ID, << Acc/binary, Data/binary >>)
|
||||
end.
|
||||
|
||||
parse_id(Req, ID, Data) ->
|
||||
case Data of
|
||||
<< ID:32, Rest/bits >> ->
|
||||
parse_id(Req, ID + 1, Rest);
|
||||
_ ->
|
||||
stream(Req, ID, Data)
|
||||
end.
|
||||
|
||||
terminate(stop, _, _) ->
|
||||
ok.
|
|
@ -1,25 +0,0 @@
|
|||
%% Feel free to use, reuse and abuse the code in this file.
|
||||
|
||||
-module(http_set_resp).
|
||||
|
||||
-export([init/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
Headers = proplists:get_value(headers, Opts, #{}),
|
||||
Body = proplists:get_value(body, Opts, <<"http_handler_set_resp">>),
|
||||
Req2 = lists:foldl(fun({Name, Value}, R) ->
|
||||
cowboy_req:set_resp_header(Name, Value, R)
|
||||
end, Req, maps:to_list(Headers)),
|
||||
Req3 = cowboy_req:set_resp_body(Body, Req2),
|
||||
Req4 = cowboy_req:set_resp_header(<<"x-cowboy-test">>, <<"ok">>, Req3),
|
||||
Req5 = cowboy_req:set_resp_cookie(<<"cake">>, <<"lie">>, Req4),
|
||||
case cowboy_req:has_resp_header(<<"x-cowboy-test">>, Req5) of
|
||||
false -> {ok, Req5, Opts};
|
||||
true ->
|
||||
case cowboy_req:has_resp_body(Req5) of
|
||||
false ->
|
||||
{ok, Req5, Opts};
|
||||
true ->
|
||||
{ok, cowboy_req:reply(200, Req5), Opts}
|
||||
end
|
||||
end.
|
|
@ -1,6 +0,0 @@
|
|||
-module(rest_empty_resource).
|
||||
|
||||
-export([init/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
|
@ -1,22 +0,0 @@
|
|||
-module(rest_expires).
|
||||
|
||||
-export([init/2]).
|
||||
-export([content_types_provided/2]).
|
||||
-export([get_text_plain/2]).
|
||||
-export([expires/2]).
|
||||
-export([last_modified/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
content_types_provided(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
|
||||
|
||||
get_text_plain(Req, State) ->
|
||||
{<<"This is REST!">>, Req, State}.
|
||||
|
||||
expires(Req, State) ->
|
||||
{{{2012, 9, 21}, {22, 36, 14}}, Req, State}.
|
||||
|
||||
last_modified(Req, State) ->
|
||||
{{{2012, 9, 21}, {22, 36, 14}}, Req, State}.
|
|
@ -1,18 +0,0 @@
|
|||
-module(rest_expires_binary).
|
||||
|
||||
-export([init/2]).
|
||||
-export([content_types_provided/2]).
|
||||
-export([get_text_plain/2]).
|
||||
-export([expires/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
content_types_provided(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
|
||||
|
||||
get_text_plain(Req, State) ->
|
||||
{<<"This is REST!">>, Req, State}.
|
||||
|
||||
expires(Req, State) ->
|
||||
{<<"0">>, Req, State}.
|
|
@ -1,32 +0,0 @@
|
|||
-module(rest_forbidden_resource).
|
||||
|
||||
-export([init/2]).
|
||||
-export([allowed_methods/2]).
|
||||
-export([forbidden/2]).
|
||||
-export([content_types_provided/2]).
|
||||
-export([content_types_accepted/2]).
|
||||
-export([to_text/2]).
|
||||
-export([from_text/2]).
|
||||
|
||||
init(Req, [Forbidden]) ->
|
||||
{cowboy_rest, Req, Forbidden}.
|
||||
|
||||
allowed_methods(Req, State) ->
|
||||
{[<<"GET">>, <<"HEAD">>, <<"POST">>], Req, State}.
|
||||
|
||||
forbidden(Req, State=true) ->
|
||||
{true, Req, State};
|
||||
forbidden(Req, State=false) ->
|
||||
{false, Req, State}.
|
||||
|
||||
content_types_provided(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, []}, to_text}], Req, State}.
|
||||
|
||||
content_types_accepted(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, []}, from_text}], Req, State}.
|
||||
|
||||
to_text(Req, State) ->
|
||||
{<<"This is REST!">>, Req, State}.
|
||||
|
||||
from_text(Req, State) ->
|
||||
{{true, cowboy_req:path(Req)}, Req, State}.
|
|
@ -1,24 +0,0 @@
|
|||
-module(rest_missing_callbacks).
|
||||
|
||||
-export([init/2]).
|
||||
-export([allowed_methods/2]).
|
||||
-export([content_types_accepted/2]).
|
||||
-export([content_types_provided/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
allowed_methods(Req, State) ->
|
||||
{[<<"GET">>, <<"PUT">>], Req, State}.
|
||||
|
||||
content_types_accepted(Req, State) ->
|
||||
ct_helper_error_h:ignore(cowboy_rest, process_content_type, 3),
|
||||
{[
|
||||
{<<"application/json">>, put_application_json}
|
||||
], Req, State}.
|
||||
|
||||
content_types_provided(Req, State) ->
|
||||
ct_helper_error_h:ignore(cowboy_rest, set_resp_body, 2),
|
||||
{[
|
||||
{<<"text/plain">>, get_text_plain}
|
||||
], Req, State}.
|
|
@ -1,18 +0,0 @@
|
|||
-module(rest_nodelete_resource).
|
||||
|
||||
-export([init/2]).
|
||||
-export([allowed_methods/2]).
|
||||
-export([content_types_provided/2]).
|
||||
-export([get_text_plain/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
allowed_methods(Req, State) ->
|
||||
{[<<"GET">>, <<"HEAD">>, <<"DELETE">>], Req, State}.
|
||||
|
||||
content_types_provided(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
|
||||
|
||||
get_text_plain(Req, State) ->
|
||||
{<<"This is REST!">>, Req, State}.
|
|
@ -1,36 +0,0 @@
|
|||
-module(rest_param_all).
|
||||
|
||||
-export([init/2]).
|
||||
-export([allowed_methods/2]).
|
||||
-export([content_types_provided/2]).
|
||||
-export([get_text_plain/2]).
|
||||
-export([content_types_accepted/2]).
|
||||
-export([put_text_plain/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
allowed_methods(Req, State) ->
|
||||
{[<<"GET">>, <<"PUT">>], Req, State}.
|
||||
|
||||
content_types_provided(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, '*'}, get_text_plain}], Req, State}.
|
||||
|
||||
get_text_plain(Req, State) ->
|
||||
{_, _, Param} = maps:get(media_type, Req, {<<"text">>, <<"plain">>, []}),
|
||||
Body = if
|
||||
Param == '*' ->
|
||||
<<"'*'">>;
|
||||
Param == [] ->
|
||||
<<"[]">>;
|
||||
Param /= [] ->
|
||||
iolist_to_binary([[Key, $=, Value] || {Key, Value} <- Param])
|
||||
end,
|
||||
{Body, Req, State}.
|
||||
|
||||
content_types_accepted(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, '*'}, put_text_plain}], Req, State}.
|
||||
|
||||
put_text_plain(Req0, State) ->
|
||||
{ok, _, Req} = cowboy_req:read_body(Req0),
|
||||
{true, Req, State}.
|
|
@ -1,38 +0,0 @@
|
|||
-module(rest_patch_resource).
|
||||
|
||||
-export([init/2]).
|
||||
-export([allowed_methods/2]).
|
||||
-export([content_types_provided/2]).
|
||||
-export([get_text_plain/2]).
|
||||
-export([content_types_accepted/2]).
|
||||
-export([patch_text_plain/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
allowed_methods(Req, State) ->
|
||||
{[<<"HEAD">>, <<"GET">>, <<"PATCH">>], Req, State}.
|
||||
|
||||
content_types_provided(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
|
||||
|
||||
get_text_plain(Req, State) ->
|
||||
{<<"This is REST!">>, Req, State}.
|
||||
|
||||
content_types_accepted(Req, State) ->
|
||||
case cowboy_req:method(Req) of
|
||||
<<"PATCH">> ->
|
||||
{[{{<<"text">>, <<"plain">>, []}, patch_text_plain}], Req, State};
|
||||
_ ->
|
||||
{[], Req, State}
|
||||
end.
|
||||
|
||||
patch_text_plain(Req, State) ->
|
||||
case cowboy_req:read_body(Req) of
|
||||
{ok, <<"stop">>, Req0} ->
|
||||
{stop, cowboy_req:reply(400, Req0), State};
|
||||
{ok, <<"false">>, Req0} ->
|
||||
{false, Req0, State};
|
||||
{ok, _Body, Req0} ->
|
||||
{true, Req0, State}
|
||||
end.
|
|
@ -1,19 +0,0 @@
|
|||
-module(rest_post_charset_resource).
|
||||
|
||||
-export([init/2]).
|
||||
-export([allowed_methods/2]).
|
||||
-export([content_types_accepted/2]).
|
||||
-export([from_text/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
allowed_methods(Req, State) ->
|
||||
{[<<"POST">>], Req, State}.
|
||||
|
||||
content_types_accepted(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, [{<<"charset">>, <<"utf-8">>}]},
|
||||
from_text}], Req, State}.
|
||||
|
||||
from_text(Req, State) ->
|
||||
{true, Req, State}.
|
|
@ -1,18 +0,0 @@
|
|||
-module(rest_postonly_resource).
|
||||
|
||||
-export([init/2]).
|
||||
-export([allowed_methods/2]).
|
||||
-export([content_types_accepted/2]).
|
||||
-export([from_text/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
allowed_methods(Req, State) ->
|
||||
{[<<"POST">>], Req, State}.
|
||||
|
||||
content_types_accepted(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, '*'}, from_text}], Req, State}.
|
||||
|
||||
from_text(Req, State) ->
|
||||
{true, Req, State}.
|
|
@ -1,37 +0,0 @@
|
|||
-module(rest_resource_etags).
|
||||
|
||||
-export([init/2]).
|
||||
-export([generate_etag/2]).
|
||||
-export([content_types_provided/2]).
|
||||
-export([get_text_plain/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
generate_etag(Req, State) ->
|
||||
#{type := Type} = cowboy_req:match_qs([type], Req),
|
||||
case Type of
|
||||
%% Correct return values from generate_etag/2.
|
||||
<<"tuple-weak">> ->
|
||||
{{weak, <<"etag-header-value">>}, Req, State};
|
||||
<<"tuple-strong">> ->
|
||||
{{strong, <<"etag-header-value">>}, Req, State};
|
||||
%% Backwards compatible return values from generate_etag/2.
|
||||
<<"binary-weak-quoted">> ->
|
||||
{<<"W/\"etag-header-value\"">>, Req, State};
|
||||
<<"binary-strong-quoted">> ->
|
||||
{<<"\"etag-header-value\"">>, Req, State};
|
||||
%% Invalid return values from generate_etag/2.
|
||||
<<"binary-strong-unquoted">> ->
|
||||
ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
|
||||
{<<"etag-header-value">>, Req, State};
|
||||
<<"binary-weak-unquoted">> ->
|
||||
ct_helper_error_h:ignore(cow_http_hd, parse_etag, 1),
|
||||
{<<"W/etag-header-value">>, Req, State}
|
||||
end.
|
||||
|
||||
content_types_provided(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
|
||||
|
||||
get_text_plain(Req, State) ->
|
||||
{<<"This is REST!">>, Req, State}.
|
|
@ -1,14 +0,0 @@
|
|||
-module(rest_simple_resource).
|
||||
|
||||
-export([init/2]).
|
||||
-export([content_types_provided/2]).
|
||||
-export([get_text_plain/2]).
|
||||
|
||||
init(Req, Opts) ->
|
||||
{cowboy_rest, Req, Opts}.
|
||||
|
||||
content_types_provided(Req, State) ->
|
||||
{[{{<<"text">>, <<"plain">>, []}, get_text_plain}], Req, State}.
|
||||
|
||||
get_text_plain(Req, State) ->
|
||||
{<<"This is REST!">>, Req, State}.
|
|
@ -39,6 +39,7 @@ end_per_group(Name, _) ->
|
|||
init_dispatch(_) ->
|
||||
cowboy_router:compile([{'_', [
|
||||
{"/", rest_hello_h, []},
|
||||
{"/accept_callback", accept_callback_h, []},
|
||||
{"/accept_callback_missing", accept_callback_missing_h, []},
|
||||
{"/charsets_provided", charsets_provided_h, []},
|
||||
{"/charsets_provided_empty", charsets_provided_empty_h, []},
|
||||
|
@ -50,7 +51,9 @@ init_dispatch(_) ->
|
|||
charset_in_content_types_provided_implicit_no_callback_h, []},
|
||||
{"/content_types_accepted", content_types_accepted_h, []},
|
||||
{"/content_types_provided", content_types_provided_h, []},
|
||||
{"/delete_resource", delete_resource_h, []},
|
||||
{"/expires", expires_h, []},
|
||||
{"/generate_etag", generate_etag_h, []},
|
||||
{"/if_range", if_range_h, []},
|
||||
{"/last_modified", last_modified_h, []},
|
||||
{"/provide_callback_missing", provide_callback_missing_h, []},
|
||||
|
@ -84,6 +87,44 @@ accept_callback_missing(Config) ->
|
|||
{response, fin, 500, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
accept_callback_patch_false(Config) ->
|
||||
do_accept_callback_false(Config, patch).
|
||||
|
||||
accept_callback_patch_true(Config) ->
|
||||
do_accept_callback_true(Config, patch).
|
||||
|
||||
accept_callback_post_false(Config) ->
|
||||
do_accept_callback_false(Config, post).
|
||||
|
||||
accept_callback_post_true(Config) ->
|
||||
do_accept_callback_true(Config, post).
|
||||
|
||||
accept_callback_put_false(Config) ->
|
||||
do_accept_callback_false(Config, put).
|
||||
|
||||
accept_callback_put_true(Config) ->
|
||||
do_accept_callback_true(Config, put).
|
||||
|
||||
do_accept_callback_false(Config, Fun) ->
|
||||
doc("When AcceptCallback returns false a 400 response must be returned."),
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:Fun(ConnPid, "/accept_callback?false", [
|
||||
{<<"accept-encoding">>, <<"gzip">>},
|
||||
{<<"content-type">>, <<"text/plain">>}
|
||||
], <<"Request body.">>),
|
||||
{response, _, 400, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
do_accept_callback_true(Config, Fun) ->
|
||||
doc("When AcceptCallback returns true a 204 response must be returned."),
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:Fun(ConnPid, "/accept_callback?true", [
|
||||
{<<"accept-encoding">>, <<"gzip">>},
|
||||
{<<"content-type">>, <<"text/plain">>}
|
||||
], <<"Request body.">>),
|
||||
{response, _, 204, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
charset_in_content_types_provided(Config) ->
|
||||
doc("When a charset is matched explictly in content_types_provided, "
|
||||
"that charset is used and the charsets_provided callback is ignored."),
|
||||
|
@ -282,6 +323,17 @@ charsets_provided_empty_noheader(Config) ->
|
|||
{response, _, 406, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
content_type_invalid(Config) ->
|
||||
doc("An invalid content-type in a POST/PATCH/PUT request "
|
||||
"must be rejected with a 415 unsupported media type response. (RFC7231 6.5.13)"),
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:put(ConnPid, "/content_types_accepted?wildcard-param", [
|
||||
{<<"accept-encoding">>, <<"gzip">>},
|
||||
{<<"content-type">>, <<"text/plain, text/html">>}
|
||||
]),
|
||||
{response, fin, 415, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
content_types_accepted_ignore_multipart_boundary(Config) ->
|
||||
doc("When a multipart content-type is provided for the request "
|
||||
"body, the boundary parameter is not expected to be returned "
|
||||
|
@ -295,6 +347,18 @@ content_types_accepted_ignore_multipart_boundary(Config) ->
|
|||
{response, _, 204, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
content_types_accepted_param(Config) ->
|
||||
doc("When a parameter is returned from the content_types_accepted "
|
||||
"callback, and the same parameter is found in the content-type "
|
||||
"header, the negotiation succeeds and the request is processed."),
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:put(ConnPid, "/content_types_accepted?param", [
|
||||
{<<"accept-encoding">>, <<"gzip">>},
|
||||
{<<"content-type">>, <<"text/plain;charset=UTF-8">>}
|
||||
], "12345"),
|
||||
{response, fin, 204, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
content_types_accepted_wildcard_param_no_content_type_param(Config) ->
|
||||
doc("When a wildcard is returned for parameters from the "
|
||||
"content_types_accepted callback, a content-type header "
|
||||
|
@ -381,6 +445,17 @@ content_types_provided_wildcard_param_no_accept_header(Config) ->
|
|||
{ok, <<"[]">>} = gun:await_body(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
delete_resource_missing(Config) ->
|
||||
doc("When a resource accepts the DELETE method and the "
|
||||
"delete_resource callback is not exported, the "
|
||||
"resource is incorrect and a 500 response is expected."),
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:delete(ConnPid, "/delete_resource?missing", [
|
||||
{<<"accept-encoding">>, <<"gzip">>}
|
||||
]),
|
||||
{response, _, 500, _} = gun:await(ConnPid, Ref),
|
||||
ok.
|
||||
|
||||
error_on_malformed_accept(Config) ->
|
||||
doc("A malformed Accept header must result in a 400 response."),
|
||||
do_error_on_malformed_header(Config, <<"accept">>).
|
||||
|
@ -443,6 +518,89 @@ expires_undefined(Config) ->
|
|||
false = lists:keyfind(<<"expires">>, 1, Headers),
|
||||
ok.
|
||||
|
||||
generate_etag_missing(Config) ->
|
||||
doc("The etag header must not be sent when "
|
||||
"the generate_etag callback is not exported."),
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:get(ConnPid, "/generate_etag?missing", [
|
||||
{<<"accept-encoding">>, <<"gzip">>}
|
||||
]),
|
||||
{response, _, 200, Headers} = gun:await(ConnPid, Ref),
|
||||
false = lists:keyfind(<<"etag">>, 1, Headers),
|
||||
ok.
|
||||
|
||||
generate_etag_binary_strong(Config) ->
|
||||
doc("The etag header must be sent when the generate_etag "
|
||||
"callback returns a strong binary. (RFC7232 2.3)"),
|
||||
do_generate_etag(Config, "binary-strong-quoted",
|
||||
[], 200, {<<"etag">>, <<"\"etag-header-value\"">>}).
|
||||
|
||||
generate_etag_binary_weak(Config) ->
|
||||
doc("The etag header must be sent when the generate_etag "
|
||||
"callback returns a weak binary. (RFC7232 2.3)"),
|
||||
do_generate_etag(Config, "binary-weak-quoted",
|
||||
[], 200, {<<"etag">>, <<"W/\"etag-header-value\"">>}).
|
||||
|
||||
generate_etag_invalid_binary_strong_unquoted(Config) ->
|
||||
doc("When Cowboy cannot parse the generate_etag callback's "
|
||||
"return value, a 500 response is returned without the etag header."),
|
||||
do_generate_etag(Config, "binary-strong-unquoted", [], 500, false).
|
||||
|
||||
generate_etag_invalid_binary_weak_unquoted(Config) ->
|
||||
doc("When Cowboy cannot parse the generate_etag callback's "
|
||||
"return value, a 500 response is returned without the etag header."),
|
||||
do_generate_etag(Config, "binary-weak-unquoted", [], 500, false).
|
||||
|
||||
generate_etag_tuple_strong(Config) ->
|
||||
doc("The etag header must be sent when the generate_etag "
|
||||
"callback returns a strong tuple. (RFC7232 2.3)"),
|
||||
do_generate_etag(Config, "tuple-strong",
|
||||
[], 200, {<<"etag">>, <<"\"etag-header-value\"">>}).
|
||||
|
||||
generate_etag_tuple_weak(Config) ->
|
||||
doc("The etag header must be sent when the generate_etag "
|
||||
"callback returns a weak tuple. (RFC7232 2.3)"),
|
||||
do_generate_etag(Config, "tuple-weak",
|
||||
[], 200, {<<"etag">>, <<"W/\"etag-header-value\"">>}).
|
||||
|
||||
if_none_match_binary_strong(Config) ->
|
||||
doc("When the if-none-match header matches a strong etag, "
|
||||
"a 304 not modified response is returned. (RFC7232 3.2)"),
|
||||
do_generate_etag(Config, "binary-strong-quoted",
|
||||
[{<<"if-none-match">>, <<"\"etag-header-value\"">>}],
|
||||
304, {<<"etag">>, <<"\"etag-header-value\"">>}).
|
||||
|
||||
if_none_match_binary_weak(Config) ->
|
||||
doc("When the if-none-match header matches a weak etag, "
|
||||
"a 304 not modified response is returned. (RFC7232 3.2)"),
|
||||
do_generate_etag(Config, "binary-weak-quoted",
|
||||
[{<<"if-none-match">>, <<"W/\"etag-header-value\"">>}],
|
||||
304, {<<"etag">>, <<"W/\"etag-header-value\"">>}).
|
||||
|
||||
if_none_match_tuple_strong(Config) ->
|
||||
doc("When the if-none-match header matches a strong etag, "
|
||||
"a 304 not modified response is returned. (RFC7232 3.2)"),
|
||||
do_generate_etag(Config, "tuple-strong",
|
||||
[{<<"if-none-match">>, <<"\"etag-header-value\"">>}],
|
||||
304, {<<"etag">>, <<"\"etag-header-value\"">>}).
|
||||
|
||||
if_none_match_tuple_weak(Config) ->
|
||||
doc("When the if-none-match header matches a weak etag, "
|
||||
"a 304 not modified response is returned. (RFC7232 3.2)"),
|
||||
do_generate_etag(Config, "tuple-weak",
|
||||
[{<<"if-none-match">>, <<"W/\"etag-header-value\"">>}],
|
||||
304, {<<"etag">>, <<"W/\"etag-header-value\"">>}).
|
||||
|
||||
do_generate_etag(Config, Qs, ReqHeaders, Status, Etag) ->
|
||||
ConnPid = gun_open(Config),
|
||||
Ref = gun:get(ConnPid, "/generate_etag?" ++ Qs, [
|
||||
{<<"accept-encoding">>, <<"gzip">>}
|
||||
|ReqHeaders
|
||||
]),
|
||||
{response, _, Status, RespHeaders} = gun:await(ConnPid, Ref),
|
||||
Etag = lists:keyfind(<<"etag">>, 1, RespHeaders),
|
||||
ok.
|
||||
|
||||
if_range_etag_equal(Config) ->
|
||||
doc("When the if-range header matches, a 206 partial content "
|
||||
"response is expected for an otherwise valid range request. (RFC7233 3.2)"),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue