diff --git a/go.mod b/go.mod index 53b7245..59df02a 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/TeaOSLab/EdgeCommon v0.0.0-00010101000000-000000000000 github.com/andybalholm/brotli v1.0.3 github.com/cespare/xxhash v1.1.0 + github.com/chai2010/webp v1.1.0 github.com/dchest/captcha v0.0.0-20200903113550-03f5f0333e1f github.com/dop251/goja v0.0.0-20210804101310-32956a348b49 github.com/go-ole/go-ole v1.2.4 // indirect @@ -22,6 +23,7 @@ require ( github.com/mssola/user_agent v0.5.2 github.com/shirou/gopsutil v3.21.5+incompatible github.com/tklauser/go-sysconf v0.3.6 // indirect + golang.org/x/image v0.0.0-20190802002840-cff245a6509b golang.org/x/net v0.0.0-20210614182718-04defd469f4e golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 golang.org/x/text v0.3.6 diff --git a/go.sum b/go.sum index d0dc2a8..800d4fb 100644 --- a/go.sum +++ b/go.sum @@ -15,6 +15,8 @@ github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/webp v1.1.0 h1:4Ei0/BRroMF9FaXDG2e4OxwFcuW2vcXd+A6tyqTJUQQ= +github.com/chai2010/webp v1.1.0/go.mod h1:LP12PG5IFmLGHUU26tBiCBKnghxx3toZFwDjOYvd3Ow= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= @@ -128,6 +130,7 @@ golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20200513190911-00229845015e/go.mod h1:4M0jN8W1tt0AVLNr8HDosyJCDCDuyL9N9+3m7wDWgKw= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= diff --git a/internal/nodes/http_request.go b/internal/nodes/http_request.go index 0b8b51f..29dece6 100644 --- a/internal/nodes/http_request.go +++ b/internal/nodes/http_request.go @@ -338,6 +338,11 @@ func (this *HTTPRequest) configureWeb(web *serverconfigs.HTTPWebConfig, isTop bo this.web.Compression = web.Compression } + // webp + if web.WebP != nil && (web.WebP.IsPrior || isTop) { + this.web.WebP = web.WebP + } + // cache if web.Cache != nil && (web.Cache.IsPrior || isTop) { this.web.Cache = web.Cache diff --git a/internal/nodes/http_writer.go b/internal/nodes/http_writer.go index 5dddcad..4f3659a 100644 --- a/internal/nodes/http_writer.go +++ b/internal/nodes/http_writer.go @@ -8,8 +8,16 @@ import ( "github.com/TeaOSLab/EdgeNode/internal/compressions" "github.com/TeaOSLab/EdgeNode/internal/remotelogs" "github.com/TeaOSLab/EdgeNode/internal/utils" + "github.com/chai2010/webp" "github.com/iwind/TeaGo/lists" "github.com/iwind/TeaGo/types" + _ "golang.org/x/image/bmp" + _ "golang.org/x/image/webp" + "image" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + "io" "net" "net/http" "path/filepath" @@ -21,6 +29,10 @@ type HTTPWriter struct { req *HTTPRequest writer http.ResponseWriter + webpIsEncoding bool + webpBuffer *bytes.Buffer + webpIsWriting bool + compressionConfig *serverconfigs.HTTPCompressionConfig compressionWriter compressions.Writer compressionType serverconfigs.HTTPCompressionType @@ -69,11 +81,20 @@ func (this *HTTPWriter) SetCompression(config *serverconfigs.HTTPCompressionConf } // Prepare 准备输出 +// 缓存不调用此函数 func (this *HTTPWriter) Prepare(size int64, status int) { this.statusCode = status - this.prepareCompression(size) + if status == http.StatusOK { + this.prepareWebP(size) + } + this.prepareCache(size) + + // 在WebP模式下,压缩暂不可用 + if !this.webpIsEncoding { + this.PrepareCompression(size) + } } // Raw 包装前的原始的Writer @@ -106,40 +127,46 @@ func (this *HTTPWriter) AddHeaders(header http.Header) { // Write 写入数据 func (this *HTTPWriter) Write(data []byte) (n int, err error) { - if this.writer != nil { - if this.compressionWriter != nil { - n, err = this.compressionWriter.Write(data) - } else { - n, err = this.writer.Write(data) - } - if n > 0 { - this.sentBodyBytes += int64(n) - } + n = len(data) - // 写入缓存 - if this.cacheWriter != nil { - _, err = this.cacheWriter.Write(data) - if err != nil { - _ = this.cacheWriter.Discard() - this.cacheWriter = nil - remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error()) - } - } - } else { - if n == 0 { - n = len(data) // 防止出现short write错误 - } - } - if this.bodyCopying { - if this.compressionBodyWriter != nil { - _, err := this.compressionBodyWriter.Write(data) - if err != nil { - remotelogs.Error("HTTP_WRITER", err.Error()) - } + if this.writer != nil { + if this.webpIsEncoding && !this.webpIsWriting { + this.webpBuffer.Write(data) } else { - this.body = append(this.body, data...) + // 写入压缩 + var n1 int + if this.compressionWriter != nil { + n1, err = this.compressionWriter.Write(data) + } else { + n1, err = this.writer.Write(data) + } + if n1 > 0 { + this.sentBodyBytes += int64(n1) + } + + // 写入缓存 + if this.cacheWriter != nil { + _, err = this.cacheWriter.Write(data) + if err != nil { + _ = this.cacheWriter.Discard() + this.cacheWriter = nil + remotelogs.Error("HTTP_WRITER", "write cache failed: "+err.Error()) + } + } + + if this.bodyCopying { + if this.compressionBodyWriter != nil { + _, err := this.compressionBodyWriter.Write(data) + if err != nil { + remotelogs.Error("HTTP_WRITER", err.Error()) + } + } else { + this.body = append(this.body, data...) + } + } } } + return } @@ -254,6 +281,40 @@ func (this *HTTPWriter) Close() { _ = this.cacheWriter.Discard() } } + + // webp writer + if this.webpIsEncoding { + imageData, _, err := image.Decode(this.webpBuffer) + if err != nil { + _, _ = io.Copy(this.writer, this.webpBuffer) + + // 处理缓存 + if this.cacheWriter != nil { + _ = this.cacheWriter.Discard() + } + this.cacheWriter = nil + } else { + var f = types.Float32(this.req.web.WebP.Quality) + if f > 100 { + f = 100 + } + this.webpIsWriting = true + err = webp.Encode(this, imageData, &webp.Options{ + Lossless: false, + Quality: f, + Exact: true, + }) + if err != nil { + remotelogs.Error("HTTP_WRITER", "encode webp failed: "+err.Error()) + + // 处理缓存 + if this.cacheWriter != nil { + _ = this.cacheWriter.Discard() + } + this.cacheWriter = nil + } + } + } } // Hijack Hijack @@ -273,8 +334,24 @@ func (this *HTTPWriter) Flush() { } } -// 准备压缩 -func (this *HTTPWriter) prepareCompression(size int64) { +// 准备Webp +func (this *HTTPWriter) prepareWebP(size int64) { + if this.req.web != nil && + this.req.web.WebP != nil && + this.req.web.WebP.IsOn && + this.req.web.WebP.MatchResponse(this.Header().Get("Content-Type"), size, filepath.Ext(this.req.requestPath()), this.req.Format) && + this.req.web.WebP.MatchAccept(this.req.requestHeader("Accept")) && + len(this.writer.Header().Get("Content-Encoding")) == 0 { + this.webpIsEncoding = true + this.webpBuffer = &bytes.Buffer{} + + this.Header().Del("Content-Length") + this.Header().Set("Content-Type", "image/webp") + } +} + +// PrepareCompression 准备压缩 +func (this *HTTPWriter) PrepareCompression(size int64) { if this.compressionConfig == nil || !this.compressionConfig.IsOn || this.compressionConfig.Level <= 0 { return } @@ -396,14 +473,6 @@ func (this *HTTPWriter) prepareCache(size int64) { return } this.cacheWriter = cacheWriter - if this.compressionWriter != nil { - cacheCompressionWriter, err := compressions.NewWriter(this.cacheWriter, this.compressionType, this.compressionWriter.Level()) - if err != nil { - remotelogs.Error("HTTP_WRITER", "create cache compression writer failed: "+err.Error()) - return - } - this.cacheWriter = caches.NewCompressionWriter(this.cacheWriter, cacheCompressionWriter, this.req.cacheKey, expiredAt) - } // 写入Header for k, v := range this.Header() { diff --git a/internal/utils/ticker_test.go b/internal/utils/ticker_test.go index b636e34..40dbbac 100644 --- a/internal/utils/ticker_test.go +++ b/internal/utils/ticker_test.go @@ -1,7 +1,6 @@ package utils import ( - "github.com/iwind/TeaGo/logs" "sync" "testing" "time" @@ -14,7 +13,7 @@ func TestTicker(t *testing.T) { ticker.Stop() }() for ticker.Next() { - logs.Println("tick") + t.Log("tick") } t.Log("finished") } @@ -26,10 +25,10 @@ func TestTicker2(t *testing.T) { ticker.Stop() }() for { - logs.Println("loop") + t.Log("loop") select { case <-ticker.C: - logs.Println("tick") + t.Log("tick") case <-ticker.S: return } @@ -42,7 +41,7 @@ func TestTickerEvery(t *testing.T) { wg.Add(1) Every(2*time.Second, func(ticker *Ticker) { i++ - logs.Println("TestTickerEvery i:", i) + t.Log("TestTickerEvery i:", i) if i >= 4 { ticker.Stop() wg.Done()