thisago's blog

Body compression via simple gorilla/mux middleware

Table of Contents

The Accept-Encoding header is sent by default by web browsers since 2015. See MDN:

This feature is well established and works across many devices and
browser versions. It’s been available across browsers since ⁨July 2015⁩.
---
Browser navigation typically has the following Accept-Encoding request
header value:

http

    GET /en-US/ HTTP/2
    Host: developer.mozilla.org
    Accept-Encoding: gzip, deflate, br, zstd
---
https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Encoding

And it's pretty easy to make your Go API understand the Accept-Encoding headers by using the CAFxX/httpcompression ↗ package, making your server get a instantaneous transfer time decrease.

Adding the Middleware

For this example we have a simple server that responds with the following phrase duplicated 100 times:

Lorem ipsum dolor sit amet, consectetuer adipiscing.

And as expected, it returns the right number of bytes (echo "100 * $(printf 'Lorem ipsum dolor sit amet, consectetuer adipiscing.' | wc -c)" | bc 5200):

curl -s http://localhost:8000 | wc -c
5200

And with this simple middleware, the API is now able to handle Accept-Encoding header!

func compressMiddleware(next http.Handler) http.Handler {
        compress, _ := httpcompression.DefaultAdapter()
        return compress(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                next.ServeHTTP(w, r)
        }))
}

Let's request gzip:

# Fetching compressed content, no decompression at client side. Counting bytes sent by server.
curl -s http://localhost:8000 -H 'Accept-Encoding: gzip' | wc -c
100

And brotli:

# Fetching compressed content, no decompression at client side. Counting bytes sent by server.
curl -s http://localhost:8000 -H 'Accept-Encoding: br' | wc -c
55

But obviously, decompressing at client-side (curl in this case), we get the full length result.

# Fetching compressed content, decompressing at client side. Counting decompressed bytes
curl -s http://localhost:8000 -H 'Accept-Encoding: gzip' --compressed | wc -c
5200

And the server is still able to send uncompressed bytes with the absence of Accept-Encoding header:

# Fetching uncompressed content. Counting bytes sent by server.
curl -s http://localhost:8000 | wc -c
5200

And integrity is preserved, obviously again.

curl -s http://localhost:8000 -H 'Accept-Encoding: gzip' --compressed | sha256sum
curl -s http://localhost:8000 | sha256sum
8bb82893245f323c60c6483b60b2f7bc0f2c8dd6c1a31c2853bb385738cf18d0  -
8bb82893245f323c60c6483b60b2f7bc0f2c8dd6c1a31c2853bb385738cf18d0  -

Headers

# Server sent headers when requesting compressed
curl http://localhost:8000 -H 'Accept-Encoding: gzip' -i | head -n5
HTTP/1.1 200 OK
Content-Encoding: gzip
Vary: Accept-Encoding
Date: Wed, 03 Dec 2025 08:23:42 GMT
Content-Length: 93
# Headers when not compressed
curl http://localhost:8000 -i | head -n5
HTTP/1.1 200 OK
Vary: Accept-Encoding
Date: Wed, 03 Dec 2025 08:24:25 GMT
Content-Type: text/plain; charset=utf-8
Transfer-Encoding: chunked

Full code of the API

server.go

package main

import (
        "fmt"
        "log"
        "net/http"
        "strings"

        "github.com/CAFxX/httpcompression"
        "github.com/gorilla/mux"
)

func compressMiddleware(next http.Handler) http.Handler {
        compress, _ := httpcompression.DefaultAdapter()
        return compress(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
                next.ServeHTTP(w, r)
        }))
}

func handler(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, strings.Repeat("Lorem ipsum dolor sit amet, consectetuer adipiscing.", 100))
}

func main() {
        router := mux.NewRouter()
        router.Use(compressMiddleware)
        router.HandleFunc("/", handler)
        http.Handle("/", router)
        log.Fatal(http.ListenAndServe(":8000", nil))
}

go.mod

module main

go 1.25.3

require (
        github.com/CAFxX/httpcompression v0.0.9
        github.com/gorilla/mux v1.8.1
)

The end

This content was intended too provide a quick solution for gorilla/mux, which I didn't found anything copy-paste ready.

But I admit, the main goal was to play with Org Noweb syntax, which is awesome (see the source code of this page) :)

See the source code here.
Generated at 2025-12-03 Wed 10:01 +0000 by Emacs 29.4 (Org mode 9.6.15)