|
|
@ -1,51 +1,128 @@ |
|
|
|
%% @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(wsHttp). |
|
|
|
|
|
|
|
-behavior(gen_srv). |
|
|
|
|
|
|
|
-include_lib("eNet/include/eNet.hrl"). |
|
|
|
-include("eWSrv.hrl"). |
|
|
|
-include("wsCom.hrl"). |
|
|
|
|
|
|
|
-export([ |
|
|
|
start_link/1 |
|
|
|
, send_response/4 |
|
|
|
, send_file/5 |
|
|
|
, mk_req/8 |
|
|
|
, mk_req/11 |
|
|
|
|
|
|
|
%% Exported for looping with a fully-qualified module name |
|
|
|
, accept/4 |
|
|
|
, handle_request/4 |
|
|
|
, chunk_loop/1 |
|
|
|
, split_args/1 |
|
|
|
, parse_path/1 |
|
|
|
, keepalive_loop/3 |
|
|
|
, keepalive_loop/5 |
|
|
|
, close_or_keepalive/2 |
|
|
|
]). |
|
|
|
|
|
|
|
%% eNet callback |
|
|
|
-export([newCon/2]). |
|
|
|
|
|
|
|
-export([ |
|
|
|
init/1 |
|
|
|
, handleCall/3 |
|
|
|
, handleCast/2 |
|
|
|
, handleInfo/2 |
|
|
|
, terminate/2 |
|
|
|
, code_change/3 |
|
|
|
]). |
|
|
|
|
|
|
|
-define(SERVER, ?MODULE). |
|
|
|
|
|
|
|
-record(state, { |
|
|
|
stage = reqLine :: reqLine | header | body | done | error %% 接受http请求可能会有多个包 分四个阶接收 |
|
|
|
, buffer = <<>> :: binary() |
|
|
|
, wsReq :: undefined | #wsReq{} |
|
|
|
, contentLength :: undefined | non_neg_integer() | chunked |
|
|
|
, wsMod :: callback() |
|
|
|
, socket :: undefined | wsNet:socket() |
|
|
|
, isSsl = false :: boolean() |
|
|
|
}). |
|
|
|
|
|
|
|
newCon(_Sock, SupPid) -> |
|
|
|
supervisor:start_link(SupPid, []). |
|
|
|
|
|
|
|
%% ******************************************** API ******************************************************************* |
|
|
|
start_link(WsMod) -> |
|
|
|
gen_srv:start_link(?MODULE, WsMod, []). |
|
|
|
|
|
|
|
%% ******************************************** callback ************************************************************** |
|
|
|
init(WsMod) -> |
|
|
|
{ok, #state{wsMod = WsMod}}. |
|
|
|
|
|
|
|
handleCall(_Msg, _State, _FROM) -> |
|
|
|
?wsErr("~p call receive unexpect msg ~p ~n ", [?MODULE, _Msg]), |
|
|
|
{reply, ok}. |
|
|
|
|
|
|
|
%% 默认匹配 |
|
|
|
handleCast(_Msg, _State) -> |
|
|
|
?wsErr("~p cast receive unexpect msg ~p ~n ", [?MODULE, _Msg]), |
|
|
|
kpS. |
|
|
|
|
|
|
|
handleInfo({tcp, Socket, Data}, #state{stage = Stage, buffer = Buffer} = State) -> |
|
|
|
ok; |
|
|
|
|
|
|
|
handleInfo({tcp_closed, Socket}, _State) -> |
|
|
|
ok; |
|
|
|
|
|
|
|
handleInfo({tcp_error, Socket, Reason}, _State) -> |
|
|
|
ok; |
|
|
|
|
|
|
|
handleInfo({ssl, Socket, Data}, _State) -> |
|
|
|
ok; |
|
|
|
|
|
|
|
handleInfo({ssl_closed, Socket}, _State) -> |
|
|
|
ok; |
|
|
|
|
|
|
|
handleInfo({ssl_error, Socket, Reason}, _State) -> |
|
|
|
ok; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
handleInfo({ssl, Socket, Data}, _State) -> |
|
|
|
ok; |
|
|
|
handleInfo({?mSockReady, Sock}, _State) -> |
|
|
|
inet:setopts(Sock, [{packet, raw}, {active, true}]), |
|
|
|
{ok, #state{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#state{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. |
|
|
|
|
|
|
|
terminate(_Reason, _State) -> |
|
|
|
ok. |
|
|
|
|
|
|
|
code_change(_OldVsn, State, _Extra) -> |
|
|
|
{ok, State}. |
|
|
|
|
|
|
|
%% 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]). |
|
|
|
|
|
|
|
-export_type([version/0]). |
|
|
|
|
|
|
|
%% @type version(). HTTP version as a tuple, i.e. `{0, 9} | {1, 0} | {1, 1}'. |
|
|
|
-type version() :: {0, 9} | {1, 0} | {1, 1}. |
|
|
|
|
|
|
|
|
|
|
|
-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. |
|
|
@ -108,7 +185,7 @@ handle_request(S, PrevB, Opts, {Mod, Args} = Callback) -> |
|
|
|
t(body_start), |
|
|
|
{RequestBody, B2} = get_body(S, ParsedRequestHeaders, B1, Opts, Callback), |
|
|
|
t(body_end), |
|
|
|
Req1 = Req#req{body = RequestBody}, |
|
|
|
Req1 = Req#wsReq{body = RequestBody}, |
|
|
|
|
|
|
|
t(user_start), |
|
|
|
Response = execute_callback(Req1), |
|
|
@ -116,7 +193,7 @@ handle_request(S, PrevB, Opts, {Mod, Args} = Callback) -> |
|
|
|
|
|
|
|
handle_response(Req1, B2, Response); |
|
|
|
{ok, handover} -> |
|
|
|
Req1 = Req#req{body = B1}, |
|
|
|
Req1 = Req#wsReq{body = B1}, |
|
|
|
|
|
|
|
t(user_start), |
|
|
|
Response = Mod:handle(Req1, Args), |
|
|
@ -128,7 +205,7 @@ handle_request(S, PrevB, Opts, {Mod, Args} = Callback) -> |
|
|
|
end. |
|
|
|
|
|
|
|
handle_response(Req, Buffer, {response, Code, UserHeaders, Body}) -> |
|
|
|
#req{callback = {Mod, Args}} = Req, |
|
|
|
#wsReq{callback = {Mod, Args}} = Req, |
|
|
|
|
|
|
|
Headers = [connection(Req, UserHeaders), |
|
|
|
content_length(UserHeaders, Body) |
|
|
@ -145,7 +222,7 @@ handle_response(Req, Buffer, {response, Code, UserHeaders, Body}) -> |
|
|
|
|
|
|
|
|
|
|
|
handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) -> |
|
|
|
#req{callback = {Mod, Args}} = Req, |
|
|
|
#wsReq{callback = {Mod, Args}} = Req, |
|
|
|
|
|
|
|
ResponseHeaders = [{?TRANSFER_ENCODING_HEADER, <<"chunked">>}, |
|
|
|
connection(Req, UserHeaders) |
|
|
@ -153,8 +230,8 @@ handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) -> |
|
|
|
send_response(Req, 200, ResponseHeaders, <<"">>), |
|
|
|
|
|
|
|
t(send_start), |
|
|
|
Initial =:= <<"">> orelse send_chunk(Req#req.socket, Initial), |
|
|
|
ClosingEnd = case start_chunk_loop(Req#req.socket) of |
|
|
|
Initial =:= <<"">> orelse send_chunk(Req#wsReq.socket, Initial), |
|
|
|
ClosingEnd = case start_chunk_loop(Req#wsReq.socket) of |
|
|
|
{error, client_closed} -> client; |
|
|
|
ok -> server |
|
|
|
end, |
|
|
@ -170,15 +247,15 @@ handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) -> |
|
|
|
|
|
|
|
handle_response(Req, Buffer, {file, ResponseCode, UserHeaders, |
|
|
|
Filename, Range}) -> |
|
|
|
#req{callback = {Mod, Args}} = Req, |
|
|
|
#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#req.socket), |
|
|
|
wsNet:close(Req#req.socket), |
|
|
|
send_server_error(Req#wsReq.socket), |
|
|
|
wsNet:close(Req#wsReq.socket), |
|
|
|
exit(normal); |
|
|
|
Size -> |
|
|
|
t(send_start), |
|
|
@ -206,11 +283,7 @@ handle_response(Req, Buffer, {file, ResponseCode, UserHeaders, |
|
|
|
t(send_end), |
|
|
|
|
|
|
|
t(request_end), |
|
|
|
handle_event(Mod, request_complete, |
|
|
|
[Req, ResponseCode, ResponseHeaders, <<>>, |
|
|
|
{get_timings(), |
|
|
|
get_sizes()}], |
|
|
|
Args), |
|
|
|
handle_event(Mod, request_complete, [Req, ResponseCode, ResponseHeaders, <<>>, {get_timings(), get_sizes()}], Args), |
|
|
|
|
|
|
|
{close_or_keepalive(Req, UserHeaders), Buffer} |
|
|
|
end. |
|
|
@ -220,7 +293,7 @@ handle_response(Req, Buffer, {file, ResponseCode, UserHeaders, |
|
|
|
send_response(Req, Code, Headers, UserBody) -> |
|
|
|
ResponseHeaders = assemble_response_headers(Code, Headers), |
|
|
|
|
|
|
|
Body = case {Req#req.method, Code} of |
|
|
|
Body = case {Req#wsReq.method, Code} of |
|
|
|
{'HEAD', _} -> <<>>; |
|
|
|
{_, 304} -> <<>>; |
|
|
|
{_, 204} -> <<>>; |
|
|
@ -231,10 +304,10 @@ send_response(Req, Code, Headers, UserBody) -> |
|
|
|
Response = [ResponseHeaders, |
|
|
|
Body], |
|
|
|
|
|
|
|
case wsNet:send(Req#req.socket, Response) of |
|
|
|
case wsNet:send(Req#wsReq.socket, Response) of |
|
|
|
ok -> ok; |
|
|
|
{error, Closed} when Closed =:= closed orelse Closed =:= enotconn -> |
|
|
|
#req{callback = {Mod, Args}} = Req, |
|
|
|
#wsReq{callback = {Mod, Args}} = Req, |
|
|
|
handle_event(Mod, client_closed, [before_response], Args), |
|
|
|
ok |
|
|
|
end. |
|
|
@ -248,23 +321,23 @@ send_response(Req, Code, Headers, UserBody) -> |
|
|
|
Headers :: elli:headers(), |
|
|
|
Filename :: file:filename(), |
|
|
|
Range :: wsUtil:range(). |
|
|
|
send_file(#req{callback = {Mod, Args}} = Req, Code, Headers, Filename, 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#req.socket), |
|
|
|
wsNet:close(Req#req.socket), |
|
|
|
send_server_error(Req#wsReq.socket), |
|
|
|
wsNet:close(Req#wsReq.socket), |
|
|
|
exit(normal) |
|
|
|
end, |
|
|
|
ok. |
|
|
|
|
|
|
|
do_send_file(Fd, {Offset, Length}, #req{callback = {Mod, Args}} = Req, Headers) -> |
|
|
|
try wsNet:send(Req#req.socket, Headers) of |
|
|
|
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#req.socket, Offset, Length, []) of |
|
|
|
case wsNet:sendfile(Fd, Req#wsReq.socket, Offset, Length, []) of |
|
|
|
{ok, BytesSent} -> s(file, BytesSent), ok; |
|
|
|
{error, Closed} when Closed =:= closed orelse |
|
|
|
Closed =:= enotconn -> |
|
|
@ -291,7 +364,7 @@ send_rescue_response(Socket, Code, Body) -> |
|
|
|
wsNet:send(Socket, Response). |
|
|
|
|
|
|
|
%% @doc Execute the user callback, translating failure into a proper response. |
|
|
|
execute_callback(#req{callback = {Mod, Args}} = Req) -> |
|
|
|
execute_callback(#wsReq{callback = {Mod, Args}} = Req) -> |
|
|
|
try Mod:handle(Req, Args) of |
|
|
|
%% {ok,...{file,...}} |
|
|
|
{ok, Headers, {file, Filename}} -> |
|
|
@ -588,7 +661,7 @@ do_check_max_size_x2(Socket, ContentLength, Buffer, MaxSize) |
|
|
|
do_check_max_size_x2(_, _, _, _) -> ok. |
|
|
|
|
|
|
|
-spec mk_req(Method, PathTuple, Headers, Headers, Body, V, Socket, Callback) -> Req when |
|
|
|
Method :: elli:http_method(), |
|
|
|
Method :: elli:method(), |
|
|
|
PathTuple :: {PathType :: atom(), RawPath :: binary()}, |
|
|
|
Headers :: elli:headers(), |
|
|
|
Body :: elli:body(), |
|
|
@ -599,7 +672,7 @@ do_check_max_size_x2(_, _, _, _) -> ok. |
|
|
|
mk_req(Method, PathTuple, Headers, ParsedHeaders, Body, V, Socket, {Mod, Args} = Callback) -> |
|
|
|
case parse_path(PathTuple) of |
|
|
|
{ok, {Scheme, Host, Port}, {Path, URL, URLArgs}} -> |
|
|
|
#req{method = Method, scheme = Scheme, host = Host, |
|
|
|
#wsReq{method = Method, scheme = Scheme, host = Host, |
|
|
|
port = Port, path = URL, args = URLArgs, |
|
|
|
version = V, raw_path = Path, original_headers = Headers, |
|
|
|
body = Body, pid = self(), socket = Socket, |
|
|
@ -614,7 +687,7 @@ mk_req(Method, PathTuple, Headers, ParsedHeaders, Body, V, Socket, {Mod, Args} = |
|
|
|
|
|
|
|
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#req{scheme = Scheme, host = Host, port = Port}. |
|
|
|
Req#wsReq{scheme = Scheme, host = Host, port = Port}. |
|
|
|
|
|
|
|
%% |
|
|
|
%% HEADERS |
|
|
@ -649,18 +722,18 @@ 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(#req{version = {1, 1}, headers = Headers}) -> |
|
|
|
connection_token(#wsReq{version = {1, 1}, headers = Headers}) -> |
|
|
|
case get_header(?CONNECTION_HEADER, Headers) of |
|
|
|
<<"close">> -> <<"close">>; |
|
|
|
<<"Close">> -> <<"close">>; |
|
|
|
_ -> <<"Keep-Alive">> |
|
|
|
end; |
|
|
|
connection_token(#req{version = {1, 0}, headers = Headers}) -> |
|
|
|
connection_token(#wsReq{version = {1, 0}, headers = Headers}) -> |
|
|
|
case get_header(?CONNECTION_HEADER, Headers) of |
|
|
|
<<"Keep-Alive">> -> <<"Keep-Alive">>; |
|
|
|
_ -> <<"close">> |
|
|
|
end; |
|
|
|
connection_token(#req{version = {0, 9}}) -> |
|
|
|
connection_token(#wsReq{version = {0, 9}}) -> |
|
|
|
<<"close">>. |
|
|
|
|
|
|
|
%% @doc Return the preferred session handling setting to close or keep the |
|
|
@ -790,7 +863,7 @@ split_args(Qs) -> |
|
|
|
%% CALLBACK HELPERS |
|
|
|
%% |
|
|
|
|
|
|
|
init(#req{callback = {Mod, Args}} = Req) -> |
|
|
|
init(#wsReq{callback = {Mod, Args}} = Req) -> |
|
|
|
?IIF(erlang:function_exported(Mod, init, 2), |
|
|
|
case Mod:init(Req, Args) of |
|
|
|
ignore -> {ok, standard}; |
|
|
@ -803,8 +876,7 @@ handle_event(Mod, Name, EventArgs, ElliArgs) -> |
|
|
|
Mod:handle_event(Name, EventArgs, ElliArgs) |
|
|
|
catch |
|
|
|
EvClass:EvError:Stacktrace -> |
|
|
|
?wsErr("~p:handle_event/3 crashed ~p:~p~n~p", |
|
|
|
[Mod, EvClass, EvError, Stacktrace]) |
|
|
|
?wsErr("~p:handle_event/3 crashed ~p:~p~n~p", [Mod, EvClass, EvError, Stacktrace]) |
|
|
|
end. |
|
|
|
|
|
|
|
%% |
|
|
|