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.Sockets;
7 using System.Text;
8 using System.Threading.Tasks;
9 using System.Text.RegularExpressions;
10 using System.Security.Cryptography;
11 using System.Threading;
12 using System.Collections;
13 using System.Diagnostics;
14 using WebSockets.Exceptions;
15 using WebSockets.Server;
16 using WebSockets.Server.Http;
17 using WebSockets.Common;
18 using System.Net.Security;
19 using System.Security.Authentication;
20 using System.Security.Cryptography.X509Certificates;
21  
22 namespace WebSockets
23 {
24 public class WebServer : IDisposable
25 {
26 // maintain a list of open connections so that we can notify the client if the server shuts down
27 private readonly List<IDisposable> _openConnections;
28 private readonly IServiceFactory _serviceFactory;
29 private readonly IWebSocketLogger _logger;
30 private X509Certificate2 _sslCertificate;
31 private TcpListener _listener;
32 private bool _isDisposed = false;
33  
34 public WebServer(IServiceFactory serviceFactory, IWebSocketLogger logger)
35 {
36 _serviceFactory = serviceFactory;
37 _logger = logger;
38 _openConnections = new List<IDisposable>();
39 }
40  
41 public void Listen(int port, X509Certificate2 sslCertificate)
42 {
43 try
44 {
45 _sslCertificate = sslCertificate;
46 IPAddress localAddress = IPAddress.Any;
47 _listener = new TcpListener(localAddress, port);
48 _listener.Start();
49 _logger.Information(this.GetType(), "Server started listening on port {0}", port);
50 StartAccept();
51 }
52 catch (SocketException ex)
53 {
54 string message = string.Format("Error listening on port {0}. Make sure IIS or another application is not running and consuming your port.", port);
55 throw new ServerListenerSocketException(message, ex);
56 }
57 }
58  
59 /// <summary>
60 /// Listens on the port specified
61 /// </summary>
62 public void Listen(int port)
63 {
64 Listen(port, null);
65 }
66  
67 /// <summary>
68 /// Gets the first available port and listens on it. Returns the port
69 /// </summary>
70 public int Listen()
71 {
72 IPAddress localAddress = IPAddress.Any;
73 _listener = new TcpListener(localAddress, 0);
74 _listener.Start();
75 StartAccept();
76 int port = ((IPEndPoint) _listener.LocalEndpoint).Port;
77 _logger.Information(this.GetType(), "Server started listening on port {0}", port);
78 return port;
79 }
80  
81 private void StartAccept()
82 {
83 // this is a non-blocking operation. It will consume a worker thread from the threadpool
84 _listener.BeginAcceptTcpClient(new AsyncCallback(HandleAsyncConnection), null);
85 }
86  
87 private static ConnectionDetails GetConnectionDetails(Stream stream, TcpClient tcpClient)
88 {
89 // read the header and check that it is a GET request
90 string header = HttpHelper.ReadHttpHeader(stream);
91 Regex getRegex = new Regex(@"^GET(.*)HTTP\/1\.1", RegexOptions.IgnoreCase);
92  
93 Match getRegexMatch = getRegex.Match(header);
94 if (getRegexMatch.Success)
95 {
96 // extract the path attribute from the first line of the header
97 string path = getRegexMatch.Groups[1].Value.Trim();
98  
99 // check if this is a web socket upgrade request
100 Regex webSocketUpgradeRegex = new Regex("Upgrade: websocket", RegexOptions.IgnoreCase);
101 Match webSocketUpgradeRegexMatch = webSocketUpgradeRegex.Match(header);
102  
103 if (webSocketUpgradeRegexMatch.Success)
104 {
105 return new ConnectionDetails(stream, tcpClient, path, ConnectionType.WebSocket, header);
106 }
107 else
108 {
109 return new ConnectionDetails(stream, tcpClient, path, ConnectionType.Http, header);
110 }
111 }
112 else
113 {
114 return new ConnectionDetails(stream, tcpClient, string.Empty, ConnectionType.Unknown, header);
115 }
116 }
117  
118 private Stream GetStream(TcpClient tcpClient)
119 {
120 Stream stream = tcpClient.GetStream();
121  
122 // we have no ssl certificate
123 if (_sslCertificate == null)
124 {
125 _logger.Information(this.GetType(), "Connection not secure");
126 return stream;
127 }
128  
129 try
130 {
131 SslStream sslStream = new SslStream(stream, false);
132 _logger.Information(this.GetType(), "Attempting to secure connection...");
133 sslStream.AuthenticateAsServer(_sslCertificate, false, SslProtocols.Tls, true);
134 _logger.Information(this.GetType(), "Connection successfully secured");
135 return sslStream;
136 }
137 catch (AuthenticationException e)
138 {
139 // TODO: send 401 Unauthorized
140 throw;
141 }
142 }
143  
144 private void HandleAsyncConnection(IAsyncResult res)
145 {
146 try
147 {
148 if (_isDisposed)
149 {
150 return;
151 }
152  
153 // this worker thread stays alive until either of the following happens:
154 // Client sends a close conection request OR
155 // An unhandled exception is thrown OR
156 // The server is disposed
157 using (TcpClient tcpClient = _listener.EndAcceptTcpClient(res))
158 {
159 // we are ready to listen for more connections (on another thread)
160 StartAccept();
161 _logger.Information(this.GetType(), "Server: Connection opened");
162  
163 // get a secure or insecure stream
164 Stream stream = GetStream(tcpClient);
165  
166 // extract the connection details and use those details to build a connection
167 ConnectionDetails connectionDetails = GetConnectionDetails(stream, tcpClient);
168 using (IService service = _serviceFactory.CreateInstance(connectionDetails))
169 {
170 try
171 {
172 // record the connection so we can close it if something goes wrong
173 lock (_openConnections)
174 {
175 _openConnections.Add(service);
176 }
177  
178 // respond to the http request.
179 // Take a look at the WebSocketConnection or HttpConnection classes
180 service.Respond();
181 }
182 finally
183 {
184 // forget the connection, we are done with it
185 lock (_openConnections)
186 {
187 _openConnections.Remove(service);
188 }
189 }
190 }
191 }
192  
193 _logger.Information(this.GetType(), "Server: Connection closed");
194 }
195 catch (ObjectDisposedException)
196 {
197 // do nothing. This will be thrown if the Listener has been stopped
198 }
199 catch (Exception ex)
200 {
201 _logger.Error(this.GetType(), ex);
202 }
203 }
204  
205 private void CloseAllConnections()
206 {
207 IDisposable[] openConnections;
208  
209 lock (_openConnections)
210 {
211 openConnections = _openConnections.ToArray();
212 _openConnections.Clear();
213 }
214  
215 // safely attempt to close each connection
216 foreach (IDisposable openConnection in openConnections)
217 {
218 try
219 {
220 openConnection.Dispose();
221 }
222 catch (Exception ex)
223 {
224 _logger.Error(this.GetType(), ex);
225 }
226 }
227 }
228  
229 public void Dispose()
230 {
231 if (!_isDisposed)
232 {
233 _isDisposed = true;
234  
235 // safely attempt to shut down the listener
236 try
237 {
238 if (_listener != null)
239 {
240 if (_listener.Server != null)
241 {
242 _listener.Server.Close();
243 }
244  
245 _listener.Stop();
246 }
247 }
248 catch (Exception ex)
249 {
250 _logger.Error(this.GetType(), ex);
251 }
252  
253 CloseAllConnections();
254 _logger.Information(this.GetType(), "Web Server disposed");
255 }
256 }
257 }
258 }