I'm coding a web application in Go, and while various mux libraries provide a way to set a custom 404 error handler, there's nothing for other 4xx and 5xx error codes.
One suggestion is to override the WriteHeader method in the ResponseWriter interface and check the status code, but I'm confused as to how this would actually be written (the overriding of ResponseWriter methods, before outputting). One possible example can be found from the negroni package.
Would this be the correct way to serve a custom template for 4xx and 5xx errors? Could anyone provide an example of how this could be implemented?
Update
Big thanks to David and elithrar for their responses and code. The Interceptor struct that David coded can be used in a wrapper for the server mux, as elithrar shows in the comments. For those looking for further explanation as to why and how this works, this section from astaxie's book gives some very good info on the workings of the net/http package, along with viewing the server.go source code from the net/http package.
The mux libraries only have a means of setting the not found handler as a means of giving you a way to intercept requests where the mux can't resolve the URL to it's known mappings.
For example, you do:
mux.Handle("/foo",fooFunc)
mux.Handle("/bar",barFunc)
But the client accesses /baz to which the mux has no mapping.
They do not actually intercept a 404 going to the client, they just invoke the not found handler when it runs in to this problem.
Also, if your /foo handler sends a 404 response, the not found does not get invoked.
If you want custom pages for various return responses from your mapped URLs, simply make the various handlers write the correct response.
If you don't control that logic (ie: the framwork is writing something and has no means to override), then you may want to intercept all requests and override the http.ResposeWriter with response code detection logic.
Here's an example interceptor that basically does what you want. On Play
package main
import (
"fmt"
"log"
)
import "net/http"
type Interceptor struct {
origWriter http.ResponseWriter
overridden bool
}
func (i *Interceptor) WriteHeader(rc int) {
switch rc {
case 500:
http.Error(i.origWriter, "Custom 500 message / content", 500)
case 404:
http.Error(i.origWriter, "Custom 404 message", 404)
case 403:
i.origWriter.WriteHeader(403)
fmt.Fprintln(i.origWriter, "Custom 403 message")
default:
i.origWriter.WriteHeader(rc)
return
}
// if the default case didn't execute (and return) we must have overridden the output
i.overridden = true
log.Println(i.overridden)
}
func (i *Interceptor) Write(b []byte) (int, error) {
if !i.overridden {
return i.origWriter.Write(b)
}
// Return nothing if we've overriden the response.
return 0, nil
}
func (i *Interceptor) Header() http.Header {
return i.origWriter.Header()
}
Related
I want to start learning about the fasthttps server from this link https://github.com/valyala/fasthttp but I dont know that how will I implement a small piece of code in this framework. Can anybody tell me that how i will implement a small piece of code in this? example please.
Code I tried
package main
import "fmt"
type MyHandler struct {
foobar string
}
func main() {
// pass bound struct method to fasthttp
myHandler := &MyHandler{
foobar: "foobar",
}
fasthttp.ListenAndServe(":8080", myHandler.HandleFastHTTP)
// pass plain function to fasthttp
fasthttp.ListenAndServe(":8081", fastHTTPHandler)
}
// request handler in net/http style, i.e. method bound to MyHandler struct.
func (h *MyHandler) HandleFastHTTP(ctx *fasthttp.RequestCtx) {
// notice that we may access MyHandler properties here - see h.foobar.
fmt.Fprintf(ctx, "Hello, world! Requested path is %q. Foobar is %q",
ctx.Path(), h.foobar)
}
// request handler in fasthttp style, i.e. just plain function.
func fastHTTPHandler(ctx *fasthttp.RequestCtx) {
fmt.Fprintf(ctx, "Hi there! RequestURI is %q", ctx.RequestURI())
}
Can you please tell me that how I will implement this code.
This code seems to be working. I pasted it to a .go file, added:
import "github.com/valyala/fasthttp"
Then you have to install this package, either by using go get github.com/valyala/fasthttp or by writing a go.mod file if you want to use the new module support.
Then run this file and open localhost:8080 in a browser.
Maybe you have a more concrete question?
As #Volker said in a comment, for newbies it's highly recommended to stick to the standard library - net/http in this case; there are way more examples and code/tutorials you can find by googling, no need to install special packages, etc.
So I'm using a go server to serve up a single page web application.
This works for serving all the assets on the root route. All the CSS and HTML are served up correctly.
fs := http.FileServer(http.Dir("build"))
http.Handle("/", fs)
So when the URL is http://myserverurl/index.html or http://myserverurl/styles.css, it serves the corresponding file.
But for a URL like http://myserverurl/myCustompage, it throws 404 if myCustompage is not a file in the build folder.
How do I make all routes for which a file does not exist serve index.html?
It is a single page web application and it will render the appropriate screen once the html and js are served. But it needs index.html to be served on routes for which there is no file.
How can this be done?
The handler returned by http.FileServer() does not support customization, it does not support providing a custom 404 page or action.
What we may do is wrap the handler returned by http.FileServer(), and in our handler we may do whatever we want of course. In our wrapper handler we will call the file server handler, and if that would send a 404 not found response, we won't send it to the client but replace it with a redirect response.
To achieve that, in our wrapper we create a wrapper http.ResponseWriter which we will pass to the handler returned by http.FileServer(), and in this wrapper response writer we may inspect the status code, and if it's 404, we may act to not send the response to the client, but instead send a redirect to /index.html.
This is an example how this wrapper http.ResponseWriter may look like:
type NotFoundRedirectRespWr struct {
http.ResponseWriter // We embed http.ResponseWriter
status int
}
func (w *NotFoundRedirectRespWr) WriteHeader(status int) {
w.status = status // Store the status for our own use
if status != http.StatusNotFound {
w.ResponseWriter.WriteHeader(status)
}
}
func (w *NotFoundRedirectRespWr) Write(p []byte) (int, error) {
if w.status != http.StatusNotFound {
return w.ResponseWriter.Write(p)
}
return len(p), nil // Lie that we successfully written it
}
And wrapping the handler returned by http.FileServer() may look like this:
func wrapHandler(h http.Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
nfrw := &NotFoundRedirectRespWr{ResponseWriter: w}
h.ServeHTTP(nfrw, r)
if nfrw.status == 404 {
log.Printf("Redirecting %s to index.html.", r.RequestURI)
http.Redirect(w, r, "/index.html", http.StatusFound)
}
}
}
Note that I used http.StatusFound redirect status code instead of http.StatusMovedPermanently as the latter may be cached by browsers, and so if a file with that name is created later, the browser would not request it but display index.html immediately.
And now put this in use, the main() function:
func main() {
fs := wrapHandler(http.FileServer(http.Dir(".")))
http.HandleFunc("/", fs)
panic(http.ListenAndServe(":8080", nil))
}
Attempting to query a non-existing file, we'll see this in the log:
2017/11/14 14:10:21 Redirecting /a.txt3 to /index.html.
2017/11/14 14:10:21 Redirecting /favicon.ico to /index.html.
Note that our custom handler (being well-behaviour) also redirected the request to /favico.ico to index.html because I do not have a favico.ico file in my file system. You may want to add this as an exception if you don't have it either.
The full example is available on the Go Playground. You can't run it there, save it to your local Go workspace and run it locally.
Also check this related question: Log 404 on http.FileServer
I'm an experienced programmer, but new to go. Apologies in advance if this is an obvious question or well tread path. I'm still getting my bearings in the language and its semantics.
I'm trying to create a web server in go that
Examines the HTTP request
Based on the results of the request, serves a specific static folder
i.e., something where the simplified pseudo code looks like
import (
"io"
"net/http"
"fmt"
"strings"
"encoding/base64"
)
func examineRequest(request *http.Request) {
//looks at request header
if(request headers have one thing){
return "foo"
}
return "bar"
}
func processRequest(responseWriter http.ResponseWriter, request *http.Request) {
folderToServe = examineRequest(request);
if folderToServe == "bar" {
//serve static files from the ./static/bar folder
//go freaks out if I try these
//http.Handle("/", http.FileServer(http.Dir("./static/bar")))
//http.FileServer(http.Dir("./static/bar")()
}
else if folderToServer == "foo" {
//serve static files from the ./static/foo folder
//go freaks out if I try these
//http.Handle("/", http.FileServer(http.Dir("./static/foo")))
//http.FileServer(http.Dir("./static/foo")()
}
}
func main(){
http.HandleFunc("/", processRequest)
//http.Handle("/", http.FileServer(http.Dir("./static")))
}
Experienced go programmers may have already spotted the problem. I'm performing my examination in processRequest, and because of that, its too late to to call Handle -- however, you can't register more than one handle for the same path in go, and nested handle calls freak go out.
I though the handler might be similar to an anonymous function in other languages and tried call it -- but go did like that either.
So -- is there a way to manually invoke the handler returned from the call to http.FileServer(http.Dir("./static"))?
Is that even the right question to be asking here?
What exactly is a handler in the context of the http module?
Use http.FileServer(http.Dir("./static/foo")).ServeHTTP(w, req).
//edit
http.FileServer returns an http.Handler which in turn provides the ServerHTTP method.
I'm a little bit confused. Much of examples shows usage of both: http.ServeFile(..) and http.FileServer(..), but seems they have very close functionality. Also I have found no information about how to set custom NotFound handler.
// This works and strip "/static/" fragment from path
fs := http.FileServer(http.Dir("static"))
http.Handle("/static/", http.StripPrefix("/static/", fs))
// This works too, but "/static2/" fragment remains and need to be striped manually
http.HandleFunc("/static2/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, r.URL.Path[1:])
})
http.ListenAndServe(":8080", nil)
I've tried to read source code and both of them use serveFile(ResponseWriter, *Request, FileSystem, string, bool) underlying function. However http.FileServer return fileHandler with its own ServeHTTP() method and make some preparation work before serving file (eg path.Clean()).
So why need this separation? Which method better to use? And how can I set custom NotFound handler, for example when requested file not found?
The main difference is that http.FileServer does effectively almost 1:1 mapping of an HTTP prefix with a filesystem. In plain english, it serves up an entire directory path. and all its children.
Say you had a directory called /home/bob/static and you had this setup:
fs := http.FileServer(http.Dir("/home/bob/static"))
http.Handle("/static/", http.StripPrefix("/static", fs))
Your server would take requests for e.g. /static/foo/bar and serve whatever is at /home/bob/static/foo/bar (or 404)
In contrast, the ServeFile is a lower level helper that can be used to implement something similar to FileServer, or implement your own path munging potentially, and any number of things. It simply takes the named local file and sends it over the HTTP connection. By itself, it won't serve a whole directory prefix (unless you wrote a handler that did some lookup similar to FileServer)
NOTE Serving up a filesystem naively is a potentially dangerous thing (there are potentially ways to break out of the rooted tree) hence I recommend that unless you really know what you're doing, use http.FileServer and http.Dir as they include checks to make sure people can't break out of the FS, which ServeFile doesn't.
Addendum
Your secondary question, how do you do a custom NotFound handler, unfortunately, is not easily answered. Because this is called from internal function serveFile as you noticed, there's no super easy place to break into that. There are potentially some sneaky things like intercepting the response with your own ResponseWriter which intercepts the 404 response code, but I'll leave that exercise to you.
Here a handler which sends a redirect to "/" if file is not found. This comes in handy when adding a fallback for an Angular application, as suggested here, which is served from within a golang service.
Note: This code is not production ready. Only illustrative (at best :-)
package main
import "net/http"
type (
// FallbackResponseWriter wraps an http.Requesthandler and surpresses
// a 404 status code. In such case a given local file will be served.
FallbackResponseWriter struct {
WrappedResponseWriter http.ResponseWriter
FileNotFound bool
}
)
// Header returns the header of the wrapped response writer
func (frw *FallbackResponseWriter) Header() http.Header {
return frw.WrappedResponseWriter.Header()
}
// Write sends bytes to wrapped response writer, in case of FileNotFound
// It surpresses further writes (concealing the fact though)
func (frw *FallbackResponseWriter) Write(b []byte) (int, error) {
if frw.FileNotFound {
return len(b), nil
}
return frw.WrappedResponseWriter.Write(b)
}
// WriteHeader sends statusCode to wrapped response writer
func (frw *FallbackResponseWriter) WriteHeader(statusCode int) {
Log.Printf("INFO: WriteHeader called with code %d\n", statusCode)
if statusCode == http.StatusNotFound {
Log.Printf("INFO: Setting FileNotFound flag\n")
frw.FileNotFound = true
return
}
frw.WrappedResponseWriter.WriteHeader(statusCode)
}
// AddFallbackHandler wraps the handler func in another handler func covering authentication
func AddFallbackHandler(handler http.HandlerFunc, filename string) http.HandlerFunc {
Log.Printf("INFO: Creating fallback handler")
return func(w http.ResponseWriter, r *http.Request) {
Log.Printf("INFO: Wrapping response writer in fallback response writer")
frw := FallbackResponseWriter{
WrappedResponseWriter: w,
FileNotFound: false,
}
handler(&frw, r)
if frw.FileNotFound {
Log.Printf("INFO: Serving fallback")
http.Redirect(w, r, "/", http.StatusSeeOther)
}
}
}
It can be added as in this example (using goji as mux):
mux.Handle(pat.Get("/*"),
AddFallbackHandler(http.FileServer(http.Dir("./html")).ServeHTTP, "/"))
I'm trying to chain HTTP handlers in go to provide some added functionality, like this:
package router
import (
// snip
"github.com/gorilla/mux"
"github.com/gorilla/handlers"
"net/http"
)
// snip
r := mux.NewRouter()
/* routing code */
var h http.Handler
h = r
if useGzip {
h = handlers.CompressHandler(h)
}
if useLogFile {
fn := pathToLog
accessLog, err := os.OpenFile(fn, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0666)
if err != nil {
panic(err)
}
h = handlers.CombinedLoggingHandler(accessLog, h)
}
// etc...
The problem is, if any HTTP headers are already set by one of the controllers that the gorilla/mux router points to (for example, w.WriteHeader(404) or w.Header().Set("Content-Type", "application/json")) - this silently breaks any "wrapper" handler trying to set or add its own headers, like the compress handler. I can't see any errors, unless I forgot to catch one somewhere, but the browser gets an invalid response.
Is there any graceful way to deal with this, short of just stashing the headers somewhere and then leaving the final handler to write them? It seems like that would mean rewriting the handlers' code, which I'd love to avoid if at all possible.
Once you call w.WriteHeader(404), the header goes on a wire. So you can't add to it anymore.
Best way you can do is to buffer status code and write it at the end of a chain.
For example, you can provide your own wrapper for http.ResponseWriter that would re-implement WriteHeader() to save status value. Then add method Commit() to actually write it.
Call Commit() in the last handler. You have to determine somehow which handler is last, of course.
I experienced the same silently-failing behaviour. But only in handlers where I did WritheHeader to set a status code other than StatusOK. I think things went wrong in this part of CompressHandler:
if h.Get("Content-Type") == "" {
h.Set("Content-Type", http.DetectContentType(b))
}
Which appears to be resolved when explicitly setting the content type in my own handler:
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(code)