skip to Main Content

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

2

Answers


  1. Chosen as BEST ANSWER

    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))
    }
    

  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)
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search