nytimes/gziphandler

Range-Requests aren't properly handled

gsauthof opened this issue · 0 comments

When wrapping a handler that supports HTTP Range-Requests (e.g. http.FileServer), gziphandler relays them as-is, thus violating the HTTP standard and breaking clients.

That means, currently, gziphandler compresses ranges returned by the wrapped handler instead of returning ranges of the compressed output (of the complete wrapped content).

The HTTP standard basically says that Content-Length is the size of the Content-Encoding output and that range requests specify ranges into the Content-Encoding encoded output, as well.

Expected behavior: Either (a) gziphandler filters out the Accept-Ranges: bytes headers in wrapped handler responses (and any Range: headers in passed requests), or, (b) it handles Range-Requests on its own and doesn't pass them down to the wrapped handler.

Note that implementing (b) would be kind of complicated, e.g. a range that is smaller than the configured minSize would have to trigger a compression up to the range end.

How to reproduce:

Create a handler like this:

gz_handler, err := gziphandler.NewGzipLevelAndMinSize(gzip.BestCompression, 100)
if err !=  nil {
    log.Fatal(err)
}
http.Handle("/static/", http.StripPrefix("/static/",
        gz_handler(http.FileServer(http.Dir("static")))))

Request a full compressed page:

$ curl --header 'Accept-Encoding: gzip' -v -o f http://localhost:8080/static/page.css
[..]
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Content-Encoding: gzip
< Content-Type: text/css; charset=utf-8
< Vary: Accept-Encoding
< Content-Length: 373
[..]
$ file f
f: gzip compressed data, max compression, original size 804

Note how the Content-Length is the size of the compressed content, as expected.

Getting a range:

$ curl --header 'Accept-Encoding: gzip' --header 'Range: bytes=0-300' -v -o a http://localhost:8080/static/page.css
[..]
< HTTP/1.1 206 Partial Content
< Accept-Ranges: bytes
< Content-Encoding: gzip
< Content-Range: bytes 0-300/804
< Content-Type: text/css; charset=utf-8
< Content-Length: 201
[..]

Note the following issues:

  1. The Content-Length is wrong: 201 vs. 301
  2. The Content-Range information doesn't match the Content-Length in the previous request: 373 vs. 804 (the size of the uncompressed content)

And the range isn't a prefix of the previous result:

$ cmp f a
f a differ: byte 11, line 1

Another range request:

$ curl --header 'Accept-Encoding: gzip' --header 'Range: bytes=301-372' -v -o b http://localhost:8080/static/page.css
[..]
< HTTP/1.1 206 Partial Content
< Accept-Ranges: bytes
< Content-Length: 72
< Content-Range: bytes 301-372/804
< Content-Type: text/css; charset=utf-8
[... no Content-Encoding header ...]

Similar issues as before and the range isn't gzip-compressed.