Skip to content

Commit 9fd0539

Browse files
committed
Check in progress
1 parent d1041a2 commit 9fd0539

File tree

6 files changed

+199
-24
lines changed

6 files changed

+199
-24
lines changed

.travis.yml

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,20 @@ before_script:
55
- wget https://s3.amazonaws.com/rebar3/rebar3
66
- chmod +x rebar3
77
env: PATH=$PATH:.
8-
# script: rebar3 eunit && rebar3 as test coveralls send || cat rebar3.crashdump
9-
script: rebar3 as test compile
8+
script: rebar3 as test do xref, dialyzer, eunit, coveralls send
109
cache:
1110
directories:
1211
- $HOME/.cache/rebar3/
1312
otp_release:
14-
- 19.0
15-
- 18.3
1613
- 18.0
17-
# - 17.5
18-
# - R16B03-1
19-
# - R15B03-1
14+
- 18.1
15+
- 18.2
16+
- 18.2.1
17+
- 18.3
18+
- 19.0
19+
- 19.1
20+
- 19.2
21+
- 19.3
22+
- 20.0
23+
- 20.1
24+
- 20.2

rebar.config

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@
88
[{add, elli_cache, [{erl_opts, [{i, "_build/test/lib"}]}]}]}.
99

1010
{deps,
11-
[{elli_cache, "0.1.1"},
12-
{mimerl, "1.1.0"}]}.
11+
[{elli_cache, "1.0.1"}]}.
1312

1413
{profiles,
1514
[{test,
1615
[{deps,
17-
[{elli, "2.0.1"},
18-
{meck, "0.8.4"}]}]},
16+
[{elli, "3.0.0"}]},
17+
{dialyzer,
18+
[{plt_extra_apps, [elli]}]}
19+
]},
1920
{docs,
20-
[{deps, [{edown, "0.8.1"}]},
21+
[{erl_opts, [{i, ["_build/test/lib"]}]},
22+
{deps, [{edown, "0.8.1"}]},
2123
{edoc_opts,
2224
[
2325
{dir, "doc"},
@@ -26,9 +28,10 @@
2628
{doc_path,
2729
["http://raw.github.com/elli-lib/elli/develop/doc",
2830
"http://raw.github.com/elli-lib/elli_cache/develop/doc"
31+
"http://raw.github.com/elli-lib/elli_static/develop/doc"
2932
]},
3033
{top_level_readme,
31-
{"./README.md", "https://github.com/elli-lib/elli_cache", "develop"}}
34+
{"./README.md", "https://github.com/elli-lib/elli_static", "develop"}}
3235
]}]}]}.
3336

3437
{post_hooks,

rebar.lock

Lines changed: 0 additions & 10 deletions
This file was deleted.

src/elli_static.app.src

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{description, "Elli middleware to serve static files."},
44
{vsn, "0.0.1"},
55
{registered, []},
6-
{applications, [kernel, stdlib, elli, elli_cache]},
6+
{applications, [kernel, stdlib, elli_cache]},
77
{env, []},
88

99
{maintainers,

src/elli_static.erl

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
-module(elli_static).
2+
3+
-behaviour(elli_handler).
4+
-behaviour(elli_cache).
5+
6+
7+
-include_lib("elli/include/elli.hrl").
8+
-include_lib("kernel/include/file.hrl").
9+
-include_lib("elli_cache/src/elli_cache_util.hrl").
10+
11+
12+
-define(TABLE, elli_static_table).
13+
14+
15+
-export([preprocess/2,
16+
handle/2, handle_event/3,
17+
postprocess/3,
18+
get_modified/2,
19+
get_size/2
20+
]).
21+
22+
-export_type([config/0]).
23+
24+
-type config() :: [{binary(), {dir, file:name_all()}}].
25+
26+
27+
preprocess(Req, Config)
28+
when not(?GET_OR_HEAD(Req#req.method)) ->
29+
MaybeMtime = get_modified(Req, Config),
30+
MaybeSize = get_size(Req, Config),
31+
MaybeETag = maybe_etag(MaybeMtime, MaybeSize),
32+
rfc7232:init(Req, MaybeMtime, MaybeETag);
33+
preprocess(Req, _Config) ->
34+
Req.
35+
36+
37+
-spec handle(elli:req(), config()) -> elli_handler:result().
38+
handle(_Req, []) ->
39+
ignore;
40+
handle(Req, [{Prefix, {dir, Dir}}|Args]) ->
41+
case maybe_file(Req, Prefix, Dir) of
42+
{just, Filename} ->
43+
{ok, [], {file, Filename}};
44+
nothing ->
45+
handle(Req, Args)
46+
end.
47+
48+
49+
-spec handle_event(elli_handler:event(), list(), config()) -> ok.
50+
handle_event(elli_startup, _Args, _Config) ->
51+
ets:new(?TABLE, [set, named_table, public]),
52+
ok;
53+
handle_event(request_complete, [Req|_Args], _Config) ->
54+
ReqKey = erlang:phash2(Req),
55+
ets:delete(?TABLE, ReqKey),
56+
ok;
57+
handle_event(_Event, _Args, _Config) ->
58+
ok.
59+
60+
61+
postprocess(Req, {ResponseCode, _Headers, _What} = Res, Config)
62+
when ?OK_GET_OR_HEAD(ResponseCode, Req#req.method) ->
63+
MaybeMtime = get_modified(Req, Config),
64+
MaybeSize = get_size(Req, Config),
65+
MaybeETag = maybe_etag(MaybeMtime, MaybeSize),
66+
rfc7232:init(Req, MaybeMtime, MaybeETag, Res);
67+
postprocess(_, Res, _) ->
68+
Res.
69+
70+
71+
get_modified(Req, Args) ->
72+
do_it(Req, fun(#file_info{mtime = Mtime}) -> Mtime end, Args).
73+
74+
75+
get_size(Req, Args) ->
76+
do_it(Req, fun(#file_info{size = Size}) -> Size end, Args).
77+
78+
79+
do_it(_Req, _Fun, []) ->
80+
nothing;
81+
do_it(Req, Fun, [{Prefix, {dir, Dir}}|Args]) ->
82+
ReqKey = erlang:phash2(Req),
83+
case ets:lookup(?TABLE, ReqKey) of
84+
[{ReqKey, FileInfo}] ->
85+
{just, Fun(FileInfo)};
86+
_ ->
87+
case maybe_file(Req, Prefix, Dir) of
88+
{just, Filename} ->
89+
FileInfo = file_info(Filename),
90+
ets:insert(?TABLE, {ReqKey, FileInfo}),
91+
{just, Fun(FileInfo)};
92+
nothing ->
93+
do_it(Req, Fun, Args)
94+
end
95+
end;
96+
do_it(Req, Fun, [_Arg|Args]) ->
97+
do_it(Req, Fun, Args).
98+
99+
100+
file_info(Filename) ->
101+
case file:read_file_info(Filename, [{time, universal}]) of
102+
{ok, FileInfo} -> FileInfo;
103+
{error, Reason} -> throw(Reason)
104+
end.
105+
106+
107+
maybe_file(Req, Prefix, Dir) ->
108+
Size = byte_size(Prefix),
109+
case elli_request:raw_path(Req) of
110+
<<Prefix:Size/binary,"/",Path/binary>> ->
111+
Filename = filename:join(Dir, Path),
112+
case filelib:is_regular(Filename) of
113+
true -> {just, Filename};
114+
false -> throw({404, [], <<"Not Found">>})
115+
end;
116+
_ ->
117+
nothing
118+
end.
119+
120+
121+
maybe_etag(nothing, _Size) ->
122+
nothing;
123+
maybe_etag(_Mtime, nothing) ->
124+
nothing;
125+
maybe_etag({just, Mtime}, {just, Size}) ->
126+
ETag = list_to_binary(httpd_util:create_etag(Mtime, Size)),
127+
{just, <<"\"", ETag/binary, "\"">>}.

test/elli_static_tests.erl

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
-module(elli_static_tests).
2+
-include_lib("eunit/include/eunit.hrl").
3+
-compile(export_all).
4+
5+
elli_static_test_() ->
6+
{setup,
7+
fun setup/0, fun teardown/1,
8+
[?_test(readme()),
9+
?_test(no_file()),
10+
?_test(not_found())]}.
11+
12+
13+
readme() ->
14+
{ok, Response} = httpc:request("http://localhost:3000/elli_static/README.md"),
15+
{ok, File} = file:read_file("README.md"),
16+
Expected = binary_to_list(File),
17+
?assertEqual([integer_to_list(iolist_size(Expected))],
18+
proplists:get_all_values("content-length", element(2, Response))),
19+
?assertMatch({_Status, _Headers, Expected}, Response).
20+
21+
22+
no_file() ->
23+
{ok, Response} = httpc:request("http://localhost:3000/elli_static/no_file"),
24+
?assertMatch({{"HTTP/1.1",404,"Not Found"}, _Headers, "Not Found"}, Response).
25+
26+
27+
not_found() ->
28+
{ok, Response} = httpc:request("http://localhost:3000/not_found"),
29+
?assertMatch({{"HTTP/1.1",404,"Not Found"}, _Headers, "Not Found"}, Response).
30+
31+
32+
setup() ->
33+
{ok, Dir} = file:get_cwd(),
34+
Args = [{<<"/elli_static">>, {dir, Dir}}],
35+
Config = [
36+
{mods, [
37+
{elli_middleware_cache, [{mod, elli_static_cache}]},
38+
{elli_static, Args}
39+
]
40+
}
41+
],
42+
{ok, Pid} = elli:start_link([{callback, elli_middleware},
43+
{callback_args, Config},
44+
{port, 3000}]),
45+
unlink(Pid),
46+
[Pid].
47+
48+
49+
teardown(Pids) ->
50+
[elli:stop(Pid) || Pid <- Pids].

0 commit comments

Comments
 (0)