go http server 默认不支持自定义404/500页面,需通过自定义handler捕获未注册路由并用defer+recover拦截panic,同时静态资源须统一挂载至/static/并使用绝对路径引用。

Go HTTP Server 默认不支持自定义 404 和 500 页面
Go 标准库的 http.ServeMux 在找不到路由时直接返回硬编码的 "404 page not found",且不调用你写的 handler;发生 panic 时更不会自动转到错误页面,而是直接中断响应或返回空内容。这不是 bug,是设计使然——http.ServeMux 不处理错误传播,也不拦截 panic。
所以你写了个 http.HandleFunc("/404", ...),但用户访问不存在路径时根本不会走到那里。
- 所有未注册路径都由
http.ServeMux内部兜底,不经过你的中间逻辑 -
http.Error只能手动触发,无法自动捕获 panic 或 404 - 标准
http.Server没有类似 Express 的app.use((err, req, res, next) => {...})错误中间件机制
用自定义 ServeMux 替换默认 mux 处理 404
核心思路:自己实现 http.Handler,在 ServeHTTP 中先查路由,查不到就调用你的 404 handler,而不是依赖默认行为。
别直接改 http.DefaultServeMux,它被多处隐式引用,容易出错;应该新建一个 http.ServeMux 实例,再包一层。
立即学习“go语言免费学习笔记(深入)”;
- 创建新
http.ServeMux,注册所有正常路由 - 用匿名函数包装它的
ServeHTTP方法,在HandlerFunc里检查是否命中——没命中就执行自定义 404 逻辑 - 404 handler 必须手动设置
w.WriteHeader(http.StatusNotFound),否则状态码仍是 200
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)
mux.HandleFunc("/api/", apiHandler)
// 包装 mux,接管未命中路径
http.ListenAndServe(":8080", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if _, pattern := mux.Handler(r); pattern == "" {
notFoundHandler(w, r)
return
}
mux.ServeHTTP(w, r)
}))
}
用 defer + recover 捕获 panic 实现 500 页面
Go HTTP handler 中 panic 会终止当前 goroutine,但默认不返回任何响应体,客户端可能卡住或收到空响应。必须显式 recover 并写入错误页面。
不能只在 main 入口 recover——那是 server 启动时的 panic;每个 handler 执行上下文是独立的,recover 必须放在 handler 内部。
- 在每个顶层 handler 函数开头加
defer func()块,里面做recover() - recover 后要立即调用
w.WriteHeader(http.StatusInternalServerError),否则状态码还是 200 - 避免在 recover 里调用可能 panic 的代码(比如模板渲染),否则二次 panic 无法被捕获
- 生产环境建议记录 panic stack,但不要直接返回给前端(
debug.PrintStack()仅用于开发)
func riskyHandler(w http.ResponseWriter, r *http.Request) {
defer func() {
if err := recover(); err != nil {
w.WriteHeader(http.StatusInternalServerError)
fmt.Fprint(w, "<h1>Server Error</h1>")
log.Printf("panic: %v", err)
}
}()
// 可能 panic 的业务逻辑
panic("something went wrong")
}
HTML 模板复用与静态资源路径陷阱
你写了漂亮的 404.html 和 500.html,但用 html/template 渲染时发现 CSS 不加载、图片 404——大概率是静态资源路径没配对。
Go 的 http.FileServer 默认以文件系统路径为根,而模板里写的 /static/css/app.css 是 URL 路径,两者映射关系必须显式声明。
- 静态资源目录建议统一挂载在
/static/,用http.StripPrefix("/static/", http.FileServer(...))配置 - 模板中所有资源链接必须以
/static/开头,不能用相对路径(如./css/app.css),因为不同 URL 深度下相对路径解析结果不同 - 如果用了子路径部署(比如
/myapp/404),模板里的路径需动态注入 base URL,不能写死 - 注意
http.ServeFile不支持目录列表,别误用它服务整个static/目录
复杂点在于:404 页面本身也是 HTML,但它可能被任意无效路径触发(比如 /a/b/c/d/e),此时浏览器会尝试从该路径下加载 ./static/...,导致 404 嵌套。唯一稳的方式是所有资源走绝对路径 /static/xxx,并确保 FileServer 正确挂载。











