Refactored WebSocketServer.cs

This commit is contained in:
sta 2014-08-29 19:49:04 +09:00
parent 532b818e2e
commit 83869d6750

View File

@ -60,7 +60,7 @@ namespace WebSocketSharp.Server
private System.Net.IPAddress _address; private System.Net.IPAddress _address;
private AuthenticationSchemes _authSchemes; private AuthenticationSchemes _authSchemes;
private X509Certificate2 _cert; private X509Certificate2 _certificate;
private Func<IIdentity, NetworkCredential> _credentialsFinder; private Func<IIdentity, NetworkCredential> _credentialsFinder;
private TcpListener _listener; private TcpListener _listener;
private Logger _logger; private Logger _logger;
@ -82,8 +82,8 @@ namespace WebSocketSharp.Server
/// Initializes a new instance of the <see cref="WebSocketServer"/> class. /// Initializes a new instance of the <see cref="WebSocketServer"/> class.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// An instance initialized by this constructor listens for the incoming connection requests on /// An instance initialized by this constructor listens for the incoming connection requests
/// port 80. /// on port 80.
/// </remarks> /// </remarks>
public WebSocketServer () public WebSocketServer ()
: this (80) : this (80)
@ -100,7 +100,7 @@ namespace WebSocketSharp.Server
/// on <paramref name="port"/>. /// on <paramref name="port"/>.
/// </para> /// </para>
/// <para> /// <para>
/// And if <paramref name="port"/> is 443, that instance provides a secure connection. /// If <paramref name="port"/> is 443, that instance provides a secure connection.
/// </para> /// </para>
/// </remarks> /// </remarks>
/// <param name="port"> /// <param name="port">
@ -121,23 +121,23 @@ namespace WebSocketSharp.Server
/// <remarks> /// <remarks>
/// <para> /// <para>
/// An instance initialized by this constructor listens for the incoming connection requests /// An instance initialized by this constructor listens for the incoming connection requests
/// on the port (if any) in <paramref name="url"/>. /// on the port in <paramref name="url"/>.
/// </para> /// </para>
/// <para> /// <para>
/// So if <paramref name="url"/> is without a port, either port 80 or 443 is used on which to /// If <paramref name="url"/> doesn't include a port, either port 80 or 443 is used on which
/// listen. It's determined by the scheme (ws or wss) in <paramref name="url"/>. (port 80 if /// to listen. It's determined by the scheme (ws or wss) in <paramref name="url"/>.
/// the scheme is ws.) /// (Port 80 if the scheme is ws.)
/// </para> /// </para>
/// </remarks> /// </remarks>
/// <param name="url"> /// <param name="url">
/// A <see cref="string"/> that represents the WebSocket URL of the server. /// A <see cref="string"/> that represents the WebSocket URL of the server.
/// </param> /// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="url"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// <paramref name="url"/> is invalid. /// <paramref name="url"/> is invalid.
/// </exception> /// </exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="url"/> is <see langword="null"/>.
/// </exception>
public WebSocketServer (string url) public WebSocketServer (string url)
{ {
if (url == null) if (url == null)
@ -150,7 +150,7 @@ namespace WebSocketSharp.Server
var host = _uri.DnsSafeHost; var host = _uri.DnsSafeHost;
_address = host.ToIPAddress (); _address = host.ToIPAddress ();
if (_address == null || !_address.IsLocal ()) if (_address == null || !_address.IsLocal ())
throw new ArgumentException ("The host part must be the local host name: " + host, "url"); throw new ArgumentException ("The host part isn't a local host name: " + host, "url");
_port = _uri.Port; _port = _uri.Port;
_secure = _uri.Scheme == "wss"; _secure = _uri.Scheme == "wss";
@ -163,22 +163,22 @@ namespace WebSocketSharp.Server
/// <paramref name="port"/> and <paramref name="secure"/>. /// <paramref name="port"/> and <paramref name="secure"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// An instance initialized by this constructor listens for the incoming connection requests on /// An instance initialized by this constructor listens for the incoming connection requests
/// <paramref name="port"/>. /// on <paramref name="port"/>.
/// </remarks> /// </remarks>
/// <param name="port"> /// <param name="port">
/// An <see cref="int"/> that represents the port number on which to listen. /// An <see cref="int"/> that represents the port number on which to listen.
/// </param> /// </param>
/// <param name="secure"> /// <param name="secure">
/// A <see cref="bool"/> that indicates providing a secure connection or not. (<c>true</c> /// A <see cref="bool"/> that indicates providing a secure connection or not.
/// indicates providing a secure connection.) /// (<c>true</c> indicates providing a secure connection.)
/// </param> /// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="port"/> isn't between 1 and 65535.
/// </exception>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// Pair of <paramref name="port"/> and <paramref name="secure"/> is invalid. /// Pair of <paramref name="port"/> and <paramref name="secure"/> is invalid.
/// </exception> /// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="port"/> isn't between 1 and 65535.
/// </exception>
public WebSocketServer (int port, bool secure) public WebSocketServer (int port, bool secure)
: this (System.Net.IPAddress.Any, port, secure) : this (System.Net.IPAddress.Any, port, secure)
{ {
@ -194,7 +194,7 @@ namespace WebSocketSharp.Server
/// on <paramref name="port"/>. /// on <paramref name="port"/>.
/// </para> /// </para>
/// <para> /// <para>
/// And if <paramref name="port"/> is 443, that instance provides a secure connection. /// If <paramref name="port"/> is 443, that instance provides a secure connection.
/// </para> /// </para>
/// </remarks> /// </remarks>
/// <param name="address"> /// <param name="address">
@ -203,15 +203,15 @@ namespace WebSocketSharp.Server
/// <param name="port"> /// <param name="port">
/// An <see cref="int"/> that represents the port number on which to listen. /// An <see cref="int"/> that represents the port number on which to listen.
/// </param> /// </param>
/// <exception cref="ArgumentException">
/// <paramref name="address"/> isn't a local IP address.
/// </exception>
/// <exception cref="ArgumentNullException"> /// <exception cref="ArgumentNullException">
/// <paramref name="address"/> is <see langword="null"/>. /// <paramref name="address"/> is <see langword="null"/>.
/// </exception> /// </exception>
/// <exception cref="ArgumentOutOfRangeException"> /// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="port"/> isn't between 1 and 65535. /// <paramref name="port"/> isn't between 1 and 65535.
/// </exception> /// </exception>
/// <exception cref="ArgumentException">
/// <paramref name="address"/> isn't a local IP address.
/// </exception>
public WebSocketServer (System.Net.IPAddress address, int port) public WebSocketServer (System.Net.IPAddress address, int port)
: this (address, port, port == 443) : this (address, port, port == 443)
{ {
@ -222,8 +222,8 @@ namespace WebSocketSharp.Server
/// <paramref name="address"/>, <paramref name="port"/>, and <paramref name="secure"/>. /// <paramref name="address"/>, <paramref name="port"/>, and <paramref name="secure"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// An instance initialized by this constructor listens for the incoming connection requests on /// An instance initialized by this constructor listens for the incoming connection requests
/// <paramref name="port"/>. /// on <paramref name="port"/>.
/// </remarks> /// </remarks>
/// <param name="address"> /// <param name="address">
/// A <see cref="System.Net.IPAddress"/> that represents the local IP address of the server. /// A <see cref="System.Net.IPAddress"/> that represents the local IP address of the server.
@ -232,15 +232,9 @@ namespace WebSocketSharp.Server
/// An <see cref="int"/> that represents the port number on which to listen. /// An <see cref="int"/> that represents the port number on which to listen.
/// </param> /// </param>
/// <param name="secure"> /// <param name="secure">
/// A <see cref="bool"/> that indicates providing a secure connection or not. (<c>true</c> /// A <see cref="bool"/> that indicates providing a secure connection or not.
/// indicates providing a secure connection.) /// (<c>true</c> indicates providing a secure connection.)
/// </param> /// </param>
/// <exception cref="ArgumentNullException">
/// <paramref name="address"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="port"/> isn't between 1 and 65535.
/// </exception>
/// <exception cref="ArgumentException"> /// <exception cref="ArgumentException">
/// <para> /// <para>
/// <paramref name="address"/> isn't a local IP address. /// <paramref name="address"/> isn't a local IP address.
@ -252,17 +246,23 @@ namespace WebSocketSharp.Server
/// Pair of <paramref name="port"/> and <paramref name="secure"/> is invalid. /// Pair of <paramref name="port"/> and <paramref name="secure"/> is invalid.
/// </para> /// </para>
/// </exception> /// </exception>
/// <exception cref="ArgumentNullException">
/// <paramref name="address"/> is <see langword="null"/>.
/// </exception>
/// <exception cref="ArgumentOutOfRangeException">
/// <paramref name="port"/> isn't between 1 and 65535.
/// </exception>
public WebSocketServer (System.Net.IPAddress address, int port, bool secure) public WebSocketServer (System.Net.IPAddress address, int port, bool secure)
{ {
if (!address.IsLocal ()) if (!address.IsLocal ())
throw new ArgumentException ("Must be the local IP address: " + address, "address"); throw new ArgumentException ("Not a local IP address: " + address, "address");
if (!port.IsPortNumber ()) if (!port.IsPortNumber ())
throw new ArgumentOutOfRangeException ("port", "Must be between 1 and 65535: " + port); throw new ArgumentOutOfRangeException ("port", "Not between 1 and 65535: " + port);
if ((port == 80 && secure) || (port == 443 && !secure)) if ((port == 80 && secure) || (port == 443 && !secure))
throw new ArgumentException ( throw new ArgumentException (
String.Format ("Invalid pair of 'port' and 'secure': {0}, {1}", port, secure)); String.Format ("An invalid pair of 'port' and 'secure': {0}, {1}", port, secure));
_address = address; _address = address;
_port = port; _port = port;
@ -292,9 +292,9 @@ namespace WebSocketSharp.Server
/// Gets or sets the scheme used to authenticate the clients. /// Gets or sets the scheme used to authenticate the clients.
/// </summary> /// </summary>
/// <value> /// <value>
/// One of the <see cref="WebSocketSharp.Net.AuthenticationSchemes"/> enum values, indicates /// One of the <see cref="WebSocketSharp.Net.AuthenticationSchemes"/> enum values,
/// the scheme used to authenticate the clients. /// indicates the scheme used to authenticate the clients. The default value is
/// The default value is <see cref="WebSocketSharp.Net.AuthenticationSchemes.Anonymous"/>. /// <see cref="WebSocketSharp.Net.AuthenticationSchemes.Anonymous"/>.
/// </value> /// </value>
public AuthenticationSchemes AuthenticationSchemes { public AuthenticationSchemes AuthenticationSchemes {
get { get {
@ -313,18 +313,19 @@ namespace WebSocketSharp.Server
/// Gets or sets the certificate used to authenticate the server on the secure connection. /// Gets or sets the certificate used to authenticate the server on the secure connection.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="X509Certificate2"/> used to authenticate the server. /// A <see cref="X509Certificate2"/> that represents the certificate used to authenticate
/// the server.
/// </value> /// </value>
public X509Certificate2 Certificate { public X509Certificate2 Certificate {
get { get {
return _cert; return _certificate;
} }
set { set {
if (!canSet ("Certificate")) if (!canSet ("Certificate"))
return; return;
_cert = value; _certificate = value;
} }
} }
@ -403,8 +404,8 @@ namespace WebSocketSharp.Server
/// Gets or sets the name of the realm associated with the server. /// Gets or sets the name of the realm associated with the server.
/// </summary> /// </summary>
/// <value> /// <value>
/// A <see cref="string"/> that represents the name of the realm. The default value is /// A <see cref="string"/> that represents the name of the realm.
/// <c>SECRET AREA</c>. /// The default value is <c>"SECRET AREA"</c>.
/// </value> /// </value>
public string Realm { public string Realm {
get { get {
@ -503,71 +504,24 @@ namespace WebSocketSharp.Server
_state = ServerState.Stop; _state = ServerState.Stop;
} }
private void acceptRequestAsync (TcpClient client)
{
ThreadPool.QueueUserWorkItem (
state => {
try {
var context = client.GetWebSocketContext (null, _secure, _cert, _logger);
if (_authSchemes != AuthenticationSchemes.Anonymous &&
!authenticateRequest (_authSchemes, context))
return;
acceptWebSocket (context);
}
catch (Exception ex) {
_logger.Fatal (ex.ToString ());
client.Close ();
}
});
}
private void acceptWebSocket (TcpListenerWebSocketContext context)
{
var reqUri = context.RequestUri;
if (reqUri == null) {
context.Close (HttpStatusCode.BadRequest);
return;
}
if (_uri.IsAbsoluteUri) {
var req = reqUri.DnsSafeHost;
var expected = _uri.DnsSafeHost;
if (Uri.CheckHostName (req) == UriHostNameType.Dns &&
Uri.CheckHostName (expected) == UriHostNameType.Dns &&
req != expected) {
context.Close (HttpStatusCode.NotFound);
return;
}
}
WebSocketServiceHost host;
if (!_services.TryGetServiceHostInternally (reqUri.AbsolutePath, out host)) {
context.Close (HttpStatusCode.NotImplemented);
return;
}
host.StartSession (context);
}
private bool authenticateRequest ( private bool authenticateRequest (
AuthenticationSchemes scheme, TcpListenerWebSocketContext context) AuthenticationSchemes scheme, TcpListenerWebSocketContext context)
{ {
var challenge = scheme == AuthenticationSchemes.Basic var chal = scheme == AuthenticationSchemes.Basic
? AuthenticationChallenge.CreateBasicChallenge (Realm).ToBasicString () ? AuthenticationChallenge.CreateBasicChallenge (Realm).ToBasicString ()
: scheme == AuthenticationSchemes.Digest : scheme == AuthenticationSchemes.Digest
? AuthenticationChallenge.CreateDigestChallenge (Realm).ToDigestString () ? AuthenticationChallenge.CreateDigestChallenge (Realm).ToDigestString ()
: null; : null;
if (challenge == null) { if (chal == null) {
context.Close (HttpStatusCode.Forbidden); context.Close (HttpStatusCode.Forbidden);
return false; return false;
} }
var retry = -1; var retry = -1;
var expected = scheme.ToString (); var schm = scheme.ToString ();
var realm = Realm; var realm = Realm;
var credentialsFinder = UserCredentialsFinder; var credFinder = UserCredentialsFinder;
Func<bool> auth = null; Func<bool> auth = null;
auth = () => { auth = () => {
retry++; retry++;
@ -576,17 +530,17 @@ namespace WebSocketSharp.Server
return false; return false;
} }
var header = context.Headers ["Authorization"]; var res = context.Headers["Authorization"];
if (header == null || !header.StartsWith (expected, StringComparison.OrdinalIgnoreCase)) { if (res == null || !res.StartsWith (schm, StringComparison.OrdinalIgnoreCase)) {
context.SendAuthenticationChallenge (challenge); context.SendAuthenticationChallenge (chal);
return auth (); return auth ();
} }
context.SetUser (scheme, realm, credentialsFinder); context.SetUser (scheme, realm, credFinder);
if (context.IsAuthenticated) if (context.IsAuthenticated)
return true; return true;
context.SendAuthenticationChallenge (challenge); context.SendAuthenticationChallenge (chal);
return auth (); return auth ();
}; };
@ -607,9 +561,9 @@ namespace WebSocketSharp.Server
return true; return true;
} }
private string checkIfCertExists () private string checkIfCertificateExists ()
{ {
return _secure && _cert == null return _secure && _certificate == null
? "The secure connection requires a server certificate." ? "The secure connection requires a server certificate."
: null; : null;
} }
@ -624,11 +578,58 @@ namespace WebSocketSharp.Server
_sync = new object (); _sync = new object ();
} }
private void processRequestAsync (TcpClient tcpClient)
{
ThreadPool.QueueUserWorkItem (
state => {
try {
var context = tcpClient.GetWebSocketContext (null, _secure, _certificate, _logger);
if (_authSchemes != AuthenticationSchemes.Anonymous &&
!authenticateRequest (_authSchemes, context))
return;
processWebSocketRequest (context);
}
catch (Exception ex) {
_logger.Fatal (ex.ToString ());
tcpClient.Close ();
}
});
}
private void processWebSocketRequest (TcpListenerWebSocketContext context)
{
var uri = context.RequestUri;
if (uri == null) {
context.Close (HttpStatusCode.BadRequest);
return;
}
if (_uri.IsAbsoluteUri) {
var actual = uri.DnsSafeHost;
var expected = _uri.DnsSafeHost;
if (Uri.CheckHostName (actual) == UriHostNameType.Dns &&
Uri.CheckHostName (expected) == UriHostNameType.Dns &&
actual != expected) {
context.Close (HttpStatusCode.NotFound);
return;
}
}
WebSocketServiceHost host;
if (!_services.TryGetServiceHostInternally (uri.AbsolutePath, out host)) {
context.Close (HttpStatusCode.NotImplemented);
return;
}
host.StartSession (context);
}
private void receiveRequest () private void receiveRequest ()
{ {
while (true) { while (true) {
try { try {
acceptRequestAsync (_listener.AcceptTcpClient ()); processRequestAsync (_listener.AcceptTcpClient ());
} }
catch (SocketException ex) { catch (SocketException ex) {
_logger.Warn ("Receiving has been stopped.\nreason: " + ex.Message); _logger.Warn ("Receiving has been stopped.\nreason: " + ex.Message);
@ -664,7 +665,7 @@ namespace WebSocketSharp.Server
if (result.PathAndQuery != "/") { if (result.PathAndQuery != "/") {
result = null; result = null;
message = "Must not contain the path or query component: " + uriString; message = "Includes the path or query component: " + uriString;
return false; return false;
} }
@ -687,9 +688,8 @@ namespace WebSocketSharp.Server
/// A <see cref="string"/> that represents the absolute path to the WebSocket service to add. /// A <see cref="string"/> that represents the absolute path to the WebSocket service to add.
/// </param> /// </param>
/// <typeparam name="TWithNew"> /// <typeparam name="TWithNew">
/// The type of the WebSocket service. /// The type of the WebSocket service. The TWithNew must inherit
/// The TWithNew must inherit the <see cref="WebSocketService"/> class and must have a public /// the <see cref="WebSocketService"/> class and must have a public parameterless constructor.
/// parameterless constructor.
/// </typeparam> /// </typeparam>
public void AddWebSocketService<TWithNew> (string path) public void AddWebSocketService<TWithNew> (string path)
where TWithNew : WebSocketService, new () where TWithNew : WebSocketService, new ()
@ -698,8 +698,8 @@ namespace WebSocketSharp.Server
} }
/// <summary> /// <summary>
/// Adds the specified typed WebSocket service with the specified <paramref name="path"/> /// Adds the specified typed WebSocket service with the specified <paramref name="path"/> and
/// and <paramref name="constructor"/>. /// <paramref name="initializer"/>.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// <para> /// <para>
@ -707,14 +707,14 @@ namespace WebSocketSharp.Server
/// from tail end of <paramref name="path"/>. /// from tail end of <paramref name="path"/>.
/// </para> /// </para>
/// <para> /// <para>
/// <paramref name="constructor"/> returns a initialized specified typed /// <paramref name="initializer"/> returns an initialized specified typed
/// <see cref="WebSocketService"/> instance. /// <see cref="WebSocketService"/> instance.
/// </para> /// </para>
/// </remarks> /// </remarks>
/// <param name="path"> /// <param name="path">
/// A <see cref="string"/> that represents the absolute path to the WebSocket service to add. /// A <see cref="string"/> that represents the absolute path to the WebSocket service to add.
/// </param> /// </param>
/// <param name="constructor"> /// <param name="initializer">
/// A Func&lt;T&gt; delegate that references the method used to initialize a new specified /// A Func&lt;T&gt; delegate that references the method used to initialize a new specified
/// typed <see cref="WebSocketService"/> instance (a new <see cref="IWebSocketSession"/> /// typed <see cref="WebSocketService"/> instance (a new <see cref="IWebSocketSession"/>
/// instance). /// instance).
@ -723,18 +723,18 @@ namespace WebSocketSharp.Server
/// The type of the WebSocket service. The T must inherit the <see cref="WebSocketService"/> /// The type of the WebSocket service. The T must inherit the <see cref="WebSocketService"/>
/// class. /// class.
/// </typeparam> /// </typeparam>
public void AddWebSocketService<T> (string path, Func<T> constructor) public void AddWebSocketService<T> (string path, Func<T> initializer)
where T : WebSocketService where T : WebSocketService
{ {
var msg = path.CheckIfValidServicePath () ?? var msg = path.CheckIfValidServicePath () ??
(constructor == null ? "'constructor' must not be null." : null); (initializer == null ? "'initializer' is null." : null);
if (msg != null) { if (msg != null) {
_logger.Error (String.Format ("{0}\nservice path: {1}", msg, path)); _logger.Error (String.Format ("{0}\nservice path: {1}", msg, path));
return; return;
} }
var host = new WebSocketServiceHost<T> (path, constructor, _logger); var host = new WebSocketServiceHost<T> (path, initializer, _logger);
if (!KeepClean) if (!KeepClean)
host.KeepClean = false; host.KeepClean = false;
@ -772,7 +772,7 @@ namespace WebSocketSharp.Server
public void Start () public void Start ()
{ {
lock (_sync) { lock (_sync) {
var msg = _state.CheckIfStartable () ?? checkIfCertExists (); var msg = _state.CheckIfStartable () ?? checkIfCertificateExists ();
if (msg != null) { if (msg != null) {
_logger.Error (String.Format ("{0}\nstate: {1}\nsecure: {2}", msg, _state, _secure)); _logger.Error (String.Format ("{0}\nstate: {1}\nsecure: {2}", msg, _state, _secure));
return; return;
@ -846,8 +846,8 @@ namespace WebSocketSharp.Server
/// <see cref="CloseStatusCode"/> and <see cref="string"/>. /// <see cref="CloseStatusCode"/> and <see cref="string"/>.
/// </summary> /// </summary>
/// <param name="code"> /// <param name="code">
/// One of the <see cref="CloseStatusCode"/> enum values, represents the status code indicating /// One of the <see cref="CloseStatusCode"/> enum values, represents the status code
/// the reason for stop. /// indicating the reason for stop.
/// </param> /// </param>
/// <param name="reason"> /// <param name="reason">
/// A <see cref="string"/> that represents the reason for stop. /// A <see cref="string"/> that represents the reason for stop.