I am using Google App Engine to serve my (semi-)static website generated with Hugo. I have a directory "public" where all the HTML files are stored and are to be served. I also have some server-side scripts for the contact form handling for example. The app.yaml
file looks like this.
// app.yaml
runtime: go
api_version: go1
handlers:
- url: /.*
script: _go_app
secure: always
And the simplified main.go
file looks like this
// main.go
package main
import (
"net/http"
"encoding/json"
"appengine"
"appengine/urlfetch"
)
func init() {
fileHandler := http.FileServer(http.Dir("public"))
http.Handle("/", fileHandler)
http.HandleFunc("/contactus/", HandleContactus)
}
This works perfectly well and serves the html files. However, I am looking at a solution to handle the cases where the pages are not found and the response is 404 Not Found
for example (or any other server error).
My thought was to create a custom handler which can be passed in the http.Handle("/", myCustomHandler)
and would handle the server response and would redirect to a custom 404.html
or the like if necessary. I am new to Go and can't seem to figure out how this should be implemented. I have also looked at the Gorilla Mux, but would prefer (if possible) not to use external libraries to keep it simple.
Based on this post, I have tried the following
package main
import (
"net/http"
"encoding/json"
"appengine"
"appengine/urlfetch"
)
func StaticSiteHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request){
h.ServeHTTP(w, r)
})
}
func init() {
fileHandler := http.FileServer(http.Dir("public"))
http.Handle("/", StaticSiteHandler(fileHandler))
http.HandleFunc("/contactus/", HandleContactus)
}
This solution works in the sense that it also does serve my HTML pages, however I still can't figure out how to handle the server response codes.
Any help would be highly appreciated. Thanks!
To keep the middleware decoupled from the http.FileServer
, as you're wrapping it, you can pass a specific implementation of http.ResponseWriter
that will:
WriteHeader
is called with a 404)WriteHeader
is called with a 404:
Write
from the wrapped handlerWriteHeader
is not called, or called with a non-404, then:
ResponseWriter
WriteHeader
and Write
calls to the real ResponseWriter
type notFoundInterceptorWriter struct {
rw http.ResponseWriter // set to nil to signal a 404 has been intercepted
h http.Header // set to nil to signal headers have been emitted
notFoundHandler http.Handler
r *http.Request
}
func (rw *notFoundInterceptorWriter) Header() http.Header {
if rw.h == nil && rw.rw != nil {
return rw.rw.Header()
}
return rw.h
}
func (rw *notFoundInterceptorWriter) WriteHeader(status int) {
if status == http.StatusNotFound {
rw.notFoundHandler.ServeHTTP(rw.rw, rw.r)
rw.rw = nil
} else {
for k, vs := range rw.h {
for _, v := range vs {
rw.rw.Header().Add(k, v)
}
}
rw.rw.WriteHeader(status)
}
rw.h = nil
}
func (rw *notFoundInterceptorWriter) Write(b []byte) (int, error) {
if rw.rw != nil {
return rw.rw.Write(b)
}
// ignore, so do as if everything was written OK
return len(b), nil
}
func StaticSiteHandler(h, notFoundHandler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w = ¬FoundInterceptorWriter{
rw: w,
h: make(http.Header),
notFoundHandler: notFoundHandler,
r: r,
}
h.ServeHTTP(w, r)
})
}