Tags: #go #golang #http #routing #mux #http
package main
type App struct {
// We could use http.Handler as a type here; using the specific type has
// the advantage that static analysis tools can link directly from
// h.UserHandler.ServeHTTP to the correct definition. The disadvantage is
// that we have slightly stronger coupling. Do the tradeoff yourself.
UserHandler *UserHandler
}
func (h *App) ServeHTTP(res http.ResponseWriter, req *http.Request) {
var head string
head, req.URL.Path = ShiftPath(req.URL.Path)
if head == "user" {
h.UserHandler.ServeHTTP(res, req)
return
}
http.Error(res, "Not Found", http.StatusNotFound)
}
type UserHandler struct {
}
func (h *UserHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
var head string
head, req.URL.Path = ShiftPath(req.URL.Path)
id, err := strconv.Atoi(head)
if err != nil {
http.Error(res, fmt.Sprintf("Invalid user id %q", head), http.StatusBadRequest)
return
}
switch req.Method {
case "GET":
h.handleGet(id)
case "PUT":
h.handlePut(id)
default:
http.Error(res, "Only GET and PUT are allowed", http.StatusMethodNotAllowed)
}
}
func main() {
a := &App{
UserHandler: new(UserHandler),
}
http.ListenAndServe(":8000", a)
}
///////////////////////////////////////////////////////////////////////////////////////////////
// ADDITIONAL MODIFICATION FOR NESTED HANDLERS
///////////////////////////////////////////////////////////////////////////////////////////////
type UserHandler struct{
ProfileHandler *ProfileHandler
}
func (h *UserHandler) ServeHTTP(res http.ResponseWriter, req *http.Request) {
var head string
head, req.URL.Path = ShiftPath(req.URL.Path)
id, err := strconv.Atoi(head)
if err != nil {
http.Error(res, fmt.Sprintf("Invalid user id %q", head), http.StatusBadRequest)
return
}
if req.URL.Path != "/" {
head, tail := ShiftPath(req.URL.Path)
switch head {
case "profile":
// We can't just make ProfileHandler an http.Handler; it needs the
// user id. Let's instead…
h.ProfileHandler.Handler(id).ServeHTTP(res, req)
case "account":
// Left as an exercise to the reader.
default:
http.Error(res, "Not Found", http.StatusNotFound)
}
return
}
// As before
...
}
type ProfileHandler struct {
}
func (h *ProfileHandler) Handler(id int) http.Handler {
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
// Do whatever
})
}
package main
import (
"fmt"
"io"
"net/http"
)
func indexHandler(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case "GET":
// Serve the resource.
fmt.Fprintf(w, "Hello %s", r.URL.Path[1:])
case "POST":
// Create a new record.
http.Error(w, "Nope. Method Not Allowed", http.StatusMethodNotAllowed)
case "PUT":
// Update an existing record.
http.Error(w, "Nope. Method Not Allowed", http.StatusMethodNotAllowed)
case "DELETE":
// Remove the record.
http.Error(w, "Nope. Method Not Allowed", http.StatusMethodNotAllowed)
default:
// Give an error message.
http.Error(w, "Nope. Method Not Allowed", http.StatusMethodNotAllowed)
}
}
type statusHandler int
func (s statusHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
code := int(s)
w.WriteHeader(code)
io.WriteString(w, http.StatusText(code))
}
// this is functionally equivalent to http.HandleFunc
type convertFunctionToHandler func(w http.ResponseWriter, r *http.Request)
func (c convertFunctionToHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// calls whatever non-standard function was converted into this type
// e.g. we do that like so: convertFunctionToHandler(randomHandler)
c(w, r)
}
// normally this function wouldn't valid when used as a handler passed into a
// http.Handle call, and so we don't do that. Instead we pass this function
// into convertFunctionToHandler to ensure this function is then converted into
// a type that would be valid for using with http.Handle
func randomHandler(w http.ResponseWriter, r *http.Request) {
// called by convertFunctionToHandler.ServeHTTP
io.WriteString(w, "Random World!")
}
var (
notFoundHandler = statusHandler(404)
serverErrorHandler = statusHandler(500)
)
/*
types
go doc http.Handler
go doc http.HandlerFunc
funcs
go doc http.Handle
go doc http.HandleFunc
`go doc` also displays any methods attached to a type, such as with
`go doc http.Handler` and `go doc http.HandlerFunc`
*/
func main() {
// HandleFunc is functionally equivalent to convertFunctionToHandler in that
// it internally converts the given non-standard function into something that
// supports the http.Handler interface (required by DefaultServeMux or your
// own custom mux instance). It does this by passing the non-standard
// function into http.HandlerFunc which does have a ServeHTTP method and that
// method calls your original non-standard function.
http.HandleFunc("/", indexHandler)
// http.Handle is similar to http.HandleFunc, but instead it doesn't attempt
// to coerce the given non-standard function into something that supports
// http.Handler, it instead will just fail to compile if it doesn't support
// the right interface.
http.Handle("/beepboop", convertFunctionToHandler(randomHandler))
http.Handle("/notfound", statusHandler(404))
http.Handle("/notfound2", notFoundHandler)
http.Handle("/error", statusHandler(500))
http.Handle("/error2", serverErrorHandler)
// notice we can't use statusHandler with http.HandleFunc because the type is
// an int and not a func type with the expected signature.
//
// http.HandleFunc("/error", statusHandler(500))
http.ListenAndServe(":9000", nil)
}
/*
http.Handler = interface
you support http.Handler if you have a `ServeHTTP(w http.ResponseWriter, r *http.Request)` method available.
http.Handle("/", <give me something that supports the http.Handler interface>)
e.g. an object with a ServeHTTP method
http.HandleFunc("/", <give me any function with the same signature as ServeHTTP >)
e.g. a function that accepts the arguments `(w http.ResponseWriter, r *http.Request)`
http.HandlerFunc = func type used internally by http.HandleFunc
e.g. it adapts the given function to the http.HandlerFunc type and that has a ServeHTTP method which then calls your given function
*/