Gin静态文件服务与缓存
静态文件服务是Web应用的重要组成部分,合理配置可显著提升性能。
Gin静态文件服务
基本配置
Go
r := gin.New()
// 静态目录映射
r.Static("/assets", "./assets")
// 单文件映射
r.StaticFile("/favicon.ico", "./favicon.ico")
// 静态文件组
r.StaticFS("/static", gin.Dir("./static", false))
Static方法实现
Go
func (group *RouterGroup) Static(relativePath, root string) IRoutes {
return group.StaticFS(relativePath, Dir(root, false))
}
func (group *RouterGroup) StaticFS(relativePath string, fs http.FileSystem) IRoutes {
fileServer := http.FileServer(fs)
group.GET(relativePath, func(c *Context) {
c.FileFromFS(relativePath, fs)
})
group.GET(relativePath+"/*filepath", func(c *Context) {
c.FileFromFS(c.Param("filepath"), fs)
})
return group.returnObj()
}
性能优化配置
禁用目录列表
Go
// 安全:禁用目录列表
r.StaticFS("/static", gin.Dir("./static", false))
// 不安全:启用目录列表
r.StaticFS("/static", gin.Dir("./static", true))
gzip压缩
Go
// 内置gzip中间件
r.Use(gin.Gzip(gin.DefaultCompression))
// 自定义压缩配置
r.Use(gin.Gzip(gin.BestCompression, gzip.WithExcludedPaths([]string{
"/api/", // API路径不压缩
})))
预压缩文件
Go
func PrecompressedStaticMiddleware(root string) gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
// 检查预压缩文件
gzPath := root + path + ".gz"
if _, err := os.Stat(gzPath); err == nil {
c.Header("Content-Encoding", "gzip")
c.File(gzPath)
c.Abort()
return
}
c.File(root + path)
}
}
缓存策略
HTTP缓存头
Go
func CacheMiddleware(maxAge int) gin.HandlerFunc {
return func(c *gin.Context) {
c.Header("Cache-Control", fmt.Sprintf("public, max-age=%d", maxAge))
// ETag基于文件内容
if c.Request.URL.Path != "" {
file := "./static" + c.Request.URL.Path
if info, err := os.Stat(file); err == nil {
etag := fmt.Sprintf(`"%x-%x"`, info.ModTime().Unix(), info.Size())
c.Header("ETag", etag)
if c.GetHeader("If-None-Match") == etag {
c.Status(304)
c.Abort()
return
}
}
}
c.Next()
}
}
// 使用:静态资源缓存1天
static := r.Group("/static")
static.Use(CacheMiddleware(86400))
static.Static("/", "./static")
Last-Modified检查
Go
func LastModifiedMiddleware(root string) gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
file := root + path
info, err := os.Stat(file)
if err != nil {
c.Next()
return
}
modTime := info.ModTime()
c.Header("Last-Modified", modTime.UTC().Format(http.TimeFormat))
// 检查客户端缓存
if since := c.GetHeader("If-Modified-Since"); since != "" {
if t, err := time.Parse(http.TimeFormat, since); err == nil {
if t.Unix() >= modTime.Unix() {
c.Status(304)
c.Abort()
return
}
}
}
c.Next()
}
}
内存缓存
小文件内存缓存
Go
type FileCache struct {
files sync.Map
}
func (fc *FileCache) Get(path string) ([]byte, bool) {
val, ok := fc.files.Load(path)
if !ok {
return nil, false
}
return val.([]byte), true
}
func (fc *FileCache) Set(path string, content []byte) {
fc.files.Store(path, content)
}
func CachedStaticMiddleware(root string, maxSize int64) gin.HandlerFunc {
cache := &FileCache{}
return func(c *gin.Context) {
path := c.Request.URL.Path
// 检查内存缓存
if content, ok := cache.Get(path); ok {
c.Data(200, getContentType(path), content)
c.Abort()
return
}
// 读取文件
file := root + path
content, err := os.ReadFile(file)
if err != nil {
c.Next()
return
}
// 小文件缓存到内存
if len(content) < int(maxSize) {
cache.Set(path, content)
}
c.Data(200, getContentType(path), content)
c.Abort()
}
}
CDN集成
CDN缓存头
Go
func CDNCacheMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// CDN缓存控制
c.Header("Cache-Control", "public, max-age=31536000") // 1年
c.Header("CDN-Cache-Control", "public, max-age=31536000")
// Surrogate-Key用于批量清除
c.Header("Surrogate-Key", "static-assets")
c.Next()
}
}
版本化静态资源
Go
// 使用版本号或hash
r.Static("/static/v1.0.0", "./dist")
// 或使用内容hash
func HashedStaticMiddleware(root string) gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
// 移除hash前缀
cleanPath := stripHashPrefix(path)
c.File(root + cleanPath)
}
}
// 路径示例:/static/app.a1b2c3.js → /static/app.js
安全配置
防止路径遍历
Go
func SafeStaticMiddleware(root string) gin.HandlerFunc {
return func(c *gin.Context) {
path := c.Request.URL.Path
// 安全检查
if strings.Contains(path, "..") {
c.AbortWithStatus(403)
return
}
cleanPath := filepath.Clean(root + path)
if !strings.HasPrefix(cleanPath, root) {
c.AbortWithStatus(403)
return
}
c.File(cleanPath)
}
}
限制文件类型
Go
func FileTypeMiddleware(allowed []string) gin.HandlerFunc {
return func(c *gin.Context) {
ext := filepath.Ext(c.Request.URL.Path)
for _, a := range allowed {
if ext == a {
c.Next()
return
}
}
c.AbortWithStatus(403)
}
}
// 仅允许特定类型
r.Use(FileTypeMiddleware([]string{".css", ".js", ".png", ".jpg", ".gif"}))
注意:生产环境建议使用CDN和反向代理缓存静态资源。
要点总结
- Static映射目录,StaticFile映射单文件
- 禁用目录列表提升安全性
- gzip压缩减少传输大小
- Cache-Control控制客户端缓存时间
- ETag和Last-Modified实现304响应
- CDN缓存适合长期不变的静态资源
📝 发现内容有误?点击此处直接编辑