Go 静态文件服务入门:在没有 embed 之前如何组织资源

本文讲解 Go 早期项目中如何使用 http.FileServer、目录资源、模板文件和运行路径处理静态文件,为理解后续 embed 思路打基础。

静态资源也是 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 服务提供。

工作目录问题

相对路径 statictemplates/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,也会更理解它解决的是“资源随二进制打包”和“路径部署”这类问题。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页