静态文件服务

Web 应用少不了静态资源:CSS、JavaScript、图片、字体等。Gin 提供了几种方式来服务静态文件。

基本静态文件服务

最简单的方式是用 Static 方法:

package main

import (
    "github.com/gin-gonic/gin"
)

func main() {
    r := gin.Default()
    
    r.Static("/assets", "./static")
    
    r.Run(":8080")
}

这样配置后,访问 /assets/css/style.css 就会返回 ./static/css/style.css 文件。

单个静态文件

如果只需要服务单个文件,比如 favicon:

r.StaticFile("/favicon.ico", "./static/favicon.ico")

使用静态文件系统

StaticFS 方法可以更灵活地控制静态文件服务:

r.StaticFS("/static", gin.Dir("./static", true))

第二个参数 true 表示列出目录内容,生产环境建议设为 false

也可以使用 Go 标准库的 http.Dir

import "net/http"

r.StaticFS("/static", http.Dir("./static"))

嵌入静态文件

Go 1.16 引入了 embed 包,可以把静态文件嵌入到二进制文件中:

package main

import (
    "embed"
    "net/http"
    
    "github.com/gin-gonic/gin"
)

//go:embed static/*
var staticFS embed.FS

func main() {
    r := gin.Default()
    
    r.StaticFS("/static", http.FS(staticFS))
    
    r.Run(":8080")
}

这样部署时就不需要带着 static 目录了,所有文件都编译进了二进制。

静态文件目录结构

一个典型的静态文件目录:

static/
├── css/
│   ├── style.css
│   └── admin.css
├── js/
│   ├── main.js
│   └── vendor.js
├── images/
│   ├── logo.png
│   └── icons/
│       └── home.svg
└── fonts/
    └── custom.woff2

配合 HTML 模板

静态文件和 HTML 模板配合使用:

r.LoadHTMLGlob("templates/*")
r.Static("/static", "./static")

r.GET("/", func(c *gin.Context) {
    c.HTML(http.StatusOK, "index.html", gin.H{
        "title": "首页",
    })
})

模板中引用静态资源:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
    <img src="/static/images/logo.png" alt="Logo">
    <script src="/static/js/main.js"></script>
</body>
</html>

设置缓存头

静态资源通常需要设置缓存策略:

r.Use(func(c *gin.Context) {
    if len(c.Request.URL.Path) >= 7 && c.Request.URL.Path[:7] == "/static" {
        c.Header("Cache-Control", "public, max-age=31536000")
    }
    c.Next()
})

r.Static("/static", "./static")

或者使用中间件:

func CacheMiddleware(duration time.Duration) gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Header("Cache-Control", fmt.Sprintf("public, max-age=%d", int(duration.Seconds())))
        c.Next()
    }
}

r.Use(CacheMiddleware(24 * time.Hour))
r.Static("/static", "./static")

防止目录遍历

默认情况下 Gin 会安全处理路径,但最好还是检查一下:

r.NoRoute(func(c *gin.Context) {
    c.String(http.StatusNotFound, "页面不存在")
})

虚拟文件系统

如果需要从数据库或其他来源提供静态文件:

type VirtualFS struct {
    files map[string][]byte
}

func (v *VirtualFS) Open(name string) (http.File, error) {
    content, ok := v.files[name]
    if !ok {
        return nil, os.ErrNotExist
    }
    return &VirtualFile{content: content, name: name}, nil
}

r.StaticFS("/virtual", &VirtualFS{
    files: map[string][]byte{
        "hello.txt": []byte("Hello from virtual FS"),
    },
})

小结

Gin 的静态文件服务简单易用,Static 方法能满足大部分需求。如果需要嵌入静态文件到二进制,使用 Go 的 embed 包很方便。记得给静态资源设置合适的缓存策略,可以显著提升性能。