使用Golang构建单页应用(SPA)服务的最佳实践
1 回复
更多关于使用Golang构建单页应用(SPA)服务的最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在Go中为单页应用(SPA)提供静态文件服务时,需要特别注意路由处理。以下是几种最佳实践方案:
1. 基础SPA服务实现
package main
import (
"net/http"
"os"
"path/filepath"
)
func spaHandler(publicDir string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
// 尝试提供请求的文件
path := filepath.Join(publicDir, r.URL.Path)
_, err := os.Stat(path)
if os.IsNotExist(err) {
// 文件不存在,回退到index.html
http.ServeFile(w, r, filepath.Join(publicDir, "index.html"))
return
}
http.ServeFile(w, r, path)
}
}
func main() {
fs := http.FileServer(http.Dir("./dist"))
http.Handle("/", spaHandler("./dist"))
http.ListenAndServe(":8080", nil)
}
2. 使用标准库的优雅方案
package main
import (
"net/http"
"strings"
)
type spaHandler struct {
staticPath string
indexPath string
}
func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path := h.staticPath + r.URL.Path
// 检查文件是否存在
if !strings.Contains(r.URL.Path, ".") {
// 可能是前端路由,返回index.html
http.ServeFile(w, r, h.staticPath+h.indexPath)
return
}
// 尝试提供静态文件
http.ServeFile(w, r, path)
}
func main() {
spa := spaHandler{
staticPath: "./dist",
indexPath: "index.html",
}
http.Handle("/", spa)
http.ListenAndServe(":8080", nil)
}
3. 支持API路由的完整示例
package main
import (
"net/http"
"path/filepath"
"strings"
)
func main() {
// API路由
http.HandleFunc("/api/data", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`{"message": "API response"}`))
})
// SPA静态文件服务
fs := http.FileServer(http.Dir("./dist"))
http.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 如果是API请求,不处理
if strings.HasPrefix(r.URL.Path, "/api/") {
return
}
// 检查文件是否存在
filePath := filepath.Join("./dist", r.URL.Path)
if _, err := http.Dir("./dist").Open(r.URL.Path); err != nil {
// 文件不存在,返回index.html
http.ServeFile(w, r, "./dist/index.html")
return
}
fs.ServeHTTP(w, r)
}))
http.ListenAndServe(":8080", nil)
}
4. 使用gorilla/mux的增强方案
package main
import (
"net/http"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
// API路由
api := r.PathPrefix("/api").Subrouter()
api.HandleFunc("/users", getUsers).Methods("GET")
api.HandleFunc("/users", createUser).Methods("POST")
// SPA静态文件
spa := spaHandler{staticPath: "dist", indexPath: "index.html"}
r.PathPrefix("/").Handler(spa)
http.ListenAndServe(":8080", r)
}
func getUsers(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`[{"id": 1, "name": "John"}]`))
}
func createUser(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusCreated)
}
5. 带缓存的性能优化版本
package main
import (
"net/http"
"time"
)
func cacheControlMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 对静态资源设置缓存
if r.URL.Path == "/" || !containsDot(r.URL.Path) {
w.Header().Set("Cache-Control", "no-cache")
} else {
w.Header().Set("Cache-Control", "public, max-age=31536000")
}
next.ServeHTTP(w, r)
})
}
func containsDot(path string) bool {
for i := 0; i < len(path); i++ {
if path[i] == '.' && i > 0 {
return true
}
}
return false
}
func main() {
fs := http.FileServer(http.Dir("./dist"))
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 处理前端路由
if !containsDot(r.URL.Path) && r.URL.Path != "/" {
http.ServeFile(w, r, "./dist/index.html")
return
}
fs.ServeHTTP(w, r)
})
http.Handle("/", cacheControlMiddleware(handler))
srv := &http.Server{
Addr: ":8080",
Handler: nil,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
srv.ListenAndServe()
}
这些实现方案都确保了:
- 静态文件正确服务
- 前端路由回退到index.html
- API路由与静态文件路由分离
- 性能优化(缓存控制)
- 支持HTML5 History模式的路由
选择哪种方案取决于具体需求,基础项目可以使用标准库方案,复杂项目推荐使用gorilla/mux或chi等路由库。


