From 313aec9304582b60a02e06419ff7291367f5b023 Mon Sep 17 00:00:00 2001 From: gzdaijie Date: Sun, 1 Sep 2019 21:37:50 +0800 Subject: [PATCH] add day4-day5 doc & fix some comments --- README.md | 4 +- day4-group/gee/gee.go | 2 +- day4-group/main.go | 21 +++- day5-middleware/gee/gee.go | 2 +- day6-template/gee/gee.go | 2 +- day7-panic-recover/gee/gee.go | 2 +- doc/gee-day1.md | 2 +- doc/gee-day2.md | 2 +- doc/gee-day3.md | 9 +- doc/gee-day4.md | 164 +++++++++++++++++++++++++ doc/gee-day4/group.jpg | Bin 0 -> 11880 bytes doc/gee-day5.md | 225 ++++++++++++++++++++++++++++++++++ doc/gee-day5/middleware.jpg | Bin 0 -> 8309 bytes doc/gee.md | 6 +- 14 files changed, 423 insertions(+), 18 deletions(-) create mode 100644 doc/gee-day4.md create mode 100644 doc/gee-day4/group.jpg create mode 100644 doc/gee-day5.md create mode 100644 doc/gee-day5/middleware.jpg diff --git a/README.md b/README.md index 4dd3322..4a9a58c 100644 --- a/README.md +++ b/README.md @@ -9,8 +9,8 @@ Gee 的设计与实现参考了Gin,这个教程可以快速入门:[Go Gin简 - [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html),[Code - Github](day1-http-base) - [第二天:上下文设计(Context)](https://geektutu.com/post/gee-day2.html),[Code - Github](day2-context) - [第三天:Tire树路由(Router)](https://geektutu.com/post/gee-day3.html),[Code - Github](day3-router) -- 第四天:分组控制(Group),[Code - Github](day4-group) -- 第五天:中间件(Middleware),[Code - Github](day5-middleware) +- [第四天:分组控制(Group)](https://geektutu.com/post/gee-day4.html),[Code - Github](day4-group) +- [第五天:中间件(Middleware)]((https://geektutu.com/post/gee-day5.html),[Code - Github](day5-middleware) - 第六天:HTML模板(Template),[Code - Github](day6-template) - 第七天:错误恢复(Panic Recover),[Code - Github](day7-panic-recover) diff --git a/day4-group/gee/gee.go b/day4-group/gee/gee.go index b7ee570..8c5d374 100644 --- a/day4-group/gee/gee.go +++ b/day4-group/gee/gee.go @@ -19,7 +19,7 @@ type ( Engine struct { *RouterGroup router *router - groups []*RouterGroup // store all group + groups []*RouterGroup // store all groups } ) diff --git a/day4-group/main.go b/day4-group/main.go index 1bd5346..68c2d74 100644 --- a/day4-group/main.go +++ b/day4-group/main.go @@ -1,7 +1,15 @@ package main /* -(1) v1 +(1) index +curl -i http://localhost:9999/index +HTTP/1.1 200 OK +Date: Sun, 01 Sep 2019 08:12:23 GMT +Content-Length: 19 +Content-Type: text/html; charset=utf-8 +

Index Page

+ +(2) v1 $ curl -i http://localhost:9999/v1/ HTTP/1.1 200 OK Date: Mon, 12 Aug 2019 18:11:07 GMT @@ -9,19 +17,19 @@ Content-Length: 18 Content-Type: text/html; charset=utf-8

Hello Gee

-(2) +(3) $ curl "http://localhost:9999/v1/hello?name=geektutu" hello geektutu, you're at /v1/hello -(3) +(4) $ curl "http://localhost:9999/v2/hello/geektutu" hello geektutu, you're at /hello/geektutu -(4) +(5) $ curl "http://localhost:9999/v2/login" -X POST -d 'username=geektutu&password=1234' {"password":"1234","username":"geektutu"} -(5) +(6) $ curl "http://localhost:9999/hello" 404 NOT FOUND: /hello */ @@ -34,6 +42,9 @@ import ( func main() { r := gee.New() + r.GET("/index", func(c *gee.Context) { + c.HTML(http.StatusOK, "

Index Page

") + }) v1 := r.Group("/v1") { v1.GET("/", func(c *gee.Context) { diff --git a/day5-middleware/gee/gee.go b/day5-middleware/gee/gee.go index 29ae1d4..e880d19 100644 --- a/day5-middleware/gee/gee.go +++ b/day5-middleware/gee/gee.go @@ -20,7 +20,7 @@ type ( Engine struct { *RouterGroup router *router - groups []*RouterGroup // store all group + groups []*RouterGroup // store all groups } ) diff --git a/day6-template/gee/gee.go b/day6-template/gee/gee.go index 54af464..e48a496 100644 --- a/day6-template/gee/gee.go +++ b/day6-template/gee/gee.go @@ -22,7 +22,7 @@ type ( Engine struct { *RouterGroup router *router - groups []*RouterGroup // store all group + groups []*RouterGroup // store all groups htmlTemplates *template.Template // for html render funcMap template.FuncMap // for html render } diff --git a/day7-panic-recover/gee/gee.go b/day7-panic-recover/gee/gee.go index 99bcf37..0643fb2 100644 --- a/day7-panic-recover/gee/gee.go +++ b/day7-panic-recover/gee/gee.go @@ -22,7 +22,7 @@ type ( Engine struct { *RouterGroup router *router - groups []*RouterGroup // store all group + groups []*RouterGroup // store all groups htmlTemplates *template.Template // for html render funcMap template.FuncMap // for html render } diff --git a/doc/gee-day1.md b/doc/gee-day1.md index 272bbc4..14385d2 100644 --- a/doc/gee-day1.md +++ b/doc/gee-day1.md @@ -1,7 +1,7 @@ --- title: Go语言动手写Web框架 - Gee第一天 http.Handler date: 2019-08-12 00:10:10 -description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,从零设计一个Web框架。本文介绍了Go标准库 net/http 和 http.Handler 接口的使用,拦截所有的 HTTP 请求,交给Gee框架处理。 +description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,以 Gin 为原型从零设计一个Web框架。本文介绍了Go标准库 net/http 和 http.Handler 接口的使用,拦截所有的 HTTP 请求,交给Gee框架处理。 tags: - Go categories: diff --git a/doc/gee-day2.md b/doc/gee-day2.md index e619d1b..e4b5f89 100644 --- a/doc/gee-day2.md +++ b/doc/gee-day2.md @@ -1,7 +1,7 @@ --- title: Go语言动手写Web框架 - Gee第二天 上下文Context date: 2019-08-19 00:10:10 -description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,从零设计一个Web框架。本文介绍了请求上下文(Context)的设计理念,封装了返回JSON/String/Data/HTML等类型响应的方法。 +description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,以 Gin 为原型从零设计一个Web框架。本文介绍了请求上下文(Context)的设计理念,封装了返回JSON/String/Data/HTML等类型响应的方法。 tags: - Go categories: diff --git a/doc/gee-day3.md b/doc/gee-day3.md index e595fc6..f838554 100644 --- a/doc/gee-day3.md +++ b/doc/gee-day3.md @@ -1,7 +1,7 @@ --- -title: Go语言动手写Web框架 - Gee第三天 路由Router +title: Go语言动手写Web框架 - Gee第三天 前缀树路由Router date: 2019-08-28 00:10:10 -description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,从零设计一个Web框架。本文介绍了如何用 Trie 前缀树实现路由。支持简单的参数解析和通配符的场景。 +description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,以 Gin 为原型从零设计一个Web框架。本文介绍了如何用 Trie 前缀树实现路由 Route。支持简单的参数解析和通配符的场景。 tags: - Go categories: @@ -15,6 +15,11 @@ image: post/gee-day3/trie_router.jpg github: https://github.com/geektutu/7days-golang --- +本文是 [7天用Go从零实现Web框架Gee教程系列](https://geektutu.com/post/gee.html)的第三篇。 + +- 使用 Tire 树实现动态路由(dynamic route)解析。 +- 支持两种模式`:name`和`*filepath`,**代码约150行**。 + ## Trie 树简介 之前,我们用了一个非常简单的`map`结构存储了路由表,使用`map`存储键值对,索引非常高效,但是有一个弊端,键值对的存储的方式,只能用来索引静态路由。那如果我们想支持类似于`/hello/:name`这样的动态路由怎么办呢?所谓动态路由,即一条路由规则可以匹配某一类型而非某一条固定的路由。例如`/hello/:name`,可以匹配`/hello/geektutu`、`hello/jack`等。 diff --git a/doc/gee-day4.md b/doc/gee-day4.md new file mode 100644 index 0000000..c6263f0 --- /dev/null +++ b/doc/gee-day4.md @@ -0,0 +1,164 @@ +--- +title: Go语言动手写Web框架 - Gee第四天 分组控制Group +date: 2019-09-01 15:10:10 +description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,以 Gin 为原型从零设计一个Web框架。本文介绍了分组控制(Group Control)的意义,以及嵌套分组路由的实现。 +tags: +- Go +categories: +- 从零实现 +keywords: +- Go语言 +- 从零实现Web框架 +- 动手写Web框架 +- Group Control +image: post/gee-day4/group.jpg +github: https://github.com/geektutu/7days-golang +--- + +本文是 [7天用Go从零实现Web框架Gee教程系列](https://geektutu.com/post/gee.html)的第四篇。 + +- 实现路由分组控制(Route Group Control),**代码约50行** + +## 分组的意义 + +分组控制(Group Control)是 Web 框架应提供的基础功能之一。所谓分组,是指路由的分组。如果没有路由分组,我们需要针对每一个路由进行控制。但是真实的业务场景中,往往某一组路由需要相似的处理。例如: + +- 以`/post`开头的路由匿名可访问。 +- 以`/admin`开头的路由需要鉴权。 +- 以`/api`开头的路由是 RESTful 接口,可以对接第三方平台,需要三方平台鉴权。 + +大部分情况下的路由分组,是以相同的前缀来区分的。因此,我们今天实现的分组控制也是以前缀来区分,并且支持分组的嵌套。例如`/post`是一个分组,`/post/a`和`/post/b`可以是该分组下的子分组。作用在`/post`分组上的中间件(middleware),也都会作用在子分组,子分组还可以应用自己特有的中间件。 + +中间件可以给框架提供无限的扩展能力,应用在分组上,可以使得分组控制的收益更为明显,而不是共享相同的路由前缀这么简单。例如`/admin`的分组,可以应用鉴权中间件;`/`分组应用日志中间件,`/`是默认的最顶层的分组,也就意味着给所有的路由,即整个框架增加了记录日志的能力。 + +提供扩展能力支持中间件的内容,我们将在下一节当中介绍。 + +##分组嵌套 + +一个 Group 对象需要具备哪些属性呢?首先是前缀(prefix),比如`/`,或者`/api`;要支持分组嵌套,那么需要知道当前分组的父亲(parent)是谁;当然了,按照我们一开始的分析,中间件是应用在分组上的,那还需要存储应用在该分组上的中间件(middlewares)。还记得,我们之前调用函数`(*Engine).addRoute()`来映射所有的路由规则和 Handler 。如果Group对象需要直接映射路由规则的话,比如我们想在使用框架时,这么调用: + +```go +r := gee.New() +v1 := r.Group("/v1") +v1.GET("/", func(c *gee.Context) { + c.HTML(http.StatusOK, "

Hello Gee

") +}) +``` + +那么Group对象,还需要有访问`Router`的能力,为了方便,我们可以在Group中,保存一个指针,指向`Engine`,整个框架的所有资源都是由`Engine`统一协调的,那么就可以通过`Engine`间接地访问各种接口了。 + +所以,最后的 Group 的定义是这样的: + +**[day4-group/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/day4-group/gee)** + +```go +RouterGroup struct { + prefix string + middlewares []HandlerFunc // support middleware + parent *RouterGroup // support nesting + engine *Engine // all groups share a Engine instance +} +``` + +我们还可以进一步地抽象,将`Engine`作为最顶层的分组,也就是说`Engine`拥有`RouterGroup`所有的能力。 + +```go +Engine struct { + *RouterGroup + router *router + groups []*RouterGroup // store all groups +} +``` + +那我们就可以将和路由有关的函数,都交给`RouterGroup`实现了。 + +```go +// New is the constructor of gee.Engine +func New() *Engine { + engine := &Engine{router: newRouter()} + engine.RouterGroup = &RouterGroup{engine: engine} + engine.groups = []*RouterGroup{engine.RouterGroup} + return engine +} + +// Group is defined to create a new RouterGroup +// remember all groups share the same Engine instance +func (group *RouterGroup) Group(prefix string) *RouterGroup { + engine := group.engine + newGroup := &RouterGroup{ + prefix: group.prefix + prefix, + parent: group, + engine: engine, + } + engine.groups = append(engine.groups, newGroup) + return newGroup +} + +func (group *RouterGroup) addRoute(method string, comp string, handler HandlerFunc) { + pattern := group.prefix + comp + group.engine.router.addRoute(method, pattern, handler) +} + +// GET defines the method to add GET request +func (group *RouterGroup) GET(pattern string, handler HandlerFunc) { + group.addRoute("GET", pattern, handler) +} + +// POST defines the method to add POST request +func (group *RouterGroup) POST(pattern string, handler HandlerFunc) { + group.addRoute("POST", pattern, handler) +} +``` + +可以仔细观察下`addRoute`函数,调用了`group.engine.router.addRoute`来实现了路由的映射。由于`Engine`从某种意义上继承了`RouterGroup`的所有属性和方法,因为 (*Engine).engine 是指向自己的。这样实现,我们既可以像原来一样添加路由,也可以通过分组添加路由。 + +## 使用 Demo + +测试框架的Demo就可以这样写了: + +```go +func main() { + r := gee.New() + r.GET("/index", func(c *gee.Context) { + c.HTML(http.StatusOK, "

Index Page

") + }) + v1 := r.Group("/v1") + { + v1.GET("/", func(c *gee.Context) { + c.HTML(http.StatusOK, "

Hello Gee

") + }) + + v1.GET("/hello", func(c *gee.Context) { + // expect /hello?name=geektutu + c.String(http.StatusOK, "hello %s, you're at %s\n", c.Query("name"), c.Path) + }) + } + v2 := r.Group("/v2") + { + v2.GET("/hello/:name", func(c *gee.Context) { + // expect /hello/geektutu + c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path) + }) + v2.POST("/login", func(c *gee.Context) { + c.JSON(http.StatusOK, gee.H{ + "username": c.PostForm("username"), + "password": c.PostForm("password"), + }) + }) + + } + + r.Run(":9999") +} +``` + +通过 curl 简单测试: + +```bash +$ curl "http://localhost:9999/v1/hello?name=geektutu" +hello geektutu, you're at /v1/hello + +$ curl "http://localhost:9999/v2/hello/geektutu" +hello geektutu, you're at /hello/geektutu +``` + diff --git a/doc/gee-day4/group.jpg b/doc/gee-day4/group.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fc7477402fc6380e2cab520487b7744a3ae5988b GIT binary patch literal 11880 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 EK?+#5;kVhHM zlDc>$h<*e@B;TRJlDr&rA;71qNHO^AU4={L^G1-}D&T}_aaDZNM#VN2MO#{A%_dd| zn%JGEb~50J7_cuDe|vj zBr;h+6QF<(zdP8--mX#b5ffMp)e?xOXpg&XnZXO;7X7G4)gk%>D70#!-hqH*jdF}n zuZ_ia;sPn+U;q{8i>h418s^p8;K@Q!Himrpb?lWGZI zE(6}guknRXPliPPfrp$RIBtl*{xOnnRmrqzeN(jW@f032#bwQ~cU{L5kQ6V;_uIsM zt4kPMu5-XB76N18jwd@(4=H7fTWMUnEc>ecdL&9IkIbzs;>#M{4IAai?`uFva-Il%A5>4vMk>63SX6^P zFaSUeOK2nr_6=kf|cpNS;58dY+11 zZ`uKpaqpHrrb)G-C1uvxboh65`ATLX9R#E*E=}VAXF?Nui1*bzm)Fg3f{gnNk`#yC z3a}+99Ao5(tyr$KA+$VM^cquOm!nCbH%Vfv*js2xNO<-nyU28b{L*UadvK3$7jkBc z>1;BUHLpQ;1(%!J!hjr%%^M0+dPk{I(v*dI6X`-}z|VM!B9>-d#1Y}=GNPh~Y2Dx? z$C33`f>wg-O(cvcm|EAx?9!3#N!3lvP(=yja|uZ!2+lLjdvOwpjU?X+U8k^7LmJ#~ z<$p+qdBRi!$IF%SNtU|1R?vYCBU9&p;M-e%uWLk^QG?0_v=h7BX4^jj&206yWeHvI z3eKAF$|*k!12y(zXtnm9L#U7SwSsBO3m3s5i#G=ZjqKOPGDD)nGHlOAzdS$m2N+Cdou#(2atPMMo0wER~4-* zd!)0ze-$4W4nv-`(loqKyG4PrLx9O}x%XIc{#zOWoO{>>^G{Ko>7wU3vH2p(M+40V zlEg=V)yc=V%iUI+aF-uXKdN4L?5@I%%3lMiqME`LJ9Ffu++%+{_F0_|1UmkV%f>$i z7apsbF_d*Lm6+!|gi6WF(WnA`yWg`UzY|UzRZY}+XvTUeb|hV|%^m5)UN~E6J%-)*Ja^dQ;rKqH1DC(-@Zu iyi?X4X}NeBZK5)Cwq8&VzWjwFn-~4?f(F3Z part3 -> Handler -> part 4 -> part2`。恰恰满足了我们对中间件的要求,接下来看调用部分的代码,就能全部串起来了。 + + +## 代码实现 + +- 定义`Use`函数,将中间件应用到某个 Group 。 + +**[day4-group/gee/gee.go](https://github.com/geektutu/7days-golang/tree/master/day5-middleware/gee)** + +```go +// Use is defined to add middleware to the group +func (group *RouterGroup) Use(middlewares ...HandlerFunc) { + group.middlewares = append(group.middlewares, middlewares...) +} + +func (engine *Engine) ServeHTTP(w http.ResponseWriter, req *http.Request) { + var middlewares []HandlerFunc + for _, group := range engine.groups { + if strings.HasPrefix(req.URL.Path, group.prefix) { + middlewares = append(middlewares, group.middlewares...) + } + } + c := newContext(w, req) + c.handlers = middlewares + engine.router.handle(c) +} +``` + +ServeHTTP 函数也有变化,当我们接收到一个具体请求时,要判断该请求适用于哪些中间件,在这里我们简单通过 URL 的前缀来判断。得到中间件列表后,赋值给 `c.handlers`。 + +- handle 函数中,将从路由匹配得到的 Handler 添加到 `c.handlers`列表中,执行`c.Next()`。 + +**[day4-group/gee/router.go](https://github.com/geektutu/7days-golang/tree/master/day5-middleware/gee)** + +```go +func (r *router) handle(c *Context) { + n, params := r.getRoute(c.Method, c.Path) + + if n != nil { + key := c.Method + "-" + n.pattern + c.Params = params + c.handlers = append(c.handlers, r.handlers[key]) + } else { + c.handlers = append(c.handlers, func(c *Context) { + c.String(http.StatusNotFound, "404 NOT FOUND: %s\n", c.Path) + }) + } + c.Next() +} +``` + +## 使用 Demo + +```go +func onlyForV2() gee.HandlerFunc { + return func(c *gee.Context) { + // Start timer + t := time.Now() + // if a server error occurred + c.Fail(500, "Internal Server Error") + // Calculate resolution time + log.Printf("[%d] %s in %v for group v2", c.StatusCode, c.Req.RequestURI, time.Since(t)) + } +} + +func main() { + r := gee.New() + r.Use(gee.Logger()) // global midlleware + r.GET("/", func(c *gee.Context) { + c.HTML(http.StatusOK, "

Hello Gee

") + }) + + v2 := r.Group("/v2") + v2.Use(onlyForV2()) // v2 group middleware + { + v2.GET("/hello/:name", func(c *gee.Context) { + // expect /hello/geektutu + c.String(http.StatusOK, "hello %s, you're at %s\n", c.Param("name"), c.Path) + }) + } + + r.Run(":9999") +} +``` + +`gee.Logger()`即我们一开始就介绍的中间件,我们将这个中间件和框架代码放在了一起,作为框架默认提供的中间件。在这个例子中,我们将`gee.Logger()`应用在了全局,所有的路由都会应用该中间件。`onlyForV2()`是用来测试功能的,仅在`v2`对应的 Group 中应用了。 + +接下来使用 curl 测试,可以看到,v2 Group 2个中间件都生效了。 + +```bash +$ curl http://localhost:9999/ +>>> log +2019/08/17 01:37:38 [200] / in 3.14µs + +(2) global + group middleware +$ curl http://localhost:9999/v2/hello/geektutu +>>> log +2019/08/17 01:38:48 [200] /v2/hello/geektutu in 61.467µs for group v2 +2019/08/17 01:38:48 [200] /v2/hello/geektutu in 281µs +``` \ No newline at end of file diff --git a/doc/gee-day5/middleware.jpg b/doc/gee-day5/middleware.jpg new file mode 100644 index 0000000000000000000000000000000000000000..4933b1e1cbd6ba8ff1f51682c179e9fa63347b81 GIT binary patch literal 8309 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