mscorlib(4.0.0.0) API with additions
ClientWebSocket.cs
1 using System.Threading;
3 
5 {
7  public sealed class ClientWebSocket : WebSocket
8  {
9  private readonly ClientWebSocketOptions options;
10 
11  private WebSocket innerWebSocket;
12 
13  private readonly CancellationTokenSource cts;
14 
15  private int state;
16 
17  private const int created = 0;
18 
19  private const int connecting = 1;
20 
21  private const int connected = 2;
22 
23  private const int disposed = 3;
24 
27  public ClientWebSocketOptions Options => options;
28 
31  public override WebSocketCloseStatus? CloseStatus
32  {
33  get
34  {
35  if (innerWebSocket != null)
36  {
37  return innerWebSocket.CloseStatus;
38  }
39  return null;
40  }
41  }
42 
45  public override string CloseStatusDescription
46  {
47  get
48  {
49  if (innerWebSocket != null)
50  {
51  return innerWebSocket.CloseStatusDescription;
52  }
53  return null;
54  }
55  }
56 
59  public override string SubProtocol
60  {
61  get
62  {
63  if (innerWebSocket != null)
64  {
65  return innerWebSocket.SubProtocol;
66  }
67  return null;
68  }
69  }
70 
73  public override WebSocketState State
74  {
75  get
76  {
77  if (innerWebSocket != null)
78  {
79  return innerWebSocket.State;
80  }
81  switch (state)
82  {
83  case 0:
84  return WebSocketState.None;
85  case 1:
86  return WebSocketState.Connecting;
87  case 3:
88  return WebSocketState.Closed;
89  default:
90  return WebSocketState.Closed;
91  }
92  }
93  }
94 
95  static ClientWebSocket()
96  {
98  }
99 
102  {
103  if (Logging.On)
104  {
105  Logging.Enter(Logging.WebSockets, this, ".ctor", null);
106  }
107  if (!WebSocketProtocolComponent.IsSupported)
108  {
109  WebSocketHelpers.ThrowPlatformNotSupportedException_WSPC();
110  }
111  state = 0;
112  options = new ClientWebSocketOptions();
113  cts = new CancellationTokenSource();
114  if (Logging.On)
115  {
116  Logging.Exit(Logging.WebSockets, this, ".ctor", null);
117  }
118  }
119 
124  public Task ConnectAsync(Uri uri, CancellationToken cancellationToken)
125  {
126  if (uri == null)
127  {
128  throw new ArgumentNullException("uri");
129  }
130  if (!uri.IsAbsoluteUri)
131  {
132  throw new ArgumentException(SR.GetString("net_uri_NotAbsolute"), "uri");
133  }
134  if (uri.Scheme != Uri.UriSchemeWs && uri.Scheme != Uri.UriSchemeWss)
135  {
136  throw new ArgumentException(SR.GetString("net_WebSockets_Scheme"), "uri");
137  }
138  switch (Interlocked.CompareExchange(ref state, 1, 0))
139  {
140  case 3:
141  throw new ObjectDisposedException(GetType().FullName);
142  default:
143  throw new InvalidOperationException(SR.GetString("net_WebSockets_AlreadyStarted"));
144  case 0:
145  options.SetToReadOnly();
146  return ConnectAsyncCore(uri, cancellationToken);
147  }
148  }
149 
150  private async Task ConnectAsyncCore(Uri uri, CancellationToken cancellationToken)
151  {
152  HttpWebResponse response = null;
153  CancellationTokenRegistration connectCancellation = default(CancellationTokenRegistration);
154  try
155  {
156  HttpWebRequest request = CreateAndConfigureRequest(uri);
157  if (Logging.On)
158  {
159  Logging.Associate(Logging.WebSockets, this, request);
160  }
161  connectCancellation = cancellationToken.Register(AbortRequest, request, useSynchronizationContext: false);
162  response = ((await request.GetResponseAsync().SuppressContextFlow()) as HttpWebResponse);
163  if (Logging.On)
164  {
165  Logging.Associate(Logging.WebSockets, this, response);
166  }
167  string subProtocol = ValidateResponse(request, response);
168  innerWebSocket = WebSocket.CreateClientWebSocket(response.GetResponseStream(), subProtocol, options.ReceiveBufferSize, options.SendBufferSize, options.KeepAliveInterval, useZeroMaskingKey: false, options.GetOrCreateBuffer());
169  if (Logging.On)
170  {
171  Logging.Associate(Logging.WebSockets, this, innerWebSocket);
172  }
173  if (Interlocked.CompareExchange(ref state, 2, 1) != 1)
174  {
175  throw new ObjectDisposedException(GetType().FullName);
176  }
177  }
178  catch (WebException innerException)
179  {
180  ConnectExceptionCleanup(response);
181  WebSocketException ex = new WebSocketException(SR.GetString("net_webstatus_ConnectFailure"), innerException);
182  if (Logging.On)
183  {
184  Logging.Exception(Logging.WebSockets, this, "ConnectAsync", ex);
185  }
186  throw ex;
187  }
188  catch (Exception e)
189  {
190  ConnectExceptionCleanup(response);
191  if (Logging.On)
192  {
193  Logging.Exception(Logging.WebSockets, this, "ConnectAsync", e);
194  }
195  throw;
196  }
197  finally
198  {
199  connectCancellation.Dispose();
200  }
201  }
202 
203  private void ConnectExceptionCleanup(HttpWebResponse response)
204  {
205  Dispose();
206  response?.Dispose();
207  }
208 
209  private HttpWebRequest CreateAndConfigureRequest(Uri uri)
210  {
211  HttpWebRequest httpWebRequest = WebRequest.Create(uri) as HttpWebRequest;
212  if (httpWebRequest == null)
213  {
214  throw new InvalidOperationException(SR.GetString("net_WebSockets_InvalidRegistration"));
215  }
216  foreach (string key in options.RequestHeaders.Keys)
217  {
218  httpWebRequest.Headers.Add(key, options.RequestHeaders[key]);
219  }
220  if (options.RequestedSubProtocols.Count > 0)
221  {
222  httpWebRequest.Headers.Add("Sec-WebSocket-Protocol", string.Join(", ", options.RequestedSubProtocols));
223  }
224  if (options.UseDefaultCredentials)
225  {
226  httpWebRequest.UseDefaultCredentials = true;
227  }
228  else if (options.Credentials != null)
229  {
230  httpWebRequest.Credentials = options.Credentials;
231  }
232  if (options.InternalClientCertificates != null)
233  {
234  httpWebRequest.ClientCertificates = options.InternalClientCertificates;
235  }
236  httpWebRequest.Proxy = options.Proxy;
237  httpWebRequest.CookieContainer = options.Cookies;
238  cts.Token.Register(AbortRequest, httpWebRequest, useSynchronizationContext: false);
239  return httpWebRequest;
240  }
241 
242  private string ValidateResponse(HttpWebRequest request, HttpWebResponse response)
243  {
244  if (response.StatusCode != HttpStatusCode.SwitchingProtocols)
245  {
246  throw new WebSocketException(SR.GetString("net_WebSockets_Connect101Expected", (int)response.StatusCode));
247  }
248  string text = response.Headers["Upgrade"];
249  if (!string.Equals(text, "websocket", StringComparison.OrdinalIgnoreCase))
250  {
251  throw new WebSocketException(SR.GetString("net_WebSockets_InvalidResponseHeader", "Upgrade", text));
252  }
253  string text2 = response.Headers["Connection"];
254  if (!string.Equals(text2, "Upgrade", StringComparison.OrdinalIgnoreCase))
255  {
256  throw new WebSocketException(SR.GetString("net_WebSockets_InvalidResponseHeader", "Connection", text2));
257  }
258  string text3 = response.Headers["Sec-WebSocket-Accept"];
259  string secWebSocketAcceptString = WebSocketHelpers.GetSecWebSocketAcceptString(request.Headers["Sec-WebSocket-Key"]);
260  if (!string.Equals(text3, secWebSocketAcceptString, StringComparison.OrdinalIgnoreCase))
261  {
262  throw new WebSocketException(SR.GetString("net_WebSockets_InvalidResponseHeader", "Sec-WebSocket-Accept", text3));
263  }
264  string text4 = response.Headers["Sec-WebSocket-Protocol"];
265  if (!string.IsNullOrWhiteSpace(text4) && options.RequestedSubProtocols.Count > 0)
266  {
267  bool flag = false;
268  foreach (string requestedSubProtocol in options.RequestedSubProtocols)
269  {
270  if (string.Equals(requestedSubProtocol, text4, StringComparison.OrdinalIgnoreCase))
271  {
272  flag = true;
273  break;
274  }
275  }
276  if (!flag)
277  {
278  throw new WebSocketException(SR.GetString("net_WebSockets_AcceptUnsupportedProtocol", string.Join(", ", options.RequestedSubProtocols), text4));
279  }
280  }
281  if (!string.IsNullOrWhiteSpace(text4))
282  {
283  return text4;
284  }
285  return null;
286  }
287 
294  public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
295  {
296  ThrowIfNotConnected();
297  return innerWebSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken);
298  }
299 
305  {
306  ThrowIfNotConnected();
307  return innerWebSocket.ReceiveAsync(buffer, cancellationToken);
308  }
309 
315  public override Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
316  {
317  ThrowIfNotConnected();
318  return innerWebSocket.CloseAsync(closeStatus, statusDescription, cancellationToken);
319  }
320 
326  public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
327  {
328  ThrowIfNotConnected();
329  return innerWebSocket.CloseOutputAsync(closeStatus, statusDescription, cancellationToken);
330  }
331 
333  public override void Abort()
334  {
335  if (state != 3)
336  {
337  if (innerWebSocket != null)
338  {
339  innerWebSocket.Abort();
340  }
341  Dispose();
342  }
343  }
344 
345  private void AbortRequest(object obj)
346  {
347  HttpWebRequest httpWebRequest = (HttpWebRequest)obj;
348  httpWebRequest.Abort();
349  }
350 
352  public override void Dispose()
353  {
354  int num = Interlocked.Exchange(ref state, 3);
355  if (num != 3)
356  {
357  cts.Cancel(throwOnFirstException: false);
358  cts.Dispose();
359  if (innerWebSocket != null)
360  {
361  innerWebSocket.Dispose();
362  }
363  }
364  }
365 
366  private void ThrowIfNotConnected()
367  {
368  if (state == 3)
369  {
370  throw new ObjectDisposedException(GetType().FullName);
371  }
372  if (state != 2)
373  {
374  throw new InvalidOperationException(SR.GetString("net_WebSockets_NotConnected"));
375  }
376  }
377  }
378 }
WebSocketCloseStatus
Represents well known WebSocket close codes as defined in section 11.7 of the WebSocket protocol spec...
The exception that is thrown when a null reference (Nothing in Visual Basic) is passed to a method th...
override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
Close the output for the T:System.Net.WebSockets.ClientWebSocket instance as an asynchronous operatio...
Propagates notification that operations should be canceled.
Options to use with a T:System.Net.WebSockets.ClientWebSocket object.
ClientWebSocketOptions Options
Gets the WebSocket options for the T:System.Net.WebSockets.ClientWebSocket instance.
abstract Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
Initiates or completes the close handshake defined in the WebSocket protocol specification section 7.
StringComparison
Specifies the culture, case, and sort rules to be used by certain overloads of the M:System....
abstract string CloseStatusDescription
Allows the remote endpoint to describe the reason why the connection was closed.
Definition: WebSocket.cs:24
WebSocketState
Defines the different states a WebSockets instance can be in.
WebSocketMessageType
Indicates the message type.
Definition: __Canon.cs:3
abstract WebSocketState State
Returns the current state of the WebSocket connection.
Definition: WebSocket.cs:38
abstract void Abort()
Aborts the WebSocket connection and cancels any pending IO operations.
string Scheme
Gets the scheme name for this URI.
Definition: Uri.cs:702
Represents a callback delegate that has been registered with a T:System.Threading....
HttpStatusCode
Contains the values of status codes defined for HTTP.
static int Exchange(ref int location1, int value)
Sets a 32-bit signed integer to a specified value and returns the original value, as an atomic operat...
override Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
Close the T:System.Net.WebSockets.ClientWebSocket instance as an asynchronous operation.
override WebSocketState State
Get the WebSocket state of the T:System.Net.WebSockets.ClientWebSocket instance.
override string CloseStatusDescription
Gets a description of the reason why the T:System.Net.WebSockets.ClientWebSocket instance was closed.
override string SubProtocol
Gets the supported WebSocket sub-protocol for the T:System.Net.WebSockets.ClientWebSocket instance.
The exception that is thrown when an operation is performed on a disposed object.
bool IsAbsoluteUri
Gets whether the T:System.Uri instance is absolute.
Definition: Uri.cs:820
The WebSocket class allows applications to send and receive data after the WebSocket upgrade has comp...
Definition: WebSocket.cs:10
virtual Task< WebResponse > GetResponseAsync()
When overridden in a descendant class, returns a response to an Internet request as an asynchronous o...
Definition: WebRequest.cs:813
static int CompareExchange(ref int location1, int value, int comparand)
Compares two 32-bit signed integers for equality and, if they are equal, replaces the first value.
Task ConnectAsync(Uri uri, CancellationToken cancellationToken)
Connect to a WebSocket server as an asynchronous operation.
override Task< WebSocketReceiveResult > ReceiveAsync(ArraySegment< byte > buffer, CancellationToken cancellationToken)
Receive data on T:System.Net.WebSockets.ClientWebSocket as an asynchronous operation.
override void Abort()
Aborts the connection and cancels any pending IO operations.
abstract Task SendAsync(ArraySegment< byte > buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
Sends data over the T:System.Net.WebSockets.WebSocket connection asynchronously.
Provides an HTTP-specific implementation of the T:System.Net.WebResponse class.
abstract Task CloseAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken)
Closes the WebSocket connection as an asynchronous operation using the close handshake defined in the...
abstract Task< WebSocketReceiveResult > ReceiveAsync(ArraySegment< byte > buffer, CancellationToken cancellationToken)
Receives data from the T:System.Net.WebSockets.WebSocket connection asynchronously.
override? WebSocketCloseStatus CloseStatus
Gets the reason why the close handshake was initiated on T:System.Net.WebSockets.ClientWebSocket inst...
static WebSocket CreateClientWebSocket(Stream innerStream, string subProtocol, int receiveBufferSize, int sendBufferSize, TimeSpan keepAliveInterval, bool useZeroMaskingKey, ArraySegment< byte > internalBuffer)
This API supports the .NET Framework infrastructure and is not intended to be used directly from your...
Definition: WebSocket.cs:92
Provides a client for connecting to WebSocket services.
The exception that is thrown when one of the arguments provided to a method is not valid.
abstract ? WebSocketCloseStatus CloseStatus
Indicates the reason why the remote endpoint initiated the close handshake.
Definition: WebSocket.cs:17
override Task SendAsync(ArraySegment< byte > buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken)
Send data on T:System.Net.WebSockets.ClientWebSocket as an asynchronous operation.
Signals to a T:System.Threading.CancellationToken that it should be canceled.
abstract string SubProtocol
The subprotocol that was negotiated during the opening handshake.
Definition: WebSocket.cs:31
The exception that is thrown when a method call is invalid for the object's current state.
ClientWebSocket()
Creates an instance of the T:System.Net.WebSockets.ClientWebSocket class.
abstract void Dispose()
Used to clean up unmanaged resources for ASP.NET and self-hosted implementations.
Provides an object representation of a uniform resource identifier (URI) and easy access to the parts...
Definition: Uri.cs:19
static void RegisterPrefixes()
This API supports the .NET Framework infrastructure and is not intended to be used directly from your...
Definition: WebSocket.cs:120
Provides an HTTP-specific implementation of the T:System.Net.WebRequest class.
override void Abort()
Cancels a request to an Internet resource.
override void Dispose()
Releases the unmanaged resources used by the T:System.Net.WebSockets.ClientWebSocket instance.
Provides atomic operations for variables that are shared by multiple threads.
Definition: Interlocked.cs:10
CancellationTokenRegistration Register(Action callback)
Registers a delegate that will be called when this T:System.Threading.CancellationToken is canceled.
override Stream GetResponseStream()
Gets the stream that is used to read the body of the response from the server.
void Dispose()
Releases all resources used by the current instance of the T:System.Threading.CancellationTokenRegist...
Represents an asynchronous operation that can return a value.
Definition: Task.cs:18