在Go Chi中使用FileServer时如何自定义404响应处理器?
Go Chi + FileServer 自定义404页面不生效问题解决
问题场景
- 背景:使用Go Chi框架的FileServer处理
public/views目录下的静态文件,期望所有错误请求路径都返回自定义404 HTML页面 - 异常表现:访问错误路径时,返回默认的"404 Page not found"文本;但注释掉
MountViewsFolder方法后,自定义404页面能正常显示 - 预期目标:任何错误URI请求都返回自定义404页面
当前代码实现
FileServer初始化代码
// FileServer conveniently sets up a http.FileServer handler to serve // static files from a http.FileSystem. func fileServerSetup(r chi.Router, path string, root http.FileSystem) { log := config.GetLogger() if strings.ContainsAny(path, "{}*") { log.Error("FileServer does not permit any URL parameters.") } if path != "/" && path[len(path)-1] != '/' { r.Get(path, http.RedirectHandler(path+"/", http.StatusMovedPermanently).ServeHTTP) path += "/" } path += "*" r.Get(path, func(w http.ResponseWriter, r *http.Request) { routeContext := chi.RouteContext(r.Context()) pathPrefix := strings.TrimSuffix(routeContext.RoutePattern(), "/*") fs := http.StripPrefix(pathPrefix, http.FileServer(root)) fs.ServeHTTP(w, r) }) }
服务器核心配置代码
type Server struct { Router *chi.Mux EnvConfig *config.EnvConfig } type ServerDefinition interface { New() (server *Server) MountViewsFolder() error ApplyMiddleWares() MountHandlers() } func (s *Server) New() (server *Server) { server = &Server{} server.Router = chi.NewRouter() server.EnvConfig = config.LoadEnv() pigeon.GetSmtp() return server } func (s *Server) MountViewsFolder() error { workDir, err := os.Getwd() if err != nil { return err } filesDir := http.Dir(filepath.Join(workDir, "public/views")) fileServerSetup(s.Router, "/", filesDir) return nil } func (s *Server) MountHandlers() { s.Router.NotFound(func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, "public/views/404.tmpl") }) // 其余路由配置(Swagger、Auth、Signup、User等)省略 }
问题根因
fileServerSetup中注册了r.Get("/*", ...)路由,该路由会匹配所有GET请求(包括错误路径)。当请求错误路径时,FileServer的处理器优先触发,直接返回默认404文本,导致自定义NotFound处理器无法执行。
解决方案
通过包装http.FileServer的响应,捕获其返回的404状态码,转而调用自定义404处理逻辑。修改后的fileServerSetup函数如下:
func fileServerSetup(r chi.Router, path string, root http.FileSystem, notFoundHandler http.HandlerFunc) { log := config.GetLogger() if strings.ContainsAny(path, "{}*") { log.Error("FileServer does not permit any URL parameters.") } if path != "/" && path[len(path)-1] != '/' { r.Get(path, http.RedirectHandler(path+"/", http.StatusMovedPermanently).ServeHTTP) path += "/" } path += "*" customFS := func(w http.ResponseWriter, r *http.Request) { routeContext := chi.RouteContext(r.Context()) pathPrefix := strings.TrimSuffix(routeContext.RoutePattern(), "/*") fs := http.StripPrefix(pathPrefix, http.FileServer(root)) // 捕获响应状态码 recorder := &statusRecorder{ResponseWriter: w, statusCode: http.StatusOK} fs.ServeHTTP(recorder, r) // 若返回404,触发自定义处理逻辑 if recorder.statusCode == http.StatusNotFound { // 重置路由上下文,避免干扰后续处理 chi.RouteContext(r.Context()).Reset() notFoundHandler(w, r) } } r.Get(path, customFS) } // 用于记录响应状态码的包装器 type statusRecorder struct { http.ResponseWriter statusCode int } func (sr *statusRecorder) WriteHeader(code int) { sr.statusCode = code sr.ResponseWriter.WriteHeader(code) }
调用方式调整
在MountViewsFolder中传递自定义NotFound处理器:
func (s *Server) MountViewsFolder() error { workDir, err := os.Getwd() if err != nil { return err } filesDir := http.Dir(filepath.Join(workDir, "public/views")) // 传入自定义NotFound处理器 fileServerSetup(s.Router, "/", filesDir, func(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, filepath.Join(workDir, "public/views/404.tmpl")) }) return nil }
说明
statusRecorder作为响应包装器,捕获FileServer返回的HTTP状态码- 当检测到404状态时,重置路由上下文并调用自定义NotFound逻辑,确保返回自定义404页面
- 通过参数传递NotFound处理器,避免函数间的耦合问题
内容的提问来源于stack exchange,提问作者DroidZed
相关产品推荐
相关产品推荐

