Subject: HTTP/2 Server Push and solid compression

From: Alan Egerton <[email-masked]>
Date: Tue, 21 May 2019 15:33:34 +0100

Dear all,

It's my understanding that one of the design goals of HTTP/2 Server
Push was to address issues in HTTP/1.1 that delayed page load and were
worked-around by resource "bundling" (e.g. as part of an offline
compilation step)?

Indeed (and especially when taken together with support for ES6
modules now in most browsers), HTTP/2 Server Push finally makes it
conceivable that web apps might in future be deployed without any such
bundling (and, in simple cases, perhaps any compilation step)
whatsoever!

That said, bundling did (perhaps unintentionally) introduce another
advantage: solid compression—that is, "the compression of a
concatenation" (which is usually far more efficient than "the
concatenation of compressions").  Accordingly, if an HTTP/2 server
separately compresses each underlying source and transmits them as
separate resources, the total payload will likely be greater in size
than if the server were to somehow first combine those sources and
compress them as a single resource.  This difference can be very
material.

I'm sure I'm preaching to the converted if I point out that such a
naive approach would be a definite regression: a clear advantage of
pushing resources separately is that they can each be cached without
any change to infrastructure; conversely, bundled resources get cached
as a bundle and requests for an intersecting set of resources cannot
be serviced (even partly) from the cache.

I see two possible solutions:

(1) standardise the bundle format in order that caches can separate
and store the underlying resources: plenty of hazards here—especially
since there will no longer be an HTTP response per resource, requiring
metadata (including cache control etc) to be encoded somehow else.  My
gut says this is probably a bad idea.

(2) use a compression format that produces a separate output file for
each input file, yet still achieves better overall compression than
compressing the files individually: I imagine that this will produce
an additional output file that is common to/referenced by all the
compressed files being returned by that single operation;
decompression of any of the transmitted resources would be achieved
using only the common file and the resource-specific file as input.

Perhaps I'm way off the mark, and neither approach is feasible—or
perhaps I am overstating the problem?  Is this an area that has been
explored already?

-- Alan

---

From: Alan Egerton <[email-masked]>
Date: Tue, 21 May 2019 16:16:38 +0100

> using only the common file and the resource-specific file as input.

Just following my own thoughts with an observation: in extremis, these
two approaches can actually become analogous.

For example, a .tar.gz could serve as both the standardised "bundle"
format (1) and the common output file (2) with the metadata
transmitted in the form of separate HTTP responses (1) whose payloads
reference the relevant constituent of that tarball (2).

I recognise that such an approach would also be a regression, because
it defeats the benefits of HTTP/2's multiplexing (the constituents of
the tarball only become available in sequence); therefore any solution
of type (2) must balance the competing requirements to minimise both
the "common file" and the overall size.  Perhaps there is no such
balance that yields material benefit over the status quo.

-- Alan

---

From: Alan Egerton <[email-masked]>
Date: Tue, 21 May 2019 18:43:49 +0100

On Tue, May 21, 2019 at 6:06 PM Felipe Gasper <[email-masked]> wrote:
> I’m reminded of WebSocket’s “permessage-deflate” extension, which includes parameters for controlling the retention of compression context from message to message.
>
> Maybe such an approach for multiple compressed payloads in series would effectively eliminate the size difference?

But then later messages can be decompressed only after such retained
compression context has been discovered from the decompression of
earlier messages?  Whilst this could help to increase the compression
ratio of subsequent response "bundles" in the same session (at
increased server complexity/memory utilization), if used within a
"bundle" as proposed would it not present the same sequential access
problem that I described for .tar.gz in my previous message (and
thereby defeat advantages of HTTP/2 multiplexing)?

-- Alan

---

From: Felipe Gasper <[email-masked]>
Date: Tue, 21 May 2019 13:06:45 -0400

> On May 21, 2019, at 11:16 AM, Alan Egerton <[email-masked]> wrote:
> 

-FG

---

From: Felipe Gasper <[email-masked]>
Date: Tue, 21 May 2019 13:51:27 -0400

> On May 21, 2019, at 1:43 PM, Alan Egerton <[email-masked]> wrote:
> 

Possibly … does h2 multiplexing use a separate compression context for each stream, or does it funnel each message through the same context? If the former, then I would think it’s a non-issue since streams are processed sequentially.

-F

---

From: Patrick McManus <[email-masked]>
Date: Tue, 21 May 2019 14:04:18 -0400

On Tue, May 21, 2019 at 1:57 PM Felipe Gasper <[email-masked]>
wrote:

> >> I’m reminded of WebSocket’s “permessage-deflate” extension, which
> includes parameters for controlling the retention of compression context
> from message to message.
> >>
> >> Maybe such an approach for multiple compressed payloads in series would
> effectively eliminate the size difference?
> >
> Possibly … does h2 multiplexing use a separate compression context for
> each stream, or does it funnel each message through the same context? If
> the former, then I would think it’s a non-issue since streams are processed
> sequentially.
>
>
>
The stream compression contexts are independent - which hurts compression
ratios. However, the WG has not been able to find a satisfactory security
answer to concerns around CRIME like attacks on TLS that sharing attacker
controlled compression streams would enable. Progress on that front would
be great.

-Patrick

---

From: Ilari Liusvaara <[email-masked]>
Date: Tue, 21 May 2019 21:07:52 +0300

On Tue, May 21, 2019 at 01:51:27PM -0400, Felipe Gasper wrote:
> 
> Possibly … does h2 multiplexing use a separate compression context
> for each stream, or does it funnel each message through the same
> context? If the former, then I would think it’s a non-issue since
> streams are processed sequentially.

AFAIK, HTTP/2 can not compress message bodies. The mechanism HTTP/1.x
had to compress message bodies (Transfer-Encoding) was removed.

And even the HTTP/1.x mechanism used separate context for each
request or response.

And I would imagine reusing contexts would run into all sorts of
data leaks by abusing compression...

-Ilari

---

From: Alan Egerton <[email-masked]>
Date: Tue, 21 May 2019 20:47:08 +0100

On Tue, May 21, 2019 at 6:51 PM Felipe Gasper <[email-masked]> wrote:
>  If the former, then I would think it’s a non-issue since streams are processed sequentially.

Are they?  That seems like it should be an implementation detail (and
at that, a poor choice)—I can't see anything in the spec that suggests
otherwise?

-- Alan

---

From: Matthew Kerwin <[email-masked]>
Date: Wed, 22 May 2019 08:35:31 +1000

On Wed, 22 May 2019 at 04:07, Patrick McManus <[email-masked]> wrote:
>
> The stream compression contexts are independent - which hurts compression ratios. However, the WG has not been able to find a satisfactory security answer to concerns around CRIME like attacks on TLS that sharing attacker controlled compression streams would enable. Progress on that front would be great.
>
> -Patrick
>

Yeah, this.  The transport layer can not decide whether it's safe to
compress *things* together, if those *things* aren't under its
control.  Since it's the application layer (or higher) that, in
theory, knows the providence and security characteristics and
everything else about the streams' data, it is the only layer than can
decide whether or not to compress them.  Which is why we can serve
statically-compressed resources with content-encoding (application
layer), and got rid of transfer-encoding (transport layer).

If you wanted to provide hooks into the transport layer so that an
application can explicitly say "these two streams are safe to share a
compression context", and then trust that everyone running said
application understands the security consequences, that could be a way
forward.  But if you have to guarantee that streams are processed in
order for the shared context to work, how is that better than
concatenating them in the same stream?

Cheers
-- 
  Matthew Kerwin
  https://matthew.kerwin.net.au/

---

From: Matthew Kerwin <[email-masked]>
Date: Wed, 22 May 2019 08:46:26 +1000

> had to compress message bodies (Transfer-Encoding) was removed.

Just because it's not in the spec, doesn't mean it can't be in *a* spec. [1]

> And even the HTTP/1.x mechanism used separate context for each

Yeah, the argument for TE was more for sub-resource compression
(request a byte range from an identity-encoded resource, like a log
file, and compress the resulting chunk in transit), rather than
super-resource compression.

> data leaks by abusing compression...

That's why it got yanked from TLS, yeah.

Cheers

[1]: https://tools.ietf.org/html/draft-kerwin-http2-encoded-data

---

From: Kari Hurtta <[email-masked]>
Date: Thu, 23 May 2019 07:31:20 +0300 (EEST)

> Possibly … does h2 multiplexing use a separate compression context for each stream, or does it 
> funnel each message through the same context?

HPACK (Header Compression for HTTP/2) uses one context for all streams.

This is processed when HEADERS or CONTINUATION frame is received. HEADERS frame is also used when
stream is opened.

Frames on HTTP/2 are processed sequentially because it is transported over TCP. So there is
no problem with out of order access of header compression context.

/ Kari Hurtta

---

From: "W. Felix Handte" <[email-masked]>
Date: Tue, 21 May 2019 16:27:32 -0400

Hi Alan,

I absolutely agree with your premise. I think you've identified a good
example of the kinds of models which are not well supported by existing
options for compression in HTTP (which may contribute to explaining why
unbundling resources into server pushes has not seen very wide adoption).

Finding a solution to this problem is extremely desirable, and a number
of attempts have been made to do so. That is, to allow individual
responses to access a shared or external compression context, and
thereby achieve good compression for individually small responses.

For examples, see Compression Dictionaries for HTTP/2 [1], and Shared
Dictionary Compression over HTTP [2]. In extremely broad strokes, your
two proposals are similar (respectively) to those two drafts.

As Patrick mentions, these solutions have largely fallen victim in the
past to security concerns: mixing different types of data into the same
compression window can provide attackers the ability to exfiltrate
private data by observing overall compression effectiveness (i.e.,
CRIME/BREACH/HEIST). This is already a problem in the existing world of
HTTP compression, when applications allow attacker-controlled and
private user data to intermingle in a single response. Extending that,
by allowing interactions between different responses, would be to throw
gasoline on that fire.

I have been working to make another attempt at addressing these problems
(both the narrow one of resolving the security questions and the broad
one of building a solution overall).

I am working on a draft [3] that discusses the security concerns in this
space, which will hopefully let us chart a path forward. I hope to have
something to circulate in advance of the next meeting in July.

Longer term, I am working on building and deploying a dictionary-based
compression scheme for HTTP (at Facebook initially, with an eye towards
eventual standardization).

Collaboration along either front would be very welcome!

- Felix

[1]
https://tools.ietf.org/html/draft-vkrasnov-h2-compression-dictionaries-03
[2] https://tools.ietf.org/html/draft-lee-sdch-spec-00
[3] https://tools.ietf.org/html/draft-kucherawy-httpbis-dict-sec-00