静态资源也是 Web 服务的一部分
写 Go Web 服务时,你可能需要提供 CSS、图片、下载文件、HTML 模板和前端打包结果。现在很多人会想到 embed,但在 2020 年 4 月的 Go 语境里,embed 还不是标准库能力。那时更常见的做法是把静态资源作为目录随程序一起部署,用 http.FileServer 提供访问。
理解这种方式仍然有价值。即使后来使用 embed,你也需要知道资源路径、URL 前缀、缓存头和部署目录之间的关系。很多静态文件问题不是 Go 语法问题,而是运行时工作目录和路径没想清楚。
这篇文章用一个小站点做例子,讲如何组织 static/ 和 templates/,如何提供静态文件,如何避免路径错乱。
一个简单目录结构
site/
├── go.mod
├── main.go
├── static/
│ ├── css/
│ │ └── app.css
│ └── images/
│ └── logo.png
└── templates/
└── index.html
模板里引用:
<link rel="stylesheet" href="/static/css/app.css">
<img src="/static/images/logo.png" alt="Logo">
Go 服务提供静态目录:
mux := http.NewServeMux()
fileServer := http.FileServer(http.Dir("static"))
mux.Handle("/static/", http.StripPrefix("/static/", fileServer))
http.Dir("static") 指向本地目录,/static/ 是 URL 前缀。http.StripPrefix 会把请求路径里的 /static/ 去掉,再去目录中找文件。访问 /static/css/app.css 时,实际读取 static/css/app.css。
渲染模板
解析模板:
tmpl, err := template.ParseFiles("templates/index.html")
if err != nil {
log.Fatal(err)
}
Handler:
func indexHandler(tmpl *template.Template) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
data := struct {
Title string
}{
Title: "Go 静态文件入门",
}
if err := tmpl.Execute(w, data); err != nil {
log.Printf("render index: %v", err)
http.Error(w, "render error", http.StatusInternalServerError)
}
}
}
注册:
mux.HandleFunc("/", indexHandler(tmpl))
这样页面和静态资源都由同一个 Go 服务提供。
工作目录问题
相对路径 static 和 templates/index.html 都依赖当前工作目录。如果你在项目根目录运行:
go run .
没有问题。但如果你从别的目录运行二进制:
/opt/site/app
程序当前工作目录可能不是 /opt/site,就找不到资源。
一种简单做法是通过参数传资源目录:
staticDir := flag.String("static", "static", "static files directory")
templateFile := flag.String("template", "templates/index.html", "index template")
flag.Parse()
部署时显式指定:
./app -static /opt/site/static -template /opt/site/templates/index.html
配置路径比在代码里猜路径更可靠。不要依赖“程序一定从某个目录启动”这种隐含条件。
给静态文件加缓存头
可以包装 file server:
func cacheStatic(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Cache-Control", "public, max-age=3600")
next.ServeHTTP(w, r)
})
}
使用:
mux.Handle("/static/", http.StripPrefix("/static/", cacheStatic(fileServer)))
如果文件名带 hash,比如 app.9f3a.css,可以设置更长缓存。普通固定文件名不要缓存太久,否则更新后用户可能还看到旧资源。
小结
在没有 embed 的 Go 版本里,静态资源通常作为目录随程序部署,通过 http.FileServer 提供服务。核心要点是:URL 前缀和本地目录要对应,StripPrefix 要用对,模板引用路径要稳定,运行时工作目录不要靠猜,最好通过配置指定资源目录。
静态文件服务看起来简单,但部署时很容易出错。把路径、缓存和资源目录想清楚,小型 Go Web 服务就能稳定提供页面、CSS 和图片。等你以后使用 embed,也会更理解它解决的是“资源随二进制打包”和“路径部署”这类问题。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。