diff --git a/fetch.bs b/fetch.bs index fa6deae20..c5eb40d98 100755 --- a/fetch.bs +++ b/fetch.bs @@ -1651,6 +1651,162 @@ these steps: +

Trailer state

+ +

A trailer state is a struct used to represent +HTTP trailer fields +([[HTTP]], Section 6.5) +associated with a request or response. +It has: + +

+ +

A trailer state is shared by reference between a +request or response and its +clones. + +

+

To complete a trailer state given a trailer state +trailerState: + +

    +
  1. If trailerState's state is not + "pending", then return. + +

  2. Set trailerState's state to + "complete". + +

  3. For each observer of + trailerState's observers: + run observer. + +

  4. Empty trailerState's + observers. +

+
+ +
+

To error a trailer state given a trailer state +trailerState and a value reason: + +

    +
  1. If trailerState's state is not + "pending", then return. + +

  2. Set trailerState's state to + "errored". + +

  3. Set trailerState's error to + reason. + +

  4. For each observer of + trailerState's observers: + run observer. + +

  5. Empty trailerState's + observers. +

+
+ +
+

To process a trailers init given a trailer state +trailerState, a {{Promise}} trailersPromise, a +headers guard guard, and a header list +headerList: + +

    +
  1. +

    Upon fulfillment of trailersPromise with + init: + +

      +
    1. Let trailerHeaders be a new {{Headers}} object whose + guard is guard. + +

    2. Fill trailerHeaders with init. + +

    3. Let declaredNames be the result of + getting, decoding, and splitting + `Trailer` from headerList. + +

    4. If declaredNames is null, then set declaredNames + to « ». + +

    5. Let lowercasedDeclaredNames be a new list. + +

    6. For each name of declaredNames, + append name, byte-lowercased, to + lowercasedDeclaredNames. + +

    7. Let namesToRemove be a new list. + +

    8. +

      For each header of trailerHeaders's + header list: + +

        +
      1. If lowercasedDeclaredNames does not contain + header's name, then + append header's name to + namesToRemove. +

      + +
    9. For each name of namesToRemove, + delete name from + trailerHeaders's header list. + +

    10. Set trailerState's header list + to trailerHeaders's header list. + +

    11. Complete a trailer state given trailerState. +

    + +
  2. +

    Upon rejection of trailersPromise with + reason: + +

      +
    1. Error a trailer state given trailerState and + reason. +

    +
+ +

The guard applied to trailerHeaders +ensures that forbidden request-header names (for requests) or +forbidden response-header names (for responses) are excluded via the +standard {{Headers}} filtering before the `Trailer` declaration +filter is applied. Trailer field names present in the resolved {{HeadersInit}} +but not pre-declared in the `Trailer` header are silently dropped. +

+ +
+

To ignore a trailers init given a {{Promise}} +trailersPromise: + +

    +
  1. Mark as handled trailersPromise. +

+ +

This algorithm is provided for use by specifications that extend +Fetch and need to discard a user-provided {{RequestInit/trailers}} or +{{ResponseInit/trailers}} promise without triggering unhandled rejection +warnings. Within this specification, process a trailers init handles +promise rejection directly. +

+ +

Requests

This section documents how requests work in detail. To get started, see @@ -2305,6 +2461,17 @@ Unless stated otherwise, it is unset.

A request has an associated done flag. Unless stated otherwise, it is unset. +

A request has an associated +trailer state +(a trailer state). Unless stated otherwise it is a new trailer state +whose state is "complete". + +

A request's trailer state defaults to +"complete" with an empty header list, +representing a request with no trailer fields to send. When the +{{RequestInit/trailers}} option is provided, the trailer state +starts as "pending" and is completed when the promise resolves. +

A request has an associated timing allow failed flag. Unless stated otherwise, it is unset. @@ -2579,6 +2746,18 @@ message as HTTP/2 does not support them.

The source and length concepts of a network's response's body are always null. +

A response has an associated +trailer state +(a trailer state). Unless stated otherwise it is a new trailer state. + +

A response's +trailer header list +is its trailer state's header list. + +

The trailer state is shared by reference between a +response and its clones (see clone), ensuring trailer fields +received after cloning are visible to all copies. +

A response has an associated cache state (the empty string, "local", or "validated"). Unless stated otherwise, it is the empty @@ -2692,17 +2871,23 @@ of defining the concrete types of filtered responses.)

A basic filtered response is a filtered response whose -type is "basic" and +type is "basic", header list excludes any headers in internal response's header list whose name is a +forbidden response-header name, and +trailer header list excludes any +headers in +internal response's +trailer header list whose +name is a forbidden response-header name.

A CORS filtered response is a filtered response whose -type is "cors" and +type is "cors", header list excludes any headers in internal response's @@ -2710,6 +2895,14 @@ of defining the concrete types of filtered responses.) name is not a CORS-safelisted response-header name, given internal response's +CORS-exposed header-name list, and +trailer header list excludes any +headers in +internal response's +trailer header list whose +name is not a +CORS-safelisted response-header name, given +internal response's CORS-exposed header-name list.

An opaque filtered response is a @@ -2719,6 +2912,7 @@ of defining the concrete types of filtered responses.) status is 0, status message is the empty byte sequence, header list is « », +trailer header list is « », body is null, and body info is a new response body info. @@ -2729,6 +2923,7 @@ is a filtered response whose status is 0, status message is the empty byte sequence, header list is « », +trailer header list is « », body is null, and body info is a new response body info. @@ -2781,7 +2976,10 @@ console.log((await fetch("/surprise-me", { redirect: "manual" })).type); // "opa internal response.

  • Let newResponse be a copy of response, except for its - body. + body and trailer state. + +

  • Set newResponse's trailer state to + response's trailer state.

  • If response's body is non-null, then set newResponse's body to the result of cloning @@ -5310,6 +5508,9 @@ steps:

  • Set fetchParams's request's done flag. +

  • Complete a trailer state given response's + trailer state. +

  • If fetchParams's process response end-of-body is non-null, then run fetchParams's process response end-of-body given response. @@ -5360,13 +5561,34 @@ steps: flushAlgorithm set to processResponseEndOfBody. -

  • Set internalResponse's body's stream to the - result of internalResponse's body's stream - piped through transformStream. +

  • Let readable be transformStream's + readable. + +

  • Let pipePromise be the result of + piping internalResponse's + body's stream to + transformStream's writable. + +

  • Set internalResponse's body's + stream to readable. + +

  • +

    Upon rejection of pipePromise with + reason: + +

      +
    1. Error a trailer state given response's + trailer state and reason. +

    -

    This {{TransformStream}} is needed for the purpose of receiving a notification when - the stream reaches its end, and is otherwise an identity transform stream. +

    The {{TransformStream}} receives a notification when the body + stream reaches its end (via the flush algorithm) or when it errors (via the + pipe promise rejection). On successful completion, + processResponseEndOfBody completes + the trailer state. On error, the trailer state is + errored, which rejects any pending + {{Response/trailers}} promises.

  • If fetchParams's process response consume body is @@ -6760,6 +6982,44 @@ optional boolean forceNewConnection (default false), run these steps:

  • Break. + +

  • +

    If request's trailer state's + state is "pending", the + implementation MAY, after the request body has been fully transmitted, + observe request's trailer state and send + trailer fields from its header list once it + is completed. Implementations that + do not support sending trailer fields MAY ignore the trailer state + entirely. + +

    Sending trailer fields is entirely optional. + Implementations that do not support sending trailer fields are fully + compliant with this specification. The {{RequestInit/trailers}} option + exists to provide a standardized interface for implementations that do + support sending trailer fields. Promise rejection is handled by the + process a trailers init algorithm (which is called during + request construction), so implementations that ignore the trailer state + do not cause unhandled promise rejections. + Whether or not to actually forward trailer fields is an implementation + decision, consistent with how HTTP intermediaries may forward, discard, + or store trailer fields + ([[HTTP]], Section 6.5). + +

  • +

    If the HTTP response includes trailer fields + ([[HTTP]], Section 6.5), + then, after the response body has been fully transmitted, set + response's trailer header list to a + header list containing each received trailer field as a + header. + +

    Trailer fields are received after the response body. The HTTP + layer is responsible for omitting trailer fields prohibited by HTTP semantics + (e.g., fields used in transfer codings, content framing, or routing). The + trailer header list is populated before + completing the trailer state + in the fetch response handover steps.

    The exact layering between Fetch and HTTP still needs to be sorted through and @@ -8616,6 +8876,7 @@ interface Request { readonly attribute boolean isHistoryNavigation; readonly attribute AbortSignal signal; readonly attribute RequestDuplex duplex; + readonly attribute Promise<Headers> trailers; [NewObject] Request clone(); }; @@ -8636,6 +8897,7 @@ dictionary RequestInit { AbortSignal? signal; RequestDuplex duplex; RequestPriority priority; + Promise<HeadersInit> trailers; any window; // can only be set to null }; @@ -8663,6 +8925,9 @@ used or observed from JavaScript.

    A {{Request}} object has an associated signal (null or an {{AbortSignal}} object), initially null. +

    A {{Request}} object has an associated +trailers promise (null or a {{Promise}}), initially null. +

    A {{Request}} object's body is its request's body. @@ -8742,6 +9007,12 @@ object), initially null.

    {{RequestInit/priority}}
    A string to set request's priority. + +
    {{RequestInit/trailers}} +
    A promise that resolves to {{HeadersInit}} providing trailer fields to send after the + request body. Trailer field names must be pre-declared in the `Trailer` header; + undeclared trailer fields are silently dropped. Implementations are not required to send + trailer fields and may ignore this option entirely.
    request . method @@ -8815,6 +9086,23 @@ object), initially null. See issue #1254 for defining "full". +
    request . trailers +
    +

    Returns a promise that resolves to immutable {{Headers}} containing the request's + trailer fields. + +

    If the {{RequestInit/trailers}} option was provided, the promise resolves when + the provided promise fulfills, with trailer fields filtered by the `Trailer` + header declaration. If no {{RequestInit/trailers}} option was provided, the + promise resolves immediately to empty immutable {{Headers}}. + +

    The resolution timing of this promise reflects the timing of the + user-provided {{RequestInit/trailers}} promise, not the timing of body + transmission. This may indirectly reveal when payload processing completes if + the caller ties the promise to body consumption (e.g., computing a digest over + the body stream), but this timing is controlled by the caller, not the + implementation. +

    request . clone()

    Returns a clone of request. @@ -9218,6 +9506,21 @@ constructor steps are:

  • Set this's request's body to finalBody. + +

  • +

    If init["{{RequestInit/trailers}}"] exists, then: + +

      +
    1. Let trailerState be a new trailer state. + +

    2. Set this's request's trailer state + to trailerState. + +

    3. Process a trailers init given trailerState, + init["{{RequestInit/trailers}}"], + this's headers's guard, and + this's request's header list. +

    @@ -9287,6 +9590,77 @@ set; otherwise false.

    The duplex getter steps are to return "half". +

    +

    The trailers getter steps are: + +

      +
    1. If this's trailers promise is null, then: + +

        +
      1. Let trailerState be this's request's + trailer state. + +

      2. Let promise be a new promise. + +

      3. +

        If trailerState's state is + "complete", then: + +

          +
        1. Let headers be a new {{Headers}} object whose + header list is trailerState's + header list and whose guard is + "immutable". + +

        2. Resolve promise with headers. +

        + +
      4. +

        Otherwise, if trailerState's state is + "errored", then reject promise with + trailerState's error. + +

      5. +

        Otherwise (trailerState's state is + "pending"): + +

          +
        1. +

          Append the following steps to trailerState's + observers: + +

            +
          1. If trailerState's state is + "complete", then: + +

              +
            1. Let headers be a new {{Headers}} object whose + header list is trailerState's + header list and whose guard is + "immutable". + +

            2. Resolve promise with headers. +

            + +
          2. Otherwise, reject promise with + trailerState's error. +

          +
        + +
      6. Set this's trailers promise to promise. +

      + +
    2. Return this's trailers promise. +

    +
    + +

    When no {{RequestInit/trailers}} option is provided, the +trailer state is immediately "complete" with an +empty header list, so the {{Request/trailers}} promise +resolves to empty immutable {{Headers}}. When a {{RequestInit/trailers}} option is +provided, the promise resolves to the filtered trailer fields once the provided +promise fulfills. +


    @@ -9331,6 +9705,7 @@ interface Response { readonly attribute boolean ok; readonly attribute ByteString statusText; [SameObject] readonly attribute Headers headers; + readonly attribute Promise<Headers> trailers; [NewObject] Response clone(); }; @@ -9340,6 +9715,7 @@ dictionary ResponseInit { unsigned short status = 200; ByteString statusText = ""; HeadersInit headers; + Promise<HeadersInit> trailers; }; enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredirect" }; @@ -9352,6 +9728,9 @@ enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredire

    A {{Response}} object also has an associated headers (null or a {{Headers}} object), initially null. +

    A {{Response}} object has an associated +trailers promise (null or a {{Promise}}), initially null. +

    A {{Response}} object's body is its response's body. @@ -9394,8 +9773,26 @@ enum ResponseType { "basic", "cors", "default", "error", "opaque", "opaqueredire

    response . headers

    Returns response's headers as {{Headers}}. +

    response . trailers +
    +

    Returns a promise that resolves to {{Headers}} containing the response's trailer fields. + +

    For responses obtained from {{WindowOrWorkerGlobalScope/fetch()}}, the promise resolves after the + response body has been fully received. For synthetically constructed responses (via + {{Response/error()}}, {{Response/redirect()}}, or + {{Response/json()}} and the {{Response/Response()}} constructor without a + {{ResponseInit/trailers}} option), the promise resolves immediately to empty immutable + {{Headers}}. + +

    If the {{ResponseInit/trailers}} option was provided (to the {{Response/Response()}} + constructor or {{Response/json()}}), the promise resolves when the provided promise fulfills, + with trailer fields filtered by the `Trailer` header declaration. + +

    If the response body stream errors, the promise is rejected. +

    response . clone() -

    Returns a clone of response. +

    Returns a clone of response. The clone shares the same + trailer state, so both will receive the same trailer fields.


    @@ -9486,12 +9883,34 @@ constructor steps are:
  • Perform initialize a response given this, init, and bodyWithType. + +

  • +

    If init["{{ResponseInit/trailers}}"] exists, then + process a trailers init given this's response's + trailer state, + init["{{ResponseInit/trailers}}"], + this's headers's guard, and + this's response's header list. + +

  • +

    Otherwise, complete a trailer state given this's + response's trailer state.

  • -

    The static error() method steps are to return the -result of creating a {{Response}} object, given a new network error, -"immutable", and the current realm. +

    +

    The static error() method steps are: + +

      +
    1. Let responseObject be the result of creating a {{Response}} + object, given a new network error, "immutable", and the current realm. + +

    2. Complete a trailer state given responseObject's + response's trailer state. + +

    3. Return responseObject. +

    +

    The static @@ -9518,6 +9937,9 @@ are:

  • Append (`Location`, value) to responseObject's response's header list. +

  • Complete a trailer state given responseObject's + response's trailer state. +

  • Return responseObject. @@ -9540,6 +9962,18 @@ are:

  • Perform initialize a response given responseObject, init, and (body, "application/json"). +

  • +

    If init["{{ResponseInit/trailers}}"] exists, then + process a trailers init given responseObject's + response's trailer state, + init["{{ResponseInit/trailers}}"], + responseObject's headers's guard, and + responseObject's response's header list. + +

  • +

    Otherwise, complete a trailer state given responseObject's + response's trailer state. +

  • Return responseObject. @@ -9574,6 +10008,72 @@ otherwise false.

    The headers getter steps are to return this's headers. +

    +

    The trailers getter steps are: + +

      +
    1. If this's trailers promise is null, then: + +

        +
      1. Let response be this's response. + +

      2. Let trailerState be response's + trailer state. + +

      3. Let promise be a new promise. + +

      4. +

        If trailerState's state is + "complete", then: + +

          +
        1. Let headers be a new {{Headers}} object whose + header list is response's + trailer header list and whose guard is + "immutable". + +

        2. Resolve promise with headers. +

        + +
      5. +

        Otherwise, if trailerState's state is + "errored", then reject promise with + trailerState's error. + +

      6. +

        Otherwise (trailerState's state is + "pending"): + +

          +
        1. +

          Append the following steps to trailerState's + observers: + +

            +
          1. If trailerState's state is + "complete", then: + +

              +
            1. Let headers be a new {{Headers}} object whose + header list is response's + trailer header list and whose guard is + "immutable". + +

            2. Resolve promise with headers. +

            + +
          2. Otherwise, reject promise with + trailerState's error. +

          +
        + +
      7. Set this's trailers promise to promise. +

      + +
    2. Return this's trailers promise. +

    +
    +
    @@ -9741,6 +10241,9 @@ with a promise, request, responseObject, and an
  • If response's body is non-null and is readable, then error response's body with error. + +

  • Error a trailer state given response's + trailer state and error.