Showing custom 404 error page with standard http package
Asked Answered
W

9

89

Assuming that we have:

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

User sees a plain "404 page not found" when they try a wrong URL. How can I return a custom page for that case?

Update concerning gorilla/mux

Accepted answer is ok for those using pure net/http package.

If you use gorilla/mux you should use something like this:

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

And implement func notFound(w http.ResponseWriter, r *http.Request) as you want.

Willful answered 3/4, 2012 at 15:43 Comment(3)
+1 Thanks for the gorilla/mux shortcut. Could have kicked myself with that.Phylissphyll
Yeah, it's always nice when a question anticipates a follow-up question!Heteroecious
Yes, and don't forget to add w.WriteHeader(404) to the 'notFound' function to tell the client that it's a 404 page.Coppins
A
88

I usually do this:

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")
    }
}

Here I've simplified the code to only show custom 404, but I actually do more with this setup: I handle all the HTTP errors with errorHandler, in which I log useful information and send email to myself.

Anglonorman answered 3/4, 2012 at 21:0 Comment(3)
As of at least Go 1.7 this answer is still correct but, 'the pattern "/" matches all paths not matched by other registered patterns'. The conditional check for errorHandler in this example would only be needed in homeHandler or "/".Spireme
@Spireme unless you want to match /smth/ but not /smth/foo, in which case it would fall under smthHandler but not raise an error.Secularity
Thank you! I paid attention that I should set handler before runningWhicker
N
8

Following is the approach I choose. It is based on a code snippet which I cannot acknowledge since I lost the browser bookmark.

Sample code : (I put it in my main package)

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);
    }
}

Please customize the func fire404 to output your own version of message for error 404.

If you happen to be using Gorilla Mux, you may wish to replace the main function with below :

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));
}

Please kindly correct the code if it is wrong, since I am only a newbie to Go. Thanks.

Nolpros answered 15/9, 2014 at 9:0 Comment(1)
I think this is where you found the snippet: groups.google.com/g/golang-nuts/c/Phk9r4IgfM8/m/7WpYaI7O1ywJFourier
C
4

i think the clean way is this:

func main() {

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

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

if the address is not /calulator or /history, then it handles notFoundHandler function.

Carbonous answered 27/5, 2021 at 0:45 Comment(0)
S
3

Ancient thread, but I just made something to intercept http.ResponseWriter, might be relevant here.

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(&HeaderWriter{original: w}, r, value, 301)
    } else {
        http.ServeFile(&HeaderWriter{original: w}, r, "static/"+r.URL.Path)
    }
}
Shute answered 10/3, 2017 at 18:4 Comment(0)
A
2

Maybe I'm wrong, but I just checked the sources: http://golang.org/src/pkg/net/http/server.go

It seems like specifying custom NotFound() function is hardly possible: NotFoundHandler() returns a hardcoded function called NotFound().

Probably, you should submit an issue on this.

As a workaround, you can use your "/" handler, which is a fallback if no other handlers were found (as it is the shortest one). So, check is page exists in that handler and return a custom 404 error.

Apprentice answered 3/4, 2012 at 16:25 Comment(3)
I think the same. I ended up checking the sources and found that NotFoundHandler function returns a hardcoded value instead of allowing you to customize that handler. I don't understand why so indirection to get the NotFound function if they don't let you customize those kind of features.Breazeale
The "workaround" given here is exactly how you do it.Pastry
This leads to weird scenarios where my home page is used as a fallback to JSON API routes too. This snippet can help: ` http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/" { // this is actually a 404 http.NotFound(w, r) return }`Nutgall
T
2

You just need to create your own notFound handler and register it with HandleFunc for the path that you don't handle.

If you want the most control over your routing logic you will need to use a custom server and custom handler type of your own.

This allows you to implement more complex routing logic than the HandleFunc will allow you to do.

Tornado answered 3/4, 2012 at 18:37 Comment(0)
P
0

you can define

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

when access not found resource, it will execute to http.HandleFunc("/", xxx)

Phosphene answered 13/5, 2019 at 3:57 Comment(2)
That means your website wont have a homepageMoria
This works, and Sathesh's comment about not having a homepage might have been means for Hasan A Yousef's answer above.Irmine
A
0

In short, just check if r.URL.Path matches a URL you're expecting to find, such as by looping through an array of pages. Requiring an exact URL match, you avoid the catchall problem pointed out by @RomaH

For now I'm doing this inside a function called drawPage:

is404 := true // assume the worst

// start loop through a data structure of all my pages
    if (r.URL.Path == data.Options[i].URL) { is404 = false }
// end loop

if is404 { // failed our test
    w.WriteHeader(404)
    w.Write([]byte(`404 not found`))
    return 
} 

// draw the page we found

Last part is taken from @许晨峰

Adopt answered 23/4, 2023 at 19:2 Comment(0)
L
-2

You can simply use something like:

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

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

If you need to get the standard one, just write:

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

And you'll get:

404 page not found
Lifesaver answered 1/11, 2021 at 18:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.