Skip to content
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

WebTransport API #42490

Closed
Daniel-Genkin-MS-2 opened this issue Jun 29, 2022 · 8 comments
Closed

WebTransport API #42490

Daniel-Genkin-MS-2 opened this issue Jun 29, 2022 · 8 comments
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions
Milestone

Comments

@Daniel-Genkin-MS-2
Copy link
Contributor

Daniel-Genkin-MS-2 commented Jun 29, 2022

Background and Motivation

I am working on adding WebTransport support to Kestrel (PR #42097). That PR adds a few new public APIs.

Proposed API

namespace Microsoft.AspNetCore.Server.Kestrel.Core.Features;

/// <summary>
/// Controls the session and streams of a WebTransport session.
/// </summary>
+ public interface IWebTransportSession
{
    /// <summary>
    /// The id of the WebTransport session.
    /// </summary>
+   public long SessionId { get; }

    /// <summary>
    /// Abruptly close the WebTransport session and stop all the streams.
    /// </summary>
    /// <param name="errorCode">HTTP error code that corresponds to the reason for causing the abort.</param>
    /// <exception cref="Exception">If This is not a valid WebTransport session.</exception>
+   public void Abort(int errorCode = (int)Http3ErrorCode.NoError);

    /// <summary>
    /// Returns the next incoming stream in the order which Kestel received it. The stream can be either bidirectional or unidirectional.
    /// </summary>
    /// <remarks>To use WebTransport, you must first enable the Microsoft.AspNetCore.Server.Kestrel.Experimental.WebTransportAndH3Datagrams AppContextSwitch</remarks>
    /// <exception cref="Exception">If this is not a valid WebTransport session or it fails to get a stream to accept.</exception>
    /// <param name="cancellationToken">The cancellation token used to cancel the operation.</param>
    /// <returns>The unidirectional or bidirectional stream that is next in the queue.</returns>
+   public ValueTask<WebTransportStream> AcceptStreamAsync(CancellationToken cancellationToken);

    /// <summary>
    /// Opens a new unidirectional output stream.
    /// </summary>
    /// <param name="cancellationToken">The cancellation token used to cancel the operation.</param>
    /// <exception cref="Exception">If This is not a valid WebTransport session.</exception>
    /// <returns>The unidirectional stream that was opened.</returns>
+   public ValueTask<WebTransportStream> OpenUnidirectionalStreamAsync(CancellationToken cancellationToken);
}
namespace Microsoft.AspNetCore.Http.Features;

/// <summary>
/// API for accepting and retrieving WebTransport sessions.
/// </summary>
+ public interface IHttpWebTransportFeature
{
    /// <summary>
    /// Indicates if this request is a WebTransport request.
    /// </summary>
+   public bool IsWebTransportRequest { get; }

    /// <summary>
    /// Accept the session request and allow streams to start being used.
    /// </summary>
    /// <param name="cancellationToken">The cancellation token to cancel waiting for the session.</param>
    /// <returns>An instance of a WebTransportSession which will be used to control the connection.</returns>
    [RequiresPreviewFeatures("WebTransport is a preview feature")]
+   public ValueTask<IWebTransportSession> AcceptAsync(CancellationToken cancellationToken);
}
namespace Microsoft.AspNetCore.Server.Kestrel.Core;

/// <summary>
/// Represents a base WebTransport stream. Do not use directly as it does not
/// contain logic for handling data.
/// </summary>
+public class WebTransportStream : Stream
{
    /// <summary>
    /// Hard abort the stream and cancel data transmission.
    /// </summary>
    /// <param name="errorCode"> the error code to pass into the logs</param>
+    public void Abort(int errorCode = (int)Http3ErrorCode.NoError);
}

Usage Examples

For a nearly-exhaustive piecewise view of how this API can be used you can refer to this doc: https://github.com/dotnet/aspnetcore/blob/c5e66c255c0254a9be191ee53031cec6b79def48/docs/WebTransport.md

Example 1

This example waits for a bidirectional stream. Once it receives one, it will read the data from it, reverse it and then write it back to the stream.

public void Configure(IApplicationBuilder app)
    {
        var memory = new Memory<byte>(new byte[4096]);

        app.Use(async (context, next) =>
        {
            var feature = context.Features.GetRequiredFeature<IHttpWebTransportFeature>();
            if (feature.IsWebTransportRequest)
            {
                // accept a new session
                var session = await feature.AcceptAsync(CancellationToken.None);

                WebTransportStream stream;
                do
                {
                    // wait until we get a bidirectional stream
                    stream = await session.AcceptStreamAsync(CancellationToken.None);
                } while (stream.CanRead && stream.CanWrite);

                // read some data from the stream
                var length = await stream.ReadAsync(memory, CancellationToken.None);

                // do some operations on the contents of the data
                memory.Span.Reverse();

                // write back the data to the stream
                await stream.WriteAsync(memory, CancellationToken.None);
                await stream.FlushAsync(CancellationToken.None);
            }
            else
            {
                await next(context);
            }
        });
    }

Example 2

This example opens a new stream from the server side and then sends data.

public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            var feature = context.Features.GetRequiredFeature<IHttpWebTransportFeature>();
            if (feature.IsWebTransportRequest)
            {
                // accept a new session
                var session = await feature.AcceptAsync(CancellationToken.None);

                // open a new stream from the server to the client
                var stream = await session.OpenUnidirectionalStreamAsync(CancellationToken.None);

                // write data to the stream
                await stream.WriteAsync(new Memory<byte>(new byte[] { 65, 66, 67, 68, 69 }), CancellationToken.None);
                await stream.FlushAsync(CancellationToken.None);
            }
            else
            {
                await next(context);
            }
        });
    }

Alternative Designs

There are many other possible designs such as using Action and Func parameters when accepting and openning streams and sessions. Then those could be called when a new session or stream is openned instead of the async/await design. However, most of ASP.NET seems to be using async/await.

I also looked into making the WebTransportStream extend from the Http3Stream class. However, as WebTransport streams don't need all the framing logic, it just complicated the design. Also, technically WebTransport is defined over HTTP/2 as well. So, this would be incorrect if we wanted to add WebTransport over HTTP/2 in the future.

Lastly, I also looked into separating the WebTransportStream class into a WebTransportinputStream, WebTransportOutputStream and a WebTransportBidirectionalStream. This caused a lot of code duplication and unnecessary complexity though. Moreover, it caused me to need to restructure the HTTP/3 stream handling logic as non-bidirectional HTTP/3 streams are assumed to be control streams. This new proposed solution avoids all this added complexity.

Risks

As WebTransport is hidden behind an AppContext switch and behind a PreviewFeature annotation, the risks are only enabled once the user specifically opts-in. Having said that, this API is only adding new functionality and trying to minimize the changes to existing functionality.

@Daniel-Genkin-MS-2 Daniel-Genkin-MS-2 added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Jun 29, 2022
@campersau
Copy link
Contributor

Nit: interface members don't need a public access modifier.

@adityamandaleeka adityamandaleeka added api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Jun 29, 2022
@Tratcher
Copy link
Member

Tratcher commented Jul 1, 2022

Remove the Stream Override APIs from WebTransportStream's API summary, those don't need to be reviewed.

@halter73
Copy link
Member

halter73 commented Jul 6, 2022

API Review Notes:

  • Can WebTransportStream be a ConnectionContext to align with Kestrel and SignalR transports?
    • It seems like it lines up nicely
  • The CancellationToken parameters should have default values.
  • Should public ValueTask<WebTransportStream> ... be public ValueTask<ConnectionContext> or public ValueTask<WebTransportConnectionContext>?
    • You can use features if you really need to.
    • It is sad that you have to use the IStreamAbortFeature (or whatever it's really called).
    • We can have the derived type and back them with features.
  • Can IWebTransportSession be a MultiplexedConnectionContext?
    • Maybe! But possibly as a follow up change.
  • All the approved public types belong in the Microsoft.AspNetCore.Http.Features assembly.

API Approved!

namespace Microsoft.AspNetCore.Http.Features;

+ public interface IWebTransportSession
+ {
+   public long SessionId { get; }
+   public void Abort(int errorCode = (int)Http3ErrorCode.NoError);
+   public ValueTask<ConnectionContext?> AcceptStreamAsync(CancellationToken cancellationToken = default);
+   public ValueTask<ConnectionContext> OpenUnidirectionalStreamAsync(CancellationToken cancellationToken = default);
+ }

+ public interface IHttpWebTransportFeature
+ {
+   public bool IsWebTransportRequest { get; }
+   [RequiresPreviewFeatures("WebTransport is a preview feature")]
+   public ValueTask<IWebTransportSession> AcceptAsync(CancellationToken cancellationToken = default);
+ }

@halter73 halter73 added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Jul 6, 2022
@adityamandaleeka adityamandaleeka added this to the 7.0-preview7 milestone Jul 6, 2022
@adityamandaleeka
Copy link
Member

Closing; this went in for Preview 7.

@adityamandaleeka
Copy link
Member

Reopening; need to reevaluate where we put the RequiresPreviewFeatures thing.

@halter73 halter73 added api-suggestion Early API idea and discussion, it is NOT ready for implementation and removed api-approved API was approved in API review, it can be implemented labels Aug 20, 2022
@halter73
Copy link
Member

halter73 commented Aug 20, 2022

These entire interfaces need to be marked preview. Given that you need to opt-in to preview features to use this anyway, we should go all the way. Down the road, we might want different interfaces. We might want to use MultiplexedConnectionContext instead for example.

namespace Microsoft.AspNetCore.Http.Features;

+ [RequiresPreviewFeatures("WebTransport is a preview feature")]
public interface IWebTransportSession
{
    public long SessionId { get; }
    public void Abort(int errorCode = (int)Http3ErrorCode.NoError);
    public ValueTask<ConnectionContext?> AcceptStreamAsync(CancellationToken cancellationToken = default);
    public ValueTask<ConnectionContext> OpenUnidirectionalStreamAsync(CancellationToken cancellationToken = default);
}

+ [RequiresPreviewFeatures("WebTransport is a preview feature")]
public interface IHttpWebTransportFeature
{
    public bool IsWebTransportRequest { get; }
-   [RequiresPreviewFeatures("WebTransport is a preview feature")]
    public ValueTask<IWebTransportSession> AcceptAsync(CancellationToken cancellationToken = default);
}

@halter73 halter73 added api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Aug 20, 2022
@ghost
Copy link

ghost commented Aug 20, 2022

Thank you for submitting this for API review. This will be reviewed by @dotnet/aspnet-api-review at the next meeting of the ASP.NET Core API Review group. Please ensure you take a look at the API review process documentation and ensure that:

  • The PR contains changes to the reference-assembly that describe the API change. Or, you have included a snippet of reference-assembly-style code that illustrates the API change.
  • The PR describes the impact to users, both positive (useful new APIs) and negative (breaking changes).
  • Someone is assigned to "champion" this change in the meeting, and they understand the impact and design of the change.

@halter73 halter73 added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for formal API review - https://github.com/dotnet/apireviews labels Aug 22, 2022
@adityamandaleeka
Copy link
Member

Done in #43419

@ghost ghost locked as resolved and limited conversation to collaborators Sep 22, 2022
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Aug 25, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions
Projects
None yet
Development

No branches or pull requests

7 participants