Skip to content

Commit

Permalink
Merge branch 'release/2.0.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
yurrriq committed Dec 27, 2016
2 parents 3ef48fc + f2d490c commit c54f954
Show file tree
Hide file tree
Showing 18 changed files with 289 additions and 83 deletions.
2 changes: 1 addition & 1 deletion Makefile
Expand Up @@ -2,4 +2,4 @@ compile: ; rebar3 do compile, xref
eunit: ; rebar3 eunit
init_dialyzer: ; rebar3 dialyzer -s false
dialyzer: ; rebar3 dialyzer -u false
travis: ; rebar3 do xref, dialyzer, eunit
travis: ; rebar3 do xref, dialyzer, eunit && rebar3 coveralls send
7 changes: 5 additions & 2 deletions README.md
@@ -1,9 +1,10 @@
# elli - Erlang web server for HTTP APIs

[![Travis CI][travis badge]][travis builds]
[![Hex.pm][hex badge]][hex package]
[![Erlang][erlang badge]][erlang downloads]
[![Documentation][doc badge]][docs]
[![Erlang][erlang badge]][erlang downloads]
[![Travis CI][travis badge]][travis builds]
[![Coverage Status][coveralls badge]][coveralls link]
[![MIT License][license badge]](LICENSE)

[travis builds]: https://travis-ci.org/elli-lib/elli
Expand All @@ -15,6 +16,8 @@
[erlang downloads]: http://www.erlang.org/downloads
[doc badge]: https://img.shields.io/badge/docs-edown-green.svg
[docs]: doc/README.md
[coveralls badge]: https://coveralls.io/repos/github/elli-lib/elli/badge.svg?branch=develop
[coveralls link]: https://coveralls.io/github/elli-lib/elli?branch=develop
[license badge]: https://img.shields.io/badge/license-MIT-blue.svg

Elli is a webserver you can run inside your Erlang application to
Expand Down
1 change: 1 addition & 0 deletions doc/README.md
Expand Up @@ -159,6 +159,7 @@ about benchmarking HTTP servers.


<table width="100%" border="0" summary="list of modules">
<tr><td><a href="adder.md" class="module">adder</a></td></tr>
<tr><td><a href="elli.md" class="module">elli</a></td></tr>
<tr><td><a href="elli_example_callback.md" class="module">elli_example_callback</a></td></tr>
<tr><td><a href="elli_example_callback_handover.md" class="module">elli_example_callback_handover</a></td></tr>
Expand Down
2 changes: 1 addition & 1 deletion doc/edoc-info
@@ -1,5 +1,5 @@
%% encoding: UTF-8
{application,elli}.
{modules,[elli,elli_example_callback,elli_example_callback_handover,
{modules,[adder,elli,elli_example_callback,elli_example_callback_handover,
elli_handler,elli_http,elli_middleware,elli_middleware_compress,
elli_request,elli_tcp,elli_test,elli_util]}.
2 changes: 1 addition & 1 deletion doc/elli_handler.md
Expand Up @@ -53,6 +53,6 @@ See [`elli_example_callback:handle_event/3`](elli_example_callback.md#handle_eve


<pre><code>
result() = {<a href="elli.md#type-response_code">elli:response_code()</a> | ok, <a href="elli.md#type-body">elli:body()</a>} | {<a href="elli.md#type-response_code">elli:response_code()</a> | ok, <a href="elli.md#type-headers">elli:headers()</a>, <a href="elli.md#type-body">elli:body()</a>} | ignore
result() = {<a href="elli.md#type-response_code">elli:response_code()</a> | ok, <a href="elli.md#type-headers">elli:headers()</a>, {file, <a href="file.md#type-name_all">file:name_all()</a>} | {file, <a href="file.md#type-name_all">file:name_all()</a>, <a href="elli_util.md#type-range">elli_util:range()</a>}} | {<a href="elli.md#type-response_code">elli:response_code()</a> | ok, <a href="elli.md#type-headers">elli:headers()</a>, <a href="elli.md#type-body">elli:body()</a>} | {<a href="elli.md#type-response_code">elli:response_code()</a> | ok, <a href="elli.md#type-body">elli:body()</a>} | {chunk, <a href="elli.md#type-headers">elli:headers()</a>} | {chunk, <a href="elli.md#type-headers">elli:headers()</a>, <a href="elli.md#type-body">elli:body()</a>} | ignore
</code></pre>

2 changes: 1 addition & 1 deletion doc/elli_test.md
Expand Up @@ -32,7 +32,7 @@ The unit tests below test `elli_example_callback`.<a name="index"></a>
### call/5 ###

<pre><code>
call(Method, Path, Headers, Body, Opts) -&gt; <a href="elli.md#type-req">elli:req()</a>
call(Method, Path, Headers, Body, Opts) -&gt; <a href="elli_handler.md#type-result">elli_handler:result()</a>
</code></pre>

<ul class="definitions"><li><code>Method = <a href="elli.md#type-http_method">elli:http_method()</a></code></li><li><code>Path = binary()</code></li><li><code>Headers = <a href="elli.md#type-headers">elli:headers()</a></code></li><li><code>Body = <a href="elli.md#type-body">elli:body()</a></code></li><li><code>Opts = <a href="proplists.md#type-proplist">proplists:proplist()</a></code></li></ul>
Expand Down
4 changes: 2 additions & 2 deletions doc/elli_util.md
Expand Up @@ -49,10 +49,10 @@ Encode Range to a Content-Range value.
### file_size/1 ###

<pre><code>
file_size(Filename::<a href="file.md#type-name">file:name()</a>) -&gt; non_neg_integer() | {error, Reason}
file_size(Filename) -&gt; Size | {error, Reason}
</code></pre>

<ul class="definitions"><li><code>Reason = badarg | <a href="file.md#type-posix">file:posix()</a></code></li></ul>
<ul class="definitions"><li><code>Filename = <a href="file.md#type-name_all">file:name_all()</a></code></li><li><code>Size = non_neg_integer()</code></li><li><code>Reason = <a href="file.md#type-posix">file:posix()</a> | badarg | invalid_file</code></li></ul>

Get the size in bytes of the file.

Expand Down
19 changes: 18 additions & 1 deletion rebar.config
@@ -1,6 +1,5 @@
{erl_first_files, ["src/elli_handler.erl"]}.
{erl_opts, [debug_info, {i, "include"}]}.
{cover_enabled, true}.
{deps, []}.
{xref_checks, [undefined_function_calls,locals_not_used]}.
{profiles, [
Expand All @@ -12,7 +11,25 @@
]},
{doclet, edown_doclet}
]}
]},
{test, [
{deps, [{hackney, "1.6.3"}]}
]}
]}.

{shell, [{script_file, "bin/shell.escript"}]}.

{project_plugins, [
{coveralls, "1.3.0"},
{rebar3_lint, "0.1.7"}
]}.

{provider_hooks, [{pre, [{eunit, lint}]}]}.

{cover_enabled, true}.
{cover_export_enabled, true}.
{cover_excl_mods, [
elli_handler
]}.
{coveralls_coverdata, "_build/test/cover/eunit.coverdata"}.
{coveralls_service_name, "travis-ci"}.
17 changes: 6 additions & 11 deletions rebar.config.script
@@ -1,12 +1,7 @@
case erl_internal:bif(is_map, 1) of
false -> CONFIG;
true ->
Lint = {rebar3_lint,
{git, "git://github.com/project-fifo/rebar3_lint.git",
{tag, "0.1.6"}}},
Config1 = lists:keystore(project_plugins, 1, CONFIG,
{project_plugins, [Lint]}),
Config2 = lists:keystore(provider_hooks, 1, Config1,
{provider_hooks, [{pre, [{eunit, lint}]}]})

case os:getenv("TRAVIS") of
"true" ->
JobId = os:getenv("TRAVIS_JOB_ID"),
lists:keystore(coveralls_service_job_id, 1, CONFIG,
{coveralls_service_job_id, JobId});
_ -> CONFIG
end.
8 changes: 4 additions & 4 deletions src/elli.app.src
@@ -1,7 +1,7 @@
{application, elli,
[
{description, "Erlang web server for HTTP APIs"},
{vsn, "2.0.0"},
{vsn, "2.0.1"},
{modules, [
elli,
elli_example_callback,
Expand All @@ -24,7 +24,7 @@
]},
{env, []},

{maintainers,["Eric Bailey", "Knut Nesheim", "Tristan Sloughter"]},
{licenses,["MIT"]},
{links,[{"Github","https://github.com/elli-lib/elli"}]}
{maintainers, ["Eric Bailey", "Knut Nesheim", "Tristan Sloughter"]},
{licenses, ["MIT"]},
{links, [{"GitHub", "https://github.com/elli-lib/elli"}]}
]}.
3 changes: 3 additions & 0 deletions src/elli_example_callback.erl
Expand Up @@ -181,6 +181,9 @@ handle('GET', [<<"chunked">>], Req) ->
handle('GET', [<<"shorthand">>], _Req) ->
{200, <<"hello">>};

handle('GET', [<<"ip">>], Req) ->
{<<"200 OK">>, elli_request:peer(Req)};

handle('GET', [<<"304">>], _Req) ->
%% A "Not Modified" response is exactly like a normal response (so
%% Content-Length is included), but the body will not be sent.
Expand Down
8 changes: 7 additions & 1 deletion src/elli_handler.erl
Expand Up @@ -22,8 +22,14 @@
| client_closed | client_timeout
| invalid_return.

-type result() :: {elli:response_code() | ok, elli:body()}
-type result() :: {elli:response_code() | ok,
elli:headers(),
{file, file:name_all()}
| {file, file:name_all(), elli_util:range()}}
| {elli:response_code() | ok, elli:headers(), elli:body()}
| {elli:response_code() | ok, elli:body()}
| {chunk, elli:headers()}
| {chunk, elli:headers(), elli:body()}
| ignore.

-callback handle(Req :: elli:req(), callback_args()) -> result().
Expand Down
76 changes: 45 additions & 31 deletions src/elli_http.erl
Expand Up @@ -70,7 +70,7 @@ keepalive_loop(Socket, Options, Callback) ->
keepalive_loop(Socket, NumRequests, Buffer, Options, Callback) ->
case ?MODULE:handle_request(Socket, Buffer, Options, Callback) of
{keep_alive, NewBuffer} ->
?MODULE:keepalive_loop(Socket, NumRequests,
?MODULE:keepalive_loop(Socket, NumRequests + 1,
NewBuffer, Options, Callback);
{close, _} ->
elli_tcp:close(Socket),
Expand All @@ -87,7 +87,6 @@ keepalive_loop(Socket, NumRequests, Buffer, Options, Callback) ->
Callback :: elli_handler:callback(),
ConnToken :: {'keep_alive' | 'close', binary()}.
handle_request(S, PrevB, Opts, {Mod, Args} = Callback) ->
t(request_start),
{Method, RawPath, V, B0} = get_request(S, PrevB, Opts, Callback),
t(headers_start),
{RequestHeaders, B1} = get_headers(S, V, B0, Opts, Callback),
Expand Down Expand Up @@ -280,10 +279,7 @@ send_server_error(Socket) ->
send_rescue_response(Socket, 500, <<"Server Error">>).

send_rescue_response(Socket, Code, Body) ->
Response = [<<"HTTP/1.1 ">>, status(Code), <<"\r\n">>,
<<"Content-Length: ">>, integer_to_list(size(Body)), <<"\r\n">>,
<<"\r\n">>,
Body],
Response = http_response(Code, Body),
elli_tcp:send(Socket, Response).

%% @doc Execute the user callback, translating failure into a proper response.
Expand Down Expand Up @@ -407,23 +403,17 @@ send_chunk(Socket, Data) ->
%%

%% @doc Retrieve the request line.
get_request(Socket, Buffer, Options, {Mod, Args} = Callback) ->
get_request(Socket, <<>>, Options, Callback) ->
NewBuffer = recv_request(Socket, <<>>, Options, Callback),
get_request(Socket, NewBuffer, Options, Callback);
get_request(Socket, Buffer, Options, Callback) ->
t(request_start),
get_request_(Socket, Buffer, Options, Callback).

get_request_(Socket, Buffer, Options, {Mod, Args} = Callback) ->
case erlang:decode_packet(http_bin, Buffer, []) of
{more, _} ->
case elli_tcp:recv(Socket, 0, request_timeout(Options)) of
{ok, Data} ->
NewBuffer = <<Buffer/binary, Data/binary>>,
get_request(Socket, NewBuffer, Options, Callback);
{error, timeout} ->
handle_event(Mod, request_timeout, [], Args),
elli_tcp:close(Socket),
exit(normal);
{error, Closed} when Closed =:= closed orelse
Closed =:= enotconn ->
handle_event(Mod, request_closed, [], Args),
elli_tcp:close(Socket),
exit(normal)
end;
recv_request(Socket, Buffer, Options, Callback);
{ok, {http_request, Method, RawPath, Version}, Rest} ->
{Method, RawPath, Version, Rest};
{ok, {http_error, _}, _} ->
Expand All @@ -436,6 +426,21 @@ get_request(Socket, Buffer, Options, {Mod, Args} = Callback) ->
exit(normal)
end.

recv_request(Socket, Buffer, Options, {Mod, Args} = _Callback) ->
case elli_tcp:recv(Socket, 0, request_timeout(Options)) of
{ok, Data} ->
<<Buffer/binary, Data/binary>>;
{error, timeout} ->
handle_event(Mod, request_timeout, [], Args),
elli_tcp:close(Socket),
exit(normal);
{error, Closed} when Closed =:= closed orelse
Closed =:= enotconn ->
handle_event(Mod, request_closed, [], Args),
elli_tcp:close(Socket),
exit(normal)
end.

-spec get_headers(Socket, V, Buffer, Opts, Callback) -> Headers when
Socket :: elli_tcp:socket(),
V :: version(),
Expand Down Expand Up @@ -544,8 +549,7 @@ maybe_send_continue(Socket, Headers) ->
% headers contains "Expect:100-continue"
case proplists:get_value(<<"Expect">>, Headers, undefined) of
<<"100-continue">> ->
Response = [<<"HTTP/1.1 ">>, status(100), <<"\r\n">>,
<<"Content-Length: 0">>, <<"\r\n\r\n">>],
Response = http_response(100),
elli_tcp:send(Socket, Response);
_Other ->
ok
Expand All @@ -561,19 +565,18 @@ check_max_size(Socket, ContentLength, Buffer, Opts, {Mod, Args}) ->
do_check_max_size(Socket, ContentLength, Buffer, MaxSize, {Mod, Args})
when ContentLength > MaxSize ->
handle_event(Mod, bad_request, [{body_size, ContentLength}], Args),
do_check_max_size_2x(Socket, ContentLength, Buffer, MaxSize),
do_check_max_size_x2(Socket, ContentLength, Buffer, MaxSize),
elli_tcp:close(Socket),
exit(normal);
do_check_max_size(_, _, _, _, _) -> ok.

do_check_max_size_2x(Socket, ContentLength, Buffer, MaxSize)
do_check_max_size_x2(Socket, ContentLength, Buffer, MaxSize)
when ContentLength < MaxSize * 2 ->
OnSocket = ContentLength - size(Buffer),
elli_tcp:recv(Socket, OnSocket, 60000),
Response = [<<"HTTP/1.1 ">>, status(413), <<"\r\n">>,
<<"Content-Length: 0">>, <<"\r\n\r\n">>],
Response = http_response(413),
elli_tcp:send(Socket, Response);
do_check_max_size_2x(_, _, _, _) -> ok.
do_check_max_size_x2(_, _, _, _) -> ok.

-spec mk_req(Method, PathTuple, Headers, Body, V, Socket, Callback) -> Req when
Method :: elli:http_method(),
Expand Down Expand Up @@ -603,9 +606,20 @@ mk_req(Method, RawPath, Headers, Body, V, Socket, {Mod, Args} = Callback) ->
%% HEADERS
%%

http_response(Code) ->
http_response(Code, <<>>).

http_response(Code, Body) ->
http_response(Code, [{<<"Content-Length">>, size(Body)}], Body).

http_response(Code, Headers, <<>>) ->
[<<"HTTP/1.1 ">>, status(Code), <<"\r\n">>,
encode_headers(Headers), <<"\r\n">>];
http_response(Code, Headers, Body) ->
[http_response(Code, Headers, <<>>), Body].

assemble_response_headers(Code, Headers) ->
ResponseHeaders = [<<"HTTP/1.1 ">>, status(Code), <<"\r\n">>,
encode_headers(Headers), <<"\r\n">>],
ResponseHeaders = http_response(Code, Headers, <<>>),
s(resp_headers, iolist_size(ResponseHeaders)),
ResponseHeaders.

Expand Down Expand Up @@ -849,6 +863,6 @@ get_body_test() ->
Buffer = binary:copy(<<".">>, 42),
Opts = [],
Callback = {no, op},
?assertEqual({Buffer, <<>>},
?assertMatch({Buffer, <<>>},
get_body(Socket, Headers, Buffer, Opts, Callback)).
-endif.
8 changes: 4 additions & 4 deletions src/elli_test.erl
Expand Up @@ -12,7 +12,7 @@

-export([call/5]).

-spec call(Method, Path, Headers, Body, Opts) -> elli:req() when
-spec call(Method, Path, Headers, Body, Opts) -> elli_handler:result() when
Method :: elli:http_method(),
Path :: binary(),
Headers :: elli:headers(),
Expand All @@ -30,13 +30,13 @@ call(Method, Path, Headers, Body, Opts) ->
-include_lib("eunit/include/eunit.hrl").

hello_world_test() ->
?assertEqual({ok, [], <<"Hello World!">>},
?assertMatch({ok, [], <<"Hello World!">>},
elli_test:call('GET', <<"/hello/world/">>, [], <<>>,
?EXAMPLE_CONF)),
?assertEqual({ok, [], <<"Hello Test1">>},
?assertMatch({ok, [], <<"Hello Test1">>},
elli_test:call('GET', <<"/hello/?name=Test1">>, [], <<>>,
?EXAMPLE_CONF)),
?assertEqual({ok,
?assertMatch({ok,
[{<<"Content-type">>,
<<"application/json; charset=ISO-8859-1">>}],
<<"{\"name\" : \"Test2\"}">>},
Expand Down
14 changes: 9 additions & 5 deletions src/elli_util.erl
Expand Up @@ -54,12 +54,16 @@ encode_range_bytes({Offset, Length}) ->
encode_range_bytes(invalid_range) -> <<"*">>.


-spec file_size(Filename::file:name()) ->
non_neg_integer() | {error, Reason}
when Reason :: badarg | file:posix().
-spec file_size(Filename) -> Size | {error, Reason} when
Filename :: file:name_all(),
Size :: non_neg_integer(),
Reason :: file:posix() | badarg | invalid_file.
%% @doc: Get the size in bytes of the file.
file_size(Filename) ->
case file:read_file_info(Filename) of
{ok, #file_info{size = Size}} -> Size;
{error, Reason} -> {error, Reason}
{ok, #file_info{type = regular, access = Perm, size = Size}}
when Perm =:= read orelse Perm =:= read_write ->
Size;
{error, Reason} -> {error, Reason};
_ -> {error, invalid_file}
end.
11 changes: 10 additions & 1 deletion test/elli_metrics_middleware.erl
@@ -1,15 +1,24 @@
-module(elli_metrics_middleware).
-export([handle/2, handle_event/3]).
-export([init/2, preprocess/2, handle/2, postprocess/3, handle_event/3]).
-behaviour(elli_handler).


%%
%% ELLI
%%

init(_Req, _Args) ->
ignore.

preprocess(Req, _Args) ->
Req.

handle(_Req, _Args) ->
ignore.

postprocess(_Req, Res, _Args) ->
Res.


%%
%% ELLI EVENT CALLBACKS
Expand Down

0 comments on commit c54f954

Please sign in to comment.