package handlers import ( "net/http" "os" "path/filepath" "strings" ) type StaticHandler struct { staticDir string } func NewStaticHandler(staticDir string) *StaticHandler { return &StaticHandler{ staticDir: staticDir, } } // ServeStatic 处理静态文件请求,实现类似 Nginx try_files 的逻辑 func (s *StaticHandler) ServeStatic(w http.ResponseWriter, r *http.Request) { path := r.URL.Path // 实现 try_files $uri $uri/ @router 逻辑 filePath := s.tryFiles(path) // 设置正确的 Content-Type s.setContentType(w, filePath) // 服务文件 http.ServeFile(w, r, filePath) } // tryFiles 实现类似 Nginx try_files 的逻辑 func (s *StaticHandler) tryFiles(requestPath string) string { // 清理路径 if requestPath == "" { requestPath = "/" } // 移除查询参数 if idx := strings.Index(requestPath, "?"); idx != -1 { requestPath = requestPath[:idx] } // 1. 尝试 $uri - 直接文件路径 directPath := filepath.Join(s.staticDir, requestPath) if s.fileExists(directPath) && !s.isDirectory(directPath) { return directPath } // 2. 尝试 $uri/ - 目录下的 index.html if requestPath != "/" { dirPath := filepath.Join(s.staticDir, requestPath) if s.isDirectory(dirPath) { indexPath := filepath.Join(dirPath, "index.html") if s.fileExists(indexPath) { return indexPath } } // 也尝试添加 .html 扩展名 htmlPath := directPath + ".html" if s.fileExists(htmlPath) { return htmlPath } } // 3. @router - 回退到根目录的 index.html (SPA 路由处理) fallbackPath := filepath.Join(s.staticDir, "index.html") return fallbackPath } // fileExists 检查文件是否存在 func (s *StaticHandler) fileExists(path string) bool { _, err := os.Stat(path) return err == nil } // isDirectory 检查路径是否为目录 func (s *StaticHandler) isDirectory(path string) bool { info, err := os.Stat(path) if err != nil { return false } return info.IsDir() } // setContentType 设置正确的 Content-Type func (s *StaticHandler) setContentType(w http.ResponseWriter, filePath string) { ext := strings.ToLower(filepath.Ext(filePath)) switch ext { case ".html": w.Header().Set("Content-Type", "text/html; charset=utf-8") case ".css": w.Header().Set("Content-Type", "text/css; charset=utf-8") case ".js": w.Header().Set("Content-Type", "application/javascript; charset=utf-8") case ".json": w.Header().Set("Content-Type", "application/json; charset=utf-8") case ".png": w.Header().Set("Content-Type", "image/png") case ".jpg", ".jpeg": w.Header().Set("Content-Type", "image/jpeg") case ".gif": w.Header().Set("Content-Type", "image/gif") case ".svg": w.Header().Set("Content-Type", "image/svg+xml") case ".ico": w.Header().Set("Content-Type", "image/x-icon") } }