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 0000000..fc74774 Binary files /dev/null and b/doc/gee-day4/group.jpg differ diff --git a/doc/gee-day5.md b/doc/gee-day5.md new file mode 100644 index 0000000..9ba1147 --- /dev/null +++ b/doc/gee-day5.md @@ -0,0 +1,225 @@ +--- +title: Go语言动手写Web框架 - Gee第四天 中间件Middleware +date: 2019-09-01 20:10:10 +description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,以 Gin 为原型从零设计一个Web框架。本文介绍了如何为Web框架添加中间件的功能(middlewares)。 +tags: +- Go +categories: +- 从零实现 +keywords: +- Go语言 +- 从零实现Web框架 +- 动手写Web框架 +- Middlewares +image: post/gee-day5/middleware.jpg +github: https://github.com/geektutu/7days-golang +--- + +本文是 [7天用Go从零实现Web框架Gee教程系列](https://geektutu.com/post/gee.html)的第五篇。 + +- 设计并实现 Web 框架的中间件(Middlewares)机制。 +- 实现通用的`Logger`中间件,能够记录请求到响应所花费的时间,**代码约50行** + +## 中间件是什么 + +中间件(middlewares),简单说,就是非业务的技术类组件。Web 框架本身不可能去理解所有的业务,因而不可能实现所有的功能。因此,框架需要有一个插口,允许用户自己定义功能,嵌入到框架中,仿佛这个功能是框架原生支持的一样。因此,对中间件而言,需要考虑2个比较关键的点: + +- 插入点在哪?使用框架的人并不关心底层逻辑的具体实现,如果插入点太底层,中间件逻辑就会非常复杂。如果插入点离用户太近,那和用户直接定义一组函数,每次在 Handler 中手工调用没有多大的优势了。 +- 中间件的输入是什么?中间件的输入,决定了扩展能力。暴露的参数太少,用户发挥空间有限。 + +那对于一个 Web 框架而言,中间件应该设计成什么样呢?接下来的实现,基本参考了 Gin 框架。 + +## 中间件设计 + +Gee 的中间件的定义与路由映射的 Handler 一致,处理的输入是`Context`对象。插入点是框架接收到请求初始化`Context`对象后,允许用户使用自己定义的中间件做一些额外的处理,例如记录日志等,以及对`Context`进行二次加工。另外通过调用`(*Context).Next()`函数,中间件可等待用户自己定义的 `Handler`处理结束后,再做一些额外的操作,例如计算本次处理所用时间等。即 Gee 的中间件支持用户在请求被处理的前后,做一些额外的操作。举个例子,我们希望最终能够支持如下定义的中间件,`c.Next()`表示等待执行其他的中间件或用户的`Handler`: + +****[day4-group/gee/logger.go](https://github.com/geektutu/7days-golang/tree/master/day5-middleware/gee)**** + +```go +func Logger() HandlerFunc { + return func(c *Context) { + // Start timer + t := time.Now() + // Process request + c.Next() + // Calculate resolution time + log.Printf("[%d] %s in %v", c.StatusCode, c.Req.RequestURI, time.Since(t)) + } +} +``` + +另外,支持设置多个中间件,依次进行调用。 + +我们上一篇文章[分组控制 Group Control](https://geektutu.com/post/gee-day4.html)中讲到,中间件是应用在`RouterGroup`上的,应用在最顶层的 Group,相当于作用于全局,所有的请求都会被中间件处理。那为什么不作用在每一条路由规则上呢?作用在某条路由规则,那还不如用户直接在 Handler 中调用直观。只作用在某条路由规则的功能通用性太差,不适合定义为中间件。 + +我们之前的框架设计是这样的,当接收到请求后,匹配路由,该请求的所有信息都保存在`Context`中。中间件也不例外,接收到请求后,应查找所有应作用于该路由的中间件,保存在`Context`中,依次进行调用。为什么依次调用后,还需要在`Context`中保存呢?因为在设计中,中间件不仅作用在处理流程前,也可以作用在处理流程后,即在用户定义的 Handler 处理完毕后,还可以执行剩下的操作。 + +为此,我们给`Context`添加了2个参数,定义了`Next`方法: + +**[day4-group/gee/context.go](https://github.com/geektutu/7days-golang/tree/master/day5-middleware/gee)** + +```go +type Context struct { + // origin objects + Writer http.ResponseWriter + Req *http.Request + // request info + Path string + Method string + Params map[string]string + // response info + StatusCode int + // middleware + handlers []HandlerFunc + index int +} + +func newContext(w http.ResponseWriter, req *http.Request) *Context { + return &Context{ + Path: req.URL.Path, + Method: req.Method, + Req: req, + Writer: w, + index: -1, + } +} + +func (c *Context) Next() { + c.index++ + s := len(c.handlers) + for ; c.index < s; c.index++ { + c.handlers[c.index](c) + } +} +``` + +`index`是记录当前执行到第几个中间件,当在中间件中调用`Next`方法时,控制权交给了下一个中间件,直到调用到最后一个中间件,然后再从后往前,调用每个中间件在`Next`方法之后定义的部分。如果我们将用户在映射路由时定义的`Handler`添加到`c.handlers`列表中,结果会怎么样呢?想必你已经猜到了。 + + +``` +func A(c *Context) { + part1 + c.Next() + part2 +} +func B(c *Context) { + part3 + c.Next() + part4 +} +``` + +假设我们应用了中间件 A 和 B,和路由映射的 Handler。`c.handlers`是这样的[A, B, Handler],`c.index`初始化为-1。调用`c.Next()`,接下来的流程是这样的: + +- c.index++,c.index 变为 0 +- 0 < 3,调用 c.handlers[0],即 A +- 执行 part1,调用 c.Next() +- c.index++,c.index 变为 1 +- 1 < 3,调用 c.handlers[1],即 B +- 执行 part3,调用 c.Next() +- c.index++,c.index 变为 2 +- 2 < 3,调用 c.handlers[2],即Handler +- Handler 调用完毕,返回到 B 中的 part4,执行 part4 +- part4 执行完毕,返回到 A 中的 part2,执行 part2 +- part2 执行完毕,结束。 + +一句话说清楚重点,最终的顺序是`part1 -> 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 0000000..4933b1e Binary files /dev/null and b/doc/gee-day5/middleware.jpg differ diff --git a/doc/gee.md b/doc/gee.md index 602315a..0a43c62 100644 --- a/doc/gee.md +++ b/doc/gee.md @@ -1,7 +1,7 @@ --- title: 7天用Go从零实现Web框架Gee教程 date: 2019-08-11 02:10:10 -description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,从零设计一个Web框架 +description: 7天用 Go语言 从零实现Web框架教程(7 days implement golang web framework from scratch tutorial),用 Go语言/golang 动手写Web框架,从零实现一个Web框架,以 Gin 为原型从零设计一个Web框架。 tags: - Go categories: @@ -65,7 +65,7 @@ func handler(w http.ResponseWriter, r *http.Request) { - [第一天:前置知识(http.Handler接口)](https://geektutu.com/post/gee-day1.html),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day1-http-base) - [第二天:上下文设计(Context)](https://geektutu.com/post/gee-day2.html),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day2-context) - [第三天:Tire树路由(Router)](https://geektutu.com/post/gee-day3.html),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day3-router) -- 第四天:分组控制(Group),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day4-group) -- 第五天:中间件(Middleware),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day5-middleware) +- [第四天:分组控制(Group)](https://geektutu.com/post/gee-day4.html),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day4-group) +- [第五天:中间件(Middleware)]((https://geektutu.com/post/gee-day5.html),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day5-middleware) - 第六天:HTML模板(Template),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day6-template) - 第七天:错误恢复(Panic Recover),[Code - Github](https://github.com/geektutu/7days-golang/tree/master/day7-panic-recover) \ No newline at end of file