websocket-server – Rev 1
?pathlinks?
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using WebSockets.Common;
using System.Diagnostics;
using System.Security.Policy;
using WebSockets.Exceptions;
using WebSockets.Server.WebSocket;
using WebSockets.Server.Http;
using System.Threading;
using WebSockets.Events;
using System.Threading.Tasks;
using System.Security.Cryptography.X509Certificates;
namespace WebSockets.Client
{
public class WebSocketClient : WebSocketBase, IDisposable
{
private readonly bool _noDelay;
private readonly IWebSocketLogger _logger;
private TcpClient _tcpClient;
private Stream _stream;
private Uri _uri;
private ManualResetEvent _conectionCloseWait;
public WebSocketClient(bool noDelay, IWebSocketLogger logger)
: base(logger)
{
_noDelay = noDelay;
_logger = logger;
_conectionCloseWait = new ManualResetEvent(false);
}
// The following method is invoked by the RemoteCertificateValidationDelegate.
public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
{
if (sslPolicyErrors == SslPolicyErrors.None)
{
return true;
}
Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
// Do not allow this client to communicate with unauthenticated servers.
return false;
}
private Stream GetStream(TcpClient tcpClient, bool isSecure)
{
if (isSecure)
{
SslStream sslStream = new SslStream(tcpClient.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
_logger.Information(this.GetType(), "Attempting to secure connection...");
sslStream.AuthenticateAsClient("clusteredanalytics.com");
_logger.Information(this.GetType(), "Connection successfully secured.");
return sslStream;
}
else
{
_logger.Information(this.GetType(), "Connection not secure");
return tcpClient.GetStream();
}
}
public virtual void OpenBlocking(Uri uri)
{
if (!_isOpen)
{
string host = uri.Host;
int port = uri.Port;
_tcpClient = new TcpClient();
_tcpClient.NoDelay = _noDelay;
IPAddress ipAddress;
if (IPAddress.TryParse(host, out ipAddress))
{
_tcpClient.Connect(ipAddress, port);
}
else
{
_tcpClient.Connect(host, port);
}
bool isSecure = port == 443;
_stream = GetStream(_tcpClient, isSecure);
_uri = uri;
_isOpen = true;
base.OpenBlocking(_stream, _tcpClient.Client);
_isOpen = false;
}
}
protected override void PerformHandshake(Stream stream)
{
Uri uri = _uri;
WebSocketFrameReader reader = new WebSocketFrameReader();
Random rand = new Random();
byte[] keyAsBytes = new byte[16];
rand.NextBytes(keyAsBytes);
string secWebSocketKey = Convert.ToBase64String(keyAsBytes);
string handshakeHttpRequestTemplate = @"GET {0} HTTP/1.1{4}" +
"Host: {1}:{2}{4}" +
"Upgrade: websocket{4}" +
"Connection: Upgrade{4}" +
"Sec-WebSocket-Key: {3}{4}" +
"Sec-WebSocket-Version: 13{4}{4}";
string handshakeHttpRequest = string.Format(handshakeHttpRequestTemplate, uri.PathAndQuery, uri.Host, uri.Port, secWebSocketKey, Environment.NewLine);
byte[] httpRequest = Encoding.UTF8.GetBytes(handshakeHttpRequest);
stream.Write(httpRequest, 0, httpRequest.Length);
_logger.Information(this.GetType(), "Handshake sent. Waiting for response.");
// make sure we escape the accept string which could contain special regex characters
string regexPattern = "Sec-WebSocket-Accept: (.*)";
Regex regex = new Regex(regexPattern);
string response = string.Empty;
try
{
response = HttpHelper.ReadHttpHeader(stream);
}
catch (Exception ex)
{
throw new WebSocketHandshakeFailedException("Handshake unexpected failure", ex);
}
// check the accept string
string expectedAcceptString = base.ComputeSocketAcceptString(secWebSocketKey);
string actualAcceptString = regex.Match(response).Groups[1].Value.Trim();
if (expectedAcceptString != actualAcceptString)
{
throw new WebSocketHandshakeFailedException(string.Format("Handshake failed because the accept string from the server '{0}' was not the expected string '{1}'", expectedAcceptString, actualAcceptString));
}
else
{
_logger.Information(this.GetType(), "Handshake response received. Connection upgraded to WebSocket protocol.");
}
}
public virtual void Dispose()
{
if (_isOpen)
{
using (MemoryStream stream = new MemoryStream())
{
// set the close reason to GoingAway
BinaryReaderWriter.WriteUShort((ushort) WebSocketCloseCode.GoingAway, stream, false);
// send close message to server to begin the close handshake
Send(WebSocketOpCode.ConnectionClose, stream.ToArray());
_logger.Information(this.GetType(), "Sent websocket close message to server. Reason: GoingAway");
}
// this needs to run on a worker thread so that the read loop (in the base class) is not blocked
Task.Factory.StartNew(WaitForServerCloseMessage);
}
}
private void WaitForServerCloseMessage()
{
// as per the websocket spec, the server must close the connection, not the client.
// The client is free to close the connection after a timeout period if the server fails to do so
_conectionCloseWait.WaitOne(TimeSpan.FromSeconds(10));
// this will only happen if the server has failed to reply with a close response
if (_isOpen)
{
_logger.Warning(this.GetType(), "Server failed to respond with a close response. Closing the connection from the client side.");
// wait for data to be sent before we close the stream and client
_tcpClient.Client.Shutdown(SocketShutdown.Both);
_stream.Close();
_tcpClient.Close();
}
_logger.Information(this.GetType(), "Client: Connection closed");
}
protected override void OnConnectionClose(byte[] payload)
{
// server has either responded to a client close request or closed the connection for its own reasons
// the server will close the tcp connection so the client will not have to do it
_isOpen = false;
_conectionCloseWait.Set();
base.OnConnectionClose(payload);
}
}
}