You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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
}

说明

  1. statusRecorder作为响应包装器,捕获FileServer返回的HTTP状态码
  2. 当检测到404状态时,重置路由上下文并调用自定义NotFound逻辑,确保返回自定义404页面
  3. 通过参数传递NotFound处理器,避免函数间的耦合问题

内容的提问来源于stack exchange,提问作者DroidZed

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 09:44:54