瀏覽代碼

ft: 代码修改

master
SisMaker 3 年之前
父節點
當前提交
64d93271aa
共有 7 個文件被更改,包括 81 次插入460 次删除
  1. +2
    -5
      include/eWSrv.hrl
  2. +7
    -5
      include/wsCom.hrl
  3. +3
    -3
      src/test/elli_tests.erl
  4. +20
    -423
      src/wsSrv/wsHttp.erl
  5. +46
    -21
      src/wsSrv/wsHttpProtocol.erl
  6. +1
    -1
      src/wsSrv/wsHttps.erl
  7. +2
    -2
      src/wsSrv/wsReq.erl

+ 2
- 5
include/eWSrv.hrl 查看文件

@ -25,17 +25,14 @@
-record(wsReq, {
method :: method(),
raw_path :: binary(),
path :: binary(),
version :: wsHttp:version(),
scheme :: undefined | binary(),
host :: undefined | binary(),
port :: undefined | 1..65535,
path :: [binary()],
args :: [{binary(), any()}],
headers :: headers(),
original_headers :: headers(),
body = <<>> :: body(),
pid :: pid()
body = <<>> :: body()
}).
-export_type([req/0, method/0, body/0, headers/0, response_code/0]).

+ 7
- 5
include/wsCom.hrl 查看文件

@ -11,7 +11,7 @@
-type stage() :: reqLine | header | body | done. %% http请求可能会有多个包
-record(state, {
-record(wsState, {
stage = reqLine :: stage() %%
, buffer = <<>> :: binary() %%
, wsReq :: undefined | #wsReq{} %% http
@ -19,11 +19,13 @@
, temHeader = [] :: [headers()] %% header临时数据
, contentLength :: undefined | non_neg_integer() | chunked %%
, temChunked = <<>> :: binary()
, method :: method()
, path :: binary()
, rn :: undefined | binary:cp()
, wsMod :: callback()
, socket :: undefined | wsNet:socket()
, isSsl = false :: boolean()
, maxSize %%
, maxChunkCnt %% chunk
, maxRecvCnt %% recv最大的次数
, wsMod :: callback()
, maxSize = 0 :: pos_integer() %%
, maxChunkCnt = 0 :: pos_integer() %% chunk
, maxRecvCnt = 0 :: pos_integer() %% recv最大的次数
}).

+ 3
- 3
src/test/elli_tests.erl 查看文件

@ -111,7 +111,7 @@ accessors_test_() ->
Method = 'POST',
Body = <<"name=knut%3D">>,
Name = <<"knut=">>,
Req1 = #wsReq{raw_path = RawPath,
Req1 = #wsReq{path = RawPath,
original_headers = Headers,
headers = Headers,
method = Method,
@ -636,7 +636,7 @@ to_proplist_test() ->
path = [<<"crash">>],
args = [],
version = {1, 1},
raw_path = <<"/crash">>,
path = <<"/crash">>,
original_headers = [{<<"Host">>, <<"localhost:3001">>}],
headers = [{<<"host">>, <<"localhost:3001">>}],
body = <<>>,
@ -666,7 +666,7 @@ is_request_test() ->
query_str_test_() ->
MakeReq = fun(Path) -> #wsReq{raw_path = Path} end,
MakeReq = fun(Path) -> #wsReq{path = Path} end,
[
%% For empty query strings, expect `query_str` to return an empty binary.
?_assertMatch(<<>>, wsReq:query_str(MakeReq(<<"/foo">>))),

+ 20
- 423
src/wsSrv/wsHttp.erl 查看文件

@ -10,15 +10,11 @@
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
@ -47,7 +43,7 @@ start_link(WsMod) ->
%% ******************************************** callback **************************************************************
init(WsMod) ->
{ok, #state{wsMod = WsMod}}.
{ok, #wsState{wsMod = WsMod}}.
handleCall(_Msg, _State, _FROM) ->
?wsErr("~p call receive unexpect msg ~p ~n ", [?MODULE, _Msg]),
@ -59,12 +55,19 @@ handleCast(_Msg, _State) ->
kpS.
handleInfo({tcp, _Socket, Data}, State) ->
#state{stage = Stage, buffer = Buffer, socket = Socket} = State,
#wsState{stage = Stage, buffer = Buffer, socket = Socket} = State,
case wsHttpProtocol:request(Stage, <<Buffer/binary, Data/binary>>, State) of
{ok, NewState} ->
{noreply, NewState};
{done, NewState} ->
ok;
Response = doHandle(NewState),
case handle_response(Req1, B2, Response) of
{keep_alive, NewBuffer} ->
handleInfo({tcp, _Socket, NewBuffer}, NewState);
{close, _} ->
wsNet:close(Socket),
ok
end;
Err ->
?wsErr("recv the http data error ~p~n", [Err]),
send_bad_request(Socket),
@ -79,7 +82,7 @@ handleInfo({tcp_error, Socket, Reason}, _State) ->
kpS;
handleInfo({ssl, Socket, Data}, State) ->
#state{stage = Stage, buffer = Buffer, socket = Socket} = State,
#wsState{stage = Stage, buffer = Buffer, socket = Socket} = State,
case wsHttpProtocol:request(Stage, <<Buffer/binary, Data/binary>>, State) of
{ok, NewState} ->
{noreply, NewState};
@ -99,12 +102,12 @@ handleInfo({ssl_error, Socket, Reason}, _State) ->
kpS;
handleInfo({?mSockReady, Sock}, _State) ->
inet:setopts(Sock, [{packet, raw}, {active, true}]),
{ok, #state{socket = Sock}};
{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#state{socket = SslSock, isSsl = true}};
{ok, State#wsState{socket = SslSock, isSsl = true}};
_Err ->
?wsErr("ssl handshake error ~p~n", [_Err]),
{stop, _Err, State}
@ -119,32 +122,6 @@ terminate(_Reason, _State) ->
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% 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).
@ -158,46 +135,6 @@ keepalive_loop(Socket, NumRequests, Buffer, Options, Callback) ->
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,
@ -213,8 +150,6 @@ handle_response(Req, Buffer, {response, Code, UserHeaders, Body}) ->
[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,
@ -237,8 +172,6 @@ handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) ->
get_sizes()}],
Args),
{close, <<>>};
handle_response(Req, Buffer, {file, ResponseCode, UserHeaders,
Filename, Range}) ->
#wsReq{callback = {Mod, Args}} = Req,
@ -282,7 +215,6 @@ handle_response(Req, Buffer, {file, ResponseCode, UserHeaders,
{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),
@ -358,8 +290,9 @@ send_rescue_response(Socket, 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
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, []};
@ -381,19 +314,19 @@ execute_callback(#wsReq{callback = {Mod, Args}} = Req) ->
{HttpCode, Body} -> {response, HttpCode, [], Body};
%% Unexpected
Unexpected ->
handle_event(Mod, invalid_return, [Req, Unexpected], Args),
?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 ->
handle_event(Mod, request_throw, [Req, Exc, Stacktrace], Args),
?wsErr("handle catch throw ~p ~p ~p~n", [WsReq, Exc, Stacktrace]),
{response, 500, [], <<"Internal server error">>};
error:Error:Stacktrace ->
handle_event(Mod, request_error, [Req, Error, Stacktrace], Args),
?wsErr("handle catch error ~p ~p ~p~n", [WsReq, Error, Stacktrace]),
{response, 500, [], <<"Internal server error">>};
exit:Exit:Stacktrace ->
handle_event(Mod, request_exit, [Req, Exit, Stacktrace], Args),
?wsErr("handle catch exit ~p ~p ~p~n", [WsReq, Exit, Stacktrace]),
{response, 500, [], <<"Internal server error">>}
end.
@ -466,159 +399,6 @@ send_chunk(Socket, Data) ->
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
@ -631,62 +411,6 @@ maybe_send_continue(Socket, Headers) ->
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, raw_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, <<>>).
@ -764,7 +488,6 @@ is_header_defined(Key, Headers) ->
get_header(Key, Headers) ->
get_header(Key, Headers, undefined).
-ifdef(OTP_RELEASE).
get_header(Key, Headers, Default) ->
CaseFoldedKey = string:casefold(Key),
case lists:search(fun({N, _}) -> string:equal(CaseFoldedKey, N, true) end, Headers) of
@ -773,72 +496,6 @@ get_header(Key, Headers, Default) ->
false ->
Default
end.
-else.
get_header(Key, Headers, Default) ->
CaseFoldedKey = string:casefold(Key),
case search(fun({N, _}) -> string:equal(CaseFoldedKey, N, true) end, Headers) of
{value, {_, Value}} ->
Value;
false ->
Default
end.
search(Pred, [Hd | Tail]) ->
case Pred(Hd) of
true -> {value, Hd};
false -> search(Pred, Tail)
end;
search(Pred, []) when is_function(Pred, 1) ->
false.
-endif.
%%
%% PATH HELPERS
%%
-ifdef(OTP_RELEASE).
- if (?OTP_RELEASE >= 22).
parse_path({abs_path, FullPath}) ->
URIMap = uri_string:parse(FullPath),
Host = maps:get(host, URIMap, undefined),
Scheme = maps:get(scheme, URIMap, undefined),
Path = maps:get(path, URIMap, <<>>),
Query = maps:get(query, URIMap, <<>>),
Port = maps:get(port, URIMap, case Scheme of http -> 80; https -> 443; _ -> undefined end),
{ok, {Scheme, Host, Port}, {Path, split_path(Path), uri_string:dissect_query(Query)}};
parse_path({absoluteURI, Scheme, Host, Port, Path}) ->
setelement(2, parse_path({abs_path, Path}), {Scheme, Host, Port});
parse_path(_) ->
{error, unsupported_uri}.
-else.
parse_path({abs_path, FullPath}) ->
Parsed = case binary:split(FullPath, [<<"?">>]) of
[URL] -> {FullPath, split_path(URL), []};
[URL, Args] -> {FullPath, split_path(URL), split_args(Args)}
end,
{ok, {undefined, undefined, undefined}, Parsed};
parse_path({absoluteURI, Scheme, Host, Port, Path}) ->
setelement(2, parse_path({abs_path, Path}), {Scheme, Host, Port});
parse_path(_) ->
{error, unsupported_uri}.
-endif.
-else.
%% same as else branch above. can drop this when only OTP 21+ is supported
parse_path({abs_path, FullPath}) ->
Parsed = case binary:split(FullPath, [<<"?">>]) of
[URL] -> {FullPath, split_path(URL), []};
[URL, Args] -> {FullPath, split_path(URL), split_args(Args)}
end,
{ok, {undefined, undefined, undefined}, Parsed};
parse_path({absoluteURI, Scheme, Host, Port, Path}) ->
setelement(2, parse_path({abs_path, Path}), {Scheme, Host, Port});
parse_path(_) ->
{error, unsupported_uri}.
-endif.
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'.
@ -853,17 +510,7 @@ split_args(Qs) ->
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
@ -873,26 +520,6 @@ handle_event(Mod, Name, EventArgs, ElliArgs) ->
?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
@ -918,18 +545,6 @@ get_sizes({{size, 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
%%
@ -994,21 +609,3 @@ 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.

+ 46
- 21
src/wsSrv/wsHttpProtocol.erl 查看文件

@ -10,17 +10,22 @@
request/3
]).
-spec request(Stage :: stage(), Data :: binary(), State :: #state{}) -> {ok, NewState :: #state{}} | {done, NewState :: #state{}} | {error, term()}.
-spec request(Stage :: stage(), Data :: binary(), State :: #wsState{}) -> {ok, NewState :: #wsState{}} | {done, NewState :: #wsState{}} | {error, term()}.
request(reqLine, Data, State) ->
case erlang:decode_packet(http_bin, Data, []) of
{more, _} ->
{ok, State#state{buffer = Data}};
{ok, State#wsState{buffer = Data}};
{ok, {http_request, Method, RawPath, Version}, Rest} ->
case byte_size(Rest) > 0 of
true ->
request(header, Rest, State#state{stage = header, buffer = Rest, wsReq = #wsReq{method = Method, raw_path = RawPath, version = Version}, headerCnt = 0});
case parsePath(RawPath) of
{ok, Scheme, Host, Port, Path, URLArgs} ->
request(header, Rest, State#wsState{stage = header, buffer = Rest, method = Method, path = Path, wsReq = #wsReq{method = Method, path = Path, version = Version, scheme = Scheme, host = Host, port = Port, args = URLArgs}, headerCnt = 0});
_Err ->
_Err
end;
_ ->
{ok, State#state{stage = header, buffer = <<>>, wsReq = #wsReq{method = Method, raw_path = RawPath, version = Version}, headerCnt = 0}}
{ok, State#wsState{stage = header, buffer = <<>>, wsReq = #wsReq{method = Method, path = RawPath, version = Version}, headerCnt = 0}}
end;
{ok, {http_error, ErrStr}, _} ->
{error, ErrStr};
@ -32,9 +37,9 @@ request(reqLine, Data, State) ->
request(header, Data, State) ->
case erlang:decode_packet(httph_bin, Data, []) of
{more, _} ->
{ok, State#state{buffer = Data}};
{ok, State#wsState{buffer = Data}};
{ok, {http_header, _, Key, _, Value}, Rest} ->
#state{headerCnt = HeaderCnt, temHeader = TemHeader, rn = Rn} = State,
#wsState{headerCnt = HeaderCnt, temHeader = TemHeader, rn = Rn, maxSize = MaxSize} = State,
NewTemHeader = [{Key, Value} | TemHeader],
NewHeaderCnt = HeaderCnt + 1,
case NewHeaderCnt >= 100 of
@ -43,37 +48,43 @@ request(header, Data, State) ->
_ ->
case Key of
'Content-Length' ->
request(header, Rest, State#state{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = binary_to_integer(Value)});
ContentLength = binary_to_integer(Value),
case ContentLength > MaxSize of
true ->
{err_code, 413};
_ ->
request(header, Rest, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = ContentLength})
end;
'Transfer-Encoding' ->
case Value of
<<"chunked">> ->
case Rn of
undefined ->
request(header, Rest, State#state{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked, rn = binary:compile_pattern(<<"\r\n">>)});
request(header, Rest, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked, rn = binary:compile_pattern(<<"\r\n">>)});
_ ->
request(header, Rest, State#state{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked})
request(header, Rest, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked})
end;
_ ->
{error, 'Transfer-Encoding'}
end;
_ ->
request(header, Rest, State#state{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader})
request(header, Rest, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader})
end
end;
{ok, http_eoh, Rest} ->
#state{temHeader = TemHeader, contentLength = CLen, wsReq = WsReq} = State,
#wsState{temHeader = TemHeader, contentLength = CLen, wsReq = WsReq} = State,
NewWsReq = WsReq#wsReq{headers = TemHeader},
case CLen of
undefined ->
{error, content_length};
0 ->
{done, State#state{buffer = Rest, wsReq = NewWsReq, temHeader = []}};
{done, State#wsState{buffer = Rest, wsReq = NewWsReq, temHeader = []}};
_ ->
case byte_size(Rest) > 0 of
true ->
request(body, Rest, State#state{stage = body, buffer = Rest, wsReq = NewWsReq, temHeader = []});
request(body, Rest, State#wsState{stage = body, buffer = Rest, wsReq = NewWsReq, temHeader = []});
_ ->
{ok, State#state{stage = body, buffer = <<>>, wsReq = NewWsReq, temHeader = []}}
{ok, State#wsState{stage = body, buffer = <<>>, wsReq = NewWsReq, temHeader = []}}
end
end;
{ok, {http_error, ErrStr}, _Rest} ->
@ -82,14 +93,14 @@ request(header, Data, State) ->
Ret
end;
request(body, Data, State) ->
#state{contentLength = CLen, wsReq = WsReq, temChunked = TemChunked, rn = Rn} = State,
#wsState{contentLength = CLen, wsReq = WsReq, temChunked = TemChunked, rn = Rn} = State,
case CLen of
chunked ->
case parseChunks(Data, Rn, TemChunked) of
{ok, NewTemChunked, Rest} ->
{ok, State#state{buffer = Rest, temChunked = NewTemChunked}};
{ok, State#wsState{buffer = Rest, temChunked = NewTemChunked}};
{over, LastTemChunked, Rest} ->
{done, State#state{buffer = Rest, temChunked = <<>>, wsReq = WsReq#wsReq{body = LastTemChunked}}};
{done, State#wsState{buffer = Rest, temChunked = <<>>, wsReq = WsReq#wsReq{body = LastTemChunked}}};
{error, _Reason} = Ret ->
Ret
end;
@ -97,12 +108,12 @@ request(body, Data, State) ->
BodySize = erlang:size(Data),
if
BodySize == CLen ->
{done, State#state{buffer = <<>>, wsReq = WsReq#wsReq{body = Data}}};
{done, State#wsState{buffer = <<>>, wsReq = WsReq#wsReq{body = Data}}};
BodySize > CLen ->
<<Body:CLen/binary, Rest/binary>> = Data,
{done, State#state{buffer = Rest, wsReq = WsReq#wsReq{body = Body}}};
{done, State#wsState{buffer = Rest, wsReq = WsReq#wsReq{body = Body}}};
true ->
{ok, State#state{buffer = Data}}
{ok, State#wsState{buffer = Data}}
end
end.
@ -138,4 +149,18 @@ chunkSize(Bin) ->
catch
error:badarg ->
undefined
end.
end.
parsePath({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, uri_string:dissect_query(Query)};
parsePath({absoluteURI, Scheme, Host, Port, Path}) ->
{_, _Scheme, _Host, _Port, RetPath, RetQuery} = parsePath({abs_path, Path}),
{ok, Scheme, Host, Port, RetPath, RetQuery};
parsePath(_) ->
{error, unsupported_uri}.

+ 1
- 1
src/wsSrv/wsHttps.erl 查看文件

@ -600,7 +600,7 @@ mk_req(Method, PathTuple, Headers, ParsedHeaders, Body, V, Socket, {Mod, Args} =
{ok, {Scheme, Host, Port}, {Path, URL, URLArgs}} ->
#wsReq{method = Method, scheme = Scheme, host = Host,
port = Port, path = URL, args = URLArgs,
version = V, raw_path = Path, original_headers = Headers,
version = V, path = Path, original_headers = Headers,
body = Body, pid = self(), socket = Socket,
callback = Callback, headers = ParsedHeaders};
{error, Reason} ->

+ 2
- 2
src/wsSrv/wsReq.erl 查看文件

@ -55,7 +55,7 @@
%% @doc Return `path' split into binary parts.
path(#wsReq{path = Path}) -> Path.
%% @doc Return the `raw_path', i.e. not split or parsed for query params.
raw_path(#wsReq{raw_path = Path}) -> Path.
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'.
@ -167,7 +167,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().
query_str(#wsReq{raw_path = Path}) ->
query_str(#wsReq{path = Path}) ->
case binary:split(Path, [<<"?">>]) of
[_, Qs] -> Qs;
[_] -> <<>>

Loading…
取消
儲存