Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/corefx.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStephen Toub <stoub@microsoft.com>2017-09-28 00:07:26 +0300
committerStephen Toub <stoub@microsoft.com>2017-09-29 04:08:28 +0300
commitdb9f342cc43e0096b42d4d6d008c095da2bb8cfa (patch)
tree31c3adcbef4dcf34267d4d527c15d79009735997 /src/System.Net.WebSockets.Client
parent87ae016da78b9a29be33cc17f076b32c57c2a699 (diff)
Use ManagedHandler in managed ClientWebSocket
ClientWebSocket's managed implementation currently has its own basic Socket-based code for establishing a websocket connection. This change switches it to use ManagedHandler instead, and in doing so benefits from improvements made to ManagedHandler thus far and in the future. For example, currently ManagedWebSocket doesn't support ClientWebSocketOptions.Proxy or ClientWebSocketOptions.Credentials, but since ManagedHandler does, now ManagedWebSocket does, too.
Diffstat (limited to 'src/System.Net.WebSockets.Client')
-rw-r--r--src/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj2
-rw-r--r--src/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs428
-rw-r--r--src/System.Net.WebSockets.Client/tests/AbortTest.cs5
3 files changed, 108 insertions, 327 deletions
diff --git a/src/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj b/src/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj
index ef0b4a6ea7..7cbd0c2585 100644
--- a/src/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj
+++ b/src/System.Net.WebSockets.Client/src/System.Net.WebSockets.Client.csproj
@@ -138,6 +138,7 @@
</ItemGroup>
<ItemGroup Condition="'$(TargetsUnix)' == 'true'">
<Reference Include="System.Buffers" />
+ <Reference Include="System.Net.Http" />
<Reference Include="System.Net.NameResolution" />
<Reference Include="System.Net.Security" />
<Reference Include="System.Net.Sockets" />
@@ -146,6 +147,7 @@
<Reference Include="System.Security.Cryptography.Algorithms" />
<Reference Include="System.Security.Cryptography.Primitives" />
<Reference Include="System.Text.Encoding.Extensions" />
+ <Reference Include="System.Threading.Thread" />
<Reference Include="System.Threading.Timer" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
diff --git a/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs b/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs
index 39d2292754..c141724ea8 100644
--- a/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs
+++ b/src/System.Net.WebSockets.Client/src/System/Net/WebSockets/WebSocketHandle.Managed.cs
@@ -5,6 +5,8 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
+using System.Net.Http;
+using System.Net.Http.Headers;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.ExceptionServices;
@@ -17,15 +19,6 @@ namespace System.Net.WebSockets
{
internal sealed class WebSocketHandle
{
- /// <summary>Per-thread cached StringBuilder for building of strings to send on the connection.</summary>
- [ThreadStatic]
- private static StringBuilder t_cachedStringBuilder;
-
- /// <summary>Default encoding for HTTP requests. Latin alphabet no 1, ISO/IEC 8859-1.</summary>
- private static readonly Encoding s_defaultHttpEncoding = Encoding.GetEncoding(28591);
-
- /// <summary>Size of the receive buffer to use.</summary>
- private const int DefaultReceiveBufferSize = 0x1000;
/// <summary>GUID appended by the server as part of the security key response. Defined in the RFC.</summary>
private const string WSServerGuid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
@@ -71,202 +64,140 @@ namespace System.Net.WebSockets
public Task CloseOutputAsync(WebSocketCloseStatus closeStatus, string statusDescription, CancellationToken cancellationToken) =>
_webSocket.CloseOutputAsync(closeStatus, statusDescription, cancellationToken);
- public async Task ConnectAsyncCore(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
+ private sealed class DirectManagedHttpClientHandler : HttpClientHandler
{
- // TODO #14480 : Not currently implemented, or explicitly ignored:
- // - ClientWebSocketOptions.UseDefaultCredentials
- // - ClientWebSocketOptions.Credentials
- // - ClientWebSocketOptions.Proxy
- // - ClientWebSocketOptions._sendBufferSize
-
- // Establish connection to the server
- CancellationTokenRegistration registration = cancellationToken.Register(s => ((WebSocketHandle)s).Abort(), this);
- try
- {
- // Connect to the remote server
- Socket connectedSocket = await ConnectSocketAsync(uri.Host, uri.Port, cancellationToken).ConfigureAwait(false);
- Stream stream = new NetworkStream(connectedSocket, ownsSocket:true);
+ private const string ManagedHandlerEnvVar = "COMPlus_UseManagedHttpClientHandler";
+ private static readonly LocalDataStoreSlot s_managedHandlerSlot = GetSlot();
+ private static readonly object s_true = true;
- // Upgrade to SSL if needed
- if (uri.Scheme == UriScheme.Wss)
+ private static LocalDataStoreSlot GetSlot()
+ {
+ LocalDataStoreSlot slot = Thread.GetNamedDataSlot(ManagedHandlerEnvVar);
+ if (slot != null)
{
- var sslStream = new SslStream(stream);
- await sslStream.AuthenticateAsClientAsync(
- uri.Host,
- options.ClientCertificates,
- SecurityProtocol.AllowedSecurityProtocols,
- checkCertificateRevocation: false).ConfigureAwait(false);
- stream = sslStream;
+ return slot;
}
- // Create the security key and expected response, then build all of the request headers
- KeyValuePair<string, string> secKeyAndSecWebSocketAccept = CreateSecKeyAndSecWebSocketAccept();
- byte[] requestHeader = BuildRequestHeader(uri, options, secKeyAndSecWebSocketAccept.Key);
-
- // Write out the header to the connection
- await stream.WriteAsync(requestHeader, 0, requestHeader.Length, cancellationToken).ConfigureAwait(false);
-
- // Parse the response and store our state for the remainder of the connection
- string subprotocol = await ParseAndValidateConnectResponseAsync(stream, options, secKeyAndSecWebSocketAccept.Value, cancellationToken).ConfigureAwait(false);
-
- _webSocket = WebSocket.CreateClientWebSocket(
- stream, subprotocol, options.ReceiveBufferSize, options.SendBufferSize, options.KeepAliveInterval, false, options.Buffer.GetValueOrDefault());
-
- // If a concurrent Abort or Dispose came in before we set _webSocket, make sure to update it appropriately
- if (_state == WebSocketState.Aborted)
- {
- _webSocket.Abort();
- }
- else if (_state == WebSocketState.Closed)
- {
- _webSocket.Dispose();
- }
- }
- catch (Exception exc)
- {
- if (_state < WebSocketState.Closed)
+ try
{
- _state = WebSocketState.Closed;
+ return Thread.AllocateNamedDataSlot(ManagedHandlerEnvVar);
}
-
- Abort();
-
- if (exc is WebSocketException)
+ catch (ArgumentException) // in case of a race condition where multiple threads all try to allocate the slot concurrently
{
- throw;
+ return Thread.GetNamedDataSlot(ManagedHandlerEnvVar);
}
- throw new WebSocketException(SR.net_webstatus_ConnectFailure, exc);
- }
- finally
- {
- registration.Dispose();
}
- }
-
- /// <summary>Connects a socket to the specified host and port, subject to cancellation and aborting.</summary>
- /// <param name="host">The host to which to connect.</param>
- /// <param name="port">The port to which to connect on the host.</param>
- /// <param name="cancellationToken">The CancellationToken to use to cancel the websocket.</param>
- /// <returns>The connected Socket.</returns>
- private async Task<Socket> ConnectSocketAsync(string host, int port, CancellationToken cancellationToken)
- {
- IPAddress[] addresses = await Dns.GetHostAddressesAsync(host).ConfigureAwait(false);
- ExceptionDispatchInfo lastException = null;
- foreach (IPAddress address in addresses)
+ public static DirectManagedHttpClientHandler CreateHandler()
{
- var socket = new Socket(address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+ Thread.SetData(s_managedHandlerSlot, s_true);
try
{
- using (cancellationToken.Register(s => ((Socket)s).Dispose(), socket))
- using (_abortSource.Token.Register(s => ((Socket)s).Dispose(), socket))
- {
- try
- {
- await socket.ConnectAsync(address, port).ConfigureAwait(false);
- }
- catch (ObjectDisposedException ode)
- {
- // If the socket was disposed because cancellation was requested, translate the exception
- // into a new OperationCanceledException. Otherwise, let the original ObjectDisposedexception propagate.
- CancellationToken token = cancellationToken.IsCancellationRequested ? cancellationToken : _abortSource.Token;
- if (token.IsCancellationRequested)
- {
- throw new OperationCanceledException(new OperationCanceledException().Message, ode, token);
- }
- }
- }
- cancellationToken.ThrowIfCancellationRequested(); // in case of a race and socket was disposed after the await
- _abortSource.Token.ThrowIfCancellationRequested();
- return socket;
- }
- catch (Exception exc)
- {
- socket.Dispose();
- lastException = ExceptionDispatchInfo.Capture(exc);
+ return new DirectManagedHttpClientHandler();
}
+ finally { Thread.SetData(s_managedHandlerSlot, null); }
}
- lastException?.Throw();
-
- Debug.Fail("We should never get here. We should have already returned or an exception should have been thrown.");
- throw new WebSocketException(SR.net_webstatus_ConnectFailure);
+ public new Task<HttpResponseMessage> SendAsync(
+ HttpRequestMessage request, CancellationToken cancellationToken) =>
+ base.SendAsync(request, cancellationToken);
}
- /// <summary>Creates a byte[] containing the headers to send to the server.</summary>
- /// <param name="uri">The Uri of the server.</param>
- /// <param name="options">The options used to configure the websocket.</param>
- /// <param name="secKey">The generated security key to send in the Sec-WebSocket-Key header.</param>
- /// <returns>The byte[] containing the encoded headers ready to send to the network.</returns>
- private static byte[] BuildRequestHeader(Uri uri, ClientWebSocketOptions options, string secKey)
+ public async Task ConnectAsyncCore(Uri uri, CancellationToken cancellationToken, ClientWebSocketOptions options)
{
- StringBuilder builder = t_cachedStringBuilder ?? (t_cachedStringBuilder = new StringBuilder());
- Debug.Assert(builder.Length == 0, $"Expected builder to be empty, got one of length {builder.Length}");
try
{
- builder.Append("GET ").Append(uri.PathAndQuery).Append(" HTTP/1.1\r\n");
+ var request = new HttpRequestMessage(HttpMethod.Get, uri);
- // Add all of the required headers, honoring Host header if set.
- string hostHeader = options.RequestHeaders[HttpKnownHeaderNames.Host];
- builder.Append("Host: ");
- if (string.IsNullOrEmpty(hostHeader))
- {
- builder.Append(uri.IdnHost).Append(':').Append(uri.Port).Append("\r\n");
- }
- else
+ if (options._requestHeaders?.Count > 0) // use field to avoid lazily initializing the collection
{
- builder.Append(hostHeader).Append("\r\n");
+ foreach (string key in options.RequestHeaders)
+ {
+ request.Headers.Add(key, options.RequestHeaders[key]);
+ }
}
- builder.Append("Connection: Upgrade\r\n");
- builder.Append("Upgrade: websocket\r\n");
- builder.Append("Sec-WebSocket-Version: 13\r\n");
- builder.Append("Sec-WebSocket-Key: ").Append(secKey).Append("\r\n");
+ // Create the security key and expected response, then build all of the request headers
+ KeyValuePair<string, string> secKeyAndSecWebSocketAccept = CreateSecKeyAndSecWebSocketAccept();
+ AddWebSocketHeaders(request, secKeyAndSecWebSocketAccept.Key, options);
- // Add all of the additionally requested headers
- foreach (string key in options.RequestHeaders.AllKeys)
+ DirectManagedHttpClientHandler handler = DirectManagedHttpClientHandler.CreateHandler();
+ handler.UseDefaultCredentials = options.UseDefaultCredentials;
+ handler.Credentials = options.Credentials;
+ handler.Proxy = options.Proxy;
+ handler.CookieContainer = options.Cookies;
+ if (options._clientCertificates?.Count > 0) // use field to avoid lazily initializing the collection
{
- if (string.Equals(key, HttpKnownHeaderNames.Host, StringComparison.OrdinalIgnoreCase))
- {
- // Host header handled above
- continue;
- }
-
- builder.Append(key).Append(": ").Append(options.RequestHeaders[key]).Append("\r\n");
+ handler.ClientCertificateOptions = ClientCertificateOption.Manual;
+ handler.ClientCertificates.AddRange(options.ClientCertificates);
}
- // Add the optional subprotocols header
- if (options.RequestedSubProtocols.Count > 0)
+ HttpResponseMessage response = await handler.SendAsync(request, cancellationToken).ConfigureAwait(false);
+ if (response.StatusCode != HttpStatusCode.SwitchingProtocols)
{
- builder.Append(HttpKnownHeaderNames.SecWebSocketProtocol).Append(": ");
- builder.Append(options.RequestedSubProtocols[0]);
- for (int i = 1; i < options.RequestedSubProtocols.Count; i++)
+ throw new WebSocketException(SR.net_webstatus_ConnectFailure);
+ }
+
+ // The Connection, Upgrade, and SecWebSocketAccept headers are required and with specific values.
+ ValidateHeader(response.Headers, HttpKnownHeaderNames.Connection, "Upgrade");
+ ValidateHeader(response.Headers, HttpKnownHeaderNames.Upgrade, "websocket");
+ ValidateHeader(response.Headers, HttpKnownHeaderNames.SecWebSocketAccept, secKeyAndSecWebSocketAccept.Value);
+
+ // The SecWebSocketProtocol header is optional. We should only get it with a non-empty value if we requested subprotocols,
+ // and then it must only be one of the ones we requested. If we got a subprotocol other than one we requested (or if we
+ // already got one in a previous header), fail. Otherwise, track which one we got.
+ string subprotocol = null;
+ IEnumerable<string> subprotocolEnumerableValues;
+ if (response.Headers.TryGetValues(HttpKnownHeaderNames.SecWebSocketProtocol, out subprotocolEnumerableValues))
+ {
+ Debug.Assert(subprotocolEnumerableValues is string[]);
+ string[] subprotocolArray = (string[])subprotocolEnumerableValues;
+ if (subprotocolArray.Length != 1 ||
+ (subprotocol = options.RequestedSubProtocols.Find(requested => string.Equals(requested, subprotocolArray[0], StringComparison.OrdinalIgnoreCase))) == null)
{
- builder.Append(", ").Append(options.RequestedSubProtocols[i]);
+ throw new WebSocketException(
+ WebSocketError.UnsupportedProtocol,
+ SR.Format(SR.net_WebSockets_AcceptUnsupportedProtocol, string.Join(", ", options.RequestedSubProtocols), subprotocol));
}
- builder.Append("\r\n");
}
- // Add an optional cookies header
- if (options.Cookies != null)
+ Stream connectedStream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
+ _webSocket = WebSocket.CreateClientWebSocket( // TODO https://github.com/dotnet/corefx/issues/21537: Use new API when available
+ connectedStream,
+ subprotocol,
+ options.ReceiveBufferSize,
+ options.SendBufferSize,
+ options.KeepAliveInterval,
+ useZeroMaskingKey: false,
+ internalBuffer:options.Buffer.GetValueOrDefault());
+ }
+ catch (Exception exc)
+ {
+ if (_state < WebSocketState.Closed)
{
- string header = options.Cookies.GetCookieHeader(uri);
- if (!string.IsNullOrWhiteSpace(header))
- {
- builder.Append(HttpKnownHeaderNames.Cookie).Append(": ").Append(header).Append("\r\n");
- }
+ _state = WebSocketState.Closed;
}
- // End the headers
- builder.Append("\r\n");
+ Abort();
- // Return the bytes for the built up header
- return s_defaultHttpEncoding.GetBytes(builder.ToString());
+ if (exc is WebSocketException)
+ {
+ throw;
+ }
+ throw new WebSocketException(SR.net_webstatus_ConnectFailure, exc);
}
- finally
+ }
+
+ /// <param name="secKey">The generated security key to send in the Sec-WebSocket-Key header.</param>
+ private static void AddWebSocketHeaders(HttpRequestMessage request, string secKey, ClientWebSocketOptions options)
+ {
+ request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.Connection, HttpKnownHeaderNames.Upgrade);
+ request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.Upgrade, "websocket");
+ request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.SecWebSocketVersion, "13");
+ request.Headers.TryAddWithoutValidation(HttpKnownHeaderNames.SecWebSocketKey, secKey);
+ if (options._requestedSubProtocols?.Count > 0)
{
- // Make sure we clear the builder
- builder.Clear();
+ request.Headers.Add(HttpKnownHeaderNames.SecWebSocketProtocol, string.Join(", ", options.RequestedSubProtocols));
}
}
@@ -287,174 +218,21 @@ namespace System.Net.WebSockets
}
}
- /// <summary>Read and validate the connect response headers from the server.</summary>
- /// <param name="stream">The stream from which to read the response headers.</param>
- /// <param name="options">The options used to configure the websocket.</param>
- /// <param name="expectedSecWebSocketAccept">The expected value of the Sec-WebSocket-Accept header.</param>
- /// <param name="cancellationToken">The CancellationToken to use to cancel the websocket.</param>
- /// <returns>The agreed upon subprotocol with the server, or null if there was none.</returns>
- private async Task<string> ParseAndValidateConnectResponseAsync(
- Stream stream, ClientWebSocketOptions options, string expectedSecWebSocketAccept, CancellationToken cancellationToken)
+ private static void ValidateHeader(HttpHeaders headers, string name, string expectedValue)
{
- // Read the first line of the response
- string statusLine = await ReadResponseHeaderLineAsync(stream, cancellationToken).ConfigureAwait(false);
-
- // Depending on the underlying sockets implementation and timing, connecting to a server that then
- // immediately closes the connection may either result in an exception getting thrown from the connect
- // earlier, or it may result in getting to here but reading 0 bytes. If we read 0 bytes and thus have
- // an empty status line, treat it as a connect failure.
- if (string.IsNullOrEmpty(statusLine))
+ if (!headers.TryGetValues(name, out IEnumerable<string> values))
{
- throw new WebSocketException(SR.Format(SR.net_webstatus_ConnectFailure));
+ ThrowConnectFailure();
}
- const string ExpectedStatusStart = "HTTP/1.1 ";
- const string ExpectedStatusStatWithCode = "HTTP/1.1 101"; // 101 == SwitchingProtocols
-
- // If the status line doesn't begin with "HTTP/1.1" or isn't long enough to contain a status code, fail.
- if (!statusLine.StartsWith(ExpectedStatusStart, StringComparison.Ordinal) || statusLine.Length < ExpectedStatusStatWithCode.Length)
- {
- throw new WebSocketException(WebSocketError.HeaderError);
- }
-
- // If the status line doesn't contain a status code 101, or if it's long enough to have a status description
- // but doesn't contain whitespace after the 101, fail.
- if (!statusLine.StartsWith(ExpectedStatusStatWithCode, StringComparison.Ordinal) ||
- (statusLine.Length > ExpectedStatusStatWithCode.Length && !char.IsWhiteSpace(statusLine[ExpectedStatusStatWithCode.Length])))
- {
- throw new WebSocketException(SR.net_webstatus_ConnectFailure);
- }
-
- // Read each response header. Be liberal in parsing the response header, treating
- // everything to the left of the colon as the key and everything to the right as the value, trimming both.
- // For each header, validate that we got the expected value.
- bool foundUpgrade = false, foundConnection = false, foundSecWebSocketAccept = false;
- string subprotocol = null;
- string line;
- while (!string.IsNullOrEmpty(line = await ReadResponseHeaderLineAsync(stream, cancellationToken).ConfigureAwait(false)))
- {
- int colonIndex = line.IndexOf(':');
- if (colonIndex == -1)
- {
- throw new WebSocketException(WebSocketError.HeaderError);
- }
-
- string headerName = line.SubstringTrim(0, colonIndex);
- string headerValue = line.SubstringTrim(colonIndex + 1);
-
- // The Connection, Upgrade, and SecWebSocketAccept headers are required and with specific values.
- ValidateAndTrackHeader(HttpKnownHeaderNames.Connection, "Upgrade", headerName, headerValue, ref foundConnection);
- ValidateAndTrackHeader(HttpKnownHeaderNames.Upgrade, "websocket", headerName, headerValue, ref foundUpgrade);
- ValidateAndTrackHeader(HttpKnownHeaderNames.SecWebSocketAccept, expectedSecWebSocketAccept, headerName, headerValue, ref foundSecWebSocketAccept);
-
- // The SecWebSocketProtocol header is optional. We should only get it with a non-empty value if we requested subprotocols,
- // and then it must only be one of the ones we requested. If we got a subprotocol other than one we requested (or if we
- // already got one in a previous header), fail. Otherwise, track which one we got.
- if (string.Equals(HttpKnownHeaderNames.SecWebSocketProtocol, headerName, StringComparison.OrdinalIgnoreCase) &&
- !string.IsNullOrWhiteSpace(headerValue))
- {
- string newSubprotocol = options.RequestedSubProtocols.Find(requested => string.Equals(requested, headerValue, StringComparison.OrdinalIgnoreCase));
- if (newSubprotocol == null || subprotocol != null)
- {
- throw new WebSocketException(
- WebSocketError.UnsupportedProtocol,
- SR.Format(SR.net_WebSockets_AcceptUnsupportedProtocol, string.Join(", ", options.RequestedSubProtocols), subprotocol));
- }
- subprotocol = newSubprotocol;
- }
- }
- if (!foundUpgrade || !foundConnection || !foundSecWebSocketAccept)
+ Debug.Assert(values is string[]);
+ string[] array = (string[])values;
+ if (array.Length != 1 || !string.Equals(array[0], expectedValue, StringComparison.OrdinalIgnoreCase))
{
- throw new WebSocketException(SR.net_webstatus_ConnectFailure);
+ throw new WebSocketException(SR.Format(SR.net_WebSockets_InvalidResponseHeader, name, string.Join(", ", array)));
}
-
- return subprotocol;
}
- /// <summary>Validates a received header against expected values and tracks that we've received it.</summary>
- /// <param name="targetHeaderName">The header name against which we're comparing.</param>
- /// <param name="targetHeaderValue">The header value against which we're comparing.</param>
- /// <param name="foundHeaderName">The actual header name received.</param>
- /// <param name="foundHeaderValue">The actual header value received.</param>
- /// <param name="foundHeader">A bool tracking whether this header has been seen.</param>
- private static void ValidateAndTrackHeader(
- string targetHeaderName, string targetHeaderValue,
- string foundHeaderName, string foundHeaderValue,
- ref bool foundHeader)
- {
- bool isTargetHeader = string.Equals(targetHeaderName, foundHeaderName, StringComparison.OrdinalIgnoreCase);
- if (!foundHeader)
- {
- if (isTargetHeader)
- {
- if (!string.Equals(targetHeaderValue, foundHeaderValue, StringComparison.OrdinalIgnoreCase))
- {
- throw new WebSocketException(SR.Format(SR.net_WebSockets_InvalidResponseHeader, targetHeaderName, foundHeaderValue));
- }
- foundHeader = true;
- }
- }
- else
- {
- if (isTargetHeader)
- {
- throw new WebSocketException(SR.Format(SR.net_webstatus_ConnectFailure));
- }
- }
- }
-
- /// <summary>Reads a line from the stream.</summary>
- /// <param name="stream">The stream from which to read.</param>
- /// <param name="cancellationToken">The CancellationToken used to cancel the websocket.</param>
- /// <returns>The read line, or null if none could be read.</returns>
- private static async Task<string> ReadResponseHeaderLineAsync(Stream stream, CancellationToken cancellationToken)
- {
- StringBuilder sb = t_cachedStringBuilder;
- if (sb != null)
- {
- t_cachedStringBuilder = null;
- Debug.Assert(sb.Length == 0, $"Expected empty StringBuilder");
- }
- else
- {
- sb = new StringBuilder();
- }
-
- var arr = new byte[1];
- char prevChar = '\0';
- try
- {
- // TODO: Reading one byte is extremely inefficient. The problem, however,
- // is that if we read multiple bytes, we could end up reading bytes post-headers
- // that are part of messages meant to be read by the managed websocket after
- // the connection. The likely solution here is to wrap the stream in a BufferedStream,
- // though a) that comes at the expense of an extra set of virtual calls, b)
- // it adds a buffer when the managed websocket will already be using a buffer, and
- // c) it's not exposed on the version of the System.IO contract we're currently using.
- while (await stream.ReadAsync(arr, 0, 1, cancellationToken).ConfigureAwait(false) == 1)
- {
- // Process the next char
- char curChar = (char)arr[0];
- if (prevChar == '\r' && curChar == '\n')
- {
- break;
- }
- sb.Append(curChar);
- prevChar = curChar;
- }
-
- if (sb.Length > 0 && sb[sb.Length - 1] == '\r')
- {
- sb.Length = sb.Length - 1;
- }
-
- return sb.ToString();
- }
- finally
- {
- sb.Clear();
- t_cachedStringBuilder = sb;
- }
- }
+ private static void ThrowConnectFailure() => throw new WebSocketException(SR.net_webstatus_ConnectFailure);
}
}
diff --git a/src/System.Net.WebSockets.Client/tests/AbortTest.cs b/src/System.Net.WebSockets.Client/tests/AbortTest.cs
index d5c6bec1a3..ff6ac0076d 100644
--- a/src/System.Net.WebSockets.Client/tests/AbortTest.cs
+++ b/src/System.Net.WebSockets.Client/tests/AbortTest.cs
@@ -16,9 +16,10 @@ namespace System.Net.WebSockets.Client.Tests
{
public AbortTest(ITestOutputHelper output) : base(output) { }
+ [ActiveIssue(23151, TestPlatforms.AnyUnix)] // need ManagedHandler support for canceling a ConnectAsync operation
[OuterLoop] // TODO: Issue #11345
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))]
- public void Abort_ConnectAndAbort_ThrowsWebSocketExceptionWithmessage(Uri server)
+ public async Task Abort_ConnectAndAbort_ThrowsWebSocketExceptionWithmessage(Uri server)
{
using (var cws = new ClientWebSocket())
{
@@ -29,7 +30,7 @@ namespace System.Net.WebSockets.Client.Tests
Task t = cws.ConnectAsync(ub.Uri, cts.Token);
cws.Abort();
- WebSocketException ex = Assert.Throws<WebSocketException>(() => t.GetAwaiter().GetResult());
+ WebSocketException ex = await Assert.ThrowsAsync<WebSocketException>(() => t);
Assert.Equal(ResourceHelper.GetExceptionMessage("net_webstatus_ConnectFailure"), ex.Message);