You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
10 KiB
344 lines
10 KiB
#if !NET20
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
|
|
namespace Apewer.WebSocket
|
|
{
|
|
|
|
/// <summary></summary>
|
|
public class Connection
|
|
{
|
|
|
|
#region internal
|
|
|
|
internal Connection(SocketWrapper socket, Action<Connection> initialize, Func<byte[], HttpRequest> parseRequest, Func<HttpRequest, ComposableHandler> handlerFactory, Func<IEnumerable<string>, string> negotiateSubProtocol)
|
|
{
|
|
Socket = socket;
|
|
OnOpen = () => { };
|
|
OnClose = () => { };
|
|
OnMessage = x => { };
|
|
OnBytes = x => { };
|
|
OnPing = x => Pong(x);
|
|
OnPong = x => { };
|
|
OnError = x => { };
|
|
_initialize = initialize;
|
|
_handlerFactory = handlerFactory;
|
|
_parseRequest = parseRequest;
|
|
_negotiateSubProtocol = negotiateSubProtocol;
|
|
}
|
|
|
|
internal SocketWrapper Socket { get; set; }
|
|
|
|
private readonly Action<Connection> _initialize;
|
|
private readonly Func<HttpRequest, ComposableHandler> _handlerFactory;
|
|
private readonly Func<IEnumerable<string>, string> _negotiateSubProtocol;
|
|
readonly Func<byte[], HttpRequest> _parseRequest;
|
|
|
|
internal ComposableHandler Handler { get; set; }
|
|
|
|
private bool _closing;
|
|
private bool _closed;
|
|
private const int ReadSize = 1024 * 4;
|
|
|
|
internal Action OnOpen { get; set; }
|
|
|
|
internal Action OnClose { get; set; }
|
|
|
|
internal Action<string> OnMessage { get; set; }
|
|
|
|
internal Action<byte[]> OnBytes { get; set; }
|
|
|
|
internal Action<byte[]> OnPing { get; set; }
|
|
|
|
internal Action<byte[]> OnPong { get; set; }
|
|
|
|
internal Action<Exception> OnError { get; set; }
|
|
|
|
internal ConnectionInfo ConnectionInfo { get; private set; }
|
|
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
private Task Send<T>(T message, Func<T, byte[]> createFrame)
|
|
{
|
|
if (Handler == null) throw new InvalidOperationException("Cannot send before handshake.");
|
|
|
|
if (!Available)
|
|
{
|
|
const string errorMessage = "Data sent while closing or after close. Ignoring.";
|
|
WebSocketLog.Warn(errorMessage);
|
|
|
|
var taskForException = new TaskCompletionSource<object>();
|
|
taskForException.SetException(new ConnectionNotAvailableException(errorMessage));
|
|
return taskForException.Task;
|
|
}
|
|
|
|
var bytes = createFrame(message);
|
|
return SendBytes(bytes);
|
|
}
|
|
|
|
/// <summary></summary>
|
|
internal void StartReceiving()
|
|
{
|
|
var data = new List<byte>(ReadSize);
|
|
var buffer = new byte[ReadSize];
|
|
Read(data, buffer);
|
|
}
|
|
|
|
internal void Close(int code)
|
|
{
|
|
if (!Available) return;
|
|
|
|
_closing = true;
|
|
|
|
if (Handler == null)
|
|
{
|
|
CloseSocket();
|
|
return;
|
|
}
|
|
|
|
var bytes = Handler.FrameClose(code);
|
|
if (bytes.Length == 0) CloseSocket();
|
|
else SendBytes(bytes, CloseSocket);
|
|
}
|
|
|
|
internal void CreateHandler(IEnumerable<byte> data)
|
|
{
|
|
var request = _parseRequest(data.ToArray());
|
|
if (request == null) return;
|
|
Handler = _handlerFactory(request);
|
|
if (Handler == null) return;
|
|
var subProtocol = _negotiateSubProtocol(request.SubProtocols);
|
|
ConnectionInfo = ConnectionInfo.Create(request, Socket.RemoteIpAddress, Socket.RemotePort, subProtocol);
|
|
|
|
_initialize(this);
|
|
|
|
var handshake = Handler.CreateHandshake(subProtocol);
|
|
SendBytes(handshake, OnOpen);
|
|
}
|
|
|
|
private void Read(List<byte> data, byte[] buffer)
|
|
{
|
|
if (!Available) return;
|
|
|
|
Socket.Receive(buffer, r =>
|
|
{
|
|
if (r <= 0)
|
|
{
|
|
WebSocketLog.Debug("0 bytes read. Closing.");
|
|
CloseSocket();
|
|
return;
|
|
}
|
|
WebSocketLog.Debug(r + " bytes read");
|
|
var readBytes = buffer.Take(r);
|
|
if (Handler != null)
|
|
{
|
|
Handler.Receive(readBytes);
|
|
}
|
|
else
|
|
{
|
|
data.AddRange(readBytes);
|
|
CreateHandler(data);
|
|
}
|
|
|
|
Read(data, buffer);
|
|
},
|
|
HandleReadError);
|
|
}
|
|
|
|
private void HandleReadError(Exception e)
|
|
{
|
|
if (e is AggregateException)
|
|
{
|
|
var agg = e as AggregateException;
|
|
HandleReadError(agg.InnerException);
|
|
return;
|
|
}
|
|
|
|
if (e is ObjectDisposedException)
|
|
{
|
|
WebSocketLog.Debug("Swallowing ObjectDisposedException", e);
|
|
return;
|
|
}
|
|
|
|
OnError(e);
|
|
|
|
if (e is WebSocketException)
|
|
{
|
|
WebSocketLog.Debug("Error while reading", e);
|
|
Close(((WebSocketException)e).StatusCode);
|
|
}
|
|
else if (e is SubProtocolNegotiationFailureException)
|
|
{
|
|
WebSocketLog.Debug(e.Message);
|
|
Close(StatusCodes.ProtocolError);
|
|
}
|
|
else if (e is IOException)
|
|
{
|
|
WebSocketLog.Debug("Error while reading", e);
|
|
Close(StatusCodes.AbnormalClosure);
|
|
}
|
|
else
|
|
{
|
|
WebSocketLog.Error("Application Error", e);
|
|
Close(StatusCodes.InternalServerError);
|
|
}
|
|
}
|
|
|
|
private Task SendBytes(byte[] bytes, Action callback = null)
|
|
{
|
|
return Socket.Send(bytes, () =>
|
|
{
|
|
WebSocketLog.Debug("Sent " + bytes.Length + " bytes");
|
|
if (callback != null) callback();
|
|
}, e =>
|
|
{
|
|
if (e is IOException) WebSocketLog.Debug("Failed to send. Disconnecting.", e);
|
|
else WebSocketLog.Info("Failed to send. Disconnecting.", e);
|
|
CloseSocket();
|
|
});
|
|
}
|
|
|
|
private void CloseSocket()
|
|
{
|
|
_closing = true;
|
|
OnClose();
|
|
_closed = true;
|
|
Socket.Close();
|
|
Socket.Dispose();
|
|
_closing = false;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region properties
|
|
|
|
/// <summary></summary>
|
|
public bool Available
|
|
{
|
|
get { return !_closing && !_closed && Socket.Connected; }
|
|
}
|
|
|
|
/// <summary>客户端 IP 地址。</summary>
|
|
public string Address
|
|
{
|
|
get { return (ConnectionInfo == null) ? "" : ConnectionInfo.ClientIpAddress; }
|
|
}
|
|
|
|
/// <summary>客户端端口。</summary>
|
|
public int Port
|
|
{
|
|
get { return (ConnectionInfo == null) ? 0 : ConnectionInfo.ClientPort; }
|
|
}
|
|
|
|
/// <summary>获取主机信息。例:
|
|
/// <para>从请求 ws://127.0.0.1:8000/page?field=value</para>
|
|
/// <para>中获取 127.0.0.1:8000</para></summary>
|
|
public string Host
|
|
{
|
|
get { return (ConnectionInfo == null) ? "" : ConnectionInfo.Host; }
|
|
}
|
|
|
|
/// <summary>获取源站信息。例:
|
|
/// <para>从请求 ws://127.0.0.1:8000/page?field=value</para>
|
|
/// <para>中获取 http://localhost</para></summary>
|
|
public string Origin
|
|
{
|
|
get { return (ConnectionInfo == null) ? "" : ConnectionInfo.Origin; }
|
|
}
|
|
|
|
/// <summary>获取 URL 查询路径。例:
|
|
/// <para>从请求 ws://127.0.0.1:8000/page?field=value</para>
|
|
/// <para>中获取 /page?field=value</para></summary>
|
|
public string QueryPath
|
|
{
|
|
get { return (ConnectionInfo == null) ? "" : ConnectionInfo.Path; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region methods
|
|
|
|
/// <summary>发送文本。</summary>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public Task Send(params char[] message)
|
|
{
|
|
if (message == null || message.Length < 1) return null;
|
|
|
|
if (message.Length == 1)
|
|
{
|
|
return Send(message[0].ToString(), Handler.FrameText);
|
|
}
|
|
else
|
|
{
|
|
var sb = new System.Text.StringBuilder();
|
|
foreach (var i in message)
|
|
{
|
|
if ((object)i != null) sb.Append(i);
|
|
}
|
|
var text = sb.ToString();
|
|
if (text.Length < 1) return null;
|
|
return Send(text, Handler.FrameText);
|
|
}
|
|
}
|
|
|
|
/// <summary>发送文本。</summary>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public Task Send(params string[] message)
|
|
{
|
|
if (message == null || message.Length < 1) return null;
|
|
if (message.Length == 1)
|
|
{
|
|
if (string.IsNullOrEmpty(message[0])) return null;
|
|
return Send(message[0], Handler.FrameText);
|
|
}
|
|
else
|
|
{
|
|
var sb = new System.Text.StringBuilder();
|
|
foreach (var i in message)
|
|
{
|
|
if (i != null) sb.Append(i);
|
|
}
|
|
var text = sb.ToString();
|
|
if (text.Length < 1) return null;
|
|
return Send(text, Handler.FrameText);
|
|
}
|
|
}
|
|
|
|
/// <summary>发送字节数组。</summary>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public Task Send(params byte[] message)
|
|
{
|
|
if (message == null || message.Length < 1) return null;
|
|
return Send(message, Handler.FrameBytes);
|
|
}
|
|
|
|
/// <summary>发送 PING。</summary>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public Task Ping(params byte[] message)
|
|
{
|
|
return Send(message, Handler.FramePing);
|
|
}
|
|
|
|
/// <summary>发送 PONG。</summary>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public Task Pong(params byte[] message)
|
|
{
|
|
return Send(message, Handler.FramePong);
|
|
}
|
|
|
|
/// <summary>关闭 Socket 连接。</summary>
|
|
public void Close()
|
|
{
|
|
Close(StatusCodes.NormalClosure);
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|