novelty.go

Single word (yes/no) site for app engine in go
git clone https://wehaveforgeathome.hates.computer/novelty.go.git
Log | Files | Refs | LICENSE

commit c9365bd271252ea3a9ccb328bd3f8c1d5e53615f
parent 186a9d038213706465dc6004e25f2bd85d93565f
Author: Ryan Wolf <rwolf@borderstylo.com>
Date:   Wed, 28 Mar 2012 00:41:19 -0700

Move router to it's own file, added unit tests.

Diffstat:
Mindex.template | 2+-
Mnovelty/novelty.go | 52++++++++++------------------------------------------
Anovelty/router/Makefile | 7+++++++
Anovelty/router/router.go | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Anovelty/router/router_test.go | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 131 insertions(+), 43 deletions(-)

diff --git a/index.template b/index.template @@ -1,6 +1,6 @@ <html> <head> - <title>{{html .Question}}</title> + <title>Is Ryan at the Office?</title> </head> <body> <h1>{{html .Answer}}</h1> diff --git a/novelty/novelty.go b/novelty/novelty.go @@ -1,72 +1,40 @@ -package hello +package novelty import ( "http" - "strings" + "novelty/router" "template" ) -var question = "Is Ryan at the Office?" var answer = "yes" var rootTemplate = template.Must(template.New("").ParseFile("index.template")) type Context struct{ - Question string Answer string } -type RequestHandler func(w http.ResponseWriter, r *http.Request) - -var routes = make(map[string]map[string]RequestHandler) - -func addRoute(path string, method string, handler RequestHandler) { - if routes[path] == nil { - routes[path] = make(map[string]RequestHandler) - } - routes[path][method] = handler -} - -func router(w http.ResponseWriter, r *http.Request) { - route := routes[r.URL.Path] - if route == nil { - http.Error(w, "Not Found", http.StatusNotFound) - return - } - handler := route[r.Method] - if handler == nil { - //TODO: Is there a cleaner way to get a comma-seperated list of keys? - methods := make([]string, len(route)) - i := 0 - for m, _ := range route { - methods[i] = m - i++ - } - w.Header().Set("Allow", strings.Join(methods, ",")) - http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) - return - } - handler(w, r) -} - func init() { - addRoute("/", "GET", func(w http.ResponseWriter, r *http.Request) { + router := router.Router{} + router.Get("/", func(w http.ResponseWriter, r *http.Request) { // TODO: Is there a way to skip the type and use an inline struct? - context := Context{ Question: question, Answer: answer } + context := Context{ Answer: answer } err := rootTemplate.Execute(w, context) if err != nil { http.Error(w, err.String(), http.StatusInternalServerError) } }) - addRoute("/yes", "GET", func(w http.ResponseWriter, r *http.Request) { + router.Get("/yes", func(w http.ResponseWriter, r *http.Request) { answer = "yes" http.Redirect(w, r, "/", http.StatusFound) }) - addRoute("/no", "GET", func(w http.ResponseWriter, r *http.Request) { + router.Get("/no", func(w http.ResponseWriter, r *http.Request) { answer = "no" http.Redirect(w, r, "/", http.StatusFound) }) - http.HandleFunc("/", router) + http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + router.HandleRequest(w, r) + }) } diff --git a/novelty/router/Makefile b/novelty/router/Makefile @@ -0,0 +1,7 @@ +include $(GOROOT)/src/Make.inc + +TARG=router +GOFILES=\ + router.go\ + +include $(GOROOT)/src/Make.pkg diff --git a/novelty/router/router.go b/novelty/router/router.go @@ -0,0 +1,52 @@ +package router + +import ( + "http" + "strings" +) + +func allowedMethods(r map[string]requestHandler) string { + //TODO: Is there a cleaner way to get a comma-seperated list of keys? + ms := make([]string, len(r)) + i := 0 + for m, _ := range r { + ms[i] = m + i++ + } + return strings.Join(ms, ",") +} + +type requestHandler func(w http.ResponseWriter, r *http.Request) + +type Router struct { + Routes map[string]map[string]requestHandler +} + +func (r *Router) addRoute(p string, m string, h requestHandler) { + if r.Routes == nil { + r.Routes = make(map[string]map[string]requestHandler) + } + if r.Routes[p] == nil { + r.Routes[p] = make(map[string]requestHandler) + } + r.Routes[p][m] = h +} + +func (r *Router) Get(p string, h requestHandler) { + r.addRoute(p, "GET", h) +} + +func (router *Router) HandleRequest(w http.ResponseWriter, r *http.Request) { + route := router.Routes[r.URL.Path] + if route == nil { + http.Error(w, "Not Found", http.StatusNotFound) + return + } + h := route[r.Method] + if h == nil { + w.Header().Set("Allow", allowedMethods(route)) + http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed) + return + } + h(w, r) +} diff --git a/novelty/router/router_test.go b/novelty/router/router_test.go @@ -0,0 +1,61 @@ +package router + +import ( + "bytes" + "http" + "http/httptest" + "testing" +) + +func Expect(t *testing.T, e, a interface{}) { + if a != e { + t.Errorf("expected %s got %s", e, a) + } +} + +func TestAllowedMethods(t *testing.T) { + handler := func(w http.ResponseWriter, r *http.Request) {} + route := map[string]requestHandler{} + + route["bees"] = handler + Expect(t, "bees", allowedMethods(route)) + route["birds"] = handler + Expect(t, "birds,bees", allowedMethods(route)) +} + +func TestNotFound(t *testing.T) { + req, _ := http.NewRequest("DELETE", "/bees", nil) + res := httptest.NewRecorder() + router := Router{} + router.HandleRequest(res, req) + + Expect(t, 404, res.Code) + Expect(t, "Not Found\n", res.Body.String()) +} + +func TestMethodNotAllowed(t *testing.T) { + req, _ := http.NewRequest("DELETE", "/bees", nil) + res := httptest.NewRecorder() + handler := func(w http.ResponseWriter, r *http.Request) {} + router := Router{} + router.Get("/bees", handler) + router.HandleRequest(res, req) + + Expect(t, 405, res.Code) + Expect(t, "GET", res.Header().Get("Allow")) + Expect(t, "Method Not Allowed\n", res.Body.String()) +} + +func TestGet(t *testing.T) { + req, _ := http.NewRequest("GET", "/bees", nil) + res := httptest.NewRecorder() + handler := func(w http.ResponseWriter, r *http.Request) { + bytes.NewBufferString("bees!").WriteTo(w) + } + router := Router{} + router.Get("/bees", handler) + router.HandleRequest(res, req) + + Expect(t, 200, res.Code) + Expect(t, "bees!", res.Body.String()) +}