diff options
Diffstat (limited to 'test/http_SUITE.erl')
-rw-r--r-- | test/http_SUITE.erl | 282 |
1 files changed, 233 insertions, 49 deletions
diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index c90e585..afe62c3 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -1,4 +1,4 @@ -%% Copyright (c) 2011-2012, Loïc Hoguin <[email protected]> +%% Copyright (c) 2011-2013, Loïc Hoguin <[email protected]> %% Copyright (c) 2011, Anthony Ramine <[email protected]> %% %% Permission to use, copy, modify, and/or distribute this software for any @@ -45,21 +45,26 @@ -export([nc_zero/1]). -export([onrequest/1]). -export([onrequest_reply/1]). +-export([onresponse_capitalize/1]). -export([onresponse_crash/1]). -export([onresponse_reply/1]). -export([pipeline/1]). -export([rest_bad_accept/1]). +-export([rest_created_path/1]). -export([rest_expires/1]). -export([rest_keepalive/1]). -export([rest_keepalive_post/1]). -export([rest_missing_get_callbacks/1]). -export([rest_missing_put_callbacks/1]). -export([rest_nodelete/1]). +-export([rest_patch/1]). -export([rest_resource_etags/1]). -export([rest_resource_etags_if_none_match/1]). -export([set_resp_body/1]). -export([set_resp_header/1]). -export([set_resp_overwrite/1]). +-export([slowloris/1]). +-export([slowloris2/1]). -export([static_attribute_etag/1]). -export([static_function_etag/1]). -export([static_mimetypes_function/1]). @@ -68,14 +73,24 @@ -export([static_test_file/1]). -export([static_test_file_css/1]). -export([stream_body_set_resp/1]). +-export([stream_body_set_resp_close/1]). -export([te_chunked/1]). +-export([te_chunked_chopped/1]). -export([te_chunked_delayed/1]). -export([te_identity/1]). %% ct. all() -> - [{group, http}, {group, https}, {group, onrequest}, {group, onresponse}]. + [ + {group, http}, + {group, https}, + {group, http_compress}, + {group, https_compress}, + {group, onrequest}, + {group, onresponse}, + {group, onresponse_capitalize} + ]. groups() -> Tests = [ @@ -98,17 +113,21 @@ groups() -> nc_zero, pipeline, rest_bad_accept, + rest_created_path, rest_expires, rest_keepalive, rest_keepalive_post, rest_missing_get_callbacks, rest_missing_put_callbacks, rest_nodelete, + rest_patch, rest_resource_etags, rest_resource_etags_if_none_match, set_resp_body, set_resp_header, set_resp_overwrite, + slowloris, + slowloris2, static_attribute_etag, static_function_etag, static_mimetypes_function, @@ -117,13 +136,17 @@ groups() -> static_test_file, static_test_file_css, stream_body_set_resp, + stream_body_set_resp_close, te_chunked, + te_chunked_chopped, te_chunked_delayed, te_identity ], [ {http, [], Tests}, {https, [], Tests}, + {http_compress, [], Tests}, + {https_compress, [], Tests}, {onrequest, [], [ onrequest, onrequest_reply @@ -131,11 +154,13 @@ groups() -> {onresponse, [], [ onresponse_crash, onresponse_reply + ]}, + {onresponse_capitalize, [], [ + onresponse_capitalize ]} ]. init_per_suite(Config) -> - application:start(inets), application:start(crypto), application:start(ranch), application:start(cowboy), @@ -145,7 +170,6 @@ end_per_suite(_Config) -> application:stop(cowboy), application:stop(ranch), application:stop(crypto), - application:stop(inets), ok. init_per_group(http, Config) -> @@ -153,7 +177,7 @@ init_per_group(http, Config) -> Transport = ranch_tcp, Config1 = init_static_dir(Config), {ok, _} = cowboy:start_http(http, 100, [{port, Port}], [ - {dispatch, init_dispatch(Config1)}, + {env, [{dispatch, init_dispatch(Config1)}]}, {max_keepalive, 50}, {timeout, 500} ]), @@ -172,18 +196,51 @@ init_per_group(https, Config) -> application:start(public_key), application:start(ssl), {ok, _} = cowboy:start_https(https, 100, Opts ++ [{port, Port}], [ - {dispatch, init_dispatch(Config1)}, + {env, [{dispatch, init_dispatch(Config1)}]}, {max_keepalive, 50}, {timeout, 500} ]), {ok, Client} = cowboy_client:init(Opts), [{scheme, <<"https">>}, {port, Port}, {opts, Opts}, {transport, Transport}, {client, Client}|Config1]; -init_per_group(onrequest, Config) -> +init_per_group(http_compress, Config) -> Port = 33082, Transport = ranch_tcp, + Config1 = init_static_dir(Config), + {ok, _} = cowboy:start_http(http_compress, 100, [{port, Port}], [ + {compress, true}, + {env, [{dispatch, init_dispatch(Config1)}]}, + {max_keepalive, 50}, + {timeout, 500} + ]), + {ok, Client} = cowboy_client:init([]), + [{scheme, <<"http">>}, {port, Port}, {opts, []}, + {transport, Transport}, {client, Client}|Config1]; +init_per_group(https_compress, Config) -> + Port = 33083, + Transport = ranch_ssl, + Opts = [ + {certfile, ?config(data_dir, Config) ++ "cert.pem"}, + {keyfile, ?config(data_dir, Config) ++ "key.pem"}, + {password, "cowboy"} + ], + Config1 = init_static_dir(Config), + application:start(public_key), + application:start(ssl), + {ok, _} = cowboy:start_https(https_compress, 100, Opts ++ [{port, Port}], [ + {compress, true}, + {env, [{dispatch, init_dispatch(Config1)}]}, + {max_keepalive, 50}, + {timeout, 500} + ]), + {ok, Client} = cowboy_client:init(Opts), + [{scheme, <<"https">>}, {port, Port}, {opts, Opts}, + {transport, Transport}, {client, Client}|Config1]; +init_per_group(onrequest, Config) -> + Port = 33084, + Transport = ranch_tcp, {ok, _} = cowboy:start_http(onrequest, 100, [{port, Port}], [ - {dispatch, init_dispatch(Config)}, + {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {onrequest, fun onrequest_hook/1}, {timeout, 500} @@ -192,25 +249,37 @@ init_per_group(onrequest, Config) -> [{scheme, <<"http">>}, {port, Port}, {opts, []}, {transport, Transport}, {client, Client}|Config]; init_per_group(onresponse, Config) -> - Port = 33083, + Port = 33085, Transport = ranch_tcp, {ok, _} = cowboy:start_http(onresponse, 100, [{port, Port}], [ - {dispatch, init_dispatch(Config)}, + {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {onresponse, fun onresponse_hook/4}, {timeout, 500} ]), {ok, Client} = cowboy_client:init([]), [{scheme, <<"http">>}, {port, Port}, {opts, []}, + {transport, Transport}, {client, Client}|Config]; +init_per_group(onresponse_capitalize, Config) -> + Port = 33086, + Transport = ranch_tcp, + {ok, _} = cowboy:start_http(onresponse_capitalize, 100, [{port, Port}], [ + {env, [{dispatch, init_dispatch(Config)}]}, + {max_keepalive, 50}, + {onresponse, fun onresponse_capitalize_hook/4}, + {timeout, 500} + ]), + {ok, Client} = cowboy_client:init([]), + [{scheme, <<"http">>}, {port, Port}, {opts, []}, {transport, Transport}, {client, Client}|Config]. -end_per_group(https, Config) -> +end_per_group(Group, Config) when Group =:= https; Group =:= https_compress -> cowboy:stop_listener(https), application:stop(ssl), application:stop(public_key), end_static_dir(Config), ok; -end_per_group(http, Config) -> +end_per_group(Group, Config) when Group =:= http; Group =:= http_compress -> cowboy:stop_listener(http), end_static_dir(Config); end_per_group(Name, _) -> @@ -220,54 +289,60 @@ end_per_group(Name, _) -> %% Dispatch configuration. init_dispatch(Config) -> - [ - {[<<"localhost">>], [ - {[<<"chunked_response">>], chunked_handler, []}, - {[<<"init_shutdown">>], http_handler_init_shutdown, []}, - {[<<"long_polling">>], http_handler_long_polling, []}, - {[<<"headers">>, <<"dupe">>], http_handler, + cowboy_router:compile([ + {"localhost", [ + {"/chunked_response", chunked_handler, []}, + {"/init_shutdown", http_handler_init_shutdown, []}, + {"/long_polling", http_handler_long_polling, []}, + {"/headers/dupe", http_handler, [{headers, [{<<"connection">>, <<"close">>}]}]}, - {[<<"set_resp">>, <<"header">>], http_handler_set_resp, + {"/set_resp/header", http_handler_set_resp, [{headers, [{<<"vary">>, <<"Accept">>}]}]}, - {[<<"set_resp">>, <<"overwrite">>], http_handler_set_resp, + {"/set_resp/overwrite", http_handler_set_resp, [{headers, [{<<"server">>, <<"DesireDrive/1.0">>}]}]}, - {[<<"set_resp">>, <<"body">>], http_handler_set_resp, + {"/set_resp/body", http_handler_set_resp, [{body, <<"A flameless dance does not equal a cycle">>}]}, - {[<<"stream_body">>, <<"set_resp">>], http_handler_stream_body, + {"/stream_body/set_resp", http_handler_stream_body, [{reply, set_resp}, {body, <<"stream_body_set_resp">>}]}, - {[<<"static">>, '...'], cowboy_static, + {"/stream_body/set_resp_close", + http_handler_stream_body, [ + {reply, set_resp_close}, + {body, <<"stream_body_set_resp_close">>}]}, + {"/static/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {mimetypes, [{<<".css">>, [<<"text/css">>]}]}]}, - {[<<"static_mimetypes_function">>, '...'], cowboy_static, + {"/static_mimetypes_function/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {mimetypes, {fun(Path, data) when is_binary(Path) -> [<<"text/html">>] end, data}}]}, - {[<<"handler_errors">>], http_handler_errors, []}, - {[<<"static_attribute_etag">>, '...'], cowboy_static, + {"/handler_errors", http_handler_errors, []}, + {"/static_attribute_etag/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {etag, {attributes, [filepath, filesize, inode, mtime]}}]}, - {[<<"static_function_etag">>, '...'], cowboy_static, + {"/static_function_etag/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {etag, {fun static_function_etag/2, etag_data}}]}, - {[<<"static_specify_file">>, '...'], cowboy_static, + {"/static_specify_file/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {mimetypes, [{<<".css">>, [<<"text/css">>]}]}, {file, <<"test_file.css">>}]}, - {[<<"multipart">>], http_handler_multipart, []}, - {[<<"echo">>, <<"body">>], http_handler_echo_body, []}, - {[<<"bad_accept">>], rest_simple_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, []}, - {[<<"resetags">>], rest_resource_etags, []}, - {[<<"rest_expires">>], rest_expires, []}, - {[<<"loop_timeout">>], http_handler_loop_timeout, []}, - {[], http_handler, []} + {"/multipart", http_handler_multipart, []}, + {"/echo/body", http_handler_echo_body, []}, + {"/bad_accept", rest_simple_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, []}, + {"/patch", rest_patch_resource, []}, + {"/created_path", rest_created_path_resource, []}, + {"/resetags", rest_resource_etags, []}, + {"/rest_expires", rest_expires, []}, + {"/loop_timeout", http_handler_loop_timeout, []}, + {"/", http_handler, []} ]} - ]. + ]). init_static_dir(Config) -> Dir = filename:join(?config(priv_dir, Config), "static"), @@ -408,10 +483,16 @@ check_status(Config) -> {Ret, URL} end || {Status, URL} <- Tests]. -%% @todo Convert to cowboy_client. chunked_response(Config) -> - {ok, {{"HTTP/1.1", 200, "OK"}, _, "chunked_handler\r\nworks fine!"}} - = httpc:request(binary_to_list(build_url("/chunked_response", Config))). + Client = ?config(client, Config), + {ok, Client2} = cowboy_client:request(<<"GET">>, + build_url("/chunked_response", Config), Client), + {ok, 200, Headers, Client3} = cowboy_client:response(Client2), + true = lists:keymember(<<"transfer-encoding">>, 1, Headers), + {ok, Transport, Socket} = cowboy_client:transport(Client3), + {ok, <<"11\r\nchunked_handler\r\n\r\nB\r\nworks fine!\r\n0\r\n\r\n">>} + = Transport:recv(Socket, 44, 1000), + {error, closed} = cowboy_client:response(Client3). %% Check if sending requests whose size is around the MTU breaks something. echo_body(Config) -> @@ -503,8 +584,8 @@ http10_hostless(Config) -> ranch:start_listener(Name, 5, ?config(transport, Config), ?config(opts, Config) ++ [{port, Port10}], cowboy_protocol, [ - {dispatch, [{'_', [ - {[<<"http1.0">>, <<"hostless">>], http_handler, []}]}]}, + {env, [{dispatch, cowboy_router:compile([ + {'_', [{"/http1.0/hostless", http_handler, []}]}])}]}, {max_keepalive, 50}, {timeout, 500}] ), @@ -517,7 +598,8 @@ keepalive_max(Config) -> URL = build_url("/", Config), ok = keepalive_max_loop(Client, URL, 50). -keepalive_max_loop(_, _, 0) -> +keepalive_max_loop(Client, _, 0) -> + {error, closed} = cowboy_client:response(Client), ok; keepalive_max_loop(Client, URL, N) -> Headers = [{<<"connection">>, <<"keep-alive">>}], @@ -536,7 +618,8 @@ keepalive_nl(Config) -> URL = build_url("/", Config), ok = keepalive_nl_loop(Client, URL, 10). -keepalive_nl_loop(_, _, 0) -> +keepalive_nl_loop(Client, _, 0) -> + {error, closed} = cowboy_client:response(Client), ok; keepalive_nl_loop(Client, URL, N) -> Headers = [{<<"connection">>, <<"keep-alive">>}], @@ -619,6 +702,21 @@ onrequest_hook(Req) -> Req3 end. +onresponse_capitalize(Config) -> + Client = ?config(client, Config), + {ok, Client2} = cowboy_client:request(<<"GET">>, + build_url("/", Config), Client), + {ok, Transport, Socket} = cowboy_client:transport(Client2), + {ok, Data} = Transport:recv(Socket, 0, 1000), + false = nomatch =:= binary:match(Data, <<"Content-Length">>). + +%% Hook for the above onresponse_capitalize test. +onresponse_capitalize_hook(Status, Headers, Body, Req) -> + Headers2 = [{cowboy_bstr:capitalize_token(N), V} + || {N, V} <- Headers], + {ok, Req2} = cowboy_req:reply(Status, Headers2, Body, Req), + Req2. + onresponse_crash(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, @@ -668,6 +766,18 @@ rest_bad_accept(Config) -> Client), {ok, 400, _, _} = cowboy_client:response(Client2). +rest_created_path(Config) -> + Headers = [{<<"content-type">>, <<"text/plain">>}], + Body = <<"Whatever">>, + Client = ?config(client, Config), + URL = build_url("/created_path", Config), + {ok, Client2} = cowboy_client:request(<<"POST">>, URL, Headers, + Body, Client), + {ok, 303, ResHeaders, _} = cowboy_client:response(Client2), + {<<"location">>, _Location} = + lists:keyfind(<<"location">>, 1, ResHeaders), + ok. + rest_expires(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, @@ -742,6 +852,21 @@ rest_nodelete(Config) -> build_url("/nodelete", Config), Client), {ok, 500, _, _} = cowboy_client:response(Client2). +rest_patch(Config) -> + Tests = [ + {204, [{<<"content-type">>, <<"text/plain">>}], <<"whatever">>}, + {422, [{<<"content-type">>, <<"text/plain">>}], <<"false">>}, + {400, [{<<"content-type">>, <<"text/plain">>}], <<"halt">>}, + {415, [{<<"content-type">>, <<"application/json">>}], <<"bad_content_type">>} + ], + Client = ?config(client, Config), + _ = [begin + {ok, Client2} = cowboy_client:request(<<"PATCH">>, + build_url("/patch", Config), Headers, Body, Client), + {ok, Status, _, _} = cowboy_client:response(Client2), + ok + end || {Status, Headers, Body} <- Tests]. + rest_resource_get_etag(Config, Type) -> rest_resource_get_etag(Config, Type, []). @@ -806,6 +931,34 @@ set_resp_overwrite(Config) -> {<<"server">>, <<"DesireDrive/1.0">>} = lists:keyfind(<<"server">>, 1, Headers). +slowloris(Config) -> + Client = ?config(client, Config), + Transport = ?config(transport, Config), + {ok, Client2} = cowboy_client:connect( + Transport, "localhost", ?config(port, Config), Client), + try + [begin + {ok, _} = cowboy_client:raw_request([C], Client2), + receive after 25 -> ok end + end || C <- "GET / HTTP/1.1\r\nHost: localhost\r\n" + "User-Agent: Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US)\r\n" + "Cookie: name=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\r\n\r\n"], + error(failure) + catch error:{badmatch, _} -> + ok + end. + +slowloris2(Config) -> + Client = ?config(client, Config), + Transport = ?config(transport, Config), + {ok, Client2} = cowboy_client:connect( + Transport, "localhost", ?config(port, Config), Client), + {ok, _} = cowboy_client:raw_request("GET / HTTP/1.1\r\n", Client2), + receive after 300 -> ok end, + {ok, _} = cowboy_client:raw_request("Host: localhost\r\n", Client2), + receive after 300 -> ok end, + {ok, 408, _, _} = cowboy_client:response(Client2). + static_attribute_etag(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, @@ -892,6 +1045,22 @@ stream_body_set_resp(Config) -> {ok, <<"stream_body_set_resp">>, _} = cowboy_client:response_body(Client3). +stream_body_set_resp_close(Config) -> + Client = ?config(client, Config), + {ok, Client2} = cowboy_client:request(<<"GET">>, + build_url("/stream_body/set_resp_close", Config), Client), + {ok, 200, _, Client3} = cowboy_client:response(Client2), + {ok, Transport, Socket} = cowboy_client:transport(Client3), + case element(7, Client3) of + <<"stream_body_set_resp_close">> -> + ok; + Buffer -> + {ok, Rest} = Transport:recv(Socket, 26 - byte_size(Buffer), 1000), + <<"stream_body_set_resp_close">> = << Buffer/binary, Rest/binary >>, + ok + end, + {error, closed} = Transport:recv(Socket, 0, 1000). + te_chunked(Config) -> Client = ?config(client, Config), Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])), @@ -903,6 +1072,21 @@ te_chunked(Config) -> {ok, 200, _, Client3} = cowboy_client:response(Client2), {ok, Body, _} = cowboy_client:response_body(Client3). +te_chunked_chopped(Config) -> + Client = ?config(client, Config), + Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])), + Body2 = iolist_to_binary(body_to_chunks(50, Body, [])), + {ok, Client2} = cowboy_client:request(<<"GET">>, + build_url("/echo/body", Config), + [{<<"transfer-encoding">>, <<"chunked">>}], Client), + {ok, Transport, Socket} = cowboy_client:transport(Client2), + _ = [begin + ok = Transport:send(Socket, << C >>), + ok = timer:sleep(10) + end || << C >> <= Body2], + {ok, 200, _, Client3} = cowboy_client:response(Client2), + {ok, Body, _} = cowboy_client:response_body(Client3). + te_chunked_delayed(Config) -> Client = ?config(client, Config), Body = list_to_binary(io_lib:format("~p", [lists:seq(1, 100)])), |