5.08 Context-处理输入之查询参数、路径参数和StringValue

PART1. 处理查询参数

本部分工程结构如下:

(base) yanglei@bogon 04-queryValue % tree ./
./
├── context.go
├── go.mod
├── go.sum
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
├── matchNode.go
├── node.go
├── router.go
├── router_test.go
└── serverInterface.go

0 directories, 11 files

查询参数:即就是指在URL问号之后的部分.

例如:http://localhost:8081/form?name=xiaoming&age=18.那么查询参数即为:

  • name=xiaoming

  • age=18

在之前处理表单处理的课程中我们可知,调用http.Request.ParseForm()方法,可以找到这部分参数值.

乍一看这个需求实现起来还是比较简单的:

1.1 问题1:多次调用是否会重复解析?

http.Request.URL.Query()方法调用了url.ParseQuery()函数,而url.ParseQuery()函数又调用了url.parseQuery()函数.实际上真正实现解析查询参数的正是这个url.parseQuery()函数.换言之,我们每一次调用http.Ruquest.URL.Query()方法,都要重新解析一次.因此我们要考虑把查询参数缓存起来.

注:

  1. 此处删除了和本节课无关的代码

  2. 这里的设计类似GIN框架,引入了一个查询参数缓存.这个缓存是不存在所谓的缓存失效或缓存不一致问题的,因为对于Web框架而言,请求收到之后,请求参数就是确切无疑不会再变的

1.2 问题2:无法区分给定的key到底是不存在,还是存在但值为空?

但是这个实现有一个问题:无法区分给定的key到底是不存在,还是存在但值为空

举例说明:

请求URL为127.0.0.1:8091/queryValue时,结果如下:

无查询参数

请求URL为127.0.0.1:8091/queryValue?name=时,结果如下:

有查询参数但值为空

再次提醒,url.Values类型的本质是map[string][]string.因此只需要对我们的Context.queryValues做一个判断取值是否成功的操作,即可判断出到底是key到底是不存在,还是存在但值为空.

这里可能会有人问:为什么访问这个map之前不需要判空?

这是因为在url.ParseQuery()函数中,已经对返回值进行了初始化,因此上述代码中拿到的c.queryValues必然不为空,就算没有任何查询参数,其值也是一个长度为0的map[string][]string.

如果再严格一些,可以再加一个对c.queryValues长度是否为0的判断,以便判断是否存在查询参数:

1.3 问题3:是否需要提供返回其他数据类型的API?

对于查询参数的读取,也面临着和读取表单参数一样的问题:是否需要提供返回其他数据类型的API?

例如:

同样问题:如果需要提供,那就还要提供很多其他数据类型的API.

这里的答案同样是不需要的.后续我们统一解决这个问题.

PART2. 处理路径参数

本章工程结构如下:

这里基本上和前面的表单、查询参数没太大的区别

注:

  1. 此处删除了和本节课无关的代码

2.1 问题1:是否需要提供返回其他数据类型的API?

对于路径参数的读取,也面临着和查询参数、读取表单参数一样的问题:是否需要提供返回其他数据类型的API?

例如:

PART3. 返回不同数据类型的输入

本章工程结构如下:

3.1 定义用于接收来自Context输入的类型

新建文件stringValue.go:

整体的思路就是:Context中处理各部分输入的方法,不再返回(string, err),而是返回一个StringValue类型的实例(注意不是指针,因为我们认为这个实例不需要被修改).后续所有类型转换的方法,均在StringValue类型上实现.换言之,StringValue类型的职责为:将各部分输入的值转换为对应的类型.

3.2 修改Context中处理各部分输入的方法的返回值

此处各方法不再返回(string, err),而是返回一个StringValue类型的实例.

3.2.1 Context.FormValue()方法

3.2.2 Context.QueryValue()方法

3.2.3 Context.PathValue()方法

3.3 新增将输入参数转换为对应类型表达的方法

3.3.1 StringValue.AsInt64()

3.3.2 StringValue.AsUint64()

3.3.3 StringValue.AsFloat64()

3.4 在handleFunc中使用

新建文件context_test.go:

尝试请求:

正确的请求参数
错误的请求参数

这样设计的好处在于这一行代码:ctx.PathValue("id").AsInt64()

这种链式调用,有2个优点:

  • 对于框架使用者而言,是非常方便的

  • 对于框架设计者而言,每个类的职责非常清晰,保持Context的简约

这种设计思路来源于GO原生的database/sql包中的sql.Row结构体.可以看到sql.Row.Scan()方法,思路和我们设计StringValue时的将输入参数转换为对应类型表达的方法是相同的.

同时,由于返回的都是结构体,因此可以保证在大多数的情况下,不会发生内存逃逸

Last updated