Skip to content

Middleware

Compress

Gzip responses for clients that accept it.

Compress gzips responses for clients that send Accept-Encoding: gzip, which makes JSON and HTML much smaller on the wire. Skip it when a CDN or reverse proxy in front of the app already compresses responses.

import "github.com/0mjs/zinc/middleware/compress"
app.Use(compress.New())

A 2,012-byte JSON response, fetched with and without gzip:

Terminal window
curl -s -D - -o /dev/null -w "%{size_download} bytes\n" \
-H "Accept-Encoding: gzip" http://localhost:8080/report
# HTTP/1.1 200 OK
# Content-Encoding: gzip
# Content-Type: application/json; charset=utf-8
# Vary: Accept-Encoding
#
# 53 bytes
curl -s -D - -o /dev/null -w "%{size_download} bytes\n" http://localhost:8080/report
# HTTP/1.1 200 OK
# Content-Type: application/json; charset=utf-8
# Vary: Accept-Encoding
#
# 2012 bytes

Error responses from your handlers, and 404s, are compressed too.

Setting Default
Level gzip.DefaultCompression
Smallest body compressed any size

Trade a little size for speed, and leave small responses alone:

import (
"compress/gzip"
"github.com/0mjs/zinc/middleware/compress"
)
app.Use(compress.New(compress.Config{Level: gzip.BestSpeed, MinLength: 1024}))

A 12-byte {"ok":true} now goes out uncompressed, and the 2,012-byte report is still gzipped. Setting MinLength is worth it: gzip adds about 20 bytes of framing, so a short error body comes out larger than it went in.

Field Default Meaning
Level 0, which means gzip.DefaultCompression A compress/gzip level, from gzip.HuffmanOnly (-2) to gzip.BestCompression (9)
MinLength 0 Responses shorter than this many bytes are sent uncompressed

The response goes out as written when:

  • the client doesn’t accept gzip, or sends gzip;q=0
  • the request method is HEAD
  • the status can’t have a body (1xx, 204, 304)
  • the handler already set Content-Encoding
  • the response is partial: a 206, or any response with Content-Range, whose byte offsets refer to the uncompressed body
  • the body is shorter than MinLength

Every response gets Vary: Accept-Encoding, compressed or not, so a shared cache keeps the two versions apart.

A gzipped body is a different representation from the uncompressed one, so the two can’t share a strong ETag. When compress gzips a response, a strong ETag becomes weak: "v1" goes out as W/"v1". A weak ETag is left as it is, and so is the ETag of a response that goes out uncompressed. If-None-Match compares ETags weakly, so a client sending W/"v1" back still matches "v1".

The compressing writer keeps Flush, Hijack and Push working, so streaming responses and WebSocket upgrades still work behind it. With MinLength set, a Flush before the body reaches that size sends the response uncompressed.

compress doesn’t reject requests. An error from writing the gzip stream is returned from the middleware.

When a handler panics after writing part of a body, compress finishes what was written before the panic carries on: a gzip stream gets its ending, so it decompresses cleanly, and a body still held back under MinLength goes out uncompressed. The status already written stays, so Recover can only send its 500 when nothing was written. A panic with http.ErrAbortHandler is passed on without finishing the body, so net/http aborts the response.

compress.New panics at startup when Level is outside -2 to 9, when MinLength is negative, or when it’s given more than one Config.