Kaynağa Gözat

ft: 代码修改

master
SisMaker 3 yıl önce
ebeveyn
işleme
8ee202d7f7
15 değiştirilmiş dosya ile 222 ekleme ve 1616 silme
  1. +4
    -46
      include/eWSrv.hrl
  2. +1
    -1
      include/wsCom.hrl
  3. +1
    -1
      src/example/wsEgHandover.erl
  4. +2
    -2
      src/example/wsEgHer.erl
  5. +4
    -11
      src/test/elli_tests.erl
  6. +0
    -56
      src/test/wsTest.erl
  7. +0
    -264
      src/wsSrv/elli.erl
  8. +25
    -5
      src/wsSrv/wsHer.erl
  9. +159
    -249
      src/wsSrv/wsHttp.erl
  10. +3
    -4
      src/wsSrv/wsHttpProtocol.erl
  11. +0
    -947
      src/wsSrv/wsHttps.erl
  12. +7
    -7
      src/wsSrv/wsMiddleware.erl
  13. +2
    -2
      src/wsSrv/wsMiddlewareCompress.erl
  14. +10
    -2
      src/wsSrv/wsNet.erl
  15. +4
    -19
      src/wsSrv/wsReq.erl

+ 4
- 46
include/eWSrv.hrl Dosyayı Görüntüle

@ -35,10 +35,10 @@
body = <<>> :: body()
}).
-export_type([req/0, method/0, body/0, headers/0, response_code/0]).
-export_type([wsReq/0, method/0, body/0, headers/0, response_code/0]).
%% @type req(). A record representing an HTTP request.
-type req() :: #wsReq{}.
-type wsReq() :: #wsReq{}.
%% @type http_method(). An uppercase atom representing a known HTTP verb or a
%% binary for other verbs.
@ -53,51 +53,9 @@
-type response_code() :: 100..999.
-define(EXAMPLE_CONF, [{callback, elli_example_callback}, {callback_args, []}]).
-define(CONTENT_LENGTH_HEADER, <<"content-length">>).
-define(CONTENT_LENGTH_HEADER, 'Content-Length').
-define(EXPECT_HEADER, <<"expect">>).
-define(CONNECTION_HEADER, <<"connection">>;).
-define(CONNECTION_HEADER, 'Connection').
-define(TRANSFER_ENCODING_HEADER, <<"Transfer-Encoding">>).
-export_type([callback/0, callback_mod/0, callback_args/0, event/0, result/0]).
%% @type callback(). A tuple of a {@type callback_mod()} and {@type
%% callback_args()}.
-type callback() :: {callback_mod(), callback_args()}.
%% @type callback_mod(). A callback module.
-type callback_mod() :: module().
%% @type callback_args(). Arguments to pass to a {@type callback_mod()}.
-type callback_args() :: list().
%% @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.
-type result() ::
{elli:response_code() |ok, elli:headers(), {file, file:name_all()}|
{file, file:name_all(), wsUtil:range()}}|
{elli:response_code() | ok, elli:headers(), elli:body()}|
{elli:response_code() | ok, elli:body()}|
{chunk, elli:headers()}|
{chunk, elli:headers(), elli:body()}|
ignore.
-type sendfile_opts() :: [{chunk_size, non_neg_integer()}].

+ 1
- 1
include/wsCom.hrl Dosyayı Görüntüle

@ -24,7 +24,7 @@
, rn :: undefined | binary:cp()
, socket :: undefined | wsNet:socket()
, isSsl = false :: boolean()
, wsMod :: callback()
, wsMod :: module()
, maxSize = 0 :: pos_integer() %%
, maxChunkCnt = 0 :: pos_integer() %% chunk
, maxRecvCnt = 0 :: pos_integer() %% recv最大的次数

+ 1
- 1
src/example/wsEgHandover.erl Dosyayı Görüntüle

@ -16,7 +16,7 @@ init(Req, _Args) ->
%% TODO: write docstring
-spec handle(Req, Args) -> Result when
Req :: elli:req(),
Req :: elli:wsReq(),
Args :: wsHer:callback_args(),
Result :: wsHer:result().
handle(Req, Args) ->

+ 2
- 2
src/example/wsEgHer.erl Dosyayı Görüntüle

@ -23,7 +23,7 @@
%% Delegate to our handler function.
%% @see handle/3
-spec handle(Req, _Args) -> Result when
Req :: elli:req(),
Req :: elli:wsReq(),
_Args :: wsHer:callback_args(),
Result :: wsHer:result().
handle(Req, _Args) -> handle(Req#wsReq.method, wsReq:path(Req), Req).
@ -64,7 +64,7 @@ handle(Req, _Args) -> handle(Req#wsReq.method, wsReq:path(Req), Req).
-spec handle(Method, Path, Req) -> wsHer:result() when
Method :: elli:method(),
Path :: [binary()],
Req :: elli:req().
Req :: elli:wsReq().
handle('GET', [<<"hello">>, <<"world">>], _Req) ->
%% Reply with a normal response.
timer:sleep(1000),

+ 4
- 11
src/test/elli_tests.erl Dosyayı Görüntüle

@ -112,12 +112,11 @@ accessors_test_() ->
Body = <<"name=knut%3D">>,
Name = <<"knut=">>,
Req1 = #wsReq{path = RawPath,
original_headers = Headers,
headers = Headers,
method = Method,
body = Body},
Args = [{<<"name">>, Name}],
Req2 = #wsReq{original_headers = Headers, headers = Headers, args = Args, body = <<>>},
Req2 = #wsReq{ headers = Headers, args = Args, body = <<>>},
[
%% POST /foo/bar
@ -627,22 +626,16 @@ body_qs_test() ->
{<<"found">>, true}],
Body = <<"foo=bar&baz=bang&found">>,
Headers = [{<<"content-type">>, <<"application/x-www-form-urlencoded">>}],
?assertMatch(Expected, wsReq:body_qs(#wsReq{body = Body,
original_headers = Headers,
headers = Headers})).
?assertMatch(Expected, wsReq:body_qs(#wsReq{body = Body, headers = Headers})).
to_proplist_test() ->
Req = #wsReq{method = 'GET',
path = [<<"crash">>],
args = [],
version = {1, 1},
path = <<"/crash">>,
original_headers = [{<<"Host">>, <<"localhost:3001">>}],
headers = [{<<"host">>, <<"localhost:3001">>}],
body = <<>>,
pid = self(),
socket = socket,
callback = {mod, []}},
body = <<>>
},
Prop = [{method, 'GET'},
{scheme, undefined},

+ 0
- 56
src/test/wsTest.erl Dosyayı Görüntüle

@ -1,56 +0,0 @@
-module(wsTest).
-compile([export_all]).
-define(Line, <<"GET /test/tttt HTTP/1.1\r\n">>).
test1(Rn) ->
parseRequestLine(?Line, Rn).
test2() ->
erlang:decode_packet(http_bin, ?Line, []).
parseRequestLine(Data, Rn) ->
case binary:split(Data, Rn) of
[Data] ->
not_enough_data;
[Line, Rest] ->
case binary:split(Line, <<" ">>, [global]) of
[Method, RawPath, V] ->
{Method, RawPath, V, Rest};
_ ->
{error, request_line}
end
end.
%% <<"Content-Type: application/json; charset=utf-8">>,
-spec request(boolean(), body(), method(), host(), binary(), path(), headers()) -> iolist().
request(true, undefined, Method, Host, _DbName, Path, Headers) ->
[
Method, <<"/_db/_system">>, Path, <<" HTTP/1.1\r\nHost: ">>, Host,
<<"\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 0\r\n">>,
spellHeaders(Headers), <<"\r\n">>
];
request(false, undefined, Method, Host, DbName, Path, Headers) ->
[
Method, DbName, Path, <<" HTTP/1.1\r\nHost: ">>, Host,
<<"\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 0\r\n">>,
spellHeaders(Headers), <<"\r\n">>
];
request(false, Body, Method, Host, DbName, Path, Headers) ->
ContentLength = integer_to_binary(iolist_size(Body)),
NewHeaders = [{<<"Content-Length">>, ContentLength} | Headers],
[
Method, DbName, Path, <<" HTTP/1.1\r\nHost: ">>, Host,
<<"\r\nContent-Type: application/json; charset=utf-8\r\n">>,
spellHeaders(NewHeaders), <<"\r\n">>, Body
];
request(true, Body, Method, Host, _DbName, Path, Headers) ->
ContentLength = integer_to_binary(iolist_size(Body)),
NewHeaders = [{<<"Content-Length">>, ContentLength} | Headers],
[
Method, <<"/_db/_system">>, Path, <<" HTTP/1.1\r\nHost: ">>, Host,
<<"\r\nContent-Type: application/json; charset=utf-8\r\n">>,
spellHeaders(NewHeaders), <<"\r\n">>, Body
].

+ 0
- 264
src/wsSrv/elli.erl Dosyayı Görüntüle

@ -1,264 +0,0 @@
-module(elli).
-behaviour(gen_server).
-include("wsCom.hrl").
-export([
start_link/0,
start_link/1,
stop/1,
get_acceptors/1,
get_open_reqs/1,
get_open_reqs/2,
set_callback/3
]).
-export([
init/1
, handle_call/3
, handle_cast/2
, handle_info/2
, terminate/2
, code_change/3
]).
-record(state, {socket :: wsNet:socket(),
acceptors :: ets:tid(),
open_reqs = 0 :: non_neg_integer(),
options = [] :: [{_, _}], % TODO: refine
callback :: wsHer:callback()
}).
%% @type state(). Internal state.
-opaque state() :: #state{}.
-export_type([state/0]).
%%%===================================================================
%%% API
%%%===================================================================
-spec start_link() -> Result when
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().
start_link(Opts) ->
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.
-spec get_acceptors(atom()) -> {reply, {ok, [ets:tid()]}, state()}.
get_acceptors(S) ->
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).
-spec get_open_reqs(S :: atom(), Timeout :: non_neg_integer()) -> Reply when
Reply :: {reply, {ok, non_neg_integer()}, state()}.
get_open_reqs(S, Timeout) ->
gen_server:call(S, get_open_reqs, Timeout).
-spec set_callback(S, Callback, CallbackArgs) -> Reply when
S :: atom(),
Callback :: wsHer:callback_mod(),
CallbackArgs :: wsHer: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}).
%% @doc Stop `Server'.
-spec stop(Server :: atom()) -> {stop, normal, ok, state()}.
stop(S) ->
gen_server:call(S, stop).
%%%===================================================================
%%% gen_server callbacks
%%%===================================================================
%% @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 = ?IIF(UseSSL, ssl, plain),
SSLSockOpts = ?IIF(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} = wsNet:listen(SockType, Port, [binary,
{ip, IPAddress},
{reuseaddr, true},
{backlog, 32768},
{packet, raw},
{active, false}
| SSLSockOpts]),
Acceptors = ets:new(acceptors, [private, set]),
[begin
Pid = wsHttp: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 :: wsHer:callback_mod(),
Args :: wsHer:callback_args().
handle_call(get_acceptors, _From, 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};
handle_call({set_callback, Callback, CallbackArgs}, _From, State) ->
ok = Callback:handle_event(elli_reconfigure, [], CallbackArgs),
{reply, ok, State#state{callback = {Callback, CallbackArgs}}};
handle_call(stop, _From, State) ->
{stop, normal, ok, State}.
%% @hidden
-spec handle_cast(accepted | _Msg, State0) -> {noreply, State1} when
State0 :: state(),
State1 :: state().
handle_cast(accepted, State) ->
{noreply, start_add_acceptor(State)};
handle_cast(_Msg, 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()}.
handle_info({'EXIT', _Pid, {error, emfile}}, State) ->
?wsErr("No more file descriptors, shutting down~n"),
{stop, emfile, State};
handle_info({'EXIT', Pid, normal}, State) ->
{noreply, remove_acceptor(State, Pid)};
handle_info({'EXIT', Pid, Reason}, State) ->
?wsErr("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.
%% @hidden
-spec code_change(_OldVsn, State, _Extra) -> {ok, State} when State :: state().
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%%%===================================================================
%%% Internal functions
%%%===================================================================
-spec remove_acceptor(State0 :: state(), Pid :: pid()) -> State1 :: state().
remove_acceptor(State, Pid) ->
ets:delete(State#state.acceptors, Pid),
dec_open_reqs(State).
-spec start_add_acceptor(State0 :: state()) -> State1 :: state().
start_add_acceptor(State) ->
Pid = wsHttp: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).
-spec required_opt(Name, Opts) -> Value when
Name :: any(),
Opts :: proplists:proplist(),
Value :: term().
required_opt(Name, Opts) ->
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)).
-spec dec_open_reqs(State0 :: state()) -> State1 :: state().
dec_open_reqs(#state{open_reqs = OpenReqs} = State) ->
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}.

+ 25
- 5
src/wsSrv/wsHer.erl Dosyayı Görüntüle

@ -2,15 +2,35 @@
-include("eWSrv.hrl").
-callback init(Req :: elli:req(), Args :: callback_args()) -> {ok, standard | handover}.
-export_type([callback/0, callback_mod/0, callback_args/0, result/0]).
-callback handle(Req :: elli:req(), callback_args()) -> result().
%% @type callback(). A tuple of a {@type callback_mod()} and {@type
%% callback_args()}.
-type callback() :: {callback_mod(), callback_args()}.
-callback handle_event(Event :: event(), Args :: callback_args(), Config :: [tuple()]) -> ok.
%% @type callback_mod(). A callback module.
-type callback_mod() :: module().
-callback preprocess(Req1 :: elli:req(), Args :: callback_args()) -> Req2 :: elli:req().
%% @type callback_args(). Arguments to pass to a {@type callback_mod()}.
-type callback_args() :: list().
-callback postprocess(Req :: elli:req(), Res1 :: result(), Args :: callback_args()) -> Res2 :: result().
-type result() ::
{response_code() |ok, headers(), {file, file:name_all()}|
{file, file:name_all(), wsUtil:range()}}|
{response_code() | ok, headers(), body()}|
{response_code() | ok, body()}|
{chunk, headers()}|
{chunk, headers(), body()}|
ignore.
-callback init(Req :: wsReq(), Args :: callback_args()) -> {ok, standard | handover}.
-callback handle(Req :: wsReq(), callback_args()) -> result().
-callback preprocess(Req1 :: wsReq(), Args :: callback_args()) -> Req2 :: wsReq().
-callback postprocess(Req :: wsReq(), Res1 :: result(), Args :: callback_args()) -> Res2 :: result().
-optional_callbacks([
init/2

+ 159
- 249
src/wsSrv/wsHttp.erl Dosyayı Görüntüle

@ -8,16 +8,12 @@
-export([
start_link/1
, send_response/4
, sendResponse/5
, send_file/5
%% Exported for looping with a fully-qualified module name
, handle_request/4
, chunk_loop/1
, split_args/1
, keepalive_loop/3
, keepalive_loop/5
, close_or_keepalive/2
, closeOrKeepAlive/2
]).
%% eNet callback
@ -61,9 +57,10 @@ handleInfo({tcp, _Socket, Data}, State) ->
{noreply, NewState};
{done, NewState} ->
Response = doHandle(NewState),
case handle_response(Req1, B2, Response) of
{keep_alive, NewBuffer} ->
handleInfo({tcp, _Socket, NewBuffer}, NewState);
#wsState{buffer = Buffer, socket = Socket, temHeader = TemHeader, method = Method} = NewState,
case doResponse(Response, Socket, TemHeader, Method) of
keep_alive ->
handleInfo({tcp, _Socket, Buffer}, newWsState(NewState));
{close, _} ->
wsNet:close(Socket),
ok
@ -104,14 +101,14 @@ handleInfo({?mSockReady, Sock}, _State) ->
inet:setopts(Sock, [{packet, raw}, {active, true}]),
{ok, #wsState{socket = Sock}};
handleInfo({?mSockReady, Sock, SslOpts, SslHSTet}, State) ->
case ntSslAcceptor:handshake(Sock, SslOpts, SslHSTet) of
{ok, SslSock} ->
ssl:setopts(Sock, [{packet, raw}, {active, true}]),
{ok, State#wsState{socket = SslSock, isSsl = true}};
_Err ->
?wsErr("ssl handshake error ~p~n", [_Err]),
{stop, _Err, State}
end;
case ntSslAcceptor:handshake(Sock, SslOpts, SslHSTet) of
{ok, SslSock} ->
ssl:setopts(Sock, [{packet, raw}, {active, true}]),
{ok, State#wsState{socket = SslSock, isSsl = true}};
_Err ->
?wsErr("ssl handshake error ~p~n", [_Err]),
{stop, _Err, State}
end;
handleInfo(_Msg, _State) ->
?wsErr("~p info receive unexpect msg ~p ~n ", [?MODULE, _Msg]),
kpS.
@ -122,156 +119,152 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% @doc Handle multiple requests on the same connection, i.e. `"keep alive"'.
keepalive_loop(Socket, Options, Callback) ->
keepalive_loop(Socket, 0, <<>>, Options, Callback).
newWsState(WsState) ->
WsState#wsState{
stage = reqLine
, buffer = <<>>
, wsReq = undefined
, headerCnt = 0
, temHeader = []
, contentLength = undefined
, temChunked = <<>>
}.
keepalive_loop(Socket, NumRequests, Buffer, Options, Callback) ->
case handle_request(Socket, Buffer, Options, Callback) of
{keep_alive, NewBuffer} ->
keepalive_loop(Socket, NumRequests + 1, NewBuffer, Options, Callback);
{close, _} ->
wsNet:close(Socket),
ok
%% @doc Execute the user callback, translating failure into a proper response.
doHandle(State) ->
#wsState{wsMod = WsMod, method = Method, path = Path, wsReq = WsReq} = State,
try WsMod:handle(Method, Path, WsReq) of
%% {ok,...{file,...}}
{ok, Headers, {file, Filename}} ->
{file, 200, Headers, Filename, []};
{ok, Headers, {file, Filename, Range}} ->
{file, 200, Headers, Filename, Range};
%% ok simple
{ok, Headers, Body} -> {response, 200, Headers, Body};
{ok, Body} -> {response, 200, [], Body};
%% Chunk
{chunk, Headers} -> {chunk, Headers, <<"">>};
{chunk, Headers, Initial} -> {chunk, Headers, Initial};
%% File
{HttpCode, Headers, {file, Filename}} ->
{file, HttpCode, Headers, Filename, {0, 0}};
{HttpCode, Headers, {file, Filename, Range}} ->
{file, HttpCode, Headers, Filename, Range};
%% Simple
{HttpCode, Headers, Body} -> {response, HttpCode, Headers, Body};
{HttpCode, Body} -> {response, HttpCode, [], Body};
%% Unexpected
Unexpected ->
?wsErr("handle return error ~p ~p~n", [WsReq, Unexpected]),
{response, 500, [], <<"Internal server error">>}
catch
throw:{ResponseCode, Headers, Body} when is_integer(ResponseCode) ->
{response, ResponseCode, Headers, Body};
throw:Exc:Stacktrace ->
?wsErr("handle catch throw ~p ~p ~p~n", [WsReq, Exc, Stacktrace]),
{response, 500, [], <<"Internal server error">>};
error:Error:Stacktrace ->
?wsErr("handle catch error ~p ~p ~p~n", [WsReq, Error, Stacktrace]),
{response, 500, [], <<"Internal server error">>};
exit:Exit:Stacktrace ->
?wsErr("handle catch exit ~p ~p ~p~n", [WsReq, Exit, Stacktrace]),
{response, 500, [], <<"Internal server error">>}
end.
handle_response(Req, Buffer, {response, Code, UserHeaders, Body}) ->
#wsReq{callback = {Mod, Args}} = Req,
Headers = [connection(Req, UserHeaders),
content_length(UserHeaders, Body)
| UserHeaders],
t(send_start),
send_response(Req, Code, Headers, Body),
t(send_end),
t(request_end),
handle_event(Mod, request_complete,
[Req, Code, Headers, Body, {get_timings(),
get_sizes()}], Args),
{close_or_keepalive(Req, UserHeaders), Buffer};
handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) ->
#wsReq{callback = {Mod, Args}} = Req,
ResponseHeaders = [{?TRANSFER_ENCODING_HEADER, <<"chunked">>},
connection(Req, UserHeaders)
| UserHeaders],
send_response(Req, 200, ResponseHeaders, <<"">>),
t(send_start),
Initial =:= <<"">> orelse send_chunk(Req#wsReq.socket, Initial),
ClosingEnd = case start_chunk_loop(Req#wsReq.socket) of
{error, client_closed} -> client;
ok -> server
end,
t(send_end),
t(request_end),
handle_event(Mod, chunk_complete,
[Req, 200, ResponseHeaders, ClosingEnd, {get_timings(),
get_sizes()}],
Args),
{close, <<>>};
handle_response(Req, Buffer, {file, ResponseCode, UserHeaders,
Filename, Range}) ->
#wsReq{callback = {Mod, Args}} = Req,
ResponseHeaders = [connection(Req, UserHeaders) | UserHeaders],
doResponse({response, Code, UserHeaders, Body}, Socket, TemHeader, Method) ->
Headers = [connection(UserHeaders, TemHeader), contentLength(UserHeaders, Body) | UserHeaders],
sendResponse(Socket, Method, Code, Headers, Body),
closeOrKeepAlive(UserHeaders, TemHeader);
doResponse({chunk, UserHeaders, Initial}, Socket, TemHeader, Method) ->
ResponseHeaders = [{?TRANSFER_ENCODING_HEADER, <<"chunked">>}, connection(UserHeaders, TemHeader) | UserHeaders],
sendResponse(Socket, Method, 200, ResponseHeaders, <<>>),
Initial =:= <<"">> orelse send_chunk(Socket, Initial),
case start_chunk_loop(Socket) of
{error, client_closed} -> client;
ok -> server
end,
close;
doResponse({file, ResponseCode, UserHeaders, Filename, Range}, Socket, TemHeader, Method) ->
ResponseHeaders = [connection(UserHeaders, TemHeader) | UserHeaders],
case wsUtil:file_size(Filename) of
{error, FileError} ->
handle_event(Mod, file_error, [FileError], Args),
send_server_error(Req#wsReq.socket),
wsNet:close(Req#wsReq.socket),
{error, _FileError} ->
send_server_error(Socket),
wsNet:close(Socket),
exit(normal);
Size ->
t(send_start),
case wsUtil:normalize_range(Range, Size) of
undefined ->
send_file(Req, ResponseCode,
[{<<"Content-Length">>, Size} |
ResponseHeaders],
Filename, {0, 0});
send_file(Socket, ResponseCode, [{<<"Content-Length">>, Size} | ResponseHeaders], Filename, {0, 0});
{Offset, Length} ->
ERange = wsUtil:encode_range({Offset, Length}, Size),
send_file(Req, 206,
lists:append(ResponseHeaders,
[{<<"Content-Length">>, Length},
{<<"Content-Range">>, ERange}]),
send_file(Socket, 206,
lists:append(ResponseHeaders, [{<<"Content-Length">>, Length}, {<<"Content-Range">>, ERange}]),
Filename, {Offset, Length});
invalid_range ->
ERange = wsUtil:encode_range(invalid_range, Size),
send_response(Req, 416,
lists:append(ResponseHeaders,
[{<<"Content-Length">>, 0},
{<<"Content-Range">>, ERange}]),
[])
sendResponse(Socket, Method, 416, lists:append(ResponseHeaders, [{<<"Content-Length">>, 0}, {<<"Content-Range">>, ERange}]), <<>>)
end,
t(send_end),
t(request_end),
handle_event(Mod, request_complete, [Req, ResponseCode, ResponseHeaders, <<>>, {get_timings(), get_sizes()}], Args),
{close_or_keepalive(Req, UserHeaders), Buffer}
closeOrKeepAlive(UserHeaders, TemHeader)
end.
%% @doc Generate a HTTP response and send it to the client.
send_response(Req, Code, Headers, UserBody) ->
ResponseHeaders = assemble_response_headers(Code, Headers),
Body = case {Req#wsReq.method, Code} of
{'HEAD', _} -> <<>>;
{_, 304} -> <<>>;
{_, 204} -> <<>>;
_ -> UserBody
end,
s(resp_body, iolist_size(Body)),
Response = [ResponseHeaders,
Body],
case wsNet:send(Req#wsReq.socket, Response) of
ok -> ok;
{error, Closed} when Closed =:= closed orelse Closed =:= enotconn ->
#wsReq{callback = {Mod, Args}} = Req,
handle_event(Mod, client_closed, [before_response], Args),
ok
sendResponse(Socket, Method, Code, Headers, UserBody) ->
Body =
case Method of
'HEAD' ->
<<>>;
_ ->
case Code of
304 ->
<<>>;
204 ->
<<>>;
_ ->
UserBody
end
end,
Response = http_response(Code, Headers, Body),
case wsNet:send(Socket, Response) of
ok ->
ok;
_Err ->
?wsErr("send_response error ~p~n", [_Err])
end.
%% @doc Send a HTTP response to the client where the body is the
%% contents of the given file. Assumes correctly set response code
%% and headers.
-spec send_file(Req, Code, Headers, Filename, Range) -> ok when
Req :: elli:req(),
Req :: elli:wsReq(),
Code :: elli:response_code(),
Headers :: elli:headers(),
Filename :: file:filename(),
Range :: wsUtil:range().
send_file(#wsReq{callback = {Mod, Args}} = Req, Code, Headers, Filename, Range) ->
send_file(Socket, Code, Headers, Filename, Range) ->
ResponseHeaders = assemble_response_headers(Code, Headers),
case file:open(Filename, [read, raw, binary]) of
{ok, Fd} -> do_send_file(Fd, Range, Req, ResponseHeaders);
{ok, Fd} -> do_send_file(Fd, Range, Socket, ResponseHeaders);
{error, FileError} ->
handle_event(Mod, file_error, [FileError], Args),
send_server_error(Req#wsReq.socket),
wsNet:close(Req#wsReq.socket),
send_server_error(Socket),
wsNet:close(Socket),
exit(normal)
end,
ok.
do_send_file(Fd, {Offset, Length}, #wsReq{callback = {Mod, Args}} = Req, Headers) ->
try wsNet:send(Req#wsReq.socket, Headers) of
do_send_file(Fd, {Offset, Length}, Socket, Headers) ->
try wsNet:send(Socket, Headers) of
ok ->
case wsNet:sendfile(Fd, Req#wsReq.socket, Offset, Length, []) of
case wsNet:sendfile(Fd, Socket, Offset, Length, []) of
{ok, BytesSent} -> s(file, BytesSent), ok;
{error, Closed} when Closed =:= closed orelse
Closed =:= enotconn ->
handle_event(Mod, client_closed, [before_response], Args)
{error, Closed} when Closed =:= closed orelse Closed =:= enotconn ->
?wsErr("send file error")
end;
{error, Closed} when Closed =:= closed orelse
Closed =:= enotconn ->
handle_event(Mod, client_closed, [before_response], Args)
{error, Closed} when Closed =:= closed orelse Closed =:= enotconn ->
?wsErr("send file error")
after
file:close(Fd)
end.
@ -289,46 +282,6 @@ send_rescue_response(Socket, Code, Body) ->
Response = http_response(Code, Body),
wsNet:send(Socket, Response).
%% @doc Execute the user callback, translating failure into a proper response.
doHandle(State) ->
#wsState{wsMod = WsMod, method = Method, path = Path, wsReq = WsReq} = State,
try WsMod:handle(Method, Path, WsReq) of
%% {ok,...{file,...}}
{ok, Headers, {file, Filename}} ->
{file, 200, Headers, Filename, []};
{ok, Headers, {file, Filename, Range}} ->
{file, 200, Headers, Filename, Range};
%% ok simple
{ok, Headers, Body} -> {response, 200, Headers, Body};
{ok, Body} -> {response, 200, [], Body};
%% Chunk
{chunk, Headers} -> {chunk, Headers, <<"">>};
{chunk, Headers, Initial} -> {chunk, Headers, Initial};
%% File
{HttpCode, Headers, {file, Filename}} ->
{file, HttpCode, Headers, Filename, {0, 0}};
{HttpCode, Headers, {file, Filename, Range}} ->
{file, HttpCode, Headers, Filename, Range};
%% Simple
{HttpCode, Headers, Body} -> {response, HttpCode, Headers, Body};
{HttpCode, Body} -> {response, HttpCode, [], Body};
%% Unexpected
Unexpected ->
?wsErr("handle return error ~p ~p~n", [WsReq, Unexpected]),
{response, 500, [], <<"Internal server error">>}
catch
throw:{ResponseCode, Headers, Body} when is_integer(ResponseCode) ->
{response, ResponseCode, Headers, Body};
throw:Exc:Stacktrace ->
?wsErr("handle catch throw ~p ~p ~p~n", [WsReq, Exc, Stacktrace]),
{response, 500, [], <<"Internal server error">>};
error:Error:Stacktrace ->
?wsErr("handle catch error ~p ~p ~p~n", [WsReq, Error, Stacktrace]),
{response, 500, [], <<"Internal server error">>};
exit:Exit:Stacktrace ->
?wsErr("handle catch exit ~p ~p ~p~n", [WsReq, Exit, Stacktrace]),
{response, 500, [], <<"Internal server error">>}
end.
%%
%% CHUNKED-TRANSFER
@ -403,7 +356,7 @@ maybe_send_continue(Socket, Headers) ->
% According to RFC2616 section 8.2.3 an origin server must respond with
% either a "100 Continue" or a final response code when the client
% headers contains "Expect:100-continue"
case get_header(?EXPECT_HEADER, Headers, undefined) of
case lists:keyfind(?EXPECT_HEADER, 1, Headers) of
<<"100-continue">> ->
Response = http_response(100),
wsNet:send(Socket, Response);
@ -417,84 +370,56 @@ http_response(Code) ->
http_response(Code, Body) ->
http_response(Code, [{?CONTENT_LENGTH_HEADER, size(Body)}], Body).
http_response(Code, Headers, <<>>) ->
[<<"HTTP/1.1 ">>, status(Code), <<"\r\n">>,
encode_headers(Headers), <<"\r\n">>];
http_response(Code, Headers, Body) ->
[http_response(Code, Headers, <<>>), Body].
[<<"HTTP/1.1 ">>, status(Code), <<"\r\n">>, spellHeaders(Headers), <<"\r\n">>, Body].
assemble_response_headers(Code, Headers) ->
ResponseHeaders = http_response(Code, Headers, <<>>),
s(resp_headers, iolist_size(ResponseHeaders)),
ResponseHeaders.
encode_headers([]) ->
[];
encode_headers([[] | H]) ->
encode_headers(H);
encode_headers([{K, V} | H]) ->
[encode_value(K), <<": ">>, encode_value(V), <<"\r\n">>, encode_headers(H)].
encode_value(V) when is_integer(V) -> integer_to_binary(V);
encode_value(V) when is_binary(V) -> V;
encode_value(V) when is_list(V) -> list_to_binary(V).
connection_token(#wsReq{version = {1, 1}, headers = Headers}) ->
case get_header(?CONNECTION_HEADER, Headers) of
<<"close">> -> <<"close">>;
<<"Close">> -> <<"close">>;
_ -> <<"Keep-Alive">>
end;
connection_token(#wsReq{version = {1, 0}, headers = Headers}) ->
case get_header(?CONNECTION_HEADER, Headers) of
<<"Keep-Alive">> -> <<"Keep-Alive">>;
_ -> <<"close">>
end;
connection_token(#wsReq{version = {0, 9}}) ->
<<"close">>.
%% @doc Return the preferred session handling setting to close or keep the
%% current session alive based on the presence of a header or the standard
%% default based on the version of HTTP of the request.
-spec close_or_keepalive(Req, Headers) -> KeepaliveOpt when
Req :: elli:req(),
Headers :: elli:headers(),
KeepaliveOpt :: close | keep_alive.
close_or_keepalive(Req, UserHeaders) ->
case get_header(?CONNECTION_HEADER, UserHeaders, connection_token(Req)) of
<<"close">> -> close;
<<"Keep-Alive">> -> keep_alive
spellHeaders(Headers) ->
<<<<(toBinStr(Key))/binary, ": ", (toBinStr(Value))/binary, "\r\n">> || {Key, Value} <- Headers>>.
toBinStr(V) when is_integer(V) -> integer_to_binary(V);
toBinStr(V) when is_binary(V) -> V;
toBinStr(V) when is_list(V) -> list_to_binary(V);
toBinStr(V) when is_atom(V) -> atom_to_binary(V).
closeOrKeepAlive(UserHeaders, ReqHeader) ->
case lists:keyfind(?CONNECTION_HEADER, 1, UserHeaders) of
{_, <<"close">>} ->
case lists:keyfind(?CONNECTION_HEADER, 1, ReqHeader) of
{_, <<"close">>} ->
close;
_ ->
keep_alive
end;
_ ->
keep_alive
end.
%% @doc Add appropriate connection header if the user did not add one already.
connection(Req, UserHeaders) ->
case get_header(?CONNECTION_HEADER, UserHeaders) of
undefined ->
{?CONNECTION_HEADER, connection_token(Req)};
connection(UserHeaders, ReqHeader) ->
case lists:keyfind(?CONNECTION_HEADER, 1, UserHeaders) of
false ->
case lists:keyfind(?CONNECTION_HEADER, 1, ReqHeader) of
{_, <<"close">>} ->
{?CONNECTION_HEADER, <<"Close">>};
{_, <<"Close">>} ->
{?CONNECTION_HEADER, <<"Close">>};
_ ->
{?CONNECTION_HEADER, <<"Keep-Alive">>}
end;
_ ->
[]
end.
content_length(Headers, Body) ->
?IIF(is_header_defined(?CONTENT_LENGTH_HEADER, Headers), [],
{?CONTENT_LENGTH_HEADER, iolist_size(Body)}).
is_header_defined(Key, Headers) ->
Key1 = string:casefold(Key),
lists:any(fun({X, _}) -> string:equal(Key1, X, true) end, Headers).
get_header(Key, Headers) ->
get_header(Key, Headers, undefined).
get_header(Key, Headers, Default) ->
CaseFoldedKey = string:casefold(Key),
case lists:search(fun({N, _}) -> string:equal(CaseFoldedKey, N, true) end, Headers) of
{value, {_, Value}} ->
Value;
contentLength(Headers, Body) ->
case lists:keyfind(?CONTENT_LENGTH_HEADER, Headers) of
false ->
Default
[];
_ ->
{?CONTENT_LENGTH_HEADER, iolist_size(Body)}
end.
%% @doc Split the URL arguments into a proplist.
@ -509,18 +434,6 @@ split_args(Qs) ->
[Name, Value] -> {Name, Value}
end || Token <- Tokens].
handle_event(Mod, Name, EventArgs, ElliArgs) ->
try
Mod:handle_event(Name, EventArgs, ElliArgs)
catch
EvClass:EvError:Stacktrace ->
?wsErr("~p:handle_event/3 crashed ~p:~p~n~p", [Mod, EvClass, EvError, Stacktrace])
end.
%%
%% SIZE HELPERS
%%
@ -536,9 +449,6 @@ s(chunks, Size) ->
s(Key, Size) ->
put({size, Key}, Size).
get_sizes() ->
lists:filtermap(fun get_sizes/1, get()).
get_sizes({{size, Key}, Value}) ->
erase({size, Key}),
{true, {Key, Value}};

+ 3
- 4
src/wsSrv/wsHttpProtocol.erl Dosyayı Görüntüle

@ -1,6 +1,5 @@
-module(wsHttpProtocol).
-include("eWSrv.hrl").
-include("wsCom.hrl").
-compile(inline).
@ -78,13 +77,13 @@ request(header, Data, State) ->
undefined ->
{error, content_length};
0 ->
{done, State#wsState{buffer = Rest, wsReq = NewWsReq, temHeader = []}};
{done, State#wsState{buffer = Rest, wsReq = NewWsReq}};
_ ->
case byte_size(Rest) > 0 of
true ->
request(body, Rest, State#wsState{stage = body, buffer = Rest, wsReq = NewWsReq, temHeader = []});
request(body, Rest, State#wsState{stage = body, buffer = Rest, wsReq = NewWsReq});
_ ->
{ok, State#wsState{stage = body, buffer = <<>>, wsReq = NewWsReq, temHeader = []}}
{ok, State#wsState{stage = body, buffer = <<>>, wsReq = NewWsReq}}
end
end;
{ok, {http_error, ErrStr}, _Rest} ->

+ 0
- 947
src/wsSrv/wsHttps.erl Dosyayı Görüntüle

@ -1,947 +0,0 @@
%% @doc: Elli HTTP request implementation
%%
%% An elli_http process blocks in elli_tcp:accept/2 until a client
%% connects. It then handles requests on that connection until it's
%% closed either by the client timing out or explicitly by the user.
-module(wsHttps).
-include("wsCom.hrl").
%% API
-export([start_link/4]).
-export([send_response/4]).
-export([send_file/5]).
-export([mk_req/8, mk_req/11]). %% useful when testing.
%% Exported for looping with a fully-qualified module name
-export([accept/4, handle_request/4, chunk_loop/1, split_args/1,
parse_path/1, keepalive_loop/3, keepalive_loop/5]).
%% Exported for correctly handling session keep-alive for handlers
%% operating in handler mode.
-export([close_or_keepalive/2]).
-define(CONTENT_LENGTH_HEADER, <<"content-length">>).
-define(EXPECT_HEADER, <<"expect">>).
-define(CONNECTION_HEADER, <<"connection">>).
-define(TRANSFER_ENCODING_HEADER, <<"Transfer-Encoding">>).
%% TODO: use this
%% -type connection_token() :: keep_alive | close.
-spec start_link(Server, ListenSocket, Options, Callback) -> pid() when
Server :: pid(),
ListenSocket :: wsNet:socket(),
Options :: proplists:proplist(),
Callback :: wsHer:callback().
start_link(Server, ListenSocket, Options, Callback) ->
proc_lib:spawn_link(?MODULE, accept,
[Server, ListenSocket, Options, Callback]).
%% @doc Accept on the socket until a client connects.
%% Handle the request, then loop if we're using keep alive or chunked transfer.
%% If {@link elli_tcp:accept/3} doesn't return a socket within a configurable
%% timeout, loop to allow code upgrades of this module.
-spec accept(Server, ListenSocket, Options, Callback) -> ok when
Server :: pid(),
ListenSocket :: wsNet:socket(),
Options :: proplists:proplist(),
Callback :: wsHer:callback().
accept(Server, ListenSocket, Options, Callback) ->
case catch wsNet:accept(ListenSocket, Server, accept_timeout(Options)) of
{ok, Socket} ->
t(accepted),
?MODULE:keepalive_loop(Socket, Options, Callback);
{error, timeout} ->
?MODULE:accept(Server, ListenSocket, Options, Callback);
{error, econnaborted} ->
?MODULE:accept(Server, ListenSocket, Options, Callback);
{error, {tls_alert, _}} ->
?MODULE:accept(Server, ListenSocket, Options, Callback);
{error, closed} ->
ok;
{error, Other} ->
exit({error, Other})
end.
%% @doc Handle multiple requests on the same connection, i.e. `"keep alive"'.
keepalive_loop(Socket, Options, Callback) ->
keepalive_loop(Socket, 0, <<>>, Options, Callback).
keepalive_loop(Socket, NumRequests, Buffer, Options, Callback) ->
case ?MODULE:handle_request(Socket, Buffer, Options, Callback) of
{keep_alive, NewBuffer} ->
?MODULE:keepalive_loop(Socket, NumRequests + 1,
NewBuffer, Options, Callback);
{close, _} ->
wsNet:close(Socket),
ok
end.
%% @doc Handle a HTTP request that will possibly come on the socket.
%% Returns the appropriate connection token and any buffer containing (parts of)
%% the next request.
-spec handle_request(Socket, PrevBin, Options, Callback) -> ConnToken when
Socket :: wsNet:socket(),
PrevBin :: binary(),
Options :: proplists:proplist(),
Callback :: wsHer:callback(),
ConnToken :: {'keep_alive' | 'close', binary()}.
handle_request(S, PrevB, Opts, {Mod, Args} = Callback) ->
{Method, RawPath, V, B0} = get_request(S, PrevB, Opts, Callback),
t(headers_start),
{{RequestHeaders, ParsedRequestHeaders}, B1} = get_headers(S, V, B0, Opts, Callback),
t(headers_end),
Req = mk_req(Method, RawPath, RequestHeaders, ParsedRequestHeaders, <<>>, V, S, Callback),
case init(Req) of
{ok, standard} ->
t(body_start),
{RequestBody, B2} = get_body(S, ParsedRequestHeaders, B1, Opts, Callback),
t(body_end),
Req1 = Req#wsReq{body = RequestBody},
t(user_start),
Response = execute_callback(Req1),
t(user_end),
handle_response(Req1, B2, Response);
{ok, handover} ->
Req1 = Req#wsReq{body = B1},
t(user_start),
Response = Mod:handle(Req1, Args),
t(user_end),
t(request_end),
handle_event(Mod, request_complete,
[Req1, handover, [], <<>>, {get_timings(),
get_sizes()}], Args),
Response
end.
handle_response(Req, Buffer, {response, Code, UserHeaders, Body}) ->
#wsReq{callback = {Mod, Args}} = Req,
Headers = [connection(Req, UserHeaders),
content_length(UserHeaders, Body)
| UserHeaders],
t(send_start),
send_response(Req, Code, Headers, Body),
t(send_end),
t(request_end),
handle_event(Mod, request_complete,
[Req, Code, Headers, Body, {get_timings(),
get_sizes()}], Args),
{close_or_keepalive(Req, UserHeaders), Buffer};
handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) ->
#wsReq{callback = {Mod, Args}} = Req,
ResponseHeaders = [{?TRANSFER_ENCODING_HEADER, <<"chunked">>},
connection(Req, UserHeaders)
| UserHeaders],
send_response(Req, 200, ResponseHeaders, <<"">>),
t(send_start),
Initial =:= <<"">> orelse send_chunk(Req#wsReq.socket, Initial),
ClosingEnd = case start_chunk_loop(Req#wsReq.socket) of
{error, client_closed} -> client;
ok -> server
end,
t(send_end),
t(request_end),
handle_event(Mod, chunk_complete,
[Req, 200, ResponseHeaders, ClosingEnd, {get_timings(),
get_sizes()}],
Args),
{close, <<>>};
handle_response(Req, Buffer, {file, ResponseCode, UserHeaders,
Filename, Range}) ->
#wsReq{callback = {Mod, Args}} = Req,
ResponseHeaders = [connection(Req, UserHeaders) | UserHeaders],
case wsUtil:file_size(Filename) of
{error, FileError} ->
handle_event(Mod, file_error, [FileError], Args),
send_server_error(Req#wsReq.socket),
wsNet:close(Req#wsReq.socket),
exit(normal);
Size ->
t(send_start),
case wsUtil:normalize_range(Range, Size) of
undefined ->
send_file(Req, ResponseCode,
[{<<"Content-Length">>, Size} |
ResponseHeaders],
Filename, {0, 0});
{Offset, Length} ->
ERange = wsUtil:encode_range({Offset, Length}, Size),
send_file(Req, 206,
lists:append(ResponseHeaders,
[{<<"Content-Length">>, Length},
{<<"Content-Range">>, ERange}]),
Filename, {Offset, Length});
invalid_range ->
ERange = wsUtil:encode_range(invalid_range, Size),
send_response(Req, 416,
lists:append(ResponseHeaders,
[{<<"Content-Length">>, 0},
{<<"Content-Range">>, ERange}]),
[])
end,
t(send_end),
t(request_end),
handle_event(Mod, request_complete,
[Req, ResponseCode, ResponseHeaders, <<>>,
{get_timings(),
get_sizes()}],
Args),
{close_or_keepalive(Req, UserHeaders), Buffer}
end.
%% @doc Generate a HTTP response and send it to the client.
send_response(Req, Code, Headers, UserBody) ->
ResponseHeaders = assemble_response_headers(Code, Headers),
Body = case {Req#wsReq.method, Code} of
{'HEAD', _} -> <<>>;
{_, 304} -> <<>>;
{_, 204} -> <<>>;
_ -> UserBody
end,
s(resp_body, iolist_size(Body)),
Response = [ResponseHeaders,
Body],
case wsNet:send(Req#wsReq.socket, Response) of
ok -> ok;
{error, Closed} when Closed =:= closed orelse Closed =:= enotconn ->
#wsReq{callback = {Mod, Args}} = Req,
handle_event(Mod, client_closed, [before_response], Args),
ok
end.
%% @doc Send a HTTP response to the client where the body is the
%% contents of the given file. Assumes correctly set response code
%% and headers.
-spec send_file(Req, Code, Headers, Filename, Range) -> ok when
Req :: elli:req(),
Code :: elli:response_code(),
Headers :: elli:headers(),
Filename :: file:filename(),
Range :: wsUtil:range().
send_file(#wsReq{callback = {Mod, Args}} = Req, Code, Headers, Filename, Range) ->
ResponseHeaders = assemble_response_headers(Code, Headers),
case file:open(Filename, [read, raw, binary]) of
{ok, Fd} -> do_send_file(Fd, Range, Req, ResponseHeaders);
{error, FileError} ->
handle_event(Mod, file_error, [FileError], Args),
send_server_error(Req#wsReq.socket),
wsNet:close(Req#wsReq.socket),
exit(normal)
end,
ok.
do_send_file(Fd, {Offset, Length}, #wsReq{callback = {Mod, Args}} = Req, Headers) ->
try wsNet:send(Req#wsReq.socket, Headers) of
ok ->
case wsNet:sendfile(Fd, Req#wsReq.socket, Offset, Length, []) of
{ok, BytesSent} -> s(file, BytesSent), ok;
{error, Closed} when Closed =:= closed orelse
Closed =:= enotconn ->
handle_event(Mod, client_closed, [before_response], Args)
end;
{error, Closed} when Closed =:= closed orelse
Closed =:= enotconn ->
handle_event(Mod, client_closed, [before_response], Args)
after
file:close(Fd)
end.
%% @doc To send a response, we must first have received everything the
%% client is sending. If this is not the case, {@link send_bad_request/1}
%% might reset the client connection.
send_bad_request(Socket) ->
send_rescue_response(Socket, 400, <<"Bad Request">>).
send_server_error(Socket) ->
send_rescue_response(Socket, 500, <<"Server Error">>).
send_rescue_response(Socket, Code, Body) ->
Response = http_response(Code, Body),
wsNet:send(Socket, Response).
%% @doc Execute the user callback, translating failure into a proper response.
execute_callback(#wsReq{callback = {Mod, Args}} = Req) ->
try Mod:handle(Req, Args) of
%% {ok,...{file,...}}
{ok, Headers, {file, Filename}} ->
{file, 200, Headers, Filename, []};
{ok, Headers, {file, Filename, Range}} ->
{file, 200, Headers, Filename, Range};
%% ok simple
{ok, Headers, Body} -> {response, 200, Headers, Body};
{ok, Body} -> {response, 200, [], Body};
%% Chunk
{chunk, Headers} -> {chunk, Headers, <<"">>};
{chunk, Headers, Initial} -> {chunk, Headers, Initial};
%% File
{HttpCode, Headers, {file, Filename}} ->
{file, HttpCode, Headers, Filename, {0, 0}};
{HttpCode, Headers, {file, Filename, Range}} ->
{file, HttpCode, Headers, Filename, Range};
%% Simple
{HttpCode, Headers, Body} -> {response, HttpCode, Headers, Body};
{HttpCode, Body} -> {response, HttpCode, [], Body};
%% Unexpected
Unexpected ->
handle_event(Mod, invalid_return, [Req, Unexpected], Args),
{response, 500, [], <<"Internal server error">>}
catch
throw:{ResponseCode, Headers, Body} when is_integer(ResponseCode) ->
{response, ResponseCode, Headers, Body};
throw:Exc:Stacktrace ->
handle_event(Mod, request_throw, [Req, Exc, Stacktrace], Args),
{response, 500, [], <<"Internal server error">>};
error:Error:Stacktrace ->
handle_event(Mod, request_error, [Req, Error, Stacktrace], Args),
{response, 500, [], <<"Internal server error">>};
exit:Exit:Stacktrace ->
handle_event(Mod, request_exit, [Req, Exit, Stacktrace], Args),
{response, 500, [], <<"Internal server error">>}
end .
%%
%% CHUNKED-TRANSFER
%%
%% @doc The chunk loop is an intermediary between the socket and the
%% user. We forward anything the user sends until the user sends an
%% empty response, which signals that the connection should be
%% closed. When the client closes the socket, the loop exits.
start_chunk_loop(Socket) ->
%% Set the socket to active so we receive the tcp_closed message
%% if the client closes the connection
wsNet:setopts(Socket, [{active, once}]),
?MODULE:chunk_loop(Socket).
chunk_loop(Socket) ->
{_SockType, InnerSocket} = Socket,
receive
{tcp_closed, InnerSocket} ->
{error, client_closed};
{chunk, close} ->
case wsNet:send(Socket, <<"0\r\n\r\n">>) of
ok ->
wsNet:close(Socket),
ok;
{error, Closed} when Closed =:= closed orelse
Closed =:= enotconn ->
{error, client_closed}
end;
{chunk, close, From} ->
case wsNet:send(Socket, <<"0\r\n\r\n">>) of
ok ->
wsNet:close(Socket),
From ! {self(), ok},
ok;
{error, Closed} when Closed =:= closed orelse
Closed =:= enotconn ->
From ! {self(), {error, closed}},
ok
end;
{chunk, Data} ->
send_chunk(Socket, Data),
?MODULE:chunk_loop(Socket);
{chunk, Data, From} ->
case send_chunk(Socket, Data) of
ok ->
From ! {self(), ok};
{error, Closed} when Closed =:= closed orelse
Closed =:= enotconn ->
From ! {self(), {error, closed}}
end,
?MODULE:chunk_loop(Socket)
after 10000 ->
?MODULE:chunk_loop(Socket)
end.
send_chunk(Socket, Data) ->
case iolist_size(Data) of
0 -> ok;
Size ->
Response = [integer_to_list(Size, 16),
<<"\r\n">>, Data, <<"\r\n">>],
s(chunks, iolist_size(Response)),
wsNet:send(Socket, Response)
end.
%%
%% RECEIVE REQUEST
%%
%% @doc Retrieve the request line.
get_request(Socket, <<>>, Options, Callback) ->
NewBuffer = recv_request(Socket, <<>>, Options, Callback),
get_request(Socket, NewBuffer, Options, Callback);
get_request(Socket, Buffer, Options, Callback) ->
t(request_start),
get_request_(Socket, Buffer, Options, Callback).
get_request_(Socket, Buffer, Options, {Mod, Args} = Callback) ->
case erlang:decode_packet(http_bin, Buffer, []) of
{more, _} ->
NewBuffer = recv_request(Socket, Buffer, Options, Callback),
get_request_(Socket, NewBuffer, Options, Callback);
{ok, {http_request, Method, RawPath, Version}, Rest} ->
{Method, RawPath, Version, Rest};
{ok, {http_error, _}, _} ->
handle_event(Mod, request_parse_error, [Buffer], Args),
send_bad_request(Socket),
wsNet:close(Socket),
exit(normal);
{ok, {http_response, _, _, _}, _} ->
wsNet:close(Socket),
exit(normal)
end.
recv_request(Socket, Buffer, Options, {Mod, Args} = _Callback) ->
case wsNet:recv(Socket, 0, request_timeout(Options)) of
{ok, Data} ->
<<Buffer/binary, Data/binary>>;
{error, timeout} ->
handle_event(Mod, request_timeout, [], Args),
wsNet:close(Socket),
exit(normal);
{error, Closed} when Closed =:= closed orelse
Closed =:= enotconn ->
handle_event(Mod, request_closed, [], Args),
wsNet:close(Socket),
exit(normal)
end.
-spec get_headers(Socket, V, Buffer, Opts, Callback) -> Headers when
Socket :: wsNet:socket(),
V :: version(),
Buffer :: binary(),
Opts :: proplists:proplist(),
Callback :: wsHer:callback(),
Headers :: {{elli:headers(), elli:headers()}, any()}. % TODO: refine
get_headers(_Socket, {0, 9}, _, _, _) ->
{{[], []}, <<>>};
get_headers(Socket, {1, _}, Buffer, Opts, Callback) ->
get_headers(Socket, Buffer, {[], []}, 0, Opts, Callback).
get_headers(Socket, _, {Headers, _}, HeadersCount, _Opts, {Mod, Args})
when HeadersCount >= 100 ->
handle_event(Mod, bad_request, [{too_many_headers, Headers}], Args),
send_bad_request(Socket),
wsNet:close(Socket),
exit(normal);
get_headers(Socket, Buffer, {Headers, ParsedHeaders}, Count, Opts, {Mod, Args} = Callback) ->
case erlang:decode_packet(httph_bin, Buffer, []) of
{ok, {http_header, _, Key, _, Value}, Rest} ->
BinKey = ensure_binary(Key),
NewHeaders = [{BinKey, Value} | Headers],
NewParsedHeaders = [{string:casefold(BinKey), Value} | ParsedHeaders],
get_headers(Socket, Rest, {NewHeaders, NewParsedHeaders}, Count + 1, Opts, Callback);
{ok, http_eoh, Rest} ->
{{Headers, ParsedHeaders}, Rest};
{ok, {http_error, _}, Rest} ->
get_headers(Socket, Rest, {Headers, ParsedHeaders}, Count, Opts, Callback);
{more, _} ->
case wsNet:recv(Socket, 0, header_timeout(Opts)) of
{ok, Data} ->
get_headers(Socket, <<Buffer/binary, Data/binary>>,
{Headers, ParsedHeaders}, Count, Opts, Callback);
{error, Closed} when Closed =:= closed orelse
Closed =:= enotconn ->
handle_event(Mod, client_closed, [receiving_headers], Args),
wsNet:close(Socket),
exit(normal);
{error, timeout} ->
handle_event(Mod, client_timeout,
[receiving_headers], Args),
wsNet:close(Socket),
exit(normal)
end
end.
%% @doc Fetch the full body of the request, if any is available.
%%
%% At the moment we don't need to handle large requests, so there is
%% no need for streaming or lazily fetching the body in the user
%% code. Fully receiving the body allows us to avoid the complex
%% request object threading in Cowboy and the caching in Mochiweb.
%%
%% As we are always receiving whatever the client sends, we might have
%% buffered too much and get parts of the next pipelined request. In
%% that case, push it back in the buffer and handle the first request.
-spec get_body(Socket, Headers, Buffer, Opts, Callback) -> FullBody when
Socket :: undefined | wsNet:socket(),
Headers :: elli:headers(),
Buffer :: binary(),
Opts :: proplists:proplist(),
Callback :: wsHer:callback(),
FullBody :: {elli:body(), binary()}.
get_body(Socket, Headers, Buffer, Opts, Callback) ->
case get_header(?CONTENT_LENGTH_HEADER, Headers, undefined) of
undefined ->
{<<>>, Buffer};
ContentLengthBin ->
maybe_send_continue(Socket, Headers),
ContentLength = binary_to_integer(binary:replace(ContentLengthBin,
<<" ">>, <<>>,
[global])),
ok = check_max_size(Socket, ContentLength, Buffer, Opts, Callback),
Result = case ContentLength - byte_size(Buffer) of
0 ->
{Buffer, <<>>};
N when N > 0 ->
do_get_body(Socket, Buffer, Opts, N, Callback);
_ ->
<<Body:ContentLength/binary, R/binary>> = Buffer,
{Body, R}
end,
%% set the size here so if do_get_body exits it won't have
%% req_body in sizes
s(req_body, ContentLength),
Result
end.
do_get_body(Socket, Buffer, Opts, N, {Mod, Args}) ->
case wsNet:recv(Socket, N, body_timeout(Opts)) of
{ok, Data} ->
{<<Buffer/binary, Data/binary>>, <<>>};
{error, Closed} when Closed =:= closed orelse Closed =:= enotconn ->
handle_event(Mod, client_closed, [receiving_body], Args),
ok = wsNet:close(Socket),
exit(normal);
{error, timeout} ->
handle_event(Mod, client_timeout, [receiving_body], Args),
ok = wsNet:close(Socket),
exit(normal)
end.
ensure_binary(Bin) when is_binary(Bin) -> Bin;
ensure_binary(Atom) when is_atom(Atom) -> atom_to_binary(Atom, latin1).
maybe_send_continue(Socket, Headers) ->
% According to RFC2616 section 8.2.3 an origin server must respond with
% either a "100 Continue" or a final response code when the client
% headers contains "Expect:100-continue"
case get_header(?EXPECT_HEADER, Headers, undefined) of
<<"100-continue">> ->
Response = http_response(100),
wsNet:send(Socket, Response);
_Other ->
ok
end.
%% @doc To send a response, we must first receive anything the client is
%% sending. To avoid allowing clients to use all our bandwidth, if the request
%% size is too big, we simply close the socket.
check_max_size(Socket, ContentLength, Buffer, Opts, {Mod, Args}) ->
MaxSize = max_body_size(Opts),
do_check_max_size(Socket, ContentLength, Buffer, MaxSize, {Mod, Args}).
do_check_max_size(Socket, ContentLength, Buffer, MaxSize, {Mod, Args})
when ContentLength > MaxSize ->
handle_event(Mod, bad_request, [{body_size, ContentLength}], Args),
do_check_max_size_x2(Socket, ContentLength, Buffer, MaxSize),
wsNet:close(Socket),
exit(normal);
do_check_max_size(_, _, _, _, _) -> ok.
do_check_max_size_x2(Socket, ContentLength, Buffer, MaxSize)
when ContentLength < MaxSize * 2 ->
OnSocket = ContentLength - size(Buffer),
wsNet:recv(Socket, OnSocket, 60000),
Response = http_response(413),
wsNet:send(Socket, Response);
do_check_max_size_x2(_, _, _, _) -> ok.
-spec mk_req(Method, PathTuple, Headers, Headers, Body, V, Socket, Callback) -> Req when
Method :: elli:method(),
PathTuple :: {PathType :: atom(), RawPath :: binary()},
Headers :: elli:headers(),
Body :: elli:body(),
V :: version(),
Socket :: wsNet:socket() | undefined,
Callback :: wsHer:callback(),
Req :: elli:req().
mk_req(Method, PathTuple, Headers, ParsedHeaders, Body, V, Socket, {Mod, Args} = Callback) ->
case parse_path(PathTuple) of
{ok, {Scheme, Host, Port}, {Path, URL, URLArgs}} ->
#wsReq{method = Method, scheme = Scheme, host = Host,
port = Port, path = URL, args = URLArgs,
version = V, path = Path, original_headers = Headers,
body = Body, pid = self(), socket = Socket,
callback = Callback, headers = ParsedHeaders};
{error, Reason} ->
handle_event(Mod, request_parse_error,
[{Reason, {Method, PathTuple}}], Args),
send_bad_request(Socket),
wsNet:close(Socket),
exit(normal)
end.
mk_req(Method, Scheme, Host, Port, PathTuple, Headers, ParsedHeaders, Body, V, Socket, Callback) ->
Req = mk_req(Method, PathTuple, Headers, ParsedHeaders, Body, V, Socket, Callback),
Req#wsReq{scheme = Scheme, host = Host, port = Port}.
%%
%% HEADERS
%%
http_response(Code) ->
http_response(Code, <<>>).
http_response(Code, Body) ->
http_response(Code, [{?CONTENT_LENGTH_HEADER, size(Body)}], Body).
http_response(Code, Headers, <<>>) ->
[<<"HTTP/1.1 ">>, status(Code), <<"\r\n">>,
encode_headers(Headers), <<"\r\n">>];
http_response(Code, Headers, Body) ->
[http_response(Code, Headers, <<>>), Body].
assemble_response_headers(Code, Headers) ->
ResponseHeaders = http_response(Code, Headers, <<>>),
s(resp_headers, iolist_size(ResponseHeaders)),
ResponseHeaders.
encode_headers([]) ->
[];
encode_headers([[] | H]) ->
encode_headers(H);
encode_headers([{K, V} | H]) ->
[encode_value(K), <<": ">>, encode_value(V), <<"\r\n">>, encode_headers(H)].
encode_value(V) when is_integer(V) -> integer_to_binary(V);
encode_value(V) when is_binary(V) -> V;
encode_value(V) when is_list(V) -> list_to_binary(V).
connection_token(#wsReq{version = {1, 1}, headers = Headers}) ->
case get_header(?CONNECTION_HEADER, Headers) of
<<"close">> -> <<"close">>;
<<"Close">> -> <<"close">>;
_ -> <<"Keep-Alive">>
end;
connection_token(#wsReq{version = {1, 0}, headers = Headers}) ->
case get_header(?CONNECTION_HEADER, Headers) of
<<"Keep-Alive">> -> <<"Keep-Alive">>;
_ -> <<"close">>
end;
connection_token(#wsReq{version = {0, 9}}) ->
<<"close">>.
%% @doc Return the preferred session handling setting to close or keep the
%% current session alive based on the presence of a header or the standard
%% default based on the version of HTTP of the request.
-spec close_or_keepalive(Req, Headers) -> KeepaliveOpt when
Req :: elli:req(),
Headers :: elli:headers(),
KeepaliveOpt :: close | keep_alive.
close_or_keepalive(Req, UserHeaders) ->
case get_header(?CONNECTION_HEADER, UserHeaders, connection_token(Req)) of
<<"close">> -> close;
<<"Keep-Alive">> -> keep_alive
end.
%% @doc Add appropriate connection header if the user did not add one already.
connection(Req, UserHeaders) ->
case get_header(?CONNECTION_HEADER, UserHeaders) of
undefined ->
{?CONNECTION_HEADER, connection_token(Req)};
_ ->
[]
end.
content_length(Headers, Body) ->
?IIF(is_header_defined(?CONTENT_LENGTH_HEADER, Headers), [],
{?CONTENT_LENGTH_HEADER, iolist_size(Body)}).
is_header_defined(Key, Headers) ->
Key1 = string:casefold(Key),
lists:any(fun({X, _}) -> string:equal(Key1, X, true) end, 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
{value, {_, Value}} ->
Value;
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.
split_path(Path) ->
[P || P <- binary:split(Path, [<<"/">>], [global]),
P =/= <<>>].
%% @doc Split the URL arguments into a proplist.
%% Lifted from `cowboy_http:x_www_form_urlencoded/2'.
-spec split_args(binary()) -> list({binary(), binary() | true}).
split_args(<<>>) ->
[];
split_args(Qs) ->
Tokens = binary:split(Qs, <<"&">>, [global, trim]),
[case binary:split(Token, <<"=">>) of
[Token] -> {Token, true};
[Name, Value] -> {Name, Value}
end || Token <- Tokens].
%%
%% CALLBACK HELPERS
%%
init(#wsReq{callback = {Mod, Args}} = Req) ->
?IIF(erlang:function_exported(Mod, init, 2),
case Mod:init(Req, Args) of
ignore -> {ok, standard};
{ok, Behaviour} -> {ok, Behaviour}
end,
{ok, standard}).
handle_event(Mod, Name, EventArgs, ElliArgs) ->
try
Mod:handle_event(Name, EventArgs, ElliArgs)
catch
EvClass:EvError:Stacktrace ->
?wsErr("~p:handle_event/3 crashed ~p:~p~n~p",
[Mod, EvClass, EvError, Stacktrace])
end.
%%
%% TIMING HELPERS
%%
%% @doc Record the current monotonic time in the process dictionary.
%% This allows easily adding time tracing wherever,
%% without passing along any variables.
t(Key) ->
put({time, Key}, erlang:monotonic_time()).
get_timings() ->
lists:filtermap(fun get_timings/1, get()).
get_timings({{time, accepted}, Value}) ->
{true, {accepted, Value}};
get_timings({{time, Key}, Value}) ->
erase({time, Key}),
{true, {Key, Value}};
get_timings(_) ->
false.
%%
%% SIZE HELPERS
%%
%% @doc stores response part size in bytes
s(chunks, Size) ->
case get({size, chunks}) of
undefined ->
put({size, chunks}, Size);
Sum ->
put({size, chunks}, Size + Sum)
end;
s(Key, Size) ->
put({size, Key}, Size).
get_sizes() ->
lists:filtermap(fun get_sizes/1, get()).
get_sizes({{size, Key}, Value}) ->
erase({size, Key}),
{true, {Key, Value}};
get_sizes(_) ->
false.
%%
%% OPTIONS
%%
accept_timeout(Opts) -> proplists:get_value(accept_timeout, Opts).
request_timeout(Opts) -> proplists:get_value(request_timeout, Opts).
header_timeout(Opts) -> proplists:get_value(header_timeout, Opts).
body_timeout(Opts) -> proplists:get_value(body_timeout, Opts).
max_body_size(Opts) -> proplists:get_value(max_body_size, Opts).
%%
%% HTTP STATUS CODES
%%
%% @doc Response code string. Lifted from `cowboy_http_req.erl'.
status(100) -> <<"100 Continue">>;
status(101) -> <<"101 Switching Protocols">>;
status(102) -> <<"102 Processing">>;
status(200) -> <<"200 OK">>;
status(201) -> <<"201 Created">>;
status(202) -> <<"202 Accepted">>;
status(203) -> <<"203 Non-Authoritative Information">>;
status(204) -> <<"204 No Content">>;
status(205) -> <<"205 Reset Content">>;
status(206) -> <<"206 Partial Content">>;
status(207) -> <<"207 Multi-Status">>;
status(226) -> <<"226 IM Used">>;
status(300) -> <<"300 Multiple Choices">>;
status(301) -> <<"301 Moved Permanently">>;
status(302) -> <<"302 Found">>;
status(303) -> <<"303 See Other">>;
status(304) -> <<"304 Not Modified">>;
status(305) -> <<"305 Use Proxy">>;
status(306) -> <<"306 Switch Proxy">>;
status(307) -> <<"307 Temporary Redirect">>;
status(400) -> <<"400 Bad Request">>;
status(401) -> <<"401 Unauthorized">>;
status(402) -> <<"402 Payment Required">>;
status(403) -> <<"403 Forbidden">>;
status(404) -> <<"404 Not Found">>;
status(405) -> <<"405 Method Not Allowed">>;
status(406) -> <<"406 Not Acceptable">>;
status(407) -> <<"407 Proxy Authentication Required">>;
status(408) -> <<"408 Request Timeout">>;
status(409) -> <<"409 Conflict">>;
status(410) -> <<"410 Gone">>;
status(411) -> <<"411 Length Required">>;
status(412) -> <<"412 Precondition Failed">>;
status(413) -> <<"413 Request Entity Too Large">>;
status(414) -> <<"414 Request-URI Too Long">>;
status(415) -> <<"415 Unsupported Media Type">>;
status(416) -> <<"416 Requested Range Not Satisfiable">>;
status(417) -> <<"417 Expectation Failed">>;
status(418) -> <<"418 I'm a teapot">>;
status(422) -> <<"422 Unprocessable Entity">>;
status(423) -> <<"423 Locked">>;
status(424) -> <<"424 Failed Dependency">>;
status(425) -> <<"425 Unordered Collection">>;
status(426) -> <<"426 Upgrade Required">>;
status(428) -> <<"428 Precondition Required">>;
status(429) -> <<"429 Too Many Requests">>;
status(431) -> <<"431 Request Header Fields Too Large">>;
status(500) -> <<"500 Internal Server Error">>;
status(501) -> <<"501 Not Implemented">>;
status(502) -> <<"502 Bad Gateway">>;
status(503) -> <<"503 Service Unavailable">>;
status(504) -> <<"504 Gateway Timeout">>;
status(505) -> <<"505 HTTP Version Not Supported">>;
status(506) -> <<"506 Variant Also Negotiates">>;
status(507) -> <<"507 Insufficient Storage">>;
status(510) -> <<"510 Not Extended">>;
status(511) -> <<"511 Network Authentication Required">>;
status(I) when is_integer(I), I >= 100, I < 1000 -> <<(integer_to_binary(I))/binary, "Status">>;
status(B) when is_binary(B) -> B.
%%
%% UNIT TESTS
%%
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
get_body_test() ->
Socket = undefined,
Headers = [{<<"Content-Length">>, <<" 42 ">>}],
Buffer = binary:copy(<<".">>, 42),
Opts = [],
Callback = {no_mod, []},
?assertMatch({Buffer, <<>>},
get_body(Socket, Headers, Buffer, Opts, Callback)).
-endif.

+ 7
- 7
src/wsSrv/wsMiddleware.erl Dosyayı Görüntüle

@ -50,14 +50,14 @@
%% @hidden
-spec init(Req, Args) -> {ok, standard | handover} when
Req :: elli:req(),
Req :: elli:wsReq(),
Args :: wsHer:callback_args().
init(Req, Args) ->
do_init(Req, callbacks(Args)).
%% @hidden
-spec handle(Req :: elli:req(), Config :: [tuple()]) -> wsHer:result().
-spec handle(Req :: elli:wsReq(), Config :: [tuple()]) -> wsHer:result().
handle(CleanReq, Config) ->
Callbacks = callbacks(Config),
PreReq = preprocess(CleanReq, Callbacks),
@ -83,7 +83,7 @@ handle_event(Event, Args, Config) ->
%%
-spec do_init(Req, Callbacks) -> {ok, standard | handover} when
Req :: elli:req(),
Req :: elli:wsReq(),
Callbacks :: wsHer:callbacks().
do_init(_, []) ->
{ok, standard};
@ -96,7 +96,7 @@ do_init(Req, [{Mod, Args} | Mods]) ->
-spec process(Req, Callbacks) -> Result when
Req :: elli:req(),
Req :: elli:wsReq(),
Callbacks :: [Callback :: wsHer:callback()],
Result :: wsHer:result().
process(_Req, []) ->
@ -109,9 +109,9 @@ process(Req, [{Mod, Args} | Mods]) ->
end).
-spec preprocess(Req1, Callbacks) -> Req2 when
Req1 :: elli:req(),
Req1 :: elli:wsReq(),
Callbacks :: [wsHer:callback()],
Req2 :: elli:req().
Req2 :: elli:wsReq().
preprocess(Req, []) ->
Req;
preprocess(Req, [{Mod, Args} | Mods]) ->
@ -119,7 +119,7 @@ preprocess(Req, [{Mod, Args} | Mods]) ->
preprocess(Mod:preprocess(Req, Args), Mods)).
-spec postprocess(Req, Res1, Callbacks) -> Res2 when
Req :: elli:req(),
Req :: elli:wsReq(),
Res1 :: wsHer:result(),
Callbacks :: [wsHer:callback()],
Res2 :: wsHer:result().

+ 2
- 2
src/wsSrv/wsMiddlewareCompress.erl Dosyayı Görüntüle

@ -10,7 +10,7 @@
%%% @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(),
Req :: elli:wsReq(),
Result :: wsHer:result(),
Config :: [{compress_byte_size, non_neg_integer()} | tuple()].
postprocess(Req, {ResponseCode, Body}, Config)
@ -36,7 +36,7 @@ postprocess(_, Res, _) ->
%%
%% NOTE: Algorithm is either `<<"gzip">>' or `<<"deflate">>'.
-spec compress(Body0 :: elli:body(), Req :: elli:req()) -> Body1 when
-spec compress(Body0 :: elli:body(), Req :: elli:wsReq()) -> Body1 when
Body1 :: {Compressed :: binary(), Algorithm :: binary()} | no_compress.
compress(Body, Req) ->
case accepted_encoding(Req) of

+ 10
- 2
src/wsSrv/wsNet.erl Dosyayı Görüntüle

@ -2,8 +2,16 @@
%%% Based on `mochiweb_socket.erl'.
-module(wsNet).
-export([listen/3, accept/3, recv/3, send/2, close/1,
setopts/2, sendfile/5, peername/1]).
-export([
listen/3
, accept/3
, recv/3
, send/2
, close/1
, setopts/2
, sendfile/5
, peername/1
]).
-export_type([socket/0]).

+ 4
- 19
src/wsSrv/wsReq.erl Dosyayı Görüntüle

@ -1,6 +1,5 @@
-module(wsReq).
-include("eWSrv.hrl").
-include("wsCom.hrl").
-export([
@ -26,9 +25,7 @@
, post_args/1
, post_args_decoded/1
, body_qs/1
, original_headers/1
, headers/1
, peer/1
, method/1
, body/1
, scheme/1
@ -59,7 +56,6 @@ raw_path(#wsReq{path = Path}) -> Path.
%% @doc Return the `headers' that have had `string:casefold/1' run on each key.
headers(#wsReq{headers = Headers}) -> Headers.
%% @doc Return the original `headers'.
original_headers(#wsReq{original_headers = Headers}) -> Headers.
%% @doc Return the `method'.
method(#wsReq{method = Method}) -> Method.
%% @doc Return the `body'.
@ -71,14 +67,6 @@ host(#wsReq{host = Host}) -> Host.
%% @doc Return the `port'.
port(#wsReq{port = Port}) -> Port.
peer(#wsReq{socket = Socket} = _Req) ->
case wsNet:peername(Socket) of
{ok, {Address, _}} ->
list_to_binary(inet_parse:ntoa(Address));
{error, _} ->
undefined
end.
get_header(Key, #wsReq{headers = Headers}) ->
CaseFoldedKey = string:casefold(Key),
proplists:get_value(CaseFoldedKey, Headers).
@ -143,7 +131,7 @@ post_arg_decoded(Key, #wsReq{} = Req, Default) ->
%% @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:wsReq()) -> QueryArgs :: proplists:proplist().
get_args(#wsReq{args = Args}) -> Args.
get_args_decoded(#wsReq{args = Args}) ->
@ -166,7 +154,7 @@ post_args_decoded(#wsReq{} = Req) ->
%% @doc Calculate the query string associated with a given `Request'
%% as a binary.
-spec query_str(elli:req()) -> QueryStr :: binary().
-spec query_str(elli:wsReq()) -> QueryStr :: binary().
query_str(#wsReq{path = Path}) ->
case binary:split(Path, [<<"?">>]) of
[_, Qs] -> Qs;
@ -177,7 +165,7 @@ query_str(#wsReq{path = Path}) ->
%% @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.
-spec get_range(elli:wsReq()) -> [http_range()] | parse_error.
get_range(#wsReq{headers = Headers}) ->
case proplists:get_value(<<"range">>, Headers) of
<<"bytes=", RangeSetBin/binary>> ->
@ -229,17 +217,14 @@ remove_whitespace(Bin) ->
%% @doc Serialize the `Req'uest record to a proplist.
%% Useful for logging.
to_proplist(#wsReq{} = Req) ->
lists:zip(record_info(fields, req), tl(tuple_to_list(Req))).
lists:zip(record_info(fields, wsReq), 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(#wsReq{version = {1, 1}} = Req) ->
Req#wsReq.pid;
chunk_ref(#wsReq{}) ->
{error, not_supported}.
%% @doc Explicitly close the chunked connection.
%% Return `{error, closed}' if the client already closed the connection.
%% @equiv send_chunk(Ref, close)

Yükleniyor…
İptal
Kaydet