├── .gitignore ├── .travis.yml ├── LICENSE ├── README.markdown ├── pattern.go ├── pattern_test.go ├── router ├── README.markdown ├── SimpleHTTPServer │ └── server.go └── router.go ├── trie.go └── trie_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.5.2 5 | - 1.5.3 6 | - 1.6 7 | - 1.7 8 | 9 | install: 10 | - go get github.com/importcjj/trie-go 11 | 12 | script: go test ./... 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Jiaju Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | Trie.go 2 | ======= 3 | [![GoDoc](https://godoc.org/github.com/importcjj/trie-go?status.svg)](https://godoc.org/github.com/importcjj/trie-go) 4 | [![Build Status](https://travis-ci.org/importcjj/trie-go.svg?branch=master)](https://travis-ci.org/importcjj/trie-go) 5 | 6 | ## Usage 7 | 8 | ```go 9 | tree := trie.New() 10 | // Put(pattern string, value interface()) 11 | tree.Put("/", "root") 12 | tree.Put("/", "name pattern") 13 | 14 | // Has(pattern string) bool 15 | duplicated := tree.Has("/") 16 | 17 | // Match(key string) bool, result 18 | ok, result := tree.Match("/") 19 | // ok is true 20 | // result.Params is nil 21 | // result.Value.(string) is "root" 22 | 23 | ok, result = tree.Match("/123") 24 | // ok is true 25 | // result.Params is {"id": 123} 26 | // result.Value.(string) is "name pattern" 27 | 28 | ok, result = tree.Match("/hi") 29 | // ok is false 30 | 31 | ok, node := tree.GetNode("/") 32 | if ok { 33 | node.Data["put_data"] = "hello" 34 | } 35 | ``` 36 | 37 | ## Examples 38 | 39 | #### A HTTP router base on it. 40 | 41 | ```go 42 | package main 43 | 44 | import ( 45 | "fmt" 46 | "net/http" 47 | "os" 48 | 49 | "github.com/importcjj/trie-go/router" 50 | ) 51 | 52 | func Helloworld(ctx *router.Context) { 53 | ctx.WriteString("hello, world!") 54 | } 55 | 56 | func ParamHandler(ctx *router.Context) { 57 | username := ctx.ParamString("username") 58 | text := fmt.Sprintf("hi, %s", username) 59 | ctx.WriteString(text) 60 | } 61 | 62 | var PageResource = &router.Handler{ 63 | OnGet: func(ctx *router.Context) { 64 | filepath := ctx.ParamString("filepath") 65 | text := fmt.Sprintf("Get page %s", filepath) 66 | ctx.WriteString(text) 67 | }, 68 | 69 | OnPost: func(ctx *router.Context) { 70 | filepath := ctx.ParamString("filepath") 71 | text := fmt.Sprintf("Post page %s", filepath) 72 | ctx.WriteString(text) 73 | }, 74 | } 75 | 76 | // BasicAuth is a Midwares 77 | func BasicAuth(ctx *router.Context) { 78 | fmt.Fprintln(os.Stderr, ctx.Request.URL, "Call Basic Auth.") 79 | } 80 | 81 | // BeforeMetric mark a time point when the request start. 82 | func BeforeMetric(ctx *router.Context) { 83 | // just a example, so use the params map to 84 | // record the time. 85 | ctx.Params["time"] = time.Now().Format("Mon Jan 2 15:04:05 -0700 MST 2006") 86 | } 87 | 88 | // AfterMetric log the time spent to handle the requeset. 89 | func AfterMetric(ctx *router.Context) { 90 | start, _ := time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", ctx.Params["time"]) 91 | dur := time.Since(start) 92 | fmt.Fprintf(os.Stderr, "%s spent", dur.String()) 93 | } 94 | 95 | var r = router.New() 96 | 97 | func init() { 98 | r.Get("/hello/world", Helloworld) 99 | r.Get("/hi/", ParamHandler) 100 | // restful api style, this pattern can match such as 101 | // "/page/hi.html" "/page/static/inde.html" eta. 102 | r.Router("/page/", PageResource) 103 | 104 | r.Before("/", BasicAuth, BeforeMetric) 105 | r.After("/", AfterMetric) 106 | } 107 | 108 | func main() { 109 | server := &http.Server{ 110 | Addr: ":8080", 111 | Handler: r, 112 | } 113 | server.ListenAndServe() 114 | } 115 | 116 | ``` 117 | -------------------------------------------------------------------------------- /pattern.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "fmt" 5 | "regexp" 6 | "strings" 7 | ) 8 | 9 | // consts 10 | const ( 11 | DefaultPatternDelimeter = ":" 12 | ) 13 | 14 | var triePattern = regexp.MustCompile(`<(?P\w+?:*[\w|\*]+?)>`) 15 | 16 | func init() { 17 | defaultPatternStore.Register("str", `\w+`) 18 | defaultPatternStore.Register("int", `\d+`) 19 | defaultPatternStore.Register("*", `.+`) 20 | defaultPatternStore.DefaultPattern = func() string { 21 | return `(\w+)` 22 | } 23 | } 24 | 25 | // PatternStore is a pattern register. 26 | type PatternStore struct { 27 | Patterns map[string]string 28 | DefaultPattern func() string 29 | } 30 | 31 | // NewPatternStore returns a new PatternStore object. 32 | func NewPatternStore() *PatternStore { 33 | return &PatternStore{ 34 | Patterns: make(map[string]string), 35 | } 36 | } 37 | 38 | // Register can adds a new customize pattern to the pattern store. 39 | func (store *PatternStore) Register(name string, pattern string) error { 40 | if _, ok := store.Patterns[name]; ok { 41 | return ErrDuplicatedPatternName 42 | } 43 | store.Patterns[name] = fmt.Sprintf(`(%s)`, pattern) 44 | return nil 45 | } 46 | 47 | // GetPattern returns a pattern which is bound to name. 48 | // If there is none, returns the default pattern. In the 49 | // Default Pattern Store, it just returns then `str` pattern. 50 | func (store *PatternStore) GetPattern(name string) string { 51 | if pattern, ok := store.Patterns[name]; ok { 52 | return pattern 53 | } 54 | return store.DefaultPattern() 55 | } 56 | 57 | var defaultPatternStore = NewPatternStore() 58 | 59 | // RegisterPattern allow you add a customize pattern to the defaultPatternStore. 60 | func RegisterPattern(name string, pattern string) error { 61 | return defaultPatternStore.Register(name, pattern) 62 | } 63 | 64 | // Pattern of trie node. 65 | type Pattern struct { 66 | pattern *regexp.Regexp 67 | params []string 68 | patternName string 69 | patternStr string 70 | regexpStr string 71 | IsRegexpPattern bool 72 | } 73 | 74 | // NewPattern returns a new Pattern object. 75 | func NewPattern(str string) *Pattern { 76 | var params []string 77 | var subPatternCount int 78 | var subPatternName string 79 | regexpPatternStr := triePattern.ReplaceAllStringFunc(str, func(substr string) string { 80 | p := strings.Split(strings.Trim(substr, "<>"), DefaultPatternDelimeter) 81 | param := p[0] 82 | params = append(params, param) 83 | subPatternName = "" 84 | if len(p) > 1 { 85 | subPatternName = p[1] 86 | } 87 | subPatternCount++ 88 | return defaultPatternStore.GetPattern(subPatternName) 89 | }) 90 | 91 | var pattern = regexp.MustCompile(regexpPatternStr) 92 | var isRegexpPattern = (str != regexpPatternStr) 93 | p := &Pattern{ 94 | pattern: pattern, 95 | params: params, 96 | patternStr: str, 97 | regexpStr: regexpPatternStr, 98 | IsRegexpPattern: isRegexpPattern, 99 | } 100 | if subPatternCount == 1 && subPatternName == "*" { 101 | p.patternName = subPatternName 102 | } 103 | 104 | return p 105 | } 106 | 107 | // Match matches the given string with self's regexpStr, if pattern matched, it 108 | // will return true and the params it found. Otherwise, just returns 109 | // false and nil. 110 | func (pattern *Pattern) Match(str string) (bool, map[string]string) { 111 | if pattern.IsRegexpPattern { 112 | matches := pattern.pattern.FindAllStringSubmatch(str, -1) 113 | if len(matches) == 0 { 114 | return false, nil 115 | } 116 | var patternMap = make(map[string]string) 117 | for i, param := range pattern.params { 118 | patternMap[param] = matches[0][i+1] 119 | } 120 | return true, patternMap 121 | } 122 | return str == pattern.patternStr, nil 123 | } 124 | 125 | // EqualToStr returns true if the pattern's regexpStr just 126 | // equal to self's patternStr 127 | func (pattern *Pattern) EqualToStr(str string) bool { 128 | return str == pattern.patternStr 129 | } 130 | 131 | // MatchEverything returns true, if the pattern is the 132 | // `*` pattern. 133 | func (pattern *Pattern) MatchEverything() bool { 134 | return pattern.patternName == "*" 135 | } 136 | -------------------------------------------------------------------------------- /pattern_test.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func handleError(t *testing.T, err error) { 9 | if err != nil { 10 | t.Error(err) 11 | } 12 | } 13 | 14 | func TestNewPattern(t *testing.T) { 15 | tests := []struct { 16 | PatternStr string 17 | RegexpStr string 18 | }{ 19 | {"a.b.c", "a.b.c"}, 20 | {".b.c.", `.b.c.(\w+)`}, 21 | {"a.b.c.", `a.b.c.(\d+)`}, 22 | {"a.b.", `a.b.(.+)`}, 23 | } 24 | 25 | for _, test := range tests { 26 | pattern := NewPattern(test.PatternStr) 27 | if pattern.regexpStr != test.RegexpStr { 28 | t.Errorf("NewPattern(%s) Parse Error! got %s", test.PatternStr, pattern.regexpStr) 29 | } 30 | } 31 | } 32 | 33 | func TestRegisterPattern(t *testing.T) { 34 | r := `^((\\+86)|(86))?(1(([35][0-9])|[8][0-9]|[7][06789]|[4][579]))\\d{8}$` 35 | err := RegisterPattern("mobile", r) 36 | handleError(t, err) 37 | 38 | s := "a.b." 39 | pattern := NewPattern(s) 40 | if fmt.Sprintf(`a.b.(%s)`, r) != pattern.regexpStr { 41 | t.Errorf("NewPattern(%s) Parse Error! got %s", s, pattern.regexpStr) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /router/README.markdown: -------------------------------------------------------------------------------- 1 | HTTP Router 2 | =========== 3 | 4 | ## Usage 5 | 6 | ```go 7 | package main 8 | 9 | import ( 10 | "fmt" 11 | "net/http" 12 | "os" 13 | 14 | "github.com/importcjj/trie-go/router" 15 | ) 16 | 17 | func Helloworld(ctx *router.Context) { 18 | ctx.WriteString("hello, world!") 19 | } 20 | 21 | func ParamHandler(ctx *router.Context) { 22 | username := ctx.ParamString("username") 23 | text := fmt.Sprintf("hi, %s", username) 24 | ctx.WriteString(text) 25 | } 26 | 27 | var PageResource = &router.Handler{ 28 | OnGet: func(ctx *router.Context) { 29 | filepath := ctx.ParamString("filepath") 30 | text := fmt.Sprintf("Get page %s", filepath) 31 | ctx.WriteString(text) 32 | }, 33 | 34 | OnPost: func(ctx *router.Context) { 35 | filepath := ctx.ParamString("filepath") 36 | text := fmt.Sprintf("Post page %s", filepath) 37 | ctx.WriteString(text) 38 | }, 39 | } 40 | 41 | // BasicAuth is a Midwares 42 | func BasicAuth(ctx *router.Context) { 43 | fmt.Fprintln(os.Stderr, ctx.Request.URL, "Call Basic Auth.") 44 | } 45 | 46 | // BeforeMetric mark a time point when the request start. 47 | func BeforeMetric(ctx *router.Context) { 48 | // just a example, so use the params map to 49 | // record the time. 50 | ctx.Params["time"] = time.Now().Format("Mon Jan 2 15:04:05 -0700 MST 2006") 51 | } 52 | 53 | // AfterMetric log the time spent to handle the requeset. 54 | func AfterMetric(ctx *router.Context) { 55 | start, _ := time.Parse("Mon Jan 2 15:04:05 -0700 MST 2006", ctx.Params["time"]) 56 | dur := time.Since(start) 57 | fmt.Fprintf(os.Stderr, "%s spent", dur.String()) 58 | } 59 | 60 | var r = router.New() 61 | 62 | func init() { 63 | r.Get("/hello/world", Helloworld) 64 | r.Get("/hi/", ParamHandler) 65 | // restful api style, this pattern can match such as 66 | // "/page/hi.html" "/page/static/inde.html" eta. 67 | r.Router("/page/", PageResource) 68 | 69 | r.Before("/", BasicAuth, BeforeMetric) 70 | r.After("/", AfterMetric) 71 | } 72 | 73 | func main() { 74 | server := &http.Server{ 75 | Addr: ":8080", 76 | Handler: r, 77 | } 78 | server.ListenAndServe() 79 | } 80 | ``` 81 | -------------------------------------------------------------------------------- /router/SimpleHTTPServer/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/importcjj/trie-go/router" 9 | ) 10 | 11 | func Helloworld(ctx *router.Context) { 12 | ctx.WriteString("hello, world!") 13 | } 14 | 15 | func ParamHandler(ctx *router.Context) { 16 | username := ctx.ParamString("username") 17 | text := fmt.Sprintf("hi, %s", username) 18 | ctx.WriteString(text) 19 | } 20 | 21 | var PageResource = &router.Handler{ 22 | OnGet: func(ctx *router.Context) { 23 | filepath := ctx.ParamString("filepath") 24 | text := fmt.Sprintf("Get page %s", filepath) 25 | ctx.WriteString(text) 26 | }, 27 | 28 | OnPost: func(ctx *router.Context) { 29 | filepath := ctx.ParamString("filepath") 30 | text := fmt.Sprintf("Post page %s", filepath) 31 | ctx.WriteString(text) 32 | }, 33 | } 34 | 35 | // BasicAuth is a Midwares 36 | func BasicAuth(ctx *router.Context) { 37 | // fmt.Fprintln(os.Stderr, "Call Basic Auth.") 38 | } 39 | 40 | // BeforeMetric mark a time point when the request start. 41 | func BeforeMetric(ctx *router.Context) { 42 | // just a example, so use the params map to 43 | // record the time. 44 | ctx.Data["time"] = time.Now() 45 | } 46 | 47 | // AfterMetric log the time spent to handle the requeset. 48 | func AfterMetric(ctx *router.Context) { 49 | start, _ := ctx.Data["time"].(time.Time) 50 | dur := time.Since(start) 51 | _ = dur 52 | // fmt.Fprintf(os.Stderr, "%s %s [%s]\n", ctx.Request.Method, ctx.Request.URL, dur.String()) 53 | } 54 | 55 | var r = router.New() 56 | 57 | func init() { 58 | r.Get("/hello/world", Helloworld) 59 | r.Get("/hi/", ParamHandler) 60 | // restful api style, this pattern can match such as 61 | // "/page/hi.html" "/page/static/inde.html" eta. 62 | r.Router("/page/", PageResource) 63 | 64 | r.Before("/", BasicAuth, BeforeMetric) 65 | r.After("/", AfterMetric) 66 | } 67 | 68 | func main() { 69 | server := &http.Server{ 70 | Addr: ":8080", 71 | Handler: r, 72 | } 73 | server.ListenAndServe() 74 | } 75 | -------------------------------------------------------------------------------- /router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strconv" 7 | 8 | "github.com/importcjj/trie-go" 9 | ) 10 | 11 | // HTTP methods 12 | const ( 13 | MethodGet = "GET" 14 | MethodPost = "POST" 15 | MethodPut = "PUT" 16 | MethodPatch = "PATCH" 17 | MethodDelete = "DELETE" 18 | ) 19 | 20 | // Router is a ServeMux. 21 | type Router struct { 22 | trie *trie.Trie 23 | handlers map[string]*Handler 24 | } 25 | 26 | // New returns a new Router object. 27 | func New() *Router { 28 | return &Router{ 29 | trie: trie.New(), 30 | } 31 | } 32 | 33 | // Router binds a handler to the pattern. When a requet come in, try to 34 | // call the matched handle func to handle the request. 35 | func (router *Router) Router(pattern string, handler HandlerInterface) { 36 | router.trie.Put(pattern, handler) 37 | } 38 | 39 | // Before binds midwares to a specific pattern. when a HTTP who's URL is matched with 40 | // this pattern, call these midwares at first. 41 | func (router *Router) Before(pattern string, midwares ...func(*Context)) { 42 | exist, node := router.trie.GetNode(pattern) 43 | if exist { 44 | if beforeHooks, ok := node.Data["_before_hooks"]; ok { 45 | hooks := beforeHooks.([]func(*Context)) 46 | node.Data["_before_hooks"] = append(hooks, midwares...) 47 | return 48 | } 49 | node.Data["_before_hooks"] = midwares 50 | return 51 | } 52 | // warn log 53 | } 54 | 55 | // After binds midwares to a specific pattern. when a HTTP who's URL is matched with 56 | // this pattern, call these midwares at last. 57 | func (router *Router) After(pattern string, midwares ...func(context *Context)) { 58 | exist, node := router.trie.GetNode(pattern) 59 | if exist { 60 | if afterHooks, ok := node.Data["_after_hooks"]; ok { 61 | hooks := afterHooks.([]func(*Context)) 62 | node.Data["_after_hooks"] = append(hooks, midwares...) 63 | return 64 | } 65 | node.Data["_after_hooks"] = midwares 66 | return 67 | } 68 | // warn log 69 | } 70 | 71 | // Get Binds the handlefunc which just handle the GET request to the pattern. 72 | func (router *Router) Get(pattern string, handlefunc func(*Context)) { 73 | exist, node := router.trie.GetNode(pattern) 74 | var handler HandlerInterface 75 | if exist { 76 | h := node.Value 77 | handler = h.(HandlerInterface) 78 | } else { 79 | handler = NewHanlder() 80 | } 81 | handler.Get(handlefunc) 82 | router.trie.Put(pattern, handler) 83 | } 84 | 85 | // Post Binds the handlefunc which just handle the POST request to the pattern. 86 | func (router *Router) Post(pattern string, handlefunc func(*Context)) { 87 | exist, node := router.trie.GetNode(pattern) 88 | var handler HandlerInterface 89 | if exist { 90 | h := node.Value 91 | handler = h.(HandlerInterface) 92 | } else { 93 | handler = &Handler{} 94 | } 95 | handler.Post(handlefunc) 96 | router.trie.Put(pattern, handler) 97 | } 98 | 99 | // Put Binds the handlefunc which just handle the PUT request to the pattern. 100 | func (router *Router) Put(pattern string, handlefunc func(*Context)) { 101 | exist, node := router.trie.GetNode(pattern) 102 | var handler HandlerInterface 103 | if exist { 104 | h := node.Value 105 | handler = h.(HandlerInterface) 106 | } else { 107 | handler = &Handler{} 108 | } 109 | handler.Put(handlefunc) 110 | router.trie.Put(pattern, handler) 111 | } 112 | 113 | // Patch Binds the handlefunc which just handle the PATCH request to the pattern. 114 | func (router *Router) Patch(pattern string, handlefunc func(*Context)) { 115 | exist, node := router.trie.GetNode(pattern) 116 | var handler HandlerInterface 117 | if exist { 118 | h := node.Value 119 | handler = h.(HandlerInterface) 120 | } else { 121 | handler = &Handler{} 122 | } 123 | handler.Patch(handlefunc) 124 | router.trie.Put(pattern, handler) 125 | } 126 | 127 | // Delete Binds the handlefunc which just handle the DELETE request to the pattern. 128 | func (router *Router) Delete(pattern string, handlefunc func(*Context)) { 129 | exist, node := router.trie.GetNode(pattern) 130 | var handler HandlerInterface 131 | if exist { 132 | h := node.Value 133 | handler = h.(HandlerInterface) 134 | } else { 135 | handler = &Handler{} 136 | } 137 | handler.Delete(handlefunc) 138 | router.trie.Put(pattern, handler) 139 | } 140 | 141 | // Handler is a HTTP handler. 142 | type Handler struct { 143 | funcs map[string]func(*Context) 144 | OnGet func(context *Context) 145 | OnPost func(context *Context) 146 | OnPut func(context *Context) 147 | OnPatch func(context *Context) 148 | OnDelete func(context *Context) 149 | } 150 | 151 | // NewHanlder returns a new Handler object. 152 | func NewHanlder() *Handler { 153 | return &Handler{ 154 | funcs: make(map[string]func(*Context)), 155 | } 156 | } 157 | 158 | // HandlerInterface is a Interface. 159 | type HandlerInterface interface { 160 | Get(func(*Context)) 161 | Post(func(*Context)) 162 | Put(func(*Context)) 163 | Patch(func(*Context)) 164 | Delete(func(*Context)) 165 | 166 | DoGet(*Context) 167 | DoPost(*Context) 168 | DoPut(*Context) 169 | DoPatch(*Context) 170 | DoDelete(*Context) 171 | } 172 | 173 | // Get updates OnGet method. 174 | func (handler *Handler) Get(handleFunc func(*Context)) { 175 | handler.OnGet = handleFunc 176 | } 177 | 178 | // Post updates OnPost method. 179 | func (handler *Handler) Post(handleFunc func(*Context)) { 180 | handler.OnPost = handleFunc 181 | } 182 | 183 | // Put updates OnPut method. 184 | func (handler *Handler) Put(handleFunc func(*Context)) { 185 | handler.OnPut = handleFunc 186 | } 187 | 188 | // Patch updates OnPatch method. 189 | func (handler *Handler) Patch(handleFunc func(*Context)) { 190 | handler.OnPatch = handleFunc 191 | } 192 | 193 | // Delete updates OnDelete method. 194 | func (handler *Handler) Delete(handleFunc func(*Context)) { 195 | handler.OnDelete = handleFunc 196 | } 197 | 198 | // DoGet handle the GET request. 199 | func (handler *Handler) DoGet(context *Context) { 200 | // handlerFunc, ok := handler.funcs[MethodGet] 201 | if handler.OnGet == nil { 202 | status := http.StatusMethodNotAllowed 203 | text := http.StatusText(status) 204 | context.ResponseWriter.Write([]byte(text)) 205 | context.ResponseWriter.WriteHeader(status) 206 | return 207 | } 208 | handler.OnGet(context) 209 | } 210 | 211 | // DoPost handle the POST request. 212 | func (handler *Handler) DoPost(context *Context) { 213 | if handler.OnPost == nil { 214 | status := http.StatusMethodNotAllowed 215 | text := http.StatusText(status) 216 | context.ResponseWriter.Write([]byte(text)) 217 | context.ResponseWriter.WriteHeader(status) 218 | return 219 | } 220 | handler.OnPost(context) 221 | } 222 | 223 | // DoPut handle the PUT request. 224 | func (handler *Handler) DoPut(context *Context) { 225 | if handler.OnPut == nil { 226 | status := http.StatusMethodNotAllowed 227 | text := http.StatusText(status) 228 | context.ResponseWriter.Write([]byte(text)) 229 | context.ResponseWriter.WriteHeader(status) 230 | return 231 | } 232 | handler.OnPut(context) 233 | } 234 | 235 | // DoPatch handle the PATCH request. 236 | func (handler *Handler) DoPatch(context *Context) { 237 | if handler.OnPatch == nil { 238 | status := http.StatusMethodNotAllowed 239 | text := http.StatusText(status) 240 | context.ResponseWriter.Write([]byte(text)) 241 | context.ResponseWriter.WriteHeader(status) 242 | return 243 | } 244 | handler.OnPatch(context) 245 | } 246 | 247 | // DoDelete handle the DELETE request. 248 | func (handler *Handler) DoDelete(context *Context) { 249 | if handler.OnDelete == nil { 250 | status := http.StatusMethodNotAllowed 251 | text := http.StatusText(status) 252 | context.ResponseWriter.Write([]byte(text)) 253 | context.ResponseWriter.WriteHeader(status) 254 | return 255 | } 256 | handler.OnDelete(context) 257 | } 258 | 259 | func (router *Router) mapMidwares(context *Context, midwares ...func(*Context)) { 260 | for _, midware := range midwares { 261 | midware(context) 262 | } 263 | } 264 | 265 | // ServeDir Serve static files. 266 | func (router *Router) ServeDir(dirname string) { 267 | prefix := fmt.Sprintf("/%s", dirname) 268 | pattern := fmt.Sprintf("/%s/", dirname) 269 | fileserver := http.StripPrefix(prefix, http.FileServer(http.Dir(dirname))) 270 | router.Get(pattern, func(ctx *Context) { 271 | fileserver.ServeHTTP(ctx.ResponseWriter, ctx.Request) 272 | }) 273 | } 274 | 275 | func (router *Router) ServeHTTP(rw http.ResponseWriter, r *http.Request) { 276 | ok, result := router.trie.Match(r.RequestURI) 277 | if !ok { 278 | rw.Write([]byte(http.StatusText(http.StatusNotFound))) 279 | // rw.WriteHeader(404) 280 | return 281 | } 282 | ctx := NewContent(rw, r) 283 | ctx.Params = result.Params 284 | h := result.Value 285 | handler := h.(HandlerInterface) 286 | 287 | // before hooks 288 | for _, data := range result.ChainData { 289 | hooks := data["_before_hooks"] 290 | if hooks != nil { 291 | for _, hook := range hooks.([]func(*Context)) { 292 | hook(ctx) 293 | } 294 | } 295 | } 296 | 297 | switch r.Method { 298 | case MethodGet: 299 | handler.DoGet(ctx) 300 | case MethodPost: 301 | handler.DoPost(ctx) 302 | case MethodPatch: 303 | handler.DoPatch(ctx) 304 | case MethodPut: 305 | handler.DoPut(ctx) 306 | case MethodDelete: 307 | handler.DoDelete(ctx) 308 | default: 309 | status := http.StatusMethodNotAllowed 310 | text := http.StatusText(status) 311 | rw.Write([]byte(text)) 312 | rw.WriteHeader(status) 313 | } 314 | // after hooks 315 | for _, data := range result.ChainData { 316 | hooks := data["_after_hooks"] 317 | if hooks != nil { 318 | for _, hook := range hooks.([]func(*Context)) { 319 | hook(ctx) 320 | } 321 | } 322 | } 323 | } 324 | 325 | // Context store the context of a request. 326 | type Context struct { 327 | Params map[string]string 328 | Data map[string]interface{} 329 | ResponseWriter http.ResponseWriter 330 | Request *http.Request 331 | } 332 | 333 | // NewContent returns a new Context object. 334 | func NewContent(rw http.ResponseWriter, r *http.Request) *Context { 335 | return &Context{ 336 | ResponseWriter: rw, 337 | Data: make(map[string]interface{}), 338 | Request: r, 339 | } 340 | } 341 | 342 | // WriteString write string to ResponseWriter. 343 | func (context *Context) WriteString(v string) { 344 | context.ResponseWriter.Write([]byte(v)) 345 | } 346 | 347 | // ParamString Get param string field with the specific key. 348 | func (context *Context) ParamString(key string, d ...string) string { 349 | if param, ok := context.Params[key]; ok { 350 | return param 351 | } 352 | if len(d) > 0 { 353 | return d[0] 354 | } 355 | 356 | return "" 357 | } 358 | 359 | // ParamInt Get param int field with the specific key. 360 | func (context *Context) ParamInt(key string, d ...int) (int, error) { 361 | if param, ok := context.Params[key]; ok { 362 | v, err := strconv.Atoi(param) 363 | if err != nil { 364 | return 0, err 365 | } 366 | return v, nil 367 | } 368 | if len(d) > 0 { 369 | return d[0], nil 370 | } 371 | 372 | return 0, nil 373 | } 374 | -------------------------------------------------------------------------------- /trie.go: -------------------------------------------------------------------------------- 1 | package trie 2 | 3 | import ( 4 | "errors" 5 | "strings" 6 | ) 7 | 8 | const ( 9 | defalutDelimeter = "/" 10 | ) 11 | 12 | // Errors 13 | var ( 14 | ErrDuplicatedPatternName = errors.New("trie.go: the pattern name already exists") 15 | ) 16 | 17 | // Trie is a tree 18 | type Trie struct { 19 | Delimeter string 20 | Root *Node 21 | } 22 | 23 | // New returns a new Trie object. 24 | func New() *Trie { 25 | return &Trie{ 26 | Delimeter: defalutDelimeter, 27 | Root: NewNode(), 28 | } 29 | } 30 | 31 | // SetDelimeter sets the delimeter of the trie object. 32 | func (trie *Trie) SetDelimeter(delimeter string) { 33 | trie.Delimeter = delimeter 34 | } 35 | 36 | func (trie *Trie) splitPattern(pattern string) []string { 37 | p := strings.TrimRight(pattern, trie.Delimeter) 38 | parts := strings.Split(p, trie.Delimeter) 39 | if parts[0] == "" { 40 | parts[0] = string(pattern[0]) 41 | } 42 | return parts 43 | } 44 | 45 | // Put add a new pattern to the trie tree. 46 | func (trie *Trie) Put(pattern string, value interface{}) error { 47 | // duplicated pattern. 48 | if trie.Has(pattern) { 49 | return ErrDuplicatedPatternName 50 | } 51 | parts := trie.splitPattern(pattern) 52 | t := trie.Root 53 | for i, part := range parts { 54 | if node, ok := t.Children[part]; ok { 55 | t = node 56 | continue 57 | } 58 | // make a new node and put it to t's children nodes. 59 | node := NewNode(part) 60 | node.Value = value 61 | t.Children[part] = node 62 | if i == len(parts)-1 { 63 | node.patternEnding = true 64 | } 65 | t = node 66 | } 67 | 68 | return nil 69 | } 70 | 71 | // Has returns true if the pattern is duplicated 72 | // in the trie tree. Otherwise, returns false. 73 | func (trie *Trie) Has(pattern string) bool { 74 | parts := trie.splitPattern(pattern) 75 | t := trie.Root 76 | for _, part := range parts { 77 | if _, ok := t.Children[part]; !ok { 78 | return false 79 | } 80 | t = t.Children[part] 81 | } 82 | return true 83 | } 84 | 85 | // Match try to match the key with a pattern, if 86 | // successful, it will return true and the value 87 | // which was put with the matched pattern. If the 88 | // pattern is't a certain string, this function 89 | // will also return the params matched by this pattern. 90 | func (trie *Trie) Match(v string) (bool, *Result) { 91 | var result = NewResult() 92 | parts := trie.splitPattern(v) 93 | length := len(parts) 94 | var paramMaps []map[string]string 95 | 96 | t := trie.Root 97 | for i, part := range parts { 98 | isMatched := false 99 | for _, node := range t.Children { 100 | if ok, params := node.Pattern.Match(part); ok { 101 | if i == length-1 && !node.isPatternEnding() { 102 | continue 103 | } 104 | 105 | t = node 106 | isMatched = true 107 | 108 | result.ChainData = append(result.ChainData, node.Data) 109 | 110 | if node.Pattern.MatchEverything() { 111 | for k, v := range params { 112 | seg := []string{v} 113 | params[k] = strings.Join(append(seg, parts[i+1:]...), defalutDelimeter) 114 | } 115 | paramMaps = append(paramMaps, params) 116 | goto finish 117 | } 118 | paramMaps = append(paramMaps, params) 119 | break 120 | } 121 | } 122 | if !isMatched { 123 | return false, nil 124 | } 125 | } 126 | finish: 127 | result.Value = t.Value 128 | var m = make(map[string]string) 129 | for _, params := range paramMaps { 130 | for k, v := range params { 131 | m[k] = v 132 | } 133 | } 134 | result.Params = m 135 | 136 | return true, result 137 | } 138 | 139 | // GetNode allows you use the origin pattern string which was used in Put 140 | // to get the node which points it. 141 | func (trie *Trie) GetNode(v string) (ok bool, node *Node) { 142 | parts := trie.splitPattern(v) 143 | t := trie.Root 144 | for _, part := range parts { 145 | isMatched := false 146 | for _, node := range t.Children { 147 | if ok := node.Pattern.EqualToStr(part); ok { 148 | t = node 149 | isMatched = true 150 | break 151 | } 152 | } 153 | if !isMatched { 154 | return false, nil 155 | } 156 | } 157 | 158 | return true, t 159 | } 160 | 161 | // Node is the tree node of the Trie. 162 | type Node struct { 163 | Pattern *Pattern 164 | Value interface{} 165 | Data map[string]interface{} 166 | Children map[string]*Node 167 | patternEnding bool 168 | } 169 | 170 | // NewNode returns a new Node object. 171 | func NewNode(str ...string) *Node { 172 | node := &Node{ 173 | Children: make(map[string]*Node), 174 | Data: make(map[string]interface{}), 175 | } 176 | if len(str) > 0 { 177 | node.Pattern = NewPattern(str[0]) 178 | // add regexpPattern 179 | } 180 | return node 181 | } 182 | 183 | func (node *Node) isPatternEnding() bool { 184 | return node.patternEnding 185 | } 186 | 187 | // Result is return by Match and GetNode. 188 | type Result struct { 189 | Params map[string]string 190 | Value interface{} 191 | ChainData []map[string]interface{} 192 | } 193 | 194 | // NewResult returns a new Result object. 195 | func NewResult() *Result { 196 | return &Result{} 197 | } 198 | -------------------------------------------------------------------------------- /trie_test.go: -------------------------------------------------------------------------------- 1 | package trie 2 | --------------------------------------------------------------------------------