英文:
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))
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论