diff --git a/.gitignore b/.gitignore index 496ee2c..ae93b1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -.DS_Store \ No newline at end of file +.DS_Store +tmp \ No newline at end of file diff --git a/README.md b/README.md index 8f2eb76..89d1eea 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ ## [目录](https://geektutu.com/post/gee.html) -- 第一天:前置知识(http.Handler接口) -- 第二天:Tire树实现路由(Router) -- 第三天:支持模板(HTML Template) -- 第四天:JSON & RESTful API +- [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html) +- [第二天:Tire树实现路由(Router)](https://geektutu.com/post/gee-day2.html) +- 第三天:设计Context +- 第四天:支持模板(HTML Template) - 第五天:支持中间件(Middleware) - 第六天:简单鉴权(BASIC AUTH) - 第七天:异常错误处理(Panic) \ No newline at end of file diff --git a/day1-http-base/base1/main.go b/day1-http-base/base1/main.go new file mode 100644 index 0000000..fda7689 --- /dev/null +++ b/day1-http-base/base1/main.go @@ -0,0 +1,31 @@ +package main + +// $ curl http://localhost:9999/ +// URL.Path = "/" +// $ curl http://localhost:9999/hello +// Header["Accept"] = ["*/*"] +// Header["User-Agent"] = ["curl/7.54.0"] + +import ( + "fmt" + "log" + "net/http" +) + +func main() { + http.HandleFunc("/", indexHandler) + http.HandleFunc("/hello", helloHandler) + log.Fatal(http.ListenAndServe(":9999", nil)) +} + +// handler echoes r.URL.Path +func indexHandler(w http.ResponseWriter, req *http.Request) { + fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path) +} + +// handler echoes r.URL.Header +func helloHandler(w http.ResponseWriter, req *http.Request) { + for k, v := range req.Header { + fmt.Fprintf(w, "Header[%q] = %q\n", k, v) + } +} diff --git a/day1-http-base/base2/main.go b/day1-http-base/base2/main.go new file mode 100644 index 0000000..685887d --- /dev/null +++ b/day1-http-base/base2/main.go @@ -0,0 +1,36 @@ +package main + +// $ curl http://localhost:9999/ +// URL.Path = "/" +// $ curl http://localhost:9999/hello +// Header["Accept"] = ["*/*"] +// Header["User-Agent"] = ["curl/7.54.0"] +// curl http://localhost:9999/world +// 404 NOT FOUND: /world + +import ( + "fmt" + "log" + "net/http" +) + +// Engine is the uni handler for all requests +type Engine struct{} + +func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { + switch req.URL.Path { + case "/": + fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path) + case "/hello": + for k, v := range req.Header { + fmt.Fprintf(w, "Header[%q] = %q\n", k, v) + } + default: + fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL) + } +} + +func main() { + engine := new(Engine) + log.Fatal(http.ListenAndServe(":9999", engine)) +} diff --git a/day1-http-base/base3/gee/gee.go b/day1-http-base/base3/gee/gee.go new file mode 100644 index 0000000..7b06c92 --- /dev/null +++ b/day1-http-base/base3/gee/gee.go @@ -0,0 +1,37 @@ +package gee + +import ( + "fmt" + "net/http" +) + +// HandlerFunc defines the request handler used by gee +type HandlerFunc func(http.ResponseWriter, *http.Request) + +// Engine implement the interface of ServeHTTP +type Engine struct { + router map[string]HandlerFunc +} + +// New is the constructor of gee.Engine +func New() *Engine { + return &Engine{router: make(map[string]HandlerFunc)} +} + +// GET defines the method to add GET request +func (engine *Engine) GET(pattern string, handler HandlerFunc) { + engine.router[pattern] = handler +} + +// Run defines the method to start a http server +func (engine *Engine) Run(addr string) (err error) { + return http.ListenAndServe(addr, engine) +} + +func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if handler, ok := engine.router[req.URL.Path]; ok { + handler(w, req) + } else { + fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL) + } +} diff --git a/day1-http-base/base3/main.go b/day1-http-base/base3/main.go new file mode 100644 index 0000000..5f55dc2 --- /dev/null +++ b/day1-http-base/base3/main.go @@ -0,0 +1,31 @@ +package main + +// $ curl http://localhost:9999/ +// URL.Path = "/" +// $ curl http://localhost:9999/hello +// Header["Accept"] = ["*/*"] +// Header["User-Agent"] = ["curl/7.54.0"] +// curl http://localhost:9999/world +// 404 NOT FOUND: /world + +import ( + "fmt" + "net/http" + + "./gee" +) + +func main() { + r := gee.New() + r.GET("/", func(w http.ResponseWriter, req *http.Request) { + fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path) + }) + + r.GET("/hello", func(w http.ResponseWriter, req *http.Request) { + for k, v := range req.Header { + fmt.Fprintf(w, "Header[%q] = %q\n", k, v) + } + }) + + r.Run(":9999") +} diff --git a/day2-router/gee/gee.go b/day2-router/gee/gee.go new file mode 100644 index 0000000..587c729 --- /dev/null +++ b/day2-router/gee/gee.go @@ -0,0 +1,52 @@ +package gee + +import ( + "fmt" + "net/http" +) + +type Params map[string]string + +func (ps *Params) Get(key string) string { + if value, ok := (*ps)[key]; ok { + return value + } + return "" +} + +func (ps *Params) set(key string, value string) { + (*ps)[key] = value +} + +// HandlerFunc defines the request handler used by gee +type HandlerFunc func(http.ResponseWriter, *http.Request, *Params) + +// Engine is defined to handle all requests +type Engine struct { + router *router +} + +// New is constructor of Engine +func New() *Engine { + return &Engine{ + router: &router{root: &node{}}, + } +} + +// GET defines the method to add GET request +func (engine *Engine) GET(pattern string, handler HandlerFunc) { + engine.router.addRoute(pattern, handler) +} + +// Run defines the method to start a http server +func (engine *Engine) Run(addr string) (err error) { + return http.ListenAndServe(addr, engine) +} + +func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if n, params := engine.router.getRoute(req.URL.Path); n != nil { + n.handler(w, req, params) + } else { + fmt.Fprintf(w, "404 NOT FOUND: %s\n", req.URL) + } +} diff --git a/day2-router/gee/router.go b/day2-router/gee/router.go new file mode 100644 index 0000000..6060167 --- /dev/null +++ b/day2-router/gee/router.go @@ -0,0 +1,49 @@ +package gee + +import ( + "strings" +) + +type router struct { + root *node +} + +func (r *router) addRoute(path string, handler HandlerFunc) { + parts := filterNonEmpty(strings.Split(path, "/")) + r.root.insert(path, parts, handler, 0) +} + +func (r *router) getRoute(path string) (*node, *Params) { + searchParts := filterNonEmpty(strings.Split(path, "/")) + n := r.root.search(searchParts, 0) + + if n.path != "" { + parts := filterNonEmpty(strings.Split(n.path, "/")) + params := &Params{} + + for index, part := range parts { + if part[0] == ':' { + params.set(part[1:], searchParts[index]) + } + } + return n, params + } + + return nil, nil +} + +func (r *router) getRoutes() []*node { + list := make([]*node, 0) + r.root.travel(&list) + return list +} + +func filterNonEmpty(vs []string) []string { + parts := make([]string, 0) + for _, item := range vs { + if item != "" { + parts = append(parts, item) + } + } + return parts +} diff --git a/day2-router/gee/router_test.go b/day2-router/gee/router_test.go new file mode 100644 index 0000000..30e573c --- /dev/null +++ b/day2-router/gee/router_test.go @@ -0,0 +1,46 @@ +package gee + +import ( + "fmt" + "testing" +) + +func newTestRouter() *router { + r := &router{root: &node{}} + r.addRoute("/", nil) + r.addRoute("/hello/:name", nil) + r.addRoute("/hello/b/c", nil) + r.addRoute("/hi/:name", nil) + return r +} + +func TestGetRoute(t *testing.T) { + r := newTestRouter() + n, ps := r.getRoute("/a/geektutu") + + fmt.Printf("matched path: %s, params['name']: %s\n", n.path, ps.Get("name")) + + if n == nil { + t.Fatal("nil shouldn't be returned") + } + + if n.path != "/a/:name" { + t.Fatal("should match /a/:name") + } + + if ps.Get("name") != "geektutu" { + t.Fatal("name should be equal to 'geektutu'") + } + +} + +func TestGetRoutes(t *testing.T) { + r := newTestRouter() + for i, n := range r.getRoutes() { + fmt.Println(i+1, n) + } + + if len(r.getRoutes()) != 4 { + t.Fatal("the number of routes shoule be 4") + } +} diff --git a/day2-router/gee/trie.go b/day2-router/gee/trie.go new file mode 100644 index 0000000..5616175 --- /dev/null +++ b/day2-router/gee/trie.go @@ -0,0 +1,82 @@ +package gee + +import ( + "fmt" +) + +type node struct { + path string + part string + children []*node + isWild bool + handler HandlerFunc +} + +func (n *node) String() string { + return fmt.Sprintf("node{path=%s, part=%s, isWild=%t}", n.path, n.part, n.isWild) +} + +func (n *node) insert(path string, parts []string, handler HandlerFunc, height int) { + if len(parts) == height { + n.path = path + n.handler = handler + return + } + + part := parts[height] + child := n.matchChild(part) + if child == nil { + child = &node{part: part, isWild: part[0] == ':'} + n.children = append(n.children, child) + } + child.insert(path, parts, handler, height+1) +} + +func (n *node) search(parts []string, height int) *node { + if len(parts) == height { + if n.path == "" { + return nil + } + return n + } + + part := parts[height] + children := n.matchChildren(part) + + for _, child := range children { + result := child.search(parts, height+1) + if result != nil { + return result + } + } + + return nil +} + +func (n *node) travel(list *([]*node)) { + if n.path != "" { + *list = append(*list, n) + } + for _, child := range n.children { + child.travel(list) + } +} + +func (n *node) matchChild(part string) *node { + for _, child := range n.children { + if child.part == part || child.isWild { + return child + } + } + return nil +} + +func (n *node) matchChildren(part string) []*node { + nodes := make([]*node, 0) + for _, child := range n.children { + if child.part == part || child.isWild { + nodes = append(nodes, child) + } + } + return nodes +} diff --git a/day2-router/main.go b/day2-router/main.go new file mode 100644 index 0000000..a1975a1 --- /dev/null +++ b/day2-router/main.go @@ -0,0 +1,31 @@ +package main + +// $ curl http://localhost:9999/ +// URL.Path = "/" +// $ curl http://localhost:9999/hello +// Header["Accept"] = ["*/*"] +// Header["User-Agent"] = ["curl/7.54.0"] +// curl http://localhost:9999/world +// 404 NOT FOUND: /world + +import ( + "fmt" + "net/http" + + "./gee" +) + +func main() { + r := gee.New() + r.GET("/", func(w http.ResponseWriter, req *http.Request, params *gee.Params) { + fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path) + }) + + r.GET("/hello/:name", helloHandler) + r.Run(":9999") +} + +func helloHandler(w http.ResponseWriter, req *http.Request, params *gee.Params) { + fmt.Fprintf(w, "URL.Path = %q\n", req.URL.Path) + fmt.Fprintf(w, "Parse params in path, name: %s\n", params.Get("name")) +} diff --git a/doc/gee.md b/doc/gee.md index 6805316..4e56bae 100644 --- a/doc/gee.md +++ b/doc/gee.md @@ -62,10 +62,10 @@ func handler(w http.ResponseWriter, r *http.Request) { ## 目录 -- 第一天:前置知识(http.Handler接口) -- 第二天:Tire树实现路由(Router) -- 第三天:支持模板(HTML Template) -- 第四天:JSON & RESTful API +- [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html) +- [第二天:Tire树实现路由(Router)](https://geektutu.com/post/gee-day2.html) +- 第三天:设计Context +- 第四天:支持模板(HTML Template) - 第五天:支持中间件(Middleware) - 第六天:简单鉴权(BASIC AUTH) - 第七天:异常错误处理(Panic)