Subject: Fwd: New Version Notification for draft-nottingham-structured-headers-00.txt

From: Mark Nottingham <[email-masked]>
Date: Mon, 30 Oct 2017 11:57:11 +1100

PHK and I have been talking in the background about how to progress the "Common Headers" work item. 

Last week we put together a straw-man of what I thought we might want the eventual product to look like. Please have a look; it'd be interesting to see how close/far this is from where we want to go.

Note that it *is* pretty thrown together, and there are a couple of TBDs. I'm also certain that the algorithms are buggy as can be.

The interesting things to discuss here IMO are:

* Are the basic data types offered the right ones? Too many, too few?
* Should parameters on parameterised labels be ordered or unordered (since writing that, I've found a use case for ordered)?
* Are the limited levels of structure (most complex being a list of parameterised labels) sufficient?
* Is limiting size and number of elements appropriate? Are the limits proposed the right ones (warning: bikeshed)?
* Is saying that non-ASCII content can use the binary content type + UTF-8 sufficient?
* Is the parsing too strict, not strict enough, or just right?

Etc.

Cheers,

P.S. Pretty version at:
  https://mnot.github.io/I-D/structured-headers/

> Begin forwarded message:
> 
> From: [email-masked]
> Subject: New Version Notification for draft-nottingham-structured-headers-00.txt
> Date: 30 October 2017 at 11:50:24 am AEDT
> To: "Poul-Henning Kamp" <[email-masked]>, "Mark Nottingham" <[email-masked]>
> 
> 
> A new version of I-D, draft-nottingham-structured-headers-00.txt
> has been successfully submitted by Mark Nottingham and posted to the
> IETF repository.
> 
> Name:		draft-nottingham-structured-headers
> Revision:	00
> Title:		Structured Headers for HTTP
> Document date:	2017-10-30
> Group:		Individual Submission
> Pages:		16
> URL:            https://www.ietf.org/internet-drafts/draft-nottingham-structured-headers-00.txt
> Status:         https://datatracker.ietf.org/doc/draft-nottingham-structured-headers/
> Htmlized:       https://tools.ietf.org/html/draft-nottingham-structured-headers-00
> Htmlized:       https://datatracker.ietf.org/doc/html/draft-nottingham-structured-headers-00
> 
> 
> Abstract:
>   This document describes Structured Headers, a way of simplifying HTTP
>   header field definition and parsing.  It is intended for use by new
>   HTTP header fields.
> 
> 
> 
> 
> Please note that it may take a couple of minutes from the time of submission
> until the htmlized version and diff are available at tools.ietf.org.
> 
> The IETF Secretariat
> 

--
Mark Nottingham   https://www.mnot.net/

---

From: Kazuho Oku <[email-masked]>
Date: Mon, 30 Oct 2017 10:50:46 +0900

Thank you for working on the draft.

I like this approach very much. It is evolutional, and to me seems
very practical.

What we have suffered until today is not being able to reuse a lexer
for parsing headers.

To me it seems that the draft is trying to solve that issue only, and
I think that it would be a good approach considering that we have had
issues in previous attempts that tried to provide other changes at the
same time.

My comments in-line.

2017-10-30 9:57 GMT+09:00 Mark Nottingham <[email-masked]>:
> Last week we put together a straw-man of what I thought we might want the
> eventual product to look like. Please have a look; it'd be interesting to
> see how close/far this is from where we want to go.
>
> Note that it *is* pretty thrown together, and there are a couple of TBDs.
> I'm also certain that the algorithms are buggy as can be.
>
> * Should parameters on parameterised labels be ordered or unordered (since
> writing that, I've found a use case for ordered)?

I think that the decision should be left up to the user of Structured
Headers. In other words, I think that the draft should focus on how
the input should be parsed.

> * Is limiting size and number of elements appropriate? Are the limits
> proposed the right ones (warning: bikeshed)?

My +1 goes to not having limits. Instead, a specification that uses
Structured Headers should set limits, if that deems appropriate.

My feeling is that it seems awkward to me to have the restrictions in
Structured Headers, considering the fact that other aspects of HTTP do
not have. For example, there is no hard limit on the length of an HTTP
header field value or on the number of HTTP headers.

> Subject: New Version Notification for
> draft-nottingham-structured-headers-00.txt
> URL:
> https://www.ietf.org/internet-drafts/draft-nottingham-structured-headers-00.txt
> Status:
> https://datatracker.ietf.org/doc/draft-nottingham-structured-headers/
> Htmlized:
> https://tools.ietf.org/html/draft-nottingham-structured-headers-00
> Htmlized:
> https://datatracker.ietf.org/doc/html/draft-nottingham-structured-headers-00
>
>
> Abstract:

-- 
Kazuho Oku

---

From: Mark Nottingham <[email-masked]>
Date: Mon, 30 Oct 2017 15:50:17 +1100

Hi Kazuho,

> On 30 Oct 2017, at 12:50 pm, Kazuho Oku <[email-masked]> wrote:
> 

Thanks!

> the input should be parsed.

Seems reasonable to me. Perhaps it would be good to have a non-normative appendix or similar offering guidance to library implementations on things like this.

> header field value or on the number of HTTP headers.

The one thing that makes me pause here is how rarely headers specify limits like this if left to their own devices, and how much trouble it can cause because there are different implementation-specific limits imposed.

Given that, it seems like if we ever want to have some general limits (with the ability for specific headers to tighten them, of course), this is a good time to try.

But it might be that we decide it's not worth trying.

Cheers,

---

From: Kazuho Oku <[email-masked]>
Date: Mon, 30 Oct 2017 14:38:19 +0900

> But it might be that we decide it's not worth trying.

Thank you for your response.

I think that it would be a good idea to specify some limits below
which we can expect interoperability. Something like JSON defines for
floating numbers (i.e., numbers that fit in IEEE754 double are
expected to have good interoperability).

I am simply against defining the limits as hard limits, since in one
way that might be interpreted as a minimum requirement that all
implementations must support, at the same time being an unnecessary
complication for some of other implementations (you can essentially
cap the amount of memory used by header parsing (including Structured
Headers) by capping the maximum number of bytes used to transfer all
the request headers; so why should we have another limit?).

-- 
Kazuho Oku

---

From: Willy Tarreau <[email-masked]>
Date: Mon, 30 Oct 2017 07:02:51 +0100

> expected to have good interoperability).

+1

> the request headers; so why should we have another limit?).

I tend to agree on this (and have changed my initial opinion). I'm
realizing that 15 years ago haproxy was using 4kB for the whole header
block and required changing a #define and recompiling to modify it. It
used to work pretty much everywhere. Now it's 16kB by default and the
limit can be changed by configuration. It's not uncommon to see it
increased further when 16kB is too small. 4kB don't work very often
anymore nowadays. This makes me think that any reasonable limit we
could enforce right now could become complicated to work with in the
future, or induce horrible workarounds when absolutely needed.

Instead I think that explaining very common implementation limits to be
expected in field (eg: 2^31-1, 2^32-1 and 2^63-1 for integers) would
help implementors decide what to support and what not. Ie if it's not
harder to support 2^63 than 2^32 for integers, better do it. But if it's
really hard and doesn't provide any benefit for a realistic use case,
better not bother implementing it. And if for any reason the best that
could be achieved was 2^48 in a much more complex way than 2^32, it's
probably pointless to do it given the expected common limits in field.
This is an example for integers as that's a common one, but this can
be applied to everything including the header block length, number of
header fields, field name length, field value length, etc.

Willy

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Mon, 30 Oct 2017 07:08:49 +0000

--------

>harder to support 2^63 than 2^32 for integers, better do it. 

... unless your programming language thinks all numbers are
floating-point.

The 15 digit limit is to make sure that numbers will not loose
precision in a floating-point double, while still being sufficiently
large for any byte-count a HTTP header can expect to ever see.

-- 
Poul-Henning Kamp       | UNIX since Zilog Zeus 3.20
[email-masked]         | TCP/IP since RFC 956
FreeBSD committer       | BSD since 4.3-tahoe    
Never attribute to malice what can adequately be explained by incompetence.

---

From: Willy Tarreau <[email-masked]>
Date: Mon, 30 Oct 2017 08:14:16 +0100

> large for any byte-count a HTTP header can expect to ever see.

That's still perfectly compatible with what I'm saying indeed, just
taking other possible implementation limits in consideration that I
didn't think about!

Willy

---

From: Wenbo Zhu <[email-masked]>
Date: Mon, 30 Oct 2017 10:36:21 -0700

I'll ask about two more things:
1) being able to separate standard headers from user-defined metadata ..
now that we have a convention for "value" types
2) a (simple) binary representation for the structure ... has it been
discussed?

Limits are useful for standard headers..

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Mon, 30 Oct 2017 18:26:20 +0000

--------

>now that we have a convention for "value" types

Some of the standard headers could be redescribed with reference
to this draft, and there may be an advantage to doing that as
an exercise along the way.  Not sure if such a redescription
would deserve publication as more than an exemplary appendix.

>2) a (simple) binary representation for the structure ... has it been

It is not obvious to me that there is much gain at this point.

Obviously the binary objects could be transmitted len+bits instead
of base64+HPACK, but as long as we do not redefine the Cookie
header in terms of this draft, it may not be worth the time.

>Limits are useful for standard headers..

Yes and also for headers in general.

---

From: Alex Rousskov <[email-masked]>
Date: Mon, 30 Oct 2017 16:24:31 -0600

> * Is the parsing too strict, not strict enough, or just right?

AFAICT, the top-level parsing algorithm in Section 3 is missing a
catch-all rule for trailing garbage. For example, right now, the
following malformed Item header field will pass all checks and return a
"valid" String to the caller. It should throw a "trailing garbage" error
instead.

  Foo: "valid" garbage!

When fixing that, tolerating trailing BWS may be a good idea.

> Note that input_string may incorporate multiple header lines combined
> into one comma-separated field-value

I understand the desire to limit this specification to parsing a single
header field or equivalent, leaving the Pandora box of combining
same-name fields closed. However, this honorable approach complicates
placing interoperability limits on the number of list members: An
application that parses individual fields (and possibly never combines
the results!) may not hit the limit that an application combining raw
value strings before parsing would hit.

IMHO, we should explicitly say something (conservative) about this
problem so that header generators know how the limits may be applied.

> However, field definitions are encouraged to clearly state additional
> constraints upon the syntax, as well as the consequences when those
> constraints are violated.

I would rephrase that phrase to avoid a misunderstanding: That phrase is
talking about additional _semantic_ restrictions (on top of the frozen
syntax rules), not additional syntax constraints.

Not sure whether this is in your "interesting things to discuss" scope,
but please allow empty quoted strings :-).

Thank you,

Alex.
P.S. I am not reporting individual BNF and algorithm problems because
they seem to be outside the "interesting things to discuss" scope.

---

From: Mark Nottingham <[email-masked]>
Date: Tue, 31 Oct 2017 09:47:26 +1100

> On 31 Oct 2017, at 9:24 am, Alex Rousskov <[email-masked]> wrote:
> 

Good point, thanks.

> problem so that header generators know how the limits may be applied.

If the document progresses this seems worth addressing.

> syntax rules), not additional syntax constraints.

Ack.

> but please allow empty quoted strings :-).

Good point :)

> they seem to be outside the "interesting things to discuss" scope.

Thanks for your forbearance; we'll get there :)

---

From: Mark Nottingham <[email-masked]>
Date: Tue, 31 Oct 2017 10:25:20 +1100

On 31 Oct 2017, at 4:36 am, Wenbo Zhu <[email-masked]> wrote:
> 1) being able to separate standard headers from user-defined metadata .. now that we have a convention for "value" types

I think that would have to be done in a new version of HTTP itself, not by a convention that's adopted header-by-header. I also suspect it would be difficult to introduce.

Do we have a crisp idea of what 'user' is vs. 'standard'?

> 2) a (simple) binary representation for the structure ... has it been discussed?

Not yet, but it's in mind. Negotiating for its use would require either a new version of HTTP, or maybe an extension (although there'd be some non-optimal behaviour in the first RT).

Cheers,

---

From: Kazuho Oku <[email-masked]>
Date: Tue, 31 Oct 2017 10:24:31 +0900

> large for any byte-count a HTTP header can expect to ever see.

While I fear I am going deep into a bikeshed, I oppose to the idea of
using IEEE 754 double as the limit for integers (either as a soft
limit or as a hard limit).

15 digits could be enough for counting bytes, but that is not the only
type of number that we want to send using an HTTP header.

Consider JSON.

Some (if not many) people use JSON for sending / receiving 64-bit
numbers. I say this based on the pressure I had against my JSON
decoder (picojson) to support 64bit int (it used to support IEEE754
doubles only).

Some people (e.g Twitter API) use strings to carry 64-bit numbers
because JSON doesn't guarantee interop.

It might be possible to argue that the majority of the use-case of
64-bit numbers (that do not fit into doubles) are IDs, but I still
think that requiring the use of string to carry a number is a pain for
the users. And there could be use-cases other than IDs that require
making calculations using the numbers on the endpoint.

So why not mandate support for 64-bit integers?

Ten years have passed since the discovery of JSON. We have much more
memory. We are building a new standard instead of adopting something
that existed another ten years ago (i.e. JavaScript).

Let's not repeat the failure made by JSON.

-- 
Kazuho Oku

---

From: "Poul-Henning Kamp" <[email-masked]>
Date: Tue, 31 Oct 2017 08:26:30 +0000

--------
In message <[email-masked]>
, Kazuho Oku writes:

>[...]

If we were designing a general-purpose data-carrier format, I would
be 100% with you there, but we are not.

The goal here is to design a maximally robust data-carrier format,
and that means conservative choices and putting the inconvenience
on the end which packages the data.

The number format is intended for sending quantities on which
arithmetic makes sense, and the point of the restriction is
to reserve to the implementor the ability to use the most
efficient hardware native data type, without loss of precision.

15 digits is 49¾ bits, and while I'm not prepared to state
that "is enough for everybody" I think we can safely say that
it covers all uses of arithmetic seen in HTTP until now.

If your 64 bit number is an identifier, the only valid operation
on it is "check for identity", and taking the detour over a decimal
representation is not only uncalled for, but also very inefficient
in terms of CPU cycles.

The natural and most efficient format for such an identifier would
be base64 binary, but if for some reason it has to be decimal, say
convenience for human debuggers, one could prefix it with a "i" and
send it as a label.

---

From: Alex Rousskov <[email-masked]>
Date: Tue, 31 Oct 2017 10:07:06 -0600

The draft already mandates support for (signed) 64-bit integers AFAICT:

>    integer = ["-"] 1*19 DIGIT
> 
>    Integers SHALL be in the range +/- 2^63-1 (= +/- 9223372036854775807)

The 15-digit limit is for floating point "numbers", not integer numbers.
There may be a naming problem, but I do not see a 64bit integer problem.

Cheers,

Alex.

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 1 Nov 2017 09:58:20 +0900

Hi Alex,

> There may be a naming problem, but I do not see a 64bit integer problem.

I think you that you might be referring to Common Header Structure [1]
instead of Structured Headers [2].

[1] https://datatracker.ietf.org/doc/draft-ietf-httpbis-header-structure/?include_text=1
[2] https://mnot.github.io/I-D/structured-headers/

-- 
Kazuho Oku

---

From: Kazuho Oku <[email-masked]>
Date: Wed, 1 Nov 2017 10:52:53 +0900

Hi, PHK!

> on the end which packages the data.

In my view, current limit (15 digits at max.) is overly conservative.

Let me explain in response to your text below.

> it covers all uses of arithmetic seen in HTTP until now.

IMO, we should consider the future instead of optimizing against what
we see now.

When Content-Length was defined in HTTP/1.0 back in May 1996, the
largest file we used to transfer were CD-ROM images (650MB ~ 1GB ~
2^30 bytes). We are now after 20 years since that, and we are seeing
SD cards of 512GB (~ 1TB ~ 2^40 bytes). Assuming that the increase
will continue, we would be seeing a storage that can store 2^50 bytes
(~1 PB) of data within 20 years.

How long is the expected lifetime of Structured Headers? Assuming that
it would be used for 20 years (HTTP has been used for 20+ years, TCP
is used for 40+ years), there is fair chance that the 49¾ bits limit
is too small. Note that even if we switch to transferring headers in
binary-encoded forms, we might continue using Structured Headers for
textual representation.

Do we want to risk making _all_ our future implementations complex in
exchange of being friendly to _some_ programming languages without
64-bit integers?

Considering the fact that filesystems are raising their maximum
filesize to exabyte range, those programming languages will anyways be
facing issues dealing with large files.

The other thing I would like to point out is that mandating support
for 64-bit integer fields does not necessary mean that you cannot
easily represent such kind of fields when using the programming
languages without 64-bit integers.

This is because there is no need to store an integer field using
integers. Decoders of Structured Headers can retain the representation
as a string (i.e. series of digits), and applications can convert them
to numbers when they want to use the value for calculation.

Since the size of the files transmitted today do not exceed 1PB, such
approach will not have any issues today. As they start handling files
larger than 1PB, they will figure out how to support 64-bit integers
anyways. Otherwise they cannot access the file! Considering that, I
would argue that we are unlikely to see issues in the future as well,
with programming languages that do not support 64-bit integers _now_.

To summarize, 49¾ bits limit is scary considering the expected
lifetime of a standard, and we can expect programming languages that
do not support 64-bit integers to start supporting them as we start
using files of petabyte size.

> convenience for human debuggers, one could prefix it with a "i" and

Requiring the use of base64 goes against the merit of using a textual
representation. The reason we use textual representation is because it
is easy for us to read and use. On most systems, 64-bit IDs are
represented as numbers. So people would want to transmit them in the
same representation over HTTP as well. So to me it seems that it is
whether we want 64-bit integers to be sent as numbers of strings (or
labels). That is the reason why I only compared the two options in my
previous mail.

In this respect, another issue we should consider is that we can more
effectively compress the data if we know that it is a number
(comparing to compressing it as a text or a label).

-- 
Kazuho Oku

---

From: Wenbo Zhu <[email-masked]>
Date: Tue, 31 Oct 2017 19:11:55 -0700

On Mon, Oct 30, 2017 at 4:25 PM, Mark Nottingham <[email-masked]> wrote:

> I think that would have to be done in a new version of HTTP itself, not by
> a convention that's adopted header-by-header. I also suspect it would be
> difficult to introduce.
>
Anything documented in httpbis specs v.s. everything else (inc. those
specified by U-A)?

> Not yet, but it's in mind. Negotiating for its use would require either a
> new version of HTTP, or maybe an extension (although there'd be some
> non-optimal behaviour in the first RT).
>
Initially, "*<base64-encoded>" for user defined headers.  I can also see
such a binary representation being useful elsewhere as a _simple_ binary
serialization format.

---

From: Alex Rousskov <[email-masked]>
Date: Tue, 31 Oct 2017 21:07:39 -0600

> [2] https://mnot.github.io/I-D/structured-headers/

Sigh. You are right! My apologies, and please reinterpret my post as a
regression bug report.

IMHO, structured headers ought to support 64-bit integers. I have not
heard about any use cases where such a requirement would make an
interoperable implementation impossible, but perhaps I have missed it.

I do not understand why a temporary lack of native mapping in a popular
programming language is a show stopper. Yes, Javascript programs cannot
support 64-bit integers natively today, but:

* There are many modules that allow for 64-bit integer support.

* There is a Stage 3 proposal for native support in the future version
  of ECMAScript[3]. Lack of 64-bit integers is a known pain point for
  Javascript. If Javascript continues to evolve, it will be addressed.
  I would be surprised if the latest Javascript standard does not
  support 64-bit integers natively in a few years.

  [3] https://github.com/tc39/proposal-bigint

* Sane Javascript programs will parse Structured Headers using a
  specialized module. To a non-expert, adding one more module (for
  64bit integer support) does not seem like such a big deal in this
  context, even outside a node js environment.

A suggestion to use binary blobs instead of 64-bit integers seems flawed
to me because it is restricted to identifiers and because binary blobs
do not have good native support in auto-encoding-prone Javascript.

Cheers,

Alex.

---

From: Willy Tarreau <[email-masked]>
Date: Wed, 1 Nov 2017 07:41:02 +0100

Hi Kazuho,

> exchange of being friendly to _some_ programming languages without

That's an interesting question that cannot be solved just by a Yes or a
No. Making a language totally unable to implement a protocol (especially
HTTP) is a no-go, and may even ignite the proposal of alternatives for
some parts. So we must at least ensure that it is reasonably possible
to implement the protocol even if that requires a little bit of efforts
and if performance sucks, because people choosing such languages despite
such limitatons will do it only for convenience and the languages will
evolve to make their lifes easier in the future. What must really be
avoided is everything requiring a full-range 64-bit internal
representation all the time. But if 64-bit are needed only for large
files, most developers will consider that their implementation is
sufficient for *their* use cases (even if only 31 or 32 bits).

This is what the text-based integer representation has brought us over
the last two decades : good interoperability between implementations with
very different limits. The ESP8266 in my alarm clock with 50kB of RAM
might very well be using 16-bit integers for content-length and despite
this it's compatible with the rest of the world. Similarly haproxy's
chunk size parser used to be limited to 28 bits for a while and was
only recently raised to 32 after hitting this limit once.

> languages without 64-bit integers.

It only depends if all bits of the fields are always needed or not in
general. If it's just a size, anyone can decide that limiting their
implementation to 32-bit can be OK for their purpose.

> to numbers when they want to use the value for calculation.

It can indeed be an option as well. A punishment I would say.

> with programming languages that do not support 64-bit integers _now_.

I totally agree with this. I like to optimize for valid use cases, and
in general use cases vary with implementations. Similarly I want my code
to be fast on fast machines (because people by fast machines for
performance) and small on resource constrained machines. People adapt
their hardware and software to their needs, the design must scale, not
necessarily the implementations.

> using files of petabyte size.

I think we can solve such issues by specifying some protocol limits
depending on implementations. Not doing this is what has caused some
issues in the past. Content-lengths larger than 2^32 causing some
implementations to wrap for example have been used to cause request
smuggling attacks. But by insisting on boundary checking for the
critical parts of the protocol depending on the storage type (for
well known types), we can at least help implementers remain safe.

> labels). That is the reason why I only compared the two options in my

There is also the option of considering such identifiers as arrays of
32-bit for implementers, since they don't need to perform operations
on them. This is something we can explain in a spec (ie: how to parse
identifiers in general).

> (comparing to compressing it as a text or a label).

Yep, I like the principle of variable length integers. It's not very
efficient in CPU cycles but is still much more than any text-based
representation when all code points are valid as it doesn't require
syntax validation. But the benefits are huge for all small elements.
We did this in haproxy's peers protocol (used to synchronize internal
tables betweeen multiple nodes) because the most common types exchanged
were server identifiers (typically values lower than 10 for 95% of
deployments), and incremental counters (up to 64-bit byte counts) and
we didn't want to use multiple types. By proceeding like this we can
ensure that implementations are not very difficult and can accept
limitations depending on their targetted use cases. And the bandwidth
remains as small as possible.

By the way it is important to keep in mind that a data type is not
necessarily related to the programming language's internal representation.
IP addresses are not numbers, identifiers are not numbers, eventhough
they can often be represented as such for convenience. Numbers have a
fairly different distribution with far more small values than large ones.
Identifiers (and addresses) on the opposite are more or less uniformly
distributed and do not benefit from variable length compression.

Cheers,
Willy