Draft: draft-ietf-quic-http

Commentary: In reply to I did a quick search for " stream error " and found a few that discusses about protocol violation . This PR changes those to " connection error . " 
 I wrote an almost identical PR I 'll now close , but I think we want to fix line 331 and 630 as I do in my PR ? On those lines , we did n't specify what type of error it was . 
 there are also some stream errors in QPACK you might want to address at the same time 
 Thank you for the comments . Addressed in the commits above . 
 How can this be justified ? This is a departure from RFC 7540 , which states in two places that this setting is only advisory . 
 An endpoint MUST NOT send a header list that exceeds the peer - advised limit . Yeah . My read of HTTP/3 is that it is already different from HTTP/2 this respect . It is no more clarifies as " advisory , " no there is a mention that a client can exceed the limit ( a text that exists in RFC 7540 ) . Regarding the ideal behavior , I prefer being strict here , because it would give us the most predictive behavior at a minimal cost ( it 's trivial to check the size , is n't it ? ) . Think it from an end - user perspective . Without the restriction , we might see one user - agent telling you that the request is too large , and the other processing the same request . That 'd be confusing . 
 The clients already differ in many , many other ways . The size check is trivial , but that 's not the problem . The problem is the risk of breaking existing web applications . The current HTTP/2 browser behavior is to advertise a limit , but to process much larger header lists . We do n't want to end up where " HTTP/3 broke my app ! " is a thing . 
 Since HTTP/3 is not a thing yet , in browsers , the advice for browsers could be to advertise a larger realistic limit . 
 H2 discusses  header list size in the same breath as header block sizes and individual header field sizes . The concepts are conflated ( perhaps for good reason ) . H3 changes some things . For instance there are no CONTINUATIONS and the header list can not span more than on HEADERS frame . Furthermore , QPACK header blocks do nt modify table state , so there are fewer dangers in refusing to process them ( or not ? ) There is more to this than meets the eye and I do nt think we can unpick the problem space as part of this change . Suggest topic is discussed on list or at Prague 104 . 
 I agree . Let 's separate the " all protocol violations = connection error " and the "  header list size is no longer advisory " issues . 
 SGTM . Reverted the changes regarding  header list size . And the issue opened as . 
 Thanks  , splitting that off makes sense . 
 The PR needs to address the following two as well ( pointed out by a PRIORITY frame on a request stream after other frames a CANCEL_PUSH frame on non - control streams 
 Resolved merge conflicts and rearranged the PRIORITY section to have a single list of impossible PRIORITY frames that provoke HTTPMALFORMEDFRAME . ( Of course , this breaks ; sorry , I suspect there 's better wording possible here , so feel free to bash on it . Went to fix the items mentioned , but they seem to already have been covered in original text , so no change needed . 
 The impact on is easily overcome . This change has been through review and has support , strongly in favour of shipping it . 
 Semantics of SETTINGSMAXHEADERLISTSIZE in unclear . We should either : state that it 's an advisory value that an endpoint is permitted to ignore . Otherwise , an endpoint written under the impression that it 's a value to be enforced might terminate the connection ( note : H3 allows a stream error to be promoted to a connection error ) thereby disrupting other HTTP requests inflight . state that an endpoint must not exceed the value advertised by the peer . spun off from . 
 My preference goes to being strict . Quoting from I also think that what points out in is an argument for making this an enforcement ( or drop the settings entirely ) . Unless we make it an enforcement , implementations would be tempted to ignore the setting for " maximum interoperability , " making the feature meaningless . 
 Thanks for creating the issue Do we have some deployment experience that can weigh in here . I 've heard anecdotal stories that nginx ignores the setting in HTTP/2 by default . And I 've found an interesting  crbug that also mentions HTTP/2 load balancers - * 
 We have experience from HTTP/2 , where it was clear that no intermediary was ever in a position to comply . The setting is hop - by - hop and header fields are end - to - end , so there is an inherent incompatibility . I do n't see any stack being able to comply with a limit that is too low for the current message . I 'd prefer to see it removed if it came to that . 
 I agree to removing it . 
 I , too , think that removing this setting is fine . 
 Does this mean constrained devices will not need a control stream ? 
 My own $ 0.02 ... I would much prefer to have this and treat it more strictly . In my experience , explicit limits are much easier to reason about than hidden limits . I 'd much rather fail a request early than send it to a peer only to have it fail with a limit that I know nothing about . 
 I agree why  . Any reasonable server is going to have a limit and will have to fail requests that violate it . It 's better to have this be explicit . 
 Agreed , explicit limits are good . Unexpected failures are not . In the case of proxies , it seems like the min of the received setting and local limit could be used ? But I guess the proxy mah not know all backends it may contact when it advertises a limit . I still think something is better than nothing , but I 'm not sure how to fully solve the proxy case . 
 If this limit is not advisory , there is a good chance that some web applications will break once the underlying server is upgraded to HTTP/3 . The reason is that HTTP/2 servers ignore limits sent by the clients and the browsers just take it .  ? Yes . But it works . 
 How about SHOULD enforce , but MAY be configured to ignore , or use a higher multiple for compat reasons . 
 An optional limit that 's rarely enforced and , perhaps worse , unpredictably enforced seems like a waste of a limit . Implementations already have limits on the size of individual headers , which trigger semantic layer errors . Why does the sum total need to trigger a syntax type of error ? Enforcing error makes this stricter than H2 , hurting interoperability between implementations that support H3 and H2 at the same time . Speculating , I can see this causing some strange issues in the wild , where clients    up to H3 , encounter a sporadic error and then purge H3 endpoints from their    cache . 
 Yes . The problem here is the difficulty of the sender of the headers block complying to the limit advertised by the peer . Therefore , " SHOULD enforce " does not work as a fix . FWIW , there are at least two issues regarding proxies . Downstream ( e.g. , origin server in case of a HTTP request ) having smaller limits . In such case , the proxy will read the request then send a LIMIT_EXCEEDED error without forwarding the request . A proxy that streams the headers block . Such a proxy can not tell the uncompressed size of the headers block until it receives the last byte of the HEADERS frame . But by the time that is observed , it would already have started sending the headers to the downstream . 
 In theory , this should advertise the limit beyond which a peer would n't have propagated the payload anyway ; it permits , but does n't require , the entity that 's about to send the payload to send a ( miniscule ) error in the opposite direction / on the API rather than forwarding a message it expects to be rejected based on header size . That 's purely an optimization , and it 's always acceptable to just transfer the big thing and find out at the end that it failed . If browsers will consume arbitrarily - large responses , then the correct value for this setting for them is unlimited . Because there could be multiple intermediaries , it 's entirely possible that one stream could see an EXCESSIVELOAD error even though the headers were n't too large for the first hop upstream . That 's one of the biggest reasons this needs to be a stream error -- the setting is hop - by - hop , but enforcement is ultimately on a per - request basis across the set of hops . If used this way , it should n't break anything that was n't broken before -- things that would have failed will simply fail faster . If you do n't implement it , then you 'll still eventually get an EXCESSIVELOAD or a 400 . I agree that the current text does n't reflect this usage pattern . If we agree that this is the correct way to envision it , I 'll write up better text . 
 Discussed in London ; an implementation SHOULD NOT send its peer a message that 's over the peer 's advertised size . Text clarifying that the peer is advertising the ( approximate ) limit at which it will refuse to accept a message , enabling the local implementation to fail more quickly and avoid the risk that the " refusal " might be a connection termination which would disrupt legitimate messages as well . 
 As long as it is n't a MUST . There might be pre - canned requested of a reasonable size that generally just works , and having to check these might not be worthwhile on the odd chance of a very restrictive peer . 
 For consistency with PR and on - list discussion , there are multiple other places in the draft an error that indicates an incorrect implementation is treated as a stream error , not a connection error . " Only servers can push ; if a server receives a client - initiated push stream , this MUST be treated as a stream error of type HTTPWRONGSTREAMDIRECTION . " " A PRIORITY frame received after other frames on a request stream MUST be treated as a stream error of type HTTPUNEXPECTEDFRAME . " Unclear : " A PRIORITY frame that references a non - existent  ID , a Placeholder ID greater than the server 's limit , or a Stream ID the client is not yet permitted to open MUST be treated as an  error . " " A CANCELPUSH frame is sent on the control stream . Receiving a CANCELPUSH frame on a stream other than the control stream MUST be treated as a stream error of type HTTPWRONGSTREAM . " Possibly should be a connection error : " A PRIORITY frame sent on a request stream with the Prioritized Element Type set to any value other than " 11 " or which expresses a dependency on a request with a greater Stream ID than the current stream MUST be treated as a stream error of type HTTPMALFORMEDFRAME . " In the appendix : " SETTINGSMAXCONCURRENTSTREAMS : QUIC controls the largest open Stream ID as part of its flow control logic . Specifying SETTINGSMAXCONCURRENTSTREAMS in the SETTINGS frame is an error . SETTINGSINITIALWINDOWSIZE : QUIC requires both stream and connection flow control window sizes to be specified in the initial transport handshake . Specifying SETTINGSINITIALWINDOWSIZE in the SETTINGS frame is an error . SETTINGSMAXFRAMESIZE : This setting has no equivalent in HTTP/3 . Specifying it in the SETTINGS frame is an error . " 
 I 'm ok with such changes but wonder if we should also add some further prose to the document that : highlights HTTP/3 difference to HTTP/2 i.e. " all errors are always connection errors " captures some of the guidance mentioned elsewhere like " identifying the stream on which a problem frame was delivered may be useful in error analysis , endpoints might want to include the stream ID in the reason phrase " 
 I 'd forgotten about the settings appendix . This reads weird . Those settings are not formally defined in core HTTP/3 by name , so I wonder if the intention is that it is a " philosophical error " . The code points are reserved , so we could suggest connection error on receipt . But we also have reserved grease code points that must be ignored . 
 I do n't believe all errors are connection errors , even after Kazuho 's PR , but I believe we could make that true with some changes . For example , when too large a header is received , it 's currently a stream error because it 's not required you communicate your  header size , and if you do n't , the peer ca n't expected to know the limit . 
 The default value of SETTINGSMAXHEADERLISTSIZE is unlimited . Would you expect a peer to generate an excessive load error on a stream with the default ? 
 The current text is : " encountering a larger message header SHOULD be treated as a stream error of type " HTTPEXCESSIVELOAD " . If an implementation wishes to advise its peer of this limit , it can be conveyed as a number of bytes in the " SETTINGSMAXHEADERLISTSIZE " parameter . " To me , the issue here is that a peer can have a limit , but is not required to advertise that limit . If we required that if a peer has a limit , it must advertise it and enforce it , then closing the connection makes sense to me . Given it 's really easy to advertise it and it 's within encryption , I do n't see any reason not to make that change , but it 's a slightly larger change than the other cases , where we 're changing obviously avoidable errors to connection errors . 
 Happy to spin off another issue . See Section 5.1.1 for usage . Is the intent here " unlimited , but not really and I may reject at any time without sharing my limit " . How is a peer that broke the rule expected to recover from such an error ? 
 How about " an endpoint encountering a header list larger than the value advertised using SETTINGSMAXHEADERLISTSIZE parameter MUST close the connection with a connection error . " I do not think we should mandate the use of SETTINGSMAXHEADERLISTSIZE when imposing a limit on the header list size . This is because the maximum can be different between requests . For a proxy , the maximum size is actually unknown , because each origin would have different maximums . PS . Though such a change would mandate the sender to respect SETTINGSMAXHEADERLISTSIZE ( it 's not required now ) . 
 Let me correct my self . This is wrong . By not forbidding responding with a connection error , we have been effectively mandating the sender to respect the settings parameter . Therefore , I think we can clarify as an editorial change that a client MUST adhere the settings parameter , and then exceeding a limit becomes a protocol violation , which should be treated as a connection error . PS . The text covering this is tentatively pushed to . PTAL . 
 Your text SGTM . Since HTTP/3 is not yet at a ' higher bar ' , it does n't matter too much as long as  agrees as well . 
 From an implementation point of view , being able to classify an error as either a connection error or a stream error would simplify error reporting since we 'd be able to classify an error as either a connection error or a stream error without having to include extra information with the error . As an example , at the moment , when returning a error from a function , I have to include whether the is a stream error or a connection error since it can be both . If every error could be classified as either a connection error or a stream error , I would n't have to include this extra information since a error would always be a connection error . 
 in keeping with HTTP/2 , an endpoint can always decide to treat a stream error as a connection error . However , you 're correct that some ( and after the associated PR , most ) errors will always be connection errors , and your implementation could choose to always treat the others as stream errors . Just make sure there 's no issue if someone else decides to treat it as a connection error . 
 Fixed in , which should have closed this . 
 thanks for writing this up . I 'm still a little torn about making almost every possible error a connection error , but they are correctness issues . Are you planning to address the two points you raise above?LGTM
Paragraph (version A):
Both clients and servers SHOULD send a value of three or greater for
the QUIC transport parameter "initial_max_uni_streams".
Paragraph (version A):
If the stream header indicates a stream type which is not supported
by the recipient, the remainder of the stream cannot be consumed as
the semantics are unknown.  Recipients of unknown stream types MAY
trigger a QUIC STOP_SENDING frame with an error code of
HTTP_UNKNOWN_STREAM_TYPE, but MUST NOT consider such streams to be an
error of any kind.
Paragraph (version B):
The performance of HTTP/3 connections in the early phase of their
lifetime is sensitive to the creation and exchange of data on
unidirectional streams.  Endpoints that set low values for the QUIC
transport parameters "initial_max_uni_streams" and
"initial_max_stream_data_uni" will increase the chance that the
remote peer reaches the limit early and becomes blocked.  In
particular, the value chosen for "initial_max_uni_streams" should
consider that remote peers may wish to exercise reserved stream
behaviour (stream-grease).  To reduce the likelihood of blocking,
both clients and servers SHOULD send a value of three or greater for
the QUIC transport parameter "initial_max_uni_streams", and a value
of 1,024 or greater for the QUIC transport parameter
"initial_max_stream_data_uni".
Paragraph (version B):
If the stream header indicates a stream type which is not supported
by the recipient, the remainder of the stream cannot be consumed as
the semantics are unknown.  Recipients of unknown stream types MAY
trigger a QUIC STOP_SENDING frame with an error code of
HTTP_UNKNOWN_STREAM_TYPE, but MUST NOT consider such streams to be a
connection error of any kind.
Paragraph (version A):
Only servers can push; if a server receives a client-initiated push
stream, this MUST be treated as a stream error of type
HTTP_WRONG_STREAM_DIRECTION.
Paragraph (version B):
Only servers can push; if a server receives a client-initiated push
stream, this MUST be treated as a connection error of type
HTTP_WRONG_STREAM_DIRECTION.
Paragraph (version A):
When a client initiates a request, a PRIORITY frame MAY be sent as
the first frame of the stream, creating a dependency on an existing
element.  In order to ensure that prioritization is processed in a
consistent order, any subsequent PRIORITY frames for that request
MUST be sent on the control stream.  A PRIORITY frame received after
other frames on a request stream MUST be treated as a stream error of
type HTTP_UNEXPECTED_FRAME.
Paragraph (version B):
When a client initiates a request, a PRIORITY frame MAY be sent as
the first frame of the stream, creating a dependency on an existing
element.  In order to ensure that prioritization is processed in a
consistent order, any subsequent PRIORITY frames for that request
MUST be sent on the control stream.  A PRIORITY frame received after
other frames on a request stream MUST be treated as a connection
error of type HTTP_UNEXPECTED_FRAME.
Paragraph (version A):
A four-bit field which MUST be zero when sent and MUST be ignored
on receipt.
Paragraph (version B):
A four-bit field which MUST be zero when sent and has no semantic
value on receipt.
Paragraph (version A):
Note that unlike in RFC7540, the root of the tree cannot be
referenced using a Stream ID of 0, as in QUIC stream 0 carries a
valid HTTP request.  The root of the tree cannot be reprioritized.  A
PRIORITY frame sent on a request stream with the Prioritized Element
Type set to any value other than "11" or which expresses a dependency
on a request with a greater Stream ID than the current stream MUST be
treated as a stream error of type HTTP_MALFORMED_FRAME.  Likewise, a
PRIORITY frame sent on a control stream with the Prioritized Element
Type set to "11" MUST be treated as a connection error of type
HTTP_MALFORMED_FRAME.
Paragraph (version A):
When a PRIORITY frame claims to reference a request, the associated
ID MUST identify a client-initiated bidirectional stream.  A server
MUST treat receipt of a PRIORITY frame identifying a stream of any
other type as a connection error of type HTTP_MALFORMED_FRAME.
Paragraph (version A):
A PRIORITY frame that references a non-existent Push ID, a
Placeholder ID greater than the server's limit, or a Stream ID the
client is not yet permitted to open MUST be treated as an
HTTP_LIMIT_EXCEEDED error.
Paragraph (version B):
Note that unlike in RFC7540, the root of the tree cannot be
referenced using a Stream ID of 0, as in QUIC stream 0 carries a
valid HTTP request.  The root of the tree cannot be reprioritized.
Paragraph (version B):
A PRIORITY frame with Empty bits not set to zero MAY be treated as a
connection error of type HTTP_MALFORMED_FRAME.
Paragraph (version B):
A PRIORITY frame that references a non-existent Push ID, a
Placeholder ID greater than the server's limit, or a Stream ID the
client is not yet permitted to open MUST be treated as a connection
error of type HTTP_LIMIT_EXCEEDED.
Paragraph (version A):
A CANCEL_PUSH frame is sent on the control stream.  Receiving a
CANCEL_PUSH frame on a stream other than the control stream MUST be
treated as a stream error of type HTTP_WRONG_STREAM.
Paragraph (version B):
A CANCEL_PUSH frame is sent on the control stream.  Receiving a
CANCEL_PUSH frame on a stream other than the control stream MUST be
treated as a connection error of type HTTP_WRONG_STREAM.
Paragraph (version A):
The MAX_PUSH_ID frame (type=0xD) is used by clients to control the
number of server pushes that the server can initiate.  This sets the
maximum value for a Push ID that the server can use in a PUSH_PROMISE
frame.  Consequently, this also limits the number of push streams
that the server can initiate in addition to the limit set by the QUIC
MAX_STREAM_ID frame.
Paragraph (version B):
The MAX_PUSH_ID frame (type=0xD) is used by clients to control the
number of server pushes that the server can initiate.  This sets the
maximum value for a Push ID that the server can use in a PUSH_PROMISE
frame.  Consequently, this also limits the number of push streams
that the server can initiate in addition to the limit set by the QUIC
MAX_STREAMS frame.
Paragraph (version A):
The same server push can be associated with additional client
requests using a DUPLICATE_PUSH frame (see frame-duplicate-push).
Ordering of a DUPLICATE_PUSH in relation to certain parts of the
response is similarly important.  Due to reordering, DUPLICATE_PUSH
frames can arrive before the corresponding PUSH_PROMISE frame, in
which case the request headers of the push would not be immediately
available.  Clients which receive a DUPLICATE_PUSH frame for an as-
yet-unknown Push ID can either delay generating new requests for
content referenced following the DUPLICATE_PUSH frame until the
request headers become available, or can initiate requests for
discovered resources and cancel the requests if the requested
resource is already being pushed.
Paragraph (version B):
The same server push can be associated with additional client
requests using a DUPLICATE_PUSH frame (see frame-duplicate-push).
Ordering of a DUPLICATE_PUSH in relation to certain parts of the
response is similarly important.
Paragraph (version B):
Due to reordering, DUPLICATE_PUSH frames or push stream data can
arrive before the corresponding PUSH_PROMISE frame.  When a client
receives a DUPLICATE_PUSH frame for an as-yet-unknown Push ID, the
request headers of the push are not immediately available.  The
client can either delay generating new requests for content
referenced following the DUPLICATE_PUSH frame until the request
headers become available, or can initiate requests for discovered
resources and cancel the requests if the requested resource is
already being pushed.  When a client receives a new push stream with
an as-yet-unknown Push ID, both the associated client request and the
pushed request headers are unknown.  The client can buffer the stream
data in expectation of the matching PUSH_PROMISE.  The client can use
stream flow control (see section 4.1 of QUIC-TRANSPORT) to limit the
amount of data a server may commit to the pushed stream.