8.03 课后复习-Middleware-AccessLog

对生成器模式不太了解的读者建议先翻看一下生成器模式arrow-up-right

PART1. 使用生成器模式创建Middleware

middlewares/accessLog/middlewareBuilder.go:

package accessLog

import "web"

// AccessMiddlewareBuilder 日志中间件构建器
type AccessMiddlewareBuilder struct{}

// Build 本方法用于构建一个日志中间件
func (b *AccessMiddlewareBuilder) Build() web.Middleware {
	return func(next web.HandleFunc) web.HandleFunc {
		return func(ctx *web.Context) {
			next(ctx)
			// 在这里记录日志 例如:命中的路由/HTTP动词/请求参数等
		}
	}
}

Tips:相比于示例,这里只是没有定义IBuilder接口,本质上还是为了调用不同的Build()函数能够得到不同"特征"(或者也可以说是不同功能)的中间件.我个人觉得这里定义接口,意义不大.原因:这个场景下没有示例中的Director类.换言之,这个场景下没有哪个类负责"编排中间件的构建过程",因为每个中间件在实例化时所需的字段或函数是不同的,没有办法让某个类去针对所有的中间件统一完成这个过程

PART2. 定义中间件结构

记录如下内容:

  • 请求主机地址

  • 命中的路由

  • 请求的HTTP动词

  • 请求的uri(也就是路径)

middlewares/accessLog/accessLog.go:

PART3. 获取要记录的字段值

3.1 基本实现

middlewares/accessLog/middlewareBuilder.go:

这里把记录的日志放在defer中的原因:

  1. 直接写在next(ctx)之后不合适,因为next(ctx)意味着执行责任链上的后续中间件.在后续中间件的执行过程中有可能出现panic,那样的话整个进程就结束了,因此写在next(ctx)之后的代码也就无法被执行

  2. 写在next(ctx)之前,也不太合适.2个原因:

    • 如果请求没有命中任何路由,有可能就不需要记录日志了,写在next(ctx)之前就意味着无论是否命中路由都记录日志

    • 如果在记录日志的过程中出现panic,就会因为这些非关键流程影响到关键流程

因此写在defer中最合适

3.2 记录命中的路由

这里的问题在于,不能直接从命中的节点上取路由,因为命中节点上的路由只是全路由中的最后一段,而非全路由.因此这里修改的思路是:

  • 新增路由时,在对应的节点(即HandleFunc所在的节点)上记录全路由

  • 匹配路由时,查找到对应节点后,将节点的全路由记录到Context上

step1. Context结构体增加用于记录命中的路由的字段

context.go:

step2. Node结构体中增加用于记录命中该节点时的全路由的字段

node.go:

step3. 添加节点时记录全路由

在原先基础上,创建节点并设置HandleFunc后,设置全路由即可

router.go:

step4. 查找到节点后将节点的全路由赋值给Context

httpServer.go:

step5. 中间件中从Context中取值即可

middlewares/accessLog/middlewareBuilder.go:

PART4. 写日志操作

4.1 定义记录日志的函数

这里不要直接写死(写成log.Print()之类的),因为框架的使用者不一定想要以这种方式记录日志.这里的关键点在于:把定义记录日志过程的能力,交给框架的使用者

middlewares/accessLog/middlewareBuilder.go:

4.2 调用记录日志的函数

middlewares/accessLog/middlewareBuilder.go:

PART5. 传递中间件

对函数选项模式不太了解的读者建议先翻看一下函数选项模式arrow-up-right

5.1 定义选项函数类型

option.go:

5.2 定义With()函数

httpServer.go:

5.3 实例化HttpServer时根据选项函数修改成员属性的值

httpServer.go:

Tips:相比于示例,这里也没有使用IOption接口,因为意义也不大.面向接口编程本质上是为了面向客户端代码时隐藏具体实现,这里我认为如果定义了IOption接口,事情反而更加复杂了,因为你还要定义这个接口的各种不同实现,例如MiddlewareOption,PortOption(这里我们假定HTTPSserver还有一个名为port的字段)等

PART6. 测试

middleware_test.go:

Last updated