有没有现成的解决方案可以用于静态文件压缩和支持范围字节?

huangapple go评论152阅读模式
英文:

Is there an out-of-the-box solution for serve static file compression and support range bytes?

问题

我有一个发送静态文件的操作。

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
}

有需要压缩静态 CSS 和 JS 文件的需求。
http.ServeFile 支持范围字节,如果压缩 http.ServeFile 返回的文件,文件结构将会被破坏。
我没有看到其他的解决办法,可以放弃范围字节,例如通过删除客户端和服务器之间报告范围支持的头部,或者需要编写自己的解决方案。
假设前端服务器(如 nginx)不会安装。

英文:

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

答案1

得分: 2

这个库 https://github.com/vearutop/statigz(我是作者)可以使用 go1.16+ 的 embed 包来提供预压缩的资源,支持文件范围,无需额外配置。

package main

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

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

// 声明你的嵌入资源。

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

func main() {
	// 将静态资源处理程序插入到你的服务器或路由器中。
	err := http.ListenAndServe(":80", statigz.FileServer(st, brotli.AddEncoding))
	if err != nil {
		log.Fatal(err)
	}
}
英文:

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

答案2

得分: 0

我不是一个代码翻译器,但我可以帮你理解这段代码的功能。这段代码是一个用于处理HTTP请求的Go语言代码。它包含了一个自定义的响应写入器compressResponseWriter,以及一个测试函数TestServeFile

compressResponseWriter结构体实现了http.ResponseWriter接口,并在写入响应时进行了压缩处理。它通过检查请求的Content-Type来确定是否需要进行压缩。如果Content-Type是"text/javascript"、"text/plain"、"text/css"或"text/html",则会使用Brotli算法进行压缩,并删除响应头中的"Accept-Ranges"和"Content-Length"字段。否则,不进行压缩。

TestServeFile函数是一个测试函数,用于测试文件的服务功能。它创建了一个HTTP服务器,并注册了一个处理函数。在处理函数中,它创建了一个compressResponseWriter实例,并使用http.ServeFile函数将文件内容作为响应返回给客户端。然后,根据响应头中的"Content-Encoding"字段,选择相应的解压缩算法对响应进行解压缩,并读取解压缩后的内容。

这段代码的主要功能是在HTTP响应中实现压缩和解压缩的功能。它使用了Brotli和gzip两种压缩算法,并根据请求的Content-Type来确定是否需要进行压缩。测试函数则验证了压缩和解压缩的正确性。

希望这能帮到你!如果你有其他问题,请随时问我。

英文:

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

huangapple
  • 本文由 发表于 2021年11月6日 19:09:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/69863463.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定