├── README.md ├── regtree ├── router_test.go ├── tree_test.go ├── router.go ├── tree.go └── github_test.go └── regexp ├── router_test.go ├── router.go └── github_test.go /README.md: -------------------------------------------------------------------------------- 1 | # router 2 | baa module providers another router. 3 | 4 | baa support replace router, like this: 5 | 6 | ``` 7 | package main 8 | 9 | import ( 10 | "github.com/go-baa/baa" 11 | "github.com/go-baa/router/regtree" 12 | ) 13 | 14 | func main() { 15 | app := baa.Default() 16 | app.SetDI("router", regtree.New(app)) 17 | 18 | app.Get("/view-:id(\\d+)", func(c *baa.Context) { 19 | c.String(200, c.Param("id")) 20 | }) 21 | app.Get("/view-:id(\\d+)/project", func(c *baa.Context) { 22 | c.String(200, c.Param("id")+"/project") 23 | }) 24 | app.Run(":1323") 25 | } 26 | ``` -------------------------------------------------------------------------------- /regtree/router_test.go: -------------------------------------------------------------------------------- 1 | package regtree 2 | 3 | import ( 4 | "io" 5 | "net/http" 6 | "net/http/httptest" 7 | "testing" 8 | 9 | "github.com/go-baa/baa" 10 | . "github.com/smartystreets/goconvey/convey" 11 | ) 12 | 13 | var b = baa.New() 14 | var r = New(b) 15 | var f = func(c *baa.Context) {} 16 | var c = baa.NewContext(nil, nil, b) 17 | 18 | func init() { 19 | b.SetDI("router", r) 20 | } 21 | 22 | func BenchmarkMatch1(b *testing.B) { 23 | router := loadBaaSingle("GET", "/user/:name/:id", f) 24 | r, _ := http.NewRequest("GET", "/user/gordon/123", nil) 25 | benchRequest(b, router, r) 26 | } 27 | 28 | type route struct { 29 | method string 30 | path string 31 | } 32 | 33 | type mockResponseWriter struct{} 34 | 35 | func (m *mockResponseWriter) Header() (h http.Header) { 36 | return http.Header{} 37 | } 38 | 39 | func (m *mockResponseWriter) Write(p []byte) (n int, err error) { 40 | return len(p), nil 41 | } 42 | 43 | func (m *mockResponseWriter) WriteString(s string) (n int, err error) { 44 | return len(s), nil 45 | } 46 | 47 | func (m *mockResponseWriter) WriteHeader(int) {} 48 | 49 | // Baa 50 | func baaHandler(c *baa.Context) { 51 | } 52 | 53 | func baaHandlerWrite(c *baa.Context) { 54 | io.WriteString(c.Resp, c.Param("name")) 55 | } 56 | 57 | func baaHandlerTest(c *baa.Context) { 58 | io.WriteString(c.Resp, c.Req.RequestURI) 59 | } 60 | func loadBaa(routes []route) http.Handler { 61 | var h baa.HandlerFunc = baaHandler 62 | // if loadTestHandler { 63 | // h = baaHandlerTest 64 | // } 65 | 66 | b := baa.New() 67 | b.SetDI("router", New(b)) 68 | for _, r := range routes { 69 | switch r.method { 70 | case "GET": 71 | b.Get(r.path, h) 72 | case "POST": 73 | b.Post(r.path, h) 74 | case "PUT": 75 | b.Put(r.path, h) 76 | case "PATCH": 77 | b.Patch(r.path, h) 78 | case "DELETE": 79 | b.Delete(r.path, h) 80 | default: 81 | panic("Unknow HTTP method: " + r.method) 82 | } 83 | } 84 | return b 85 | } 86 | 87 | func loadBaaSingle(method, path string, h baa.HandlerFunc) http.Handler { 88 | b := baa.New() 89 | b.SetDI("router", New(b)) 90 | switch method { 91 | case "GET": 92 | b.Get(path, h) 93 | case "POST": 94 | b.Post(path, h) 95 | case "PUT": 96 | b.Put(path, h) 97 | case "PATCH": 98 | b.Patch(path, h) 99 | case "DELETE": 100 | b.Delete(path, h) 101 | default: 102 | panic("Unknow HTTP method: " + method) 103 | } 104 | return b 105 | } 106 | 107 | func benchRequest(b *testing.B, router http.Handler, r *http.Request) { 108 | w := new(mockResponseWriter) 109 | u := r.URL 110 | rq := u.RawQuery 111 | r.RequestURI = u.RequestURI() 112 | 113 | b.ReportAllocs() 114 | b.ResetTimer() 115 | 116 | for i := 0; i < b.N; i++ { 117 | u.RawQuery = rq 118 | router.ServeHTTP(w, r) 119 | } 120 | } 121 | 122 | func benchRoutes(b *testing.B, router http.Handler, routes []route) { 123 | w := new(mockResponseWriter) 124 | r, _ := http.NewRequest("GET", "/", nil) 125 | u := r.URL 126 | rq := u.RawQuery 127 | 128 | b.ReportAllocs() 129 | b.ResetTimer() 130 | 131 | for i := 0; i < b.N; i++ { 132 | for _, route := range routes { 133 | r.Method = route.method 134 | r.RequestURI = route.path 135 | u.Path = route.path 136 | u.RawQuery = rq 137 | router.ServeHTTP(w, r) 138 | } 139 | } 140 | } 141 | 142 | func request(method, uri string) *httptest.ResponseRecorder { 143 | req, _ := http.NewRequest(method, uri, nil) 144 | w := httptest.NewRecorder() 145 | b.ServeHTTP(w, req) 146 | return w 147 | } 148 | 149 | func TestNameRoute(t *testing.T) { 150 | Convey("name route", t, func() { 151 | b.Get("/foo/bar/:p", func(c *baa.Context) {}).Name("foo_bar") 152 | h, name := b.Router().Match("GET", "/foo/bar/abc", c) 153 | So(h, ShouldNotBeNil) 154 | So(name, ShouldEqual, "foo_bar") 155 | }) 156 | } 157 | -------------------------------------------------------------------------------- /regexp/router_test.go: -------------------------------------------------------------------------------- 1 | package regexp 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | "net/http/httptest" 8 | "testing" 9 | 10 | "github.com/go-baa/baa" 11 | . "github.com/smartystreets/goconvey/convey" 12 | ) 13 | 14 | var b = baa.New() 15 | var r = New(b) 16 | var f = func(c *baa.Context) {} 17 | var c = baa.NewContext(nil, nil, b) 18 | 19 | func init() { 20 | b.SetDI("router", r) 21 | } 22 | 23 | func BenchmarkMatch1(b *testing.B) { 24 | router := loadBaaSingle("GET", "/user/:name/:age/:class", f) 25 | r, _ := http.NewRequest("GET", "/user/gordon/123/c12", nil) 26 | benchRequest(b, router, r) 27 | } 28 | 29 | type route struct { 30 | method string 31 | path string 32 | } 33 | 34 | type mockResponseWriter struct{} 35 | 36 | func (m *mockResponseWriter) Header() (h http.Header) { 37 | return http.Header{} 38 | } 39 | 40 | func (m *mockResponseWriter) Write(p []byte) (n int, err error) { 41 | return len(p), nil 42 | } 43 | 44 | func (m *mockResponseWriter) WriteString(s string) (n int, err error) { 45 | return len(s), nil 46 | } 47 | 48 | func (m *mockResponseWriter) WriteHeader(int) {} 49 | 50 | // Baa 51 | func baaHandler(c *baa.Context) { 52 | } 53 | 54 | func baaHandlerWrite(c *baa.Context) { 55 | io.WriteString(c.Resp, c.Param("name")) 56 | } 57 | 58 | func baaHandlerTest(c *baa.Context) { 59 | io.WriteString(c.Resp, c.Req.RequestURI) 60 | } 61 | func loadBaa(routes []route) http.Handler { 62 | var h baa.HandlerFunc = baaHandler 63 | // if loadTestHandler { 64 | // h = baaHandlerTest 65 | // } 66 | 67 | b := baa.New() 68 | b.SetDI("router", New(b)) 69 | for _, r := range routes { 70 | switch r.method { 71 | case "GET": 72 | b.Get(r.path, h) 73 | case "POST": 74 | b.Post(r.path, h) 75 | case "PUT": 76 | b.Put(r.path, h) 77 | case "PATCH": 78 | b.Patch(r.path, h) 79 | case "DELETE": 80 | b.Delete(r.path, h) 81 | default: 82 | panic("Unknow HTTP method: " + r.method) 83 | } 84 | } 85 | return b 86 | } 87 | 88 | func loadBaaSingle(method, path string, h baa.HandlerFunc) http.Handler { 89 | b := baa.New() 90 | b.SetDI("router", New(b)) 91 | switch method { 92 | case "GET": 93 | b.Get(path, h) 94 | case "POST": 95 | b.Post(path, h) 96 | case "PUT": 97 | b.Put(path, h) 98 | case "PATCH": 99 | b.Patch(path, h) 100 | case "DELETE": 101 | b.Delete(path, h) 102 | default: 103 | panic("Unknow HTTP method: " + method) 104 | } 105 | return b 106 | } 107 | 108 | func benchRequest(b *testing.B, router http.Handler, r *http.Request) { 109 | w := new(mockResponseWriter) 110 | u := r.URL 111 | rq := u.RawQuery 112 | r.RequestURI = u.RequestURI() 113 | 114 | b.ReportAllocs() 115 | b.ResetTimer() 116 | 117 | for i := 0; i < b.N; i++ { 118 | u.RawQuery = rq 119 | router.ServeHTTP(w, r) 120 | } 121 | } 122 | 123 | func benchRoutes(b *testing.B, router http.Handler, routes []route) { 124 | w := new(mockResponseWriter) 125 | r, _ := http.NewRequest("GET", "/", nil) 126 | u := r.URL 127 | rq := u.RawQuery 128 | 129 | b.ReportAllocs() 130 | b.ResetTimer() 131 | 132 | for i := 0; i < b.N; i++ { 133 | for _, route := range routes { 134 | r.Method = route.method 135 | r.RequestURI = route.path 136 | u.Path = route.path 137 | u.RawQuery = rq 138 | router.ServeHTTP(w, r) 139 | } 140 | } 141 | } 142 | 143 | func request(method, uri string) *httptest.ResponseRecorder { 144 | req, _ := http.NewRequest(method, uri, nil) 145 | w := httptest.NewRecorder() 146 | b.ServeHTTP(w, req) 147 | return w 148 | } 149 | 150 | func TestTreeRoutePrint2(t *testing.T) { 151 | b.Get("/abc/:id", f) 152 | b.Post("/abc/:id", f) 153 | Convey("print routes", t, func() { 154 | fmt.Println("") 155 | for method, routes := range r.Routes() { 156 | fmt.Println("Method: ", method) 157 | for i, route := range routes { 158 | fmt.Printf(" %3d %s\n", i, route) 159 | } 160 | } 161 | }) 162 | } 163 | 164 | func TestTreeRoutePrint3(t *testing.T) { 165 | b.Get("/user/:id", f).Name("user_get") 166 | b.Post("/user/:id", f).Name("user_update") 167 | Convey("print named routes", t, func() { 168 | fmt.Println("") 169 | for name, route := range r.NamedRoutes() { 170 | fmt.Printf("%20s \t %s\n", name, route) 171 | } 172 | }) 173 | } 174 | -------------------------------------------------------------------------------- /regtree/tree_test.go: -------------------------------------------------------------------------------- 1 | package regtree 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "testing" 7 | 8 | "github.com/go-baa/baa" 9 | . "github.com/smartystreets/goconvey/convey" 10 | ) 11 | 12 | type storeTreeEntry struct { 13 | pattern string 14 | params int 15 | } 16 | 17 | func TestTreeAdd(t *testing.T) { 18 | tests := []struct { 19 | id string 20 | entries []storeTreeEntry 21 | }{ 22 | { 23 | "all static", 24 | []storeTreeEntry{ 25 | {"/gopher/bumper.png", 0}, 26 | {"/gopher/bumper192x108.png", 0}, 27 | {"/gopher/doc.png", 0}, 28 | {"/gopher/bumper320x180.png", 0}, 29 | {"/gopher/docpage.png", 0}, 30 | {"/gopher/doc", 0}, 31 | }, 32 | }, 33 | { 34 | "parametric", 35 | []storeTreeEntry{ 36 | {"/users/:id", 1}, 37 | {"/users/:id/profile", 1}, 38 | {"/users/:id/:accnt(\\d+)/address", 2}, 39 | {"/users/:id/age", 1}, 40 | {"/users/:id/:accnt(\\d+)", 2}, 41 | }, 42 | }, 43 | { 44 | "corner cases", 45 | []storeTreeEntry{ 46 | {"/users/:id/test/:name", 2}, 47 | {"/users/abc/:id/:name", 2}, 48 | }, 49 | }, 50 | } 51 | 52 | Convey("tree add", t, func() { 53 | for _, test := range tests { 54 | h := NewTree("/", nil) 55 | Convey(test.id, func() { 56 | for _, entry := range test.entries { 57 | node := h.Add(entry.pattern, []baa.HandlerFunc{f}, nil) 58 | if node == nil { 59 | fmt.Printf("nil node: %#v", entry) 60 | } 61 | So(node, ShouldNotBeNil) 62 | if len(node.params) != entry.params { 63 | fmt.Printf("error node: %#v\n", node) 64 | } 65 | So(len(node.params), ShouldEqual, entry.params) 66 | } 67 | }) 68 | } 69 | }) 70 | } 71 | 72 | func TestStoreGet(t *testing.T) { 73 | pairs := []struct { 74 | pattern string 75 | handler baa.HandlerFunc 76 | }{ 77 | {"/gopher/bumper.png", func(c *baa.Context) { c.SetParam("value", "1"); c.JSON(200, c.Params()) }}, 78 | {"/gopher/bumper192x108.png", func(c *baa.Context) { c.SetParam("value", "2"); c.JSON(200, c.Params()) }}, 79 | {"/gopher/doc.png", func(c *baa.Context) { c.SetParam("value", "3"); c.JSON(200, c.Params()) }}, 80 | {"/gopher/bumper320x180.png", func(c *baa.Context) { c.SetParam("value", "4"); c.JSON(200, c.Params()) }}, 81 | {"/gopher/docpage.png", func(c *baa.Context) { c.SetParam("value", "5"); c.JSON(200, c.Params()) }}, 82 | {"/gopher/doc", func(c *baa.Context) { c.SetParam("value", "6"); c.JSON(200, c.Params()) }}, 83 | {"/users/:id", func(c *baa.Context) { c.SetParam("value", "7"); c.JSON(200, c.Params()) }}, 84 | {"/users/:id/profile", func(c *baa.Context) { c.SetParam("value", "8"); c.JSON(200, c.Params()) }}, 85 | {"/users/:id/:account(\\d+)/address", func(c *baa.Context) { c.SetParam("value", "9"); c.JSON(200, c.Params()) }}, 86 | {"/users/:id/age", func(c *baa.Context) { c.SetParam("value", "10"); c.JSON(200, c.Params()) }}, 87 | {"/users/:id/:account(\\d+)", func(c *baa.Context) { c.SetParam("value", "11"); c.JSON(200, c.Params()) }}, 88 | {"/users/:id/test/:name", func(c *baa.Context) { c.SetParam("value", "12"); c.JSON(200, c.Params()) }}, 89 | {"/users/abc/:id/:name", func(c *baa.Context) { c.SetParam("value", "13"); c.JSON(200, c.Params()) }}, 90 | {"/all/*", func(c *baa.Context) { c.SetParam("value", "14"); c.JSON(200, c.Params()) }}, 91 | } 92 | for _, pair := range pairs { 93 | b.Get(pair.pattern, pair.handler) 94 | } 95 | 96 | tests := []struct { 97 | pattern string 98 | value interface{} 99 | params map[string]string 100 | }{ 101 | {"/gopher/bumper.png", "1", nil}, 102 | {"/gopher/bumper192x108.png", "2", nil}, 103 | {"/gopher/doc.png", "3", nil}, 104 | {"/gopher/bumper320x180.png", "4", nil}, 105 | {"/gopher/docpage.png", "5", nil}, 106 | {"/gopher/doc", "6", nil}, 107 | {"/users/abc", "7", map[string]string{"id": "abc"}}, 108 | {"/users/abc/profile", "8", map[string]string{"id": "abc"}}, 109 | {"/users/abc/123/address", "13", map[string]string{"id": "123", "name": "address"}}, 110 | {"/users/abcd/age", "10", map[string]string{"id": "abcd"}}, 111 | {"/users/abc/123", "11", map[string]string{"id": "abc", "account": "123"}}, 112 | {"/users/abc/test/123", "13", map[string]string{"id": "test", "name": "123"}}, 113 | {"/users/abc/xyz/123", "13", map[string]string{"id": "xyz", "name": "123"}}, 114 | {"/g", nil, nil}, 115 | {"/all", nil, nil}, 116 | {"/all/", "14", nil}, 117 | {"/all/abc", "14", map[string]string{"": "abc"}}, 118 | {"/users/abc/xyz", nil, nil}, 119 | } 120 | Convey("tree get", t, func() { 121 | for _, test := range tests { 122 | resp := request("GET", test.pattern) 123 | if test.value == nil { 124 | So(resp.Code, ShouldEqual, 404) 125 | continue 126 | } 127 | body := resp.Body.String() 128 | values := make(map[string]string) 129 | err := json.Unmarshal([]byte(body), &values) 130 | if err != nil { 131 | t.Logf("json error: %v", err) 132 | } 133 | So(resp.Code, ShouldEqual, 200) 134 | So(values["value"], ShouldEqual, test.value) 135 | if len(test.params) > 0 { 136 | // for key, val := range test.params { 137 | // So(values[key], ShouldEqual, val) 138 | // } 139 | } 140 | } 141 | }) 142 | } 143 | 144 | func TestTreeRoutePrint2(t *testing.T) { 145 | b.Get("/abc/:id", f) 146 | b.Post("/abc/:id", f) 147 | Convey("print routes", t, func() { 148 | fmt.Println("") 149 | for method, routes := range r.Routes() { 150 | fmt.Println("Method: ", method) 151 | for i, route := range routes { 152 | fmt.Printf(" %3d %s\n", i, route) 153 | } 154 | } 155 | }) 156 | } 157 | 158 | func TestTreeRoutePrint3(t *testing.T) { 159 | b.Get("/user/:id", f).Name("user_get") 160 | b.Post("/user/:id", f).Name("user_update") 161 | Convey("print named routes", t, func() { 162 | fmt.Println("") 163 | for name, route := range r.NamedRoutes() { 164 | fmt.Printf("%20s \t %s\n", name, route) 165 | } 166 | }) 167 | } 168 | -------------------------------------------------------------------------------- /regtree/router.go: -------------------------------------------------------------------------------- 1 | package regtree 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | 7 | "github.com/go-baa/baa" 8 | ) 9 | 10 | // Router provlider router for baa 11 | type Router struct { 12 | autoHead bool 13 | autoTrailingSlash bool 14 | groups []*group 15 | nodes [baa.RouteLength]*Tree 16 | nameNodes map[string]*Node 17 | mu sync.RWMutex 18 | baa *baa.Baa 19 | } 20 | 21 | // Node is a router node 22 | type Node struct { 23 | paramNum int 24 | format string 25 | name string 26 | root *Router 27 | } 28 | 29 | // group route 30 | type group struct { 31 | pattern string 32 | handlers []baa.HandlerFunc 33 | } 34 | 35 | // New create a router instance 36 | func New(b *baa.Baa) baa.Router { 37 | r := new(Router) 38 | for _, i := range baa.RouterMethods { 39 | r.nodes[i] = NewTree("/", nil) 40 | } 41 | r.nameNodes = make(map[string]*Node) 42 | r.groups = make([]*group, 0) 43 | r.baa = b 44 | return r 45 | } 46 | 47 | // newNode create a route node 48 | func newNode(format string, paramsNum int, router *Router) *Node { 49 | n := new(Node) 50 | n.format = format 51 | n.paramNum = paramsNum 52 | n.root = router 53 | return n 54 | } 55 | 56 | // newGroup create a group router 57 | func newGroup() *group { 58 | g := new(group) 59 | g.handlers = make([]baa.HandlerFunc, 0) 60 | return g 61 | } 62 | 63 | // SetAutoHead sets the value who determines whether add HEAD method automatically 64 | // when GET method is added. Combo router will not be affected by this value. 65 | func (r *Router) SetAutoHead(v bool) { 66 | r.autoHead = v 67 | } 68 | 69 | // SetAutoTrailingSlash optional trailing slash. 70 | func (r *Router) SetAutoTrailingSlash(v bool) { 71 | r.autoTrailingSlash = v 72 | } 73 | 74 | // Match find matched route then returns handlers and name 75 | func (r *Router) Match(method, uri string, c *baa.Context) ([]baa.HandlerFunc, string) { 76 | return r.nodes[baa.RouterMethods[method]].Get(uri, c) 77 | } 78 | 79 | // URLFor use named route return format url 80 | func (r *Router) URLFor(name string, args ...interface{}) string { 81 | if name == "" { 82 | return "" 83 | } 84 | node := r.nameNodes[name] 85 | if node == nil || len(node.format) == 0 { 86 | return "" 87 | } 88 | format := make([]byte, len(node.format)) 89 | copy(format, node.format) 90 | for i := node.paramNum + 1; i <= len(args); i++ { 91 | format = append(format, "%v"...) 92 | } 93 | return fmt.Sprintf(string(format), args...) 94 | } 95 | 96 | // Routes returns registered route uri in a string slice 97 | func (r *Router) Routes() map[string][]string { 98 | routes := make(map[string][]string) 99 | for _, method := range baa.RouterMethodName { 100 | routes[method] = make([]string, 0) 101 | } 102 | for k := range r.nodes { 103 | routes[baa.RouterMethodName[k]] = r.routes(r.nodes[k]) 104 | } 105 | 106 | return routes 107 | } 108 | 109 | // routes print the route table 110 | func (r *Router) routes(l *Tree) []string { 111 | if l == nil { 112 | return nil 113 | } 114 | var data []string 115 | if l.handlers != nil { 116 | data = append(data, l.String()) 117 | } 118 | l.schildren = append(l.schildren, l.rchildren...) 119 | for i := range l.schildren { 120 | if l.schildren[i] != nil { 121 | data = append(data, r.routes(l.schildren[i])...) 122 | } 123 | } 124 | 125 | return data 126 | } 127 | 128 | // NamedRoutes returns named route uri in a string slice 129 | func (r *Router) NamedRoutes() map[string]string { 130 | routes := make(map[string]string) 131 | for k, v := range r.nameNodes { 132 | routes[k] = v.format 133 | } 134 | return routes 135 | } 136 | 137 | // Add registers a new handle with the given method, pattern and handlers. 138 | // add check training slash option. 139 | func (r *Router) Add(method, pattern string, handlers []baa.HandlerFunc) baa.RouteNode { 140 | if method == "GET" && r.autoHead { 141 | r.add("HEAD", pattern, handlers) 142 | } 143 | if r.autoTrailingSlash && (len(pattern) > 1 || len(r.groups) > 0) { 144 | var index byte 145 | if len(pattern) > 0 { 146 | index = pattern[len(pattern)-1] 147 | } 148 | if index == '/' { 149 | r.add(method, pattern[:len(pattern)-1], handlers) 150 | } else { 151 | r.add(method, pattern+"/", handlers) 152 | } 153 | } 154 | return r.add(method, pattern, handlers) 155 | } 156 | 157 | // GroupAdd add a group route has same prefix and handle chain 158 | func (r *Router) GroupAdd(pattern string, f func(), handlers []baa.HandlerFunc) { 159 | g := newGroup() 160 | g.pattern = pattern 161 | g.handlers = handlers 162 | r.groups = append(r.groups, g) 163 | 164 | f() 165 | 166 | r.groups = r.groups[:len(r.groups)-1] 167 | } 168 | 169 | // add registers a new request handle with the given method, pattern and handlers. 170 | func (r *Router) add(method, pattern string, handlers []baa.HandlerFunc) *Node { 171 | if _, ok := baa.RouterMethods[method]; !ok { 172 | panic("Router.add: unsupport http method [" + method + "]") 173 | } 174 | 175 | r.mu.Lock() 176 | defer r.mu.Unlock() 177 | 178 | // check group set 179 | if len(r.groups) > 0 { 180 | var gpattern string 181 | var ghandlers []baa.HandlerFunc 182 | for i := range r.groups { 183 | gpattern += r.groups[i].pattern 184 | if len(r.groups[i].handlers) > 0 { 185 | ghandlers = append(ghandlers, r.groups[i].handlers...) 186 | } 187 | } 188 | pattern = gpattern + pattern 189 | ghandlers = append(ghandlers, handlers...) 190 | handlers = ghandlers 191 | } 192 | 193 | // check pattern (for training slash move behind group check) 194 | if pattern == "" { 195 | panic("Router.add: pattern can not be emtpy!") 196 | } 197 | if pattern[0] != '/' { 198 | panic("Router.add: pattern must begin /") 199 | } 200 | 201 | nameNode := newNode("", 0, r) 202 | 203 | for i := 0; i < len(handlers); i++ { 204 | handlers[i] = baa.WrapHandlerFunc(handlers[i]) 205 | } 206 | 207 | node := r.nodes[baa.RouterMethods[method]].Add(pattern, handlers, nameNode) 208 | if node == nil { 209 | panic("Router.add: tree.add error") 210 | } 211 | 212 | nameNode.format = string(node.formatStr()) 213 | nameNode.paramNum = len(node.params) 214 | return nameNode 215 | } 216 | 217 | // Name set name of route 218 | func (n *Node) Name(name string) { 219 | if name == "" { 220 | return 221 | } 222 | n.name = name 223 | n.root.nameNodes[name] = n 224 | } 225 | -------------------------------------------------------------------------------- /regexp/router.go: -------------------------------------------------------------------------------- 1 | package regexp 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "sync" 7 | 8 | "github.com/go-baa/baa" 9 | ) 10 | 11 | // Router provlider router for baa with regexp 12 | type Router struct { 13 | autoHead bool 14 | autoTrailingSlash bool 15 | mu sync.RWMutex 16 | groups []*group 17 | nodes [baa.RouteLength][]*Node 18 | baa *baa.Baa 19 | nameNodes map[string]*Node 20 | } 21 | 22 | // Node is a router node 23 | type Node struct { 24 | hasParam bool 25 | pattern string 26 | format []byte 27 | params []string 28 | re *regexp.Regexp 29 | handlers []baa.HandlerFunc 30 | root *Router 31 | name string 32 | } 33 | 34 | // group route 35 | type group struct { 36 | pattern string 37 | handlers []baa.HandlerFunc 38 | } 39 | 40 | // New create a router instance 41 | func New(b *baa.Baa) baa.Router { 42 | r := new(Router) 43 | for i := 0; i < len(r.nodes); i++ { 44 | r.nodes[i] = make([]*Node, 0) 45 | } 46 | r.nameNodes = make(map[string]*Node) 47 | r.groups = make([]*group, 0) 48 | r.baa = b 49 | return r 50 | } 51 | 52 | // newNode create a route node 53 | func newNode(pattern string, handlers []baa.HandlerFunc, root *Router) *Node { 54 | n := new(Node) 55 | n.pattern = pattern 56 | n.handlers = handlers 57 | n.root = root 58 | return n 59 | } 60 | 61 | // newGroup create a group router 62 | func newGroup() *group { 63 | g := new(group) 64 | g.handlers = make([]baa.HandlerFunc, 0) 65 | return g 66 | } 67 | 68 | // SetAutoHead sets the value who determines whether add HEAD method automatically 69 | // when GET method is added. Combo router will not be affected by this value. 70 | func (r *Router) SetAutoHead(v bool) { 71 | r.autoHead = v 72 | } 73 | 74 | // SetAutoTrailingSlash optional trailing slash. 75 | func (r *Router) SetAutoTrailingSlash(v bool) { 76 | r.autoTrailingSlash = v 77 | } 78 | 79 | // Match find matched route then returns handlers and name 80 | func (r *Router) Match(method, uri string, c *baa.Context) ([]baa.HandlerFunc, string) { 81 | for _, n := range r.nodes[baa.RouterMethods[method]] { 82 | if !n.hasParam { 83 | if n.pattern == uri { 84 | return n.handlers, n.name 85 | } 86 | continue 87 | } 88 | 89 | matches := n.re.FindStringSubmatchIndex(uri) 90 | if len(matches) != (len(n.params)+1)*2 || matches[0] != 0 { 91 | continue 92 | } 93 | for i := range n.params { 94 | c.SetParam(n.params[i], uri[matches[(i+1)*2]:matches[(i+1)*2+1]]) 95 | } 96 | 97 | return n.handlers, n.name 98 | } 99 | return nil, "" 100 | } 101 | 102 | // URLFor use named route return format url 103 | func (r *Router) URLFor(name string, args ...interface{}) string { 104 | if name == "" { 105 | return "" 106 | } 107 | node := r.nameNodes[name] 108 | if node == nil || len(node.format) == 0 { 109 | return "" 110 | } 111 | format := make([]byte, len(node.format)) 112 | copy(format, node.format) 113 | for i := len(node.params) + 1; i <= len(args); i++ { 114 | format = append(format, "%v"...) 115 | } 116 | return fmt.Sprintf(string(format), args...) 117 | } 118 | 119 | // Routes returns registered route uri in a string slice 120 | func (r *Router) Routes() map[string][]string { 121 | routes := make(map[string][]string) 122 | for _, method := range baa.RouterMethodName { 123 | routes[method] = make([]string, 0) 124 | } 125 | for k := range r.nodes { 126 | for i := range r.nodes[k] { 127 | routes[baa.RouterMethodName[k]] = append(routes[baa.RouterMethodName[k]], r.nodes[k][i].String()) 128 | } 129 | } 130 | 131 | return routes 132 | } 133 | 134 | // NamedRoutes returns named route uri in a string slice 135 | func (r *Router) NamedRoutes() map[string]string { 136 | routes := make(map[string]string) 137 | for k, v := range r.nameNodes { 138 | routes[k] = v.pattern 139 | } 140 | return routes 141 | } 142 | 143 | // Add registers a new handle with the given method, pattern and handlers. 144 | // add check training slash option. 145 | func (r *Router) Add(method, pattern string, handlers []baa.HandlerFunc) baa.RouteNode { 146 | if method == "GET" && r.autoHead { 147 | r.add("HEAD", pattern, handlers) 148 | } 149 | if r.autoTrailingSlash && (len(pattern) > 1 || len(r.groups) > 0) { 150 | var index byte 151 | if len(pattern) > 0 { 152 | index = pattern[len(pattern)-1] 153 | } 154 | if index == '/' { 155 | r.add(method, pattern[:len(pattern)-1], handlers) 156 | } else { 157 | r.add(method, pattern+"/", handlers) 158 | } 159 | } 160 | return r.add(method, pattern, handlers) 161 | } 162 | 163 | // GroupAdd add a group route has same prefix and handle chain 164 | func (r *Router) GroupAdd(pattern string, f func(), handlers []baa.HandlerFunc) { 165 | g := newGroup() 166 | g.pattern = pattern 167 | g.handlers = handlers 168 | r.groups = append(r.groups, g) 169 | 170 | f() 171 | 172 | r.groups = r.groups[:len(r.groups)-1] 173 | } 174 | 175 | // add registers a new request handle with the given method, pattern and handlers. 176 | func (r *Router) add(method, pattern string, handlers []baa.HandlerFunc) *Node { 177 | if _, ok := baa.RouterMethods[method]; !ok { 178 | panic("Router.add: unsupport http method [" + method + "]") 179 | } 180 | 181 | r.mu.Lock() 182 | defer r.mu.Unlock() 183 | 184 | // check group set 185 | if len(r.groups) > 0 { 186 | var gpattern string 187 | var ghandlers []baa.HandlerFunc 188 | for i := range r.groups { 189 | gpattern += r.groups[i].pattern 190 | if len(r.groups[i].handlers) > 0 { 191 | ghandlers = append(ghandlers, r.groups[i].handlers...) 192 | } 193 | } 194 | pattern = gpattern + pattern 195 | ghandlers = append(ghandlers, handlers...) 196 | handlers = ghandlers 197 | } 198 | 199 | // check pattern (for training slash move behind group check) 200 | if pattern == "" { 201 | panic("Router.add: pattern can not be emtpy!") 202 | } 203 | if pattern[0] != '/' { 204 | panic("Router.add: pattern must begin /") 205 | } 206 | 207 | for i := 0; i < len(handlers); i++ { 208 | handlers[i] = baa.WrapHandlerFunc(handlers[i]) 209 | } 210 | 211 | tn := newNode("", handlers, r) 212 | var i, j, k int 213 | var param, restr string 214 | var tpattern []byte 215 | for i = 0; i < len(pattern); i++ { 216 | if pattern[i] == '*' { 217 | restr = "(.*)" 218 | param = "" 219 | tpattern = append(tpattern, restr...) 220 | tn.format = append(tn.format, "%v"...) 221 | tn.params = append(tn.params, param) 222 | tn.hasParam = true 223 | continue 224 | } 225 | if pattern[i] == ':' { 226 | // in param route 227 | for j = i + 1; j < len(pattern) && baa.IsParamChar(pattern[j]); j++ { 228 | } 229 | param = pattern[i+1 : j] 230 | if param == "" { 231 | panic("Router.add: pattern param is empty") 232 | } 233 | i = j - 1 234 | // check regexp 235 | restr = "" 236 | if j < len(pattern) && pattern[j] == '(' { 237 | for k = j + 1; k < len(pattern) && pattern[k] != ')'; k++ { 238 | } 239 | restr = pattern[j+1 : k] 240 | i = k 241 | } 242 | if restr == "" { 243 | restr = "([^\\/]+)" 244 | } else if restr == "int" { 245 | restr = "([\\d]+)" 246 | } else if restr == "string" { 247 | restr = "([\\w]+)" 248 | } else { 249 | restr = "(" + restr + ")" 250 | } 251 | tpattern = append(tpattern, restr...) 252 | tn.format = append(tn.format, "%v"...) 253 | tn.params = append(tn.params, param) 254 | tn.hasParam = true 255 | continue 256 | } 257 | tpattern = append(tpattern, pattern[i]) 258 | tn.format = append(tn.format, pattern[i]) 259 | } 260 | tn.pattern = string(tpattern) 261 | if tn.hasParam { 262 | var err error 263 | tn.re, err = regexp.Compile(tn.pattern + "$") 264 | if err != nil { 265 | panic("Router.add: " + err.Error()) 266 | } 267 | } 268 | // check repeat route 269 | if r.baa.Debug() && tn.pattern != "/" { 270 | for _, n := range r.nodes[baa.RouterMethods[method]] { 271 | if n.pattern == tn.pattern { 272 | panic("Router.add: route already exist -> " + tn.pattern) 273 | } 274 | } 275 | } 276 | r.nodes[baa.RouterMethods[method]] = append(r.nodes[baa.RouterMethods[method]], tn) 277 | return tn 278 | } 279 | 280 | // String returns pattern of node 281 | func (n *Node) String() string { 282 | return n.pattern 283 | } 284 | 285 | // Name set name of route 286 | func (n *Node) Name(name string) { 287 | if name == "" { 288 | return 289 | } 290 | n.name = name 291 | n.root.nameNodes[name] = n 292 | } 293 | -------------------------------------------------------------------------------- /regtree/tree.go: -------------------------------------------------------------------------------- 1 | package regtree 2 | 3 | import ( 4 | "regexp" 5 | 6 | "github.com/go-baa/baa" 7 | ) 8 | 9 | // Tree provlider router store for baa with regexp and radix tree 10 | type Tree struct { 11 | static bool 12 | alpha byte 13 | pattern string 14 | handlers []baa.HandlerFunc 15 | params []string 16 | format []byte 17 | re *regexp.Regexp 18 | schildren []*Tree 19 | rchildren []*Tree 20 | parent *Tree 21 | nameNode *Node 22 | } 23 | 24 | // NewTree create a new tree route node 25 | func NewTree(pattern string, handlers []baa.HandlerFunc) *Tree { 26 | if pattern == "" { 27 | panic("tree.new: pattern can be empty") 28 | } 29 | return &Tree{ 30 | static: true, 31 | alpha: pattern[0], 32 | pattern: pattern, 33 | format: []byte(pattern), 34 | handlers: handlers, 35 | } 36 | } 37 | 38 | // Get match node fill param values for key then return handlers and name 39 | func (t *Tree) Get(pattern string, c *baa.Context) ([]baa.HandlerFunc, string) { 40 | var name string 41 | // regexp rule 42 | if !t.static { 43 | matches := t.re.FindStringSubmatchIndex(pattern) 44 | if len(matches) == (len(t.params)+1)*2 && matches[0] == 0 { 45 | for j := range t.params { 46 | c.SetParam(t.params[j], pattern[matches[(j+1)*2]:matches[(j+1)*2+1]]) 47 | } 48 | if t.nameNode != nil { 49 | name = t.nameNode.name 50 | } 51 | return t.handlers, name 52 | } 53 | } 54 | // static rule 55 | matched := 0 56 | for ; matched < len(pattern) && matched < len(t.pattern) && pattern[matched] == t.pattern[matched]; matched++ { 57 | } 58 | // no prefix 59 | if matched != len(t.pattern) { 60 | return nil, "" 61 | } 62 | // found 63 | if matched == len(pattern) { 64 | if t.handlers != nil { 65 | if t.nameNode != nil { 66 | name = t.nameNode.name 67 | } 68 | return t.handlers, name 69 | } 70 | } 71 | // node is prefix 72 | pattern = pattern[matched:] 73 | // first, static rule 74 | if len(pattern) > 0 { 75 | if snode := t.findChild(pattern[0]); snode != nil { 76 | if h, name := snode.Get(pattern, c); h != nil { 77 | return h, name 78 | } 79 | } 80 | } 81 | 82 | // then, regexp rule 83 | for i := range t.rchildren { 84 | matches := t.rchildren[i].re.FindStringSubmatchIndex(pattern) 85 | if len(matches) != (len(t.rchildren[i].params)+1)*2 || matches[0] > 0 { 86 | continue 87 | } 88 | for j := range t.rchildren[i].params { 89 | c.SetParam(t.rchildren[i].params[j], pattern[matches[(j+1)*2]:matches[(j+1)*2+1]]) 90 | } 91 | if t.rchildren[i].nameNode != nil { 92 | name = t.rchildren[i].nameNode.name 93 | } 94 | return t.rchildren[i].handlers, name 95 | } 96 | 97 | return nil, "" 98 | } 99 | 100 | // Add return new node with key and val 101 | func (t *Tree) Add(pattern string, handlers []baa.HandlerFunc, nameNode *Node) *Tree { 102 | // find the common prefix 103 | matched := 0 104 | for ; matched < len(pattern) && matched < len(t.pattern) && pattern[matched] == t.pattern[matched]; matched++ { 105 | } 106 | 107 | // no prefix 108 | if matched == 0 { 109 | return nil 110 | } 111 | 112 | if matched == len(t.pattern) { 113 | // the node pattern is the same as the pattern: make the current node as data node 114 | if matched == len(pattern) { 115 | if handlers != nil { 116 | if t.handlers != nil { 117 | panic("the route is be exists: " + t.String()) 118 | } 119 | t.handlers = handlers 120 | t.nameNode = nameNode 121 | } 122 | return t 123 | } 124 | 125 | // the node pattern is a prefix of the pattern: create a child node 126 | pattern = pattern[matched:] 127 | for _, child := range t.schildren { 128 | if node := child.Add(pattern, handlers, nameNode); node != nil { 129 | return node 130 | } 131 | } 132 | 133 | // no child match, to be a new child 134 | return t.addChild(pattern, handlers, nameNode) 135 | } 136 | 137 | // the pattern is a prefix of node pattern: create a new node instead of child 138 | if matched == len(pattern) { 139 | node := NewTree(t.pattern[matched:], t.handlers) 140 | node.nameNode = t.nameNode 141 | node.schildren = t.schildren 142 | node.rchildren = t.rchildren 143 | node.parent = t 144 | t.pattern = pattern 145 | t.format = []byte(t.pattern) 146 | t.handlers = handlers 147 | t.nameNode = nameNode 148 | t.schildren = []*Tree{node} 149 | t.rchildren = nil 150 | return t 151 | } 152 | 153 | // the node pattern shares a partial prefix with the key: split the node pattern 154 | node := NewTree(t.pattern[matched:], t.handlers) 155 | node.nameNode = t.nameNode 156 | node.schildren = t.schildren 157 | node.rchildren = t.rchildren 158 | node.parent = t 159 | t.pattern = pattern[:matched] 160 | t.format = []byte(t.pattern) 161 | t.handlers = nil 162 | t.nameNode = nil 163 | t.schildren = nil 164 | t.rchildren = nil 165 | t.schildren = append(t.schildren, node) 166 | return t.addChild(pattern[matched:], handlers, nameNode) 167 | } 168 | 169 | func (t *Tree) addChild(pattern string, handlers []baa.HandlerFunc, nameNode *Node) *Tree { 170 | // check it is a static route child or not 171 | var staticPattern, param, rule string 172 | var params []string 173 | var newPattern, format []byte 174 | var i, j, k int 175 | for i = 0; i < len(pattern); i++ { 176 | if pattern[i] == '*' { 177 | // set static prefix 178 | if len(staticPattern) == 0 && len(params) == 0 && i > 0 { 179 | staticPattern = pattern[:i] 180 | } 181 | rule = "(^.*)" 182 | param = "" 183 | newPattern = append(newPattern, rule...) 184 | format = append(format, "%v"...) 185 | params = append(params, param) 186 | continue 187 | } 188 | if pattern[i] == ':' { 189 | for j = i + 1; j < len(pattern) && baa.IsParamChar(pattern[j]); j++ { 190 | } 191 | // set static prefix 192 | if len(staticPattern) == 0 && len(params) == 0 && i > 0 { 193 | staticPattern = pattern[:i] 194 | } 195 | param = pattern[i+1 : j] 196 | i = j - 1 197 | // check regexp rule 198 | rule = "" 199 | if j < len(pattern) && pattern[j] == '(' { 200 | for k = j + 1; k < len(pattern) && pattern[k] != ')'; k++ { 201 | } 202 | rule = pattern[j+1 : k] 203 | i = k 204 | } 205 | if rule == "" { 206 | rule = "([^\\/]+)" 207 | } else if rule == "int" { 208 | rule = "([\\d]+)" 209 | } else if rule == "string" { 210 | rule = "([\\w]+)" 211 | } else { 212 | rule = "(" + rule + ")" 213 | } 214 | newPattern = append(newPattern, rule...) 215 | format = append(format, "%v"...) 216 | params = append(params, param) 217 | continue 218 | } 219 | newPattern = append(newPattern, pattern[i]) 220 | format = append(format, pattern[i]) 221 | } 222 | 223 | var reNode, staticNode *Tree 224 | var err error 225 | if len(params) > 0 { 226 | // key has regexp rule, new regexp rule 227 | reNode = NewTree(string(newPattern[len(staticPattern):]), handlers) 228 | reNode.nameNode = nameNode 229 | reNode.static = false 230 | reNode.params = params 231 | reNode.format = format[len(staticPattern):] 232 | reNode.re, err = regexp.Compile(reNode.pattern + "$") 233 | if err != nil { 234 | panic("tree.addChild: " + err.Error()) 235 | } 236 | // set pattern with static prefix 237 | pattern = staticPattern 238 | } 239 | 240 | if len(pattern) > 0 { 241 | // key has static rule 242 | staticNode = NewTree(pattern, nil) 243 | staticNode.parent = t 244 | if reNode != nil { 245 | reNode.parent = staticNode 246 | staticNode.rchildren = append(staticNode.rchildren, reNode) 247 | t.schildren = append(t.schildren, staticNode) 248 | return reNode 249 | } 250 | staticNode.handlers = handlers 251 | staticNode.nameNode = nameNode 252 | t.schildren = append(t.schildren, staticNode) 253 | return staticNode 254 | } 255 | 256 | // key has regexp rule without static rule 257 | reNode.parent = t 258 | for _, child := range t.rchildren { 259 | if child.pattern == reNode.pattern { 260 | panic("the route is be exists: " + child.String()) 261 | } 262 | } 263 | t.rchildren = append(t.rchildren, reNode) 264 | return reNode 265 | } 266 | 267 | // findChild find a match node from tree 268 | func (t *Tree) findChild(b byte) *Tree { 269 | var i int 270 | var j = len(t.schildren) 271 | for ; i < j; i++ { 272 | if t.schildren[i].alpha == b { 273 | return t.schildren[i] 274 | } 275 | } 276 | return nil 277 | } 278 | 279 | // String return full key 280 | func (t *Tree) String() string { 281 | s := t.pattern 282 | if t.parent != nil { 283 | s = t.parent.String() + s 284 | } 285 | return s 286 | } 287 | 288 | // formatStr return parsed format string 289 | func (t *Tree) formatStr() []byte { 290 | var s []byte 291 | s = append(s, t.format...) 292 | if t.parent != nil { 293 | t := t.parent.formatStr() 294 | t = append(t, s...) 295 | s = t 296 | } 297 | return s 298 | } 299 | -------------------------------------------------------------------------------- /regexp/github_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Julien Schmidt. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be found 3 | // in the LICENSE file. 4 | 5 | package regexp 6 | 7 | import ( 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | // http://developer.github.com/v3/ 13 | var githubAPI = []route{ 14 | // OAuth Authorizations 15 | {"GET", "/authorizations"}, 16 | {"GET", "/authorizations/:id"}, 17 | {"POST", "/authorizations"}, 18 | //{"PUT", "/authorizations/clients/:client_id"}, 19 | //{"PATCH", "/authorizations/:id"}, 20 | {"DELETE", "/authorizations/:id"}, 21 | {"GET", "/applications/:client_id/tokens/:access_token"}, 22 | {"DELETE", "/applications/:client_id/tokens"}, 23 | {"DELETE", "/applications/:client_id/tokens/:access_token"}, 24 | 25 | // Activity 26 | {"GET", "/events"}, 27 | {"GET", "/repos/:owner/:repo/events"}, 28 | {"GET", "/networks/:owner/:repo/events"}, 29 | {"GET", "/orgs/:org/events"}, 30 | {"GET", "/users/:user/received_events"}, 31 | {"GET", "/users/:user/received_events/public"}, 32 | {"GET", "/users/:user/events"}, 33 | {"GET", "/users/:user/events/public"}, 34 | {"GET", "/users/:user/events/orgs/:org"}, 35 | {"GET", "/feeds"}, 36 | {"GET", "/notifications"}, 37 | {"GET", "/repos/:owner/:repo/notifications"}, 38 | {"PUT", "/notifications"}, 39 | {"PUT", "/repos/:owner/:repo/notifications"}, 40 | {"GET", "/notifications/threads/:id"}, 41 | //{"PATCH", "/notifications/threads/:id"}, 42 | {"GET", "/notifications/threads/:id/subscription"}, 43 | {"PUT", "/notifications/threads/:id/subscription"}, 44 | {"DELETE", "/notifications/threads/:id/subscription"}, 45 | {"GET", "/repos/:owner/:repo/stargazers"}, 46 | {"GET", "/users/:user/starred"}, 47 | {"GET", "/user/starred"}, 48 | {"GET", "/user/starred/:owner/:repo"}, 49 | {"PUT", "/user/starred/:owner/:repo"}, 50 | {"DELETE", "/user/starred/:owner/:repo"}, 51 | {"GET", "/repos/:owner/:repo/subscribers"}, 52 | {"GET", "/users/:user/subscriptions"}, 53 | {"GET", "/user/subscriptions"}, 54 | {"GET", "/repos/:owner/:repo/subscription"}, 55 | {"PUT", "/repos/:owner/:repo/subscription"}, 56 | {"DELETE", "/repos/:owner/:repo/subscription"}, 57 | {"GET", "/user/subscriptions/:owner/:repo"}, 58 | {"PUT", "/user/subscriptions/:owner/:repo"}, 59 | {"DELETE", "/user/subscriptions/:owner/:repo"}, 60 | 61 | // Gists 62 | {"GET", "/users/:user/gists"}, 63 | {"GET", "/gists"}, 64 | //{"GET", "/gists/public"}, 65 | //{"GET", "/gists/starred"}, 66 | {"GET", "/gists/:id"}, 67 | {"POST", "/gists"}, 68 | //{"PATCH", "/gists/:id"}, 69 | {"PUT", "/gists/:id/star"}, 70 | {"DELETE", "/gists/:id/star"}, 71 | {"GET", "/gists/:id/star"}, 72 | {"POST", "/gists/:id/forks"}, 73 | {"DELETE", "/gists/:id"}, 74 | 75 | // Git Data 76 | {"GET", "/repos/:owner/:repo/git/blobs/:sha"}, 77 | {"POST", "/repos/:owner/:repo/git/blobs"}, 78 | {"GET", "/repos/:owner/:repo/git/commits/:sha"}, 79 | {"POST", "/repos/:owner/:repo/git/commits"}, 80 | //{"GET", "/repos/:owner/:repo/git/refs/*ref"}, 81 | {"GET", "/repos/:owner/:repo/git/refs"}, 82 | {"POST", "/repos/:owner/:repo/git/refs"}, 83 | //{"PATCH", "/repos/:owner/:repo/git/refs/*ref"}, 84 | //{"DELETE", "/repos/:owner/:repo/git/refs/*ref"}, 85 | {"GET", "/repos/:owner/:repo/git/tags/:sha"}, 86 | {"POST", "/repos/:owner/:repo/git/tags"}, 87 | {"GET", "/repos/:owner/:repo/git/trees/:sha"}, 88 | {"POST", "/repos/:owner/:repo/git/trees"}, 89 | 90 | // Issues 91 | {"GET", "/issues"}, 92 | {"GET", "/user/issues"}, 93 | {"GET", "/orgs/:org/issues"}, 94 | {"GET", "/repos/:owner/:repo/issues"}, 95 | {"GET", "/repos/:owner/:repo/issues/:number"}, 96 | {"POST", "/repos/:owner/:repo/issues"}, 97 | //{"PATCH", "/repos/:owner/:repo/issues/:number"}, 98 | {"GET", "/repos/:owner/:repo/assignees"}, 99 | {"GET", "/repos/:owner/:repo/assignees/:assignee"}, 100 | {"GET", "/repos/:owner/:repo/issues/:number/comments"}, 101 | //{"GET", "/repos/:owner/:repo/issues/comments"}, 102 | //{"GET", "/repos/:owner/:repo/issues/comments/:id"}, 103 | {"POST", "/repos/:owner/:repo/issues/:number/comments"}, 104 | //{"PATCH", "/repos/:owner/:repo/issues/comments/:id"}, 105 | //{"DELETE", "/repos/:owner/:repo/issues/comments/:id"}, 106 | {"GET", "/repos/:owner/:repo/issues/:number/events"}, 107 | //{"GET", "/repos/:owner/:repo/issues/events"}, 108 | //{"GET", "/repos/:owner/:repo/issues/events/:id"}, 109 | {"GET", "/repos/:owner/:repo/labels"}, 110 | {"GET", "/repos/:owner/:repo/labels/:name"}, 111 | {"POST", "/repos/:owner/:repo/labels"}, 112 | //{"PATCH", "/repos/:owner/:repo/labels/:name"}, 113 | {"DELETE", "/repos/:owner/:repo/labels/:name"}, 114 | {"GET", "/repos/:owner/:repo/issues/:number/labels"}, 115 | {"POST", "/repos/:owner/:repo/issues/:number/labels"}, 116 | {"DELETE", "/repos/:owner/:repo/issues/:number/labels/:name"}, 117 | {"PUT", "/repos/:owner/:repo/issues/:number/labels"}, 118 | {"DELETE", "/repos/:owner/:repo/issues/:number/labels"}, 119 | {"GET", "/repos/:owner/:repo/milestones/:number/labels"}, 120 | {"GET", "/repos/:owner/:repo/milestones"}, 121 | {"GET", "/repos/:owner/:repo/milestones/:number"}, 122 | {"POST", "/repos/:owner/:repo/milestones"}, 123 | //{"PATCH", "/repos/:owner/:repo/milestones/:number"}, 124 | {"DELETE", "/repos/:owner/:repo/milestones/:number"}, 125 | 126 | // Miscellaneous 127 | {"GET", "/emojis"}, 128 | {"GET", "/gitignore/templates"}, 129 | {"GET", "/gitignore/templates/:name"}, 130 | {"POST", "/markdown"}, 131 | {"POST", "/markdown/raw"}, 132 | {"GET", "/meta"}, 133 | {"GET", "/rate_limit"}, 134 | 135 | // Organizations 136 | {"GET", "/users/:user/orgs"}, 137 | {"GET", "/user/orgs"}, 138 | {"GET", "/orgs/:org"}, 139 | //{"PATCH", "/orgs/:org"}, 140 | {"GET", "/orgs/:org/members"}, 141 | {"GET", "/orgs/:org/members/:user"}, 142 | {"DELETE", "/orgs/:org/members/:user"}, 143 | {"GET", "/orgs/:org/public_members"}, 144 | {"GET", "/orgs/:org/public_members/:user"}, 145 | {"PUT", "/orgs/:org/public_members/:user"}, 146 | {"DELETE", "/orgs/:org/public_members/:user"}, 147 | {"GET", "/orgs/:org/teams"}, 148 | {"GET", "/teams/:id"}, 149 | {"POST", "/orgs/:org/teams"}, 150 | //{"PATCH", "/teams/:id"}, 151 | {"DELETE", "/teams/:id"}, 152 | {"GET", "/teams/:id/members"}, 153 | {"GET", "/teams/:id/members/:user"}, 154 | {"PUT", "/teams/:id/members/:user"}, 155 | {"DELETE", "/teams/:id/members/:user"}, 156 | {"GET", "/teams/:id/repos"}, 157 | {"GET", "/teams/:id/repos/:owner/:repo"}, 158 | {"PUT", "/teams/:id/repos/:owner/:repo"}, 159 | {"DELETE", "/teams/:id/repos/:owner/:repo"}, 160 | {"GET", "/user/teams"}, 161 | 162 | // Pull Requests 163 | {"GET", "/repos/:owner/:repo/pulls"}, 164 | {"GET", "/repos/:owner/:repo/pulls/:number"}, 165 | {"POST", "/repos/:owner/:repo/pulls"}, 166 | //{"PATCH", "/repos/:owner/:repo/pulls/:number"}, 167 | {"GET", "/repos/:owner/:repo/pulls/:number/commits"}, 168 | {"GET", "/repos/:owner/:repo/pulls/:number/files"}, 169 | {"GET", "/repos/:owner/:repo/pulls/:number/merge"}, 170 | {"PUT", "/repos/:owner/:repo/pulls/:number/merge"}, 171 | {"GET", "/repos/:owner/:repo/pulls/:number/comments"}, 172 | //{"GET", "/repos/:owner/:repo/pulls/comments"}, 173 | //{"GET", "/repos/:owner/:repo/pulls/comments/:number"}, 174 | {"PUT", "/repos/:owner/:repo/pulls/:number/comments"}, 175 | //{"PATCH", "/repos/:owner/:repo/pulls/comments/:number"}, 176 | //{"DELETE", "/repos/:owner/:repo/pulls/comments/:number"}, 177 | 178 | // Repositories 179 | {"GET", "/user/repos"}, 180 | {"GET", "/users/:user/repos"}, 181 | {"GET", "/orgs/:org/repos"}, 182 | {"GET", "/repositories"}, 183 | {"POST", "/user/repos"}, 184 | {"POST", "/orgs/:org/repos"}, 185 | {"GET", "/repos/:owner/:repo"}, 186 | //{"PATCH", "/repos/:owner/:repo"}, 187 | {"GET", "/repos/:owner/:repo/contributors"}, 188 | {"GET", "/repos/:owner/:repo/languages"}, 189 | {"GET", "/repos/:owner/:repo/teams"}, 190 | {"GET", "/repos/:owner/:repo/tags"}, 191 | {"GET", "/repos/:owner/:repo/branches"}, 192 | {"GET", "/repos/:owner/:repo/branches/:branch"}, 193 | {"DELETE", "/repos/:owner/:repo"}, 194 | {"GET", "/repos/:owner/:repo/collaborators"}, 195 | {"GET", "/repos/:owner/:repo/collaborators/:user"}, 196 | {"PUT", "/repos/:owner/:repo/collaborators/:user"}, 197 | {"DELETE", "/repos/:owner/:repo/collaborators/:user"}, 198 | {"GET", "/repos/:owner/:repo/comments"}, 199 | {"GET", "/repos/:owner/:repo/commits/:sha/comments"}, 200 | {"POST", "/repos/:owner/:repo/commits/:sha/comments"}, 201 | {"GET", "/repos/:owner/:repo/comments/:id"}, 202 | //{"PATCH", "/repos/:owner/:repo/comments/:id"}, 203 | {"DELETE", "/repos/:owner/:repo/comments/:id"}, 204 | {"GET", "/repos/:owner/:repo/commits"}, 205 | {"GET", "/repos/:owner/:repo/commits/:sha"}, 206 | {"GET", "/repos/:owner/:repo/readme"}, 207 | //{"GET", "/repos/:owner/:repo/contents/*path"}, 208 | //{"PUT", "/repos/:owner/:repo/contents/*path"}, 209 | //{"DELETE", "/repos/:owner/:repo/contents/*path"}, 210 | //{"GET", "/repos/:owner/:repo/:archive_format/:ref"}, 211 | {"GET", "/repos/:owner/:repo/keys"}, 212 | {"GET", "/repos/:owner/:repo/keys/:id"}, 213 | {"POST", "/repos/:owner/:repo/keys"}, 214 | //{"PATCH", "/repos/:owner/:repo/keys/:id"}, 215 | {"DELETE", "/repos/:owner/:repo/keys/:id"}, 216 | {"GET", "/repos/:owner/:repo/downloads"}, 217 | {"GET", "/repos/:owner/:repo/downloads/:id"}, 218 | {"DELETE", "/repos/:owner/:repo/downloads/:id"}, 219 | {"GET", "/repos/:owner/:repo/forks"}, 220 | {"POST", "/repos/:owner/:repo/forks"}, 221 | {"GET", "/repos/:owner/:repo/hooks"}, 222 | {"GET", "/repos/:owner/:repo/hooks/:id"}, 223 | {"POST", "/repos/:owner/:repo/hooks"}, 224 | //{"PATCH", "/repos/:owner/:repo/hooks/:id"}, 225 | {"POST", "/repos/:owner/:repo/hooks/:id/tests"}, 226 | {"DELETE", "/repos/:owner/:repo/hooks/:id"}, 227 | {"POST", "/repos/:owner/:repo/merges"}, 228 | {"GET", "/repos/:owner/:repo/releases"}, 229 | {"GET", "/repos/:owner/:repo/releases/:id"}, 230 | {"POST", "/repos/:owner/:repo/releases"}, 231 | //{"PATCH", "/repos/:owner/:repo/releases/:id"}, 232 | {"DELETE", "/repos/:owner/:repo/releases/:id"}, 233 | {"GET", "/repos/:owner/:repo/releases/:id/assets"}, 234 | {"GET", "/repos/:owner/:repo/stats/contributors"}, 235 | {"GET", "/repos/:owner/:repo/stats/commit_activity"}, 236 | {"GET", "/repos/:owner/:repo/stats/code_frequency"}, 237 | {"GET", "/repos/:owner/:repo/stats/participation"}, 238 | {"GET", "/repos/:owner/:repo/stats/punch_card"}, 239 | {"GET", "/repos/:owner/:repo/statuses/:ref"}, 240 | {"POST", "/repos/:owner/:repo/statuses/:ref"}, 241 | 242 | // Search 243 | {"GET", "/search/repositories"}, 244 | {"GET", "/search/code"}, 245 | {"GET", "/search/issues"}, 246 | {"GET", "/search/users"}, 247 | {"GET", "/legacy/issues/search/:owner/:repository/:state/:keyword"}, 248 | {"GET", "/legacy/repos/search/:keyword"}, 249 | {"GET", "/legacy/user/search/:keyword"}, 250 | {"GET", "/legacy/user/email/:email"}, 251 | 252 | // Users 253 | {"GET", "/users/:user"}, 254 | {"GET", "/user"}, 255 | //{"PATCH", "/user"}, 256 | {"GET", "/users"}, 257 | {"GET", "/user/emails"}, 258 | {"POST", "/user/emails"}, 259 | {"DELETE", "/user/emails"}, 260 | {"GET", "/users/:user/followers"}, 261 | {"GET", "/user/followers"}, 262 | {"GET", "/users/:user/following"}, 263 | {"GET", "/user/following"}, 264 | {"GET", "/user/following/:user"}, 265 | {"GET", "/users/:user/following/:target_user"}, 266 | {"PUT", "/user/following/:user"}, 267 | {"DELETE", "/user/following/:user"}, 268 | {"GET", "/users/:user/keys"}, 269 | {"GET", "/user/keys"}, 270 | {"GET", "/user/keys/:id"}, 271 | {"POST", "/user/keys"}, 272 | //{"PATCH", "/user/keys/:id"}, 273 | {"DELETE", "/user/keys/:id"}, 274 | } 275 | 276 | var ( 277 | githubBaa http.Handler 278 | ) 279 | 280 | func init() { 281 | println("#GithubAPI Routes:", len(githubAPI)) 282 | githubBaa = loadBaa(githubAPI) 283 | println() 284 | } 285 | 286 | // Static 287 | func BenchmarkBaa_GithubStatic(b *testing.B) { 288 | req, _ := http.NewRequest("GET", "/user/repos", nil) 289 | benchRequest(b, githubBaa, req) 290 | } 291 | 292 | // Param 293 | func BenchmarkBaa_GithubParam(b *testing.B) { 294 | req, _ := http.NewRequest("GET", "/repos/julienschmidt/httprouter/stargazers", nil) 295 | benchRequest(b, githubBaa, req) 296 | } 297 | 298 | // All routes 299 | func BenchmarkBaa_GithubAll(b *testing.B) { 300 | benchRoutes(b, githubBaa, githubAPI) 301 | } 302 | -------------------------------------------------------------------------------- /regtree/github_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2013 Julien Schmidt. All rights reserved. 2 | // Use of this source code is governed by a BSD-style license that can be found 3 | // in the LICENSE file. 4 | 5 | package regtree 6 | 7 | import ( 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | // http://developer.github.com/v3/ 13 | var githubAPI = []route{ 14 | // OAuth Authorizations 15 | {"GET", "/authorizations"}, 16 | {"GET", "/authorizations/:id"}, 17 | {"POST", "/authorizations"}, 18 | //{"PUT", "/authorizations/clients/:client_id"}, 19 | //{"PATCH", "/authorizations/:id"}, 20 | {"DELETE", "/authorizations/:id"}, 21 | {"GET", "/applications/:client_id/tokens/:access_token"}, 22 | {"DELETE", "/applications/:client_id/tokens"}, 23 | {"DELETE", "/applications/:client_id/tokens/:access_token"}, 24 | 25 | // Activity 26 | {"GET", "/events"}, 27 | {"GET", "/repos/:owner/:repo/events"}, 28 | {"GET", "/networks/:owner/:repo/events"}, 29 | {"GET", "/orgs/:org/events"}, 30 | {"GET", "/users/:user/received_events"}, 31 | {"GET", "/users/:user/received_events/public"}, 32 | {"GET", "/users/:user/events"}, 33 | {"GET", "/users/:user/events/public"}, 34 | {"GET", "/users/:user/events/orgs/:org"}, 35 | {"GET", "/feeds"}, 36 | {"GET", "/notifications"}, 37 | {"GET", "/repos/:owner/:repo/notifications"}, 38 | {"PUT", "/notifications"}, 39 | {"PUT", "/repos/:owner/:repo/notifications"}, 40 | {"GET", "/notifications/threads/:id"}, 41 | //{"PATCH", "/notifications/threads/:id"}, 42 | {"GET", "/notifications/threads/:id/subscription"}, 43 | {"PUT", "/notifications/threads/:id/subscription"}, 44 | {"DELETE", "/notifications/threads/:id/subscription"}, 45 | {"GET", "/repos/:owner/:repo/stargazers"}, 46 | {"GET", "/users/:user/starred"}, 47 | {"GET", "/user/starred"}, 48 | {"GET", "/user/starred/:owner/:repo"}, 49 | {"PUT", "/user/starred/:owner/:repo"}, 50 | {"DELETE", "/user/starred/:owner/:repo"}, 51 | {"GET", "/repos/:owner/:repo/subscribers"}, 52 | {"GET", "/users/:user/subscriptions"}, 53 | {"GET", "/user/subscriptions"}, 54 | {"GET", "/repos/:owner/:repo/subscription"}, 55 | {"PUT", "/repos/:owner/:repo/subscription"}, 56 | {"DELETE", "/repos/:owner/:repo/subscription"}, 57 | {"GET", "/user/subscriptions/:owner/:repo"}, 58 | {"PUT", "/user/subscriptions/:owner/:repo"}, 59 | {"DELETE", "/user/subscriptions/:owner/:repo"}, 60 | 61 | // Gists 62 | {"GET", "/users/:user/gists"}, 63 | {"GET", "/gists"}, 64 | //{"GET", "/gists/public"}, 65 | //{"GET", "/gists/starred"}, 66 | {"GET", "/gists/:id"}, 67 | {"POST", "/gists"}, 68 | //{"PATCH", "/gists/:id"}, 69 | {"PUT", "/gists/:id/star"}, 70 | {"DELETE", "/gists/:id/star"}, 71 | {"GET", "/gists/:id/star"}, 72 | {"POST", "/gists/:id/forks"}, 73 | {"DELETE", "/gists/:id"}, 74 | 75 | // Git Data 76 | {"GET", "/repos/:owner/:repo/git/blobs/:sha"}, 77 | {"POST", "/repos/:owner/:repo/git/blobs"}, 78 | {"GET", "/repos/:owner/:repo/git/commits/:sha"}, 79 | {"POST", "/repos/:owner/:repo/git/commits"}, 80 | //{"GET", "/repos/:owner/:repo/git/refs/*ref"}, 81 | {"GET", "/repos/:owner/:repo/git/refs"}, 82 | {"POST", "/repos/:owner/:repo/git/refs"}, 83 | //{"PATCH", "/repos/:owner/:repo/git/refs/*ref"}, 84 | //{"DELETE", "/repos/:owner/:repo/git/refs/*ref"}, 85 | {"GET", "/repos/:owner/:repo/git/tags/:sha"}, 86 | {"POST", "/repos/:owner/:repo/git/tags"}, 87 | {"GET", "/repos/:owner/:repo/git/trees/:sha"}, 88 | {"POST", "/repos/:owner/:repo/git/trees"}, 89 | 90 | // Issues 91 | {"GET", "/issues"}, 92 | {"GET", "/user/issues"}, 93 | {"GET", "/orgs/:org/issues"}, 94 | {"GET", "/repos/:owner/:repo/issues"}, 95 | {"GET", "/repos/:owner/:repo/issues/:number"}, 96 | {"POST", "/repos/:owner/:repo/issues"}, 97 | //{"PATCH", "/repos/:owner/:repo/issues/:number"}, 98 | {"GET", "/repos/:owner/:repo/assignees"}, 99 | {"GET", "/repos/:owner/:repo/assignees/:assignee"}, 100 | {"GET", "/repos/:owner/:repo/issues/:number/comments"}, 101 | //{"GET", "/repos/:owner/:repo/issues/comments"}, 102 | //{"GET", "/repos/:owner/:repo/issues/comments/:id"}, 103 | {"POST", "/repos/:owner/:repo/issues/:number/comments"}, 104 | //{"PATCH", "/repos/:owner/:repo/issues/comments/:id"}, 105 | //{"DELETE", "/repos/:owner/:repo/issues/comments/:id"}, 106 | {"GET", "/repos/:owner/:repo/issues/:number/events"}, 107 | //{"GET", "/repos/:owner/:repo/issues/events"}, 108 | //{"GET", "/repos/:owner/:repo/issues/events/:id"}, 109 | {"GET", "/repos/:owner/:repo/labels"}, 110 | {"GET", "/repos/:owner/:repo/labels/:name"}, 111 | {"POST", "/repos/:owner/:repo/labels"}, 112 | //{"PATCH", "/repos/:owner/:repo/labels/:name"}, 113 | {"DELETE", "/repos/:owner/:repo/labels/:name"}, 114 | {"GET", "/repos/:owner/:repo/issues/:number/labels"}, 115 | {"POST", "/repos/:owner/:repo/issues/:number/labels"}, 116 | {"DELETE", "/repos/:owner/:repo/issues/:number/labels/:name"}, 117 | {"PUT", "/repos/:owner/:repo/issues/:number/labels"}, 118 | {"DELETE", "/repos/:owner/:repo/issues/:number/labels"}, 119 | {"GET", "/repos/:owner/:repo/milestones/:number/labels"}, 120 | {"GET", "/repos/:owner/:repo/milestones"}, 121 | {"GET", "/repos/:owner/:repo/milestones/:number"}, 122 | {"POST", "/repos/:owner/:repo/milestones"}, 123 | //{"PATCH", "/repos/:owner/:repo/milestones/:number"}, 124 | {"DELETE", "/repos/:owner/:repo/milestones/:number"}, 125 | 126 | // Miscellaneous 127 | {"GET", "/emojis"}, 128 | {"GET", "/gitignore/templates"}, 129 | {"GET", "/gitignore/templates/:name"}, 130 | {"POST", "/markdown"}, 131 | {"POST", "/markdown/raw"}, 132 | {"GET", "/meta"}, 133 | {"GET", "/rate_limit"}, 134 | 135 | // Organizations 136 | {"GET", "/users/:user/orgs"}, 137 | {"GET", "/user/orgs"}, 138 | {"GET", "/orgs/:org"}, 139 | //{"PATCH", "/orgs/:org"}, 140 | {"GET", "/orgs/:org/members"}, 141 | {"GET", "/orgs/:org/members/:user"}, 142 | {"DELETE", "/orgs/:org/members/:user"}, 143 | {"GET", "/orgs/:org/public_members"}, 144 | {"GET", "/orgs/:org/public_members/:user"}, 145 | {"PUT", "/orgs/:org/public_members/:user"}, 146 | {"DELETE", "/orgs/:org/public_members/:user"}, 147 | {"GET", "/orgs/:org/teams"}, 148 | {"GET", "/teams/:id"}, 149 | {"POST", "/orgs/:org/teams"}, 150 | //{"PATCH", "/teams/:id"}, 151 | {"DELETE", "/teams/:id"}, 152 | {"GET", "/teams/:id/members"}, 153 | {"GET", "/teams/:id/members/:user"}, 154 | {"PUT", "/teams/:id/members/:user"}, 155 | {"DELETE", "/teams/:id/members/:user"}, 156 | {"GET", "/teams/:id/repos"}, 157 | {"GET", "/teams/:id/repos/:owner/:repo"}, 158 | {"PUT", "/teams/:id/repos/:owner/:repo"}, 159 | {"DELETE", "/teams/:id/repos/:owner/:repo"}, 160 | {"GET", "/user/teams"}, 161 | 162 | // Pull Requests 163 | {"GET", "/repos/:owner/:repo/pulls"}, 164 | {"GET", "/repos/:owner/:repo/pulls/:number"}, 165 | {"POST", "/repos/:owner/:repo/pulls"}, 166 | //{"PATCH", "/repos/:owner/:repo/pulls/:number"}, 167 | {"GET", "/repos/:owner/:repo/pulls/:number/commits"}, 168 | {"GET", "/repos/:owner/:repo/pulls/:number/files"}, 169 | {"GET", "/repos/:owner/:repo/pulls/:number/merge"}, 170 | {"PUT", "/repos/:owner/:repo/pulls/:number/merge"}, 171 | {"GET", "/repos/:owner/:repo/pulls/:number/comments"}, 172 | //{"GET", "/repos/:owner/:repo/pulls/comments"}, 173 | //{"GET", "/repos/:owner/:repo/pulls/comments/:number"}, 174 | {"PUT", "/repos/:owner/:repo/pulls/:number/comments"}, 175 | //{"PATCH", "/repos/:owner/:repo/pulls/comments/:number"}, 176 | //{"DELETE", "/repos/:owner/:repo/pulls/comments/:number"}, 177 | 178 | // Repositories 179 | {"GET", "/user/repos"}, 180 | {"GET", "/users/:user/repos"}, 181 | {"GET", "/orgs/:org/repos"}, 182 | {"GET", "/repositories"}, 183 | {"POST", "/user/repos"}, 184 | {"POST", "/orgs/:org/repos"}, 185 | {"GET", "/repos/:owner/:repo"}, 186 | //{"PATCH", "/repos/:owner/:repo"}, 187 | {"GET", "/repos/:owner/:repo/contributors"}, 188 | {"GET", "/repos/:owner/:repo/languages"}, 189 | {"GET", "/repos/:owner/:repo/teams"}, 190 | {"GET", "/repos/:owner/:repo/tags"}, 191 | {"GET", "/repos/:owner/:repo/branches"}, 192 | {"GET", "/repos/:owner/:repo/branches/:branch"}, 193 | {"DELETE", "/repos/:owner/:repo"}, 194 | {"GET", "/repos/:owner/:repo/collaborators"}, 195 | {"GET", "/repos/:owner/:repo/collaborators/:user"}, 196 | {"PUT", "/repos/:owner/:repo/collaborators/:user"}, 197 | {"DELETE", "/repos/:owner/:repo/collaborators/:user"}, 198 | {"GET", "/repos/:owner/:repo/comments"}, 199 | {"GET", "/repos/:owner/:repo/commits/:sha/comments"}, 200 | {"POST", "/repos/:owner/:repo/commits/:sha/comments"}, 201 | {"GET", "/repos/:owner/:repo/comments/:id"}, 202 | //{"PATCH", "/repos/:owner/:repo/comments/:id"}, 203 | {"DELETE", "/repos/:owner/:repo/comments/:id"}, 204 | {"GET", "/repos/:owner/:repo/commits"}, 205 | {"GET", "/repos/:owner/:repo/commits/:sha"}, 206 | {"GET", "/repos/:owner/:repo/readme"}, 207 | //{"GET", "/repos/:owner/:repo/contents/*path"}, 208 | //{"PUT", "/repos/:owner/:repo/contents/*path"}, 209 | //{"DELETE", "/repos/:owner/:repo/contents/*path"}, 210 | //{"GET", "/repos/:owner/:repo/:archive_format/:ref"}, 211 | {"GET", "/repos/:owner/:repo/keys"}, 212 | {"GET", "/repos/:owner/:repo/keys/:id"}, 213 | {"POST", "/repos/:owner/:repo/keys"}, 214 | //{"PATCH", "/repos/:owner/:repo/keys/:id"}, 215 | {"DELETE", "/repos/:owner/:repo/keys/:id"}, 216 | {"GET", "/repos/:owner/:repo/downloads"}, 217 | {"GET", "/repos/:owner/:repo/downloads/:id"}, 218 | {"DELETE", "/repos/:owner/:repo/downloads/:id"}, 219 | {"GET", "/repos/:owner/:repo/forks"}, 220 | {"POST", "/repos/:owner/:repo/forks"}, 221 | {"GET", "/repos/:owner/:repo/hooks"}, 222 | {"GET", "/repos/:owner/:repo/hooks/:id"}, 223 | {"POST", "/repos/:owner/:repo/hooks"}, 224 | //{"PATCH", "/repos/:owner/:repo/hooks/:id"}, 225 | {"POST", "/repos/:owner/:repo/hooks/:id/tests"}, 226 | {"DELETE", "/repos/:owner/:repo/hooks/:id"}, 227 | {"POST", "/repos/:owner/:repo/merges"}, 228 | {"GET", "/repos/:owner/:repo/releases"}, 229 | {"GET", "/repos/:owner/:repo/releases/:id"}, 230 | {"POST", "/repos/:owner/:repo/releases"}, 231 | //{"PATCH", "/repos/:owner/:repo/releases/:id"}, 232 | {"DELETE", "/repos/:owner/:repo/releases/:id"}, 233 | {"GET", "/repos/:owner/:repo/releases/:id/assets"}, 234 | {"GET", "/repos/:owner/:repo/stats/contributors"}, 235 | {"GET", "/repos/:owner/:repo/stats/commit_activity"}, 236 | {"GET", "/repos/:owner/:repo/stats/code_frequency"}, 237 | {"GET", "/repos/:owner/:repo/stats/participation"}, 238 | {"GET", "/repos/:owner/:repo/stats/punch_card"}, 239 | {"GET", "/repos/:owner/:repo/statuses/:ref"}, 240 | {"POST", "/repos/:owner/:repo/statuses/:ref"}, 241 | 242 | // Search 243 | {"GET", "/search/repositories"}, 244 | {"GET", "/search/code"}, 245 | {"GET", "/search/issues"}, 246 | {"GET", "/search/users"}, 247 | {"GET", "/legacy/issues/search/:owner/:repository/:state/:keyword"}, 248 | {"GET", "/legacy/repos/search/:keyword"}, 249 | {"GET", "/legacy/user/search/:keyword"}, 250 | {"GET", "/legacy/user/email/:email"}, 251 | 252 | // Users 253 | {"GET", "/users/:user"}, 254 | {"GET", "/user"}, 255 | //{"PATCH", "/user"}, 256 | {"GET", "/users"}, 257 | {"GET", "/user/emails"}, 258 | {"POST", "/user/emails"}, 259 | {"DELETE", "/user/emails"}, 260 | {"GET", "/users/:user/followers"}, 261 | {"GET", "/user/followers"}, 262 | {"GET", "/users/:user/following"}, 263 | {"GET", "/user/following"}, 264 | {"GET", "/user/following/:user"}, 265 | {"GET", "/users/:user/following/:target_user"}, 266 | {"PUT", "/user/following/:user"}, 267 | {"DELETE", "/user/following/:user"}, 268 | {"GET", "/users/:user/keys"}, 269 | {"GET", "/user/keys"}, 270 | {"GET", "/user/keys/:id"}, 271 | {"POST", "/user/keys"}, 272 | //{"PATCH", "/user/keys/:id"}, 273 | {"DELETE", "/user/keys/:id"}, 274 | } 275 | 276 | var ( 277 | githubBaa http.Handler 278 | ) 279 | 280 | func init() { 281 | println("#GithubAPI Routes:", len(githubAPI)) 282 | githubBaa = loadBaa(githubAPI) 283 | println() 284 | } 285 | 286 | // Static 287 | func BenchmarkBaa_GithubStatic(b *testing.B) { 288 | req, _ := http.NewRequest("GET", "/user/repos", nil) 289 | benchRequest(b, githubBaa, req) 290 | } 291 | 292 | // Param 293 | func BenchmarkBaa_GithubParam(b *testing.B) { 294 | req, _ := http.NewRequest("GET", "/repos/julienschmidt/httprouter/stargazers", nil) 295 | benchRequest(b, githubBaa, req) 296 | } 297 | 298 | // All routes 299 | func BenchmarkBaa_GithubAll(b *testing.B) { 300 | benchRoutes(b, githubBaa, githubAPI) 301 | } 302 | --------------------------------------------------------------------------------