Skip to content

HttpListenerContext.AcceptWebSocketAsync unsupported for IWindowsPrincipal in .NET Core 3.1 #63738

Closed
@iconics-janb

Description

@iconics-janb

Description

HttpListenerContext.AcceptWebSocketAsync throws PlatformNotSupportedException when called with User set to WindowsPrincipal. This happens in .NET Core 3.1 when running on Windows. Works fine when running in .NET Framework.

Reproduction Steps

using System;
using System.Diagnostics;
using System.Net;
using System.Net.WebSockets;
using System.Threading;
using System.Threading.Tasks;

namespace WebSocketCloseTest
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var serverTask = Server(http://localhost:45000/test/ws/);
            await Client("ws://localhost:45000/test/ws/");
            await serverTask;
        }

        private static async Task Server(String serverAddress)
        {
            var listener = new HttpListener();
            listener.Prefixes.Add(serverAddress);
            listener.AuthenticationSchemes = AuthenticationSchemes.IntegratedWindowsAuthentication;
            listener.Start();

            HttpListenerContext ctx = await listener.GetContextAsync();
            try
            {
                Debug.Assert(ctx.User != null); // this is supposed to be a WindowsPrincipal
                HttpListenerWebSocketContext wsCtx = await ctx.AcceptWebSocketAsync("subProtocol"); // this throws on .NET Core
                await wsCtx.WebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
            }
            finally
            {
                ctx.Response.Close();
            }
        }

        private static async Task Client(String serverAddress)
        {
            try
            {
                using (ClientWebSocket webSocket = new ClientWebSocket())
                {
                    webSocket.Options.AddSubProtocol("subProtocol");
                    webSocket.Options.Credentials = CredentialCache.DefaultCredentials;
                    await webSocket.ConnectAsync(new Uri(serverAddress), CancellationToken.None);
                    await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, null, CancellationToken.None);
                }
            }
            catch (Exception e)
            {
                Console.WriteLine($"Client WS exception: {e.Message}");
            }
        }
    }
}

Expected behavior

It should not throw the exception and work the same way as it does in .NET Framework

Actual behavior

Throws PlatformNotSupportedException.

Regression?

Yes, works in .NET Framework

Known Workarounds

Use reflection to set the _user field to null, make the call and set it back to the original value, like this:

HttpListenerContext ctx = await listener.GetContextAsync();

IPrincipal originalPrincipal = ctx.User;
FieldInfo field = ctx.GetType().GetField("_user", BindingFlags.NonPublic | BindingFlags.Instance);
field.SetValue(_ctx, null);

// make the call with User = null to prevent the exception
wsCtx = await ctx.AcceptWebSocketAsync(WsConstants.SubProtocol, TimeSpan.FromSeconds(WsConstants.KeepAliveIntervalSeconds));

// revert to the original value
field.SetValue(_ctx, originalPrincipal);

Configuration

.NET Core 3.1, Windows 10 x64

Other information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-System.NetenhancementProduct code improvement that does NOT require public API changes/additionshelp wanted[up-for-grabs] Good issue for external contributors

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions