Skip to content

Commit efbe19a

Browse files
initial implementation with README
0 parents  commit efbe19a

File tree

2 files changed

+145
-0
lines changed

2 files changed

+145
-0
lines changed

README.md

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
Introduction
2+
============
3+
4+
This is an extremely minimal router, for usage in your own application. Supports only 2 things:
5+
* routing based on request-method and path
6+
* parameters in URLs
7+
8+
I build it for a json-only REST api with a few routes.
9+
10+
Usage
11+
=====
12+
13+
This intentionally does NOT implement `ServeHTTP`, as you should implement this
14+
yourself, to do any wrapping and processing of request or response.
15+
16+
Example
17+
=======
18+
19+
```go
20+
package main
21+
22+
import (
23+
"encoding/json"
24+
"github.com/parse-nl/MinimalRouter"
25+
"net/http"
26+
)
27+
28+
type appController struct{}
29+
type appRequest struct{ Params map[string]string }
30+
type appResponse struct{ Result string }
31+
32+
var router *minimalrouter.Router
33+
34+
func init() {
35+
router = minimalrouter.New()
36+
37+
router.Add("GET", "/ping", handleGetPing)
38+
router.Add("POST", "/hello-from/:name", handlePostHello)
39+
}
40+
41+
func main() {
42+
http.ListenAndServe(":8088", &appController{})
43+
}
44+
45+
// implement the standard ServeHTTP as required by golang
46+
// handles json encoding and decoding, could implement authentication as well
47+
func (c *appController) ServeHTTP(w http.ResponseWriter, r *http.Request) {
48+
var result *appResponse
49+
50+
fn, params := router.Match(r.Method, r.URL.Path)
51+
request := &appRequest{params}
52+
53+
if fn == nil {
54+
w.WriteHeader(http.StatusNotFound)
55+
result = &appResponse{"error"}
56+
} else {
57+
w.WriteHeader(http.StatusOK)
58+
59+
// convert the generic interface{} to the function-signature we expect
60+
result = fn.(func(*appRequest) *appResponse)(request)
61+
}
62+
63+
w.Header().Add("Content-Type", "application/json")
64+
enc := json.NewEncoder(w)
65+
enc.Encode(result)
66+
}
67+
68+
func handleGetPing(r *appRequest) *appResponse {
69+
return &appResponse{"pong"}
70+
}
71+
72+
func handlePostHello(r *appRequest) *appResponse {
73+
return &appResponse{"hey " + r.Params["name"]}
74+
}
75+
76+
```

router.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package minimalrouter
2+
3+
import "strings"
4+
5+
func New() *Router {
6+
return &Router{children: make(map[string]*Router)}
7+
}
8+
9+
// Represents both the Router and each node
10+
type Router struct {
11+
children map[string]*Router
12+
varName string
13+
handler interface{}
14+
}
15+
16+
func (this *Router) Add(method, path string, h interface{}) {
17+
if path[0] != '/' {
18+
panic("path must begin with '/' in: " + path)
19+
} else if path[len(path)-1] == '/' {
20+
panic("path must not end with '/' in: " + path)
21+
}
22+
23+
parts := append([]string{method}, strings.Split(path[1:], "/")...)
24+
node := this
25+
26+
for _, p := range parts {
27+
if p[0] == ':' && len(p) > 2 {
28+
varName := p[1:]
29+
p = ":"
30+
31+
if node.varName == "" {
32+
node.varName = varName
33+
} else if node.varName != varName {
34+
panic("conflict while adding " + path + "; parameter conflicts with :" + node.varName)
35+
}
36+
}
37+
38+
n, ok := node.children[p]
39+
if !ok {
40+
node.children[p] = New()
41+
n = node.children[p]
42+
}
43+
44+
node = n
45+
}
46+
47+
node.handler = h
48+
}
49+
50+
func (this *Router) Match(method, path string) (interface{}, map[string]string) {
51+
parts := append([]string{method}, strings.Split(path[1:], "/")...)
52+
params := make(map[string]string)
53+
node := this
54+
55+
for _, p := range parts {
56+
n, ok := node.children[p]
57+
58+
if !ok && node.varName != "" {
59+
n = node.children[":"]
60+
params[node.varName] = p
61+
} else if !ok {
62+
return nil, nil
63+
}
64+
65+
node = n
66+
}
67+
68+
return node.handler, params
69+
}

0 commit comments

Comments
 (0)