mirror of
https://github.com/ninenines/cowboy.git
synced 2025-07-14 12:20:24 +00:00
Add built-in cowboy_http_static handler.
This commit is contained in:
parent
f56479ffc2
commit
ea7ae14df8
2 changed files with 421 additions and 11 deletions
349
src/cowboy_http_static.erl
Normal file
349
src/cowboy_http_static.erl
Normal file
|
@ -0,0 +1,349 @@
|
||||||
|
%% Copyright (c) 2011, Magnus Klaar <magnus.klaar@gmail.com>
|
||||||
|
%%
|
||||||
|
%% 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.
|
||||||
|
|
||||||
|
%% @doc Static resource handler.
|
||||||
|
%%
|
||||||
|
%% This built in HTTP handler provides a simple file serving capability for
|
||||||
|
%% cowboy applications. It should be considered an experimental feature because
|
||||||
|
%% of it's dependency on the experimental REST handler. It's recommended to be
|
||||||
|
%% used for small or temporary environments where it is not preferrable to set
|
||||||
|
%% up a second server just to serve files.
|
||||||
|
%%
|
||||||
|
%% == Base configuration ==
|
||||||
|
%%
|
||||||
|
%% The handler must be configured with a request path prefix to serve files
|
||||||
|
%% under and the path to a directory to read files from. The request path prefix
|
||||||
|
%% is defined in the path pattern of the cowboy dispatch rule for the handler.
|
||||||
|
%% The request path pattern must end with a ``'...''' token.
|
||||||
|
%% The directory path can be set to either an absolute or relative path in the
|
||||||
|
%% form of a list or binary string representation of a file system path. A list
|
||||||
|
%% of binary path segments, as is used throughout cowboy, is also a valid
|
||||||
|
%% directory path.
|
||||||
|
%%
|
||||||
|
%% The directory path can also be set to a relative path within the `priv/'
|
||||||
|
%% directory of an application. This is configured by setting the value of the
|
||||||
|
%% directory option to a tuple of the form `{priv_dir, Application, Relpath}'.
|
||||||
|
%%
|
||||||
|
%% ==== Examples ====
|
||||||
|
%% ```
|
||||||
|
%% %% Serve files from /var/www/ under http://example.com/static/
|
||||||
|
%% {[<<"static">>, '...'], cowboy_http_static,
|
||||||
|
%% [{directory, "/var/www"}]}
|
||||||
|
%%
|
||||||
|
%% %% Serve files from the current working directory under http://example.com/static/
|
||||||
|
%% {[<<"static">>, '...'], cowboy_http_static,
|
||||||
|
%% [{directory, <<"./">>}]}
|
||||||
|
%%
|
||||||
|
%% %% Serve files from cowboy/priv/www under http://example.com/
|
||||||
|
%% {['...'], cowboy_http_static,
|
||||||
|
%% [{directory, {priv_dir, cowboy, [<<"www">>]}}]}
|
||||||
|
%% '''
|
||||||
|
%%
|
||||||
|
%% == Content type configuration ==
|
||||||
|
%%
|
||||||
|
%% By default the content type of all static resources will be set to
|
||||||
|
%% `application/octet-stream'. This can be overriden by supplying a list
|
||||||
|
%% of filename extension to mimetypes pairs in the `mimetypes' option.
|
||||||
|
%% The filename extension should be a binary string including the leading dot.
|
||||||
|
%% The mimetypes must be of a type that the `cowboy_http_rest' protocol can
|
||||||
|
%% handle.
|
||||||
|
%%
|
||||||
|
%% ==== Example ====
|
||||||
|
%% ```
|
||||||
|
%% {[<<"static">>, '...'], cowboy_http_static,
|
||||||
|
%% [{directory, {priv_dir, cowboy, []}},
|
||||||
|
%% {mimetypes, [
|
||||||
|
%% {<<".css">>, [<<"text/css">>]},
|
||||||
|
%% {<<".js">>, [<<"application/javascript">>]}]}]}
|
||||||
|
%% '''
|
||||||
|
-module(cowboy_http_static).
|
||||||
|
|
||||||
|
%% include files
|
||||||
|
-include("http.hrl").
|
||||||
|
-include_lib("kernel/include/file.hrl").
|
||||||
|
|
||||||
|
%% cowboy_http_protocol callbacks
|
||||||
|
-export([init/3]).
|
||||||
|
|
||||||
|
%% cowboy_http_rest callbacks
|
||||||
|
-export([rest_init/2, allowed_methods/2, malformed_request/2, resource_exists/2,
|
||||||
|
forbidden/2, last_modified/2, content_types_provided/2, file_contents/2]).
|
||||||
|
|
||||||
|
%% internal
|
||||||
|
-export([path_to_mimetypes/2]).
|
||||||
|
|
||||||
|
%% types
|
||||||
|
-type dirpath() :: string() | binary() | [binary()].
|
||||||
|
-type dirspec() :: dirpath() | {priv, atom(), dirpath()}.
|
||||||
|
-type mimedef() :: {binary(), binary(), [{binary(), binary()}]}.
|
||||||
|
|
||||||
|
%% handler state
|
||||||
|
-record(state, {
|
||||||
|
filepath :: binary() | error,
|
||||||
|
fileinfo :: {ok, #file_info{}} | {error, _} | error,
|
||||||
|
mimetypes :: {fun((binary(), T) -> [mimedef()]), T} | undefined}).
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Upgrade from HTTP handler to REST handler.
|
||||||
|
init({_Transport, http}, _Req, _Opts) ->
|
||||||
|
{upgrade, protocol, cowboy_http_rest}.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Set up initial state of REST handler.
|
||||||
|
-spec rest_init(#http_req{}, list()) -> {ok, #http_req{}, #state{}}.
|
||||||
|
rest_init(Req, Opts) ->
|
||||||
|
Directory = proplists:get_value(directory, Opts),
|
||||||
|
Directory1 = directory_path(Directory),
|
||||||
|
Mimetypes = proplists:get_value(mimetypes, Opts, []),
|
||||||
|
Mimetypes1 = case Mimetypes of
|
||||||
|
{_, _} -> Mimetypes;
|
||||||
|
[] -> {fun path_to_mimetypes/2, []};
|
||||||
|
[_|_] -> {fun path_to_mimetypes/2, Mimetypes}
|
||||||
|
end,
|
||||||
|
{Filepath, Req1} = cowboy_http_req:path_info(Req),
|
||||||
|
State = case check_path(Filepath) of
|
||||||
|
error ->
|
||||||
|
#state{filepath=error, fileinfo=error, mimetypes=undefined};
|
||||||
|
ok ->
|
||||||
|
Filepath1 = join_paths(Directory1, Filepath),
|
||||||
|
Fileinfo = file:read_file_info(Filepath1),
|
||||||
|
#state{filepath=Filepath1, fileinfo=Fileinfo, mimetypes=Mimetypes1}
|
||||||
|
end,
|
||||||
|
{ok, Req1, State}.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Only allow GET and HEAD requests on files.
|
||||||
|
-spec allowed_methods(#http_req{}, #state{}) ->
|
||||||
|
{[atom()], #http_req{}, #state{}}.
|
||||||
|
allowed_methods(Req, State) ->
|
||||||
|
{['GET', 'HEAD'], Req, State}.
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
-spec malformed_request(#http_req{}, #state{}) ->
|
||||||
|
{boolean(), #http_req{}, #state{}}.
|
||||||
|
malformed_request(Req, #state{filepath=error}=State) ->
|
||||||
|
{true, Req, State};
|
||||||
|
malformed_request(Req, State) ->
|
||||||
|
{false, Req, State}.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Check if the resource exists under the document root.
|
||||||
|
-spec resource_exists(#http_req{}, #state{}) ->
|
||||||
|
{boolean(), #http_req{}, #state{}}.
|
||||||
|
resource_exists(Req, #state{fileinfo={error, _}}=State) ->
|
||||||
|
{false, Req, State};
|
||||||
|
resource_exists(Req, #state{fileinfo={ok, Fileinfo}}=State) ->
|
||||||
|
{Fileinfo#file_info.type =:= regular, Req, State}.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private
|
||||||
|
%% Access to a file resource is forbidden if it exists and the local node does
|
||||||
|
%% not have permission to read it. Directory listings are always forbidden.
|
||||||
|
-spec forbidden(#http_req{}, #state{}) -> {boolean(), #http_req{}, #state{}}.
|
||||||
|
forbidden(Req, #state{fileinfo={_, #file_info{type=directory}}}=State) ->
|
||||||
|
{true, Req, State};
|
||||||
|
forbidden(Req, #state{fileinfo={error, eacces}}=State) ->
|
||||||
|
{true, Req, State};
|
||||||
|
forbidden(Req, #state{fileinfo={error, _}}=State) ->
|
||||||
|
{false, Req, State};
|
||||||
|
forbidden(Req, #state{fileinfo={ok, #file_info{access=Access}}}=State) ->
|
||||||
|
{not (Access =:= read orelse Access =:= read_write), Req, State}.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Read the time a file system system object was last modified.
|
||||||
|
-spec last_modified(#http_req{}, #state{}) ->
|
||||||
|
{cowboy_clock:datetime(), #http_req{}, #state{}}.
|
||||||
|
last_modified(Req, #state{fileinfo={ok, #file_info{mtime=Modified}}}=State) ->
|
||||||
|
{Modified, Req, State}.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Return the content type of a file.
|
||||||
|
-spec content_types_provided(#http_req{}, #state{}) -> tuple().
|
||||||
|
content_types_provided(Req, #state{filepath=Filepath,
|
||||||
|
mimetypes={MimetypesFun, MimetypesData}}=State) ->
|
||||||
|
Mimetypes = [{T, file_contents}
|
||||||
|
|| T <- MimetypesFun(Filepath, MimetypesData)],
|
||||||
|
{Mimetypes, Req, State}.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Return a function that writes a file directly to the socket.
|
||||||
|
-spec file_contents(#http_req{}, #state{}) -> tuple().
|
||||||
|
file_contents(Req, #state{filepath=Filepath,
|
||||||
|
fileinfo={ok, #file_info{size=Filesize}}}=State) ->
|
||||||
|
{ok, Transport, Socket} = cowboy_http_req:transport(Req),
|
||||||
|
Writefile = content_function(Transport, Socket, Filepath),
|
||||||
|
{{stream, Filesize, Writefile}, Req, State}.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Return a function writing the contents of a file to a socket.
|
||||||
|
%% The function returns the number of bytes written to the socket to enable
|
||||||
|
%% the calling function to determine if the expected number of bytes were
|
||||||
|
%% written to the socket.
|
||||||
|
-spec content_function(module(), inet:socket(), binary()) ->
|
||||||
|
fun(() -> {sent, non_neg_integer()}).
|
||||||
|
content_function(Transport, Socket, Filepath) ->
|
||||||
|
%% `file:sendfile/2' will only work with the `cowboy_tcp_transport'
|
||||||
|
%% transport module. SSL or future SPDY transports that require the
|
||||||
|
%% content to be encrypted or framed as the content is sent.
|
||||||
|
case erlang:function_exported(file, sendfile, 2) of
|
||||||
|
false ->
|
||||||
|
fun() -> sfallback(Transport, Socket, Filepath) end;
|
||||||
|
_ when Transport =/= cowboy_tcp_transport ->
|
||||||
|
fun() -> sfallback(Transport, Socket, Filepath) end;
|
||||||
|
true ->
|
||||||
|
fun() -> sendfile(Socket, Filepath) end
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Sendfile fallback function.
|
||||||
|
-spec sfallback(module(), inet:socket(), binary()) -> {sent, non_neg_integer()}.
|
||||||
|
sfallback(Transport, Socket, Filepath) ->
|
||||||
|
{ok, File} = file:open(Filepath, [read,binary,raw]),
|
||||||
|
sfallback(Transport, Socket, File, 0).
|
||||||
|
|
||||||
|
-spec sfallback(module(), inet:socket(), file:io_device(),
|
||||||
|
non_neg_integer()) -> {sent, non_neg_integer()}.
|
||||||
|
sfallback(Transport, Socket, File, Sent) ->
|
||||||
|
case file:read(File, 16#1FFF) of
|
||||||
|
eof ->
|
||||||
|
ok = file:close(File),
|
||||||
|
{sent, Sent};
|
||||||
|
{ok, Bin} ->
|
||||||
|
ok = Transport:send(Socket, Bin),
|
||||||
|
sfallback(Transport, Socket, File, Sent + byte_size(Bin))
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Wrapper for sendfile function.
|
||||||
|
-spec sendfile(inet:socket(), binary()) -> {sent, non_neg_integer()}.
|
||||||
|
sendfile(Socket, Filepath) ->
|
||||||
|
{ok, Sent} = file:sendfile(Filepath, Socket),
|
||||||
|
{sent, Sent}.
|
||||||
|
|
||||||
|
-spec directory_path(dirspec()) -> dirpath().
|
||||||
|
directory_path({priv_dir, App, []}) ->
|
||||||
|
priv_dir_path(App);
|
||||||
|
directory_path({priv_dir, App, [H|_]=Path}) when is_integer(H) ->
|
||||||
|
filename:join(priv_dir_path(App), Path);
|
||||||
|
directory_path({priv_dir, App, [H|_]=Path}) when is_binary(H) ->
|
||||||
|
filename:join(filename:split(priv_dir_path(App)) ++ Path);
|
||||||
|
directory_path({priv_dir, App, Path}) when is_binary(Path) ->
|
||||||
|
filename:join(priv_dir_path(App), Path);
|
||||||
|
directory_path(Path) ->
|
||||||
|
Path.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Validate a request path for unsafe characters.
|
||||||
|
%% There is no way to escape special characters in a filesystem path.
|
||||||
|
-spec check_path(Path::[binary()]) -> ok | error.
|
||||||
|
check_path([]) -> ok;
|
||||||
|
check_path([<<"">>|_T]) -> error;
|
||||||
|
check_path([<<".">>|_T]) -> error;
|
||||||
|
check_path([<<"..">>|_T]) -> error;
|
||||||
|
check_path([H|T]) ->
|
||||||
|
case binary:match(H, <<"/">>) of
|
||||||
|
{_, _} -> error;
|
||||||
|
nomatch -> check_path(T)
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Join the the directory and request paths.
|
||||||
|
-spec join_paths(dirpath(), [binary()]) -> binary().
|
||||||
|
join_paths([H|_]=Dirpath, Filepath) when is_integer(H) ->
|
||||||
|
filename:join(filename:split(Dirpath) ++ Filepath);
|
||||||
|
join_paths([H|_]=Dirpath, Filepath) when is_binary(H) ->
|
||||||
|
filename:join(Dirpath ++ Filepath);
|
||||||
|
join_paths(Dirpath, Filepath) when is_binary(Dirpath) ->
|
||||||
|
filename:join([Dirpath] ++ Filepath);
|
||||||
|
join_paths([], Filepath) ->
|
||||||
|
filename:join(Filepath).
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Return the path to the priv/ directory of an application.
|
||||||
|
-spec priv_dir_path(atom()) -> string().
|
||||||
|
priv_dir_path(App) ->
|
||||||
|
case code:priv_dir(App) of
|
||||||
|
{error, bad_name} -> priv_dir_mod(App);
|
||||||
|
Dir -> Dir
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec priv_dir_mod(atom()) -> string().
|
||||||
|
priv_dir_mod(Mod) ->
|
||||||
|
case code:which(Mod) of
|
||||||
|
File when not is_list(File) -> "../priv";
|
||||||
|
File -> filename:join([filename:dirname(File),"../priv"])
|
||||||
|
end.
|
||||||
|
|
||||||
|
|
||||||
|
%% @private Use application/octet-stream as the default mimetype.
|
||||||
|
%% If a list of extension - mimetype pairs are provided as the mimetypes
|
||||||
|
%% an attempt to find the mimetype using the file extension. If no match
|
||||||
|
%% is found the default mimetype is returned.
|
||||||
|
-spec path_to_mimetypes(binary(), [{binary(), [mimedef()]}]) ->
|
||||||
|
[mimedef()].
|
||||||
|
path_to_mimetypes(Filepath, Extensions) when is_binary(Filepath) ->
|
||||||
|
Ext = filename:extension(Filepath),
|
||||||
|
case Ext of
|
||||||
|
<<>> -> default_mimetype();
|
||||||
|
_Ext -> path_to_mimetypes_(Ext, Extensions)
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec path_to_mimetypes_(binary(), [{binary(), [mimedef()]}]) -> [mimedef()].
|
||||||
|
path_to_mimetypes_(Ext, Extensions) ->
|
||||||
|
case lists:keyfind(Ext, 1, Extensions) of
|
||||||
|
{_, MTs} -> MTs;
|
||||||
|
_Unknown -> default_mimetype()
|
||||||
|
end.
|
||||||
|
|
||||||
|
-spec default_mimetype() -> [mimedef()].
|
||||||
|
default_mimetype() ->
|
||||||
|
[{<<"application">>, <<"octet-stream">>, []}].
|
||||||
|
|
||||||
|
|
||||||
|
-ifdef(TEST).
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
-define(_eq(E, I), ?_assertEqual(E, I)).
|
||||||
|
|
||||||
|
check_path_test_() ->
|
||||||
|
C = fun check_path/1,
|
||||||
|
[?_eq(error, C([<<>>])),
|
||||||
|
?_eq(ok, C([<<"abc">>])),
|
||||||
|
?_eq(error, C([<<".">>])),
|
||||||
|
?_eq(error, C([<<"..">>])),
|
||||||
|
?_eq(error, C([<<"/">>]))
|
||||||
|
].
|
||||||
|
|
||||||
|
join_paths_test_() ->
|
||||||
|
P = fun join_paths/2,
|
||||||
|
[?_eq(<<"a">>, P([], [<<"a">>])),
|
||||||
|
?_eq(<<"a/b/c">>, P(<<"a/b">>, [<<"c">>])),
|
||||||
|
?_eq(<<"a/b/c">>, P("a/b", [<<"c">>])),
|
||||||
|
?_eq(<<"a/b/c">>, P([<<"a">>, <<"b">>], [<<"c">>]))
|
||||||
|
].
|
||||||
|
|
||||||
|
directory_path_test_() ->
|
||||||
|
P = fun directory_path/1,
|
||||||
|
PL = fun(I) -> length(filename:split(P(I))) end,
|
||||||
|
Base = PL({priv_dir, cowboy, []}),
|
||||||
|
[?_eq(Base + 1, PL({priv_dir, cowboy, "a"})),
|
||||||
|
?_eq(Base + 1, PL({priv_dir, cowboy, <<"a">>})),
|
||||||
|
?_eq(Base + 1, PL({priv_dir, cowboy, [<<"a">>]})),
|
||||||
|
?_eq(Base + 2, PL({priv_dir, cowboy, "a/b"})),
|
||||||
|
?_eq(Base + 2, PL({priv_dir, cowboy, <<"a/b">>})),
|
||||||
|
?_eq(Base + 2, PL({priv_dir, cowboy, [<<"a">>, <<"b">>]})),
|
||||||
|
?_eq("a/b", P("a/b"))
|
||||||
|
].
|
||||||
|
|
||||||
|
|
||||||
|
-endif.
|
|
@ -22,7 +22,8 @@
|
||||||
keepalive_nl/1, max_keepalive/1, nc_rand/1, nc_zero/1,
|
keepalive_nl/1, max_keepalive/1, nc_rand/1, nc_zero/1,
|
||||||
pipeline/1, raw/1, set_resp_header/1, set_resp_overwrite/1,
|
pipeline/1, raw/1, set_resp_header/1, set_resp_overwrite/1,
|
||||||
set_resp_body/1, stream_body_set_resp/1, response_as_req/1]). %% http.
|
set_resp_body/1, stream_body_set_resp/1, response_as_req/1]). %% http.
|
||||||
-export([http_200/1, http_404/1]). %% http and https.
|
-export([http_200/1, http_404/1, file_200/1, file_403/1,
|
||||||
|
dir_403/1, file_404/1, file_400/1]). %% http and https.
|
||||||
-export([http_10_hostless/1]). %% misc.
|
-export([http_10_hostless/1]). %% misc.
|
||||||
-export([rest_simple/1, rest_keepalive/1]). %% rest.
|
-export([rest_simple/1, rest_keepalive/1]). %% rest.
|
||||||
|
|
||||||
|
@ -32,7 +33,8 @@ all() ->
|
||||||
[{group, http}, {group, https}, {group, misc}, {group, rest}].
|
[{group, http}, {group, https}, {group, misc}, {group, rest}].
|
||||||
|
|
||||||
groups() ->
|
groups() ->
|
||||||
BaseTests = [http_200, http_404],
|
BaseTests = [http_200, http_404, file_200, file_403, dir_403, file_404,
|
||||||
|
file_400],
|
||||||
[{http, [], [chunked_response, headers_dupe, headers_huge,
|
[{http, [], [chunked_response, headers_dupe, headers_huge,
|
||||||
keepalive_nl, max_keepalive, nc_rand, nc_zero, pipeline, raw,
|
keepalive_nl, max_keepalive, nc_rand, nc_zero, pipeline, raw,
|
||||||
set_resp_header, set_resp_overwrite,
|
set_resp_header, set_resp_overwrite,
|
||||||
|
@ -53,14 +55,16 @@ end_per_suite(_Config) ->
|
||||||
|
|
||||||
init_per_group(http, Config) ->
|
init_per_group(http, Config) ->
|
||||||
Port = 33080,
|
Port = 33080,
|
||||||
|
Config1 = init_static_dir(Config),
|
||||||
cowboy:start_listener(http, 100,
|
cowboy:start_listener(http, 100,
|
||||||
cowboy_tcp_transport, [{port, Port}],
|
cowboy_tcp_transport, [{port, Port}],
|
||||||
cowboy_http_protocol, [{max_keepalive, 50},
|
cowboy_http_protocol, [{max_keepalive, 50},
|
||||||
{dispatch, init_http_dispatch()}]
|
{dispatch, init_http_dispatch(Config1)}]
|
||||||
),
|
),
|
||||||
[{scheme, "http"}, {port, Port}|Config];
|
[{scheme, "http"}, {port, Port}|Config1];
|
||||||
init_per_group(https, Config) ->
|
init_per_group(https, Config) ->
|
||||||
Port = 33081,
|
Port = 33081,
|
||||||
|
Config1 = init_static_dir(Config),
|
||||||
application:start(crypto),
|
application:start(crypto),
|
||||||
application:start(public_key),
|
application:start(public_key),
|
||||||
application:start(ssl),
|
application:start(ssl),
|
||||||
|
@ -69,9 +73,9 @@ init_per_group(https, Config) ->
|
||||||
cowboy_ssl_transport, [
|
cowboy_ssl_transport, [
|
||||||
{port, Port}, {certfile, DataDir ++ "cert.pem"},
|
{port, Port}, {certfile, DataDir ++ "cert.pem"},
|
||||||
{keyfile, DataDir ++ "key.pem"}, {password, "cowboy"}],
|
{keyfile, DataDir ++ "key.pem"}, {password, "cowboy"}],
|
||||||
cowboy_http_protocol, [{dispatch, init_https_dispatch()}]
|
cowboy_http_protocol, [{dispatch, init_https_dispatch(Config1)}]
|
||||||
),
|
),
|
||||||
[{scheme, "https"}, {port, Port}|Config];
|
[{scheme, "https"}, {port, Port}|Config1];
|
||||||
init_per_group(misc, Config) ->
|
init_per_group(misc, Config) ->
|
||||||
Port = 33082,
|
Port = 33082,
|
||||||
cowboy:start_listener(misc, 100,
|
cowboy:start_listener(misc, 100,
|
||||||
|
@ -89,19 +93,21 @@ init_per_group(rest, Config) ->
|
||||||
]}]}]),
|
]}]}]),
|
||||||
[{port, Port}|Config].
|
[{port, Port}|Config].
|
||||||
|
|
||||||
end_per_group(https, _Config) ->
|
end_per_group(https, Config) ->
|
||||||
cowboy:stop_listener(https),
|
cowboy:stop_listener(https),
|
||||||
application:stop(ssl),
|
application:stop(ssl),
|
||||||
application:stop(public_key),
|
application:stop(public_key),
|
||||||
application:stop(crypto),
|
application:stop(crypto),
|
||||||
|
end_static_dir(Config),
|
||||||
ok;
|
ok;
|
||||||
end_per_group(Listener, _Config) ->
|
end_per_group(Listener, Config) ->
|
||||||
cowboy:stop_listener(Listener),
|
cowboy:stop_listener(Listener),
|
||||||
|
end_static_dir(Config),
|
||||||
ok.
|
ok.
|
||||||
|
|
||||||
%% Dispatch configuration.
|
%% Dispatch configuration.
|
||||||
|
|
||||||
init_http_dispatch() ->
|
init_http_dispatch(Config) ->
|
||||||
[
|
[
|
||||||
{[<<"localhost">>], [
|
{[<<"localhost">>], [
|
||||||
{[<<"chunked_response">>], chunked_handler, []},
|
{[<<"chunked_response">>], chunked_handler, []},
|
||||||
|
@ -117,12 +123,37 @@ init_http_dispatch() ->
|
||||||
[{body, <<"A flameless dance does not equal a cycle">>}]},
|
[{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">>}]},
|
[{reply, set_resp}, {body, <<"stream_body_set_resp">>}]},
|
||||||
|
{[<<"static">>, '...'], cowboy_http_static,
|
||||||
|
[{directory, ?config(static_dir, Config)},
|
||||||
|
{mimetypes, [{<<".css">>, [<<"text/css">>]}]}]},
|
||||||
{[], http_handler, []}
|
{[], http_handler, []}
|
||||||
]}
|
]}
|
||||||
].
|
].
|
||||||
|
|
||||||
init_https_dispatch() ->
|
init_https_dispatch(Config) ->
|
||||||
init_http_dispatch().
|
init_http_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: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:del_dir(Level1("test_dir")),
|
||||||
|
ok = file:del_dir(Dir),
|
||||||
|
Config.
|
||||||
|
|
||||||
%% http.
|
%% http.
|
||||||
|
|
||||||
|
@ -355,6 +386,36 @@ http_404(Config) ->
|
||||||
{ok, {{"HTTP/1.1", 404, "Not Found"}, _Headers, _Body}} =
|
{ok, {{"HTTP/1.1", 404, "Not Found"}, _Headers, _Body}} =
|
||||||
httpc:request(build_url("/not/found", Config)).
|
httpc:request(build_url("/not/found", Config)).
|
||||||
|
|
||||||
|
file_200(Config) ->
|
||||||
|
{ok, {{"HTTP/1.1", 200, "OK"}, Headers, "test_file\n"}} =
|
||||||
|
httpc:request(build_url("/static/test_file", Config)),
|
||||||
|
"application/octet-stream" = ?config("content-type", Headers),
|
||||||
|
|
||||||
|
{ok, {{"HTTP/1.1", 200, "OK"}, Headers1, "test_file.css\n"}} =
|
||||||
|
httpc:request(build_url("/static/test_file.css", Config)),
|
||||||
|
"text/css" = ?config("content-type", Headers1).
|
||||||
|
|
||||||
|
file_403(Config) ->
|
||||||
|
{ok, {{"HTTP/1.1", 403, "Forbidden"}, _Headers, _Body}} =
|
||||||
|
httpc:request(build_url("/static/test_noread", Config)).
|
||||||
|
|
||||||
|
dir_403(Config) ->
|
||||||
|
{ok, {{"HTTP/1.1", 403, "Forbidden"}, _Headers, _Body}} =
|
||||||
|
httpc:request(build_url("/static/test_dir", Config)),
|
||||||
|
{ok, {{"HTTP/1.1", 403, "Forbidden"}, _Headers, _Body}} =
|
||||||
|
httpc:request(build_url("/static/test_dir/", Config)).
|
||||||
|
|
||||||
|
file_404(Config) ->
|
||||||
|
{ok, {{"HTTP/1.1", 404, "Not Found"}, _Headers, _Body}} =
|
||||||
|
httpc:request(build_url("/static/not_found", Config)).
|
||||||
|
|
||||||
|
file_400(Config) ->
|
||||||
|
{ok, {{"HTTP/1.1", 400, "Bad Request"}, _Headers, _Body}} =
|
||||||
|
httpc:request(build_url("/static/%2f", Config)),
|
||||||
|
{ok, {{"HTTP/1.1", 400, "Bad Request"}, _Headers1, _Body1}} =
|
||||||
|
httpc:request(build_url("/static/%2e", Config)),
|
||||||
|
{ok, {{"HTTP/1.1", 400, "Bad Request"}, _Headers2, _Body2}} =
|
||||||
|
httpc:request(build_url("/static/%2e%2e", Config)).
|
||||||
%% misc.
|
%% misc.
|
||||||
|
|
||||||
http_10_hostless(Config) ->
|
http_10_hostless(Config) ->
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue