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

http3: support WebTransport #3256

Closed
wants to merge 222 commits into from
Closed

http3: support WebTransport #3256

wants to merge 222 commits into from

Conversation

ydnar
Copy link
Contributor

@ydnar ydnar commented Aug 12, 2021

This PR adds initial support for the WebTransport draft.

To see this in action, open these demos in Chrome Canary:

Background

Internally, it exposes a bit of additional API from the http3 package, including an http3.Conn which wraps quic.EarlySession and an http3.RequestStream which wraps quic.Stream.

An http3.Conn allows client and server code to share common stream and frame handling, as well as providing a foundation for (de)multiplexing datagrams in support of the MASQUE datagram draft. An http3.RequestStream is used to read and write HEADERS and DATA frames, as well as process other HTTP/3 frames like MASQUE CAPSULE frames.

  • An http3.ServerConn is constructed using http3.Accept(quic.EarlySession, Settings).
  • An http3.ClientConn is constructed using http3.Open(quic.EarlySession, Settings).
  • A server http3.RequestStream is accepted from (ServerConn).AcceptRequestStream(context.Context).
  • A client http3.RequestStream is opened with (ClientConn).OpenRequestStream(context.Context).

It is possible to construct alternate HTTP/3 clients or servers using these primitives.

This PR builds on #3238 and #3254. It fixes #3191, and replaces #3226. It also adds support for HTTP trailers, modeled after the trailers interface in the standard net/http package.

To do

Screenshots

Chrome demonstrating WebTransport unidirectional, bidirectional streams, and datagrams:

quic-go-webtransport-datagrams-2

@codecov
Copy link

codecov bot commented Aug 12, 2021

Codecov Report

Merging #3256 (7151b11) into master (c5aeee3) will decrease coverage by 2.07%.
The diff coverage is 57.56%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #3256      +/-   ##
==========================================
- Coverage   85.69%   83.62%   -2.07%     
==========================================
  Files         132      140       +8     
  Lines        9775    10197     +422     
==========================================
+ Hits         8376     8527     +151     
- Misses       1024     1279     +255     
- Partials      375      391      +16     
Impacted Files Coverage Δ
closed_session.go 79.41% <0.00%> (ø)
http3/capsule_type.go 0.00% <0.00%> (ø)
http3/context_extension_type.go 0.00% <0.00%> (ø)
server.go 80.46% <ø> (ø)
session.go 77.63% <ø> (ø)
http3/stream_type.go 21.43% <21.43%> (ø)
packet_handler_map.go 75.00% <25.00%> (ø)
http3/frame_type.go 30.43% <30.43%> (ø)
http3/conn.go 43.49% <43.49%> (ø)
http3/headers.go 55.66% <55.66%> (ø)
... and 21 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c5aeee3...7151b11. Read the comment docs.

http3/client.go Outdated Show resolved Hide resolved
http3/stream.go Outdated Show resolved Hide resolved
@ydnar ydnar changed the title [WIP] http3: add Conn type [WIP] http3: support WebTransport Aug 18, 2021
@ydnar ydnar mentioned this pull request Aug 18, 2021
6 tasks
@ydnar
Copy link
Contributor Author

ydnar commented Aug 18, 2021

@marten-seemann could I get your feedback on this PR?

thanks!

Copy link
Member

@marten-seemann marten-seemann left a comment

Choose a reason for hiding this comment

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

Not a complete review yet, but I already left some comments. Do you have an example how a WebTransport application would use this API? That would make it easier for me to understand.

@@ -89,7 +89,7 @@ func (s *closedLocalSession) destroy(error) {
})
}

func (s *closedLocalSession) getPerspective() protocol.Perspective {
func (s *closedLocalSession) Perspective() Perspective {
Copy link
Member

Choose a reason for hiding this comment

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

I still think this should be handled on the http3 layer, not on the quic layer.

writeDatagram(SessionID, []byte) error
}

type connection struct {
Copy link
Member

Choose a reason for hiding this comment

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

To elaborate, you can have a bool here which tells you if this is a client or a server connection. Then you don't need to ask the Session for its Perspective(). You just have to set that bool once when you initialize this struct.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

How would Open or Accept know to error on the wrong perspective if it's not exposed at the quic layer?

streamType := StreamType(t)

// Store control, QPACK, and push streams on conn
if streamType < 4 {
Copy link
Member

Choose a reason for hiding this comment

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

Where does this magic number come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

4 standard H3 streams: control, qpack, and push (0x0-0x3).

http3/conn.go Outdated
incomingStreamsMutex sync.Mutex
incomingStreams map[SessionID]chan quic.Stream // Lazily constructed

incomingUniStreamsOnce sync.Once
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to start the go routine right away, and get rid of this sync.Once? Every HTTP/3 session will use unidirectional streams.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True.

Deferring it simplifies the test mocking. My thought was that a Conn wouldn't start any goroutines until asked to minimize resource consumption.

@ydnar
Copy link
Contributor Author

ydnar commented Aug 22, 2021

Not a complete review yet, but I already left some comments. Do you have an example how a WebTransport application would use this API? That would make it easier for me to understand.

Thanks!

I'll throw an example together. Are you OK with an examples directory, or would you prefer a separate repo?

FWIW I'm not happy yet with the WebTransport API here. It's still a bit clunky for the server side.

@marten-seemann
Copy link
Member

I'll throw an example together. Are you OK with an examples directory, or would you prefer a separate repo?

Since WebTransport will be explicitly supported by the http3 package, it’s justified (and super helpful!) to have an example in this repository.

Thank you for all the effort you’re putting into this!

@ydnar
Copy link
Contributor Author

ydnar commented Aug 31, 2021

@marten-seemann I merged my other WIP branch into this one, which reorganizes the HTTP/3 primitives into Conn, RequestStream, and FrameReader. I was able to easily add support for HTTP trailers with this, and simplify some of the error handling to match the H3 spec.

Next up is building a WebTransport example and tests.

I’d love it if you could take another look, thanks!

@ydnar
Copy link
Contributor Author

ydnar commented Sep 1, 2021

@marten-seemann any reason you’re still keeping CircleCI around? Doesn’t GH Actions cover these?

@ydnar
Copy link
Contributor Author

ydnar commented Sep 2, 2021

Landed integration tests for WebTransport!

@ydnar
Copy link
Contributor Author

ydnar commented Sep 2, 2021

@ydnar ydnar marked this pull request as ready for review September 8, 2021 21:32
@ydnar
Copy link
Contributor Author

ydnar commented Sep 8, 2021

Demo server built on this code: https://basic.webtransport.dev/

@ydnar ydnar changed the title [WIP] http3: support WebTransport http3: support WebTransport Sep 8, 2021
@ydnar
Copy link
Contributor Author

ydnar commented Sep 14, 2021

Multiplayer mouse cursor demo: https://cursors.webtransport.dev/

Copy link
Member

@marten-seemann marten-seemann left a comment

Choose a reason for hiding this comment

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

Hi @ydnar,
I'm sorry for the late review. I really appreciate all the effort you're putting into is! The reason I'm so slow with reviews here is that I honestly don't know how to review a PR that touch 5k LOC. Going through it commit-by-commit is also not feasible, considering that this branch contains >200 commits.
It would be a lot easier and faster to review if these were separate, independent PRs.

From a quick glance over your PR, I'm not sure if all changes here are actually used and needed to make WebTransport work. For example, it's not clear to me

  1. what the CAPSULE frame does. You're defining it, but it doesn't seem to be used (unless I'm missing something)
  2. why we're adding HTTP Trailer support in this PR. As far as I understand, it's not needed for WebTransport, is it?

If they're actually not necessary, could you remove them from this PR (along with all the other things that are not needed)? If they are necessary, they sound like changes that could be split into separate PRs.

onFrameError func()

bytesRemainingInFrame uint64
onTrailers trailerFunc
Copy link
Member

Choose a reason for hiding this comment

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

Is trailer parsing something we have to do in this PR? It would be really helpful to separate stuff like this into smaller, self-contained PRs.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Trailers are not required for WebTransport, but were trivial to add using the new http3.Conn and RequestStream primitives. I can separate this out.

// Settings specifies the HTTP/3 settings transmitted to the server.
// If nil, reasonable default values will be used.
// See https://quicwg.org/base-drafts/draft-ietf-quic-http.html#name-http-2-settings-parameters.
// If non-nil, overrides EnableDatagrams and EnableWebTransport.
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need this? I don't like the fact that this overrides other options.
Do we need to export Settings at all? Ideally, the user of the library wouldn't have to deal with setting code points at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My preference is to remove EnableDatagrams and EnableWebTransport in favor of letting the caller specify Settings.

The Settings type has helpers for enabling datagrams or WebTransport, or other common settings.

)

// StreamType represents an HTTP/3 stream type.
type StreamType uint64
Copy link
Member

Choose a reason for hiding this comment

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

Why is this needed? Why is it exported?


// A ContextExtensionType represents an HTTP datagram context extension type code.
// https://www.ietf.org/archive/id/draft-ietf-masque-h3-datagram-03.html#name-context-extension-types
type ContextExtensionType uint64
Copy link
Member

Choose a reason for hiding this comment

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

This type seems entirely unused.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CapsuleType and ContextExtensionType are intended to support the HTTP/3 MASQUE datagram draft, which the WebTransport draft is converging against.

This can be deferred to a future PR.

// value will be clamped to GreaseMax.
func writeGreaseFrame(w quicvarint.Writer, n uint64) {
quicvarint.Write(w, Grease(n))
quicvarint.Write(w, 0) // Zero frame payload length
Copy link
Member

Choose a reason for hiding this comment

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

Better: this should probably write a (short) frame.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good idea!


// RequestHeaders returns valid HTTP/3 header fields for req, or an error if req
// is malformed.
func RequestHeaders(req *http.Request) ([]qpack.HeaderField, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Not sure where this is coming from. I assume that you renamed a file and now Git recognizes it as new? It would be helpful if you revert the renaming, so Git can show the actual changes that happened here.

Copy link
Contributor Author

@ydnar ydnar Sep 23, 2021

Choose a reason for hiding this comment

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

It wasn’t so much of a rename as a refactor of (*requestWriter).writeHeaders. The rest of requestWriter was subsumed by http3.client and http3.RequestStream.

The requestWriter type is now gone.

return id&0b11 == 0
}

type requestStream struct {
Copy link
Member

Choose a reason for hiding this comment

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

Where does this come from? I assume some renamed file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The requestStream type is entirely new.


// A CapsuleType represents an HTTP capsule type.
// https://www.ietf.org/archive/id/draft-ietf-masque-h3-datagram-03.html#name-capsule-types
type CapsuleType uint64
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't seem to be used anywhere, is it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

CapsuleType and ContextExtensionType are intended to support the HTTP/3 MASQUE datagram draft, which the WebTransport draft is converging against.

This can be deferred to a future PR.

// If a frame has already been partially read from R, a FrameReader
// can be initialized by setting Type to the current frame type and N to
// the number of bytes remaining in the payload.
type FrameReader struct {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this exported? What was wrong with the parseNextFrame function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FrameReader allows the caller to determine which frames to skip, and makes no attempt to parse the underlying frames. This was necessary because different stream types (and perspectives) support specific frame types, and the spec(s) require closing the stream or connection in the event of certain frame types.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It’s exported so callers implementing lower-level parts of the HTTP/3 protocol could use the same mechanism. In theory, this could go in a http3guts package.


// A FrameType represents an HTTP/3 frame type.
// https://www.ietf.org/archive/id/draft-ietf-quic-http-34.html#name-frame-definitions
type FrameType uint64
Copy link
Member

Choose a reason for hiding this comment

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

Why is this exported?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

FrameType constants are (IETF) spec-defined values which can be used by callers implementing parts of HTTP/3 directly.

ydnar added a commit to alta/quic-go that referenced this pull request Oct 4, 2021
… trailers

This is a squashed commit of quic-go#3256. Subsequent commits will remove:

- WebTransport
- Trailers
- Preemptive MASQUE/DATAGRAM features

To slim down this PR.
@Atul-Bari
Copy link

@marten-seemann I merged my other WIP branch into this one, which reorganizes the HTTP/3 primitives into Conn, RequestStream, and FrameReader. I was able to easily add support for HTTP trailers with this, and simplify some of the error handling to match the H3 spec.

Next up is building a WebTransport example and tests.

I’d love it if you could take another look, thanks!

Hey @ydnar
Can you share an example to test this
I am trying this https://gist.github.com/ydnar/f73e1806c50c83f90f9b46e13bf751fc for localhost server,

@marten-seemann
Copy link
Member

@ydnar Are you going to participate in the WebTransport interop testing? https://mailarchive.ietf.org/arch/msg/webtransport/8c2dp_MP9GQEcnmurndjwAx8sOI/

@ydnar
Copy link
Contributor Author

ydnar commented Oct 26, 2021

@ydnar Are you going to participate in the WebTransport interop testing? https://mailarchive.ietf.org/arch/msg/webtransport/8c2dp_MP9GQEcnmurndjwAx8sOI/

I'd love to. I'll need your help though. Have you had time to look at #3284?

@marten-seemann
Copy link
Member

Not yet. Is that the replacement for this PR?
What do you need help with? Can you do interop tests with this branch?

@ydnar
Copy link
Contributor Author

ydnar commented Oct 26, 2021

#3284 is a discrete PR that implements the foundation necessary to support the datagram draft, capsule support, WebTransport, and HTTP trailers.

I broke it out 3 weeks ago at your request. See the #quic-go channel in Slack for more context.

The commits can be reviewed in reverse order to see how each of the above features depend on it.

If you can review and merge that, we can start adding WT or the datagram draft next.

@marten-seemann
Copy link
Member

#3284 is a discrete PR that implements the foundation necessary to support the datagram draft, capsule support, WebTransport, and HTTP trailers.

Now I'm really confused. Why first add features, and then remove them in subsequent commits? I have no idea how to start reviewing this.

@ydnar
Copy link
Contributor Author

ydnar commented Oct 26, 2021

#3284 is a discrete PR that implements the foundation necessary to support the datagram draft, capsule support, WebTransport, and HTTP trailers.

Now I'm really confused. Why first add features, and then remove them in subsequent commits? I have no idea how to start reviewing this.

This PR is big. At your request, I broke it down into discrete parts.

  • http3: Conn and RequestStream types #3284 is the first part, which lays the groundwork for supporting WebTransport, datagrams, and HTTP trailers. I created it by squash rebasing this PR, and then progressively removed features in discrete commits.
  • After it’s merged, I’ll create a new PR or rebase this PR on top of it to add WebTransport. Similarly, I can create a discrete PR that adds support for HTTP trailers.
  • It may make more sense to add datagram support first, then layer on WebTransport, or do them in the same PR. It‘s been a few weeks since I looked at the drafts, so I’m not sure what the best order of operations is.

ydnar added a commit to alta/quic-go that referenced this pull request Oct 31, 2021
… trailers

This is a squashed commit of quic-go#3256. Subsequent commits will remove:

- WebTransport
- Trailers
- Preemptive MASQUE/DATAGRAM features

To slim down this PR.
@ydnar
Copy link
Contributor Author

ydnar commented Oct 31, 2021

@marten-seemann I updated the description on #3284 to clarify what it is. How can I help you get #3284 reviewed and merged so we can land datagram and WebTransport support?

@gedw99
Copy link

gedw99 commented Nov 13, 2021

Wow this looks great

can’t wait to try this compared to web sockets

@gedw99
Copy link

gedw99 commented Jan 4, 2022

@marten-seemann

is there a possibility that this will be merged ?

Are there technical blockers that your worried about or is it more just lack of time ?

@adriancable
Copy link

adriancable commented Feb 17, 2022

@ydnar - I am very interested in this. I think that WebTransport support is important for the future of quic-go, and it's sad to see it has not yet been merged.

A couple of questions. @marten-seemann / @lucas-clemente can you share whether this not being merged is due to lack of time or lack of interest? You sharing that with us would be very helpful to the community on how best to move forward. If it is lack of interest, we respect that as it is your project after all, but communicating that directly to us would be helpful to prevent further development work on a PR that will never get merged. So, I would appreciate very much if you could take a few moments to do that. Thank you!

@ydnar - can you kindly share the disco/DISCO bidirectional stream example server (in other words, what's running at 'the other end' of your browser screenshot) to make this easier to test? Thank you.

@ydnar
Copy link
Contributor Author

ydnar commented Feb 22, 2022

@ydnar - can you kindly share the disco/DISCO bidirectional stream example server (in other words, what's running at 'the other end' of your browser screenshot) to make this easier to test? Thank you.

Sure. I need to dust off the branch that enables this. I’d prefer not to maintain a fork of quic-go just to support WebTransport (or a fork of quic/http3).

There’s some relevant stuff in Go 1.18 that should improve QUIC performance too (allocation-free UDP, net/netip package).

@adriancable
Copy link

Hi @ydnar - actually, I got it figured out.

I decided to develop a new Go package which implements a WebTransport server over HTTP/3 (updated per the latest IETF drafts). Its API is inspired by the PR’s API but it implements it with the standard quic-go package as a dependency. It has pretty complete support for all of WebTransport including datagrams, server and client initiated uni/bidi streams and so on. It’s currently fully functional but needs docs written and further testing. I’ll share the link when ready if interested.

@ydnar
Copy link
Contributor Author

ydnar commented Feb 22, 2022

Hi @ydnar - actually, I got it figured out.

I decided to develop a new Go package which implements a WebTransport server over HTTP/3 (updated per the latest IETF drafts). Its API is inspired by the PR’s API but it implements it with the standard quic-go package as a dependency. It has pretty complete support for all of WebTransport including datagrams, server and client initiated uni/bidi streams and so on. It’s currently fully functional but needs docs written and further testing. I’ll share the link when ready if interested.

Sure, I’ll take a look!

@adriancable
Copy link

Sure, I’ll take a look!

@ydnar - great!

Package is here: github.com/adriancable/webtransport-go

Example server & client are here: github.com/adriancable/webtransport-go-example

GoDoc is here: pkg.go.dev/github.com/adriancable/webtransport-go

Hope that someone finds this useful.

@marten-seemann
Copy link
Member

Closing, since there's now a WebTransport implementation using quic-go: https://github.com/marten-seemann/webtransport-go.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

WebTransport Support
5 participants