使用标准 http 包显示自定义 404 错误页面

Posted

技术标签:

【中文标题】使用标准 http 包显示自定义 404 错误页面【英文标题】:Showing custom 404 error page with standard http package 【发布时间】:2012-04-03 15:43:43 【问题描述】:

假设我们有:

http.HandleFunc("/smth", smthPage)
http.HandleFunc("/", homePage)

用户在尝试错误的 URL 时会看到一个简单的“404 页面未找到”。我怎样才能为这种情况返回自定义页面?

关于 gorilla/mux 的更新

对于那些使用纯 net/http 包的人来说,接受的答案是可以的。

如果你使用 gorilla/mux,你应该使用这样的东西:

func main() 
    r := mux.NewRouter()
    r.NotFoundHandler = http.HandlerFunc(notFound)

并根据需要实现func notFound(w http.ResponseWriter, r *http.Request)

【问题讨论】:

+1 感谢 gorilla/mux 快捷方式。本来可以踢自己的。 是的,当一个问题预示着一个后续问题时,这总是很好的! 【参考方案1】:

我通常这样做:

package main

import (
    "fmt"
    "net/http"
)

func main() 
    http.HandleFunc("/", homeHandler)
    http.HandleFunc("/smth/", smthHandler)
    http.ListenAndServe(":12345", nil)


func homeHandler(w http.ResponseWriter, r *http.Request) 
    if r.URL.Path != "/" 
        errorHandler(w, r, http.StatusNotFound)
        return
    
    fmt.Fprint(w, "welcome home")


func smthHandler(w http.ResponseWriter, r *http.Request) 
    if r.URL.Path != "/smth/" 
        errorHandler(w, r, http.StatusNotFound)
        return
    
    fmt.Fprint(w, "welcome smth")


func errorHandler(w http.ResponseWriter, r *http.Request, status int) 
    w.WriteHeader(status)
    if status == http.StatusNotFound 
        fmt.Fprint(w, "custom 404")
    

在这里,我已将代码简化为仅显示自定义 404,但实际上我通过此设置做了更多工作:我使用 errorHandler 处理所有 HTTP 错误,我在其中记录有用信息并向自己发送电子邮件。

【讨论】:

至少从 Go 1.7 开始,这个答案仍然是正确的,但是,“模式“/”匹配所有与其他注册模式不匹配的路径”。此示例中的 errorHandler 条件检查仅在 homeHandler 或“/”中需要。 @RomaH 除非你想匹配 /smth/ 而不是 /smth/foo,在这种情况下它会属于 smthHandler 但不会引发错误。 谢谢!我注意到我应该在运行之前设置处理程序【参考方案2】:

以下是我选择的方法。它基于我无法确认的代码 sn-p,因为我丢失了浏览器书签。

示例代码:(我把它放在我的主包中)

type hijack404 struct 
    http.ResponseWriter
    R *http.Request
    Handle404 func (w http.ResponseWriter, r *http.Request) bool


func (h *hijack404) WriteHeader(code int) 
    if 404 == code && h.Handle404(h.ResponseWriter, h.R) 
        panic(h)
    

    h.ResponseWriter.WriteHeader(code)


func Handle404(handler http.Handler, handle404 func (w http.ResponseWriter, r *http.Request) bool) http.Handler 
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request)
        hijack := &hijack404 ResponseWriter:w, R: r, Handle404: handle404 

        defer func() 
            if p:=recover(); p!=nil 
                if p==hijack 
                    return
                
                panic(p)
            
        ()

        handler.ServeHTTP(hijack, r)
    )


func fire404(res http.ResponseWriter, req *http.Request) bool
    fmt.Fprintf(res, "File not found. Please check to see if your URL is correct.");

    return true;


func main()
    handler_statics := http.StripPrefix("/static/", http.FileServer(http.Dir("/Path_To_My_Static_Files")));

    var v_blessed_handler_statics http.Handler = Handle404(handler_statics, fire404);

    http.Handle("/static/", v_blessed_handler_statics);

    // add other handlers using http.Handle() as necessary

    if err := http.ListenAndServe(":8080", nil); err != nil
        log.Fatal("ListenAndServe: ", err);
    

请自定义 func fire404 以输出您自己版本的错误 404 消息。

如果您碰巧使用 Gorilla Mux,您可能希望将 main 函数替换为以下内容:

func main()
    handler_statics := http.StripPrefix("/static/", http.FileServer(http.Dir("/Path_To_My_Static_Files")));

    var v_blessed_handler_statics http.Handler = Handle404(handler_statics, fire404);

    r := mux.NewRouter();
    r.PathPrefix("/static/").Handler(v_blessed_handler_statics);

    // add other handlers with r.HandleFunc() if necessary...

    http.Handle("/", r);

    log.Fatal(http.ListenAndServe(":8080", nil));

如果代码有误,请更正代码,因为我只是 Go 的新手。谢谢。

【讨论】:

【参考方案3】:

我认为干净的方法是这样的:

func main() 

http.HandleFunc("/calculator", calculatorHandler)
http.HandleFunc("/history", historyHandler)
http.HandleFunc("/", notFoundHandler)

log.Fatal(http.ListenAndServe(":80", nil))

如果地址不是 /calulator 或 /history,则处理 notFoundHandler 函数。

【讨论】:

【参考方案4】:

您只需要创建自己的 notFound 处理程序并将其注册到 HandleFunc 以获取您不处理的路径。

如果您想最大程度地控制您的路由逻辑,您将需要使用您自己的自定义服务器和自定义处理程序类型。

http://golang.org/pkg/net/http/#Handler http://golang.org/pkg/net/http/#Server

这允许您实现比 HandleFunc 允许的更复杂的路由逻辑。

【讨论】:

【参考方案5】:

也许我错了,但我刚刚检查了来源:http://golang.org/src/pkg/net/http/server.go

似乎几乎不可能指定自定义 NotFound() 函数:NotFoundHandler() 返回一个名为 NotFound() 的硬编码函数。

或许,您应该就此提交问题。

作为一种解决方法,您可以使用“/”处理程序,如果没有找到其他处理程序(因为它是最短的处理程序),这是一种备用。因此,检查该处理程序中是否存在页面并返回自定义 404 错误。

【讨论】:

我也这么认为。我最终检查了源代码,发现 NotFoundHandler 函数返回一个硬编码的值,而不是允许您自定义该处理程序。如果它们不允许您自定义这些功能,我不明白为什么要如此间接地获取 NotFound 功能。 这里给出的“解决方法”正是你如何做到的。【参考方案6】:

古老的线程,不过我只是截取了http.ResponseWriter,可能和这里有关。

package main

//GAE POC originally inspired by https://thornelabs.net/2017/03/08/use-google-app-engine-and-golang-to-host-a-static-website-with-same-domain-redirects.html

import (
    "net/http"
)

func init() 
    http.HandleFunc("/", handler)


// HeaderWriter is a wrapper around http.ResponseWriter which manipulates headers/content based on upstream response
type HeaderWriter struct 
    original http.ResponseWriter
    done     bool


func (hw *HeaderWriter) Header() http.Header 
    return hw.original.Header()


func (hw *HeaderWriter) Write(b []byte) (int, error) 
    if hw.done 
        //Silently let caller think they are succeeding in sending their boring 404...
        return len(b), nil
    
    return hw.original.Write(b)


func (hw *HeaderWriter) WriteHeader(s int) 
    if hw.done 
        //Hmm... I don't think this is needed...
        return
    
    if s < 400 
        //Set CC header when status is < 400...
        //TODO: Use diff header if static extensions
        hw.original.Header().Set("Cache-Control", "max-age=60, s-maxage=2592000, public")
    
    hw.original.WriteHeader(s)
    if s == 404 
        hw.done = true
        hw.original.Write([]byte("This be custom 404..."))
    


func handler(w http.ResponseWriter, r *http.Request) 
    urls := map[string]string
        "/example-post-1.html": "https://example.com/post/example-post-1.html",
        "/example-post-2.html": "https://example.com/post/example-post-2.html",
        "/example-post-3.html": "https://example.com/post/example-post-3.html",
    
    w.Header().Set("Strict-Transport-Security", "max-age=15768000")
    //TODO: Put own logic
    if value, ok := urls[r.URL.Path]; ok 
        http.Redirect(&HeaderWriteroriginal: w, r, value, 301)
     else 
        http.ServeFile(&HeaderWriteroriginal: w, r, "static/"+r.URL.Path)
    

【讨论】:

【参考方案7】:

你可以定义

http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) 
    if request.URL.Path != "/" 
        writer.WriteHeader(404)
        writer.Write([]byte(`not found, da xiong dei !!!`))
        return
    
)

访问未找到资源时,会执行到http.HandleFunc("/", xxx)

【讨论】:

这意味着你的网站不会有主页【参考方案8】:

你可以简单地使用类似的东西:

func Handle404(w http.ResponseWriter, r *http.Request) 
    fmt.Fprint(w, "404 error\n")


func main()
   http.HandleFunc("/", routes.Handle404)

如果你需要得到标准的,只需写:

func main()
   http.HandleFunc("/", http.NotFound)

你会得到:

404 page not found

【讨论】:

以上是关于使用标准 http 包显示自定义 404 错误页面的主要内容,如果未能解决你的问题,请参考以下文章

Golang 标准库net/http自定义404页面

如何在 Django 的自定义 404 错误模板中显示传递给 Http404 的消息?

为啥jsp页面在root下可以显示,而在自定义应用目录下提示Http404错误呢

自定义 ASP.NET MVC 404 错误页面的路由

标准 404 页面何时出现?

Failed to load resource: the server responded with a status of 404 (Not Found)