Add APIs for reading/writing HTTP Datagrams - #106
Conversation
### Motivation Users may wish to send/receive HTTP Datagrams ([RFC 9297](https://datatracker.ietf.org/doc/html/rfc9297)) using NIOHTTPServer. This PR introduces reader and writer APIs for unreliable datagrams. Note that the underlying support is yet to be provided by `swift-nio-http3`. The read/write source is not wired up, and we currently throw an error when attempting to read or write to/from the unreliable datagram stream. ### Modifications - Added an `UnstableHTTPDatagrams` trait, disabled by default. All new APIs are behind that trait (note that the `HTTP3` trait must also be enabled as unreliable datagrams are currently only supported over QUIC). - Added a `withDatagramReader` method on `NIOHTTPServer.Reader` and a `withDatagramWriter` method on `NIOHTTPServer.ResponseSender.Writer`. Both methods are `consuming` and take a closure that receive the reliable request/response body reader/writer and optionally the unreliable datagram reader/writer as arguments. - In both methods, the readers/writers are passed as `consuming sending`, so callers can run the readers/writers separately in concurrent tasks. - Added `ConnectUDPExample.swift` to show the proposed shape end to end. ### Result APIs for sending/receiving unreliable HTTP Datagrams added.
| #if HTTP3 && UnstableHTTPDatagrams | ||
| /// The unreliable datagram reader, present when the underlying transport is capable of reading/writing | ||
| /// unreliable datagrams. | ||
| private var datagramReader: Disconnected<NIOHTTPServer.DatagramReader>? |
There was a problem hiding this comment.
Yes, without this, the datagramReader can't be vended out as sending in withDatagramReader.
There was a problem hiding this comment.
I see.
I am quite unconvinced about storing this reader inside the stream reader to be honest - I think it would make more sense to have this somewhere else, like the request context. But I'm also aware this will likely be temporary until we can come up with a more general solution to CONNECT.
| public consuming func withDatagramWriter( | ||
| _ body: (consuming sending Self, consuming sending NIOHTTPServer.DatagramWriter?) async throws -> Void | ||
| ) async throws { | ||
| let streamFinish = NIOHTTPServer.StreamFinish(writer: self.writer, state: self.writerState) |
There was a problem hiding this comment.
I think this type is a bit unnecessary and adds some indirection which makes things harder to follow, IMO. I'd just finish both streams inline in this func, like we do elsewhere.
There was a problem hiding this comment.
We unfortunately need this indirection because we pass the stream writer (and the datagram writer) as consuming sending to the body closure, which means we can't call finish on either after the body closure.
There was a problem hiding this comment.
This is actually making me doubt we need to have a with-style method (also on the reader). I don't think we should finish the writers implicitly - we don't do this for the normal stream writer: we just expose write and finish. Why not return a tuple with both writers (and readers) instead of having a closure?
There was a problem hiding this comment.
Regardless of whether we should automatically finish or not
Why not return a tuple with both writers (and readers) instead of having a closure?
This won't work because tuples with non-Copyable elements aren't supported yet.
I think the closure shape is the only shape that allows both the readers/writers to be run in concurrent tasks.
| #if HTTP3 && UnstableHTTPDatagrams | ||
| /// The unreliable datagram reader, present when the underlying transport is capable of reading/writing | ||
| /// unreliable datagrams. | ||
| private var datagramReader: Disconnected<NIOHTTPServer.DatagramReader>? |
There was a problem hiding this comment.
I see.
I am quite unconvinced about storing this reader inside the stream reader to be honest - I think it would make more sense to have this somewhere else, like the request context. But I'm also aware this will likely be temporary until we can come up with a more general solution to CONNECT.
| /// | ||
| /// - Note: Both readers are passed as `consuming sending`. This means that `body` can use both readers in separate | ||
| /// tasks. | ||
| public consuming func withDatagramReader( |
There was a problem hiding this comment.
Related to my last comment, some things about this function I'm not sure about. The fact we need to create a new Reader isn't great. Users would also be able to call withDatagramReader again on the streamReader that's returned (since it's the same type), which is a bit weird. Again, perhaps it's fine since this will be temporary, but I thought I'd point them out.
| public consuming func withDatagramWriter( | ||
| _ body: (consuming sending Self, consuming sending NIOHTTPServer.DatagramWriter?) async throws -> Void | ||
| ) async throws { | ||
| let streamFinish = NIOHTTPServer.StreamFinish(writer: self.writer, state: self.writerState) |
There was a problem hiding this comment.
This is actually making me doubt we need to have a with-style method (also on the reader). I don't think we should finish the writers implicitly - we don't do this for the normal stream writer: we just expose write and finish. Why not return a tuple with both writers (and readers) instead of having a closure?
|
LGTM, thank you! |
| extension NIOHTTPServer { | ||
| /// A reader for the unreliable datagram stream. | ||
| public struct DatagramReader: AsyncReader, ~Copyable { | ||
| public typealias ReadElement = UInt8 |
There was a problem hiding this comment.
Should the element be a datagram instead of a byte?
Motivation
Users may wish to send/receive HTTP Datagrams (RFC 9297) using NIOHTTPServer. This PR introduces reader and writer APIs for unreliable datagrams.
Note that the underlying support is yet to be provided by
swift-nio-http3. The read/write source is not wired up, and we currently throw an error when attempting to read or write to/from the unreliable datagram stream.Modifications
Added an
UnstableHTTPDatagramstrait, disabled by default. All new APIs are behind that trait (note that theHTTP3trait must also be enabled as unreliable datagrams are currently only supported over QUIC).Added a
withDatagramReadermethod onNIOHTTPServer.Readerand awithDatagramWritermethod onNIOHTTPServer.ResponseSender.Writer. Both methods areconsumingand take a closure that receive the reliable request/response body reader/writer and optionally the unreliable datagram reader/writer as arguments.consuming sending, so callers can run the readers/writers separately in concurrent tasks.Added
ConnectUDPExample.swiftto show the proposed shape end to end.Result
APIs for sending/receiving unreliable HTTP Datagrams added.