xfy f2352ab9cc docs(config,stream,logging,handler,proxy,cache,server,ssl,middleware): 为核心模块添加详细 GoDoc 文档注释
- config: 为 Config 和所有子配置结构添加完整文档,包含使用示例和注意事项
- stream: 为负载均衡器和服务器添加详细的参数、返回值和功能说明
- logging: 为日志格式化和输出函数添加文档,说明支持的变量替换
- handler: 为路由器、静态文件和 sendfile 处理器添加文档
- proxy: 为健康检查器和代理功能添加完整文档
- cache/server/ssl/middleware: 补充相关模块的文档注释
- config.example.yaml: 添加可信代理配置、加密套件示例,更新压缩级别说明

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-07 15:36:09 +08:00

309 lines
6.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package compression 提供 HTTP 响应压缩中间件,支持 gzip 和 brotli 算法。
//
// 该文件包含压缩相关的核心逻辑,包括:
// - gzip 压缩(兼容性好,所有浏览器支持)
// - brotli 压缩(压缩率更高,适合现代浏览器)
// - MIME 类型过滤
// - 最小压缩大小控制
//
// 主要用途:
//
// 用于压缩 HTTP 响应内容,减少传输数据量,提升页面加载速度。
//
// 注意事项:
// - 使用缓冲池复用压缩对象,减少内存分配
// - 小于 MinSize 的响应不压缩
//
// 作者xfy
package compression
import (
"bytes"
"strings"
"sync"
"github.com/andybalholm/brotli"
"github.com/klauspost/compress/gzip"
"github.com/valyala/fasthttp"
"rua.plus/lolly/internal/config"
)
// Algorithm 压缩算法类型。
type Algorithm int
const (
// AlgorithmGzip 使用 gzip 压缩。
AlgorithmGzip Algorithm = iota
// AlgorithmBrotli 使用 brotli 压缩。
AlgorithmBrotli
)
// CompressionMiddleware 响应压缩中间件。
type CompressionMiddleware struct {
// types 可压缩的 MIME 类型列表
types []string
// level 压缩级别1-9
level int
// minSize 最小压缩大小(字节)
minSize int
// algorithm 压缩算法
algorithm Algorithm
// gzipPool gzip.Writer 缓冲池
gzipPool sync.Pool
// brotliPool brotli.Writer 缓冲池
brotliPool sync.Pool
}
// New 创建压缩中间件。
//
// 参数:
// - cfg: 压缩配置,包含算法类型、压缩级别、最小压缩大小等
//
// 返回值:
// - *CompressionMiddleware: 压缩中间件实例
// - error: 配置无效时返回错误
func New(cfg *config.CompressionConfig) (*CompressionMiddleware, error) {
if cfg == nil {
cfg = &config.CompressionConfig{
Type: "gzip",
Level: 6,
MinSize: 1024,
Types: defaultCompressibleTypes(),
}
}
// 设置默认值
if cfg.Level == 0 {
cfg.Level = 6
}
if cfg.MinSize == 0 {
cfg.MinSize = 1024
}
if len(cfg.Types) == 0 {
cfg.Types = defaultCompressibleTypes()
}
// 解析算法类型
var algo Algorithm
switch strings.ToLower(cfg.Type) {
case "brotli":
algo = AlgorithmBrotli
case "gzip":
algo = AlgorithmGzip
case "both":
// both 模式优先使用 brotli如果客户端支持
algo = AlgorithmBrotli
default:
algo = AlgorithmGzip
}
m := &CompressionMiddleware{
types: cfg.Types,
level: cfg.Level,
minSize: cfg.MinSize,
algorithm: algo,
}
// 初始化缓冲池
m.gzipPool = sync.Pool{
New: func() interface{} {
w, _ := gzip.NewWriterLevel(nil, cfg.Level)
return w
},
}
// 初始化 brotli 缓冲池
m.brotliPool = sync.Pool{
New: func() interface{} {
return brotli.NewWriterOptions(nil, brotli.WriterOptions{
Quality: cfg.Level,
})
},
}
return m, nil
}
// defaultCompressibleTypes 返回默认可压缩的 MIME 类型。
func defaultCompressibleTypes() []string {
return []string{
"text/html",
"text/css",
"text/javascript",
"text/plain",
"text/xml",
"application/json",
"application/javascript",
"application/xml",
"application/xhtml+xml",
}
}
// Name 返回中间件名称。
func (m *CompressionMiddleware) Name() string {
return "compression"
}
// Process 应用压缩中间件。
//
// 参数:
// - next: 下一个请求处理器
//
// 返回值:
// - fasthttp.RequestHandler: 包装后的请求处理器
func (m *CompressionMiddleware) Process(next fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
// 检查客户端是否支持压缩
acceptEncoding := string(ctx.Request.Header.Peek("Accept-Encoding"))
// 根据算法和客户端支持选择压缩方式
var useGzip, useBrotli bool
switch m.algorithm {
case AlgorithmGzip:
useGzip = strings.Contains(acceptEncoding, "gzip")
case AlgorithmBrotli:
// brotli 或 both 模式
if strings.Contains(acceptEncoding, "br") {
useBrotli = true
} else if strings.Contains(acceptEncoding, "gzip") {
useGzip = true
}
}
// 如果不需要压缩,直接执行
if !useGzip && !useBrotli {
next(ctx)
return
}
// 执行处理器
next(ctx)
// 获取响应体
body := ctx.Response.Body()
bodyLen := len(body)
// 检查是否满足压缩条件
if bodyLen < m.minSize {
return // 不压缩
}
// 检查 MIME 类型
contentType := string(ctx.Response.Header.ContentType())
if !m.isCompressible(contentType) {
return // 不压缩此类型
}
// 执行压缩
var compressed []byte
var encoding string
if useBrotli {
compressed = m.compressBrotli(body)
encoding = "br"
} else if useGzip {
compressed = m.compressGzip(body)
encoding = "gzip"
}
if len(compressed) > 0 && len(compressed) < bodyLen {
ctx.Response.SetBody(compressed)
ctx.Response.Header.Set("Content-Encoding", encoding)
ctx.Response.Header.Del("Content-Length") // 让 fasthttp 自动计算
}
}
}
// isCompressible 检查 MIME 类型是否可压缩。
//
// 参数:
// - contentType: 内容类型MIME 类型)
//
// 返回值:
// - bool: 是否可压缩
func (m *CompressionMiddleware) isCompressible(contentType string) bool {
// 移除 charset 等参数
ct := contentType
if idx := strings.Index(ct, ";"); idx >= 0 {
ct = ct[:idx]
}
ct = strings.TrimSpace(strings.ToLower(ct))
for _, t := range m.types {
if strings.ToLower(t) == ct {
return true
}
// 支持通配符匹配
if strings.HasSuffix(t, "/*") {
base := strings.TrimSuffix(t, "/*")
if strings.HasPrefix(ct, base) {
return true
}
}
}
return false
}
// compressGzip 使用 gzip 压缩数据。
//
// 参数:
// - data: 待压缩的原始数据
//
// 返回值:
// - []byte: 压缩后的数据
func (m *CompressionMiddleware) compressGzip(data []byte) []byte {
w := m.gzipPool.Get().(*gzip.Writer)
defer m.gzipPool.Put(w)
var buf bytes.Buffer
w.Reset(&buf)
_, _ = w.Write(data)
_ = w.Close()
return buf.Bytes()
}
// compressBrotli 使用 brotli 压缩数据。
//
// 参数:
// - data: 待压缩的原始数据
//
// 返回值:
// - []byte: 压缩后的数据
func (m *CompressionMiddleware) compressBrotli(data []byte) []byte {
w := m.brotliPool.Get().(*brotli.Writer)
defer m.brotliPool.Put(w)
var buf bytes.Buffer
w.Reset(&buf)
_, _ = w.Write(data)
_ = w.Close()
return buf.Bytes()
}
// Types 返回可压缩的 MIME 类型列表。
//
// 返回值:
// - []string: 可压缩的 MIME 类型列表
func (m *CompressionMiddleware) Types() []string {
return m.types
}
// Level 返回压缩级别。
//
// 返回值:
// - int: 压缩级别1-9
func (m *CompressionMiddleware) Level() int {
return m.level
}
// MinSize 返回最小压缩大小。
//
// 返回值:
// - int: 最小压缩大小(字节)
func (m *CompressionMiddleware) MinSize() int {
return m.minSize
}