Gin源码分析 – 中间件 – Gzip

1 介绍

本文介绍中间件Gzip的使用以及源码实现。

2 使用

首先下载该中间件

go get github.com/gin-contrib/gzip

编写如下的代码

package main

import (
	"net/http"

	"github.com/gin-contrib/gzip"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.New()

	r.Use(gzip.Gzip(gzip.DefaultCompression))

	r.GET("/ping", func(c *gin.Context) {
		c.String(http.StatusOK, "pong "+fmt.Sprint(time.Now().Unix()))
	})
         
        r.Run(":8080")
}

使用HTTPie进行测试结果如下,能够看到在响应的头中,Content-Encoding:gzip

(python_study) D:\Python\Envs\python_study>http -v 127.0.0.1:8080/ping
GET /ping HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: 127.0.0.1:8080
User-Agent: HTTPie/3.1.0

HTTP/1.1 200 OK
Content-Encoding: gzip
Content-Length: 39
Content-Type: text/plain; charset=utf-8
Date: Tue, 12 Apr 2022 12:39:01 GMT
Vary: Accept-Encoding

pong 1649767141

3 代码分析

3.1 入口函数

func Gzip(level int, options ...Option) gin.HandlerFunc {
 return newGzipHandler(level, options...).Handle
}

这个函数传入两个参数

(1)level,表示压缩等级,有如下几种形式

const (
	BestCompression    = gzip.BestCompression
	BestSpeed          = gzip.BestSpeed
	DefaultCompression = gzip.DefaultCompression
	NoCompression      = gzip.NoCompression
)

(2)options,表示一些选项,有如下几种设置选项,每一种选项实际都是一个选项设置函数

type Options struct {
	ExcludedExtensions   ExcludedExtensions
	ExcludedPaths        ExcludedPaths
	ExcludedPathesRegexs ExcludedPathesRegexs
	DecompressFn         func(c *gin.Context)
}

3.2 选项函数

相关数据结构如下:

var (
	DefaultExcludedExtentions = NewExcludedExtensions([]string{
		".png", ".gif", ".jpeg", ".jpg",
	})
	DefaultOptions = &Options{
		ExcludedExtensions: DefaultExcludedExtentions,
	}
)

type Options struct {
	ExcludedExtensions   ExcludedExtensions
	ExcludedPaths        ExcludedPaths
	ExcludedPathesRegexs ExcludedPathesRegexs
	DecompressFn         func(c *gin.Context)
}

// Using map for better lookup performance
type ExcludedExtensions map[string]bool

type ExcludedPaths []string

type ExcludedPathesRegexs []*regexp.Regexp

type Option func(*Options)
  • ExcludedExtensions ,管理不需要进行压缩的文件类型的后缀;
  • ExcludedPaths,管理不需要进行压缩的URL路径;
  • ExcludedPathesRegecs,管理不需要进行压缩的URL路径的正则表达式;
  • Option是一个用于设置选项的函数。

(1)WithExcludedExtension,定义不需要进行压缩的文件类型,下面的代码表示当服务器返回pdf, mp4两种格式的文件时,不会进行gzip压缩。

gzip.WithExcludedExtensions([]string{".pdf",".mp4"}))

代码实现如下所示。

func WithExcludedExtensions(args []string) Option {
 return func(o *Options) {
        o.ExcludedExtensions = NewExcludedExtensions(args)
    }
}

// Using map for better lookup performance
type ExcludedExtensions map[string]bool

func NewExcludedExtensions(extensions []string) ExcludedExtensions {
	res := make(ExcludedExtensions)
	for _, e := range extensions {
		res[e] = true
	}
	return res
}

func (e ExcludedExtensions) Contains(target string) bool {
	_, ok := e[target]
	return ok
}
  • 这个函数实际上返回一个Option的函数NewExcludedExtensions,该函数完成对Options的具体设置;
  • NewExcludedExtensions,创建一个管理文件后缀的map;
  • 提供一个Contains函数,判读是否包含某一个文件类型。

(2)WithExcludedPaths,定义不需要进行压缩的请求路径,下面的代码表示针对/api/的HTTP请求,不会进行gzip压缩。

gzip.WithExcludedPaths([]string{"/api/"})

代码实现如下所示。

func WithExcludedPaths(args []string) Option {
	return func(o *Options) {
		o.ExcludedPaths = NewExcludedPaths(args)
	}
}

type ExcludedPaths []string

func NewExcludedPaths(paths []string) ExcludedPaths {
	return ExcludedPaths(paths)
}

func (e ExcludedPaths) Contains(requestURI string) bool {
	for _, path := range e {
		if strings.HasPrefix(requestURI, path) {
			return true
		}
	}
	return false
}
  • WithExcludedPaths返回一个Option函数NewExcludedPaths,
  • NewExcludedPaths返回一个ExcludedPaths类型的字符串数组;
  • ExcludedPaths提供了一个Contains函数,判读是否包含某一个文件类型。

(3)WithExcludedPathsRegexs,定义不需要进行压缩的请求路径,采用的是正则表达式的方式,下面给出了一个例子

gzip.WithExcludedPathsRegexs([]string{".*"})

代码实现如下所示。

func WithExcludedPathsRegexs(args []string) Option {
	return func(o *Options) {
		o.ExcludedPathesRegexs = NewExcludedPathesRegexs(args)
	}
}

type ExcludedPathesRegexs []*regexp.Regexp

func NewExcludedPathesRegexs(regexs []string) ExcludedPathesRegexs {
    result := make([]*regexp.Regexp, len(regexs))
    for i, reg := range regexs {
        result[i] = regexp.MustCompile(reg)
    }
    return result
}

func (e ExcludedPathesRegexs) Contains(requestURI string) bool {
 for _, reg := range e {
    if reg.MatchString(requestURI) {
        return true
    }
 }
 return false
}
  • WithExcludedPathsRegexs返回一个Option函数NewExcludedPathesRegexs,
  • NewExcludedPathesRegexs,调用regexp.MustCompile进行编译,然后返回一个ExcludedPathesRegexs 类型的数组;
  • ExcludedPathesRegexs 提供了一个Contains函数,判读是否包含该URL,调用reg.MatchString进行判断。

(4)WithDecompressFn,自定义压缩函数

func WithDecompressFn(decompressFn func(c *gin.Context)) Option {
 return func(o *Options) {
        o.DecompressFn = decompressFn
    }
}

下面给出了默认的压缩函数的代码

func DefaultDecompressHandle(c *gin.Context) {
	if c.Request.Body == nil {
		return
	}
	r, err := gzip.NewReader(c.Request.Body)
	if err != nil {
		_ = c.AbortWithError(http.StatusBadRequest, err)
		return
	}
	c.Request.Header.Del("Content-Encoding")
	c.Request.Header.Del("Content-Length")
	c.Request.Body = r
}