Commit e00eab7f authored by astaxie's avatar astaxie

beego: change to tree

parent bfabcfcb
......@@ -121,40 +121,7 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
fmt.Fprintln(rw, "AdminHttpPort:", AdminHttpPort)
case "router":
fmt.Fprintln(rw, "Print all router infomation:")
for _, router := range BeeApp.Handlers.fixrouters {
if router.routerType == routerTypeBeego {
if router.hasMethod {
fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.controllerType.Name())
} else {
fmt.Fprintln(rw, router.pattern, "----", router.controllerType.Name())
}
} else if router.routerType == routerTypeRESTFul {
fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.runfunction)
} else if router.routerType == routerTypeHandler {
fmt.Fprintln(rw, router.pattern, "----", router.handler)
}
}
for _, router := range BeeApp.Handlers.routers {
if router.routerType == routerTypeBeego {
if router.hasMethod {
fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.controllerType.Name())
} else {
fmt.Fprintln(rw, router.pattern, "----", router.controllerType.Name())
}
} else if router.routerType == routerTypeRESTFul {
fmt.Fprintln(rw, router.pattern, "----", router.methods, "----", router.runfunction)
} else if router.routerType == routerTypeHandler {
fmt.Fprintln(rw, router.pattern, "----", router.handler)
}
}
if BeeApp.Handlers.enableAuto {
for controllerName, methodObj := range BeeApp.Handlers.autoRouter {
fmt.Fprintln(rw, controllerName, "----")
for methodName, obj := range methodObj {
fmt.Fprintln(rw, " ", methodName, "-----", obj.Name())
}
}
}
// @todo print routers
case "filter":
fmt.Fprintln(rw, "Print all filter infomation:")
if BeeApp.Handlers.enableFilter {
......@@ -164,12 +131,6 @@ func listConf(rw http.ResponseWriter, r *http.Request) {
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
}
}
fmt.Fprintln(rw, "AfterStatic:")
if bf, ok := BeeApp.Handlers.filters[AfterStatic]; ok {
for _, f := range bf {
fmt.Fprintln(rw, f.pattern, utils.GetFuncName(f.filterFunc))
}
}
fmt.Fprintln(rw, "BeforeExec:")
if bf, ok := BeeApp.Handlers.filters[BeforeExec]; ok {
for _, f := range bf {
......
......@@ -100,7 +100,7 @@ func AddGroupRouter(prefix string, groups GroupRouters) *App {
//
// regex router
//
// beego.Router(“/api/:id([0-9]+)“, &controllers.RController{})
// beego.Router("/api/:id([0-9]+)", &controllers.RController{})
//
// custom rules
// beego.Router("/api/list",&RestController{},"*:ListFood")
......@@ -112,6 +112,38 @@ func Router(rootpath string, c ControllerInterface, mappingMethods ...string) *A
return BeeApp
}
// Router add list from
// usage:
// beego.Include(&BankAccount{}, &OrderController{},&RefundController{},&ReceiptController{})
// type BankAccount struct{
// beego.Controller
// }
//
// register the function
// func (b *BankAccount)Mapping(){
// b.Mapping("ShowAccount" , b.ShowAccount)
// b.Mapping("ModifyAccount", b.ModifyAccount)
//}
//
// //@router /account/:id [get]
// func (b *BankAccount) ShowAccount(){
// //logic
// }
//
//
// //@router /account/:id [post]
// func (b *BankAccount) ModifyAccount(){
// //logic
// }
//
// the comments @router url methodlist
// url support all the function Router's pattern
// methodlist [get post head put delete options *]
func Include(cList ...ControllerInterface) *App {
BeeApp.Handlers.Include(cList...)
return BeeApp
}
// RESTRouter adds a restful controller handler to BeeApp.
// its' controller implements beego.ControllerInterface and
// defines a param "pattern/:objectId" to visit each resource.
......@@ -261,14 +293,6 @@ func DelStaticPath(url string) *App {
return BeeApp
}
// [Deprecated] use InsertFilter.
// Filter adds a FilterFunc under pattern condition and named action.
// The actions contains BeforeRouter,AfterStatic,BeforeExec,AfterExec and FinishRouter.
func AddFilter(pattern, action string, filter FilterFunc) *App {
BeeApp.Handlers.AddFilter(pattern, action, filter)
return BeeApp
}
// InsertFilter adds a FilterFunc with pattern condition and action constant.
// The pos means action constant including
// beego.BeforeRouter, beego.AfterStatic, beego.BeforeExec, beego.AfterExec and beego.FinishRouter.
......
......@@ -34,7 +34,8 @@ const (
var (
// custom error when user stop request handler manually.
USERSTOPRUN = errors.New("User stop run")
USERSTOPRUN = errors.New("User stop run")
GlobalControllerRouter map[string]map[string]*Tree //pkgpath+controller:method:routertree
)
// Controller defines some basic http request handler operations, such as
......@@ -55,6 +56,7 @@ type Controller struct {
AppController interface{}
EnableRender bool
EnableXSRF bool
Routers map[string]*Tree //method:routertree
}
// ControllerInterface is an interface to uniform all controller handler.
......@@ -72,6 +74,8 @@ type ControllerInterface interface {
Render() error
XsrfToken() string
CheckXsrfCookie() bool
HandlerFunc(fn interface{})
URLMapping()
}
// Init generates default values of controller operations.
......@@ -86,6 +90,7 @@ func (c *Controller) Init(ctx *context.Context, controllerName, actionName strin
c.EnableRender = true
c.EnableXSRF = true
c.Data = ctx.Input.Data
c.Routers = make(map[string]*Tree)
}
// Prepare runs after Init before request function execution.
......@@ -133,6 +138,32 @@ func (c *Controller) Options() {
http.Error(c.Ctx.ResponseWriter, "Method Not Allowed", 405)
}
// call function fn
func (c *Controller) HandlerFunc(fn interface{}) {
if v, ok := fn.(func()); ok {
v()
}
}
// URLMapping register the internal Controller router.
func (c *Controller) URLMapping() {
}
func (c *Controller) Mapping(method, pattern string, fn func()) {
method = strings.ToLower(method)
if !utils.InSlice(method, HTTPMETHOD) && method != "*" {
Critical("add mapping method:" + method + " is a valid method")
return
}
if t, ok := c.Routers[method]; ok {
t.AddRouter(pattern, fn)
} else {
t = NewTree()
t.AddRouter(pattern, fn)
c.Routers[method] = t
}
}
// Render sends the response with rendered template bytes as text/html type.
func (c *Controller) Render() error {
if !c.EnableRender {
......@@ -295,7 +326,6 @@ func (c *Controller) ServeXml() {
}
// ServeFormatted serve Xml OR Json, depending on the value of the Accept header
func (c *Controller) ServeFormatted() {
accept := c.Ctx.Input.Header("Accept")
switch accept {
......
......@@ -6,51 +6,24 @@
package beego
import "regexp"
// FilterRouter defines filter operation before controller handler execution.
// it can match patterned url and do filter function when action arrives.
type FilterRouter struct {
pattern string
regex *regexp.Regexp
filterFunc FilterFunc
hasregex bool
params map[int]string
parseParams map[string]string
filterFunc FilterFunc
tree *Tree
pattern string
}
// ValidRouter check current request is valid for this filter.
// if matched, returns parsed params in this request by defined filter router pattern.
func (mr *FilterRouter) ValidRouter(router string) (bool, map[string]string) {
if mr.pattern == "" {
return true, nil
}
if mr.pattern == "*" {
return true, nil
}
if router == mr.pattern {
return true, nil
func (f *FilterRouter) ValidRouter(router string) (bool, map[string]string) {
isok, params := f.tree.Match(router)
if isok == nil {
return false, nil
}
//pattern /admin router /admin/ match
//pattern /admin/ router /admin don't match, because url will 301 in router
if n := len(router); n > 1 && router[n-1] == '/' && router[:n-2] == mr.pattern {
return true, nil
}
if mr.hasregex {
if !mr.regex.MatchString(router) {
return false, nil
}
matches := mr.regex.FindStringSubmatch(router)
if len(matches) > 0 {
if len(matches[0]) == len(router) {
params := make(map[string]string)
for i, match := range matches[1:] {
params[mr.params[i]] = match
}
return true, params
}
}
if isok, ok := isok.(bool); ok {
return isok, params
} else {
return false, nil
}
return false, nil
}
......@@ -22,7 +22,7 @@ func TestFilter(t *testing.T) {
r, _ := http.NewRequest("GET", "/person/asta/Xie", nil)
w := httptest.NewRecorder()
handler := NewControllerRegistor()
handler.AddFilter("/person/:last/:first", "AfterStatic", FilterUser)
handler.InsertFilter("/person/:last/:first", BeforeRouter, FilterUser)
handler.Add("/person/:last/:first", &TestController{})
handler.ServeHTTP(w, r)
if w.Body.String() != "i am astaXie" {
......@@ -41,7 +41,7 @@ func TestPatternTwo(t *testing.T) {
r, _ := http.NewRequest("GET", "/admin/", nil)
w := httptest.NewRecorder()
handler := NewControllerRegistor()
handler.AddFilter("/admin/:all", "AfterStatic", FilterAdminUser)
handler.InsertFilter("/admin/?:all", BeforeRouter, FilterAdminUser)
handler.ServeHTTP(w, r)
if w.Body.String() != "i am admin" {
t.Errorf("filter /admin/ can't run")
......@@ -52,7 +52,7 @@ func TestPatternThree(t *testing.T) {
r, _ := http.NewRequest("GET", "/admin/astaxie", nil)
w := httptest.NewRecorder()
handler := NewControllerRegistor()
handler.AddFilter("/admin/:all", "AfterStatic", FilterAdminUser)
handler.InsertFilter("/admin/:all", BeforeRouter, FilterAdminUser)
handler.ServeHTTP(w, r)
if w.Body.String() != "i am admin" {
t.Errorf("filter /admin/astaxie can't run")
......
......@@ -7,18 +7,17 @@ package beego
import (
"net/http"
"strings"
beecontext "github.com/astaxie/beego/context"
"github.com/astaxie/beego/middleware"
)
type namespaceCond func(*beecontext.Context) bool
// Namespace is store all the info
type Namespace struct {
prefix string
condition namespaceCond
handlers *ControllerRegistor
prefix string
handlers *ControllerRegistor
}
// get new Namespace
......@@ -39,8 +38,23 @@ func NewNamespace(prefix string) *Namespace {
// }
// return false
// })
// Cond as the first filter
func (n *Namespace) Cond(cond namespaceCond) *Namespace {
n.condition = cond
fn := func(ctx *beecontext.Context) {
if !cond(ctx) {
middleware.Exception("405", ctx.ResponseWriter, ctx.Request, "Method not allowed")
}
}
if v, ok := n.handlers.filters[BeforeRouter]; ok {
mr := new(FilterRouter)
mr.tree = NewTree()
mr.pattern = "*"
mr.filterFunc = fn
mr.tree.AddRouter("*", true)
n.handlers.filters[BeforeRouter] = append([]*FilterRouter{mr}, v...)
} else {
n.handlers.InsertFilter("*", BeforeRouter, fn)
}
return n
}
......@@ -55,12 +69,13 @@ func (n *Namespace) Cond(cond namespaceCond) *Namespace {
// }
// })
func (n *Namespace) Filter(action string, filter FilterFunc) *Namespace {
var a int
if action == "before" {
action = "BeforeRouter"
a = BeforeRouter
} else if action == "after" {
action = "FinishRouter"
a = FinishRouter
}
n.handlers.AddFilter("*", action, filter)
n.handlers.InsertFilter("*", a, filter)
return n
}
......@@ -167,39 +182,35 @@ func (n *Namespace) Handler(rootpath string, h http.Handler) *Namespace {
//)
func (n *Namespace) Namespace(ns ...*Namespace) *Namespace {
for _, ni := range ns {
n.handlers.Handler(ni.prefix, ni, true)
n.handlers.routers.AddTree(ni.prefix, ni.handlers.routers)
if n.handlers.enableFilter {
for pos, filterList := range ni.handlers.filters {
for _, mr := range filterList {
t := NewTree()
t.AddTree(ni.prefix, mr.tree)
mr.tree = t
n.handlers.insertFilterRouter(pos, mr)
}
}
}
}
return n
}
// Namespace implement the http.Handler
func (n *Namespace) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
//trim the preifix from URL.Path
r.URL.Path = strings.TrimPrefix(r.URL.Path, n.prefix)
// init context
context := &beecontext.Context{
ResponseWriter: rw,
Request: r,
Input: beecontext.NewInput(r),
Output: beecontext.NewOutput(),
}
context.Output.Context = context
context.Output.EnableGzip = EnableGzip
if context.Input.IsWebsocket() {
context.ResponseWriter = rw
}
if n.condition != nil && !n.condition(context) {
http.Error(rw, "Method Not Allowed", 405)
return
}
n.handlers.ServeHTTP(rw, r)
}
// register Namespace into beego.Handler
// support multi Namespace
func AddNamespace(nl ...*Namespace) {
for _, n := range nl {
Handler(n.prefix, n, true)
BeeApp.Handlers.routers.AddTree(n.prefix, n.handlers.routers)
if n.handlers.enableFilter {
for pos, filterList := range n.handlers.filters {
for _, mr := range filterList {
t := NewTree()
t.AddTree(n.prefix, mr.tree)
mr.tree = t
BeeApp.Handlers.insertFilterRouter(pos, mr)
}
}
}
}
}
......@@ -23,7 +23,8 @@ func TestNamespaceGet(t *testing.T) {
ns.Get("/user", func(ctx *context.Context) {
ctx.Output.Body([]byte("v1_user"))
})
ns.ServeHTTP(w, r)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "v1_user" {
t.Errorf("TestNamespaceGet can't run, get the response is " + w.Body.String())
}
......@@ -37,7 +38,8 @@ func TestNamespacePost(t *testing.T) {
ns.Post("/user/:id", func(ctx *context.Context) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
})
ns.ServeHTTP(w, r)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "123" {
t.Errorf("TestNamespacePost can't run, get the response is " + w.Body.String())
}
......@@ -54,7 +56,8 @@ func TestNamespaceNest(t *testing.T) {
ctx.Output.Body([]byte("order"))
}),
)
ns.ServeHTTP(w, r)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "order" {
t.Errorf("TestNamespaceNest can't run, get the response is " + w.Body.String())
}
......@@ -71,36 +74,21 @@ func TestNamespaceNestParam(t *testing.T) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
}),
)
ns.ServeHTTP(w, r)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "123" {
t.Errorf("TestNamespaceNestParam can't run, get the response is " + w.Body.String())
}
}
func TestNamespaceFilter(t *testing.T) {
r, _ := http.NewRequest("GET", "/v1/user/123", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v1")
ns.Filter("before", func(ctx *context.Context) {
ctx.Output.Body([]byte("this is Filter"))
}).
Get("/user/:id", func(ctx *context.Context) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
})
ns.ServeHTTP(w, r)
if w.Body.String() != "this is Filter" {
t.Errorf("TestNamespaceFilter can't run, get the response is " + w.Body.String())
}
}
func TestNamespaceRouter(t *testing.T) {
r, _ := http.NewRequest("GET", "/v1/api/list", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v1")
ns.Router("/api/list", &TestController{}, "*:List")
ns.ServeHTTP(w, r)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "i am list" {
t.Errorf("TestNamespaceRouter can't run, get the response is " + w.Body.String())
}
......@@ -112,17 +100,36 @@ func TestNamespaceAutoFunc(t *testing.T) {
ns := NewNamespace("/v1")
ns.AutoRouter(&TestController{})
ns.ServeHTTP(w, r)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "i am list" {
t.Errorf("user define func can't run")
}
}
func TestNamespaceCond(t *testing.T) {
r, _ := http.NewRequest("GET", "/v1/test/list", nil)
func TestNamespaceFilter(t *testing.T) {
r, _ := http.NewRequest("GET", "/v1/user/123", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v1")
ns.Filter("before", func(ctx *context.Context) {
ctx.Output.Body([]byte("this is Filter"))
}).
Get("/user/:id", func(ctx *context.Context) {
ctx.Output.Body([]byte(ctx.Input.Param(":id")))
})
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Body.String() != "this is Filter" {
t.Errorf("TestNamespaceFilter can't run, get the response is " + w.Body.String())
}
}
func TestNamespaceCond(t *testing.T) {
r, _ := http.NewRequest("GET", "/v2/test/list", nil)
w := httptest.NewRecorder()
ns := NewNamespace("/v2")
ns.Cond(func(ctx *context.Context) bool {
if ctx.Input.Domain() == "beego.me" {
return true
......@@ -130,7 +137,8 @@ func TestNamespaceCond(t *testing.T) {
return false
}).
AutoRouter(&TestController{})
ns.ServeHTTP(w, r)
AddNamespace(ns)
BeeApp.Handlers.ServeHTTP(w, r)
if w.Code != 405 {
t.Errorf("TestNamespaceCond can't run get the result " + strconv.Itoa(w.Code))
}
......
// Beego (http://beego.me/)
// @description beego is an open-source, high-performance web framework for the Go programming language.
// @link http://github.com/astaxie/beego for the canonical source repository
// @license http://github.com/astaxie/beego/blob/master/LICENSE
// @authors astaxie
package beego
This diff is collapsed.
......@@ -10,6 +10,7 @@ import (
"net/http"
"net/http/httptest"
"testing"
"github.com/astaxie/beego/context"
)
......@@ -76,16 +77,17 @@ func TestUrlFor(t *testing.T) {
handler.Add("/person/:last/:first", &TestController{})
handler.AddAuto(&TestController{})
if handler.UrlFor("TestController.List") != "/api/list" {
Info(handler.UrlFor("TestController.List"))
t.Errorf("TestController.List must equal to /api/list")
}
if handler.UrlFor("TestController.Get", ":last", "xie", ":first", "asta") != "/person/xie/asta" {
t.Errorf("TestController.Get must equal to /person/xie/asta")
}
if handler.UrlFor("TestController.Myext") != "/Test/Myext" {
t.Errorf("TestController.Myext must equal to /Test/Myext")
if handler.UrlFor("TestController.Myext") != "/test/myext" {
t.Errorf("TestController.Myext must equal to /test/myext")
}
if handler.UrlFor("TestController.GetUrl") != "/Test/GetUrl" {
t.Errorf("TestController.GetUrl must equal to /Test/GetUrl")
if handler.UrlFor("TestController.GetUrl") != "/test/geturl" {
t.Errorf("TestController.GetUrl must equal to /test/geturl")
}
}
......
......@@ -25,6 +25,26 @@ func NewTree() *Tree {
}
}
// add Tree to the exist Tree
// prefix should has no params
func (t *Tree) AddTree(prefix string, tree *Tree) {
t.addtree(splitPath(prefix), tree)
}
func (t *Tree) addtree(segments []string, tree *Tree) {
if len(segments) == 0 {
panic("prefix should has path")
}
if len(segments) == 1 && segments[0] != "" {
t.fixrouters[segments[0]] = tree
return
}
seg := segments[0]
subTree := NewTree()
t.fixrouters[seg] = subTree
subTree.addtree(segments[1:], tree)
}
// call addseg function
func (t *Tree) AddRouter(pattern string, runObject interface{}) {
t.addseg(splitPath(pattern), runObject, nil, "")
......@@ -83,22 +103,40 @@ func (t *Tree) match(segments []string, wildcardValues []string) (runObject inte
return t.leaf.runObject, pa
}
}
if t.wildcard != nil && t.wildcard.leaf != nil {
if ok, pa := t.wildcard.leaf.match(wildcardValues); ok {
return t.wildcard.leaf.runObject, pa
}
}
return nil, nil
}
var seg string
seg, segments = segments[0], segments[1:]
seg, segs := segments[0], segments[1:]
subTree, ok := t.fixrouters[seg]
if ok {
runObject, params = subTree.match(segments, wildcardValues)
runObject, params = subTree.match(segs, wildcardValues)
} else if len(segs) == 0 { //.json .xml
if subindex := strings.LastIndex(seg, "."); subindex != -1 {
subTree, ok = t.fixrouters[seg[:subindex]]
if ok {
runObject, params = subTree.match(segs, wildcardValues)
if runObject != nil {
if params == nil {
params = make(map[string]string)
}
params[":ext"] = seg[subindex+1:]
return runObject, params
}
}
}
}
if runObject == nil && t.wildcard != nil {
runObject, params = t.wildcard.match(segments, append(wildcardValues, seg))
runObject, params = t.wildcard.match(segs, append(wildcardValues, seg))
}
if runObject == nil {
if t.leaf != nil {
if ok, pa := t.leaf.match(append(wildcardValues, seg)); ok {
if ok, pa := t.leaf.match(append(wildcardValues, segments...)); ok {
return t.leaf.runObject, pa
}
}
......@@ -122,7 +160,21 @@ func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params map[string
// has error
if len(wildcardValues) == 0 && len(leaf.wildcards) > 0 {
if utils.InSlice(":", leaf.wildcards) {
return true, nil
params = make(map[string]string)
j := 0
for _, v := range leaf.wildcards {
if v == ":" {
continue
}
params[v] = ""
j += 1
}
return true, params
}
if len(leaf.wildcards) == 1 && leaf.wildcards[0] == ":splat" {
params = make(map[string]string)
params[":splat"] = ""
return true, params
}
Error("bug of router")
return false, nil
......@@ -155,10 +207,27 @@ func (leaf *leafInfo) match(wildcardValues []string) (ok bool, params map[string
if v == ":" {
continue
}
if v == "." {
lastone := wildcardValues[len(wildcardValues)-1]
strs := strings.SplitN(lastone, ".", 2)
if len(strs) == 2 {
params[":ext"] = strs[1]
} else {
params[":ext"] = ""
}
if len(wildcardValues[j:]) == 1 {
params[":path"] = strs[0]
} else {
params[":path"] = path.Join(wildcardValues[j:]...) + "/" + strs[0]
}
return true, params
}
params[v] = wildcardValues[j]
j += 1
}
if len(params) != len(wildcardValues) {
Info(params)
Info(wildcardValues)
Error("bug of router")
return false, nil
}
......@@ -193,7 +262,7 @@ func splitPath(key string) []string {
// "admin" -> false, nil, ""
// ":id" -> true, [:id], ""
// "?:id" -> true, [: id], "" : meaning can empty
// "?:id" -> true, [: :id], "" : meaning can empty
// ":id:int" -> true, [:id], ([0-9]+)
// ":name:string" -> true, [:name], ([\w]+)
// ":id([0-9]+)" -> true, [:id], ([0-9]+)
......
......@@ -13,10 +13,16 @@ var routers []testinfo
func init() {
routers = make([]testinfo, 0)
routers = append(routers, testinfo{"/:id", "/123", map[string]string{":id": "123"}})
routers = append(routers, testinfo{"/hello/?:id", "/hello", map[string]string{":id": ""}})
routers = append(routers, testinfo{"/", "/", nil})
routers = append(routers, testinfo{"/customer/login", "/customer/login", nil})
routers = append(routers, testinfo{"/customer/login", "/customer/login.json", map[string]string{":ext": "json"}})
routers = append(routers, testinfo{"/*", "/customer/123", map[string]string{":splat": "customer/123"}})
routers = append(routers, testinfo{"/customer/*", "/customer", map[string]string{":splat": ""}})
routers = append(routers, testinfo{"/*", "/customer/2009/12/11", map[string]string{":splat": "customer/2009/12/11"}})
routers = append(routers, testinfo{"/*.*", "/nice/api.json", map[string]string{":path": "nice/api", ":ext": "json"}})
routers = append(routers, testinfo{"/:name/*.*", "/nice/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}})
routers = append(routers, testinfo{"/:name/test/*.*", "/nice/test/api.json", map[string]string{":name": "nice", ":path": "api", ":ext": "json"}})
routers = append(routers, testinfo{"/v1/shop/:id:int", "/v1/shop/123", map[string]string{":id": "123"}})
routers = append(routers, testinfo{"/v1/shop/:id/:name", "/v1/shop/123/nike", map[string]string{":id": "123", ":name": "nike"}})
routers = append(routers, testinfo{"/v1/shop/:id/account", "/v1/shop/123/account", map[string]string{":id": "123"}})
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment