Showing custom 404 error page with standard http package

Go

Go Problem Overview


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.

Go Solutions


Solution 1 - Go

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.

Solution 2 - Go

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.

Solution 3 - Go

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.

Solution 4 - Go

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.

Solution 5 - Go

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.

Solution 6 - Go

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

Solution 7 - Go

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)

Solution 8 - Go

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

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionshalakhinView Question on Stackoverflow
Solution 1 - GoMostafaView Answer on Stackoverflow
Solution 2 - GozeldaView Answer on Stackoverflow
Solution 3 - GomodosView Answer on Stackoverflow
Solution 4 - GoJeremy WallView Answer on Stackoverflow
Solution 5 - GozsergeView Answer on Stackoverflow
Solution 6 - GosajalView Answer on Stackoverflow
Solution 7 - Go许晨峰View Answer on Stackoverflow
Solution 8 - GoHasan A YousefView Answer on Stackoverflow