1

I have action to send static files

func (ws *webserver) staticAction(w http.ResponseWriter, r *http.Request) bool {
    staticFile, err := filepath.Abs(path.Join(ws.staticPath, path.Clean(r.URL.Path)))
    if err == nil {
        fi, err := os.Stat(staticFile)
        if err == nil {
            if mode := fi.Mode(); mode.IsRegular() {
                http.ServeFile(w, r, staticFile)
                return true
            }
        }
    }
    return false
}

There is a need to compress statics css and js. http.ServeFile suppor range bytes, and if compress the return from http.ServeFile the file structure will be broken. I do not see any other way out how to abandon range bytes, for example, by removing headers between the client and the server reporting range support or need write own solution It is assumed that the front server like nginx will not install

Genius Merely
  • 374
  • 2
  • 11
  • 1
    Does this answer your question? [Serving gzipped content for Go](https://stackoverflow.com/questions/14073393/serving-gzipped-content-for-go) – Ярослав Рахматуллин Nov 06 '21 at 11:26
  • 1
    all existing compression solutions do not respect the header Range: bytes=n-n. If client will send this header - http.ServeFile will send part of the data and after compression client will receive not what expected – Genius Merely Nov 06 '21 at 12:51
  • The structure of headers will be broken: Content-Range or content type: multipart/byteranges; boundary=... – Genius Merely Nov 06 '21 at 12:57

2 Answers2

2

This library https://github.com/vearutop/statigz (I'm the author) can serve pre-compressed assets using embed package of go1.16+, file ranges are supported with no additional configuration.

package main

import (
    "embed"
    "log"
    "net/http"

    "github.com/vearutop/statigz"
    "github.com/vearutop/statigz/brotli"
)

// Declare your embedded assets.

//go:embed static/*
var st embed.FS

func main() {
    // Plug static assets handler to your server or router.
    err := http.ListenAndServe(":80", statigz.FileServer(st, brotli.AddEncoding))
    if err != nil {
        log.Fatal(err)
    }
}
vearutop
  • 3,924
  • 24
  • 41
0

I had to write my own stub, which will disable the header range and encode the files Maybe someone will be useful


import (
    "compress/gzip"
    "github.com/andybalholm/brotli"
    "io"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"
)
type compressResponseWriter struct {
    w http.ResponseWriter
    r *http.Request
    compressor io.WriteCloser
    initBrotli   bool
    isInitBrotli bool
}
func (rw *compressResponseWriter) Header() http.Header {
    return rw.w.Header()
}
func (rw *compressResponseWriter) Write(d []byte) (int, error) {
    if !rw.initBrotli {
        rw.initCompressor()
    }
    if rw.isInitBrotli {
        return rw.compressor.Write(d)
    }
    return rw.w.Write(d)
}
func (rw *compressResponseWriter) initCompressor() {
    ct := rw.w.Header().Get("Content-Type")
    rw.initBrotli = true
    if ct == "" {
        return
    }
    switch strings.Split(ct, ";")[0] {
    case "text/javascript", "text/plain", "text/css", "text/html":
        rw.w.Header().Del("Accept-Ranges")
        rw.w.Header().Del("Content-Length")
        rw.compressor = brotli.HTTPCompressor(rw.w, rw.r)
        rw.isInitBrotli = true
    default:
        rw.isInitBrotli = false
    }
}
func (rw *compressResponseWriter) WriteHeader(statusCode int) {
    if statusCode == 200 {
        if !rw.initBrotli {
            rw.initCompressor()
        }
    } else {
        rw.initBrotli = true
    }
    rw.w.WriteHeader(statusCode)
}
func (rw *compressResponseWriter) Close() {
    if rw.isInitBrotli {
        rw.compressor.Close()
    }
}
func NewCompressResponseWriter(w http.ResponseWriter, r *http.Request) *compressResponseWriter {
    r.Header.Del("Range")
    return &compressResponseWriter{w: w, r: r, initBrotli: false}
}

func TestServeFile(t *testing.T) {

    handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        c := NewCompressResponseWriter(w, r)
        http.ServeFile(c, r, "./test.txt")
        c.Close()
    })
    ts := httptest.NewServer(handler)
    defer ts.Close()
    req, _ := http.NewRequest(http.MethodGet, ts.URL, nil)
    req.Header.Set("Accept-Encoding", "gzip, br")
    req.Header.Set("Range", "bytes=0-9")
    req.Header.Set("If-Modified-Since", "Sat, 06 Nov 2021 13:17:06 GMT")
    res, err := http.DefaultClient.Do(req)
    if err != nil {
        t.Fatal(err)
    }
    defer res.Body.Close()
    var reader io.Reader = res.Body
    switch res.Header.Get("Content-Encoding") {
    case "br":
        reader = brotli.NewReader(res.Body)
    case "gzip":
        reader, err = gzip.NewReader(res.Body)
        if err != nil {
            t.Fatal(err.Error())
        }
    }
    body, err := io.ReadAll(reader)
    res.Body.Close()
    if err != nil {
        t.Fatal(err.Error())
    }
    t.Logf("len: %d == 101",len(body))
}
Genius Merely
  • 374
  • 2
  • 11