浏览代码

st: 格式化

master
SisMaker 3 年前
父节点
当前提交
c6cfe3d160
共有 23 个文件被更改,包括 2216 次插入2145 次删除
  1. +77
    -0
      README.md
  2. +1
    -1
      rebar.config
  3. +31
    -31
      src/test/elli_handover_tests.erl
  4. +17
    -17
      src/test/elli_http_tests.erl
  5. +11
    -11
      src/test/elli_metrics_middleware.erl
  6. +73
    -73
      src/test/elli_middleware_tests.erl
  7. +91
    -91
      src/test/elli_ssl_tests.erl
  8. +7
    -7
      src/test/elli_test.hrl
  9. +576
    -576
      src/test/elli_tests.erl
  10. +147
    -147
      src/wsSrv/elli.erl
  11. +106
    -108
      src/wsSrv/elli_example_callback.erl
  12. +19
    -19
      src/wsSrv/elli_example_callback_handover.erl
  13. +4
    -4
      src/wsSrv/elli_example_middleware.erl
  14. +27
    -27
      src/wsSrv/elli_handler.erl
  15. +609
    -609
      src/wsSrv/elli_http.erl
  16. +55
    -57
      src/wsSrv/elli_middleware.erl
  17. +28
    -28
      src/wsSrv/elli_middleware_compress.erl
  18. +167
    -168
      src/wsSrv/elli_request.erl
  19. +50
    -50
      src/wsSrv/elli_sendfile.erl
  20. +56
    -56
      src/wsSrv/elli_tcp.erl
  21. +24
    -24
      src/wsSrv/elli_test.erl
  22. +37
    -37
      src/wsSrv/elli_util.erl
  23. +3
    -4
      src/wsSrv/elli_util.hrl

+ 77
- 0
README.md 查看文件

@ -7,3 +7,80 @@ Build
-----
$ rebar3 compile
## Usage
```sh
$ rebar3 shell
```
```erlang
%% starting elli
1 > {ok, Pid} = elli:start_link([{callback, elli_example_callback}, {port, 3000}]).
```
## Examples
### Callback Module
The best source to learn how to write a callback module
is [src/elli_example_callback.erl](src/elli_example_callback.erl) and
its [generated documentation](doc/elli_example_callback.md). There are a bunch of examples used in the tests as well as
descriptions of all the events.
A minimal callback module could look like this:
```erlang
-module(elli_minimal_callback).
-export([handle/2, handle_event/3]).
-include_lib("elli/include/elli.hrl").
-behaviour(elli_handler).
handle(Req, _Args) ->
%% Delegate to our handler function
handle(Req#req.method, elli_request:path(Req), Req).
handle('GET', [<<"hello">>, <<"world">>], _Req) ->
%% Reply with a normal response. `ok' can be used instead of `200'
%% to signal success.
{ok, [], <<"Hello World!">>};
handle(_, _, _Req) ->
{404, [], <<"Not Found">>}.
%% @doc Handle request events, like request completed, exception
%% thrown, client timeout, etc. Must return `ok'.
handle_event(_Event, _Data, _Args) ->
ok.
```
### Supervisor Childspec
To add `elli` to a supervisor you can use the following example and adapt it to your needs.
```erlang
-module(fancyapi_sup).
-behaviour(supervisor).
-export([start_link/0]).
-export([init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
ElliOpts = [{callback, fancyapi_callback}, {port, 3000}],
ElliSpec = {
fancy_http,
{elli, start_link, [ElliOpts]},
permanent,
5000,
worker,
[elli]},
{ok, {{one_for_one, 5, 10}, [ElliSpec]}}.
```
## Further Reading
For more information about the features and design philosophy of `elli` check out the [overview](doc/README.md).

+ 1
- 1
rebar.config 查看文件

@ -8,7 +8,7 @@
]}.
{deps, [
{eNet, ".*", {git, "http://sismaker.tpddns.cn:53000/SisMaker/eNets.git", {branch, "master"}}},
{eNet, ".*", {git, "http://sismaker.tpddns.cn:53000/SisMaker/eNet.git", {branch, "master"}}},
{eFmt, ".*", {git, "http://sismaker.tpddns.cn:53000/SisMaker/eFmt.git", {branch, "master"}}},
{eGbh, ".*", {git, "http://sismaker.tpddns.cn:53000/SisMaker/eGbh.git", {branch, "master"}}},
{eSync, ".*", {git, "http://sismaker.tpddns.cn:53000/SisMaker/eSync.git", {branch, "master"}}},

+ 31
- 31
src/test/elli_handover_tests.erl 查看文件

@ -3,35 +3,35 @@
-include("elli_test.hrl").
elli_test_() ->
{setup,
fun setup/0, fun teardown/1,
[
?_test(hello_world()),
?_test(echo())
]}.
{setup,
fun setup/0, fun teardown/1,
[
?_test(hello_world()),
?_test(echo())
]}.
setup() ->
application:start(crypto),
application:start(public_key),
application:start(ssl),
{ok, _} = application:ensure_all_started(hackney),
Config = [
{mods, [
{elli_example_callback_handover, []}
]}
],
{ok, P} = elli:start_link([{callback, elli_middleware},
{callback_args, Config},
{port, 3003}]),
unlink(P),
[P].
application:start(crypto),
application:start(public_key),
application:start(ssl),
{ok, _} = application:ensure_all_started(hackney),
Config = [
{mods, [
{elli_example_callback_handover, []}
]}
],
{ok, P} = elli:start_link([{callback, elli_middleware},
{callback_args, Config},
{port, 3003}]),
unlink(P),
[P].
teardown(Pids) ->
[elli:stop(P) || P <- Pids].
[elli:stop(P) || P <- Pids].
%%
@ -40,13 +40,13 @@ teardown(Pids) ->
%%
hello_world() ->
Response = hackney:get("http://localhost:3003/hello/world"),
?assertMatch(200, status(Response)),
?assertMatch([{<<"Connection">>, <<"close">>},
{<<"Content-Length">>, <<"12">>}], headers(Response)),
?assertMatch(<<"Hello World!">>, body(Response)).
Response = hackney:get("http://localhost:3003/hello/world"),
?assertMatch(200, status(Response)),
?assertMatch([{<<"Connection">>, <<"close">>},
{<<"Content-Length">>, <<"12">>}], headers(Response)),
?assertMatch(<<"Hello World!">>, body(Response)).
echo() ->
Response = hackney:get("http://localhost:3003/hello?name=knut"),
?assertMatch(200, status(Response)),
?assertMatch(<<"Hello knut">>, body(Response)).
Response = hackney:get("http://localhost:3003/hello?name=knut"),
?assertMatch(200, status(Response)),
?assertMatch(<<"Hello knut">>, body(Response)).

+ 17
- 17
src/test/elli_http_tests.erl 查看文件

@ -4,24 +4,24 @@
%% UNIT TESTS
chunk_loop_test_() ->
fun () ->
Here = self(),
Pid = spawn_link(chunk_loop_wrapper(Here)),
Pid ! {tcp_closed, some_socket},
Message = receive_message(),
?assertMatch({error, client_closed}, Message)
end.
fun() ->
Here = self(),
Pid = spawn_link(chunk_loop_wrapper(Here)),
Pid ! {tcp_closed, some_socket},
Message = receive_message(),
?assertMatch({error, client_closed}, Message)
end.
chunk_loop_wrapper(Here) ->
fun () ->
Result = elli_http:chunk_loop({some_type, some_socket}),
Here ! Result,
ok
end.
fun() ->
Result = elli_http:chunk_loop({some_type, some_socket}),
Here ! Result,
ok
end.
receive_message() ->
receive
X -> X
after
1 -> fail
end.
receive
X -> X
after
1 -> fail
end.

+ 11
- 11
src/test/elli_metrics_middleware.erl 查看文件

@ -8,27 +8,27 @@
%%
init(_Req, _Args) ->
ignore.
ignore.
preprocess(Req, _Args) ->
Req.
Req.
handle(_Req, _Args) ->
ignore.
ignore.
postprocess(_Req, Res, _Args) ->
Res.
Res.
%%
%% ELLI EVENT CALLBACKS
%%
handle_event(request_complete, [_Req,_C,_Hs,_B, {Timings, Sizes}], _) ->
ets:insert(elli_stat_table, {timings, Timings}),
ets:insert(elli_stat_table, {sizes, Sizes});
handle_event(chunk_complete, [_Req,_C,_Hs,_B, {Timings, Sizes}], _) ->
ets:insert(elli_stat_table, {timings, Timings}),
ets:insert(elli_stat_table, {sizes, Sizes});
handle_event(request_complete, [_Req, _C, _Hs, _B, {Timings, Sizes}], _) ->
ets:insert(elli_stat_table, {timings, Timings}),
ets:insert(elli_stat_table, {sizes, Sizes});
handle_event(chunk_complete, [_Req, _C, _Hs, _B, {Timings, Sizes}], _) ->
ets:insert(elli_stat_table, {timings, Timings}),
ets:insert(elli_stat_table, {sizes, Sizes});
handle_event(_Event, _Data, _Args) ->
ok.
ok.

+ 73
- 73
src/test/elli_middleware_tests.erl 查看文件

@ -3,14 +3,14 @@
-include("elli_test.hrl").
elli_test_() ->
{setup,
fun setup/0, fun teardown/1,
[
?_test(hello_world()),
?_test(short_circuit()),
?_test(compress()),
?_test(no_callbacks())
]}.
{setup,
fun setup/0, fun teardown/1,
[
?_test(hello_world()),
?_test(short_circuit()),
?_test(compress()),
?_test(no_callbacks())
]}.
%%
@ -18,53 +18,53 @@ elli_test_() ->
%%
short_circuit() ->
URL = "http://localhost:3002/middleware/short-circuit",
Response = hackney:get(URL),
?assertMatch(<<"short circuit!">>, body(Response)).
URL = "http://localhost:3002/middleware/short-circuit",
Response = hackney:get(URL),
?assertMatch(<<"short circuit!">>, body(Response)).
hello_world() ->
URL = "http://localhost:3002/hello/world",
Response = hackney:get(URL),
?assertMatch(<<"Hello World!">>, body(Response)).
URL = "http://localhost:3002/hello/world",
Response = hackney:get(URL),
?assertMatch(<<"Hello World!">>, body(Response)).
compress() ->
Url = "http://localhost:3002/compressed",
Headers = [{<<"Accept-Encoding">>, <<"gzip">>}],
Response = hackney:get(Url, Headers),
?assertHeadersEqual([{<<"Connection">>, <<"Keep-Alive">>},
{<<"Content-Encoding">>, <<"gzip">>},
{<<"Content-Length">>, <<"41">>}],
headers(Response)),
?assertEqual(binary:copy(<<"Hello World!">>, 86),
zlib:gunzip(body(Response))),
Response1 = hackney:get("http://localhost:3002/compressed"),
?assertHeadersEqual([{<<"Connection">>, <<"Keep-Alive">>},
{<<"Content-Length">>, <<"1032">>}],
headers(Response1)),
?assertEqual(iolist_to_binary(lists:duplicate(86, "Hello World!")),
body(Response1)),
Url2 = "http://localhost:3002/compressed-io_list",
Headers2 = [{<<"Accept-Encoding">>, <<"gzip">>}],
Response2 = hackney:get(Url2, Headers2),
?assertMatch(200, status(Response2)),
?assertHeadersEqual([{<<"Connection">>, <<"Keep-Alive">>},
{<<"Content-Encoding">>, <<"gzip">>},
{<<"Content-Length">>, <<"41">>}],
headers(Response2)),
?assertEqual(binary:copy(<<"Hello World!">>, 86),
zlib:gunzip(body(Response2))),
Response3 = hackney:request("http://localhost:3002/compressed-io_list"),
?assertMatch(200, status(Response3)),
?assertHeadersEqual([{<<"Connection">>, <<"Keep-Alive">>},
{<<"Content-Length">>, <<"1032">>}],
headers(Response3)),
?assertEqual(iolist_to_binary(lists:duplicate(86, "Hello World!")),
body(Response3)).
Url = "http://localhost:3002/compressed",
Headers = [{<<"Accept-Encoding">>, <<"gzip">>}],
Response = hackney:get(Url, Headers),
?assertHeadersEqual([{<<"Connection">>, <<"Keep-Alive">>},
{<<"Content-Encoding">>, <<"gzip">>},
{<<"Content-Length">>, <<"41">>}],
headers(Response)),
?assertEqual(binary:copy(<<"Hello World!">>, 86),
zlib:gunzip(body(Response))),
Response1 = hackney:get("http://localhost:3002/compressed"),
?assertHeadersEqual([{<<"Connection">>, <<"Keep-Alive">>},
{<<"Content-Length">>, <<"1032">>}],
headers(Response1)),
?assertEqual(iolist_to_binary(lists:duplicate(86, "Hello World!")),
body(Response1)),
Url2 = "http://localhost:3002/compressed-io_list",
Headers2 = [{<<"Accept-Encoding">>, <<"gzip">>}],
Response2 = hackney:get(Url2, Headers2),
?assertMatch(200, status(Response2)),
?assertHeadersEqual([{<<"Connection">>, <<"Keep-Alive">>},
{<<"Content-Encoding">>, <<"gzip">>},
{<<"Content-Length">>, <<"41">>}],
headers(Response2)),
?assertEqual(binary:copy(<<"Hello World!">>, 86),
zlib:gunzip(body(Response2))),
Response3 = hackney:request("http://localhost:3002/compressed-io_list"),
?assertMatch(200, status(Response3)),
?assertHeadersEqual([{<<"Connection">>, <<"Keep-Alive">>},
{<<"Content-Length">>, <<"1032">>}],
headers(Response3)),
?assertEqual(iolist_to_binary(lists:duplicate(86, "Hello World!")),
body(Response3)).
no_callbacks() ->
Response = hackney:get("http://localhost:3004/whatever"),
?assertMatch(404, status(Response)),
?assertMatch(<<"Not Found">>, body(Response)).
Response = hackney:get("http://localhost:3004/whatever"),
?assertMatch(404, status(Response)),
?assertMatch(<<"Not Found">>, body(Response)).
%%
@ -72,31 +72,31 @@ no_callbacks() ->
%%
setup() ->
application:start(crypto),
application:start(public_key),
application:start(ssl),
{ok, _} = application:ensure_all_started(hackney),
application:start(crypto),
application:start(public_key),
application:start(ssl),
{ok, _} = application:ensure_all_started(hackney),
Config = [
{mods, [
{elli_access_log, [{name, elli_syslog},
{ip, "127.0.0.1"},
{port, 514}]},
{elli_example_middleware, []},
{elli_middleware_compress, []},
{elli_example_callback, []}
]}
],
Config = [
{mods, [
{elli_access_log, [{name, elli_syslog},
{ip, "127.0.0.1"},
{port, 514}]},
{elli_example_middleware, []},
{elli_middleware_compress, []},
{elli_example_callback, []}
]}
],
{ok, P1} = elli:start_link([{callback, elli_middleware},
{callback_args, Config},
{port, 3002}]),
unlink(P1),
{ok, P2} = elli:start_link([{callback, elli_middleware},
{callback_args, [{mods, []}]},
{port, 3004}]),
unlink(P2),
[P1, P2].
{ok, P1} = elli:start_link([{callback, elli_middleware},
{callback_args, Config},
{port, 3002}]),
unlink(P1),
{ok, P2} = elli:start_link([{callback, elli_middleware},
{callback_args, [{mods, []}]},
{port, 3004}]),
unlink(P2),
[P1, P2].
teardown(Pids) ->
[elli:stop(P) || P <- Pids].
[elli:stop(P) || P <- Pids].

+ 91
- 91
src/test/elli_ssl_tests.erl 查看文件

@ -5,120 +5,120 @@
-define(README, "README.md").
elli_ssl_test_() ->
{setup,
fun setup/0, fun teardown/1,
[{foreach,
fun init_stats/0, fun clear_stats/1,
[
?_test(hello_world()),
?_test(chunked()),
?_test(sendfile()),
?_test(acceptor_leak_regression())
]}
]}.
{setup,
fun setup/0, fun teardown/1,
[{foreach,
fun init_stats/0, fun clear_stats/1,
[
?_test(hello_world()),
?_test(chunked()),
?_test(sendfile()),
?_test(acceptor_leak_regression())
]}
]}.
get_size_value(Key) ->
[{sizes, Sizes}] = ets:lookup(elli_stat_table, sizes),
proplists:get_value(Key, Sizes).
[{sizes, Sizes}] = ets:lookup(elli_stat_table, sizes),
proplists:get_value(Key, Sizes).
get_timing_value(Key) ->
[{timings, Timings}] = ets:lookup(elli_stat_table, timings),
proplists:get_value(Key, Timings).
[{timings, Timings}] = ets:lookup(elli_stat_table, timings),
proplists:get_value(Key, Timings).
%%% Tests
hello_world() ->
Response = hackney:get("https://localhost:3443/hello/world",
[], <<>>, [insecure]),
?assertMatch(200, status(Response)),
?assertMatch({ok, 200, _, _}, Response).
Response = hackney:get("https://localhost:3443/hello/world",
[], <<>>, [insecure]),
?assertMatch(200, status(Response)),
?assertMatch({ok, 200, _, _}, Response).
chunked() ->
Expected = <<"chunk10chunk9chunk8chunk7chunk6chunk5chunk4chunk3chunk2chunk1">>,
Expected = <<"chunk10chunk9chunk8chunk7chunk6chunk5chunk4chunk3chunk2chunk1">>,
Response = hackney:get("https://localhost:3443/chunked", [], <<>>, [insecure]),
Response = hackney:get("https://localhost:3443/chunked", [], <<>>, [insecure]),
?assertMatch(200, status(Response)),
?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>},
{<<"content-type">>, <<"text/event-stream">>},
{<<"transfer-encoding">>,<<"chunked">>}], headers(Response)),
?assertMatch(Expected, body(Response)).
?assertMatch(200, status(Response)),
?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>},
{<<"content-type">>, <<"text/event-stream">>},
{<<"transfer-encoding">>, <<"chunked">>}], headers(Response)),
?assertMatch(Expected, body(Response)).
sendfile() ->
Response = hackney:get("https://localhost:3443/sendfile", [], <<>>, [insecure]),
F = ?README,
{ok, Expected} = file:read_file(F),
?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>},
{<<"content-length">>, ?I2B(size(Expected))}],
headers(Response)),
?assertEqual(Expected, body(Response)),
%% sizes
?assertEqual(size(Expected), get_size_value(file)),
?assertMatch(65, get_size_value(resp_headers)),
%% timings
?assertNotMatch(undefined, get_timing_value(request_start)),
?assertNotMatch(undefined, get_timing_value(headers_start)),
?assertNotMatch(undefined, get_timing_value(headers_end)),
?assertNotMatch(undefined, get_timing_value(body_start)),
?assertNotMatch(undefined, get_timing_value(body_end)),
?assertNotMatch(undefined, get_timing_value(user_start)),
?assertNotMatch(undefined, get_timing_value(user_end)),
?assertNotMatch(undefined, get_timing_value(send_start)),
?assertNotMatch(undefined, get_timing_value(send_end)),
?assertNotMatch(undefined, get_timing_value(request_end)).
Response = hackney:get("https://localhost:3443/sendfile", [], <<>>, [insecure]),
F = ?README,
{ok, Expected} = file:read_file(F),
?assertHeadersEqual([{<<"connection">>, <<"Keep-Alive">>},
{<<"content-length">>, ?I2B(size(Expected))}],
headers(Response)),
?assertEqual(Expected, body(Response)),
%% sizes
?assertEqual(size(Expected), get_size_value(file)),
?assertMatch(65, get_size_value(resp_headers)),
%% timings
?assertNotMatch(undefined, get_timing_value(request_start)),
?assertNotMatch(undefined, get_timing_value(headers_start)),
?assertNotMatch(undefined, get_timing_value(headers_end)),
?assertNotMatch(undefined, get_timing_value(body_start)),
?assertNotMatch(undefined, get_timing_value(body_end)),
?assertNotMatch(undefined, get_timing_value(user_start)),
?assertNotMatch(undefined, get_timing_value(user_end)),
?assertNotMatch(undefined, get_timing_value(send_start)),
?assertNotMatch(undefined, get_timing_value(send_end)),
?assertNotMatch(undefined, get_timing_value(request_end)).
acceptor_leak_regression() ->
{ok, Before} = elli:get_acceptors(elli_under_test),
Opts = [{verify, verify_peer},
{verify_fun, {fun(_,_) -> {fail, 23} end, []}},
{reuse_sessions, false}],
{error, _} = ssl:connect("localhost", 3443, Opts),
{ok, After} = elli:get_acceptors(elli_under_test),
?assertEqual(length(Before), length(After)).
{ok, Before} = elli:get_acceptors(elli_under_test),
Opts = [{verify, verify_peer},
{verify_fun, {fun(_, _) -> {fail, 23} end, []}},
{reuse_sessions, false}],
{error, _} = ssl:connect("localhost", 3443, Opts),
{ok, After} = elli:get_acceptors(elli_under_test),
?assertEqual(length(Before), length(After)).
%%% Internal helpers
setup() ->
application:start(asn1),
application:start(crypto),
application:start(public_key),
application:start(ssl),
{ok, _} = application:ensure_all_started(hackney),
EbinDir = filename:dirname(code:which(?MODULE)),
CertDir = filename:join([EbinDir, "..", "test"]),
CertFile = filename:join(CertDir, "server_cert.pem"),
KeyFile = filename:join(CertDir, "server_key.pem"),
Config = [
{mods, [
{elli_metrics_middleware, []},
{elli_middleware_compress, []},
{elli_example_callback, []}
]}
],
{ok, P} = elli:start_link([
{port, 3443},
ssl,
{keyfile, KeyFile},
{certfile, CertFile},
{callback, elli_middleware},
{callback_args, Config}
]),
unlink(P),
erlang:register(elli_under_test, P),
[P].
application:start(asn1),
application:start(crypto),
application:start(public_key),
application:start(ssl),
{ok, _} = application:ensure_all_started(hackney),
EbinDir = filename:dirname(code:which(?MODULE)),
CertDir = filename:join([EbinDir, "..", "test"]),
CertFile = filename:join(CertDir, "server_cert.pem"),
KeyFile = filename:join(CertDir, "server_key.pem"),
Config = [
{mods, [
{elli_metrics_middleware, []},
{elli_middleware_compress, []},
{elli_example_callback, []}
]}
],
{ok, P} = elli:start_link([
{port, 3443},
ssl,
{keyfile, KeyFile},
{certfile, CertFile},
{callback, elli_middleware},
{callback_args, Config}
]),
unlink(P),
erlang:register(elli_under_test, P),
[P].
teardown(Pids) ->
[elli:stop(P) || P <- Pids],
application:stop(ssl),
application:stop(public_key),
application:stop(crypto).
[elli:stop(P) || P <- Pids],
application:stop(ssl),
application:stop(public_key),
application:stop(crypto).
init_stats() ->
ets:new(elli_stat_table, [set, named_table, public]).
ets:new(elli_stat_table, [set, named_table, public]).
clear_stats(_) ->
ets:delete(elli_stat_table).
ets:delete(elli_stat_table).

+ 7
- 7
src/test/elli_test.hrl 查看文件

@ -1,21 +1,21 @@
-define(I2B(I), list_to_binary(integer_to_list(I))).
-define(assertHeadersEqual(H1, H2), ?assertEqual(lists:sort([{string:casefold(K), V} || {K, V} <- H1]),
lists:sort([{string:casefold(K), V} || {K, V} <- H2]))).
lists:sort([{string:casefold(K), V} || {K, V} <- H2]))).
status({ok, Status, _Headers, _ClientRef}) ->
Status;
Status;
status({ok, Status, _Headers}) ->
Status.
Status.
body({ok, _Status, _Headers, ClientRef}) ->
{ok, Body} = hackney:body(ClientRef),
Body.
{ok, Body} = hackney:body(ClientRef),
Body.
headers({ok, _Status, Headers, _ClientRef}) ->
lists:sort(Headers);
lists:sort(Headers);
headers({ok, _Status, Headers}) ->
lists:sort(Headers).
lists:sort(Headers).

+ 576
- 576
src/test/elli_tests.erl
文件差异内容过多而无法显示
查看文件


+ 147
- 147
src/wsSrv/elli.erl 查看文件

@ -12,17 +12,17 @@
%% API
-export([start_link/0,
start_link/1,
stop/1,
get_acceptors/1,
get_open_reqs/1,
get_open_reqs/2,
set_callback/3
]).
start_link/1,
stop/1,
get_acceptors/1,
get_open_reqs/1,
get_open_reqs/2,
set_callback/3
]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
terminate/2, code_change/3]).
-export_type([req/0, http_method/0, body/0, headers/0, response_code/0]).
@ -32,22 +32,22 @@
%% @type http_method(). An uppercase atom representing a known HTTP verb or a
%% binary for other verbs.
-type http_method() :: 'OPTIONS' | 'GET' | 'HEAD' | 'POST'
| 'PUT' | 'DELETE' | 'TRACE' | binary().
| 'PUT' | 'DELETE' | 'TRACE' | binary().
%% @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.
-record(state, {socket :: elli_tcp:socket(),
acceptors :: ets:tid(),
open_reqs = 0 :: non_neg_integer(),
options = [] :: [{_, _}], % TODO: refine
callback :: elli_handler:callback()
}).
-record(state, {socket :: elli_tcp:socket(),
acceptors :: ets:tid(),
open_reqs = 0 :: non_neg_integer(),
options = [] :: [{_, _}], % TODO: refine
callback :: elli_handler:callback()
}).
%% @type state(). Internal state.
-opaque state() :: #state{}.
-export_type([state/0]).
@ -58,57 +58,57 @@
%%%===================================================================
-spec start_link() -> Result when
Result :: {ok, Pid} | ignore | {error, Error},
Pid :: pid(),
Error :: {already_started, Pid} | term().
Result :: {ok, Pid} | ignore | {error, Error},
Pid :: pid(),
Error :: {already_started, Pid} | term().
%% @equiv start_link({@EXAMPLE_CONF})
%% @doc Create an Elli server process as part of a supervision tree, using the
%% default configuration.
start_link() -> start_link(?EXAMPLE_CONF).
-spec start_link(Opts) -> Result when
Opts :: [{_, _}], % TODO: refine
Result :: {ok, Pid} | ignore | {error, Error},
Pid :: pid(),
Error :: {already_started, Pid} | term().
Opts :: [{_, _}], % TODO: refine
Result :: {ok, Pid} | ignore | {error, Error},
Pid :: pid(),
Error :: {already_started, Pid} | term().
start_link(Opts) ->
valid_callback(required_opt(callback, Opts))
orelse throw(invalid_callback),
valid_callback(required_opt(callback, Opts))
orelse throw(invalid_callback),
case proplists:get_value(name, Opts) of
undefined ->
gen_server:start_link(?MODULE, [Opts], []);
Name ->
gen_server:start_link(Name, ?MODULE, [Opts], [])
end.
case proplists:get_value(name, Opts) of
undefined ->
gen_server:start_link(?MODULE, [Opts], []);
Name ->
gen_server:start_link(Name, ?MODULE, [Opts], [])
end.
-spec get_acceptors(atom()) -> {reply, {ok, [ets:tid()]}, state()}.
get_acceptors(S) ->
gen_server:call(S, get_acceptors).
gen_server:call(S, get_acceptors).
-spec get_open_reqs(S :: atom()) -> {reply, {ok, non_neg_integer()}, state()}.
%% @equiv get_open_reqs(S, 5000)
get_open_reqs(S) ->
get_open_reqs(S, 5000).
get_open_reqs(S, 5000).
-spec get_open_reqs(S :: atom(), Timeout :: non_neg_integer()) -> Reply when
Reply :: {reply, {ok, non_neg_integer()}, state()}.
Reply :: {reply, {ok, non_neg_integer()}, state()}.
get_open_reqs(S, Timeout) ->
gen_server:call(S, get_open_reqs, Timeout).
gen_server:call(S, get_open_reqs, Timeout).
-spec set_callback(S, Callback, CallbackArgs) -> Reply when
S :: atom(),
Callback :: elli_handler:callback_mod(),
CallbackArgs :: elli_handler:callback_args(),
Reply :: {reply, ok, state()}.
S :: atom(),
Callback :: elli_handler:callback_mod(),
CallbackArgs :: elli_handler:callback_args(),
Reply :: {reply, ok, state()}.
set_callback(S, Callback, CallbackArgs) ->
valid_callback(Callback) orelse throw(invalid_callback),
gen_server:call(S, {set_callback, Callback, CallbackArgs}).
valid_callback(Callback) orelse throw(invalid_callback),
gen_server:call(S, {set_callback, Callback, CallbackArgs}).
%% @doc Stop `Server'.
-spec stop(Server :: atom()) -> {stop, normal, ok, state()}.
stop(S) ->
gen_server:call(S, stop).
gen_server:call(S, stop).
%%%===================================================================
@ -118,126 +118,126 @@ stop(S) ->
%% @hidden
-spec init([Opts :: [{_, _}]]) -> {ok, state()}.
init([Opts]) ->
%% Use the exit signal from the acceptor processes to know when
%% they exit
process_flag(trap_exit, true),
Callback = required_opt(callback, Opts),
CallbackArgs = proplists:get_value(callback_args, Opts),
IPAddress = proplists:get_value(ip, Opts, {0, 0, 0, 0}),
Port = proplists:get_value(port, Opts, 8080),
MinAcceptors = proplists:get_value(min_acceptors, Opts, 20),
UseSSL = proplists:get_value(ssl, Opts, false),
KeyFile = proplists:get_value(keyfile, Opts),
CertFile = proplists:get_value(certfile, Opts),
SockType = ?IF(UseSSL, ssl, plain),
SSLSockOpts = ?IF(UseSSL,
[{keyfile, KeyFile}, {certfile, CertFile}],
[]),
AcceptTimeout = proplists:get_value(accept_timeout, Opts, 10000),
RequestTimeout = proplists:get_value(request_timeout, Opts, 60000),
HeaderTimeout = proplists:get_value(header_timeout, Opts, 10000),
BodyTimeout = proplists:get_value(body_timeout, Opts, 30000),
MaxBodySize = proplists:get_value(max_body_size, Opts, 1024000),
Options = [{accept_timeout, AcceptTimeout},
{request_timeout, RequestTimeout},
{header_timeout, HeaderTimeout},
{body_timeout, BodyTimeout},
{max_body_size, MaxBodySize}],
%% Notify the handler that we are about to start accepting
%% requests, so it can create necessary supporting processes, ETS
%% tables, etc.
ok = Callback:handle_event(elli_startup, [], CallbackArgs),
{ok, Socket} = elli_tcp:listen(SockType, Port, [binary,
{ip, IPAddress},
{reuseaddr, true},
{backlog, 32768},
{packet, raw},
{active, false}
| 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)],
{ok, #state{socket = Socket,
acceptors = Acceptors,
open_reqs = 0,
options = Options,
callback = {Callback, CallbackArgs}}}.
%% Use the exit signal from the acceptor processes to know when
%% they exit
process_flag(trap_exit, true),
Callback = required_opt(callback, Opts),
CallbackArgs = proplists:get_value(callback_args, Opts),
IPAddress = proplists:get_value(ip, Opts, {0, 0, 0, 0}),
Port = proplists:get_value(port, Opts, 8080),
MinAcceptors = proplists:get_value(min_acceptors, Opts, 20),
UseSSL = proplists:get_value(ssl, Opts, false),
KeyFile = proplists:get_value(keyfile, Opts),
CertFile = proplists:get_value(certfile, Opts),
SockType = ?IF(UseSSL, ssl, plain),
SSLSockOpts = ?IF(UseSSL,
[{keyfile, KeyFile}, {certfile, CertFile}],
[]),
AcceptTimeout = proplists:get_value(accept_timeout, Opts, 10000),
RequestTimeout = proplists:get_value(request_timeout, Opts, 60000),
HeaderTimeout = proplists:get_value(header_timeout, Opts, 10000),
BodyTimeout = proplists:get_value(body_timeout, Opts, 30000),
MaxBodySize = proplists:get_value(max_body_size, Opts, 1024000),
Options = [{accept_timeout, AcceptTimeout},
{request_timeout, RequestTimeout},
{header_timeout, HeaderTimeout},
{body_timeout, BodyTimeout},
{max_body_size, MaxBodySize}],
%% Notify the handler that we are about to start accepting
%% requests, so it can create necessary supporting processes, ETS
%% tables, etc.
ok = Callback:handle_event(elli_startup, [], CallbackArgs),
{ok, Socket} = elli_tcp:listen(SockType, Port, [binary,
{ip, IPAddress},
{reuseaddr, true},
{backlog, 32768},
{packet, raw},
{active, false}
| 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)],
{ok, #state{socket = Socket,
acceptors = Acceptors,
open_reqs = 0,
options = Options,
callback = {Callback, CallbackArgs}}}.
%% @hidden
-spec handle_call(get_acceptors, {pid(), _Tag}, state()) ->
{reply, {ok, [ets:tid()]}, state()};
(get_open_reqs, {pid(), _Tag}, state()) ->
{reply, {ok, OpenReqs :: non_neg_integer()}, state()};
(stop, {pid(), _Tag}, state()) -> {stop, normal, ok, state()};
({set_callback, Mod, Args}, {pid(), _Tag}, state()) ->
{reply, ok, state()} when
Mod :: elli_handler:callback_mod(),
Args :: elli_handler:callback_args().
{reply, {ok, [ets:tid()]}, state()};
(get_open_reqs, {pid(), _Tag}, state()) ->
{reply, {ok, OpenReqs :: non_neg_integer()}, state()};
(stop, {pid(), _Tag}, state()) -> {stop, normal, ok, state()};
({set_callback, Mod, Args}, {pid(), _Tag}, state()) ->
{reply, ok, state()} when
Mod :: elli_handler:callback_mod(),
Args :: elli_handler:callback_args().
handle_call(get_acceptors, _From, State) ->
Acceptors = [Pid || {Pid} <- ets:tab2list(State#state.acceptors)],
{reply, {ok, Acceptors}, State};
Acceptors = [Pid || {Pid} <- ets:tab2list(State#state.acceptors)],
{reply, {ok, Acceptors}, State};
handle_call(get_open_reqs, _From, State) ->
{reply, {ok, State#state.open_reqs}, State};
{reply, {ok, State#state.open_reqs}, State};
handle_call({set_callback, Callback, CallbackArgs}, _From, State) ->
ok = Callback:handle_event(elli_reconfigure, [], CallbackArgs),
{reply, ok, State#state{callback = {Callback, CallbackArgs}}};
ok = Callback:handle_event(elli_reconfigure, [], CallbackArgs),
{reply, ok, State#state{callback = {Callback, CallbackArgs}}};
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
{stop, normal, ok, State}.
%% @hidden
-spec handle_cast(accepted | _Msg, State0) -> {noreply, State1} when
State0 :: state(),
State1 :: state().
State0 :: state(),
State1 :: state().
handle_cast(accepted, State) ->
{noreply, start_add_acceptor(State)};
{noreply, start_add_acceptor(State)};
handle_cast(_Msg, State) ->
{noreply, State}.
{noreply, State}.
%% @hidden
-spec handle_info({'EXIT', _Pid, Reason}, State0) -> Result when
State0 :: state(),
Reason :: {error, emfile},
Result :: {stop, emfile, State0}
| {noreply, State1 :: state()}.
State0 :: state(),
Reason :: {error, emfile},
Result :: {stop, emfile, State0}
| {noreply, State1 :: state()}.
handle_info({'EXIT', _Pid, {error, emfile}}, State) ->
?LOG_ERROR("No more file descriptors, shutting down~n"),
{stop, emfile, State};
?LOG_ERROR("No more file descriptors, shutting down~n"),
{stop, emfile, State};
handle_info({'EXIT', Pid, normal}, State) ->
{noreply, remove_acceptor(State, Pid)};
{noreply, remove_acceptor(State, Pid)};
handle_info({'EXIT', Pid, Reason}, State) ->
?LOG_ERROR("Elli request (pid ~p) unexpectedly crashed:~n~p~n", [Pid, Reason]),
{noreply, remove_acceptor(State, Pid)}.
?LOG_ERROR("Elli request (pid ~p) unexpectedly crashed:~n~p~n", [Pid, Reason]),
{noreply, remove_acceptor(State, Pid)}.
%% @hidden
-spec terminate(_Reason, _State) -> ok.
terminate(_Reason, _State) ->
ok.
ok.
%% @hidden
-spec code_change(_OldVsn, State, _Extra) -> {ok, State} when State :: state().
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
{ok, State}.
%%%===================================================================
%%% Internal functions
@ -245,41 +245,41 @@ code_change(_OldVsn, State, _Extra) ->
-spec remove_acceptor(State0 :: state(), Pid :: pid()) -> State1 :: state().
remove_acceptor(State, Pid) ->
ets:delete(State#state.acceptors, Pid),
dec_open_reqs(State).
ets:delete(State#state.acceptors, Pid),
dec_open_reqs(State).
-spec start_add_acceptor(State0 :: state()) -> State1 :: state().
start_add_acceptor(State) ->
Pid = elli_http:start_link(self(), State#state.socket,
State#state.options, State#state.callback),
add_acceptor(State, Pid).
Pid = elli_http:start_link(self(), State#state.socket,
State#state.options, State#state.callback),
add_acceptor(State, Pid).
-spec add_acceptor(State0 :: state(), Pid :: pid()) -> State1 :: state().
add_acceptor(#state{acceptors = As} = State, Pid) ->
ets:insert(As, {Pid}),
inc_open_reqs(State).
ets:insert(As, {Pid}),
inc_open_reqs(State).
-spec required_opt(Name, Opts) -> Value when
Name :: any(),
Opts :: proplists:proplist(),
Value :: term().
Name :: any(),
Opts :: proplists:proplist(),
Value :: term().
required_opt(Name, Opts) ->
case proplists:get_value(Name, Opts) of
undefined ->
throw(badarg);
Value ->
Value
end.
case proplists:get_value(Name, Opts) of
undefined ->
throw(badarg);
Value ->
Value
end.
-spec valid_callback(Mod :: module()) -> Exported :: boolean().
valid_callback(Mod) ->
lists:member({handle, 2}, Mod:module_info(exports)) andalso
lists:member({handle_event, 3}, Mod:module_info(exports)).
lists:member({handle, 2}, Mod:module_info(exports)) andalso
lists:member({handle_event, 3}, Mod:module_info(exports)).
-spec dec_open_reqs(State0 :: state()) -> State1 :: state().
dec_open_reqs(#state{open_reqs = OpenReqs} = State) ->
State#state{open_reqs = OpenReqs - 1}.
State#state{open_reqs = OpenReqs - 1}.
-spec inc_open_reqs(State0 :: state()) -> State1 :: state().
inc_open_reqs(#state{open_reqs = OpenReqs} = State) ->
State#state{open_reqs = OpenReqs + 1}.
State#state{open_reqs = OpenReqs + 1}.

+ 106
- 108
src/wsSrv/elli_example_callback.erl 查看文件

@ -24,13 +24,12 @@
%% Delegate to our handler function.
%% @see handle/3
-spec handle(Req, _Args) -> Result when
Req :: elli:req(),
_Args :: elli_handler:callback_args(),
Result :: elli_handler:result().
Req :: elli:req(),
_Args :: elli_handler:callback_args(),
Result :: elli_handler:result().
handle(Req, _Args) -> handle(Req#req.method, elli_request:path(Req), Req).
%% @doc Route `Method' and `Path' to the appropriate clause.
%%
%% `ok' can be used instead of `200' to signal success.
@ -64,172 +63,171 @@ handle(Req, _Args) -> handle(Req#req.method, elli_request:path(Req), Req).
%% @see elli_request:chunk_ref/1
%% @see chunk_loop/1
-spec handle(Method, Path, Req) -> elli_handler:result() when
Method :: elli:http_method(),
Path :: [binary()],
Req :: elli:req().
Method :: elli:http_method(),
Path :: [binary()],
Req :: elli:req().
handle('GET', [<<"hello">>, <<"world">>], _Req) ->
%% Reply with a normal response.
timer:sleep(1000),
{ok, [], <<"Hello World!">>};
%% Reply with a normal response.
timer:sleep(1000),
{ok, [], <<"Hello World!">>};
handle('GET', [<<"hello">>], Req) ->
%% Fetch a GET argument from the URL.
Name = elli_request:get_arg(<<"name">>, Req, <<"undefined">>),
{ok, [], <<"Hello ", Name/binary>>};
%% Fetch a GET argument from the URL.
Name = elli_request:get_arg(<<"name">>, Req, <<"undefined">>),
{ok, [], <<"Hello ", Name/binary>>};
handle('POST', [<<"hello">>], Req) ->
%% Fetch a POST argument from the POST body.
Name = elli_request:post_arg(<<"name">>, Req, <<"undefined">>),
%% Fetch and decode
City = elli_request:post_arg_decoded(<<"city">>, Req, <<"undefined">>),
{ok, [], <<"Hello ", Name/binary, " of ", City/binary>>};
%% Fetch a POST argument from the POST body.
Name = elli_request:post_arg(<<"name">>, Req, <<"undefined">>),
%% Fetch and decode
City = elli_request:post_arg_decoded(<<"city">>, Req, <<"undefined">>),
{ok, [], <<"Hello ", Name/binary, " of ", City/binary>>};
handle('GET', [<<"hello">>, <<"iolist">>], Req) ->
%% Iolists will be kept as iolists all the way to the socket.
Name = elli_request:get_arg(<<"name">>, Req),
{ok, [], [<<"Hello ">>, Name]};
%% Iolists will be kept as iolists all the way to the socket.
Name = elli_request:get_arg(<<"name">>, Req),
{ok, [], [<<"Hello ">>, Name]};
handle('GET', [<<"type">>], Req) ->
Name = elli_request:get_arg(<<"name">>, Req),
%% Fetch a header.
case elli_request:get_header(<<"Accept">>, Req, <<"text/plain">>) of
<<"text/plain">> ->
{ok, [{<<"content-type">>, <<"text/plain; charset=ISO-8859-1">>}],
<<"name: ", Name/binary>>};
<<"application/json">> ->
{ok, [{<<"content-type">>,
<<"application/json; charset=ISO-8859-1">>}],
<<"{\"name\" : \"", Name/binary, "\"}">>}
end;
Name = elli_request:get_arg(<<"name">>, Req),
%% Fetch a header.
case elli_request:get_header(<<"Accept">>, Req, <<"text/plain">>) of
<<"text/plain">> ->
{ok, [{<<"content-type">>, <<"text/plain; charset=ISO-8859-1">>}],
<<"name: ", Name/binary>>};
<<"application/json">> ->
{ok, [{<<"content-type">>,
<<"application/json; charset=ISO-8859-1">>}],
<<"{\"name\" : \"", Name/binary, "\"}">>}
end;
handle('GET', [<<"headers.html">>], _Req) ->
%% Set custom headers, for example 'Content-Type'
{ok, [{<<"X-Custom">>, <<"foobar">>}], <<"see headers">>};
%% Set custom headers, for example 'Content-Type'
{ok, [{<<"X-Custom">>, <<"foobar">>}], <<"see headers">>};
%% See note in function doc re: overriding Elli's default behaviour
%% via Connection and Content-Length headers.
handle('GET', [<<"user">>, <<"defined">>, <<"behaviour">>], _Req) ->
{304, [{<<"Connection">>, <<"close">>},
{<<"Content-Length">>, <<"123">>}], <<"ignored">>};
{304, [{<<"Connection">>, <<"close">>},
{<<"Content-Length">>, <<"123">>}], <<"ignored">>};
handle('GET', [<<"user">>, <<"content-length">>], _Req) ->
{200, [{<<"Content-Length">>, 123}], <<"foobar">>};
{200, [{<<"Content-Length">>, 123}], <<"foobar">>};
handle('GET', [<<"crash">>], _Req) ->
%% Throwing an exception results in a 500 response and
%% request_throw being called
throw(foobar);
%% Throwing an exception results in a 500 response and
%% request_throw being called
throw(foobar);
handle('GET', [<<"decoded-hello">>], Req) ->
%% Fetch a URI decoded GET argument from the URL.
Name = elli_request:get_arg_decoded(<<"name">>, Req, <<"undefined">>),
{ok, [], <<"Hello ", Name/binary>>};
%% Fetch a URI decoded GET argument from the URL.
Name = elli_request:get_arg_decoded(<<"name">>, Req, <<"undefined">>),
{ok, [], <<"Hello ", Name/binary>>};
handle('GET', [<<"decoded-list">>], Req) ->
%% Fetch a URI decoded GET argument from the URL.
[{<<"name">>, Name}, {<<"foo">>, true}] =
elli_request:get_args_decoded(Req),
{ok, [], <<"Hello ", Name/binary>>};
%% Fetch a URI decoded GET argument from the URL.
[{<<"name">>, Name}, {<<"foo">>, true}] =
elli_request:get_args_decoded(Req),
{ok, [], <<"Hello ", Name/binary>>};
handle('GET', [<<"sendfile">>], _Req) ->
%% Returning {file, "/path/to/file"} instead of the body results
%% in Elli using sendfile.
F = "README.md",
{ok, [], {file, F}};
%% Returning {file, "/path/to/file"} instead of the body results
%% in Elli using sendfile.
F = "README.md",
{ok, [], {file, F}};
handle('GET', [<<"send_no_file">>], _Req) ->
%% Returning {file, "/path/to/file"} instead of the body results
%% in Elli using sendfile.
F = "README",
{ok, [], {file, F}};
%% Returning {file, "/path/to/file"} instead of the body results
%% in Elli using sendfile.
F = "README",
{ok, [], {file, F}};
handle('GET', [<<"sendfile">>, <<"error">>], _Req) ->
F = "test",
{ok, [], {file, F}};
F = "test",
{ok, [], {file, F}};
handle('GET', [<<"sendfile">>, <<"range">>], Req) ->
%% Read the Range header of the request and use the normalized
%% range with sendfile, otherwise send the entire file when
%% no range is present, or respond with a 416 if the range is invalid.
F = "README.md",
{ok, [], {file, F, elli_request:get_range(Req)}};
%% Read the Range header of the request and use the normalized
%% range with sendfile, otherwise send the entire file when
%% no range is present, or respond with a 416 if the range is invalid.
F = "README.md",
{ok, [], {file, F, elli_request:get_range(Req)}};
handle('GET', [<<"compressed">>], _Req) ->
%% Body with a byte size over 1024 are automatically gzipped by
%% elli_middleware_compress
{ok, binary:copy(<<"Hello World!">>, 86)};
%% Body with a byte size over 1024 are automatically gzipped by
%% elli_middleware_compress
{ok, binary:copy(<<"Hello World!">>, 86)};
handle('GET', [<<"compressed-io_list">>], _Req) ->
%% Body with a iolist size over 1024 are automatically gzipped by
%% elli_middleware_compress
{ok, lists:duplicate(86, [<<"Hello World!">>])};
%% Body with a iolist size over 1024 are automatically gzipped by
%% elli_middleware_compress
{ok, lists:duplicate(86, [<<"Hello World!">>])};
handle('HEAD', [<<"head">>], _Req) ->
{200, [], <<"body must be ignored">>};
{200, [], <<"body must be ignored">>};
handle('GET', [<<"chunked">>], Req) ->
%% Start a chunked response for streaming real-time events to the
%% browser.
%%
%% Calling elli_request:send_chunk(ChunkRef, Body) will send that
%% part to the client. elli_request:close_chunk(ChunkRef) will
%% close the response.
%%
%% Return immediately {chunk, Headers} to signal we want to chunk.
Ref = elli_request:chunk_ref(Req),
spawn(fun() -> ?MODULE:chunk_loop(Ref) end),
{chunk, [{<<"Content-Type">>, <<"text/event-stream">>}]};
%% Start a chunked response for streaming real-time events to the
%% browser.
%%
%% Calling elli_request:send_chunk(ChunkRef, Body) will send that
%% part to the client. elli_request:close_chunk(ChunkRef) will
%% close the response.
%%
%% Return immediately {chunk, Headers} to signal we want to chunk.
Ref = elli_request:chunk_ref(Req),
spawn(fun() -> ?MODULE:chunk_loop(Ref) end),
{chunk, [{<<"Content-Type">>, <<"text/event-stream">>}]};
handle('GET', [<<"shorthand">>], _Req) ->
{200, <<"hello">>};
{200, <<"hello">>};
handle('GET', [<<"ip">>], Req) ->
{<<"200 OK">>, elli_request:peer(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.
{304, [{<<"Etag">>, <<"foobar">>}], <<"Ignored">>};
%% A "Not Modified" response is exactly like a normal response (so
%% Content-Length is included), but the body will not be sent.
{304, [{<<"Etag">>, <<"foobar">>}], <<"Ignored">>};
handle('GET', [<<"302">>], _Req) ->
{302, [{<<"Location">>, <<"/hello/world">>}], <<>>};
{302, [{<<"Location">>, <<"/hello/world">>}], <<>>};
handle('GET', [<<"403">>], _Req) ->
%% Exceptions formatted as return codes can be used to
%% short-circuit a response, for example in case of
%% authentication/authorization
throw({403, [], <<"Forbidden">>});
%% Exceptions formatted as return codes can be used to
%% short-circuit a response, for example in case of
%% authentication/authorization
throw({403, [], <<"Forbidden">>});
handle('GET', [<<"invalid_return">>], _Req) ->
{invalid_return};
{invalid_return};
handle(_, _, _Req) ->
{404, [], <<"Not Found">>}.
{404, [], <<"Not Found">>}.
%% @doc Send 10 separate chunks to the client.
%% @equiv chunk_loop(Ref, 10)
chunk_loop(Ref) ->
chunk_loop(Ref, 10).
chunk_loop(Ref, 10).
%% @doc If `N > 0', send a chunk to the client, checking for errors,
%% as the user might have disconnected.
%% When `N == 0', call {@link elli_request:close_chunk/1.
%% elli_request:close_chunk(Ref)}.
chunk_loop(Ref, 0) ->
elli_request:close_chunk(Ref);
elli_request:close_chunk(Ref);
chunk_loop(Ref, N) ->
timer:sleep(10),
timer:sleep(10),
case elli_request:send_chunk(Ref, [<<"chunk">>, integer_to_binary(N)]) of
ok -> ok;
{error, Reason} -> ?LOG_ERROR("error in sending chunk: ~p~n", [Reason])
end,
case elli_request:send_chunk(Ref, [<<"chunk">>, integer_to_binary(N)]) of
ok -> ok;
{error, Reason} -> ?LOG_ERROR("error in sending chunk: ~p~n", [Reason])
end,
chunk_loop(Ref, N-1).
chunk_loop(Ref, N - 1).
%%
@ -297,13 +295,13 @@ 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(),
Args :: elli_handler:callback_args(),
Config :: [tuple()].
Event :: elli:event(),
Args :: elli_handler:callback_args(),
Config :: [tuple()].
handle_event(elli_startup, [], _) -> ok;
handle_event(request_complete, [_Request,
_ResponseCode, _ResponseHeaders, _ResponseBody,
{_Timings, _Sizes}], _) -> ok;
_ResponseCode, _ResponseHeaders, _ResponseBody,
{_Timings, _Sizes}], _) -> ok;
handle_event(request_throw, [_Request, _Exception, _Stacktrace], _) -> ok;
handle_event(request_error, [_Request, _Exception, _Stacktrace], _) -> ok;
handle_event(request_exit, [_Request, _Exception, _Stacktrace], _) -> ok;
@ -311,8 +309,8 @@ handle_event(request_exit, [_Request, _Exception, _Stacktrace], _) -> ok;
handle_event(invalid_return, [_Request, _ReturnValue], _) -> ok;
handle_event(chunk_complete, [_Request,
_ResponseCode, _ResponseHeaders, _ClosingEnd,
{_Timings, _Sizes}], _) -> ok;
_ResponseCode, _ResponseHeaders, _ClosingEnd,
{_Timings, _Sizes}], _) -> ok;
handle_event(request_closed, [], _) -> ok;

+ 19
- 19
src/wsSrv/elli_example_callback_handover.erl 查看文件

@ -7,36 +7,36 @@
%% @doc Return `{ok, handover}' if `Req''s path is `/hello/world',
%% otherwise `ignore'.
init(Req, _Args) ->
case elli_request:path(Req) of
[<<"hello">>, <<"world">>] ->
{ok, handover};
_ ->
ignore
end.
case elli_request:path(Req) of
[<<"hello">>, <<"world">>] ->
{ok, handover};
_ ->
ignore
end.
%% TODO: write docstring
-spec handle(Req, Args) -> Result when
Req :: elli:req(),
Args :: elli_handler:callback_args(),
Result :: elli_handler:result().
Req :: elli:req(),
Args :: elli_handler:callback_args(),
Result :: elli_handler:result().
handle(Req, Args) ->
handle(elli_request:method(Req), elli_request:path(Req), Req, Args).
handle(elli_request:method(Req), elli_request:path(Req), Req, Args).
handle('GET', [<<"hello">>, <<"world">>], Req, _Args) ->
Body = <<"Hello World!">>,
Size = integer_to_binary(size(Body)),
Headers = [{"Connection", "close"}, {"Content-Length", Size}],
elli_http:send_response(Req, 200, Headers, Body),
{close, <<>>};
Body = <<"Hello World!">>,
Size = integer_to_binary(size(Body)),
Headers = [{"Connection", "close"}, {"Content-Length", Size}],
elli_http:send_response(Req, 200, Headers, Body),
{close, <<>>};
handle('GET', [<<"hello">>], Req, _Args) ->
%% Fetch a GET argument from the URL.
Name = elli_request:get_arg(<<"name">>, Req, <<"undefined">>),
{ok, [], <<"Hello ", Name/binary>>}.
%% Fetch a GET argument from the URL.
Name = elli_request:get_arg(<<"name">>, Req, <<"undefined">>),
{ok, [], <<"Hello ", Name/binary>>}.
%% @hidden
handle_event(_, _, _) ->
ok.
ok.

+ 4
- 4
src/wsSrv/elli_example_middleware.erl 查看文件

@ -9,12 +9,12 @@
%%
handle(Req, _Args) ->
do_handle(elli_request:path(Req)).
do_handle(elli_request:path(Req)).
do_handle([<<"middleware">>, <<"short-circuit">>]) ->
{200, [], <<"short circuit!">>};
{200, [], <<"short circuit!">>};
do_handle(_) ->
ignore.
ignore.
%%
@ -23,4 +23,4 @@ do_handle(_) ->
handle_event(_Event, _Data, _Args) ->
ok.
ok.

+ 27
- 27
src/wsSrv/elli_handler.erl 查看文件

@ -9,7 +9,7 @@
-type callback() :: {callback_mod(), callback_args()}.
%% @type callback_mod(). A callback module.
-type callback_mod() :: module().
-type callback_mod() :: module().
%% @type callback_args(). Arguments to pass to a {@type callback_mod()}.
-type callback_args() :: list().
@ -17,41 +17,41 @@
%% @type event(). Fired throughout processing a request.
%% See {@link elli_example_callback:handle_event/3} for descriptions.
-type event() :: elli_startup
| bad_request | file_error
| chunk_complete | request_complete
| request_throw | request_error | request_exit
| request_closed | request_parse_error
| client_closed | client_timeout
| invalid_return.
| bad_request | file_error
| chunk_complete | request_complete
| request_throw | request_error | request_exit
| request_closed | request_parse_error
| client_closed | client_timeout
| invalid_return.
-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.
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().
-callback handle_event(Event, Args, Config) -> ok when
Event :: event(),
Args :: callback_args(),
Config :: [tuple()].
Event :: event(),
Args :: callback_args(),
Config :: [tuple()].
-callback init(Req, Args) -> {ok, standard | handover} when
Req :: elli:req(),
Args :: callback_args().
Req :: elli:req(),
Args :: callback_args().
-callback preprocess(Req1, Args) -> Req2 when
Req1 :: elli:req(),
Args :: callback_args(),
Req2 :: elli:req().
Req1 :: elli:req(),
Args :: callback_args(),
Req2 :: elli:req().
-callback postprocess(Req, Res1, Args) -> Res2 when
Req :: elli:req(),
Res1 :: result(),
Args :: callback_args(),
Res2 :: result().
Req :: elli:req(),
Res1 :: result(),
Args :: callback_args(),
Res2 :: result().

+ 609
- 609
src/wsSrv/elli_http.erl
文件差异内容过多而无法显示
查看文件


+ 55
- 57
src/wsSrv/elli_middleware.erl 查看文件

@ -42,7 +42,7 @@
%% Macros.
-define(IF_NOT_EXPORTED(M, F, A, T, E),
case erlang:function_exported(M, F, A) of true -> E; false -> T end).
case erlang:function_exported(M, F, A) of true -> E; false -> T end).
%%
%% ELLI CALLBACKS
@ -50,34 +50,32 @@
%% @hidden
-spec init(Req, Args) -> {ok, standard | handover} when
Req :: elli:req(),
Args :: elli_handler:callback_args().
Req :: elli:req(),
Args :: elli_handler:callback_args().
init(Req, Args) ->
do_init(Req, callbacks(Args)).
do_init(Req, callbacks(Args)).
%% @hidden
-spec handle(Req :: elli:req(), Config :: [tuple()]) -> elli_handler:result().
handle(CleanReq, Config) ->
Callbacks = callbacks(Config),
PreReq = preprocess(CleanReq, Callbacks),
Res = process(PreReq, Callbacks),
postprocess(PreReq, Res, lists:reverse(Callbacks)).
Callbacks = callbacks(Config),
PreReq = preprocess(CleanReq, Callbacks),
Res = process(PreReq, Callbacks),
postprocess(PreReq, Res, lists:reverse(Callbacks)).
%% @hidden
-spec handle_event(Event, Args, Config) -> ok when
Event :: elli_handler:event(),
Args :: elli_handler:callback_args(),
Config :: [tuple()].
Event :: elli_handler:event(),
Args :: elli_handler:callback_args(),
Config :: [tuple()].
handle_event(elli_startup, Args, Config) ->
Callbacks = callbacks(Config),
[code:ensure_loaded(M) || {M, _} <- Callbacks],
forward_event(elli_startup, Args, Callbacks);
Callbacks = callbacks(Config),
[code:ensure_loaded(M) || {M, _} <- Callbacks],
forward_event(elli_startup, Args, Callbacks);
handle_event(Event, Args, Config) ->
forward_event(Event, Args, lists:reverse(callbacks(Config))).
forward_event(Event, Args, lists:reverse(callbacks(Config))).
%%
@ -85,62 +83,62 @@ handle_event(Event, Args, Config) ->
%%
-spec do_init(Req, Callbacks) -> {ok, standard | handover} when
Req :: elli:req(),
Callbacks :: elli_handler:callbacks().
Req :: elli:req(),
Callbacks :: elli_handler:callbacks().
do_init(_, []) ->
{ok, standard};
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);
Result -> Result
end).
{ok, standard};
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);
Result -> Result
end).
-spec process(Req, Callbacks) -> Result when
Req :: elli:req(),
Callbacks :: [Callback :: elli_handler:callback()],
Result :: elli_handler:result().
Req :: elli:req(),
Callbacks :: [Callback :: elli_handler:callback()],
Result :: elli_handler:result().
process(_Req, []) ->
{404, [], <<"Not Found">>};
{404, [], <<"Not Found">>};
process(Req, [{Mod, Args} | Mods]) ->
?IF_NOT_EXPORTED(Mod, handle, 2, process(Req, Mods),
case Mod:handle(Req, Args) of
ignore -> process(Req, Mods);
Response -> Response
end).
?IF_NOT_EXPORTED(Mod, handle, 2, process(Req, Mods),
case Mod:handle(Req, Args) of
ignore -> process(Req, Mods);
Response -> Response
end).
-spec preprocess(Req1, Callbacks) -> Req2 when
Req1 :: elli:req(),
Callbacks :: [elli_handler:callback()],
Req2 :: elli:req().
Req1 :: elli:req(),
Callbacks :: [elli_handler:callback()],
Req2 :: elli:req().
preprocess(Req, []) ->
Req;
Req;
preprocess(Req, [{Mod, Args} | Mods]) ->
?IF_NOT_EXPORTED(Mod, preprocess, 2, preprocess(Req, Mods),
preprocess(Mod:preprocess(Req, Args), Mods)).
?IF_NOT_EXPORTED(Mod, preprocess, 2, preprocess(Req, Mods),
preprocess(Mod:preprocess(Req, Args), Mods)).
-spec postprocess(Req, Res1, Callbacks) -> Res2 when
Req :: elli:req(),
Res1 :: elli_handler:result(),
Callbacks :: [elli_handler:callback()],
Res2 :: elli_handler:result().
Req :: elli:req(),
Res1 :: elli_handler:result(),
Callbacks :: [elli_handler:callback()],
Res2 :: elli_handler:result().
postprocess(_Req, Res, []) ->
Res;
Res;
postprocess(Req, Res, [{Mod, Args} | Mods]) ->
?IF_NOT_EXPORTED(Mod, postprocess, 3, postprocess(Req, Res, Mods),
postprocess(Req, Mod:postprocess(Req, Res, Args), Mods)).
?IF_NOT_EXPORTED(Mod, postprocess, 3, postprocess(Req, Res, Mods),
postprocess(Req, Mod:postprocess(Req, Res, Args), Mods)).
-spec forward_event(Event, Args, Callbacks) -> ok when
Event :: elli_handler:event(),
Args :: elli_handler:callback_args(),
Callbacks :: [elli_handler:callback()].
Event :: elli_handler:event(),
Args :: elli_handler:callback_args(),
Callbacks :: [elli_handler:callback()].
forward_event(Event, Args, Callbacks) ->
[?IF_NOT_EXPORTED(M, handle_event, 3, ok,
M:handle_event(Event, Args, XArgs))
|| {M, XArgs} <- Callbacks],
ok.
[?IF_NOT_EXPORTED(M, handle_event, 3, ok,
M:handle_event(Event, Args, XArgs))
|| {M, XArgs} <- Callbacks],
ok.
%%
@ -148,6 +146,6 @@ forward_event(Event, Args, Callbacks) ->
%%
-spec callbacks(Config :: [{mod, Callbacks} | tuple()]) -> Callbacks when
Callbacks :: [elli_handler:callback()].
Callbacks :: [elli_handler:callback()].
callbacks(Config) ->
proplists:get_value(mods, Config, []).
proplists:get_value(mods, Config, []).

+ 28
- 28
src/wsSrv/elli_middleware_compress.erl 查看文件

@ -10,26 +10,26 @@
%%% @doc Postprocess all requests and compress bodies larger than
%%% `compress_byte_size' (`1024' by default).
-spec postprocess(Req, Result, Config) -> Result when
Req :: elli:req(),
Result :: elli_handler:result(),
Config :: [{compress_byte_size, non_neg_integer()} | tuple()].
Req :: elli:req(),
Result :: elli_handler:result(),
Config :: [{compress_byte_size, non_neg_integer()} | tuple()].
postprocess(Req, {ResponseCode, Body}, Config)
when is_integer(ResponseCode) orelse ResponseCode =:= ok ->
postprocess(Req, {ResponseCode, [], Body}, Config);
when is_integer(ResponseCode) orelse ResponseCode =:= ok ->
postprocess(Req, {ResponseCode, [], Body}, Config);
postprocess(Req, {ResponseCode, Headers, Body} = Res, Config)
when is_integer(ResponseCode) orelse ResponseCode =:= ok ->
Threshold = proplists:get_value(compress_byte_size, Config, 1024),
?IF(not should_compress(Body, Threshold), Res,
case compress(Body, Req) of
no_compress ->
Res;
{CompressedBody, Encoding} ->
NewHeaders = [{<<"Content-Encoding">>, Encoding} | Headers],
{ResponseCode, NewHeaders, CompressedBody}
end);
when is_integer(ResponseCode) orelse ResponseCode =:= ok ->
Threshold = proplists:get_value(compress_byte_size, Config, 1024),
?IF(not should_compress(Body, Threshold), Res,
case compress(Body, Req) of
no_compress ->
Res;
{CompressedBody, Encoding} ->
NewHeaders = [{<<"Content-Encoding">>, Encoding} | Headers],
{ResponseCode, NewHeaders, CompressedBody}
end);
postprocess(_, Res, _) ->
Res.
Res.
%%
%% INTERNALS
@ -37,21 +37,21 @@ postprocess(_, Res, _) ->
%% NOTE: Algorithm is either `<<"gzip">>' or `<<"deflate">>'.
-spec compress(Body0 :: elli:body(), Req :: elli:req()) -> Body1 when
Body1 :: {Compressed :: binary(), Algorithm :: binary()} | no_compress.
Body1 :: {Compressed :: binary(), Algorithm :: binary()} | no_compress.
compress(Body, Req) ->
case accepted_encoding(Req) of
<<"gzip">> = E -> {zlib:gzip(Body), E};
<<"deflate">> = E -> {zlib:compress(Body), E};
_ -> no_compress
end.
case accepted_encoding(Req) of
<<"gzip">> = E -> {zlib:gzip(Body), E};
<<"deflate">> = E -> {zlib:compress(Body), E};
_ -> no_compress
end.
accepted_encoding(Req) ->
hd(binary:split(elli_request:get_header(<<"Accept-Encoding">>, Req, <<>>),
[<<",">>, <<";">>], [global])).
hd(binary:split(elli_request:get_header(<<"Accept-Encoding">>, Req, <<>>),
[<<",">>, <<";">>], [global])).
-spec should_compress(Body, Threshold) -> boolean() when
Body :: binary(),
Threshold :: non_neg_integer().
Body :: binary(),
Threshold :: non_neg_integer().
should_compress(Body, S) ->
is_binary(Body) andalso byte_size(Body) >= S orelse
is_list(Body) andalso iolist_size(Body) >= S.
is_binary(Body) andalso byte_size(Body) >= S orelse
is_list(Body) andalso iolist_size(Body) >= S.

+ 167
- 168
src/wsSrv/elli_request.erl 查看文件

@ -3,46 +3,46 @@
-include("elli_util.hrl").
-export([send_chunk/2
, async_send_chunk/2
, chunk_ref/1
, close_chunk/1
, path/1
, raw_path/1
, query_str/1
, get_header/2
, get_header/3
, get_arg_decoded/2
, get_arg_decoded/3
, get_arg/2
, get_arg/3
, get_args/1
, get_args_decoded/1
, post_arg/2
, post_arg/3
, post_arg_decoded/2
, post_arg_decoded/3
, post_args/1
, post_args_decoded/1
, body_qs/1
, original_headers/1
, headers/1
, peer/1
, method/1
, body/1
, scheme/1
, host/1
, port/1
, get_range/1
, to_proplist/1
, is_request/1
, uri_decode/1
]).
, async_send_chunk/2
, chunk_ref/1
, close_chunk/1
, path/1
, raw_path/1
, query_str/1
, get_header/2
, get_header/3
, get_arg_decoded/2
, get_arg_decoded/3
, get_arg/2
, get_arg/3
, get_args/1
, get_args_decoded/1
, post_arg/2
, post_arg/3
, post_arg_decoded/2
, post_arg_decoded/3
, post_args/1
, post_args_decoded/1
, body_qs/1
, original_headers/1
, headers/1
, peer/1
, method/1
, body/1
, scheme/1
, host/1
, port/1
, get_range/1
, to_proplist/1
, is_request/1
, uri_decode/1
]).
-export_type([http_range/0]).
-type http_range() :: {First:: non_neg_integer(), Last:: non_neg_integer()}
| {offset, Offset:: non_neg_integer()}
| {suffix, Length:: pos_integer()}.
-type http_range() :: {First :: non_neg_integer(), Last :: non_neg_integer()}
| {offset, Offset :: non_neg_integer()}
| {suffix, Length :: pos_integer()}.
%%
@ -51,249 +51,248 @@
%% @doc Return `path' split into binary parts.
path(#req{path = Path}) -> Path.
path(#req{path = Path}) -> Path.
%% @doc Return the `raw_path', i.e. not split or parsed for query params.
raw_path(#req{raw_path = Path}) -> Path.
raw_path(#req{raw_path = Path}) -> Path.
%% @doc Return the `headers' that have had `string:casefold/1' run on each key.
headers(#req{headers = Headers}) -> Headers.
%% @doc Return the original `headers'.
original_headers(#req{original_headers = Headers}) -> Headers.
%% @doc Return the `method'.
method(#req{method = Method}) -> Method.
method(#req{method = Method}) -> Method.
%% @doc Return the `body'.
body(#req{body = Body}) -> Body.
body(#req{body = Body}) -> Body.
%% @doc Return the `scheme'.
scheme(#req{scheme = Scheme}) -> Scheme.
scheme(#req{scheme = Scheme}) -> Scheme.
%% @doc Return the `host'.
host(#req{host = Host}) -> Host.
host(#req{host = Host}) -> Host.
%% @doc Return the `port'.
port(#req{port = Port}) -> Port.
port(#req{port = Port}) -> Port.
peer(#req{socket = Socket} = _Req) ->
case elli_tcp:peername(Socket) of
{ok, {Address, _}} ->
list_to_binary(inet_parse:ntoa(Address));
{error, _} ->
undefined
end.
case elli_tcp:peername(Socket) of
{ok, {Address, _}} ->
list_to_binary(inet_parse:ntoa(Address));
{error, _} ->
undefined
end.
get_header(Key, #req{headers = Headers}) ->
CaseFoldedKey = string:casefold(Key),
proplists:get_value(CaseFoldedKey, Headers).
CaseFoldedKey = string:casefold(Key),
proplists:get_value(CaseFoldedKey, Headers).
get_header(Key, #req{headers = Headers}, Default) ->
CaseFoldedKey = string:casefold(Key),
proplists:get_value(CaseFoldedKey, Headers, Default).
CaseFoldedKey = string:casefold(Key),
proplists:get_value(CaseFoldedKey, Headers, Default).
%% @equiv get_arg(Key, Req, undefined)
get_arg(Key, #req{} = Req) ->
get_arg(Key, Req, undefined).
get_arg(Key, Req, undefined).
%% @equiv proplists:get_value(Key, Args, Default)
get_arg(Key, #req{args = Args}, Default) ->
proplists:get_value(Key, Args, Default).
proplists:get_value(Key, Args, Default).
%% @equiv get_arg_decoded(Key, Req, undefined)
get_arg_decoded(Key, #req{} = Req) ->
get_arg_decoded(Key, Req, undefined).
get_arg_decoded(Key, Req, undefined).
get_arg_decoded(Key, #req{args = Args}, Default) ->
case proplists:get_value(Key, Args) of
undefined -> Default;
true -> true;
EncodedValue ->
uri_decode(EncodedValue)
end.
case proplists:get_value(Key, Args) of
undefined -> Default;
true -> true;
EncodedValue ->
uri_decode(EncodedValue)
end.
%% @doc Parse `application/x-www-form-urlencoded' body into a proplist.
body_qs(#req{body = <<>>}) -> [];
body_qs(#req{body = Body} = Req) ->
case get_header(<<"Content-Type">>, Req) of
<<"application/x-www-form-urlencoded">> ->
elli_http:split_args(Body);
<<"application/x-www-form-urlencoded;", _/binary>> -> % ; charset=...
elli_http:split_args(Body);
_ ->
erlang:error(badarg)
end.
case get_header(<<"Content-Type">>, Req) of
<<"application/x-www-form-urlencoded">> ->
elli_http:split_args(Body);
<<"application/x-www-form-urlencoded;", _/binary>> -> % ; charset=...
elli_http:split_args(Body);
_ ->
erlang:error(badarg)
end.
%% @equiv post_arg(Key, Req, undefined)
post_arg(Key, #req{} = Req) ->
post_arg(Key, Req, undefined).
post_arg(Key, Req, undefined).
post_arg(Key, #req{} = Req, Default) ->
proplists:get_value(Key, body_qs(Req), Default).
proplists:get_value(Key, body_qs(Req), Default).
%% @equiv post_arg_decoded(Key, Req, undefined)
post_arg_decoded(Key, #req{} = Req) ->
post_arg_decoded(Key, Req, undefined).
post_arg_decoded(Key, Req, undefined).
post_arg_decoded(Key, #req{} = Req, Default) ->
case proplists:get_value(Key, body_qs(Req)) of
undefined -> Default;
true -> true;
EncodedValue ->
uri_decode(EncodedValue)
end.
case proplists:get_value(Key, body_qs(Req)) of
undefined -> Default;
true -> true;
EncodedValue ->
uri_decode(EncodedValue)
end.
%% @doc Return a proplist of keys and values of the original query string.
%% Both keys and values in the returned proplists will be binaries or the atom
%% `true' in case no value was supplied for the query value.
-spec get_args(elli:req()) -> QueryArgs :: proplists:proplist().
-spec get_args(elli:req()) -> QueryArgs :: proplists:proplist().
get_args(#req{args = Args}) -> Args.
get_args_decoded(#req{args = Args}) ->
lists:map(fun ({K, true}) ->
{K, true};
({K, V}) ->
{K, uri_decode(V)}
end, Args).
lists:map(fun({K, true}) ->
{K, true};
({K, V}) ->
{K, uri_decode(V)}
end, Args).
post_args(#req{} = Req) ->
body_qs(Req).
body_qs(Req).
post_args_decoded(#req{} = Req) ->
lists:map(fun ({K, true}) ->
{K, true};
({K, V}) ->
{K, uri_decode(V)}
end, body_qs(Req)).
lists:map(fun({K, true}) ->
{K, true};
({K, V}) ->
{K, uri_decode(V)}
end, body_qs(Req)).
%% @doc Calculate the query string associated with a given `Request'
%% as a binary.
-spec query_str(elli:req()) -> QueryStr :: binary().
query_str(#req{raw_path = Path}) ->
case binary:split(Path, [<<"?">>]) of
[_, Qs] -> Qs;
[_] -> <<>>
end.
case binary:split(Path, [<<"?">>]) of
[_, Qs] -> Qs;
[_] -> <<>>
end.
%% @doc Parse the `Range' header from the request.
%% The result is either a `byte_range_set()' or the atom `parse_error'.
%% Use {@link elli_util:normalize_range/2} to get a validated, normalized range.
-spec get_range(elli:req()) -> [http_range()] | parse_error.
get_range(#req{headers = Headers}) ->
case proplists:get_value(<<"range">>, Headers) of
<<"bytes=", RangeSetBin/binary>> ->
parse_range_set(RangeSetBin);
_ -> []
end.
get_range(#req{headers = Headers}) ->
case proplists:get_value(<<"range">>, Headers) of
<<"bytes=", RangeSetBin/binary>> ->
parse_range_set(RangeSetBin);
_ -> []
end.
-spec parse_range_set(Bin:: binary()) -> [http_range()] | parse_error.
-spec parse_range_set(Bin :: binary()) -> [http_range()] | parse_error.
parse_range_set(<<ByteRangeSet/binary>>) ->
RangeBins = binary:split(ByteRangeSet, <<",">>, [global]),
Parsed = [parse_range(remove_whitespace(RangeBin))
|| RangeBin <- RangeBins],
case lists:member(parse_error, Parsed) of
true -> parse_error;
false -> Parsed
end.
-spec parse_range(Bin:: binary()) -> http_range() | parse_error.
RangeBins = binary:split(ByteRangeSet, <<",">>, [global]),
Parsed = [parse_range(remove_whitespace(RangeBin))
|| RangeBin <- RangeBins],
case lists:member(parse_error, Parsed) of
true -> parse_error;
false -> Parsed
end.
-spec parse_range(Bin :: binary()) -> http_range() | parse_error.
parse_range(<<$-, SuffixBin/binary>>) ->
%% suffix-byte-range
try {suffix, binary_to_integer(SuffixBin)}
catch
error:badarg -> parse_error
end;
%% suffix-byte-range
try {suffix, binary_to_integer(SuffixBin)}
catch
error:badarg -> parse_error
end;
parse_range(<<ByteRange/binary>>) ->
case binary:split(ByteRange, <<"-">>) of
%% byte-range without last-byte-pos
[FirstBytePosBin, <<>>] ->
try {offset, binary_to_integer(FirstBytePosBin)}
catch
error:badarg -> parse_error
end;
%% full byte-range
[FirstBytePosBin, LastBytePosBin] ->
try {bytes,
binary_to_integer(FirstBytePosBin),
binary_to_integer(LastBytePosBin)}
catch
error:badarg -> parse_error
end;
_ -> parse_error
end.
case binary:split(ByteRange, <<"-">>) of
%% byte-range without last-byte-pos
[FirstBytePosBin, <<>>] ->
try {offset, binary_to_integer(FirstBytePosBin)}
catch
error:badarg -> parse_error
end;
%% full byte-range
[FirstBytePosBin, LastBytePosBin] ->
try {bytes,
binary_to_integer(FirstBytePosBin),
binary_to_integer(LastBytePosBin)}
catch
error:badarg -> parse_error
end;
_ -> parse_error
end.
-spec remove_whitespace(binary()) -> binary().
remove_whitespace(Bin) ->
binary:replace(Bin, <<" ">>, <<>>, [global]).
binary:replace(Bin, <<" ">>, <<>>, [global]).
%% @doc Serialize the `Req'uest record to a proplist.
%% Useful for logging.
to_proplist(#req{} = Req) ->
lists:zip(record_info(fields, req), tl(tuple_to_list(Req))).
lists:zip(record_info(fields, req), tl(tuple_to_list(Req))).
%% @doc Return a reference that can be used to send chunks to the client.
%% If the protocol does not support it, return `{error, not_supported}'.
chunk_ref(#req{version = {1, 1}} = Req) ->
Req#req.pid;
Req#req.pid;
chunk_ref(#req{}) ->
{error, not_supported}.
{error, not_supported}.
%% @doc Explicitly close the chunked connection.
%% Return `{error, closed}' if the client already closed the connection.
%% @equiv send_chunk(Ref, close)
close_chunk(Ref) ->
send_chunk(Ref, close).
send_chunk(Ref, close).
%% @doc Send a chunk asynchronously.
async_send_chunk(Ref, Data) ->
Ref ! {chunk, Data}.
Ref ! {chunk, Data}.
%% @doc Send a chunk synchronously.
%% If the referenced process is dead, return early with `{error, closed}',
%% instead of timing out.
send_chunk(Ref, Data) ->
?IF(is_ref_alive(Ref),
send_chunk(Ref, Data, 5000),
{error, closed}).
?IF(is_ref_alive(Ref),
send_chunk(Ref, Data, 5000),
{error, closed}).
send_chunk(Ref, Data, Timeout) ->
Ref ! {chunk, Data, self()},
receive
{Ref, ok} ->
ok;
{Ref, {error, Reason}} ->
{error, Reason}
after Timeout ->
{error, timeout}
end.
Ref ! {chunk, Data, self()},
receive
{Ref, ok} ->
ok;
{Ref, {error, Reason}} ->
{error, Reason}
after Timeout ->
{error, timeout}
end.
is_ref_alive(Ref) ->
?IF(node(Ref) =:= node(),
is_process_alive(Ref),
rpc:call(node(Ref), erlang, is_process_alive, [Ref])).
?IF(node(Ref) =:= node(),
is_process_alive(Ref),
rpc:call(node(Ref), erlang, is_process_alive, [Ref])).
is_request(#req{}) -> true;
is_request(_) -> false.
is_request(_) -> false.
uri_decode(Bin) ->
case binary:match(Bin, [<<"+">>, <<"%">>]) of
nomatch -> Bin;
{Pos, _} ->
<<Prefix:Pos/binary, Rest/binary>> = Bin,
uri_decode(Rest, Prefix)
end.
case binary:match(Bin, [<<"+">>, <<"%">>]) of
nomatch -> Bin;
{Pos, _} ->
<<Prefix:Pos/binary, Rest/binary>> = Bin,
uri_decode(Rest, Prefix)
end.
uri_decode(<<>>, Acc) -> Acc;
uri_decode(<<$+, Rest/binary>>, Acc) ->
uri_decode(Rest, <<Acc/binary, $\s>>);
uri_decode(Rest, <<Acc/binary, $\s>>);
uri_decode(<<$%, H, L, Rest/binary>>, Acc) ->
uri_decode(Rest, <<Acc/binary, (hex_to_int(H)):4, (hex_to_int(L)):4>>);
uri_decode(Rest, <<Acc/binary, (hex_to_int(H)):4, (hex_to_int(L)):4>>);
uri_decode(<<C, Rest/binary>>, Acc) ->
uri_decode(Rest, <<Acc/binary, C>>).
uri_decode(Rest, <<Acc/binary, C>>).
-compile({inline, [hex_to_int/1]}).
hex_to_int(X) when X >= $0, X =< $9 -> X - $0;
hex_to_int(X) when X >= $a, X =< $f -> X - ($a-10);
hex_to_int(X) when X >= $A, X =< $F -> X - ($A-10).
hex_to_int(X) when X >= $a, X =< $f -> X - ($a - 10);
hex_to_int(X) when X >= $A, X =< $F -> X - ($A - 10).

+ 50
- 50
src/wsSrv/elli_sendfile.erl 查看文件

@ -14,65 +14,65 @@
%%
%% @end
-spec sendfile(file:fd(), inet:socket() | ssl:sslsocket(),
non_neg_integer(), non_neg_integer(), sendfile_opts())
-> {ok, non_neg_integer()} | {error, atom()}.
non_neg_integer(), non_neg_integer(), sendfile_opts())
-> {ok, non_neg_integer()} | {error, atom()}.
sendfile(RawFile, Socket, Offset, Bytes, Opts) ->
ChunkSize = chunk_size(Opts),
Initial2 = case file:position(RawFile, {cur, 0}) of
{ok, Offset} ->
Offset;
{ok, Initial} ->
{ok, _} = file:position(RawFile, {bof, Offset}),
Initial
end,
case sendfile_loop(Socket, RawFile, Bytes, 0, ChunkSize) of
{ok, _Sent} = Result ->
{ok, _} = file:position(RawFile, {bof, Initial2}),
Result;
{error, _Reason} = Error ->
Error
end.
ChunkSize = chunk_size(Opts),
Initial2 = case file:position(RawFile, {cur, 0}) of
{ok, Offset} ->
Offset;
{ok, Initial} ->
{ok, _} = file:position(RawFile, {bof, Offset}),
Initial
end,
case sendfile_loop(Socket, RawFile, Bytes, 0, ChunkSize) of
{ok, _Sent} = Result ->
{ok, _} = file:position(RawFile, {bof, Initial2}),
Result;
{error, _Reason} = Error ->
Error
end.
-spec chunk_size(sendfile_opts()) -> pos_integer().
chunk_size(Opts) ->
case lists:keyfind(chunk_size, 1, Opts) of
{chunk_size, ChunkSize}
when is_integer(ChunkSize) andalso ChunkSize > 0 ->
ChunkSize;
{chunk_size, 0} ->
16#1FFF;
false ->
16#1FFF
end.
case lists:keyfind(chunk_size, 1, Opts) of
{chunk_size, ChunkSize}
when is_integer(ChunkSize) andalso ChunkSize > 0 ->
ChunkSize;
{chunk_size, 0} ->
16#1FFF;
false ->
16#1FFF
end.
-spec sendfile_loop(inet:socket() | ssl:sslsocket(), file:fd(), non_neg_integer(),
non_neg_integer(), pos_integer())
-> {ok, non_neg_integer()} | {error, term()}.
non_neg_integer(), pos_integer())
-> {ok, non_neg_integer()} | {error, term()}.
sendfile_loop(_Socket, _RawFile, Sent, Sent, _ChunkSize)
when Sent =/= 0 ->
%% All requested data has been read and sent, return number of bytes sent.
{ok, Sent};
when Sent =/= 0 ->
%% All requested data has been read and sent, return number of bytes sent.
{ok, Sent};
sendfile_loop(Socket, RawFile, Bytes, Sent, ChunkSize) ->
ReadSize = read_size(Bytes, Sent, ChunkSize),
case file:read(RawFile, ReadSize) of
{ok, IoData} ->
case ssl:send(Socket, IoData) of
ok ->
Sent2 = iolist_size(IoData) + Sent,
sendfile_loop(Socket, RawFile, Bytes, Sent2,
ChunkSize);
{error, _Reason} = Error ->
Error
end;
eof ->
{ok, Sent};
{error, _Reason} = Error ->
Error
end.
ReadSize = read_size(Bytes, Sent, ChunkSize),
case file:read(RawFile, ReadSize) of
{ok, IoData} ->
case ssl:send(Socket, IoData) of
ok ->
Sent2 = iolist_size(IoData) + Sent,
sendfile_loop(Socket, RawFile, Bytes, Sent2,
ChunkSize);
{error, _Reason} = Error ->
Error
end;
eof ->
{ok, Sent};
{error, _Reason} = Error ->
Error
end.
-spec read_size(non_neg_integer(), non_neg_integer(), non_neg_integer()) ->
non_neg_integer().
non_neg_integer().
read_size(0, _Sent, ChunkSize) ->
ChunkSize;
ChunkSize;
read_size(Bytes, Sent, ChunkSize) ->
min(Bytes - Sent, ChunkSize).
min(Bytes - Sent, ChunkSize).

+ 56
- 56
src/wsSrv/elli_tcp.erl 查看文件

@ -3,95 +3,95 @@
-module(elli_tcp).
-export([listen/3, accept/3, recv/3, send/2, close/1,
setopts/2, sendfile/5, peername/1]).
setopts/2, sendfile/5, peername/1]).
-export_type([socket/0]).
-type socket() :: {plain, inet:socket()} | {ssl, ssl:sslsocket()}.
listen(plain, Port, Opts) ->
case gen_tcp:listen(Port, Opts) of
{ok, Socket} ->
{ok, {plain, Socket}};
{error, Reason} ->
{error, Reason}
end;
case gen_tcp:listen(Port, Opts) of
{ok, Socket} ->
{ok, {plain, Socket}};
{error, Reason} ->
{error, Reason}
end;
listen(ssl, Port, Opts) ->
case ssl:listen(Port, Opts) of
{ok, Socket} ->
{ok, {ssl, Socket}};
{error, Reason} ->
{error, Reason}
end.
case ssl:listen(Port, Opts) of
{ok, Socket} ->
{ok, {ssl, Socket}};
{error, Reason} ->
{error, Reason}
end.
accept({plain, Socket}, Server, Timeout) ->
case gen_tcp:accept(Socket, Timeout) of
{ok, S} ->
gen_server:cast(Server, accepted),
{ok, {plain, S}};
{error, Reason} ->
{error, Reason}
end;
case gen_tcp:accept(Socket, Timeout) of
{ok, S} ->
gen_server:cast(Server, accepted),
{ok, {plain, S}};
{error, Reason} ->
{error, Reason}
end;
accept({ssl, Socket}, Server, Timeout) ->
case ssl:transport_accept(Socket, Timeout) of
{ok, S} ->
handshake(S, Server, Timeout);
{error, Reason} ->
{error, Reason}
end.
case ssl:transport_accept(Socket, Timeout) of
{ok, S} ->
handshake(S, Server, Timeout);
{error, Reason} ->
{error, Reason}
end.
-ifdef(post20).
handshake(S, Server, Timeout) ->
case ssl:handshake(S, Timeout) of
{ok, S1} ->
gen_server:cast(Server, accepted),
{ok, {ssl, S1}};
{error, closed} ->
{error, econnaborted};
{error, Reason} ->
{error, Reason}
end.
case ssl:handshake(S, Timeout) of
{ok, S1} ->
gen_server:cast(Server, accepted),
{ok, {ssl, S1}};
{error, closed} ->
{error, econnaborted};
{error, Reason} ->
{error, Reason}
end.
-else.
handshake(S, Server, Timeout) ->
case ssl:ssl_accept(S, Timeout) of
ok ->
gen_server:cast(Server, accepted),
{ok, {ssl, S}};
{error, closed} ->
{error, econnaborted};
{error, Reason} ->
{error, Reason}
end.
case ssl:ssl_accept(S, Timeout) of
ok ->
gen_server:cast(Server, accepted),
{ok, {ssl, S}};
{error, closed} ->
{error, econnaborted};
{error, Reason} ->
{error, Reason}
end.
-endif.
recv({plain, Socket}, Size, Timeout) ->
gen_tcp:recv(Socket, Size, Timeout);
gen_tcp:recv(Socket, Size, Timeout);
recv({ssl, Socket}, Size, Timeout) ->
ssl:recv(Socket, Size, Timeout).
ssl:recv(Socket, Size, Timeout).
send({plain, Socket}, Data) ->
gen_tcp:send(Socket, Data);
gen_tcp:send(Socket, Data);
send({ssl, Socket}, Data) ->
ssl:send(Socket, Data).
ssl:send(Socket, Data).
close({plain, Socket}) ->
gen_tcp:close(Socket);
gen_tcp:close(Socket);
close({ssl, Socket}) ->
ssl:close(Socket).
ssl:close(Socket).
setopts({plain, Socket}, Opts) ->
inet:setopts(Socket, Opts);
inet:setopts(Socket, Opts);
setopts({ssl, Socket}, Opts) ->
ssl:setopts(Socket, Opts).
ssl:setopts(Socket, Opts).
sendfile(Fd, {plain, Socket}, Offset, Length, Opts) ->
file:sendfile(Fd, Socket, Offset, Length, Opts);
file:sendfile(Fd, Socket, Offset, Length, Opts);
sendfile(Fd, {ssl, Socket}, Offset, Length, Opts) ->
elli_sendfile:sendfile(Fd, Socket, Offset, Length, Opts).
elli_sendfile:sendfile(Fd, Socket, Offset, Length, Opts).
peername({plain, Socket}) ->
inet:peername(Socket);
inet:peername(Socket);
peername({ssl, Socket}) ->
ssl:peername(Socket).
ssl:peername(Socket).

+ 24
- 24
src/wsSrv/elli_test.erl 查看文件

@ -13,35 +13,35 @@
-export([call/5]).
-spec call(Method, Path, Headers, Body, Opts) -> elli_handler:result() when
Method :: elli:http_method(),
Path :: binary(),
Headers :: elli:headers(),
Body :: elli:body(),
Opts :: proplists:proplist().
Method :: elli:http_method(),
Path :: binary(),
Headers :: elli:headers(),
Body :: elli:body(),
Opts :: proplists:proplist().
call(Method, Path, Headers, Body, Opts) ->
Callback = proplists:get_value(callback, Opts),
CallbackArgs = proplists:get_value(callback_args, Opts),
Req = elli_http:mk_req(Method, {abs_path, Path}, Headers, Headers,
Body, {1, 1}, undefined, {Callback, CallbackArgs}),
ok = Callback:handle_event(elli_startup, [], CallbackArgs),
Callback:handle(Req, CallbackArgs).
Callback = proplists:get_value(callback, Opts),
CallbackArgs = proplists:get_value(callback_args, Opts),
Req = elli_http:mk_req(Method, {abs_path, Path}, Headers, Headers,
Body, {1, 1}, undefined, {Callback, CallbackArgs}),
ok = Callback:handle_event(elli_startup, [], CallbackArgs),
Callback:handle(Req, CallbackArgs).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
hello_world_test() ->
?assertMatch({ok, [], <<"Hello World!">>},
elli_test:call('GET', <<"/hello/world/">>, [], <<>>,
?EXAMPLE_CONF)),
?assertMatch({ok, [], <<"Hello Test1">>},
elli_test:call('GET', <<"/hello/?name=Test1">>, [], <<>>,
?EXAMPLE_CONF)),
?assertMatch({ok,
[{<<"content-type">>,
<<"application/json; charset=ISO-8859-1">>}],
<<"{\"name\" : \"Test2\"}">>},
elli_test:call('GET', <<"/type?name=Test2">>,
[{<<"accept">>, <<"application/json">>}], <<>>,
?EXAMPLE_CONF)).
?assertMatch({ok, [], <<"Hello World!">>},
elli_test:call('GET', <<"/hello/world/">>, [], <<>>,
?EXAMPLE_CONF)),
?assertMatch({ok, [], <<"Hello Test1">>},
elli_test:call('GET', <<"/hello/?name=Test1">>, [], <<>>,
?EXAMPLE_CONF)),
?assertMatch({ok,
[{<<"content-type">>,
<<"application/json; charset=ISO-8859-1">>}],
<<"{\"name\" : \"Test2\"}">>},
elli_test:call('GET', <<"/type?name=Test2">>,
[{<<"accept">>, <<"application/json">>}], <<>>,
?EXAMPLE_CONF)).
-endif. %% TEST

+ 37
- 37
src/wsSrv/elli_util.erl 查看文件

@ -5,67 +5,67 @@
-include_lib("kernel/include/file.hrl").
-export([normalize_range/2
, encode_range/2
, file_size/1
]).
, encode_range/2
, file_size/1
]).
-export_type([range/0]).
-type range() :: {Offset:: non_neg_integer(), Length:: non_neg_integer()}.
-type range() :: {Offset :: non_neg_integer(), Length :: non_neg_integer()}.
-spec normalize_range(RangeOrSet, Size) -> Normalized when
RangeOrSet :: any(),
Size :: integer(),
Normalized :: range() | undefined | invalid_range.
RangeOrSet :: any(),
Size :: integer(),
Normalized :: range() | undefined | invalid_range.
%% @doc: If a valid byte-range, or byte-range-set of size 1
%% is supplied, returns a normalized range in the format
%% {Offset, Length}. Returns undefined when an empty byte-range-set
%% is supplied and the atom `invalid_range' in all other cases.
normalize_range({suffix, Length}, Size)
when is_integer(Length), Length > 0 ->
Length0 = erlang:min(Length, Size),
{Size - Length0, Length0};
when is_integer(Length), Length > 0 ->
Length0 = erlang:min(Length, Size),
{Size - Length0, Length0};
normalize_range({offset, Offset}, Size)
when is_integer(Offset), Offset >= 0, Offset < Size ->
{Offset, Size - Offset};
when is_integer(Offset), Offset >= 0, Offset < Size ->
{Offset, Size - Offset};
normalize_range({bytes, First, Last}, Size)
when is_integer(First), is_integer(Last), First =< Last ->
normalize_range({First, Last - First + 1}, Size);
when is_integer(First), is_integer(Last), First =< Last ->
normalize_range({First, Last - First + 1}, Size);
normalize_range({Offset, Length}, Size)
when is_integer(Offset), is_integer(Length),
Offset >= 0, Length >= 0, Offset < Size ->
Length0 = erlang:min(Length, Size - Offset),
{Offset, Length0};
when is_integer(Offset), is_integer(Length),
Offset >= 0, Length >= 0, Offset < Size ->
Length0 = erlang:min(Length, Size - Offset),
{Offset, Length0};
normalize_range([ByteRange], Size) ->
normalize_range(ByteRange, Size);
normalize_range(ByteRange, Size);
normalize_range([], _Size) -> undefined;
normalize_range(_, _Size) -> invalid_range.
normalize_range(_, _Size) -> invalid_range.
-spec encode_range(Range:: range() | invalid_range,
Size:: non_neg_integer()) -> ByteRange:: iolist().
-spec encode_range(Range :: range() | invalid_range,
Size :: non_neg_integer()) -> ByteRange :: iolist().
%% @doc: Encode Range to a Content-Range value.
encode_range(Range, Size) ->
[<<"bytes ">>, encode_range_bytes(Range),
<<"/">>, integer_to_binary(Size)].
[<<"bytes ">>, encode_range_bytes(Range),
<<"/">>, integer_to_binary(Size)].
encode_range_bytes({Offset, Length}) ->
[integer_to_binary(Offset),
<<"-">>,
integer_to_binary(Offset + Length - 1)];
[integer_to_binary(Offset),
<<"-">>,
integer_to_binary(Offset + Length - 1)];
encode_range_bytes(invalid_range) -> <<"*">>.
-spec file_size(Filename) -> Size | {error, Reason} when
Filename :: file:name_all(),
Size :: non_neg_integer(),
Reason :: file:posix() | badarg | invalid_file.
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{type = regular, access = Perm, size = Size}}
when Perm =:= read orelse Perm =:= read_write ->
Size;
{error, Reason} -> {error, Reason};
_ -> {error, invalid_file}
end.
case file:read_file_info(Filename) of
{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.

+ 3
- 4
src/wsSrv/elli_util.hrl 查看文件

@ -1,10 +1,9 @@
-ifdef(OTP_RELEASE).
-include_lib("kernel/include/logger.hrl").
-else.
-define(LOG_ERROR(Str), error_logger:error_msg(Str)).
-define(LOG_ERROR(Format,Data), error_logger:error_msg(Format, Data)).
-define(LOG_INFO(Format,Data), error_logger:info_msg(Format, Data)).
-define(LOG_ERROR(Format, Data), error_logger:error_msg(Format, Data)).
-define(LOG_INFO(Format, Data), error_logger:info_msg(Format, Data)).
-endif.
-ifdef(OTP_RELEASE).
@ -14,4 +13,4 @@
-endif.
%% Bloody useful
-define(IF(Test,True,False), case Test of true -> True; false -> False end).
-define(IF(Test, True, False), case Test of true -> True; false -> False end).

正在加载...
取消
保存