websocket-server – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 using System;
2 using System.Collections.Generic;
3 using System.IO;
4 using System.Linq;
5 using System.Net;
6 using System.Net.Security;
7 using System.Net.Sockets;
8 using System.Text;
9 using System.Text.RegularExpressions;
10 using WebSockets.Common;
11 using System.Diagnostics;
12 using System.Security.Policy;
13 using WebSockets.Exceptions;
14 using WebSockets.Server.WebSocket;
15 using WebSockets.Server.Http;
16 using System.Threading;
17 using WebSockets.Events;
18 using System.Threading.Tasks;
19 using System.Security.Cryptography.X509Certificates;
20  
21 namespace WebSockets.Client
22 {
23 public class WebSocketClient : WebSocketBase, IDisposable
24 {
25 private readonly bool _noDelay;
26 private readonly IWebSocketLogger _logger;
27 private TcpClient _tcpClient;
28 private Stream _stream;
29 private Uri _uri;
30 private ManualResetEvent _conectionCloseWait;
31  
32 public WebSocketClient(bool noDelay, IWebSocketLogger logger)
33 : base(logger)
34 {
35 _noDelay = noDelay;
36 _logger = logger;
37 _conectionCloseWait = new ManualResetEvent(false);
38 }
39  
40 // The following method is invoked by the RemoteCertificateValidationDelegate.
41 public static bool ValidateServerCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
42 {
43 if (sslPolicyErrors == SslPolicyErrors.None)
44 {
45 return true;
46 }
47  
48 Console.WriteLine("Certificate error: {0}", sslPolicyErrors);
49  
50 // Do not allow this client to communicate with unauthenticated servers.
51 return false;
52 }
53  
54 private Stream GetStream(TcpClient tcpClient, bool isSecure)
55 {
56 if (isSecure)
57 {
58 SslStream sslStream = new SslStream(tcpClient.GetStream(), false, new RemoteCertificateValidationCallback(ValidateServerCertificate), null);
59 _logger.Information(this.GetType(), "Attempting to secure connection...");
60 sslStream.AuthenticateAsClient("clusteredanalytics.com");
61 _logger.Information(this.GetType(), "Connection successfully secured.");
62 return sslStream;
63 }
64 else
65 {
66 _logger.Information(this.GetType(), "Connection not secure");
67 return tcpClient.GetStream();
68 }
69 }
70  
71 public virtual void OpenBlocking(Uri uri)
72 {
73 if (!_isOpen)
74 {
75 string host = uri.Host;
76 int port = uri.Port;
77 _tcpClient = new TcpClient();
78 _tcpClient.NoDelay = _noDelay;
79  
80 IPAddress ipAddress;
81 if (IPAddress.TryParse(host, out ipAddress))
82 {
83 _tcpClient.Connect(ipAddress, port);
84 }
85 else
86 {
87 _tcpClient.Connect(host, port);
88 }
89  
90 bool isSecure = port == 443;
91 _stream = GetStream(_tcpClient, isSecure);
92  
93 _uri = uri;
94 _isOpen = true;
95 base.OpenBlocking(_stream, _tcpClient.Client);
96 _isOpen = false;
97 }
98 }
99  
100 protected override void PerformHandshake(Stream stream)
101 {
102 Uri uri = _uri;
103 WebSocketFrameReader reader = new WebSocketFrameReader();
104 Random rand = new Random();
105 byte[] keyAsBytes = new byte[16];
106 rand.NextBytes(keyAsBytes);
107 string secWebSocketKey = Convert.ToBase64String(keyAsBytes);
108  
109 string handshakeHttpRequestTemplate = @"GET {0} HTTP/1.1{4}" +
110 "Host: {1}:{2}{4}" +
111 "Upgrade: websocket{4}" +
112 "Connection: Upgrade{4}" +
113 "Sec-WebSocket-Key: {3}{4}" +
114 "Sec-WebSocket-Version: 13{4}{4}";
115  
116 string handshakeHttpRequest = string.Format(handshakeHttpRequestTemplate, uri.PathAndQuery, uri.Host, uri.Port, secWebSocketKey, Environment.NewLine);
117 byte[] httpRequest = Encoding.UTF8.GetBytes(handshakeHttpRequest);
118 stream.Write(httpRequest, 0, httpRequest.Length);
119 _logger.Information(this.GetType(), "Handshake sent. Waiting for response.");
120  
121 // make sure we escape the accept string which could contain special regex characters
122 string regexPattern = "Sec-WebSocket-Accept: (.*)";
123 Regex regex = new Regex(regexPattern);
124  
125 string response = string.Empty;
126  
127 try
128 {
129 response = HttpHelper.ReadHttpHeader(stream);
130 }
131 catch (Exception ex)
132 {
133 throw new WebSocketHandshakeFailedException("Handshake unexpected failure", ex);
134 }
135  
136 // check the accept string
137 string expectedAcceptString = base.ComputeSocketAcceptString(secWebSocketKey);
138 string actualAcceptString = regex.Match(response).Groups[1].Value.Trim();
139 if (expectedAcceptString != actualAcceptString)
140 {
141 throw new WebSocketHandshakeFailedException(string.Format("Handshake failed because the accept string from the server '{0}' was not the expected string '{1}'", expectedAcceptString, actualAcceptString));
142 }
143 else
144 {
145 _logger.Information(this.GetType(), "Handshake response received. Connection upgraded to WebSocket protocol.");
146 }
147 }
148  
149 public virtual void Dispose()
150 {
151 if (_isOpen)
152 {
153 using (MemoryStream stream = new MemoryStream())
154 {
155 // set the close reason to GoingAway
156 BinaryReaderWriter.WriteUShort((ushort) WebSocketCloseCode.GoingAway, stream, false);
157  
158 // send close message to server to begin the close handshake
159 Send(WebSocketOpCode.ConnectionClose, stream.ToArray());
160 _logger.Information(this.GetType(), "Sent websocket close message to server. Reason: GoingAway");
161 }
162  
163 // this needs to run on a worker thread so that the read loop (in the base class) is not blocked
164 Task.Factory.StartNew(WaitForServerCloseMessage);
165 }
166 }
167  
168 private void WaitForServerCloseMessage()
169 {
170 // as per the websocket spec, the server must close the connection, not the client.
171 // The client is free to close the connection after a timeout period if the server fails to do so
172 _conectionCloseWait.WaitOne(TimeSpan.FromSeconds(10));
173  
174 // this will only happen if the server has failed to reply with a close response
175 if (_isOpen)
176 {
177 _logger.Warning(this.GetType(), "Server failed to respond with a close response. Closing the connection from the client side.");
178  
179 // wait for data to be sent before we close the stream and client
180 _tcpClient.Client.Shutdown(SocketShutdown.Both);
181 _stream.Close();
182 _tcpClient.Close();
183 }
184  
185 _logger.Information(this.GetType(), "Client: Connection closed");
186 }
187  
188 protected override void OnConnectionClose(byte[] payload)
189 {
190 // server has either responded to a client close request or closed the connection for its own reasons
191 // the server will close the tcp connection so the client will not have to do it
192 _isOpen = false;
193 _conectionCloseWait.Set();
194 base.OnConnectionClose(payload);
195 }
196  
197 }
198 }