Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move support to OTP 22+ #114

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 39 additions & 7 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,50 @@ jobs:
runs-on: ${{ matrix.os }}

strategy:
fail-fast: false
matrix:
otp_version: ['24', '23', '22', '21']
os: [ubuntu-latest]
include:
- otp_version: 25
os: ubuntu-22.04
rebar3_version: 3.22
- otp_version: 24
os: ubuntu-22.04
rebar3_version: 3.22
- otp_version: 23
os: ubuntu-20.04
rebar3_version: 3.18
- otp_version: 22
os: ubuntu-20.04
rebar3_version: 3.18

steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3

- uses: erlef/setup-beam@v1
id: setup-beam
with:
otp-version: ${{ matrix.otp_version }}
rebar3-version: '3.14'
rebar3-version: ${{ matrix.rebar3_version }}

- name: Restore _build
uses: actions/cache@v3
with:
path: _build
key: "_build-cache-for\
-os-${{ matrix.os }}\
-otp-${{ steps.setup-beam.outputs.otp-version }}\
-rebar3-${{ steps.setup-beam.outputs.rebar3-version }}\
-hash-${{ hashFiles('rebar.lock') }}"

- name: Restore rebar3's cache
uses: actions/cache@v3
with:
path: ~/.cache/rebar3
key: "rebar3-cache-for\
-os-${{ matrix.os }}\
-otp-${{ steps.setup-beam.outputs.otp-version }}\
-rebar3-${{ steps.setup-beam.outputs.rebar3-version }}\
-hash-${{ hashFiles('rebar.lock') }}"

- name: Compile
run: rebar3 compile
Expand All @@ -36,9 +69,8 @@ jobs:
run: rebar3 xref

- name: Covertool
if: ${{ always() }}
run: rebar3 covertool generate
- uses: codecov/codecov-action@v1
- uses: codecov/codecov-action@v3
with:
file: _build/test/covertool/elli.covertool.xml
files: _build/test/covertool/elli.covertool.xml
env_vars: OTP_VERSION
33 changes: 5 additions & 28 deletions elvis.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,35 +3,12 @@
elvis,
[
{config,
[#{dirs => [
"src"
%% TODO: "test"
],
[#{dirs => ["src", "test"],
filter => "*.erl",
rules => [
{elvis_style, no_tabs},
{elvis_style, no_trailing_whitespace},
{elvis_style, no_if_expression},
{elvis_style, no_nested_try_catch},
{elvis_style, invalid_dynamic_call,
#{ignore => [
elli,
elli_http,
elli_middleware,
elli_test
]}},
{elvis_style, used_ignored_variable},
{elvis_style, no_behavior_info},
{elvis_style, state_record_and_type},
{elvis_style, no_spec_with_records},
{elvis_style, dont_repeat_yourself},
{elvis_style, no_debug_call}
],
ruleset => erl_files
},
#{dirs => ["."],
filter => "Makefile",
ruleset => makefiles
ruleset => erl_files,
rules => [{elvis_style, no_throw, disable},
{elvis_style, invalid_dynamic_call, disable},
{elvis_style, dont_repeat_yourself, disable}]
},
#{dirs => ["."],
filter => "rebar.config",
Expand Down
18 changes: 7 additions & 11 deletions rebar.config
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
{erl_first_files, ["src/elli_handler.erl"]}.
{erl_opts, [debug_info,
{i, "include"},
{platform_define, "^2", binary_http_uri},
{platform_define, "^2[1-9]", post20}]}.
{minimum_otp_vsn, "20.0"}.
{deps, []}.
{xref_checks, [undefined_function_calls,locals_not_used]}.
{erl_opts, [debug_info, warnings_as_errors]}.
{minimum_otp_vsn, "21.0"}.
{xref_checks, [deprecated_function_calls, undefined_function_calls, locals_not_used]}.
{profiles, [
{docs, [
{deps, [{edown, "0.8.1"}]},
Expand All @@ -18,19 +14,19 @@
]}
]},
{test, [
{deps, [{hackney, "1.17.4"}]}
{deps, [{hackney, "1.18.1"}]}
]}
]}.

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

{project_plugins, [
{covertool, "2.0.3"},
{rebar3_lint, "v0.1.10"}
{covertool, "2.0.6"},
{rebar3_lint, "1.0.2"}
]}.

{provider_hooks, [{pre, [{eunit, lint}]}]}.
{dialyzer, [{plt_extra_apps, [ssl]}]}.
{dialyzer, [{plt_extra_apps, [ssl]}, {warnings, [unknown]}]}.

{cover_enabled, true}.
{cover_export_enabled, true}.
Expand Down
14 changes: 7 additions & 7 deletions src/elli.erl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@

%% @type req(). A record representing an HTTP request.
-type req() :: #req{}.
-elvis([{elvis_style, private_data_types, disable}]).

%% @type http_method(). An uppercase atom representing a known HTTP verb or a
%% binary for other verbs.
Expand All @@ -37,7 +38,7 @@
%% @type body(). A binary or iolist.
-type body() :: binary() | iolist().

-type header() :: {Key::binary(), Value::binary() | string()}.
-type header() :: {Key :: binary(), Value :: binary() | string()}.
-type headers() :: [header()].

-type response_code() :: 100..999.
Expand Down Expand Up @@ -162,19 +163,18 @@ init([Opts]) ->
| SSLSockOpts]),

Acceptors = ets:new(acceptors, [private, set]),
[begin
Pid = elli_http:start_link(self(), Socket, Options,
{Callback, CallbackArgs}),
ets:insert(Acceptors, {Pid})
end
|| _ <- lists:seq(1, MinAcceptors)],
[http_start(Socket, Options, Callback, CallbackArgs, Acceptors)
|| _ <- lists:seq(1, MinAcceptors)],

{ok, #state{socket = Socket,
acceptors = Acceptors,
open_reqs = 0,
options = Options,
callback = {Callback, CallbackArgs}}}.

http_start(Socket, Options, Callback, CallbackArgs, Acceptors) ->
Pid = elli_http:start_link(self(), Socket, Options, {Callback, CallbackArgs}),
ets:insert(Acceptors, {Pid}).

%% @hidden
-spec handle_call(get_acceptors, {pid(), _Tag}, state()) ->
Expand Down
4 changes: 2 additions & 2 deletions src/elli_example_callback.erl
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ chunk_loop(Ref, N) ->
{error, Reason} -> ?LOG_ERROR("error in sending chunk: ~p~n", [Reason])
end,

chunk_loop(Ref, N-1).
chunk_loop(Ref, N - 1).


%%
Expand Down Expand Up @@ -297,7 +297,7 @@ chunk_loop(Ref, N) ->
%% `file_error' is sent when the user wants to return a file as a
%% response, but for some reason it cannot be opened.
-spec handle_event(Event, Args, Config) -> ok when
Event :: elli:event(),
Event :: elli_handler:event(),
Args :: elli_handler:callback_args(),
Config :: [tuple()].
handle_event(elli_startup, [], _) -> ok;
Expand Down
84 changes: 20 additions & 64 deletions src/elli_http.erl
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
-define(CONNECTION_HEADER, <<"connection">>).
-define(TRANSFER_ENCODING_HEADER, <<"Transfer-Encoding">>).

-elvis([{elvis_style, max_function_arity, disable}]).

%% TODO: use this
%% -type connection_token() :: keep_alive | close.

Expand All @@ -58,7 +60,7 @@ start_link(Server, ListenSocket, Options, Callback) ->
Options :: proplists:proplist(),
Callback :: elli_handler:callback().
accept(Server, ListenSocket, Options, Callback) ->
case catch elli_tcp:accept(ListenSocket, Server, accept_timeout(Options)) of
case elli_tcp:accept(ListenSocket, Server, accept_timeout(Options)) of
Comment on lines -61 to +63
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see where catch was actually used, since all the clauses derive not from exceptions, but simple function returns.

{ok, Socket} ->
t(accepted),
?MODULE:keepalive_loop(Socket, Options, Callback);
Expand Down Expand Up @@ -323,17 +325,17 @@ execute_callback(#req{callback = {Mod, Args}} = Req) ->
catch
throw:{ResponseCode, Headers, Body} when is_integer(ResponseCode) ->
{response, ResponseCode, Headers, Body};
?WITH_STACKTRACE(throw, Exc, Stacktrace)
throw:Exc:Stacktrace ->
handle_event(Mod, request_throw,
[Req, Exc, Stacktrace],
Args),
{response, 500, [], <<"Internal server error">>};
?WITH_STACKTRACE(error, Error, Stacktrace)
error:Error:Stacktrace ->
handle_event(Mod, request_error,
[Req, Error, Stacktrace],
Args),
{response, 500, [], <<"Internal server error">>};
?WITH_STACKTRACE(exit, Exit, Stacktrace)
exit:Exit:Stacktrace ->
handle_event(Mod, request_exit,
[Req, Exit, Stacktrace],
Args),
Expand Down Expand Up @@ -697,7 +699,7 @@ connection(Req, UserHeaders) ->
[]
end.

content_length(Headers, Body)->
content_length(Headers, Body) ->
?IF(is_header_defined(?CONTENT_LENGTH_HEADER, Headers), [],
{?CONTENT_LENGTH_HEADER, iolist_size(Body)}).

Expand All @@ -708,7 +710,6 @@ is_header_defined(Key, Headers) ->
get_header(Key, Headers) ->
get_header(Key, Headers, undefined).

-ifdef(OTP_RELEASE).
get_header(Key, Headers, Default) ->
CaseFoldedKey = string:casefold(Key),
case lists:search(fun({N, _}) -> string:equal(CaseFoldedKey, N, true) end, Headers) of
Expand All @@ -717,68 +718,23 @@ get_header(Key, Headers, Default) ->
false ->
Default
end.
-else.
get_header(Key, Headers, Default) ->
CaseFoldedKey = string:casefold(Key),
case search(fun({N, _}) -> string:equal(CaseFoldedKey, N, true) end, Headers) of
{value, {_, Value}} ->
Value;
false ->
Default
end.

search(Pred, [Hd|Tail]) ->
case Pred(Hd) of
true -> {value, Hd};
false -> search(Pred, Tail)
end;
search(Pred, []) when is_function(Pred, 1) ->
false.
-endif.

%%
%% PATH HELPERS
%%

-ifdef(OTP_RELEASE).
-if(?OTP_RELEASE >= 22).
parse_path({abs_path, FullPath}) ->
URIMap = uri_string:parse(FullPath),
Host = maps:get(host, URIMap, undefined),
Scheme = maps:get(scheme, URIMap, undefined),
Path = maps:get(path, URIMap, <<>>),
Query = maps:get(query, URIMap, <<>>),
Port = maps:get(port, URIMap, case Scheme of http -> 80; https -> 443; _ -> undefined end),
{ok, {Scheme, Host, Port}, {Path, split_path(Path), uri_string:dissect_query(Query)}};
parse_path({absoluteURI, Scheme, Host, Port, Path}) ->
setelement(2, parse_path({abs_path, Path}), {Scheme, Host, Port});
parse_path(_) ->
{error, unsupported_uri}.
-else.
parse_path({abs_path, FullPath}) ->
Parsed = case binary:split(FullPath, [<<"?">>]) of
[URL] -> {FullPath, split_path(URL), []};
[URL, Args] -> {FullPath, split_path(URL), split_args(Args)}
end,
{ok, {undefined, undefined, undefined}, Parsed};
parse_path({absoluteURI, Scheme, Host, Port, Path}) ->
setelement(2, parse_path({abs_path, Path}), {Scheme, Host, Port});
parse_path(_) ->
{error, unsupported_uri}.
-endif.
-else.
%% same as else branch above. can drop this when only OTP 21+ is supported
parse_path({abs_path, FullPath}) ->
Parsed = case binary:split(FullPath, [<<"?">>]) of
[URL] -> {FullPath, split_path(URL), []};
[URL, Args] -> {FullPath, split_path(URL), split_args(Args)}
end,
{ok, {undefined, undefined, undefined}, Parsed};
parse_path({absoluteURI, Scheme, Host, Port, Path}) ->
setelement(2, parse_path({abs_path, Path}), {Scheme, Host, Port});
parse_path(_) ->
{error, unsupported_uri}.
-endif.
parse_path({abs_path, FullPath}) ->
URIMap = uri_string:parse(FullPath),
Host = maps:get(host, URIMap, undefined),
Scheme = maps:get(scheme, URIMap, undefined),
Path = maps:get(path, URIMap, <<>>),
Query = maps:get(query, URIMap, <<>>),
Port = maps:get(port, URIMap, case Scheme of http -> 80; https -> 443; _ -> undefined end),
{ok, {Scheme, Host, Port}, {Path, split_path(Path), uri_string:dissect_query(Query)}};
parse_path({'absoluteURI', Scheme, Host, Port, Path}) ->
setelement(2, parse_path({abs_path, Path}), {Scheme, Host, Port});
parse_path(_) ->
{error, unsupported_uri}.

split_path(Path) ->
[P || P <- binary:split(Path, [<<"/">>], [global]),
Expand Down Expand Up @@ -813,7 +769,7 @@ handle_event(Mod, Name, EventArgs, ElliArgs) ->
try
Mod:handle_event(Name, EventArgs, ElliArgs)
catch
?WITH_STACKTRACE(EvClass, EvError, Stacktrace)
EvClass:EvError:Stacktrace ->
?LOG_ERROR("~p:handle_event/3 crashed ~p:~p~n~p",
[Mod, EvClass, EvError, Stacktrace])
end.
Expand Down
4 changes: 2 additions & 2 deletions src/elli_middleware.erl
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,10 @@ handle_event(Event, Args, Config) ->

-spec do_init(Req, Callbacks) -> {ok, standard | handover} when
Req :: elli:req(),
Callbacks :: elli_handler:callbacks().
Callbacks :: [elli_handler:callback()].
do_init(_, []) ->
{ok, standard};
do_init(Req, [{Mod, Args}|Mods]) ->
do_init(Req, [{Mod, Args} | Mods]) ->
?IF_NOT_EXPORTED(Mod, init, 2, do_init(Req, Mods),
case Mod:init(Req, Args) of
ignore -> do_init(Req, Mods);
Expand Down
Loading