Skip to content

Instantly share code, notes, and snippets.

@lukearno
Last active October 6, 2018 21:37
Show Gist options
  • Save lukearno/567288d4e102213553c2fac2946a91fc to your computer and use it in GitHub Desktop.
Save lukearno/567288d4e102213553c2fac2946a91fc to your computer and use it in GitHub Desktop.
// inspired by https://blog.merovius.de/2017/06/18/how-not-to-use-an-http-router.html
package main
import (
"fmt"
"net/http"
"path"
"strconv"
"strings"
)
func GETRoot(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintf(w, "GET Root")
}
func GETTag(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintf(w, "GET Tag")
}
func GETCustomers(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintf(w, "GET Customers")
}
func GETCustomer(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintf(w, "GET Customer %d", c.CustId)
}
func GETOrders(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintf(w, "GET Orders for Customer %d", c.CustId)
}
func GETOrder(w http.ResponseWriter, r *http.Request, c *Context) {
fmt.Fprintf(w, "GET Order %d for Customer %d", c.OrderId, c.CustId)
}
type Path struct {
Parts []string
Index int
}
func (p *Path) Next() (next string) {
if !p.Done() {
next = p.Parts[p.Index]
p.Index++
}
return
}
func (p *Path) This() (this string) {
if p.Index > 0 {
this = p.Parts[p.Index-1]
}
return
}
func (p *Path) Done() bool {
return p.Index == len(p.Parts)
}
type Handler func(w http.ResponseWriter, r *http.Request, c *Context)
type Endpoint struct {
GET Handler
POST Handler
PUT Handler
DELETE Handler
}
func Dispatch(w http.ResponseWriter, r *http.Request, c *Context, e *Endpoint) {
var h Handler
switch r.Method {
case "GET":
h = e.GET
case "POST":
h = e.POST
case "PUT":
h = e.PUT
case "DELETE":
h = e.DELETE
}
if h != nil {
h(w, r, c) // middleware here
} else {
http.Error(w, "Not Allowed", http.StatusMethodNotAllowed)
}
}
type Context struct {
CustId int
OrderId int
}
func Route(w http.ResponseWriter, r *http.Request) {
pth := Path{strings.Split(strings.Trim(path.Clean(r.URL.Path), "/"), "/"), 0}
cxt := Context{}
switch pth.Next() {
case "":
Dispatch(w, r, &cxt, &Endpoint{GET: GETRoot})
case "tag":
Dispatch(w, r, &cxt, &Endpoint{GET: GETTag})
case "customers":
switch pth.Next() {
case "":
Dispatch(w, r, &cxt, &Endpoint{GET: GETCustomers})
default:
i, err := strconv.Atoi(pth.This())
if err != nil {
goto notfound
}
cxt.CustId = i
switch pth.Next() {
case "":
Dispatch(w, r, &cxt, &Endpoint{GET: GETCustomer})
case "orders":
switch pth.Next() {
case "":
Dispatch(w, r, &cxt, &Endpoint{GET: GETOrders})
default:
i, err = strconv.Atoi(pth.This())
if err != nil {
goto notfound
}
cxt.OrderId = i
Dispatch(w, r, &cxt, &Endpoint{GET: GETOrder})
}
}
}
}
return
notfound:
http.Error(w, "Not Found", http.StatusNotFound)
}
type API struct{}
func (a *API) ServeHTTP(w http.ResponseWriter, r *http.Request) {
Route(w, r)
}
func main() {
http.ListenAndServe(":8000", &API{})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment