-module(agSslAgencyIns). -include("agHttpCli.hrl"). -include("erlArango.hrl"). -compile(inline). -compile({inline_size, 128}). -export([ %% 内部行为API init/1 , handleMsg/3 , terminate/3 ]). -spec init(term()) -> no_return(). init({PoolName, AgencyName, #agencyOpts{reconnect = Reconnect, backlogSize = BacklogSize, reconnectTimeMin = Min, reconnectTimeMax = Max}}) -> ReconnectState = agAgencyUtils:initReconnectState(Reconnect, Min, Max), self() ! ?miDoNetConnect, {ok, #srvState{poolName = PoolName, serverName = AgencyName, rn = binary:compile_pattern(<<"\r\n">>), rnrn = binary:compile_pattern(<<"\r\n\r\n">>), reconnectState = ReconnectState}, #cliState{backlogSize = BacklogSize}}. -spec handleMsg(term(), srvState(), cliState()) -> {ok, term(), term()}. handleMsg(#miRequest{method = Method, path = Path, headers = Headers, body = Body, requestId = RequestId, fromPid = FromPid, overTime = OverTime, isSystem = IsSystem} = MiRequest, #srvState{serverName = ServerName, host = Host, userPassWord = UserPassWord, dbName = DbName, socket = Socket} = SrvState, #cliState{backlogNum = BacklogNum, backlogSize = BacklogSize, requestsIn = RequestsIn, status = Status} = CliState) -> case Socket of undefined -> agAgencyUtils:agencyReply(FromPid, RequestId, undefined, {error, no_socket}), {ok, SrvState, CliState}; _ -> case BacklogNum >= BacklogSize of true -> ?WARN(ServerName, ":backlog full curNum:~p Total: ~p ~n", [BacklogNum, BacklogSize]), agAgencyUtils:agencyReply(FromPid, RequestId, undefined, {error, backlog_full}), {ok, SrvState, CliState}; _ -> case Status of leisure -> %% 空闲模式 Request = agHttpProtocol:request(IsSystem, Body, Method, Host, DbName, Path, [UserPassWord | Headers]), case ssl:send(Socket, Request) of ok -> TimerRef = case OverTime of infinity -> undefined; _ -> erlang:start_timer(OverTime, self(), waiting_over, [{abs, true}]) end, {ok, SrvState, CliState#cliState{isHeadMethod = Method == ?AgHead, status = waiting, backlogNum = BacklogNum + 1, curInfo = {FromPid, RequestId, TimerRef}}}; {error, Reason} -> ?WARN(ServerName, ":send error: ~p ~p ~p ~n", [Reason, FromPid, RequestId]), ssl:close(Socket), agAgencyUtils:agencyReply(FromPid, RequestId, undefined, {error, {socket_send_error, Reason}}), agAgencyUtils:dealClose(SrvState, CliState, {error, {socket_send_error, Reason}}) end; _ -> agAgencyUtils:addQueue(RequestsIn, MiRequest), {ok, SrvState, CliState#cliState{requestsIn = RequestsIn + 1, backlogNum = BacklogNum + 1}} end end end; handleMsg({ssl, Socket, Data}, #srvState{serverName = ServerName, rn = Rn, rnrn = RnRn, socket = Socket} = SrvState, #cliState{isHeadMethod = IsHeadMethod, backlogNum = BacklogNum, curInfo = CurInfo, requestsOut = RequestsOut, recvState = RecvState} = CliState) -> try agHttpProtocol:response(RecvState, Rn, RnRn, Data, IsHeadMethod) of {done, #recvState{statusCode = StatusCode, headers = Headers, body = Body}} -> agAgencyUtils:agencyReply(CurInfo, {ok, Body, StatusCode, Headers}), case agAgencyUtils:getQueue(RequestsOut + 1) of undefined -> {ok, SrvState, CliState#cliState{backlogNum = BacklogNum - 1, status = leisure, curInfo = undefined, recvState = undefined}}; MiRequest -> dealQueueRequest(MiRequest, SrvState, CliState#cliState{backlogNum = BacklogNum - 1, status = leisure, curInfo = undefined, recvState = undefined}) end; {ok, NewRecvState} -> {ok, SrvState, CliState#cliState{recvState = NewRecvState}}; {error, Reason} -> ?WARN(ServerName, "handle ssl data error: ~p ~p ~n", [Reason, CurInfo]), ssl:close(Socket), agAgencyUtils:dealClose(SrvState, CliState, {error, {ssl_data_error, Reason}}) catch E:R:S -> ?WARN(ServerName, "handle ssl data crash: ~p:~p~n~p~n ~p~n ", [E, R, S, CurInfo]), ssl:close(Socket), agAgencyUtils:dealClose(SrvState, CliState, {{error, agency_handledata_error}}) end; handleMsg({timeout, TimerRef, waiting_over}, #srvState{socket = Socket} = SrvState, #cliState{backlogNum = BacklogNum, curInfo = {FromPid, RequestId, TimerRef}} = CliState) -> agAgencyUtils:agencyReply(FromPid, RequestId, undefined, {error, timeout}), %% 之前的数据超时之后 要关闭ssl 然后重新建立连接 以免后面该ssl收到该次超时数据 影响后面请求的接收数据 导致数据错乱 ssl:close(Socket), handleMsg(?miDoNetConnect, SrvState#srvState{socket = undefined}, CliState#cliState{backlogNum = BacklogNum - 1}); handleMsg({ssl_closed, Socket}, #srvState{socket = Socket, serverName = ServerName} = SrvState, CliState) -> ?WARN(ServerName, "connection closed~n", []), ssl:close(Socket), agAgencyUtils:dealClose(SrvState, CliState, {error, ssl_closed}); handleMsg({ssl_error, Socket, Reason}, #srvState{socket = Socket, serverName = ServerName} = SrvState, CliState) -> ?WARN(ServerName, "connection error: ~p~n", [Reason]), ssl:close(Socket), agAgencyUtils:dealClose(SrvState, CliState, {error, {ssl_error, Reason}}); handleMsg(?miDoNetConnect, #srvState{poolName = PoolName, serverName = ServerName, reconnectState = ReconnectState} = SrvState, #cliState{requestsOut = RequestsOut} = CliState) -> case ?agBeamPool:get(PoolName) of #dbOpts{host = Host, port = Port, hostname = HostName, dbName = DbName, userPassword = UserPassword, socketOpts = SocketOpts} -> case dealConnect(ServerName, HostName, Port, SocketOpts) of {ok, Socket} -> NewReconnectState = agAgencyUtils:resetReconnectState(ReconnectState), %% 新建连接之后 需要重置之前的buff之类状态数据 NewCliState = CliState#cliState{status = leisure, recvState = undefined, curInfo = undefined}, case agAgencyUtils:getQueue(RequestsOut + 1) of undefined -> {ok, SrvState#srvState{userPassWord = UserPassword, dbName = DbName, host = Host, reconnectState = NewReconnectState, socket = Socket}, NewCliState}; MiRequest -> dealQueueRequest(MiRequest, SrvState#srvState{socket = Socket, reconnectState = NewReconnectState}, NewCliState) end; {error, _Reason} -> agAgencyUtils:reconnectTimer(SrvState, CliState) end; _Ret -> ?WARN(ServerName, "deal connect not found agBeamPool:get(~p) ret ~p is error ~n", [PoolName, _Ret]) end; handleMsg(Msg, #srvState{serverName = ServerName} = SrvState, CliState) -> ?WARN(ServerName, "unknown msg: ~p~n", [Msg]), {ok, SrvState, CliState}. -spec terminate(term(), srvState(), cliState()) -> ok. terminate(_Reason, #srvState{socket = Socket} = SrvState, CliState) -> {ok, NewSrvState, NewCliState} = overAllWork(SrvState, CliState), ssl:close(Socket), agAgencyUtils:dealClose(NewSrvState, NewCliState, {error, shutdown}), ok. -spec overAllWork(srvState(), cliState()) -> {ok, srvState(), cliState()}. overAllWork(SrvState, #cliState{requestsOut = RequestsOut, status = Status} = CliState) -> case Status of leisure -> case agAgencyUtils:getQueue(RequestsOut + 1) of undefined -> {ok, SrvState, CliState}; MiRequest -> overDealQueueRequest(MiRequest, SrvState, CliState) end; _ -> overReceiveSslData(SrvState, CliState) end. -spec overDealQueueRequest(miRequest(), srvState(), cliState()) -> {ok, srvState(), cliState()}. overDealQueueRequest(#miRequest{method = Method, path = Path, headers = Headers, body = Body, requestId = RequestId, fromPid = FromPid, overTime = OverTime, isSystem = IsSystem}, #srvState{serverName = ServerName, host = Host, userPassWord = UserPassWord, dbName = DbName, socket = Socket} = SrvState, #cliState{requestsOut = RequestsOut, backlogNum = BacklogNum} = CliState) -> agAgencyUtils:delQueue(RequestsOut + 1), case erlang:system_time(millisecond) > OverTime of true -> %% 超时了 agAgencyUtils:agencyReply(FromPid, RequestId, undefined, {error, timeout}), case agAgencyUtils:getQueue(RequestsOut + 2) of undefined -> {ok, SrvState, CliState#cliState{requestsOut = RequestsOut + 1, backlogNum = BacklogNum - 1}}; MiRequest -> overDealQueueRequest(MiRequest, SrvState, CliState#cliState{requestsOut = RequestsOut + 1, backlogNum = BacklogNum - 1}) end; _ -> Request = agHttpProtocol:request(IsSystem, Body, Method, Host, DbName, Path, [UserPassWord | Headers]), case ssl:send(Socket, Request) of ok -> TimerRef = case OverTime of infinity -> undefined; _ -> erlang:start_timer(OverTime, self(), waiting_over, [{abs, true}]) end, overReceiveSslData(SrvState, CliState#cliState{isHeadMethod = Method == ?AgHead, status = waiting, requestsOut = RequestsOut + 1, curInfo = {FromPid, RequestId, TimerRef}}); {error, Reason} -> ?WARN(ServerName, ":send error: ~p~n", [Reason]), ssl:close(Socket), agAgencyUtils:agencyReply(FromPid, RequestId, undefined, {error, socket_send_error}), agAgencyUtils:dealClose(SrvState, CliState, {error, socket_send_error}) end end. -spec overReceiveSslData(srvState(), cliState()) -> {ok, srvState(), cliState()}. overReceiveSslData(#srvState{poolName = PoolName, serverName = ServerName, rn = Rn, rnrn = RnRn, socket = Socket} = SrvState, #cliState{isHeadMethod = IsHeadMethod, backlogNum = BacklogNum, curInfo = CurInfo, requestsIn = RequestsIn, requestsOut = RequestsOut, recvState = RecvState} = CliState) -> receive {ssl, Socket, Data} -> try agHttpProtocol:response(RecvState, Rn, RnRn, Data, IsHeadMethod) of {done, #recvState{statusCode = StatusCode, headers = Headers, body = Body}} -> agAgencyUtils:agencyReply(CurInfo, {ok, Body, StatusCode, Headers}), case agAgencyUtils:getQueue(RequestsOut + 1) of undefined -> {ok, SrvState, CliState#cliState{backlogNum = BacklogNum - 1, status = leisure, curInfo = undefined, recvState = undefined}}; MiRequest -> overDealQueueRequest(MiRequest, SrvState, CliState#cliState{backlogNum = BacklogNum - 1, status = leisure, curInfo = undefined, recvState = undefined}) end; {ok, NewRecvState} -> overReceiveSslData(SrvState, CliState#cliState{recvState = NewRecvState}); {error, Reason} -> ?WARN(overReceiveSslData, "handle ssl data error: ~p ~n", [Reason]), ssl:close(Socket), agAgencyUtils:dealClose(SrvState, CliState, {error, {ssl_data_error, Reason}}) catch E:R:S -> ?WARN(overReceiveSslData, "handle ssl data crash: ~p:~p~n~p ~n ", [E, R, S]), ssl:close(Socket), agAgencyUtils:dealClose(SrvState, CliState, {error, {ssl_error, handledata_error}}) end; {timeout, TimerRef, waiting_over} -> case CurInfo of {_PidForm, _RequestId, TimerRef} -> ssl:close(Socket), agAgencyUtils:agencyReply(CurInfo, {error, timeout}), case agAgencyUtils:getQueue(RequestsOut + 1) of undefined -> {ok, SrvState, CliState#cliState{backlogNum = BacklogNum - 1, status = leisure, curInfo = undefined, recvState = undefined}}; MiRequest -> case ?agBeamPool:get(PoolName) of #dbOpts{port = Port, hostname = HostName, socketOpts = SocketOpts} -> case dealConnect(ServerName, HostName, Port, SocketOpts) of {ok, NewSocket} -> %% 新建连接之后 需要重置之前的buff之类状态数据 NewCliState = CliState#cliState{status = leisure, recvState = undefined, curInfo = undefined}, overDealQueueRequest(MiRequest, SrvState#srvState{socket = NewSocket}, NewCliState); {error, _Reason} -> agAgencyUtils:dealClose(SrvState, CliState, {error, {new_ssl_connect_error_over, _Reason}}) end; _Ret -> agAgencyUtils:dealClose(SrvState, CliState, {error, {not_found_poolName, PoolName}}) end end; _ -> ?WARN(overReceiveSslData, "receive waiting_over TimerRef not match: ~p~n", [TimerRef]), overReceiveSslData(SrvState, CliState) end; {ssl_closed, Socket} -> ssl:close(Socket), agAgencyUtils:dealClose(SrvState, CliState, {error, ssl_closed}); {ssl_error, Socket, Reason} -> ssl:close(Socket), agAgencyUtils:dealClose(SrvState, CliState, {error, {ssl_error, Reason}}); #miRequest{} = MiRequest -> agAgencyUtils:addQueue(RequestsIn, MiRequest), overReceiveSslData(SrvState, CliState#cliState{requestsIn = RequestsIn + 1, backlogNum = BacklogNum + 1}); _Msg -> ?WARN(overReceiveSslData, "receive unexpect msg: ~p~n", [_Msg]), overReceiveSslData(SrvState, CliState) end. -spec dealConnect(atom(), hostName(), port(), socketOpts()) -> {ok, socket()} | {error, term()}. dealConnect(ServerName, HostName, Port, SocketOptions) -> case inet:getaddrs(HostName, inet) of {ok, IPList} -> Ip = agMiscUtils:randomElement(IPList), case ssl:connect(Ip, Port, SocketOptions, ?DEFAULT_CONNECT_TIMEOUT) of {ok, Socket} -> {ok, Socket}; {error, Reason} -> ?WARN(ServerName, "connect error: ~p~n", [Reason]), {error, Reason} end; {error, Reason} -> ?WARN(ServerName, "getaddrs error: ~p~n", [Reason]), {error, Reason} end. -spec dealQueueRequest(miRequest(), srvState(), cliState()) -> {ok, srvState(), cliState()}. dealQueueRequest(#miRequest{method = Method, path = Path, headers = Headers, body = Body, requestId = RequestId, fromPid = FromPid, overTime = OverTime, isSystem = IsSystem}, #srvState{serverName = ServerName, host = Host, userPassWord = UserPassWord, dbName = DbName, socket = Socket} = SrvState, #cliState{requestsOut = RequestsOut, backlogNum = BacklogNum} = CliState) -> agAgencyUtils:delQueue(RequestsOut + 1), case erlang:system_time(millisecond) > OverTime of true -> %% 超时了 agAgencyUtils:agencyReply(FromPid, RequestId, undefined, {error, timeout}), case agAgencyUtils:getQueue(RequestsOut + 2) of undefined -> {ok, SrvState, CliState#cliState{requestsOut = RequestsOut + 1, backlogNum = BacklogNum - 1}}; MiRequest -> dealQueueRequest(MiRequest, SrvState, CliState#cliState{requestsOut = RequestsOut + 1, backlogNum = BacklogNum - 1}) end; _ -> Request = agHttpProtocol:request(IsSystem, Body, Method, Host, DbName, Path, [UserPassWord | Headers]), case ssl:send(Socket, Request) of ok -> TimerRef = case OverTime of infinity -> undefined; _ -> erlang:start_timer(OverTime, self(), waiting_over, [{abs, true}]) end, {ok, SrvState, CliState#cliState{isHeadMethod = Method == ?AgHead, status = waiting, requestsOut = RequestsOut + 1, curInfo = {FromPid, RequestId, TimerRef}}}; {error, Reason} -> ?WARN(ServerName, ":send error: ~p~n", [Reason]), ssl:close(Socket), agAgencyUtils:agencyReply(FromPid, RequestId, undefined, {error, socket_send_error}), agAgencyUtils:dealClose(SrvState, CliState, {error, socket_send_error}) end end.