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.

182 lines
6.3 KiB

#if !NET20
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
namespace Apewer.WebSocket
{
internal class WebSocketServer : IDisposable
{
private readonly string _scheme;
private readonly IPAddress _locationIP;
private Action<Connection> _config;
/// <summary></summary>
/// <exception cref="FormatException"></exception>
public WebSocketServer(string location, bool supportDualStack = true)
{
var uri = new Uri(location);
Port = uri.Port;
Location = location;
SupportDualStack = supportDualStack;
_locationIP = ParseIPAddress(uri);
_scheme = uri.Scheme;
var socket = new Socket(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP);
if (SupportDualStack)
{
if (!Runtime.IsRunningOnMono() && Runtime.IsRunningOnWindows())
{
socket.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only, false);
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
}
}
ListenerSocket = new SocketWrapper(socket);
SupportedSubProtocols = new string[0];
}
public SocketWrapper ListenerSocket { get; set; }
public string Location { get; private set; }
public bool SupportDualStack { get; }
public int Port { get; private set; }
public X509Certificate2 Certificate { get; set; }
public SslProtocols EnabledSslProtocols { get; set; }
public IEnumerable<string> SupportedSubProtocols { get; set; }
public bool RestartAfterListenError { get; set; }
public bool IsSecure
{
get { return _scheme == "wss" && Certificate != null; }
}
public void Dispose()
{
ListenerSocket.Dispose();
}
/// <summary></summary>
/// <exception cref="FormatException"></exception>
private IPAddress ParseIPAddress(Uri uri)
{
string ipStr = uri.Host;
if (ipStr == "0.0.0.0")
{
return IPAddress.Any;
}
else if (ipStr == "[0000:0000:0000:0000:0000:0000:0000:0000]")
{
return IPAddress.IPv6Any;
}
else
{
try
{
return IPAddress.Parse(ipStr);
}
catch (Exception ex)
{
throw new FormatException("Failed to parse the IP address part of the location. Please make sure you specify a valid IP address. Use 0.0.0.0 or [::] to listen on all interfaces.", ex);
}
}
}
public void Start(Action<Connection> config)
{
var ipLocal = new IPEndPoint(_locationIP, Port);
ListenerSocket.Bind(ipLocal);
ListenerSocket.Listen(100);
Port = ((IPEndPoint)ListenerSocket.LocalEndPoint).Port;
WebSocketLog.Info(string.Format("Server started at {0} (actual port {1})", Location, Port));
if (_scheme == "wss")
{
if (Certificate == null)
{
WebSocketLog.Error("Scheme cannot be 'wss' without a Certificate");
return;
}
if (EnabledSslProtocols == SslProtocols.None)
{
EnabledSslProtocols = SslProtocols.Tls;
WebSocketLog.Debug("Using default TLS 1.0 security protocol.");
}
}
ListenForClients();
_config = config;
}
private void ListenForClients()
{
ListenerSocket.Accept(OnClientConnect, e =>
{
WebSocketLog.Error("Listener socket is closed", e);
if (RestartAfterListenError)
{
WebSocketLog.Info("Listener socket restarting");
try
{
ListenerSocket.Dispose();
var socket = new Socket(_locationIP.AddressFamily, SocketType.Stream, ProtocolType.IP);
ListenerSocket = new SocketWrapper(socket);
Start(_config);
WebSocketLog.Info("Listener socket restarted");
}
catch (Exception ex)
{
WebSocketLog.Error("Listener could not be restarted", ex);
}
}
});
}
private void OnClientConnect(SocketWrapper clientSocket)
{
if (clientSocket == null) return; // socket closed
WebSocketLog.Debug(String.Format("Client connected from {0}:{1}", clientSocket.RemoteIpAddress, clientSocket.RemotePort.ToString()));
ListenForClients();
Connection connection = null;
connection = new Connection(
clientSocket,
_config,
bytes => RequestParser.Parse(bytes, _scheme),
r => HandlerFactory.BuildHandler(r,
s => connection.OnMessage(s),
connection.Close,
b => connection.OnBytes(b),
b => connection.OnPing(b),
b => connection.OnPong(b)),
s => SubProtocolNegotiator.Negotiate(SupportedSubProtocols, s));
if (IsSecure)
{
WebSocketLog.Debug("Authenticating Secure Connection");
clientSocket
.Authenticate(Certificate,
EnabledSslProtocols,
connection.StartReceiving,
e => WebSocketLog.Warn("Failed to Authenticate", e));
}
else
{
connection.StartReceiving();
}
}
}
}
#endif