diff --git a/include/eWSrv.hrl b/include/eWSrv.hrl index 6304dc9..6239172 100644 --- a/include/eWSrv.hrl +++ b/include/eWSrv.hrl @@ -2,40 +2,46 @@ -export_type([wsOpt/0]). -type wsOpt() :: - listenOpt() | - {wsMod, module()}. + listenOpt() | %% eNet相关配置 + {wsMod, module()} | %% 请求处理的回调模块 + {maxSize, infinity | pos_integer()} | %% 单次请求Content-Length的最大值 默认值 infinity + {chunkedSupp, boolean()}. %% 服务器是否运行客户端发送Transfer-Encoding 默认不运行 主要是为了防攻击 + -record(wsReq, { - method :: method(), + method :: wsMethod(), path :: binary(), - version :: wsHttp:version(), + version :: wsHttp:wsVersion(), scheme :: undefined | binary(), host :: undefined | binary(), port :: undefined | 1..65535, + socket :: inet:socket() | ssl:sslsocket(), args :: [{binary(), any()}], - headers :: headers(), - body = <<>> :: body() + headers :: wsHeaders(), + body = <<>> :: wsBody() }). -export_type([ wsReq/0 - , method/0 - , body/0 - , path/0 - , headers/0 - , httpCode/0 - , version/0 + , wsMethod/0 + , wsBody/0 + , wsPath/0 + , wsHeaders/0 + , wsHttpCode/0 + , wsVersion/0 , header_key/0 + , wsSocket/0 ]). -type wsReq() :: #wsReq{}. --type method() :: 'OPTIONS' | 'GET' | 'HEAD' | 'POST'| 'PUT' | 'DELETE' | 'TRACE' | binary(). --type body() :: binary() | iolist(). --type path() :: binary(). --type header() :: {Key :: binary(), Value :: binary() | string()}. --type headers() :: [header()]. --type httpCode() :: 100..999. --type version() :: {0, 9} | {1, 0} | {1, 1}. +-type wsMethod() :: 'OPTIONS' | 'GET' | 'HEAD' | 'POST'| 'PUT' | 'DELETE' | 'TRACE' | binary(). +-type wsBody() :: binary() | iolist(). +-type wsPath() :: binary(). +-type wsHeader() :: {Key :: binary(), Value :: binary() | string()}. +-type wsHeaders() :: [wsHeader()]. +-type wsHttpCode() :: 100..999. +-type wsVersion() :: {0, 9} | {1, 0} | {1, 1}. +-type wsSocket() :: inet:socket() | ssl:sslsocket(). -define(CONTENT_LENGTH_HEADER, 'Content-Length'). -define(CONNECTION_HEADER, 'Connection'). diff --git a/include/wsCom.hrl b/include/wsCom.hrl index d43b513..696f3fb 100644 --- a/include/wsCom.hrl +++ b/include/wsCom.hrl @@ -27,22 +27,21 @@ -type sendfile_opts() :: [{chunk_size, non_neg_integer()}]. --type stage() :: reqLine | header | body | done. %% 接受http请求可能会有多个包 分四个阶接收 +-type stage() :: reqLine | wsHeader | wsBody | done. %% 接受http请求可能会有多个包 分四个阶接收 -record(wsState, { stage = reqLine :: stage() %% 接收数据阶段 , buffer = <<>> :: binary() %% 缓存接收到的数据 , wsReq :: undefined | #wsReq{} %% 解析后的http , headerCnt = 0 :: pos_integer() %% header计数 - , temHeader = [] :: [headers()] %% 解析header临时数据 + , temHeader = [] :: [wsHeaders()] %% 解析header临时数据 , contentLength :: undefined | non_neg_integer() | chunked %% 长度 - , temChunked = <<>> :: binary() - , method :: method() - , path :: binary() - , rn :: undefined | binary:cp() - , socket :: undefined | wsNet:socket() - , isSsl = false :: boolean() - , wsMod :: module() - , maxSize = infinity :: pos_integer() %% 允许接收的最大长度 - , maxChunkCnt = 0 :: pos_integer() %% 允许接收的最大chunk 数量 - , maxRecvCnt = 0 :: pos_integer() %% 允许允许recv最大的次数 + , temChunked = <<>> :: binary() %% chunked 模式下保存数据 + , method :: wsMethod() %% 请求的method + , path :: binary() %% 请求的URL + , rn :: undefined | binary:cp() %% binary:cp() + , socket :: undefined | inet:socket() | ssl:sslsocket() %% 连接的socket + , isSsl = false :: boolean() %% 是否是ssl + , wsMod :: module() %% 回调请求的模块 + , maxSize = infinity :: pos_integer() %% 单次允许接收的最大长度 + , chunkedSupp = false :: boolean() %% 是否运行 chunked }). diff --git a/src/eWSrv.erl b/src/eWSrv.erl index bbb1f7e..2da13f4 100644 --- a/src/eWSrv.erl +++ b/src/eWSrv.erl @@ -23,7 +23,9 @@ wSrvName(Port) -> openSrv(Port, WsOpts) -> T1WsOpts = lists:keystore(conMod, 1, WsOpts, {conMod, wsHttp}), WsMod = ?wsGLV(wsMod, WsOpts, wsTPHer), - T2WsOpts = lists:keystore(conArgs, 1, T1WsOpts, {conArgs, WsMod}), + MaxSize = ?wsGLV(maxSize, WsOpts, infinity), + ChunkedSupp = ?wsGLV(chunkedSupp, WsOpts, false), + T2WsOpts = lists:keystore(conArgs, 1, T1WsOpts, {conArgs, {WsMod, MaxSize, ChunkedSupp}}), TcpOpts = ?wsGLV(tcpOpts, T2WsOpts, []), NewTcpOpts = wsUtil:mergeOpts(?DefWsOpts, TcpOpts), LWsOpts = lists:keystore(tcpOpts, 1, T2WsOpts, {tcpOpts, NewTcpOpts}), @@ -40,7 +42,9 @@ openSrv(Port, WsOpts) -> openSrv(WSrvName, Port, WsOpts) -> T1WsOpts = lists:keystore(conMod, 1, WsOpts, {conMod, wsHttp}), WsMod = ?wsGLV(wsMod, WsOpts, wsTPHer), - T2WsOpts = lists:keystore(conArgs, 1, T1WsOpts, {conArgs, WsMod}), + MaxSize = ?wsGLV(maxSize, WsOpts, infinity), + ChunkedSupp = ?wsGLV(chunkedSupp, WsOpts, false), + T2WsOpts = lists:keystore(conArgs, 1, T1WsOpts, {conArgs, {WsMod, MaxSize, ChunkedSupp}}), TcpOpts = ?wsGLV(tcpOpts, T2WsOpts, []), NewTcpOpts = wsUtil:mergeOpts(?DefWsOpts, TcpOpts), LWsOpts = lists:keystore(tcpOpts, 1, T2WsOpts, {tcpOpts, NewTcpOpts}), diff --git a/src/wsSrv/wsHer.erl b/src/wsSrv/wsHer.erl index 3f35ccf..f1c16e3 100644 --- a/src/wsSrv/wsHer.erl +++ b/src/wsSrv/wsHer.erl @@ -7,11 +7,11 @@ ]). -type response() :: - {httpCode() | ok, body()}| - {httpCode() | ok, headers(), body()}| - {httpCode() | ok, headers(), {file, file:name_all()}| + {wsHttpCode() | ok, wsBody()}| + {wsHttpCode() | ok, wsHeaders(), wsBody()}| + {wsHttpCode() | ok, wsHeaders(), {file, file:name_all()}| {file, file:name_all(), wsUtil:range()}}| - {chunk, headers()}| - {chunk, headers(), body()}. + {chunk, wsHeaders()}| + {chunk, wsHeaders(), wsBody()}. --callback handle(Method :: method(), Path :: binary(), Req :: wsReq()) -> response(). \ No newline at end of file +-callback handle(Method :: wsMethod(), Path :: binary(), Req :: wsReq()) -> response(). \ No newline at end of file diff --git a/src/wsSrv/wsHttp.erl b/src/wsSrv/wsHttp.erl index bede01a..61431a2 100644 --- a/src/wsSrv/wsHttp.erl +++ b/src/wsSrv/wsHttp.erl @@ -26,13 +26,13 @@ , system_terminate/4 ]). -newConn(_Sock, WsMod) -> - ?MODULE:start_link(WsMod). +newConn(_Sock, ConnArgs) -> + ?MODULE:start_link(ConnArgs). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% genActor start %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -spec(start_link(atom()) -> {ok, pid()} | ignore | {error, term()}). -start_link(WsMod) -> - proc_lib:start_link(?MODULE, init_it, [self(), WsMod], infinity, []). +start_link(ConnArgs) -> + proc_lib:start_link(?MODULE, init_it, [self(), ConnArgs], infinity, []). init_it(Parent, Args) -> process_flag(trap_exit, true), @@ -84,13 +84,12 @@ loop(Parent, State) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% genActor end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% ************************************************ API *************************************************************** -init(WsMod) -> - {ok, #wsState{wsMod = WsMod}}. +init({WsMod, MaxSize, ChunkedSupp}) -> + {ok, #wsState{wsMod = WsMod, maxSize = MaxSize, chunkedSupp = ChunkedSupp}}. handleMsg({tcp, _Socket, Data}, State) -> - ?wsWarn("IMY***************tcp data ~p ~n XXXXXXXXXXXX ~p ~n", [Data, State]), #wsState{stage = Stage, buffer = Buffer, socket = Socket} = State, - case wsHttpProtocol:request(Stage, <>, State) of + case wsHttpProtocol:request(Stage, <>, Socket, State) of {ok, _NewState} = LRet -> LRet; {done, NewState} -> @@ -120,7 +119,7 @@ handleMsg({tcp_error, _Socket, Reason}, _State) -> handleMsg({ssl, _Socket, Data}, State) -> #wsState{stage = Stage, buffer = Buffer, socket = Socket} = State, - case wsHttpProtocol:request(Stage, <>, State) of + case wsHttpProtocol:request(Stage, <>, Socket, State) of {ok, _NewState} = LRet -> LRet; {done, NewState} -> @@ -290,8 +289,8 @@ sendResponse(Socket, Method, Code, Headers, UserBody) -> %% and headers. -spec sendFile(Req, Code, Headers, Filename, Range) -> ok when Req :: elli:wsReq(), - Code :: elli:httpCode(), - Headers :: elli:headers(), + Code :: elli:wsHttpCode(), + Headers :: elli:wsHeaders(), Filename :: file:filename(), Range :: wsUtil:range(). sendFile(Socket, Code, Headers, Filename, Range) -> diff --git a/src/wsSrv/wsHttpProtocol.erl b/src/wsSrv/wsHttpProtocol.erl index 880e857..04ff3cb 100644 --- a/src/wsSrv/wsHttpProtocol.erl +++ b/src/wsSrv/wsHttpProtocol.erl @@ -5,22 +5,21 @@ -compile(inline). -compile({inline_size, 128}). --export([request/3]). +-export([request/4]). --spec request(Stage :: stage(), Data :: binary(), State :: #wsState{}) -> {ok, NewState :: #wsState{}} | {done, NewState :: #wsState{}} | {error, term()}. -request(reqLine, Data, State) -> +-spec request(Stage :: stage(), Data :: binary(), wsSocket(), State :: #wsState{}) -> {ok, NewState :: #wsState{}} | {done, NewState :: #wsState{}} | {error, term()}. +request(reqLine, Data, Socket, State) -> case erlang:decode_packet(http_bin, Data, []) of {more, _} -> {ok, State#wsState{buffer = Data}}; {ok, {http_request, Method, RawPath, Version}, Rest} -> - ?wsErr("IMY**************11111 ~p~n", [{http_request, Method, RawPath, Version}]), case parsePath(RawPath) of {ok, Scheme, Host, Port, Path, URLArgs} -> case Rest of <<>> -> - {ok, State#wsState{stage = header, buffer = <<>>, method = Method, path = Path, wsReq = #wsReq{method = Method, path = Path, version = Version, scheme = Scheme, host = Host, port = Port, args = URLArgs}}}; + {ok, State#wsState{stage = header, buffer = <<>>, method = Method, path = Path, socket = Socket, wsReq = #wsReq{method = Method, path = Path, version = Version, scheme = Scheme, host = Host, port = Port, args = 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}}) + request(header, Rest, Socket, 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}}) end; _Err -> _Err @@ -32,12 +31,12 @@ request(reqLine, Data, State) -> {error, _Reason} = Ret -> Ret end; -request(header, Data, State) -> +request(header, Data, Socket, State) -> case erlang:decode_packet(httph_bin, Data, []) of {more, _} -> {ok, State#wsState{buffer = Data}}; {ok, {http_header, _, Key, _, Value}, Rest} -> - #wsState{headerCnt = HeaderCnt, temHeader = TemHeader, rn = Rn, maxSize = MaxSize} = State, + #wsState{headerCnt = HeaderCnt, temHeader = TemHeader, rn = Rn, maxSize = MaxSize, chunkedSupp = ChunkedSupp} = State, NewTemHeader = [{Key, Value} | TemHeader], NewHeaderCnt = HeaderCnt + 1, case NewHeaderCnt >= 100 of @@ -51,29 +50,28 @@ request(header, Data, State) -> true -> {err_code, 413}; _ -> - request(header, Rest, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = ContentLength}) + request(header, Rest, Socket, 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#wsState{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}) - end; - <<"Chunked">> -> - case Rn of - undefined -> - request(header, Rest, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked, rn = binary:compile_pattern(<<"\r\n">>)}); + IsChunked = ?IIF(Value == <<"chunked">> orelse Value == <<"Chunked">>, true, false), + case IsChunked of + true -> + case ChunkedSupp of + true -> + case Rn of + undefined -> + request(header, Rest, Socket, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked, rn = binary:compile_pattern(<<"\r\n">>)}); + _ -> + request(header, Rest, Socket, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked}) + end; _ -> - request(header, Rest, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked}) + {error, not_support_chunked} end; _ -> {error, 'Transfer-Encoding'} end; _ -> - request(header, Rest, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader}) + request(header, Rest, Socket, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader}) end end; {ok, http_eoh, Rest} -> @@ -89,7 +87,7 @@ request(header, Data, State) -> <<>> -> {ok, State#wsState{stage = body, buffer = <<>>, wsReq = NewWsReq}}; _ -> - request(body, Rest, State#wsState{stage = body, buffer = Rest, wsReq = NewWsReq}) + request(body, Rest, Socket, State#wsState{stage = body, buffer = Rest, wsReq = NewWsReq}) end end; {ok, {http_error, ErrStr}, _Rest} -> @@ -97,7 +95,7 @@ request(header, Data, State) -> {error, _Reason} = Ret -> Ret end; -request(body, Data, State) -> +request(body, Data, _Socket, State) -> #wsState{contentLength = CLen, wsReq = WsReq, temChunked = TemChunked, rn = Rn} = State, case CLen of chunked -> diff --git a/src/wsSrv/wsTPHer.erl b/src/wsSrv/wsTPHer.erl index 251e4ad..cbed437 100644 --- a/src/wsSrv/wsTPHer.erl +++ b/src/wsSrv/wsTPHer.erl @@ -13,7 +13,7 @@ chunk_loop/1 ]). --spec handle(Method :: method(), Path :: path(), WsReq :: wsReq()) -> wsHer:response(). +-spec handle(Method :: wsMethod(), Path :: wsPath(), WsReq :: wsReq()) -> wsHer:response(). handle('GET', <<"/hello/world">>, WsReq) -> io:format("IMY************XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX ~n receive WsReq: ~p~n", [WsReq]), %% Reply with a normal response.