vault backup: 2026-05-15 16:26:14
This commit is contained in:
+17
-11
@@ -7,7 +7,7 @@ create time: 2026-04-28 00:00
|
||||
|
||||
## 概述
|
||||
|
||||
Gin 提供了三种方式提供静态资源:`Static`(目录映射)、`StaticFS`(自定义文件系统)和 `StaticFile`(单文件)。理解它们的区别和使用场景,是构建完整 Web 服务的基础。
|
||||
Gin 提供了三种核心 API 提供静态资源:`Static`(目录映射)、`StaticFS`(自定义文件系统)和 `StaticFile`(单文件)。此外,通过 `io.Reader` 直接返回文件流以及自定义中间件也是实际项目中常见的手段。理解它们的区别和使用场景,是构建完整 Web 服务的基础。
|
||||
|
||||
思考题:为什么生产环境通常不推荐用 Go 直接提供静态文件?Nginx/CDN 相比有什么优势?
|
||||
|
||||
@@ -94,9 +94,9 @@ r.StaticFile("/robots.txt", "./static/robots.txt")
|
||||
r.Run(":8080")
|
||||
```
|
||||
|
||||
### 4. `io.Reader` 直接返回文件流
|
||||
### 4. 从内存直接返回文件
|
||||
|
||||
对于不需要落盘的文件(如数据库读取的图片、动态生成的 PDF),可以直接从 Reader 输出:
|
||||
对于不需要落盘的文件(如数据库读取的图片、动态生成的 PDF),可以直接从内存输出到响应体:
|
||||
|
||||
```go
|
||||
func getFileFromDB(c *gin.Context) {
|
||||
@@ -107,14 +107,14 @@ func getFileFromDB(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
// 直接用 io.Reader 写入响应
|
||||
// DataBytes 直接写入 body,比 SetHeader + Write 更简洁
|
||||
c.DataBytes(http.StatusOK, contentType, fileData)
|
||||
// 或者
|
||||
// c.Writer.Header().Set("Content-Type", contentType)
|
||||
// c.Writer.Write(fileData)
|
||||
}
|
||||
```
|
||||
|
||||
> [!tip] Reader vs []byte
|
||||
> Gin 没有提供专门的 `Reader` 接口写入流式数据。大文件场景建议配合 `c.File()`(内部使用 `sendfile`)或手动实现分块写入,避免一次性加载整个文件到内存。
|
||||
|
||||
### 5. 自定义文件服务器
|
||||
|
||||
如果需要控制缓存头、限速、访问权限等,可以自己实现 `StaticFS`:
|
||||
@@ -124,19 +124,22 @@ func secureStatic() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
path := c.Request.URL.Path
|
||||
|
||||
// 安全检查:防止目录遍历攻击
|
||||
// 安全检查:确保请求路径在允许的目录范围内
|
||||
// filepath.Clean 会解析 "..",防止目录遍历攻击
|
||||
cleanPath := filepath.Clean(path)
|
||||
if strings.Contains(cleanPath, "..") {
|
||||
baseDir, _ := filepath.Abs("./static") // 允许的基础目录
|
||||
targetPath, _ := filepath.Abs(filepath.Join(baseDir, cleanPath))
|
||||
if !strings.HasPrefix(targetPath, baseDir+"/") && targetPath != baseDir {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
// 设置缓存头
|
||||
// 设置缓存和安全头
|
||||
c.Header("Cache-Control", "public, max-age=31536000") // 一年
|
||||
c.Header("X-Content-Type-Options", "nosniff")
|
||||
|
||||
// 交给内置文件服务器
|
||||
http.FileServer(http.Dir("./static")).ServeHTTP(c.Writer, c.Request)
|
||||
http.FileServer(http.Dir(baseDir)).ServeHTTP(c.Writer, c.Request)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,6 +151,9 @@ func main() {
|
||||
}
|
||||
```
|
||||
|
||||
> [!warning] 常见错误
|
||||
> 不要这样写:`strings.Contains(path, "..")`。因为 `filepath.Clean()` 已经解析了 `..`,清理后的路径中不会出现 `..`。正确做法是比较**解析后**的绝对路径是否仍在允许的基目录内。
|
||||
|
||||
### 6. 静态文件 vs API 性能对比
|
||||
|
||||
```mermaid
|
||||
|
||||
@@ -157,7 +157,7 @@ func main() {
|
||||
|
||||
> **安全警告:** 如果不设置可信代理,攻击者可以伪造 `X-Forwarded-For` 头注入任意 IP,绕过 IP 白名单限流。务必只信任你知道的代理 IP 段。
|
||||
|
||||
### 5. 完整的生產環境啟動範例
|
||||
### 5. 完整的生产环境启动规范
|
||||
|
||||
```go
|
||||
func main() {
|
||||
|
||||
Reference in New Issue
Block a user