Skip to content

[wasi] Enable SocketsHttpHandler and WebSockets - #136234

Draft
lewing wants to merge 9 commits into
dotnet:mainfrom
lewing:lewing-wasi-sockets-http-handler
Draft

lewing wants to merge 9 commits into
dotnet:mainfrom
lewing:lewing-wasi-sockets-http-handler

Conversation

@lewing

@lewing lewing commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Enables SocketsHttpHandler, System.Net.WebSockets and ClientWebSocket on WASI over wasi:sockets. WASI has no System.Net.Security, so only http:// and ws:// work. This lets WASI apps and tools (for example a dotnet watch agent) talk to a local server without wasi:http, which can't upgrade a connection to a WebSocket.

The commits are meant to be reviewed separately:

  1. Enable SocketsHttpHandler and WebSockets on WASI. HttpClientHandler on WASI now follows the mobile UseNativeHttpHandler pattern. The default is still WasiHttpHandler, and WasiApp.targets defaults UseNativeHttpHandler=true, so the SDK passes the switch to ILLink and SocketsHttpHandler is trimmed away. A trimmed hello-world HttpClient app keeps System.Net.Http.dll at ~289 KB by default, and ~631 KB with UseNativeHttpHandler=false.

  2. Zero-byte socket receives. Unix implements these as a 1-byte MSG_PEEK, which WASI doesn't support. HTTP/2 depends on zero-byte reads.

  3. WASI event loop.

    • Poll hooks were re-registered once per subscribed pollable, so each connected socket doubled its hook count every iteration until memory ran out.
    • Connected sockets always subscribed to their (always-writable) output stream, so any idle connection kept the event loop spinning.

    These bugs aren't specific to HTTP, but the new tests hit them.

  4. Tests. Enables the SocketsHttpHandler functional tests, System.Net.WebSockets.Tests and System.Net.WebSockets.Client.Tests on CoreCLR WASI.

    • TLS- and MD5-dependent tests are gated on new PlatformDetection.IsTlsSupported/IsMD5Supported capability probes rather than IsWasi, so they light up if either becomes available.
    • LoopbackProxyServer is now async and doesn't block in Dispose without threads.
    • Configuration.Certificates no longer fails fast when TLS isn't supported. Its PFX files can't be decrypted on WASI.

Known gaps and follow-ups:

  • Mono WASI builds the same libraries but doesn't run these tests.
  • The functional tests use UseNativeHttpHandler=false, so they no longer exercise the default HttpClientHandler over WasiHttpHandler path on WASI.
  • A bound UDP socket still always subscribes to its outgoing stream, so it can keep the event loop busy.
  • A zero-byte receive can complete early once after its data is drained, because wasmtime keeps a socket readable until a read would block. wasmtime treats this as by design (wasmtime-wasi: Unexpected tcp stream break (Returning 0 for std::io::ErrorKind::WouldBlock) bytecodealliance/wasmtime#9667); the following real read waits correctly.
  • HttpClient.DefaultProxy on WASI now reads the standard proxy environment variables, as on Unix.

Validated locally on CoreCLR WASI (Release) with the System.Net.Http functional, System.Net.Sockets, System.Net.WebSockets and System.Net.WebSockets.Client tests, plus macOS runs of the changed shared test infrastructure.

Note

This PR description was generated with GitHub Copilot.

lewing and others added 4 commits October 21, 2026 13:27
Build the real SocketsHttpHandler for WASI on top of wasi:sockets, add a WASI
build of System.Net.WebSockets, and allow ClientWebSocket on WASI.

HttpClientHandler on WASI follows the mobile pattern: it uses the wasi:http
based WasiHttpHandler by default and SocketsHttpHandler when
System.Net.Http.UseNativeHttpHandler is false. WasiApp.targets defaults
UseNativeHttpHandler=true so the SDK passes the switch to the trimmer and the
unused handler is removed.

System.Net.Security has no WASI build, so the socket path doesn't configure
TLS options, and ClientWebSocket only touches SslOptions when a certificate
validation callback is set. Only http:// and ws:// work over SocketsHttpHandler.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Unix implements a zero-byte receive as a 1-byte MSG_PEEK, which WASI sockets
don't support, so the receive failed with OperationNotSupported. SocketsHttpHandler
relies on zero-byte reads for HTTP/2. On WASI, report that the receive would
block; the queued zero-byte operation completes once the socket is readable.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
CheckPollables re-registered a poll hook once per pollable it subscribed, so a
connected socket (input + output pollables) doubled its hook entries every
iteration until memory ran out, and hooks with no pollables were dropped.
Re-register each live hook once.

A connected socket also always subscribed to its output stream, which is
almost always writable, so the event loop never blocked and spun with any idle
connection. Only subscribe to directions with a waiting operation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Run the SocketsHttpHandler functional tests and the System.Net.WebSockets and
System.Net.WebSockets.Client tests on WASI. The test projects use
SocketsHttpHandler through HttpClientHandler (UseNativeHttpHandler=false), as on
Android.

Tests that need TLS or MD5 are gated on new PlatformDetection.IsTlsSupported and
IsMD5Supported capability probes, so they light up if those become available.
Tests that need APM, pipes, NetworkInformation, synchronous socket operations or
blocking waits are skipped on WASI.

Make shared test infrastructure work without TLS or threads:
- Configuration.Certificates only fails fast when TLS is supported, so an
  unloadable test certificate doesn't terminate the test process.
- LoopbackProxyServer uses async I/O and doesn't block in Dispose without
  multithreading.
- DefaultConnectCallback creates an address-family-specific socket on WASI,
  which doesn't support dual-mode sockets.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 4 pipeline(s).
12 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @karelz, @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

lewing and others added 3 commits October 21, 2026 13:44
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Guard SslOptions use in ClientWebSocket on WASI (CA1416), suppress CA1416 for
the System.Net.Http wasi TFM where TLS paths throw PlatformNotSupportedException,
and narrow CreateNativeHandler's return type (CA1859).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lewing
lewing requested a balanced review from Copilot October 21, 2026 20:27
@lewing lewing added arch-wasm WebAssembly architecture os-wasi Related to WASI variant of arch-wasm labels Oct 21, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

@lewing
lewing requested a review from pavelsavara October 21, 2026 20:34

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Source inspection confirms zero-byte receive correctness issues and missing TLS guards in newly enabled tests.

5 open issues
What changed in this PR

Enables socket-based HTTP and WebSockets on WASI while retaining WasiHttpHandler as the default.

Changes:

  • Adds WASI handler selection and WebSocket support.
  • Adjusts zero-byte receives and event-loop polling.
  • Enables networking tests with capability-based exclusions and asynchronous test infrastructure.

Validation here was limited to source inspection; no builds or tests were run.

File Description
src/​mono/​wasi/​build/​WasiApp.targets Defaults to the native HTTP handler.
src/​libraries/​tests.proj Enables WASI WebSocket test projects.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​Wasi/​WasiEventLoop.cs Preserves each live hook once.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​UnixHandleAsyncContext.Wasi.cs Limits connected TCP subscriptions to pending operations.
src/​libraries/​System.Private.CoreLib/​src/​System/​Threading/​UnixHandleAsyncContext.cs Exposes queue waiting state.
src/​libraries/​System.Net.WebSockets/​tests/​WebSocketStreamTests.cs Separates threading-dependent stream tests.
src/​libraries/​System.Net.WebSockets/​src/​System.Net.WebSockets.csproj Adds the WASI target.
src/​libraries/​System.Net.WebSockets.Client/​tests/​System.Net.WebSockets.Client.Tests.csproj Selects socket-based HTTP for WASI tests.
src/​libraries/​System.Net.WebSockets.Client/​tests/​ConnectTest.Invoker.cs Guards TLS-dependent invoker cases.
src/​libraries/​System.Net.WebSockets.Client/​tests/​ClientWebSocketTestBase.cs Guards certificate callback configuration.
src/​libraries/​System.Net.WebSockets.Client/​tests/​ClientWebSocketOptionsTests.cs Guards secure proxy cases.
src/​libraries/​System.Net.WebSockets.Client/​src/​System/​Net/​WebSockets/​WebSocketHandle.Managed.cs Avoids unsupported WASI TLS configuration.
src/​libraries/​System.Net.WebSockets.Client/​src/​System.Net.WebSockets.Client.csproj Removes the WASI exclusion.
src/​libraries/​System.Net.Sockets/​tests/​FunctionalTests/​SendReceive/​SendReceive.cs Enables zero-byte disconnect coverage.
src/​libraries/​System.Net.Sockets/​src/​System/​Net/​Sockets/​SocketPal.Unix.cs Adds WASI zero-byte receive handling.
src/​libraries/​System.Net.Http/​tests/​FunctionalTests/​System.Net.Http.Functional.Tests.csproj Selects SocketsHttpHandler on WASI.
src/​libraries/​System.Net.Http/​tests/​FunctionalTests/​SyncHttpHandlerTest.cs Updates synchronous-send skip reasons.
src/​libraries/​System.Net.Http/​tests/​FunctionalTests/​SocketsHttpHandlerTest.Http2ExtendedConnect.cs Guards TLS-dependent cases.
src/​libraries/​System.Net.Http/​tests/​FunctionalTests/​SocketsHttpHandlerTest.cs Enables WASI tests and adds capability guards.
src/​libraries/​System.Net.Http/​tests/​FunctionalTests/​SocketsHttpHandlerTest.Cancellation.cs Uses cleartext when TLS is unavailable.
src/​libraries/​System.Net.Http/​tests/​FunctionalTests/​MetricsTest.cs Guards TLS-dependent metrics tests.
src/​libraries/​System.Net.Http/​tests/​FunctionalTests/​HttpClientHandlerTestBase.SocketsHttpHandler.cs Adapts socket creation and TLS configuration.
src/​libraries/​System.Net.Http/​src/​System/​Net/​Http/​WasiHttpHandler/​SystemProxyInfo.Wasi.cs Removes the no-proxy implementation.
src/​libraries/​System.Net.Http/​src/​System/​Net/​Http/​SocketsHttpHandler/​SocketsHttpHandler.cs Reports support on WASI.
src/​libraries/​System.Net.Http/​src/​System/​Net/​Http/​HttpProtocolException.cs Includes HTTP/2 exception helpers on WASI.
src/​libraries/​System.Net.Http/​src/​System/​Net/​Http/​HttpClientHandler.Wasi.InvokeNativeHandler.cs Adds native-handler forwarding.
src/​libraries/​System.Net.Http/​src/​System/​Net/​Http/​HttpClientHandler.cs Removes WASI-specific branches from the shared implementation.
src/​libraries/​System.Net.Http/​src/​System/​Net/​Http/​HttpClientHandler.AnyMobile.cs Extends handler selection to WASI.
src/​libraries/​System.Net.Http/​src/​System/​Net/​Http/​Headers/​KnownHeaders.cs Clarifies the Alt-Svc comment only.
src/​libraries/​System.Net.Http/​src/​System/​Net/​Http/​GlobalHttpSettings.cs Enables HTTP/2 settings on WASI.
src/​libraries/​System.Net.Http/​src/​System.Net.Http.csproj Includes socket HTTP implementation and dependencies.
src/​libraries/​Common/​tests/​TestUtilities/​System/​PlatformDetection.cs Adds TLS and MD5 capability probes.
src/​libraries/​Common/​tests/​System/​Net/​Http/​TestHelper.cs Guards TLS configuration.
src/​libraries/​Common/​tests/​System/​Net/​Http/​LoopbackProxyServer.cs Uses asynchronous I/O and avoids threadless blocking disposal.
src/​libraries/​Common/​tests/​System/​Net/​Http/​HttpClientHandlerTest.Proxy.cs Gates TLS and MD5 scenarios.
src/​libraries/​Common/​tests/​System/​Net/​Http/​HttpClientHandlerTest.MaxResponseHeadersLength.cs Gates threading-dependent tests.
src/​libraries/​Common/​tests/​System/​Net/​Http/​HttpClientHandlerTest.cs Adds capability and unsupported-operation guards.
src/​libraries/​Common/​tests/​System/​Net/​Http/​HttpClientHandlerTest.Asynchrony.cs Gates synchronization-context tests.
src/​libraries/​Common/​tests/​System/​Net/​Configuration.Certificates.cs Limits fail-fast diagnostics to TLS-capable platforms.
src/​coreclr/​vm/​wasm/​wasi/​callhelpers-pinvoke.cpp Documents WebSocket compression consumers in comments.

Review effort: Balanced

Comment thread src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs Outdated
Comment thread src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs Outdated
Comment thread src/libraries/System.Net.Sockets/src/System/Net/Sockets/SocketPal.Unix.cs Outdated
lewing and others added 2 commits October 21, 2026 15:59
Completing a queued zero-byte receive on any poll event mistook output
readiness for input, left receives on unconnected TCP sockets pending, and
completed UDP receives before a datagram arrived. Poll the socket for input
instead, and report the receive error when it can't be polled.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lewing

lewing commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

/azp run runtime-coreclr outerloop

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 1 pipeline(s).

@lewing

lewing commented Oct 21, 2026

Copy link
Copy Markdown
Member Author

I'm staging several follow-ups to this to add back wasihttphandler tests, enable more features and fix the things uncovered in the process.

@@ -0,0 +1,109 @@
// Licensed to the .NET Foundation under one or more agreements.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file name confuses me. I would say that the WasiHttpHandler is "system" HTTP client, rather than "native". As in "system interface"

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this follows the mobile naming convention, native here is essentially "not managed/system" vs socketshttphandler which is the managed implementation on top of sockets

var holders = new List<PollableHolder>(s_pollables.Count);
var hooks = new List<PollHook>(s_pollables.Count);
var pending = new List<Pollable>(s_pollables.Count);
// One entry per subscribed pollable, used to map ready indexes back to their hook.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like good bugfix to separate into different PR

public static bool TryCompleteReceiveFrom(SafeSocketHandle socket, IList<ArraySegment<byte>> buffers, SocketFlags flags, Span<byte> socketAddress, out int socketAddressLen, out int bytesReceived, out SocketFlags receivedFlags, out SocketError errorCode) =>
TryCompleteReceiveFrom(socket, default(Span<byte>), buffers, flags, socketAddress, out socketAddressLen, out bytesReceived, out receivedFlags, out errorCode);

// WASI sockets don't support MSG_PEEK, so a zero-byte receive checks readiness with a non-blocking poll instead.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please separate socket fixes into different PR, thanks

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-System.Net.Http os-wasi Related to WASI variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants