| File: System\Net\WebSockets\ClientWebSocket.cs | Web Access |
| Project: src\runtime\src\libraries\System.Net.WebSockets.Client\src\System.Net.WebSockets.Client.csproj (System.Net.WebSockets.Client) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; using System.Diagnostics; using System.Net.Http; using System.Threading; using System.Threading.Tasks; namespace System.Net.WebSockets { public sealed partial class ClientWebSocket : WebSocket { private InternalState _state; private WebSocketHandle? _innerWebSocket; public ClientWebSocket() { _state = InternalState.Created; Options = WebSocketHandle.CreateDefaultOptions(); } public ClientWebSocketOptions Options { get; } public override WebSocketCloseStatus? CloseStatus => _innerWebSocket?.WebSocket?.CloseStatus; public override string? CloseStatusDescription => _innerWebSocket?.WebSocket?.CloseStatusDescription; public override string? SubProtocol => _innerWebSocket?.WebSocket?.SubProtocol; public override WebSocketState State { get { // state == Connected or Disposed if (_innerWebSocket != null) { return _innerWebSocket.State; } switch (_state) { case InternalState.Created: return WebSocketState.None; case InternalState.Connecting: return WebSocketState.Connecting; default: // We only get here if disposed before connecting Debug.Assert(_state == InternalState.Disposed); return WebSocketState.Closed; } } } /// <summary> /// Gets the upgrade response status code if <see cref="ClientWebSocketOptions.CollectHttpResponseDetails" /> is set. /// </summary> public System.Net.HttpStatusCode HttpStatusCode => _innerWebSocket?.HttpStatusCode ?? 0; /// <summary> /// Gets the upgrade response headers if <see cref="ClientWebSocketOptions.CollectHttpResponseDetails" /> is set. /// The setter may be used to reduce the memory usage of an active WebSocket connection once headers are no longer needed. /// </summary> public IReadOnlyDictionary<string, IEnumerable<string>>? HttpResponseHeaders { get => _innerWebSocket?.HttpResponseHeaders; set { _innerWebSocket?.HttpResponseHeaders = value; } } /// <summary> /// Connects to a WebSocket server as an asynchronous operation. /// </summary> /// <param name="uri">The URI of the WebSocket server to connect to.</param> /// <param name="cancellationToken">A cancellation token used to propagate notification that the operation should be canceled.</param> /// <returns>The task object representing the asynchronous operation.</returns> public Task ConnectAsync(Uri uri, CancellationToken cancellationToken) { return ConnectAsync(uri, null, cancellationToken); } /// <summary> /// Connects to a WebSocket server as an asynchronous operation. /// </summary> /// <param name="uri">The URI of the WebSocket server to connect to.</param> /// <param name="invoker">The <see cref="HttpMessageInvoker" /> instance to use for connecting.</param> /// <param name="cancellationToken">A cancellation token used to propagate notification that the operation should be canceled.</param> /// <returns>The task object representing the asynchronous operation.</returns> public Task ConnectAsync(Uri uri, HttpMessageInvoker? invoker, CancellationToken cancellationToken) { ArgumentNullException.ThrowIfNull(uri); if (!uri.IsAbsoluteUri) { throw new ArgumentException(SR.net_uri_NotAbsolute, nameof(uri)); } if (uri.Scheme != UriScheme.Ws && uri.Scheme != UriScheme.Wss) { throw new ArgumentException(SR.net_WebSockets_Scheme, nameof(uri)); } // Check that we have not started already. switch (Interlocked.CompareExchange(ref _state, InternalState.Connecting, InternalState.Created)) { case InternalState.Disposed: throw new ObjectDisposedException(GetType().FullName); case InternalState.Created: break; default: throw new InvalidOperationException(SR.net_WebSockets_AlreadyStarted); } Options.SetToReadOnly(); return ConnectAsyncCore(uri, invoker, cancellationToken); } private async Task ConnectAsyncCore(Uri uri, HttpMessageInvoker? invoker, CancellationToken cancellationToken) { _innerWebSocket = new WebSocketHandle(); try { await _innerWebSocket.ConnectAsync(uri, invoker, cancellationToken, Options).ConfigureAwait(false); } catch { Dispose(); throw; } if (Interlocked.CompareExchange(ref _state, InternalState.Connected, InternalState.Connecting) != InternalState.Connecting) { Debug.Assert(_state == InternalState.Disposed); throw new ObjectDisposedException(GetType().FullName); } } public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) => ConnectedWebSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken); public override ValueTask SendAsync(ReadOnlyMemory<byte> buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) => ConnectedWebSocket.SendAsync(buffer, messageType, endOfMessage, cancellationToken); public override ValueTask SendAsync(ReadOnlyMemory<byte> buffer, WebSocketMessageType messageType, WebSocketMessageFlags messageFlags, CancellationToken cancellationToken) => ConnectedWebSocket.SendAsync(buffer, messageType, messageFlags, cancellationToken); public override Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken) => ConnectedWebSocket.ReceiveAsync(buffer, cancellationToken); public override ValueTask<ValueWebSocketReceiveResult> ReceiveAsync(Memory<byte> buffer, CancellationToken cancellationToken) => ConnectedWebSocket.ReceiveAsync(buffer, cancellationToken); public override Task CloseAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken) => ConnectedWebSocket.CloseAsync(closeStatus, statusDescription, cancellationToken); public override Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken) => ConnectedWebSocket.CloseOutputAsync(closeStatus, statusDescription, cancellationToken); private WebSocket ConnectedWebSocket { get { ObjectDisposedException.ThrowIf(_state == InternalState.Disposed, this); if (_state != InternalState.Connected) { throw new InvalidOperationException(SR.net_WebSockets_NotConnected); } Debug.Assert(_innerWebSocket != null); Debug.Assert(_innerWebSocket.WebSocket != null); return _innerWebSocket.WebSocket; } } public override void Abort() { if (_state != InternalState.Disposed) { _innerWebSocket?.Abort(); Dispose(); } } public override void Dispose() { if (Interlocked.Exchange(ref _state, InternalState.Disposed) != InternalState.Disposed) { _innerWebSocket?.Dispose(); } } private enum InternalState { Created = 0, Connecting = 1, Connected = 2, Disposed = 3 } } }