From 7ae8b21dac0a3767b126e29d9e98d99568395adb Mon Sep 17 00:00:00 2001 From: SisMaker <156736github> Date: Tue, 18 Jan 2022 00:16:44 +0800 Subject: [PATCH] =?UTF-8?q?ft:=20=E4=BB=A3=E7=A0=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/eWSrv.hrl | 6 +- include/wsCom.hrl | 22 +++ src/example/wsEgHer.erl | 1 - src/test/elli_tests.erl | 1 - src/wsSrv/elli.erl | 1 - src/wsSrv/wsHttp.erl | 72 +++++----- src/wsSrv/wsHttpProtocol.erl | 251 ++++++++++++----------------------- src/wsSrv/wsHttps.erl | 1 - src/wsSrv/wsUtil.erl | 2 +- 9 files changed, 147 insertions(+), 210 deletions(-) diff --git a/include/eWSrv.hrl b/include/eWSrv.hrl index bba3bce..f55dd85 100644 --- a/include/eWSrv.hrl +++ b/include/eWSrv.hrl @@ -25,16 +25,16 @@ -record(wsReq, { method :: method(), + raw_path :: binary(), + version :: wsHttp:version(), scheme :: undefined | binary(), host :: undefined | binary(), port :: undefined | 1..65535, path :: [binary()], args :: [{binary(), any()}], - raw_path :: binary(), - version :: wsHttp:version(), headers :: headers(), original_headers :: headers(), - body :: body(), + body = <<>> :: body(), pid :: pid() }). diff --git a/include/wsCom.hrl b/include/wsCom.hrl index 273a34c..6fccf90 100644 --- a/include/wsCom.hrl +++ b/include/wsCom.hrl @@ -1,3 +1,5 @@ +-include("eWSrv.hrl"). + -define(wsErr(Str), error_logger:error_msg(Str)). -define(wsErr(Format, Args), error_logger:error_msg(Format, Args)). -define(wsWarn(Format, Args), error_logger:warning_msg(Format, Args)). @@ -5,3 +7,23 @@ -define(wsGLV(Key, List, Default), wsUtil:gLV(Key, List, Default)). -define(IIF(Cond, Then, That), case Cond of true -> Then; _ -> That end). + +-type stage() :: reqLine | header | body | done. %% 接受http请求可能会有多个包 分四个阶接收 + + +-record(state, { + stage = reqLine :: stage() %% 接收数据阶段 + , buffer = <<>> :: binary() %% 缓存接收到的数据 + , wsReq :: undefined | #wsReq{} %% 解析后的http + , headerCnt = 0 :: pos_integer() %% header计数 + , temHeader = [] :: [headers()] %% 解析header临时数据 + , contentLength :: undefined | non_neg_integer() | chunked %% 长度 + , temChunked = <<>> :: binary() + , rn :: undefined | binary:cp() + , wsMod :: callback() + , socket :: undefined | wsNet:socket() + , isSsl = false :: boolean() + , maxSize %% 允许接收的最大长度 + , maxChunkCnt %% 允许接收的最大chunk 数量 + , maxRecvCnt %% 允许允许recv最大的次数 +}). diff --git a/src/example/wsEgHer.erl b/src/example/wsEgHer.erl index 62e2b40..47949d3 100644 --- a/src/example/wsEgHer.erl +++ b/src/example/wsEgHer.erl @@ -10,7 +10,6 @@ -export([handle/2, handle_event/3]). -export([chunk_loop/1]). --include("eWSrv.hrl"). -include("wsCom.hrl"). -behaviour(wsHer). diff --git a/src/test/elli_tests.erl b/src/test/elli_tests.erl index ad49a9d..756a7b7 100644 --- a/src/test/elli_tests.erl +++ b/src/test/elli_tests.erl @@ -1,6 +1,5 @@ -module(elli_tests). -include_lib("eunit/include/eunit.hrl"). --include("eWSrv.hrl"). -include("wsCom.hrl"). -include("elli_test.hrl"). diff --git a/src/wsSrv/elli.erl b/src/wsSrv/elli.erl index 83897db..ddf9a8f 100644 --- a/src/wsSrv/elli.erl +++ b/src/wsSrv/elli.erl @@ -2,7 +2,6 @@ -behaviour(gen_server). --include("eWSrv.hrl"). -include("wsCom.hrl"). -export([ diff --git a/src/wsSrv/wsHttp.erl b/src/wsSrv/wsHttp.erl index 46b9984..3675fb5 100644 --- a/src/wsSrv/wsHttp.erl +++ b/src/wsSrv/wsHttp.erl @@ -3,7 +3,7 @@ -behavior(gen_srv). -include_lib("eNet/include/eNet.hrl"). --include("eWSrv.hrl"). + -include("wsCom.hrl"). -export([ @@ -38,16 +38,6 @@ -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, []). @@ -68,28 +58,45 @@ 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) -> +handleInfo({tcp, _Socket, Data}, State) -> + #state{stage = Stage, buffer = Buffer, socket = Socket} = State, + case wsHttpProtocol:request(Stage, <>, State) of + {ok, NewState} -> + {noreply, NewState}; + {done, NewState} -> + ok; + Err -> + ?wsErr("recv the http data error ~p~n", [Err]), + send_bad_request(Socket), + gen_tcp:close(Socket) + end; +handleInfo({tcp_closed, _Socket}, _State) -> ok; handleInfo({tcp_error, Socket, Reason}, _State) -> - ok; - -handleInfo({ssl, Socket, Data}, _State) -> - ok; - + ?wsErr("the http socket error ~p~n", [Reason]), + gen_tcp:close(Socket), + kpS; + +handleInfo({ssl, Socket, Data}, State) -> + #state{stage = Stage, buffer = Buffer, socket = Socket} = State, + case wsHttpProtocol:request(Stage, <>, State) of + {ok, NewState} -> + {noreply, NewState}; + {done, NewState} -> + ok; + Err -> + ?wsErr("recv the http data error ~p~n", [Err]), + send_bad_request(Socket), + ssl:close(Socket) + end; handleInfo({ssl_closed, Socket}, _State) -> ok; handleInfo({ssl_error, Socket, Reason}, _State) -> - ok; - - - -handleInfo({ssl, Socket, Data}, _State) -> - ok; + ?wsErr("the http socket error ~p~n", [Reason]), + ssl:close(Socket), + kpS; handleInfo({?mSockReady, Sock}, _State) -> inet:setopts(Sock, [{packet, raw}, {active, true}]), {ok, #state{socket = Sock}}; @@ -112,19 +119,6 @@ terminate(_Reason, _State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. - - - - - - - - - - - - -%% @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. diff --git a/src/wsSrv/wsHttpProtocol.erl b/src/wsSrv/wsHttpProtocol.erl index 1079475..28ea4ff 100644 --- a/src/wsSrv/wsHttpProtocol.erl +++ b/src/wsSrv/wsHttpProtocol.erl @@ -7,20 +7,20 @@ -compile({inline_size, 128}). -export([ - request/2 + request/3 ]). --spec request(undefined | recvState(), binary()) -> {ok, recvState()} | error(). -request(reqLine, Data) -> +-spec request(Stage :: stage(), Data :: binary(), State :: #state{}) -> {ok, NewState :: #state{}} | {done, NewState :: #state{}} | {error, term()}. +request(reqLine, Data, State) -> case erlang:decode_packet(http_bin, Data, []) of {more, _} -> - reqLine; + {ok, State#state{buffer = Data}}; {ok, {http_request, Method, RawPath, Version}, Rest} -> case byte_size(Rest) > 0 of true -> - request(header, Rest); + request(header, Rest, State#state{stage = header, buffer = Rest, wsReq = #wsReq{method = Method, raw_path = RawPath, version = Version}, headerCnt = 0}); _ -> - {header, Method, RawPath, Version} + {ok, State#state{stage = header, buffer = <<>>, wsReq = #wsReq{method = Method, raw_path = RawPath, version = Version}, headerCnt = 0}} end; {ok, {http_error, ErrStr}, _} -> {error, ErrStr}; @@ -28,178 +28,103 @@ request(reqLine, Data) -> {error, http_response}; {error, _Reason} = Ret -> Ret - end. - - - case parseRequestLine(Data) of - {StatusCode, Rest} -> - case splitHeaders(Rest, Rn, RnRn) of - {undefined, Headers, Body} -> - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = undefined, body = Body}}; - {0, Headers, Rest} -> - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = 0, body = Rest}}; - {chunked, Headers, Body} -> - case IsHeadMethod orelse StatusCode == 204 orelse StatusCode == 304 orelse (StatusCode < 200 andalso StatusCode >= 100) of - true -> - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = 0, body = Body}}; - _ -> - RecvState = #recvState{stage = body, contentLength = chunked, statusCode = StatusCode, headers = Headers}, - request(RecvState, Rn, RnRn, Body, IsHeadMethod) - end; - {ContentLength, Headers, Body} -> - BodySize = erlang:size(Body), - if - BodySize == ContentLength -> - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; - BodySize > ContentLength -> - ?wsWarn("11 contentLength get to long data why? more: ~p ~n", [BodySize - ContentLength]), - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; - true -> - case IsHeadMethod orelse StatusCode == 204 orelse StatusCode == 304 orelse (StatusCode < 200 andalso StatusCode >= 100) of - true -> - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; + end; +request(header, Data, State) -> + case erlang:decode_packet(httph_bin, Data, []) of + {more, _} -> + {ok, State#state{buffer = Data}}; + {ok, {http_header, _, Key, _, Value}, Rest} -> + #state{headerCnt = HeaderCnt, temHeader = TemHeader, rn = Rn} = State, + NewTemHeader = [{Key, Value} | TemHeader], + NewHeaderCnt = HeaderCnt + 1, + case NewHeaderCnt >= 100 of + true -> + {error, too_many_headers}; + _ -> + case Key of + 'Content-Length' -> + request(header, Rest, State#state{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = binary_to_integer(Value)}); + '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#state{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked}) + end; _ -> - {ok, #recvState{stage = body, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}} - end - end; - not_enough_data -> - {ok, #recvState{stage = header, body = Data}} + {error, 'Transfer-Encoding'} + end; + _ -> + request(header, Rest, State#state{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader}) + end end; - not_enough_data -> - {ok, #recvState{stage = header, body = Data}}; - {error, Reason} -> - {error, Reason} - end; -request(#recvState{stage = body, contentLength = chunked, body = Body, buffer = Buffer} = RecvState, Rn, _RnRn, Data, _IsHeadMethod) -> - NewBuffer = <>, - case parseChunks(NewBuffer, Rn, []) of - {ok, AddBody, _Rest} -> - LastBody = <>, - {done, RecvState#recvState{stage = done, body = LastBody}}; - {not_enough_data, AddBody, Rest} -> - NewBody = <>, - {ok, RecvState#recvState{body = NewBody, buffer = Rest}}; - {error, Reason} -> - {error, Reason} - end; -request(#recvState{stage = body, contentLength = ContentLength, body = Body} = RecvState, _Rn, _RnRn, Data, _IsHeadMethod) -> - CurData = <>, - BodySize = erlang:size(CurData), - if - BodySize == ContentLength -> - {done, RecvState#recvState{stage = done, body = CurData}}; - BodySize > ContentLength -> - ?wsWarn("22 contentLength get to long data why? more: ~p ~n", [BodySize - ContentLength]), - {done, #recvState{stage = done, body = CurData}}; - true -> - {ok, RecvState#recvState{body = CurData}} - end; -request(#recvState{stage = header, body = OldBody}, Rn, RnRn, Data, IsHeadMethod) -> - CurBody = <>, - case parseRequestLine(CurBody, Rn) of - {StatusCode, Rest} -> - case splitHeaders(Rest, Rn, RnRn) of - {undefined, Headers, Body} -> - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = undefined, body = Body}}; - {0, Headers, Body} -> - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = 0, body = Body}}; - {chunked, Headers, Rest} -> - case IsHeadMethod orelse StatusCode == 204 orelse StatusCode == 304 orelse (StatusCode < 200 andalso StatusCode >= 100) of + {ok, http_eoh, Rest} -> + #state{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 = []}}; + _ -> + case byte_size(Rest) > 0 of true -> - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = 0, body = <<>>}}; + request(body, Rest, State#state{stage = body, buffer = Rest, wsReq = NewWsReq, temHeader = []}); _ -> - RecvState = #recvState{stage = body, contentLength = chunked, statusCode = StatusCode, headers = Headers}, - request(RecvState, Rn, RnRn, Rest, IsHeadMethod) - end; - {ContentLength, Headers, Body} -> - BodySize = erlang:size(Body), - if - BodySize == ContentLength -> - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; - BodySize > ContentLength -> - ?wsWarn("33 contentLength get to long data why? more: ~p ~n", [BodySize - ContentLength]), - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; - true -> - case IsHeadMethod orelse StatusCode == 204 orelse StatusCode == 304 orelse (StatusCode < 200 andalso StatusCode >= 100) of - true -> - {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; - _ -> - {ok, #recvState{stage = body, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}} - end - end; - not_enough_data -> - {ok, #recvState{stage = header, body = CurBody}} + {ok, State#state{stage = body, buffer = <<>>, wsReq = NewWsReq, temHeader = []}} + end end; - not_enough_data -> - {ok, #recvState{stage = header, body = CurBody}}; - {error, Reason} -> - {error, Reason} - end. - -splitHeaders(Data, Rn, RnRn) -> - case binary:split(Data, RnRn) of - [Data] -> - not_enough_data; - [Headers, Body] -> - HeadersList = binary:split(Headers, Rn, [global]), - ContentLength = contentLength(HeadersList), - {ContentLength, Headers, Body} - end. - -contentLength([]) -> - undefined; -contentLength([<<"Content-Length: ", Rest/binary>> | _T]) -> - binary_to_integer(Rest); -contentLength([<<"content-length: ", Rest/binary>> | _T]) -> - binary_to_integer(Rest); -contentLength([<<"Transfer-Encoding: chunked">> | _T]) -> - chunked; -contentLength([<<"transfer-encoding: chunked">> | _T]) -> - chunked; -contentLength([_ | T]) -> - contentLength(T). - -parseRequestLine(Data, Rn) -> - case binary:split(Data, Rn) of - [Data] -> - not_enough_data; - [Line, Rest] -> - case binary:split(Line, <<" ">>) of - [Method, RawPath, V] -> - {Method, RawPath, V, Rest}; - _ -> - {error, request_line} + {ok, {http_error, ErrStr}, _Rest} -> + {error, ErrStr}; + {error, _Reason} = Ret -> + Ret + end; +request(body, Data, State) -> + #state{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}}; + {over, LastTemChunked, Rest} -> + {done, State#state{buffer = Rest, temChunked = <<>>, wsReq = WsReq#wsReq{body = LastTemChunked}}}; + {error, _Reason} = Ret -> + Ret + end; + _ -> + BodySize = erlang:size(Data), + if + BodySize == CLen -> + {done, State#state{buffer = <<>>, wsReq = WsReq#wsReq{body = Data}}}; + BodySize > CLen -> + <> = Data, + {done, State#state{buffer = Rest, wsReq = WsReq#wsReq{body = Body}}}; + true -> + {ok, State#state{buffer = Data}} end end. parseChunks(Data, Rn, Acc) -> - case parseChunk(Data, Rn) of - done -> - {ok, iolist_to_binary(lists:reverse(Acc)), <<>>}; - {ok, Body, Rest} -> - parseChunks(Rest, Rn, [Body | Acc]); - not_enough_data -> - {not_enough_data, iolist_to_binary(lists:reverse(Acc)), Data}; - {error, Reason} -> - {error, Reason} - end. - -parseChunk(Data, Rn) -> case binary:split(Data, Rn) of [Size, Rest] -> - case parseChunkSize(Size) of + case chunkSize(Size) of undefined -> {error, invalid_chunk_size}; 0 -> - done; + {over, Acc, Rest}; HexSize -> - parseChunkBody(Rest, HexSize) - end; - [Data] -> - not_enough_data + case chunkBody(Rest, HexSize) of + not_enough_data -> + {ok, Acc, Data}; + {ok, Body, Rest} -> + parseChunks(Rest, Rn, <>) + end + end end. -parseChunkBody(Data, Size) -> +chunkBody(Data, Size) -> case Data of <> -> {ok, Body, Rest}; @@ -207,10 +132,10 @@ parseChunkBody(Data, Size) -> not_enough_data end. -parseChunkSize(Bin) -> +chunkSize(Bin) -> try binary_to_integer(Bin, 16) catch error:badarg -> undefined - end. + end. \ No newline at end of file diff --git a/src/wsSrv/wsHttps.erl b/src/wsSrv/wsHttps.erl index bfd5dd6..a4f9510 100644 --- a/src/wsSrv/wsHttps.erl +++ b/src/wsSrv/wsHttps.erl @@ -4,7 +4,6 @@ %% 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("eWSrv.hrl"). -include("wsCom.hrl"). diff --git a/src/wsSrv/wsUtil.erl b/src/wsSrv/wsUtil.erl index 56109f0..a123a81 100644 --- a/src/wsSrv/wsUtil.erl +++ b/src/wsSrv/wsUtil.erl @@ -1,5 +1,5 @@ -module(wsUtil). --include("eWSrv.hrl"). + -include("wsCom.hrl"). -include_lib("kernel/include/file.hrl").