using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Security.Authentication; using System.Text; using System.Threading; using SuperSocket.Common; using SuperSocket.SocketBase.Command; using SuperSocket.SocketBase.Config; using SuperSocket.SocketBase.Logging; using SuperSocket.SocketBase.Protocol; namespace SuperSocket.SocketBase { /// /// AppSession base class /// /// The type of the app session. /// The type of the request info. public abstract class AppSession : IAppSession, IAppSession where TAppSession : AppSession, IAppSession, new() where TRequestInfo : class, IRequestInfo { #region Properties /// /// Gets the app server instance assosiated with the session. /// public virtual AppServerBase AppServer { get; private set; } /// /// Gets the app server instance assosiated with the session. /// IAppServer IAppSession.AppServer { get { return this.AppServer; } } /// /// Gets or sets the charset which is used for transfering text message. /// /// /// The charset. /// public Encoding Charset { get; set; } private IDictionary m_Items; /// /// Gets the items dictionary, only support 10 items maximum /// public IDictionary Items { get { if (m_Items == null) m_Items = new Dictionary(10); return m_Items; } } private bool m_Connected = false; /// /// Gets a value indicating whether this is connected. /// /// /// true if connected; otherwise, false. /// public bool Connected { get { return m_Connected; } internal set { m_Connected = value; } } /// /// Gets or sets the previous command. /// /// /// The prev command. /// public string PrevCommand { get; set; } /// /// Gets or sets the current executing command. /// /// /// The current command. /// public string CurrentCommand { get; set; } /// /// Gets or sets the secure protocol of transportation layer. /// /// /// The secure protocol. /// public SslProtocols SecureProtocol { get { return SocketSession.SecureProtocol; } set { SocketSession.SecureProtocol = value; } } /// /// Gets the local listening endpoint. /// public IPEndPoint LocalEndPoint { get { return SocketSession.LocalEndPoint; } } /// /// Gets the remote endpoint of client. /// public IPEndPoint RemoteEndPoint { get { return SocketSession.RemoteEndPoint; } } /// /// Gets the logger. /// public ILog Logger { get { return AppServer.Logger; } } /// /// Gets or sets the last active time of the session. /// /// /// The last active time. /// public DateTime LastActiveTime { get; set; } /// /// Gets the start time of the session. /// public DateTime StartTime { get; private set; } /// /// Gets the session ID. /// public string SessionID { get; private set; } /// /// Gets the socket session of the AppSession. /// public ISocketSession SocketSession { get; private set; } /// /// Gets the config of the server. /// public IServerConfig Config { get { return AppServer.Config; } } IReceiveFilter m_ReceiveFilter; #endregion /// /// Initializes a new instance of the class. /// public AppSession() { this.StartTime = DateTime.Now; this.LastActiveTime = this.StartTime; } /// /// Initializes the specified app session by AppServer and SocketSession. /// /// The app server. /// The socket session. public virtual void Initialize(IAppServer appServer, ISocketSession socketSession) { var castedAppServer = (AppServerBase)appServer; AppServer = castedAppServer; Charset = castedAppServer.TextEncoding; SocketSession = socketSession; SessionID = socketSession.SessionID; m_Connected = true; m_ReceiveFilter = castedAppServer.ReceiveFilterFactory.CreateFilter(appServer, this, socketSession.RemoteEndPoint); var filterInitializer = m_ReceiveFilter as IReceiveFilterInitializer; if (filterInitializer != null) filterInitializer.Initialize(castedAppServer, this); socketSession.Initialize(this); OnInit(); } /// /// Starts the session. /// void IAppSession.StartSession() { OnSessionStarted(); } /// /// Called when [init]. /// protected virtual void OnInit() { } /// /// Called when [session started]. /// protected virtual void OnSessionStarted() { } /// /// Called when [session closed]. /// /// The reason. internal protected virtual void OnSessionClosed(CloseReason reason) { } /// /// Handles the exceptional error, it only handles application error. /// /// The exception. protected virtual void HandleException(Exception e) { Logger.Error(this, e); this.Close(CloseReason.ApplicationError); } /// /// Handles the unknown request. /// /// The request info. protected virtual void HandleUnknownRequest(TRequestInfo requestInfo) { } internal void InternalHandleUnknownRequest(TRequestInfo requestInfo) { HandleUnknownRequest(requestInfo); } internal void InternalHandleExcetion(Exception e) { HandleException(e); } /// /// Closes the session by the specified reason. /// /// The close reason. public virtual void Close(CloseReason reason) { this.SocketSession.Close(reason); } /// /// Closes this session. /// public virtual void Close() { Close(CloseReason.ServerClosing); } #region Sending processing /// /// Try to send the message to client. /// /// The message which will be sent. /// Indicate whether the message was pushed into the sending queue public virtual bool TrySend(string message) { var data = this.Charset.GetBytes(message); return InternalTrySend(new ArraySegment(data, 0, data.Length)); } /// /// Sends the message to client. /// /// The message which will be sent. public virtual void Send(string message) { var data = this.Charset.GetBytes(message); Send(data, 0, data.Length); } /// /// Try to send the data to client. /// /// The data which will be sent. /// The offset. /// The length. /// Indicate whether the message was pushed into the sending queue public virtual bool TrySend(byte[] data, int offset, int length) { return InternalTrySend(new ArraySegment(data, offset, length)); } /// /// Sends the data to client. /// /// The data which will be sent. /// The offset. /// The length. public virtual void Send(byte[] data, int offset, int length) { InternalSend(new ArraySegment(data, offset, length)); } private bool InternalTrySend(ArraySegment segment) { if (!SocketSession.TrySend(segment)) return false; LastActiveTime = DateTime.Now; return true; } /// /// Try to send the data segment to client. /// /// The segment which will be sent. /// Indicate whether the message was pushed into the sending queue public virtual bool TrySend(ArraySegment segment) { if (!m_Connected) return false; return InternalTrySend(segment); } private void InternalSend(ArraySegment segment) { if (!m_Connected) return; if (InternalTrySend(segment)) return; var sendTimeOut = Config.SendTimeOut; //Don't retry, timeout directly if (sendTimeOut < 0) { throw new TimeoutException("The sending attempt timed out"); } var timeOutTime = sendTimeOut > 0 ? DateTime.Now.AddMilliseconds(sendTimeOut) : DateTime.Now; var spinWait = new SpinWait(); while (m_Connected) { spinWait.SpinOnce(); if (InternalTrySend(segment)) return; //If sendTimeOut = 0, don't have timeout check if (sendTimeOut > 0 && DateTime.Now >= timeOutTime) { throw new TimeoutException("The sending attempt timed out"); } } } /// /// Sends the data segment to client. /// /// The segment which will be sent. public virtual void Send(ArraySegment segment) { InternalSend(segment); } private bool InternalTrySend(IList> segments) { if (!SocketSession.TrySend(segments)) return false; LastActiveTime = DateTime.Now; return true; } /// /// Try to send the data segments to client. /// /// The segments. /// Indicate whether the message was pushed into the sending queue; if it returns false, the sending queue may be full or the socket is not connected public virtual bool TrySend(IList> segments) { if (!m_Connected) return false; return InternalTrySend(segments); } private void InternalSend(IList> segments) { if (!m_Connected) return; if (InternalTrySend(segments)) return; var sendTimeOut = Config.SendTimeOut; //Don't retry, timeout directly if (sendTimeOut < 0) { throw new TimeoutException("The sending attempt timed out"); } var timeOutTime = sendTimeOut > 0 ? DateTime.Now.AddMilliseconds(sendTimeOut) : DateTime.Now; var spinWait = new SpinWait(); while (m_Connected) { spinWait.SpinOnce(); if (InternalTrySend(segments)) return; //If sendTimeOut = 0, don't have timeout check if (sendTimeOut > 0 && DateTime.Now >= timeOutTime) { throw new TimeoutException("The sending attempt timed out"); } } } /// /// Sends the data segments to client. /// /// The segments. public virtual void Send(IList> segments) { InternalSend(segments); } /// /// Sends the response. /// /// The message which will be sent. /// The parameter values. public virtual void Send(string message, params object[] paramValues) { var data = this.Charset.GetBytes(string.Format(message, paramValues)); InternalSend(new ArraySegment(data, 0, data.Length)); } #endregion #region Receiving processing /// /// Sets the next Receive filter which will be used when next data block received /// /// The next receive filter. protected void SetNextReceiveFilter(IReceiveFilter nextReceiveFilter) { m_ReceiveFilter = nextReceiveFilter; } /// /// Gets the maximum allowed length of the request. /// /// protected virtual int GetMaxRequestLength() { return AppServer.Config.MaxRequestLength; } /// /// Filters the request. /// /// The read buffer. /// The offset. /// The length. /// if set to true [to be copied]. /// The rest, the size of the data which has not been processed /// return offset delta of next receiving buffer. /// TRequestInfo FilterRequest(byte[] readBuffer, int offset, int length, bool toBeCopied, out int rest, out int offsetDelta) { if (!AppServer.OnRawDataReceived(this, readBuffer, offset, length)) { rest = 0; offsetDelta = 0; return null; } var currentRequestLength = m_ReceiveFilter.LeftBufferSize; var requestInfo = m_ReceiveFilter.Filter(readBuffer, offset, length, toBeCopied, out rest); if (m_ReceiveFilter.State == FilterState.Error) { rest = 0; offsetDelta = 0; Close(CloseReason.ProtocolError); return null; } var offsetAdapter = m_ReceiveFilter as IOffsetAdapter; offsetDelta = offsetAdapter != null ? offsetAdapter.OffsetDelta : 0; if (requestInfo == null) { //current buffered length currentRequestLength = m_ReceiveFilter.LeftBufferSize; } else { //current request length currentRequestLength = currentRequestLength + length - rest; } var maxRequestLength = GetMaxRequestLength(); if (currentRequestLength >= maxRequestLength) { if (Logger.IsErrorEnabled) Logger.Error(this, string.Format("Max request length: {0}, current processed length: {1}", maxRequestLength, currentRequestLength)); Close(CloseReason.ProtocolError); return null; } //If next Receive filter wasn't set, still use current Receive filter in next round received data processing if (m_ReceiveFilter.NextReceiveFilter != null) m_ReceiveFilter = m_ReceiveFilter.NextReceiveFilter; return requestInfo; } /// /// Processes the request data. /// /// The read buffer. /// The offset. /// The length. /// if set to true [to be copied]. /// /// return offset delta of next receiving buffer /// int IAppSession.ProcessRequest(byte[] readBuffer, int offset, int length, bool toBeCopied) { int rest, offsetDelta; while (true) { var requestInfo = FilterRequest(readBuffer, offset, length, toBeCopied, out rest, out offsetDelta); if (requestInfo != null) { try { AppServer.ExecuteCommand(this, requestInfo); } catch (Exception e) { HandleException(e); } } if (rest <= 0) { return offsetDelta; } //Still have data has not been processed offset = offset + length - rest; length = rest; } } #endregion } /// /// AppServer basic class for whose request infoe type is StringRequestInfo /// /// The type of the app session. public abstract class AppSession : AppSession where TAppSession : AppSession, IAppSession, new() { private bool m_AppendNewLineForResponse = false; private static string m_NewLine = "\r\n"; /// /// Initializes a new instance of the class. /// public AppSession() : this(true) { } /// /// Initializes a new instance of the class. /// /// if set to true [append new line for response]. public AppSession(bool appendNewLineForResponse) { m_AppendNewLineForResponse = appendNewLineForResponse; } /// /// Handles the unknown request. /// /// The request info. protected override void HandleUnknownRequest(StringRequestInfo requestInfo) { Send("Unknown request: " + requestInfo.Key); } /// /// Processes the sending message. /// /// The raw message. /// protected virtual string ProcessSendingMessage(string rawMessage) { if (!m_AppendNewLineForResponse) return rawMessage; if (AppServer.Config.Mode == SocketMode.Udp) return rawMessage; if (string.IsNullOrEmpty(rawMessage) || !rawMessage.EndsWith(m_NewLine)) return rawMessage + m_NewLine; else return rawMessage; } /// /// Sends the specified message. /// /// The message. /// public override void Send(string message) { base.Send(ProcessSendingMessage(message)); } /// /// Sends the response. /// /// The message. /// The param values. /// Indicate whether the message was pushed into the sending queue public override void Send(string message, params object[] paramValues) { base.Send(ProcessSendingMessage(message), paramValues); } } /// /// AppServer basic class for whose request infoe type is StringRequestInfo /// public class AppSession : AppSession { } }