For our chat application, we implemented our own http.Handler type (the room) in order to easily compile, execute, and deliver HTML content to browsers. Since this is a very simple but powerful interface, we are going to continue to use it wherever possible when adding functionality to our HTTP processing.
In order to determine whether a user is allowed to proceed, we will create an authorization wrapper handler that will perform the check and pass the execution on to the inner handler only if the user is authorized.
Our wrapper handler will satisfy the same http.Handler interface as the object inside it, allowing us to wrap any valid handler. In fact, even the authentication handler we are about to write could be later encapsulated inside a similar wrapper if required.
Chaining pattern when applied to HTTP handlers
The preceding diagram shows how this pattern could be applied in a more complicated HTTP handler scenario. Each object implements the http.Handler interface. This means that an object could be passed to the http.Handle method to directly handle a request, or it can be given to another object, which could add some kind of extra functionality. The Logging handler may write to a log file before and after the ServeHTTP method is called on the inner handler. Because the inner handler is just another http.Handler, any other handler can be wrapped in (or decorated with) the Logging handler.
It is also common for an object to contain logic that decides which inner handler should be executed. For example, our authentication handler will either pass the execution to the wrapped handler, or handle the request itself by issuing a redirect to the browser.
That's plenty of theory for now; let's write some code. Create a new file called auth.go in the chat folder:
package main
import ("net/http")
type authHandler struct {
next http.Handler
}
func (h *authHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
_, err := r.Cookie("auth")
if err == http.ErrNoCookie {
// not authenticated
w.Header().Set("Location", "/login")
w.WriteHeader(http.StatusTemporaryRedirect)
return
}
if err != nil {
// some other error
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// success - call the next handler
h.next.ServeHTTP(w, r)
}
func MustAuth(handler http.Handler) http.Handler {
return &authHandler{next: handler}
}
The authHandler type not only implements the ServeHTTP method (which satisfies the http.Handler interface), but also stores (wraps) http.Handler in the next field. Our MustAuth helper function simply creates authHandler that wraps any other http.Handler. This is the pattern that allows us to easily add authorization to our code in main.go.
Let's change the first argument to make it explicit about the page meant for chatting. Next, let's use the MustAuth function to wrap templateHandler for the second argument:
Wrapping templateHandler with the MustAuth function will cause the execution to run through authHandler first; it will run only to templateHandler if the request is authenticated.
The ServeHTTP method in authHandler will look for a special cookie called auth, and it will use the Header and WriteHeader methods on http.ResponseWriter to redirect the user to a login page if the cookie is missing. Notice that we discard the cookie itself using the underscore character and capture only the returning error; this is because we only care about whether the cookie is present at this point.
Build and run the chat application and try to hit http://localhost:8080/chat:
go build -o chat./chat -host=":8080"
Tip
You need to delete your cookies to clear out previous authentication tokens or any other cookies that might be left over from other development projects served through the localhost.
If you look in the address bar of your browser, you will notice that you are immediately redirected to the /login page. Since we cannot handle that path yet, you'll just get a 404 page not found error.