File: InProcess\WebSocketTests.cs
Web Access
Project: src\src\Servers\IIS\IIS\test\IISExpress.FunctionalTests\IISExpress.FunctionalTests.csproj (IISExpress.FunctionalTests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.IIS.FunctionalTests;
using Microsoft.AspNetCore.InternalTesting;
using Xunit;
 
namespace Microsoft.AspNetCore.Server.IIS.IISExpress.FunctionalTests;
 
[Collection(IISTestSiteCollection.Name)]
[MinimumOSVersion(OperatingSystems.Windows, WindowsVersions.Win8, SkipReason = "No WebSocket supported on Win7")]
[SkipOnHelix("Unsupported queue", Queues = "Windows.Amd64.VS2022.Pre.Open;")]
public class WebSocketsTests
{
    private readonly string _requestUri;
    private readonly string _webSocketUri;
 
    public WebSocketsTests(IISTestSiteFixture fixture)
    {
        _requestUri = fixture.DeploymentResult.ApplicationBaseUri;
        _webSocketUri = _requestUri.Replace("http:", "ws:");
    }
 
    [ConditionalFact]
    public async Task RequestWithBody_NotUpgradable()
    {
        using var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(200) };
        using var response = await client.PostAsync(_requestUri + "WebSocketNotUpgradable", new StringContent("Hello World"));
        response.EnsureSuccessStatusCode();
    }
 
    [ConditionalFact]
    public async Task RequestWithoutBody_Upgradable()
    {
        using var client = new HttpClient() { Timeout = TimeSpan.FromSeconds(200) };
        // POST with Content-Length: 0 counts as not having a body.
        using var response = await client.PostAsync(_requestUri + "WebSocketUpgradable", new StringContent(""));
        response.EnsureSuccessStatusCode();
    }
 
    [ConditionalFact]
    public async Task OnStartedCalledForWebSocket()
    {
        var cws = new ClientWebSocket();
        await cws.ConnectAsync(new Uri(_webSocketUri + "WebSocketLifetimeEvents"), default);
 
        await ReceiveMessage(cws, "OnStarting");
        await ReceiveMessage(cws, "Upgraded");
    }
 
    [ConditionalFact]
    public async Task WebReadBeforeUpgrade()
    {
        var cws = new ClientWebSocket();
        await cws.ConnectAsync(new Uri(_webSocketUri + "WebSocketReadBeforeUpgrade"), default);
 
        await ReceiveMessage(cws, "Yay");
    }
 
    [ConditionalFact]
    public async Task CanSendAndReceieveData()
    {
        var cws = new ClientWebSocket();
        await cws.ConnectAsync(new Uri(_webSocketUri + "WebSocketEcho"), default);
 
        for (int i = 0; i < 1000; i++)
        {
            var mesage = i.ToString(CultureInfo.InvariantCulture);
            await SendMessage(cws, mesage);
            await ReceiveMessage(cws, mesage);
        }
    }
 
    [ConditionalFact]
    public async Task Http1_0_Request_NotUpgradable()
    {
        Uri uri = new Uri(_requestUri + "WebSocketNotUpgradable");
        using TcpClient client = new TcpClient();
 
        await client.ConnectAsync(uri.Host, uri.Port);
        NetworkStream stream = client.GetStream();
 
        await SendHttp10Request(stream, uri);
 
        StreamReader reader = new StreamReader(stream);
        string statusLine = await reader.ReadLineAsync();
        string[] parts = statusLine.Split(' ');
        if (int.Parse(parts[1], CultureInfo.InvariantCulture) != 200)
        {
            throw new InvalidOperationException("The response status code was incorrect: " + statusLine);
        }
    }
 
    [ConditionalFact]
    public async Task Http1_0_Request_UpgradeErrors()
    {
        Uri uri = new Uri(_requestUri + "WebSocketUpgradeFails");
        using TcpClient client = new TcpClient();
 
        await client.ConnectAsync(uri.Host, uri.Port);
        NetworkStream stream = client.GetStream();
 
        await SendHttp10Request(stream, uri);
 
        StreamReader reader = new StreamReader(stream);
        string statusLine = await reader.ReadLineAsync();
        string[] parts = statusLine.Split(' ');
        if (int.Parse(parts[1], CultureInfo.InvariantCulture) != 200)
        {
            throw new InvalidOperationException("The response status code was incorrect: " + statusLine);
        }
    }
 
    private async Task SendHttp10Request(NetworkStream stream, Uri uri)
    {
        // Send an HTTP GET request
        StringBuilder builder = new StringBuilder();
        builder.Append("GET");
        builder.Append(" ");
        builder.Append(uri.PathAndQuery);
        builder.Append(" HTTP/1.0");
        builder.AppendLine();
 
        builder.Append("Host: ");
        builder.Append(uri.Host);
        builder.Append(':');
        builder.Append(uri.Port);
        builder.AppendLine();
 
        builder.AppendLine();
        var requestBytes = Encoding.ASCII.GetBytes(builder.ToString());
        await stream.WriteAsync(requestBytes, 0, requestBytes.Length);
    }
 
    private async Task SendMessage(ClientWebSocket webSocket, string message)
    {
        await webSocket.SendAsync(new ArraySegment<byte>(Encoding.ASCII.GetBytes(message)), WebSocketMessageType.Text, true, default);
    }
 
    private async Task ReceiveMessage(ClientWebSocket webSocket, string expectedMessage)
    {
        var received = new byte[expectedMessage.Length];
 
        var offset = 0;
        WebSocketReceiveResult result;
        do
        {
            result = await webSocket.ReceiveAsync(new ArraySegment<byte>(received, offset, received.Length - offset), default);
            offset += result.Count;
        } while (!result.EndOfMessage);
 
        Assert.Equal(expectedMessage, Encoding.ASCII.GetString(received));
    }
}