8.02 课后复习-Context

PART1. 对Body的输入进行JSON反序列化

context.go:

package web

import (
	"encoding/json"
	"errors"
	"net/http"
)

// Context HandleFunc的上下文
type Context struct {
	// Req 请求
	Req *http.Request
	// Resp 响应
	Resp http.ResponseWriter
	// PathParams 路径参数名值对
	PathParams map[string]string
}

// BindJSON 绑定请求体中的JSON数据到给定的目标对象上 这个目标对象可能是某个结构体的实例 也有可能是个map
func (c *Context) BindJSON(target any) error {
	if target == nil {
		return errors.New("web绑定错误: 给定的实例为空")
	}

	if c.Req.Body == nil {
		return errors.New("web绑定错误: 请求体为空")
	}

	decoder := json.NewDecoder(c.Req.Body)
	return decoder.Decode(target)
}

这里之所以不用json.Unmarshal(),是因为用这个API多一步将io.Reader接口转换为[]byte的过程:

context.go:

PART2. 处理表单输入

  1. 无论你后续准备使用http.Request.Form还是http.Request.PostForm,都得先调用http.ParseForm()方法解析表单数据,这俩字段才有值

  2. http.Request.FormValue()方法内部虽然会调用http.ParseForm()方法解析表单数据,但是这个方法不抛出error,所以最好事前手动调用一次

  3. 我在复习时,将检测给定的key是否存在也作为了一个判断错误的条件

context.go:

PART3. 处理查询参数

3.1 基本实现

context.go:

3.2 缓存查询参数

多次调用http.Request.URL.Query()会重复解析URL,因此需要将查询参数缓存起来

注意,这里缓存参数时,不要使用map[string][]string的数据类型,虽然它和url.Values是同一种类型,因为后边的代码写的会非常难受:

context.go:

就保持和API一致,还使用url.Values即可.原因有2个:

  1. 这里将queryValue设置成私有字段,意味着它的作用域不会离开web包,所以它和框架使用者的代码是隔离的,只能通过Context. QueryValue()方法来访问queryValue中的K-V(确切的说只能访问V)

  2. 后续通过Key查找Value时,url.Values类型是有API的(当然最终的版本并没有使用这个API),而如果使用map[string][]string,则必须自行判断:

    • key是否存在

    • key对应的value是否为空slice

context.go:

这里需要注意的是,这个缓存是不存在失效和不一致的问题的.因为一旦服务端接收到一个请求,那么这个请求的参数就是固定的了

3.3 无法区分key不存在的情况与key对应的参数值是空字符串的情况

注意url.Values.Get()方法的返回值类型为string,所以即使没有找到key对应的查询参数,它依然返回一个空字符串.所以其实到底用url.Values还是map[string][]string其实区别并不大

context.go:

PART4. 处理路径参数

路径参数在查找路由树时就已经存起来了,这里只需要取就行

context.go:

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

思路:定义一个结构体,该结构体用于将string类型的输入转换为不同的类型;以上所有方法不再直接返回string类型的值,而是返回一个该结构体idea实例.注意这里返回的是实例而非指针,因为这个结构体中的属性不该被修改,换言之该结构体是"只读"的.

5.1 定义ReqValue结构体

reqValue.go:

5.2 定义各种类型转换的方法

reqValue.go:

5.3 context的各个处理请求参数的方法中返回ReqValue

context.go:

5.4 测试用例

context_test.go:

Last updated