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

如何在Golang中自定义404页面?兼解路由冲突问题

Go Web开发路由冲突与自定义404问题解答

1. 路由冲突的原因

Go的http.ListenAndServe第二个参数是全局根Handler:

  • 当传入nil时,默认使用http.DefaultServeMux——也就是你通过http.HandleFunc注册路由的多路复用器,所有自定义路由都会生效。
  • 当你传入http.FileServer(http.Dir(""))作为根Handler时,所有HTTP请求会直接交给FileServer处理,完全不会经过DefaultServeMux。你注册的/test等路由根本不会被触发,看似正常的/路由其实是FileServer默认读取当前目录index.html的行为,并非你写的Handler在工作。

2. 为什么用nil时不存在的路由返回首页

当使用DefaultServeMux(即ListenAndServe传nil)时,Go遵循最长前缀匹配规则:

  • 若请求路径没有对应的HandleFunc注册(比如/abc),DefaultServeMux会匹配到最接近的前缀路由——也就是你注册的/(所有路径都以/开头),执行该Handler返回index.html,而非404。

3. 解决路由冲突+自定义404页面

方案一:正确注册静态文件路由(推荐)

不要把FileServer作为根Handler,而是将其注册到子路径下(比如/static/),既保留自定义路由,又能处理静态文件:

package main

import (
    "fmt"
    "html/template"
    "net/http"
)

func main() {
    // 静态文件服务绑定到/static路径,自动去掉前缀后读取static目录下的文件
    http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))

    // 首页路由:仅处理根路径请求
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path != "/" {
            custom404(w)
            return
        }
        tmpl, err := template.ParseFiles("index.html")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        tmpl.Execute(w, nil)
    })

    // 测试路由
    http.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Test page")
    })

    // 使用默认多路复用器启动服务
    http.ListenAndServe(":80", nil)
}

// 自定义404页面逻辑
func custom404(w http.ResponseWriter) {
    w.WriteHeader(http.StatusNotFound)
    // 可直接输出HTML或解析404模板
    tmpl, err := template.ParseFiles("404.html")
    if err != nil {
        fmt.Fprint(w, "<h1>404 - 页面不存在</h1>")
        return
    }
    tmpl.Execute(w, nil)
}

方案二:自定义全局Handler实现全量控制

如果需要更灵活的路由和错误处理,可自行实现http.Handler接口,统一管理路由匹配与404:

package main

import (
    "fmt"
    "html/template"
    "net/http"
)

// 自定义全局Handler
type CustomRootHandler struct {
    mux *http.ServeMux
}

func (h *CustomRootHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 先尝试用多路复用器处理请求
    h.mux.ServeHTTP(w, r)
    // 检查是否未返回任何响应(即无匹配路由)
    if w.Header().Get("Content-Type") == "" {
        custom404(w)
    }
}

func main() {
    mux := http.NewServeMux()

    // 注册静态文件服务
    mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
    // 注册首页路由
    mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path != "/" {
            return // 交给全局Handler返回404
        }
        tmpl, err := template.ParseFiles("index.html")
        if err != nil {
            http.Error(w, err.Error(), http.StatusInternalServerError)
            return
        }
        tmpl.Execute(w, nil)
    })
    // 注册测试路由
    mux.HandleFunc("/test", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Test page")
    })

    // 使用自定义全局Handler启动服务
    http.ListenAndServe(":80", &CustomRootHandler{mux: mux})
}

func custom404(w http.ResponseWriter) {
    w.WriteHeader(http.StatusNotFound)
    tmpl, err := template.ParseFiles("404.html")
    if err != nil {
        fmt.Fprint(w, "<h1>404 - 页面找不到</h1>")
        return
    }
    tmpl.Execute(w, nil)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 04:02:56