1. Introduction
This section is non-normative.
This specification uses [WEB-TRANSPORT-HTTP3] to send data to and receive data from servers. It can be used like WebSockets but with support for multiple streams, unidirectional streams, out-of-order delivery, and reliable as well as unreliable transport.
Note: The API presented in this specification represents a preliminary proposal based on work-in-progress within the IETF WEBTRANS WG. Since the [WEB-TRANSPORT-HTTP3] specification is a work-in-progress, both the protocol and API are likely to change significantly going forward.
2. Conformance
As well as sections marked as non-normative, all authoring guidelines, diagrams, examples, and notes in this specification are non-normative. Everything else in this specification is normative.
The key words MUST and SHOULD are to be interpreted as described in [RFC2119].
This specification defines conformance criteria that apply to a single product: the user agent that implements the interfaces that it contains.
Conformance requirements phrased as algorithms or specific steps may be implemented in any manner, so long as the end result is equivalent. (In particular, the algorithms defined in this specification are intended to be easy to follow, and not intended to be performant.)
Implementations that use ECMAScript to implement the APIs defined in this specification MUST implement them in a manner consistent with the ECMAScript Bindings defined in the Web IDL specification [WEBIDL], as this specification uses that specification and terminology.
3. Terminology
The EventHandler
interface, representing a callback used for event
handlers, and the ErrorEvent
interface are defined in [HTML].
The concepts queue a task and networking task source are defined in [HTML].
The terms event, event handlers and event handler event types are defined in [HTML].
When referring to exceptions, the terms throw and create are defined in [WEBIDL].
The terms fulfilled, rejected, resolved, pending and settled used in the context of Promises are defined in [ECMASCRIPT-6.0].
The terms ReadableStream
and WritableStream
are defined in [WHATWG-STREAMS].
4. UnidirectionalStreamsTransport
Mixin
A UnidirectionalStreamsTransport
can send and receive unidirectional
streams. Data within a stream is delivered in order, but data between streams
may be delivered out of order. Data is generally sent reliably, but
retransmissions may be disabled or the stream may aborted to produce a form of
unreliability. All stream data is encrypted and congestion-controlled.
interface mixin {
UnidirectionalStreamsTransport Promise <SendStream >createUnidirectionalStream (optional SendStreamParameters = {}); /* a ReadableStream of ReceiveStream objects */
parameters readonly attribute ReadableStream incomingUnidirectionalStreams ; };
4.1. Methods
createUnidirectionalStream()
-
Creates a
SendStream
object for an outgoing unidirectional stream. Note that in some transports, the mere creation of a stream is not immediately visible to the peer until it is used to send data.When
createUnidirectionalStream()
method is called, the user agent MUST run the following steps:-
Let transport be the
UnidirectionalStreamsTransport
on whichcreateUnidirectionalStream
is invoked. -
If transport’s
state
is"closed"
or"failed"
, immediately return a new rejected promise with a newly createdInvalidStateError
and abort these steps. -
Let p be a new promise.
-
Return p and continue the remaining steps in parallel.
-
Create a
SendStream
with transport and p when all of the following conditions are met: -
Queue a task to reject p with a newly created
InvalidStateError
when all of the following conditions are met:-
The transport’s state is
"closed"
or"failed"
. -
p has not been settled.
-
-
incomingUnidirectionalStreams
, of type ReadableStream, readonly-
A
ReadableStream
of unidirectional streams, each represented by aReceiveStream
object, that have been received from the remote host.The getter steps for
incomingUnidirectionalStreams
are:-
Let transport be the
UnidirectionalStreamsTransport
on which theincomingUnidirectionalStreams
getter is invoked. -
Return transport’s [[ReceivedStreams]].
-
For each unidirectional stream received, create a corresponding
ReceiveStream
and insert it into [[ReceivedStreams]]. As data is received over the unidirectional stream, insert that data into the correspondingReceiveStream
object. When the remote side closes or aborts the stream, close or abort the correspondingReceiveStream
.
-
4.2. Procedures
4.2.1. Add SendStream to UnidirectionalStreamsTransport
To create a SendStream
given a transport and a promise p, run the following
steps:
-
Reserve a unidirectional stream association in the underlying transport.
-
Queue a task to run the following sub-steps:
-
If transport’s state is
"closed"
or"failed"
, abort these sub-steps. -
Let stream be a newly created
SendStream
for association. -
Add stream to transport’s [[OutgoingStreams]].
-
Resolve p with stream.
-
4.3. SendStreamParameters Dictionary
The SendStreamParameters
dictionary includes information
relating to stream configuration.
dictionary SendStreamParameters { };
5. BidirectionalStreamsTransport
Mixin
A BidirectionalStreamsTransport
can send and receive bidirectional streams.
Data within a stream is delivered in order, but data between streams may be
delivered out of order. Data is generally sent reliably, but retransmissions
may be disabled or the stream may aborted to produce a form of unreliability.
All stream data is encrypted and congestion-controlled.
interface mixin {
BidirectionalStreamsTransport Promise <BidirectionalStream >createBidirectionalStream (); /* a ReadableStream of BidirectionalStream objects */readonly attribute ReadableStream incomingBidirectionalStreams ; };
5.1. Methods
createBidirectionalStream()
-
Creates a
BidirectionalStream
object for an outgoing bidirectional stream. Note that in some transports, the mere creation of a stream is not immediately visible to the peer until it is used to send data.When
createBidirectionalStream
is called, the user agent MUST run the following steps:-
Let transport be the
BidirectionalStreamsTransport
on whichcreateBidirectionalStream
is invoked. -
If transport’s
state
is"closed"
or"failed"
, immediately return a new rejected promise with a newly createdInvalidStateError
and abort these steps. -
Let p be a new promise.
-
Return p and continue the remaining steps in parallel.
-
Create a
BidirectionalStream
with transport and p when all of the following conditions are met: -
Queue a task to reject p with a newly created
InvalidStateError
when all of the following conditions are met:-
The transport’s state is
"closed"
or"failed"
. -
p has not been settled.
-
-
incomingBidirectionalStreams
, of type ReadableStream, readonly-
Returns a
ReadableStream
ofBidirectionalStream
s that have been received from the remote host.The getter steps for the
incomingBidirectionalStreams
attribute SHALL be:-
Return [[ReceivedBidirectionalStreams]].
-
For each bidirectional stream received, create a corresponding
BidirectionalStream
and insert it into [[ReceivedBidirectionalStreams]]. As data is received over the bidirectional stream, insert that data into the correspondingBidirectionalStream
. When the remote side closes or aborts the stream, close or abort the correspondingBidirectionalStream
.
-
5.2. Procedures
5.2.1. Add BidirectionalStream to BidirectionalStreamsTransport
To create a BidirectionalStream
given a transport and a promise p, run
the following steps:
-
Reserve a bidirectional stream association in the underlying transport.
-
Queue a task to run the following sub-steps:
-
If transport’s state is
"closed"
or"failed"
, abort these sub-steps. -
Let stream be a newly created
BidirectionalStream
object for association. -
Add stream to transport’s [[ReceivedBidirectionalStreams]].
-
Add stream to transport’s [[OutgoingStreams]].
-
Resolve p with stream.
-
6. DatagramTransport
Mixin
A DatagramTransport
can send and receive datagrams,
as defined in [HTTP3-DATAGRAM][QUIC-DATAGRAM].
Datagrams are sent out of order, unreliably, and have a limited maximum size.
Datagrams are encrypted and congestion controlled.
interface mixin DatagramTransport {readonly attribute unsigned short maxDatagramSize ;readonly attribute DatagramDuplexStream datagrams ; };
6.1. Attributes
maxDatagramSize
, of type unsigned short, readonly-
The maximum size data that may be passed to
datagrams
'writable
. datagrams
, of type DatagramDuplexStream, readonly-
A single duplex stream for sending and receiving datagrams over this connection.
Each datagram received will be readable on the
ReadableStream
member.If too many datagrams are queued because the stream is not being read quickly enough, datagrams are dropped to avoid queueing. Implementations should drop older datagrams in favor of newer datagrams. The number of datagrams to queue should be kept small enough to avoid adding significant latency to packet delivery when the stream is being read slowly (due to the reader being slow) but large enough to avoid dropping packets when for the stream is not read for short periods of time (due to the reader being paused).
Datagrams written to the returned
WritableStream
member will be sent.The getter steps for the
datagrams
attribute SHALL be:-
Return this's [[Datagrams]].
-
6.2. Procedures
The readDatagrams algorithm is given a transport as parameter. It is defined by running the following steps:
-
TODO
-
enqueue data in transport.
[[IncomingDatagrams]]
.
The writeDatagrams algorithm is given a transport as parameter and data as input. It is defined by running the following steps:
-
Let timestamp be a timestamp representing now.
-
If data is not a
Uint8Array
object, then return a promise rejected with aTypeError
. -
Let promise be a new promise.
-
Let bytes be a copy of bytes which data represents.
-
Let chunk be a tuple of bytes, timestamp and promise.
-
Enqueue chunk to transport’s [[OutgoingDatagramsQueue]].
-
If the length of transport’s [[OutgoingDatagramsQueue]] is less than transport’s [[OutgoingDatagramsHighWaterMark]], then resolve promise with undefined.
-
Return promise.
Note: The associated WritableStream
calls writeDatagrams only when all the promises that
have been returned by writeDatagrams have been resolved. Hence the timestamp and the expiration
duration work well only when the web developer pays attention to WritableStreamDefaultWriter.ready
.
To sendDatagrams, given a WebTransport
object transport, run these steps:
-
Let queue be transport’s [[OutgoingDatagramsQueue]].
-
Let duration be transport’s [[OutgoingDatagramsExpirationDuration]].
-
If duration is null, then set duration to an implementation-defined value.
-
While queue is not empty:
-
Let bytes, timestamp and promise be queue’s first element.
-
If more than duration milliseconds have passed since timestamp, then:
-
Resolve promise with undefined.
-
Remove the first element from queue.
-
-
Otherwise, break this loop.
-
-
While queue is not empty:
-
Let bytes, timestamp and promise be queue’s first element.
-
If it is not possible to send bytes to the network immediately, then break this loop.
-
Send bytes to the network. [WEB-TRANSPORT-HTTP3]
-
Resolve promise with undefined.
-
Remove the first element from queue.
-
The user agent SHOULD run sendDatagrams for any WebTransport
object whose [[State]] is "connected"
as soon as reasonably possible whenever the algorithm can make
progress.
7. DatagramDuplexStream
Interface
A DatagramDuplexStream
is a generic duplex stream.
[Exposed =(Window ,Worker )]interface DatagramDuplexStream {readonly attribute ReadableStream readable ;readonly attribute WritableStream writable ; };
To create a DatagramDuplexStream
given a readable, and
a writable,
perform the following steps.
-
Let stream be a new
DatagramDuplexStream
. -
Initialize stream.
[[readable]]
to readable. -
Initialize stream.
[[writable]]
to writable. -
Return stream.
7.1. Attributes
readable
, of type ReadableStream, readonly-
The getter steps are:
-
Return this.
[[readable]]
.
-
writable
, of type WritableStream, readonly-
The getter steps are:
-
Return this.
[[writable]]
.
-
8. WebTransport
Interface
WebTransport
provides an API to the HTTP/3 transport functionality
defined in [WEB-TRANSPORT-HTTP3]. It implements all of the transport
mixins (UnidirectionalStreamsTransport
, BidirectionalStreamsTransport
, DatagramTransport
), as well as stats and transport state information.
[Exposed =(Window ,Worker )]interface {
WebTransport (
constructor USVString ,
url optional WebTransportOptions = {});
options Promise <WebTransportStats >getStats ();readonly attribute WebTransportState state ;readonly attribute Promise <undefined >ready ;readonly attribute Promise <WebTransportCloseInfo >closed ;undefined close (optional WebTransportCloseInfo = {});
closeInfo attribute EventHandler onstatechange ; };WebTransport includes UnidirectionalStreamsTransport ;WebTransport includes BidirectionalStreamsTransport ;WebTransport includes DatagramTransport ;
8.1. Internal slots
A WebTransport
object has the following internal slots.
Internal Slot | Description (non-normative) |
---|---|
[[OutgoingStreams]] | A sequence of SendStream objects.
|
[[ReceivedStreams]] | A ReadableStream consisting of ReceiveStream objects.
|
[[ReceivedBidirectionalStreams]] | A ReadableStream consisting of BidirectionalStream objects.
|
[[State]] | A WebTransportState .
|
[[Ready]] | A promise fulfilled when the associated WebTransport object is
ready to use, or rejected if it failed to connect.
|
[[Closed]] | A promise fulfilled when the associated WebTransport object is
closed gracefully, or rejected when it is closed abruptly or failed on initialization.
|
[[Datagrams]] | A DatagramDuplexStream .
|
[[OutgoingDatagramsQueue]] | A queue of tuples of an outgoing datagram, a timestamp and a promise which is resolved when the datagram is sent or discarded. |
[[OutgoingDatagramsHighWaterMark]] | An integer representing the high water mark of the outgoing datagrams. |
[[OutgoingDatagramsExpirationDuration]] | A double value representing the expiration duration for outgoing
datagrams (in seconds), or null.
|
[[Connection]] | A connection for this WebTransport object, or null.
|
8.2. Constructor
When the WebTransport()
constructor is invoked, the user
agent MUST run the following steps:
-
Let parsedURL be the URL record resulting from parsing
url
. -
If parsedURL is a failure, throw a
SyntaxError
exception. -
If parsedURL scheme is not
https
, throw aSyntaxError
exception. -
If parsedURL fragment is not null, throw a
SyntaxError
exception. -
Let allowPooling be
options
'sallowPooling
if it exists, and false otherwise. -
Let dedicated be the negation of allowPooling.
-
Let serverCertificateFingerprints be
options
'sserverCertificateFingerprints
if it exists, and null otherwise. -
If dedicated is false and serverCertificateFingerprints is non-null, then throw a
TypeError
. -
Let transport be a newly constructed
WebTransport
object, with:- [[OutgoingStreams]]
-
empty
- [[ReceivedStreams]]
-
a new
ReadableStream
- [[ReceivedBidirectionalStreams]]
-
a new
ReadableStream
- [[State]]
-
"connecting"
- [[Ready]]
-
a new promise
- [[Closed]]
-
a new promise
- [[Datagrams]]
-
null
This slot cannot be set to null, but this case is fine because we set a value in the subsequent steps.
- [[OutgoingDatagramsQueue]]
-
an empty queue
- [[OutgoingDatagramsHighWaterMark]]
-
an implementation-defined integer
This implementation-defined value should be tuned to ensure decent throughput, without jeopardizing the timeliness of transmitted data.
- [[OutgoingDatagramsExpirationDuration]]
-
null
- [[Connection]]
-
null
-
Let outgoingDatagrams be the result of creating a
WritableStream
, its writeAlgorithm set to writeDatagrams given transport as a parameter. -
Let incomingDatagrams be the result of creating a
ReadableStream
.transport.[[IncomingDatagrams]]
is provided datagrams using the readDatagrams algorithm given transport as a parameter. -
Set transport’s [[Datagrams]] to the result of creating a
DatagramDuplexStream
, its readable set to incomingDatagrams and its writable set to outgoingDatagrams. -
Let promise be the result of initializing WebTransport over HTTP, with transport, parsedURL and dedicated.
-
Upon fulfillment of promise, run these steps:
-
Upon rejection of promise with error, run these steps:
-
Set transport’s [[State]] to
"failed"
. -
Reject transport’s [[Ready]] with error.
-
Reject transport’s [[Closed]] with error.
-
-
Return transport.
WebTransport
object transport, a URL record url, and a boolean dedicated, run these steps.
-
Let promise be a new promise.
-
Let networkPartitionKey be the result of determining the network partition key with transport’s relevant settings object.
-
Return promise and run the following steps in parallel.
-
Let connection be the result of obtaining a connection with networkPartitionKey, url’s origin, false, http3Only set to true, and dedicated set to dedicated.
-
If connection is failure, then reject promise with a
TypeError
and abort these steps. -
Set transport’s [[Connection]] to connection.
-
Wait for connection to receive the first SETTINGS frame, and let settings be a dictionary that represents the SETTINGS frame.
-
If settings doesn’t contain SETTINGS_ENABLE_WEBTRANPORT with a value of 1, or it doesn’t contain H3_DATAGRAM with a value of 1, then reject promise with a
TypeError
and abort these steps. -
Create a WebTransport session with connection, as described in [WEB-TRANSPORT-HTTP3].
-
If the previous step fails, then reject promise with a
TypeError
and abort these steps. -
Resolve promise with
undefined
.
8.3. Attributes
state
, of type WebTransportState, readonly-
The current state of the transport. On getting, it MUST return this's [[State]].
ready
, of type Promise<undefined>, readonlyclosed
, of type Promise<WebTransportCloseInfo>, readonly-
On getting, it MUST return this's [[Closed]].
This promise MUST be resolved when the transport is closed; an implementation SHOULD include error information in the
reason
anderrorCode
fields ofWebTransportCloseInfo
. onstatechange
, of type EventHandler-
This event handler, of event handler event type
statechange
, MUST be fired any time the [[State]] changes, unless the state changes due to callingclose
.
8.4. Methods
close()
-
Closes the WebTransport object. For a dedicated connection, this triggers an Immediate Close as described in [QUIC] section 10.2.
When close is called, the user agent MUST run the following steps:
-
Let transport be the WebTransport on which
close
is invoked. -
If transport’s [[State]] is
"closed"
or"failed"
, then abort these steps. -
Set transport’s [[State]] to
"closed"
. -
Let
closeInfo
be the first argument. -
If the connection is dedicated, start the Immediate Close procedure by sending an CONNECTION_CLOSE frame with its error code value set to the value of
errorCode
and its reason value set to the value ofreason
.
-
getStats()
-
Gathers stats for this
WebTransport
's HTTP/3 connection and reports the result asynchronously.When close is called, the user agent MUST run the following steps:
-
Let transport be the WebTransport on which
getStats
is invoked. -
Let p be a new promise.
-
If the URL scheme associated with transport is not
https
, reject p withNotSupportedError
and return p. -
Return p and continue the following steps in parallel.
-
Gather the stats from the underlying QUIC connection.
-
Once stats have been gathered, resolve p with the
WebTransportStats
object, representing the gathered stats.
-
-
8.5. Configuration
dictionary WebTransportOptions {boolean allowPooling ;sequence <RTCDtlsFingerprint >serverCertificateFingerprints ; };
WebTransportOptions
is a dictionary of parameters
that determine how WebTransport connection is established and used.
allowPooling
, of type boolean-
When set to true, the WebTransport connection can be pooled, that is, the network connection for the WebTransport session can be shared with other HTTP/3 sessions.
serverCertificateFingerprints
, of type sequence<RTCDtlsFingerprint>-
This option is only supported for transports using dedicated connections. For transport protocols that do not support this feature, having this field non-empty SHALL result in a
NotSupportedError
exception being thrown.If supported and non-empty, the user agent SHALL deem a server certificate trusted if and only if it can successfully verify a certificate fingerprint against
serverCertificateFingerprints
and satisfies custom certificate requirements. The user agent SHALL ignore any fingerprint that uses an unknownalgorithm
or has a malformedvalue
. If empty, the user agent SHALL use certificate verification procedures it would use for normal fetch operations.This cannot be used with
allowPooling
.
-
Let fingerprints be the input array of fingerprints.
-
Let referenceFingerprint be the computed fingerprint of the input certificate.
-
For every fingerprint fingerprint in fingerprints:
-
Return false.
The custom certificate requirements are as follows: the certificate MUST be an X.509v3 certificate as defined in [RFC5280], the current time MUST be within the validity period of the certificate as defined in Section 4.1.2.5 of [RFC5280] and the total length of the validity period MUST NOT exceed two weeks.
Reconsider the time period above. We want it to be sufficiently large that applications using this for ephemeral certificates can do so without having to fight the clock skew, but small enough to discourage long-term use without key rotation.
8.6. WebTransportState
Enum
WebTransportState
indicates the state of the transport.
enum WebTransportState {"connecting" ,"connected" ,"closed" ,"failed" };
"connecting"
-
The transport is in the process of negotiating a secure connection. Once a secure connection is negotiated, incoming data can flow through.
"connected"
-
The transport has completed negotiation of a secure connection. Outgoing data and media can now flow through.
"closed"
-
The transport has been closed intentionally via a call to
close()
or receipt of a closing message from the remote side. When theWebTransport
's [[State]] transitions toclosed
the user agent MUST run the following steps:-
Let transport be the
WebTransport
. -
Close the
ReadableStream
in transport’s [[ReceivedStreams]]. -
Close the
ReadableStream
in transport’s [[ReceivedBidirectionalStreams]]. -
For each
SendStream
in transport’s [[OutgoingStreams]] run the following:-
Let stream be the
SendStream
. -
Remove the stream from the transport’s [[OutgoingStreams]].
-
-
"failed"
-
The transport has been closed as the result of an error (such as receipt of an error alert). When the WebTransport’s [[State]] transitions to
failed
the user agent MUST run the following steps:-
Close the
ReadableStream
in transport’s [[ReceivedStreams]]. -
Close the
ReadableStream
in transport’s [[ReceivedBidirectionalStreams]]. -
For each
SendStream
in transport’s [[OutgoingStreams]] run the following:-
Remove the stream from the transport’s [[OutgoingStreams]].
-
-
8.7. WebTransportCloseInfo
Dictionary
The WebTransportCloseInfo
dictionary includes information
relating to the error code for closing a WebTransport
. This
information is used to set the error code and reason for a CONNECTION_CLOSE
frame.
dictionary WebTransportCloseInfo {unsigned long long errorCode = 0;DOMString reason = ""; };
The dictionary SHALL have the following attributes:
errorCode
, of type unsigned long long, defaulting to0
-
The error code communicated to the peer.
reason
, of type DOMString, defaulting to""
-
The reason for closing the
WebTransport
.
8.8. WebTransportStats
Dictionary
The WebTransportStats
dictionary includes information
on HTTP/3 connection stats.
Now that quic-transport has been removed, this section needs to be revised. Some of those are safe to expose for HTTP/2 and HTTP/3 connections (like min-RTT), while most would either result in information disclosure or are impossible to define for pooled connections.
dictionary WebTransportStats {DOMHighResTimeStamp timestamp ;unsigned long long bytesSent ;unsigned long long packetsSent ;unsigned long numOutgoingStreamsCreated ;unsigned long numIncomingStreamsCreated ;unsigned long long bytesReceived ;unsigned long long packetsReceived ;DOMHighResTimeStamp minRtt ;unsigned long numReceivedDatagramsDropped ; };
The dictionary SHALL have the following attributes:
timestamp
, of type DOMHighResTimeStamp-
The
timestamp
for when the stats are gathered, relative to the UNIX epoch (Jan 1, 1970, UTC). bytesSent
, of type unsigned long long-
The number of bytes sent on the QUIC connection, including retransmissions. Does not include UDP or any other outer framing.
packetsSent
, of type unsigned long long-
The number of packets sent on the QUIC connection, including retransmissions.
numOutgoingStreamsCreated
, of type unsigned long-
The number of outgoing QUIC streams created on the QUIC connection.
numIncomingStreamsCreated
, of type unsigned long-
The number of incoming QUIC streams created on the QUIC connection.
bytesReceived
, of type unsigned long long-
The number of total bytes received on the QUIC connection, including duplicate data for streams. Does not include UDP or any other outer framing.
packetsReceived
, of type unsigned long long-
The number of total packets received on the QUIC connection, including packets that were not processable.
minRtt
, of type DOMHighResTimeStamp-
The minimum RTT observed on the entire connection.
numReceivedDatagramsDropped
, of type unsigned long-
The number of datagrams that were dropped, due to too many datagrams buffered between calls to
datagrams
'readable
.
9. Interface SendStream
A SendStream
is a WritableStream
of Uint8Array
that can be written to, to transmit data to the remote host.
[Exposed =(Window ,Worker ) ]interface SendStream :WritableStream /* of Uint8Array */ {readonly attribute Promise <StreamAbortInfo >writingAborted ;undefined abortWriting (optional StreamAbortInfo = {}); };
abortInfo
9.1. Overview
The SendStream
will be initialized by running the WritableStream
initialization steps.
9.2. Attributes
writingAborted
, of type Promise<StreamAbortInfo>, readonly-
The
writingAborted
attribute represents a promise that is fulfilled when the a message from the remote side aborting the stream is received. For QUIC, that message is a STOP_SENDING frame. When the stream receives this mesage, the user agent MUST run the following:-
Let stream be the
SendStream
object. -
Let transport be the
WebTransport
, which the stream was created from. -
Remove the stream from the transport’s [[OutgoingStreams]].
-
Resolve the promise with the resulting
StreamAbortInfo
with theerrorCode
set to the value from the aborting message from the remote side.
-
9.3. Methods
abortWriting()
-
A hard shutdown of the
SendStream
. It may be called regardless of whether theSendStream
was created by the local or remote peer. When theabortWriting
method is called, the user agent MUST run the following steps:-
Let stream be the
SendStream
object which is about to abort writing. -
Let transport be the
WebTransport
, which the stream was created from. -
Remove the stream from the transport’s [[OutgoingStreams]].
-
Let abortInfo be the first argument.
-
Start the closing procedure by sending a RST_STREAM frame with its error code set to the value of |abortInfo.errorCode|.
-
9.4. StreamAbortInfo
Dictionary
The StreamAbortInfo
dictionary includes information
relating to the error code for aborting an incoming or outgoing stream (
in either a RST_STREAM frame or a STOP_SENDING frame).
dictionary StreamAbortInfo { [EnforceRange ]octet errorCode = 0; };
The dictionary SHALL have the following fields:
errorCode
, of type octet, defaulting to0
-
The error code. The default value of 0 means "CLOSING."
10. Interface ReceiveStream
A ReceiveStream
is a ReadableStream
of Uint8Array
that can be read from, to consume data received from the remote host.
[Exposed =(Window ,Worker ) ]interface ReceiveStream :ReadableStream /* of Uint8Array */ {readonly attribute Promise <StreamAbortInfo >readingAborted ;undefined abortReading (optional StreamAbortInfo = {}); };
abortInfo
10.1. Overview
The ReceiveStream
will be initialized by running the ReadableStream
initialization steps.
10.2. Attributes
readingAborted
, of type Promise<StreamAbortInfo>, readonly-
The
readingAborted
attribute represents a promise that is fulfilled when the a message from the remote side aborting the stream is received. For QUIC, that message is a RST_STREAM frame. When the stream receives this mesage, the user agent MUST run the following:-
Let stream be the
ReceiveStream
object for which the abort message was received. -
Let transport be the
WebTransport
, which the stream was created from. -
Resolve the promise with the resulting
StreamAbortInfo
with theerrorCode
set to the value from the aborting message from the remote side.
-
10.3. Methods
abortReading()
-
A hard shutdown of the
ReceiveStream
. It may be called regardless of whether theReceiveStream
was created by the local or remote peer. When theabortWriting
method is called, the user agent MUST run the following steps:-
Let stream be the
ReceiveStream
object which is about to abort reading. -
Let transport be the
WebTransport
, which the stream was created from. -
Let abortInfo be the first argument.
-
Start the closing procedure by sending a message to the remote side indicating that the stream has been aborted (using a STOP_SENDING frame) with its error code set to the value of |abortInfo.errorCode|.
-
11. Interface BidirectionalStream
[Exposed =(Window ,Worker ) ]interface {
BidirectionalStream readonly attribute ReceiveStream ;
readable readonly attribute SendStream ; };
writable
The BidirectionalStream
will initialize with the following:
-
Let duplexStream be the
BidirectionalStream
. -
Let duplexStream have a
[[Readable]]
internal slot initialized to a newReceiveStream
. -
Let duplexStream have a
[[Writable]]
internal slot initialized to a newSendStream
.
12. Protocol Mappings
This section is non-normative.
This section describes the [QUIC] protocol behavior of methods defined in this specification, utilizing [WEB-TRANSPORT-HTTP3].
Method | QUIC Protocol Action |
---|---|
abortReading
| send STOP_SENDING with code |
abortWriting
| send RESET_STREAM with code |
writable .abort()
| send RESET_STREAM |
writable .close()
| send STREAM_FINAL |
writable .getWriter().write()
| send STREAM |
writable .getWriter().close()
| send STREAM_FINAL |
writable .getWriter().abort()
| send RESET_STREAM |
readable .cancel()
| send STOP_SENDING |
readable .getReader().read()
| receive STREAM or STREAM_FINAL |
readable .getReader().cancel()
| send STOP_SENDING |
13. Privacy and Security Considerations
This section is non-normative; it specifies no new behaviour, but instead summarizes information already present in other parts of the specification.
13.1. Confidentiality of Communications
The fact that communication is taking place cannot be hidden from adversaries that can observe the network, so this has to be regarded as public information.
All of the transport protocols described in this document use either TLS [RFC8446] or a semantically equivalent protocol, thus providing all of the security properties of TLS, including confidentiality and integrity of the traffic. Http3Transport uses the same certificate verification mechanism as outbound HTTP requests, thus relying on the same public key infrastructure for authentication of the remote server. In WebTransport, certificate verification errors are fatal; no interstitial allowing bypassing certificate validation is available.
13.2. State Persistence
WebTransport by itself does not create any new unique identifiers or new ways to persistently store state, nor does it automatically expose any of the existing persistent state to the server. For instance, none of the transports defined in this document automatically send cookies, support HTTP authentication or caching invalidation mechanisms. Since they use TLS, they may support TLS session tickets, which could be used by the server (though not by passive network observers) to correlate different connections from the same client. This is not specific to WebTransport by itself, but rather an inherent property of all TLS-based protocols; thus, this is out-of-scope for this specification.
13.3. Protocol Security
WebTransport imposes a set of requirements as described in [WEB-TRANSPORT-OVERVIEW], including:
-
Ensuring that the remote server is aware that the connection in question originates from a Web application; this is required to prevent cross-protocol attacks. [WEB-TRANSPORT-HTTP3] uses ALPN [RFC7301] for this purpose.
-
Allowing the server to filter connections based on the origin of the resource originating the transport session.
Protocol security considerations related are described in the Security Considerations sections of [WEB-TRANSPORT-HTTP3].
Networking APIs can be commonly used to scan the local network for available hosts, and thus be used for fingerprinting and other forms of attacks. WebTransport follows the WebSocket approach to this problem: the specific connection error is not returned until an endpoint is verified to be a WebTransport endpoint; thus, the Web application cannot distinguish between a non-existing endpoint and the endpoint that is not willing to accept connections from the Web.
14. Examples
14.1. Sending a buffer of datagrams
This section is non-normative.
Sending a buffer of datagrams can be achieved by using the datagrams
' writable
attribute. In the
following example datagrams are only sent if the DatagramTransport
is ready to send.
async function sendDatagrams( url, datagrams) { const wt= new WebTransport( url); const writer= wt. datagrams. writable. getWriter(); for ( const datagramof datagrams) { await writer. ready; writer. write( datagram). catch (() => {}); } }
14.2. Sending datagrams at a fixed rate
This section is non-normative.
Sending datagrams at a fixed rate regardless if the transport is ready to send
can be achieved by simply using datagrams
' writable
and not using the ready
attribute. More complex
scenarios can utilize the ready
attribute.
// Sends datagrams every 100 ms. async function sendFixedRate( url, createDatagram, ms= 100 ) { const wt= new WebTransport( url); await wt. ready; const writer= wt. datagrams. writable. getWriter(); const datagram= createDatagram(); setInterval(() => writer. write( datagram). catch (() => {}), ms); }
14.3. Receiving datagrams
This section is non-normative.
Datagrams can be received by reading from the
transport.datagrams
.datagrams
.readable
attribute. Null values may indicate that packets are not being processed quickly
enough.
async function receiveDatagrams( url) { const wt= new WebTransport( url); for await ( const datagramof wt. datagrams. readable) { // Process the datagram } }
14.4. Sending a stream
This section is non-normative.
Sending data as a one-way stream can be achieved by using the createUnidirectionalStream
function and the
resulting stream’s writer.
async function sendData( url, data) { const wt= new WebTransport( url); const writable= await wt. createUnidirectionalStream(); const writer= writable. getWriter(); await writer. write( data); await writer. close(); }
Encoding can also be done through pipes from a ReadableStream
, for example using TextEncoderStream
.
async function sendText( url, readableStreamOfTextData) { const wt= new WebTransport( url); const writable= await wt. createUnidirectionalStream(); await readableStreamOfTextData. pipeThrough( new TextEncoderStream( "utf-8" )) . pipeTo( writable); }
14.5. Receiving incoming streams
This section is non-normative.
Reading incoming streams can be achieved by iterating over the incomingUnidirectionalStreams
attribute,
and then consuming each ReceiveStream
by iterating over its chunks.
async function receiveData( url, processTheData) { const wt= new WebTransport( url); for await ( const readableof wt. incomingUnidirectionalStreams) { // consume streams individually, reporting per-stream errors (( async () => { try { for await ( const chunkof readable. getReader()) { processTheData( chunk); } } catch ( e) { console. error( e); } })()); } }
Decoding can also be done through pipes to new WritableStreams, for example using TextDecoderStream
. This example assumes text output should not be
interleaved, and therefore only reads one stream at a time.
async function receiveText( url, createWritableStreamForTextData) { const wt= new WebTransport( url); for await ( const readableof wt. incomingUnidirectionalStreams) { // consume sequentially to not interleave output, reporting per-stream errors try { await readable. pipeThrough( new TextDecoderStream( "utf-8" )) . pipeTo( createWritableStreamForTextData()); } catch ( e) { console. error( e); } } }
14.6. Complete example
This section is non-normative.
This example illustrates use of the closed and ready promises, opening of uni-directional and bi-directional streams by either the client or the server, and sending and receiving datagrams.
// Adds an entry to the event log on the page, optionally applying a specified // CSS class. let wt, streamNumber, datagramWriter; connect. onclick= async () => { try { const url= document. getElementById( 'url' ). value; wt= new WebTransport( url); addToEventLog( 'Initiating connection...' ); await wt. ready; addToEventLog( 'Connection ready.' ); wt. closed. then(() => addToEventLog( 'Connection closed normally.' )) . catch (() => addToEventLog( 'Connection closed abruptly.' , 'error' )); streamNumber= 1 ; datagramWriter= wt. datagrams. writable. getWriter(); readDatagrams(); acceptUnidirectionalStreams(); document. forms. sending. elements. send. disabled= false ; document. getElementById( 'connect' ). disabled= true ; } catch ( e) { addToEventLog( `Connection failed. ${ e} ` , 'error' ); } } sendData. onclick= async () => { const form= document. forms. sending. elements; const rawData= sending. data. value; const data= new TextEncoder( 'utf-8' ). encode( rawData); try { switch ( form. sendtype. value) { case 'datagram' : { await datagramWriter. write( data); addToEventLog( `Sent datagram: ${ rawData} ` ); break ; } case 'unidi' : { const writable= await wt. createUnidirectionalStream(); const writer= writable. getWriter(); await writer. write( data); await writer. close(); addToEventLog( `Sent a unidirectional stream with data: ${ rawData} ` ); break ; } case 'bidi' : { const duplexStream= await wt. createBidirectionalStream(); const n= streamNumber++ ; readFromIncomingStream( duplexStream. readable, n); const writer= duplexStream. writable. getWriter(); await writer. write( data); await writer. close(); addToEventLog( `Sent bidirectional stream # ${ n} with data: ${ rawData} ` ); break ; } } } catch ( e) { addToEventLog( `Error while sending data: ${ e} ` , 'error' ); } } // Reads datagrams into the event log until EOF is reached. async function readDatagrams() { try { const decoder= new TextDecoderStream( 'utf-8' ); for await ( const dataof wt. datagrams. readable. pipeThrough( decoder)) { addToEventLog( `Datagram received: ${ data} ` ); } addToEventLog( 'Done reading datagrams!' ); } catch ( e) { addToEventLog( `Error while reading datagrams: ${ e} ` , 'error' ); } } async function acceptUnidirectionalStreams() { try { for await ( const readableof wt. incomingUnidirectionalStreams) { const number= streamNumber++ ; addToEventLog( `New incoming unidirectional stream # ${ number} ` ); readFromIncomingStream( readable, number); } addToEventLog( 'Done accepting unidirectional streams!' ); } catch ( e) { addToEventLog( `Error while accepting streams ${ e} ` , 'error' ); } } async function readFromIncomingStream( readable, number) { try { const decoder= new TextDecoderStream( 'utf-8' ); for await ( const chunkof readable. pipeThrough( decoder)) { addToEventLog( `Received data on stream # ${ number} : ${ chunk} ` ); } addToEventLog( `Stream # ${ number} closed` ); } catch ( e) { addToEventLog( `Error while reading from stream # ${ number"} : ${ e} ` , 'error' ); addToEventLog( ` ${ e. message} ` ); } } function addToEventLog( text, severity= 'info' ) { const log= document. getElementById( 'event-log' ); const previous= log. lastElementChild; const entry= document. createElement( 'li' ); entry. innerText= text; entry. className= `log- ${ severity} ` ; log. appendChild( entry); // If the previous entry in the log was visible, scroll to the new element. if ( previous&& previous. getBoundingClientRect(). top< log. getBoundingClientRect(). bottom) { entry. scrollIntoView(); } }
15. Acknowledgements
The editors wish to thank the Working Group chairs and Team Contact, Jan-Ivar Bruaroey, Will Law and Yves Lafon, for their support.The WebTransport
interface is based on the QuicTransport
interface
initially described in the W3C ORTC CG,
and has been adapted for use in this specification.