diff --git a/README.md b/README.md index e603950..03fd840 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ This way, you can write tests without relying on an external dependency like [ht - `/cache/:n` Sets a Cache-Control header for _n_ seconds. - `/gzip` Returns gzip-encoded data. - `/deflate` Returns deflate-encoded data. +- `/brotli` Returns brotli-encoded data. - `/robots.txt` Returns some robots.txt rules. - `/deny` Denied by robots.txt file. - `/basic-auth/:user/:passwd` Challenges HTTP Basic Auth. diff --git a/glide.lock b/glide.lock index 2a0a2c4..1fd51d3 100644 --- a/glide.lock +++ b/glide.lock @@ -1,6 +1,8 @@ -hash: c074de462b0a6e667f5766563335c9a2eaf1516145f761c578e68563783833e7 -updated: 2019-06-07T11:58:06.044198393-07:00 +hash: 09ca2e335bb4d2607ccbfb105bc73dbc72e1ff1ed4a4f4d8ef346822cb8fbc07 +updated: 2020-09-21T12:06:57.23507818+09:00 imports: +- name: github.com/andybalholm/brotli + version: b60f0d972eeb79a5fba5fb60f1e0568bc8c97e42 - name: github.com/gorilla/context version: 08b5f424b9271eedf6f9f0ce86cb9396ed337a42 - name: github.com/gorilla/mux @@ -22,13 +24,13 @@ testImports: - assert - require - name: golang.org/x/net - version: 461777fb6f67e8cb9d70cda16573678d085a74cf + version: 62affa334b73ec65ed44a326519ac12c421905e3 subpackages: - html - html/atom - html/charset - name: golang.org/x/text - version: 342b2e1fbaa52c93f31447ad2c6abc048c63e475 + version: a8b4671254579a87fadf9f7fa577dc7368e9d009 subpackages: - encoding - encoding/charmap diff --git a/glide.yaml b/glide.yaml index 91d0582..b53ce88 100644 --- a/glide.yaml +++ b/glide.yaml @@ -4,6 +4,8 @@ import: version: ~1.3.0 - package: github.com/pkg/errors version: ~0.8.0 +- package: github.com/andybalholm/brotli + version: ~1.0.0 testImport: - package: github.com/stretchr/testify version: ~1.2.1 diff --git a/handlers.go b/handlers.go index eead2af..272d775 100644 --- a/handlers.go +++ b/handlers.go @@ -22,6 +22,7 @@ import ( "strings" "time" + "github.com/andybalholm/brotli" "github.com/gorilla/mux" "github.com/pkg/errors" ) @@ -64,6 +65,7 @@ func GetMux() *mux.Router { r.HandleFunc(`/cache`, CacheHandler).Methods(http.MethodGet, http.MethodHead) r.HandleFunc(`/cache/{n:[\d]+}`, SetCacheHandler).Methods(http.MethodGet, http.MethodHead) r.HandleFunc(`/gzip`, GZIPHandler).Methods(http.MethodGet, http.MethodHead) + r.HandleFunc(`/brotli`, BrotliHandler).Methods(http.MethodGet, http.MethodHead) r.HandleFunc(`/deflate`, DeflateHandler).Methods(http.MethodGet, http.MethodHead) r.HandleFunc(`/html`, HTMLHandler).Methods(http.MethodGet, http.MethodHead) r.HandleFunc(`/xml`, XMLHandler).Methods(http.MethodGet, http.MethodHead) @@ -441,6 +443,25 @@ func DeflateHandler(w http.ResponseWriter, r *http.Request) { } } +// BrotliHandler returns a Brotli-encoded response +func BrotliHandler(w http.ResponseWriter, r *http.Request) { + h, _, _ := net.SplitHostPort(r.RemoteAddr) + + v := brotliResponse{ + headersResponse: headersResponse{getHeaders(r)}, + ipResponse: ipResponse{h}, + Compressed: true, + } + + w.Header().Set("Content-Type", "application/json") + w.Header().Add("Content-Encoding", "br") + ww := brotli.NewWriter(w) + defer ww.Close() // flush + if err := writeJSON(ww, v); err != nil { + writeErrorJSON(w, errors.Wrap(err, "failed to write json")) + } +} + // RobotsTXTHandler returns a robots.txt response. func RobotsTXTHandler(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/plain") diff --git a/handlers_test.go b/handlers_test.go index 3b6f4b8..876a8ce 100644 --- a/handlers_test.go +++ b/handlers_test.go @@ -19,6 +19,7 @@ import ( "time" "github.com/ahmetb/go-httpbin" + "github.com/andybalholm/brotli" "github.com/stretchr/testify/require" "golang.org/x/net/html/charset" @@ -570,6 +571,30 @@ func TestDeflate(t *testing.T) { require.True(t, v.Deflated) } +func TestBrotli(t *testing.T) { + srv := testServer() + defer srv.Close() + + client := new(http.Client) + req, err := http.NewRequest("GET", srv.URL+"/brotli", nil) + require.Nil(t, err) + + req.Header.Add("Accept-Encoding", "br") + resp, err := client.Do(req) + require.Nil(t, err) + defer resp.Body.Close() + + require.EqualValues(t, "br", resp.Header.Get("Content-Encoding")) + require.EqualValues(t, "application/json", resp.Header.Get("Content-Type")) + zr := brotli.NewReader(resp.Body) + + var v struct { + Compressed bool `json:"compressed"` + } + require.Nil(t, json.NewDecoder(zr).Decode(&v)) + require.True(t, v.Compressed) +} + func TestRobotsTXT(t *testing.T) { srv := testServer() defer srv.Close() diff --git a/types.go b/types.go index fe2405b..96dddad 100644 --- a/types.go +++ b/types.go @@ -54,6 +54,12 @@ type deflateResponse struct { Deflated bool `json:"deflated"` } +type brotliResponse struct { + headersResponse + ipResponse + Compressed bool `json:"compressed"` +} + type basicAuthResponse struct { Authenticated bool `json:"authenticated"` User string `json:"user"`