-
Notifications
You must be signed in to change notification settings - Fork 10.4k
Implement Http/2 WebSockets #41558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement Http/2 WebSockets #41558
Changes from all commits
e0092f0
dbdcd89
7cb5829
62a8e3f
3636f70
fe0094e
c4a1207
22044ef
69d715a
fc10999
428551f
f8ac923
eb7d237
acc77b7
c78bcee
9048dfe
4b82712
ff7fd2b
5e413ba
237d79c
9a98b8d
14f39cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using System.Diagnostics.CodeAnalysis; | ||
|
||
namespace Microsoft.AspNetCore.Http.Features; | ||
|
||
/// <summary> | ||
/// Used with protocols that require the Extended CONNECT handshake such as HTTP/2 WebSockets and WebTransport. | ||
/// https://www.rfc-editor.org/rfc/rfc8441#section-4 | ||
/// </summary> | ||
public interface IHttpExtendedConnectFeature | ||
{ | ||
/// <summary> | ||
/// Indicates if the current request is a Extended CONNECT request that can be transitioned to an opaque connection via AcceptAsync. | ||
/// </summary> | ||
[MemberNotNullWhen(true, nameof(Protocol))] | ||
bool IsExtendedConnect { get; } | ||
|
||
/// <summary> | ||
/// The <c>:protocol</c> header included in the request. | ||
/// </summary> | ||
string? Protocol { get; } | ||
|
||
/// <summary> | ||
/// Send the response headers with a 200 status code and transition to opaque streaming. | ||
/// </summary> | ||
/// <returns>An opaque bidirectional stream.</returns> | ||
ValueTask<Stream> AcceptAsync(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
#nullable enable | ||
Microsoft.AspNetCore.Http.Features.IHttpExtendedConnectFeature | ||
Microsoft.AspNetCore.Http.Features.IHttpExtendedConnectFeature.AcceptAsync() -> System.Threading.Tasks.ValueTask<System.IO.Stream!> | ||
Microsoft.AspNetCore.Http.Features.IHttpExtendedConnectFeature.IsExtendedConnect.get -> bool | ||
Microsoft.AspNetCore.Http.Features.IHttpExtendedConnectFeature.Protocol.get -> string? |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
// Licensed to the .NET Foundation under one or more agreements. | ||
// The .NET Foundation licenses this file to you under the MIT license. | ||
|
||
using Microsoft.AspNetCore.Builder; | ||
using Microsoft.AspNetCore.Hosting; | ||
using Microsoft.AspNetCore.Http; | ||
using Microsoft.AspNetCore.Http.Features; | ||
using Microsoft.AspNetCore.TestHost; | ||
using Microsoft.Extensions.Hosting; | ||
using Microsoft.Net.Http.Headers; | ||
|
||
namespace Microsoft.AspNetCore.WebSockets.Tests; | ||
|
||
public class Http2WebSocketTests | ||
{ | ||
[Fact] | ||
public async Task Http2Handshake_Success() | ||
{ | ||
using var host = new HostBuilder() | ||
.ConfigureWebHost(webHost => | ||
{ | ||
webHost.UseTestServer(); | ||
webHost.Configure(app => | ||
{ | ||
app.UseWebSockets(); | ||
app.Run(httpContext => | ||
{ | ||
Assert.True(httpContext.WebSockets.IsWebSocketRequest); | ||
Assert.Equal(new[] { "p1", "p2" }, httpContext.WebSockets.WebSocketRequestedProtocols); | ||
return httpContext.WebSockets.AcceptWebSocketAsync("p2"); | ||
}); | ||
}); | ||
}).Start(); | ||
|
||
var testServer = host.GetTestServer(); | ||
|
||
var result = await testServer.SendAsync(httpContext => | ||
{ | ||
httpContext.Request.Method = HttpMethods.Connect; | ||
httpContext.Features.Set<IHttpExtendedConnectFeature>(new ConnectFeature() | ||
{ | ||
IsExtendedConnect = true, | ||
Protocol = "WebSocket", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we test sending and receiving data frames over the WebSocket and then gracefully closing it here? I understand that nothing should change relative to HTTP/1.1 WebSockets once you get the underlying Stream, but it's good to verify. It's also tempting to open up the stream and connection windows in both directions Http2ConnectionTests and create a duplex Stream over the data frames for the request and wrap that in a client WebSocket so we can get better end-to-end testing of this scenario since we don't have any HttpClient or JS WebSocket based tests for the end-to-end. That'd be a more work, but probably not too much. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm planning to add more functional tests once the HttpClient implementation is available. Without that it's pretty hard to write any realistic tests. The client team has asked us to merge this ASAP so they can use it develop the client. |
||
}); | ||
httpContext.Request.Headers.SecWebSocketVersion = Constants.Headers.SupportedVersion; | ||
httpContext.Request.Headers.SecWebSocketProtocol = "p1, p2"; | ||
}); | ||
|
||
Assert.Equal(StatusCodes.Status200OK, result.Response.StatusCode); | ||
var headers = result.Response.Headers; | ||
Assert.Equal("p2", headers.SecWebSocketProtocol); | ||
Assert.False(headers.TryGetValue(HeaderNames.Connection, out var _)); | ||
Assert.False(headers.TryGetValue(HeaderNames.Upgrade, out var _)); | ||
Assert.False(headers.TryGetValue(HeaderNames.SecWebSocketAccept, out var _)); | ||
} | ||
|
||
public sealed class ConnectFeature : IHttpExtendedConnectFeature | ||
{ | ||
public bool IsExtendedConnect { get; set; } | ||
public string Protocol { get; set; } | ||
public Stream Stream { get; set; } = Stream.Null; | ||
|
||
/// <inheritdoc/> | ||
public ValueTask<Stream> AcceptAsync() | ||
{ | ||
if (!IsExtendedConnect) | ||
{ | ||
throw new InvalidOperationException("This is not an Extended CONNECT request."); | ||
} | ||
|
||
return new ValueTask<Stream>(Stream); | ||
} | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.