diff options
-rw-r--r-- | manual/cowboy_req.md | 9 | ||||
-rw-r--r-- | manual/cowboy_rest.md | 11 | ||||
-rw-r--r-- | src/cowboy_http.erl | 20 | ||||
-rw-r--r-- | src/cowboy_req.erl | 10 | ||||
-rw-r--r-- | src/cowboy_rest.erl | 6 | ||||
-rw-r--r-- | src/cowboy_spdy.erl | 46 | ||||
-rw-r--r-- | test/http_SUITE.erl | 97 | ||||
-rw-r--r-- | test/http_SUITE_data/rest_post_charset_resource.erl | 15 | ||||
-rw-r--r-- | test/spdy_SUITE.erl | 14 |
9 files changed, 152 insertions, 76 deletions
diff --git a/manual/cowboy_req.md b/manual/cowboy_req.md index f10120a..8a765dc 100644 --- a/manual/cowboy_req.md +++ b/manual/cowboy_req.md @@ -176,7 +176,7 @@ Request related exports > | accept-language | `[{LanguageTag, Quality}]` | > | authorization | `{AuthType, Credentials}` | > | content-length | `non_neg_integer()` | -> | content-type | `{Type, SubType, Params}` | +> | content-type | `{Type, SubType, ContentTypeParams}` | > | cookie | `[{binary(), binary()}]` | > | expect | `[Expect | {Expect, ExpectValue, Params}]` | > | if-match | `'*' | [{weak | strong, OpaqueTag}]` | @@ -192,7 +192,7 @@ Request related exports > Types for the above table: > * Type = SubType = Charset = Encoding = LanguageTag = binary() > * AuthType = Expect = OpaqueTag = Unit = binary() -> * Params = [{binary(), binary()}] +> * Params = ContentTypeParams = [{binary(), binary()}] > * Quality = 0..1000 > * AcceptExt = [{binary(), binary()} | binary()] > * Credentials - see below @@ -201,8 +201,9 @@ Request related exports > The cookie names and values, the values of the sec-websocket-protocol > and x-forwarded-for headers, the values in `AcceptExt` and `Params`, > the authorization `Credentials`, the `ExpectValue` and `OpaqueTag` -> are case sensitive. All other values are case insensitive and -> will be returned as lowercase. +> are case sensitive. All values in `ContentTypeParams` are case sensitive +> except the value of the charset parameter, which is case insensitive. +> All other values are case insensitive and will be returned as lowercase. > > The headers accept, accept-encoding and cookie headers can return > an empty list. Others will return `{error, badarg}` if the header diff --git a/manual/cowboy_rest.md b/manual/cowboy_rest.md index 4d5862a..110e224 100644 --- a/manual/cowboy_rest.md +++ b/manual/cowboy_rest.md @@ -168,7 +168,9 @@ REST callbacks description > Cowboy will select the most appropriate content-type from the list. > If any parameter is acceptable, then the tuple form should be used > with parameters set to `'*'`. If the parameters value is set to `[]` -> only content-type values with no parameters will be accepted. +> only content-type values with no parameters will be accepted. All +> parameter values are treated in a case sensitive manner except the +> `charset` parameter, if present, which is case insensitive. > > This function will be called for POST, PUT and PATCH requests. > It is entirely possible to define different callbacks for different @@ -219,7 +221,9 @@ REST callbacks description > Cowboy will select the most appropriate content-type from the list. > If any parameter is acceptable, then the tuple form should be used > with parameters set to `'*'`. If the parameters value is set to `[]` -> only content-type values with no parameters will be accepted. +> only content-type values with no parameters will be accepted. All +> parameter values are treated in a case sensitive manner except the +> `charset` parameter, if present, which is case insensitive. > > The `ProvideResource` value is the name of the callback that will > be called if the content-type matches. It is defined as follow. @@ -299,7 +303,8 @@ REST callbacks description > This value will be sent as the value of the etag header. > > If a binary is returned, then the value will be parsed -> to the tuple form automatically. +> to the tuple form automatically. The value must be in +> the same format as the etag header, including quotes. ### is_authorized diff --git a/src/cowboy_http.erl b/src/cowboy_http.erl index af60dd9..d2bdf3b 100644 --- a/src/cowboy_http.erl +++ b/src/cowboy_http.erl @@ -162,14 +162,26 @@ cookie_value(<< C, Rest/binary >>, Fun, Acc) -> cookie_value(Rest, Fun, << Acc/binary, C >>). %% @doc Parse a content type. +%% +%% We lowercase the charset header as we know it's case insensitive. -spec content_type(binary()) -> any(). content_type(Data) -> media_type(Data, fun (Rest, Type, SubType) -> - params(Rest, - fun (<<>>, Params) -> {Type, SubType, Params}; - (_Rest2, _) -> {error, badarg} - end) + params(Rest, + fun (<<>>, Params) -> + case lists:keyfind(<<"charset">>, 1, Params) of + false -> + {Type, SubType, Params}; + {_, Charset} -> + Charset2 = cowboy_bstr:to_lower(Charset), + Params2 = lists:keyreplace(<<"charset">>, + 1, Params, {<<"charset">>, Charset2}), + {Type, SubType, Params2} + end; + (_Rest2, _) -> + {error, badarg} + end) end). %% @doc Parse a media range. diff --git a/src/cowboy_req.erl b/src/cowboy_req.erl index 093663c..0e1c8a7 100644 --- a/src/cowboy_req.erl +++ b/src/cowboy_req.erl @@ -492,7 +492,10 @@ cookie(Name, Req=#http_req{cookies=undefined}, Default) when is_binary(Name) -> {ok, undefined, Req2} -> {Default, Req2#http_req{cookies=[]}}; {ok, Cookies, Req2} -> - cookie(Name, Req2#http_req{cookies=Cookies}, Default) + cookie(Name, Req2#http_req{cookies=Cookies}, Default); + %% Flash player incorrectly sends an empty Cookie header. + {error, badarg} -> + {Default, Req#http_req{cookies=[]}} end; cookie(Name, Req, Default) -> case lists:keyfind(Name, 1, Req#http_req.cookies) of @@ -507,7 +510,10 @@ cookies(Req=#http_req{cookies=undefined}) -> {ok, undefined, Req2} -> {[], Req2#http_req{cookies=[]}}; {ok, Cookies, Req2} -> - cookies(Req2#http_req{cookies=Cookies}) + cookies(Req2#http_req{cookies=Cookies}); + %% Flash player incorrectly sends an empty Cookie header. + {error, badarg} -> + {[], Req#http_req{cookies=[]}} end; cookies(Req=#http_req{cookies=Cookies}) -> {Cookies, Req}. diff --git a/src/cowboy_rest.erl b/src/cowboy_rest.erl index b87c7df..ecbe7bc 100644 --- a/src/cowboy_rest.erl +++ b/src/cowboy_rest.erl @@ -145,6 +145,9 @@ allowed_methods(Req, State=#state{method=Method}) -> end end. +method_not_allowed(Req, State, []) -> + Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req), + respond(Req2, State, 405); method_not_allowed(Req, State, Methods) -> << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>, Req2 = cowboy_req:set_resp_header(<<"allow">>, Allow, Req), @@ -186,6 +189,9 @@ valid_entity_length(Req, State) -> %% you should do it directly in the options/2 call using set_resp_headers. options(Req, State=#state{allowed_methods=Methods, method= <<"OPTIONS">>}) -> case call(Req, State, options) of + no_call when Methods =:= [] -> + Req2 = cowboy_req:set_resp_header(<<"allow">>, <<>>, Req), + respond(Req2, State, 200); no_call -> << ", ", Allow/binary >> = << << ", ", M/binary >> || M <- Methods >>, diff --git a/src/cowboy_spdy.erl b/src/cowboy_spdy.erl index 3fe477b..182e6da 100644 --- a/src/cowboy_spdy.erl +++ b/src/cowboy_spdy.erl @@ -42,6 +42,7 @@ %% Internal transport functions. -export([name/0]). -export([send/2]). +-export([sendfile/2]). -record(child, { streamid :: non_neg_integer(), @@ -174,6 +175,14 @@ loop(State=#state{parent=Parent, socket=Socket, transport=Transport, Children2 = lists:keyreplace(StreamID, #child.streamid, Children, Child#child{output=fin}), loop(State#state{children=Children2}); + {sendfile, {Pid, StreamID}, Filepath} + when Pid =:= self() -> + Child = #child{output=nofin} = lists:keyfind(StreamID, + #child.streamid, Children), + data_from_file(State, StreamID, Filepath), + Children2 = lists:keyreplace(StreamID, + #child.streamid, Children, Child#child{output=fin}), + loop(State#state{children=Children2}); {'EXIT', Parent, Reason} -> exit(Reason); {'EXIT', Pid, _} -> @@ -212,12 +221,14 @@ system_code_change(Misc, _, _, _) -> {ok, Misc}. %% We do not support SYN_STREAM with FLAG_UNIDIRECTIONAL set. -control_frame(State, << _:38, 1:1, _:26, StreamID:31, _/bits >>) -> +control_frame(State, << 1:1, 3:15, 1:16, _:6, 1:1, _:26, + StreamID:31, _/bits >>) -> rst_stream(State, StreamID, internal_error), loop(State); %% We do not support Associated-To-Stream-ID and CREDENTIAL Slot. -control_frame(State, << _:65, StreamID:31, _:1, AssocToStreamID:31, - _:8, Slot:8, _/bits >>) when AssocToStreamID =/= 0; Slot =/= 0 -> +control_frame(State, << 1:1, 3:15, 1:16, _:33, StreamID:31, _:1, + AssocToStreamID:31, _:8, Slot:8, _/bits >>) + when AssocToStreamID =/= 0; Slot =/= 0 -> rst_stream(State, StreamID, internal_error), loop(State); %% SYN_STREAM @@ -435,6 +446,27 @@ data(#state{socket=Socket, transport=Transport}, IsFin, StreamID, Data) -> << 0:1, StreamID:31, Flags:8, Len:24 >>, Data]). +data_from_file(#state{socket=Socket, transport=Transport}, + StreamID, Filepath) -> + {ok, IoDevice} = file:open(Filepath, [read, binary, raw]), + data_from_file(Socket, Transport, StreamID, IoDevice). + +data_from_file(Socket, Transport, StreamID, IoDevice) -> + case file:read(IoDevice, 16#1fff) of + eof -> + _ = Transport:send(Socket, << 0:1, StreamID:31, 1:8, 0:24 >>), + ok; + {ok, Data} -> + Len = byte_size(Data), + Data2 = [<< 0:1, StreamID:31, 0:8, Len:24 >>, Data], + case Transport:send(Socket, Data2) of + ok -> + data_from_file(Socket, Transport, StreamID, IoDevice); + {error, _} -> + ok + end + end. + %% Request process. request_init(Parent, StreamID, Peer, @@ -540,10 +572,16 @@ stream_close(Socket = {Pid, _}) -> ok. %% Internal transport functions. -%% @todo recv, sendfile +%% @todo recv name() -> spdy. send(Socket, Data) -> stream_data(Socket, Data). + +%% We don't wait for the result of the actual sendfile call, +%% therefore we can't know how much was actually sent. +sendfile(Socket = {Pid, _}, Filepath) -> + _ = Pid ! {sendfile, Socket, Filepath}, + {ok, undefined}. diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index 21cdd4b..2d7f420 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -64,6 +64,7 @@ -export([rest_options_default/1]). -export([rest_param_all/1]). -export([rest_patch/1]). +-export([rest_post_charset/1]). -export([rest_postonly/1]). -export([rest_resource_etags/1]). -export([rest_resource_etags_if_none_match/1]). @@ -138,6 +139,7 @@ groups() -> rest_options_default, rest_param_all, rest_patch, + rest_post_charset, rest_postonly, rest_resource_etags, rest_resource_etags_if_none_match, @@ -187,9 +189,13 @@ init_per_suite(Config) -> application:start(crypto), application:start(ranch), application:start(cowboy), - Config. + Dir = ?config(priv_dir, Config) ++ "/static", + ct_helper:create_static_dir(Dir), + [{static_dir, Dir}|Config]. -end_per_suite(_Config) -> +end_per_suite(Config) -> + Dir = ?config(static_dir, Config), + ct_helper:delete_static_dir(Dir), application:stop(cowboy), application:stop(ranch), application:stop(crypto), @@ -197,62 +203,58 @@ end_per_suite(_Config) -> init_per_group(http, Config) -> Transport = ranch_tcp, - Config1 = init_static_dir(Config), {ok, _} = cowboy:start_http(http, 100, [{port, 0}], [ - {env, [{dispatch, init_dispatch(Config1)}]}, + {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {timeout, 500} ]), Port = ranch:get_port(http), {ok, Client} = cowboy_client:init([]), [{scheme, <<"http">>}, {port, Port}, {opts, []}, - {transport, Transport}, {client, Client}|Config1]; + {transport, Transport}, {client, Client}|Config]; init_per_group(https, Config) -> Transport = ranch_ssl, {_, Cert, Key} = ct_helper:make_certs(), Opts = [{cert, Cert}, {key, Key}], - Config1 = init_static_dir(Config), application:start(public_key), application:start(ssl), {ok, _} = cowboy:start_https(https, 100, Opts ++ [{port, 0}], [ - {env, [{dispatch, init_dispatch(Config1)}]}, + {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {timeout, 500} ]), Port = ranch:get_port(https), {ok, Client} = cowboy_client:init(Opts), [{scheme, <<"https">>}, {port, Port}, {opts, Opts}, - {transport, Transport}, {client, Client}|Config1]; + {transport, Transport}, {client, Client}|Config]; init_per_group(http_compress, Config) -> Transport = ranch_tcp, - Config1 = init_static_dir(Config), {ok, _} = cowboy:start_http(http_compress, 100, [{port, 0}], [ {compress, true}, - {env, [{dispatch, init_dispatch(Config1)}]}, + {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {timeout, 500} ]), Port = ranch:get_port(http_compress), {ok, Client} = cowboy_client:init([]), [{scheme, <<"http">>}, {port, Port}, {opts, []}, - {transport, Transport}, {client, Client}|Config1]; + {transport, Transport}, {client, Client}|Config]; init_per_group(https_compress, Config) -> Transport = ranch_ssl, {_, Cert, Key} = ct_helper:make_certs(), Opts = [{cert, Cert}, {key, Key}], - Config1 = init_static_dir(Config), application:start(public_key), application:start(ssl), {ok, _} = cowboy:start_https(https_compress, 100, Opts ++ [{port, 0}], [ {compress, true}, - {env, [{dispatch, init_dispatch(Config1)}]}, + {env, [{dispatch, init_dispatch(Config)}]}, {max_keepalive, 50}, {timeout, 500} ]), Port = ranch:get_port(https_compress), {ok, Client} = cowboy_client:init(Opts), [{scheme, <<"https">>}, {port, Port}, {opts, Opts}, - {transport, Transport}, {client, Client}|Config1]; + {transport, Transport}, {client, Client}|Config]; init_per_group(onrequest, Config) -> Transport = ranch_tcp, {ok, _} = cowboy:start_http(onrequest, 100, [{port, 0}], [ @@ -301,15 +303,11 @@ init_per_group(set_env, Config) -> [{scheme, <<"http">>}, {port, Port}, {opts, []}, {transport, Transport}, {client, Client}|Config]. -end_per_group(Group, Config) when Group =:= https; Group =:= https_compress -> - cowboy:stop_listener(https), +end_per_group(Name, _) when Name =:= https; Name =:= https_compress -> + cowboy:stop_listener(Name), application:stop(ssl), application:stop(public_key), - end_static_dir(Config), ok; -end_per_group(Group, Config) when Group =:= http; Group =:= http_compress -> - cowboy:stop_listener(http), - end_static_dir(Config); end_per_group(Name, _) -> cowboy:stop_listener(Name), ok. @@ -357,7 +355,7 @@ init_dispatch(Config) -> {"/static_specify_file/[...]", cowboy_static, [{directory, ?config(static_dir, Config)}, {mimetypes, [{<<".css">>, [<<"text/css">>]}]}, - {file, <<"test_file.css">>}]}, + {file, <<"style.css">>}]}, {"/multipart", http_multipart, []}, {"/echo/body", http_echo_body, []}, {"/echo/body_qs", http_body_qs, []}, @@ -370,6 +368,7 @@ init_dispatch(Config) -> {"/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, []}, @@ -381,29 +380,6 @@ init_dispatch(Config) -> ]} ]). -init_static_dir(Config) -> - Dir = filename:join(?config(priv_dir, Config), "static"), - Level1 = fun(Name) -> filename:join(Dir, Name) end, - ok = file:make_dir(Dir), - ok = file:write_file(Level1("test_file"), "test_file\n"), - ok = file:write_file(Level1("test_file.css"), "test_file.css\n"), - ok = file:write_file(Level1("test_noread"), "test_noread\n"), - ok = file:change_mode(Level1("test_noread"), 8#0333), - ok = file:write_file(Level1("test.html"), "test.html\n"), - ok = file:make_dir(Level1("test_dir")), - [{static_dir, Dir}|Config]. - -end_static_dir(Config) -> - Dir = ?config(static_dir, Config), - Level1 = fun(Name) -> filename:join(Dir, Name) end, - ok = file:delete(Level1("test_file")), - ok = file:delete(Level1("test_file.css")), - ok = file:delete(Level1("test_noread")), - ok = file:delete(Level1("test.html")), - ok = file:del_dir(Level1("test_dir")), - ok = file:del_dir(Dir), - Config. - %% Convenience functions. quick_raw(Data, Config) -> @@ -513,9 +489,9 @@ check_status(Config) -> {400, "/static/%2f"}, {400, "/static/%2e"}, {400, "/static/%2e%2e"}, - {403, "/static/test_dir"}, - {403, "/static/test_dir/"}, - {403, "/static/test_noread"}, + {403, "/static/directory"}, + {403, "/static/directory/"}, + {403, "/static/unreadable"}, {404, "/not/found"}, {404, "/static/not_found"}, {500, "/handler_errors?case=handler_before_reply"}, @@ -999,6 +975,15 @@ rest_patch(Config) -> ok end || {Status, Headers, Body} <- Tests]. +rest_post_charset(Config) -> + Client = ?config(client, Config), + Headers = [ + {<<"content-type">>, <<"text/plain;charset=UTF-8">>} + ], + {ok, Client2} = cowboy_client:request(<<"POST">>, + build_url("/post_charset", Config), Headers, "12345", Client), + {ok, 204, _, _} = cowboy_client:response(Client2). + rest_postonly(Config) -> Client = ?config(client, Config), Headers = [ @@ -1114,9 +1099,9 @@ slowloris2(Config) -> static_attribute_etag(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, - build_url("/static_attribute_etag/test.html", Config), Client), + build_url("/static_attribute_etag/index.html", Config), Client), {ok, Client3} = cowboy_client:request(<<"GET">>, - build_url("/static_attribute_etag/test.html", Config), Client2), + build_url("/static_attribute_etag/index.html", Config), Client2), {ok, 200, Headers1, Client4} = cowboy_client:response(Client3), {ok, 200, Headers2, _} = cowboy_client:response(Client4), {<<"etag">>, ETag1} = lists:keyfind(<<"etag">>, 1, Headers1), @@ -1127,9 +1112,9 @@ static_attribute_etag(Config) -> static_function_etag(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, - build_url("/static_function_etag/test.html", Config), Client), + build_url("/static_function_etag/index.html", Config), Client), {ok, Client3} = cowboy_client:request(<<"GET">>, - build_url("/static_function_etag/test.html", Config), Client2), + build_url("/static_function_etag/index.html", Config), Client2), {ok, 200, Headers1, Client4} = cowboy_client:response(Client3), {ok, 200, Headers2, _} = cowboy_client:response(Client4), {<<"etag">>, ETag1} = lists:keyfind(<<"etag">>, 1, Headers1), @@ -1150,7 +1135,7 @@ static_function_etag(Arguments, etag_data) -> static_mimetypes_function(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, - build_url("/static_mimetypes_function/test.html", Config), Client), + build_url("/static_mimetypes_function/index.html", Config), Client), {ok, 200, Headers, _} = cowboy_client:response(Client2), {<<"content-type">>, <<"text/html">>} = lists:keyfind(<<"content-type">>, 1, Headers). @@ -1162,7 +1147,7 @@ static_specify_file(Config) -> {ok, 200, Headers, Client3} = cowboy_client:response(Client2), {<<"content-type">>, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, Headers), - {ok, <<"test_file.css\n">>, _} = cowboy_client:response_body(Client3). + {ok, <<"body{color:red}\n">>, _} = cowboy_client:response_body(Client3). static_specify_file_catchall(Config) -> Client = ?config(client, Config), @@ -1171,12 +1156,12 @@ static_specify_file_catchall(Config) -> {ok, 200, Headers, Client3} = cowboy_client:response(Client2), {<<"content-type">>, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, Headers), - {ok, <<"test_file.css\n">>, _} = cowboy_client:response_body(Client3). + {ok, <<"body{color:red}\n">>, _} = cowboy_client:response_body(Client3). static_test_file(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, - build_url("/static/test_file", Config), Client), + build_url("/static/unknown", Config), Client), {ok, 200, Headers, _} = cowboy_client:response(Client2), {<<"content-type">>, <<"application/octet-stream">>} = lists:keyfind(<<"content-type">>, 1, Headers). @@ -1184,7 +1169,7 @@ static_test_file(Config) -> static_test_file_css(Config) -> Client = ?config(client, Config), {ok, Client2} = cowboy_client:request(<<"GET">>, - build_url("/static/test_file.css", Config), Client), + build_url("/static/style.css", Config), Client), {ok, 200, Headers, _} = cowboy_client:response(Client2), {<<"content-type">>, <<"text/css">>} = lists:keyfind(<<"content-type">>, 1, Headers). diff --git a/test/http_SUITE_data/rest_post_charset_resource.erl b/test/http_SUITE_data/rest_post_charset_resource.erl new file mode 100644 index 0000000..9ccfa61 --- /dev/null +++ b/test/http_SUITE_data/rest_post_charset_resource.erl @@ -0,0 +1,15 @@ +-module(rest_post_charset_resource). +-export([init/3, allowed_methods/2, content_types_accepted/2, from_text/2]). + +init(_Transport, _Req, _Opts) -> + {upgrade, protocol, cowboy_rest}. + +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}. diff --git a/test/spdy_SUITE.erl b/test/spdy_SUITE.erl index df29281..1089991 100644 --- a/test/spdy_SUITE.erl +++ b/test/spdy_SUITE.erl @@ -44,9 +44,13 @@ init_per_suite(Config) -> application:start(cowboy), application:start(public_key), application:start(ssl), - Config. + Dir = ?config(priv_dir, Config) ++ "/static", + ct_helper:create_static_dir(Dir), + [{static_dir, Dir}|Config]. -end_per_suite(_Config) -> +end_per_suite(Config) -> + Dir = ?config(static_dir, Config), + ct_helper:delete_static_dir(Dir), application:stop(ssl), application:stop(public_key), application:stop(cowboy), @@ -69,9 +73,12 @@ end_per_group(Name, _) -> %% Dispatch configuration. -init_dispatch(_) -> +init_dispatch(Config) -> cowboy_router:compile([ {"localhost", [ + {"/static/[...]", cowboy_static, + [{directory, ?config(static_dir, Config)}, + {mimetypes, [{<<".css">>, [<<"text/css">>]}]}]}, {"/chunked", http_chunked, []}, {"/", http_handler, []} ]} @@ -152,6 +159,7 @@ check_status(Config) -> Tests = [ {200, nofin, "localhost", "/"}, {200, nofin, "localhost", "/chunked"}, + {200, nofin, "localhost", "/static/style.css"}, {400, fin, "bad-host", "/"}, {400, fin, "localhost", "bad-path"}, {404, fin, "localhost", "/this/path/does/not/exist"} |