
本文介绍了如何使用 go 语言的 net/http 包中的 FileServer 函数来安全地提供静态文件,特别是如何在只允许客户端访问特定文件(例如 index.html)的同时,保护其他文件(例如 javaScript 文件)不被直接访问。文章提供了两种实现方法:使用中间件包装 FileServer 和自定义 http.Filesystem 接口。
在使用 Go 构建 Web 应用时,经常需要提供静态文件,例如 html、css、javascript 和图片。net/http 包中的 FileServer 函数提供了一种简单的方式来实现这个功能。然而,默认情况下,FileServer 会暴露指定目录下的所有文件,这可能带来安全风险。例如,你可能希望只允许用户访问 index.html,而阻止直接访问 JavaScript 文件。本文将介绍两种方法来解决这个问题。
方法一:使用中间件过滤文件
第一种方法是使用中间件来包装 FileServer,通过中间件来过滤客户端的请求,只允许访问特定类型的文件。下面是一个示例代码:
package main import ( "log" "net/http" "path/filepath" ) // GlobFilterHandler 是一个中间件,用于过滤文件,只允许访问符合指定模式的文件。 func GlobFilterHandler(h http.Handler, pattern string) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { path := r.URL.Path fileName := filepath.Base(path) ok, err := filepath.Match(pattern, fileName) if err != nil { log.Println("Error in pattern match:", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) // 更加友好的错误提示 return } if !ok { http.NotFound(w, r) return } h.ServeHTTP(w, r) }) } func main() { // 假设静态文件位于 /tmp/dtest 目录 fileHandler := http.FileServer(http.Dir("/tmp/dtest")) // 只允许访问 *.js 文件 wrappedHandler := GlobFilterHandler(fileHandler, "*.js") // 将处理程序注册到服务器 http.Handle("/", wrappedHandler) // 注意这里需要指定路由 // 启动服务器 log.Fatal(http.ListenAndServe(":8080", nil)) }
代码解释:
- GlobFilterHandler 函数接收一个 http.Handler 和一个模式字符串作为参数,返回一个新的 http.Handler。
- 新的 http.Handler 会检查请求的 URL 路径,提取文件名,并使用 filepath.Match 函数来判断文件名是否符合指定的模式。
- 如果文件名不符合模式,或者匹配过程中发生错误,则返回 404 Not Found 错误。
- 如果文件名符合模式,则调用原始的 http.Handler 来处理请求。
使用方法:
- 将上述代码保存为 main.go 文件。
- 创建一个名为 /tmp/dtest 的目录,并在该目录下创建一些文件,例如 index.html 和 script.js。
- 运行 go run main.go 命令启动服务器。
- 在浏览器中访问 http://localhost:8080/script.js 可以正常访问,访问 http://localhost:8080/index.html 会返回 404 Not Found 错误。
注意事项:
- 这个示例只允许访问 .js 文件。你可以根据需要修改模式字符串来允许访问其他类型的文件。
- 需要注意路由的设置,确保中间件能够正确地拦截请求。
方法二:自定义 http.FileSystem 接口
第二种方法是自定义 http.FileSystem 接口,并实现自己的文件访问控制逻辑。下面是一个示例代码:
package main import ( "fmt" "log" "net/http" "path/filepath" ) // GlobDir 是一个自定义的 http.FileSystem 实现,用于过滤文件,只允许访问符合指定模式的文件。 type GlobDir struct { Dir http.Dir Pattern string } // Open 方法实现了 http.FileSystem 接口,用于打开文件。 func (d GlobDir) Open(name string) (http.File, error) { baseName := filepath.Base(name) ok, err := filepath.Match(d.Pattern, baseName) if err != nil { return nil, err } if !ok { return nil, fmt.Errorf("%s not match GlobDir pattern.", baseName) } return d.Dir.Open(name) } func main() { // 假设静态文件位于 /tmp/dtest 目录 fileHandler := http.FileServer(GlobDir{ Dir: http.Dir("/tmp/dtest"), Pattern: "*.js", }) // 将处理程序注册到服务器 http.Handle("/", fileHandler) // 注意这里需要指定路由 // 启动服务器 log.Fatal(http.ListenAndServe(":8080", nil)) }
代码解释:
- GlobDir 结构体实现了 http.FileSystem 接口,包含一个 http.Dir 类型的成员和一个模式字符串。
- Open 方法是 http.FileSystem 接口的必须实现的方法,用于打开文件。
- Open 方法首先提取文件名,并使用 filepath.Match 函数来判断文件名是否符合指定的模式。
- 如果文件名不符合模式,则返回一个错误。
- 如果文件名符合模式,则调用原始的 http.Dir 的 Open 方法来打开文件。
使用方法:
- 将上述代码保存为 main.go 文件。
- 创建一个名为 /tmp/dtest 的目录,并在该目录下创建一些文件,例如 index.html 和 script.js。
- 运行 go run main.go 命令启动服务器。
- 在浏览器中访问 http://localhost:8080/script.js 可以正常访问,访问 http://localhost:8080/index.html 会返回一个错误。
注意事项:
- 这个示例只允许访问 .js 文件。你可以根据需要修改模式字符串来允许访问其他类型的文件。
- 自定义 http.FileSystem 接口可以提供更灵活的文件访问控制逻辑。
总结
本文介绍了两种使用 Go 的 FileServer 安全地提供静态文件的方法:使用中间件过滤文件和自定义 http.FileSystem 接口。你可以根据实际需求选择合适的方法。使用中间件的方法更加简单,但灵活性稍差。自定义 http.FileSystem 接口的方法更加灵活,但实现起来稍微复杂一些。无论选择哪种方法,都需要注意文件访问控制,确保Web应用的安全。


