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:
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())
+}