« Back to Index

[Go Standard Lib HTTP Routing]

View original Gist on GitHub

Tags: #go #golang #http #routing #mux #http

1. Simple and Non-Confusing.go

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

2. Go Standard Lib HTTP Routing.go

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
*/