为你自己学Go
  • README
  • PART01.Web框架概览
    • 1.01 Web框架概览:学习路线
    • 1.02 Web框架概览-Beego框架分析
    • 1.03 Web框架概览-GIN框架分析
    • 1.04 Web框架概览-Iris框架分析
    • 1.05 Web框架概览-Echo框架分析
  • PART02.Server
    • 2.01 Server详解与面试要点
  • PART03.路由树
    • 3.01 路由树-Beego&GIN&Echo实现与设计总结
    • 3.02 路由树-全静态匹配
    • 3.03 路由树-TDD起步
    • 3.04 路由树-静态匹配测试用例
    • 3.05 路由树-静态匹配之路由查找
    • 3.06 路由树-静态匹配之集成Server
    • 3.07 路由树-通配符匹配之路由注册
    • 3.08 路由树-通配符匹配之路由查找与测试
    • 3.09 路由树-参数路径之基本注册和查找
    • 3.10 路由树-参数路径之校验
    • 3.11 路由树-参数路径之参数值
    • 3.12 路由树-总结与面试要点
  • PART04.课后复习
    • 4.01 课后复习-Server
    • 4.02 课后复习-Route
  • PART05.Context
    • 5.01 Context-简介
    • 5.02 Context-Beego Context设计分析
    • 5.03 Context-Gin Context设计分析
    • 5.04 Context-Echo和Iris的Context设计分析
    • 5.05 Context-处理输入输出总结
    • 5.06 Context-处理输入之Body输入
    • 5.07 Context-处理输入之表单输入
    • 5.08 Context-处理输入之查询参数、路径参数和StringValue
    • 5.09 Context-处理输出
    • 5.10 Context-总结与面试要点
  • PART06.AOP
    • 6.01 AOP简介与不同框架设计概览
    • 6.02 AOP设计方案-Middleware
  • PART07.Middleware
    • 7.01 Middleware-AccessLog
    • 7.02 Middleware-Trace简介和OpenTelemetry
    • 7.03 Middleware-OpenTelemetry测试
    • 7.04 Middleware-OpenTelemetry总结
    • 7.05 Prometheus详解
    • 7.06 Middleware-Prometheus
    • 7.07 Middleware-错误页面
    • 7.08 Middleware-从panic中恢复
    • 7.09 Middleware总结和面试
  • PART08.Review
    • 8.01 课后复习-AOP
    • 8.02 课后复习-Context
    • 8.03 课后复习-Middleware-AccessLog
  • PART09.Appendix
    • 附录1.责任链模式
    • 附录2.生成器模式
    • 附录3.函数选项模式
  • xiaochengxu
    • 01.原力去水印
    • 02.KeePass密码管理:安全轻松的管理您的密码
Powered by GitBook
On this page
  • PART1. http.Request.Form与http.Request.PostForm的区别
  • PART2. 实现Context.FormValue()方法
  • 2.1 完全自行实现
  • 2.2 依赖原生API的实现
  • PART3. 相关问题
  • 3.1 多次调用Context.FormValue()方法是否会造成重复解析?
  • 3.2 是否需要提供返回其他数据类型的API?
  1. PART05.Context

5.07 Context-处理输入之表单输入

本节课工程路径如下:

(base) yanglei@yuanhong 03-formValue % tree ./
./
├── context.go
├── handleFunc.go
├── httpServer.go
├── httpServer_test.go
├── matchNode.go
├── node.go
├── router.go
├── router_test.go
└── serverInterface.go

0 directories, 9 files

注:相比于02-bindJSON,有如下变更:

  1. 删除了演示用的config.go

  2. 删除了HTTPServer结构体中的useNumber和disallowUnknownFields字段(这两个字段也是上节课演示用的)

PART1. http.Request.Form与http.Request.PostForm的区别

  • http.Request.Form:可以接收URL中的参数以及HTTP动词为PATCH、POST、PUT时的表单数据

  • http.Request.PostForm:只能接收HTTP动词为PATCH、POST、PUT时的表单数据

  • 不管使用以上二者中的哪一种,都要先调用http.Request.ParseForm()解析表单数据后,这两个字段才有数据

示例:

package main

import (
	"fmt"
	"log"
	"net/http"
)

func main() {
	http.HandleFunc("/formAndPostForm", formAndPostFormHandle)
	log.Fatal(http.ListenAndServe(":8091", nil))
}

func formAndPostFormHandle(w http.ResponseWriter, r *http.Request) {
	err := r.ParseForm()
	if err != nil {
		log.Fatal("ParseForm failed: ", err)
	}

	fmt.Fprintf(w, "Form: %v\n", r.Form)
	fmt.Fprintf(w, "PostForm: %v\n", r.PostForm)
}

请求如下图示:

可以看到,URL中的参数为:

  • name=zhangsan

  • age=18

表单数据中的参数为:

  • name=lisi

  • gender=male

响应如下图示:

可以看到:

  • http.Request.Form既可以收到URL中的参数,也可以接收到表单中的参数

  • http.Request.PostForm只能接收到表单中的参数

二者最核心的区别在于:

  • http.Request.Form:基本上可以认为能够拿到所有的表单数据

  • http.Request.PostForm:只能拿到ContentType为x-www-form-urlencoded时的表单数据

不建议使用表单的方式进行通信,而是建议在Body中写入JSON或Protobuf的方式进行通信.因为在数据结构较为复杂的场景下,使用使用JSON或Protobuf比使用Form的可读性要更高.因此在可选择的前提下,尽可能采用Body中中写入JSON或Protobuf的方式进行通信.

PART2. 实现Context.FormValue()方法

2.1 完全自行实现

// FormValue1 获取表单中给定键的值
func (c *Context) FormValue1(key string) (value string, err error) {
	err = c.Req.ParseForm()
	if err != nil {
		return "", errors.New("web绑定错误: 解析表单失败: " + err.Error())
	}

	values, ok := c.Req.Form[key]
	if !ok {
		return "", errors.New("web绑定错误: 表单中不存在键: " + key)
	}

	// Tips: 这里只返回第一个值,这样的设计是参照了net/http包中的FormValue()方法
	return values[0], nil
}

这里不建议如下方式的实现:

// FormValue2 获取表单中给定键的值 不建议按此方式返回 因为大部分场景下表单中的键都是唯一的
func (c *Context) FormValue2(key string) (values []string, err error) {
	err = c.Req.ParseForm()
	if err != nil {
		return nil, errors.New("web绑定错误: 解析表单失败: " + err.Error())
	}

	values, ok := c.Req.Form[key]
	if !ok {
		return nil, errors.New("web绑定错误: 表单中不存在键: " + key)
	}

	return values, nil
}

不建议按此方式返回,因为大部分场景下表单中的键都是唯一的.还是上节课提到的观点:为了支持一个小众的需求,反而会影响到大部分主流用户的使用,那么就不要支持这个小众的需求.

2.2 依赖原生API的实现

// FormValue3 获取表单中给定键的值 推荐使用这种实现 因为这种实现的语义和原生API语义相同
func (c *Context) FormValue3(key string) (value string, err error) {
	err = c.Req.ParseForm()
	if err != nil {
		return "", errors.New("web绑定错误: 解析表单失败: " + err.Error())
	}

	return c.Req.FormValue(key), nil
}

推荐使用这种实现,因为这种实现的语义和原生API语义相同.

PART3. 相关问题

3.1 多次调用Context.FormValue()方法是否会造成重复解析?

答案是不会的.因为http.Request.ParseForm()方法的调用是幂等的.证据如下:

3.2 是否需要提供返回其他数据类型的API?

例如:根据给定的key,返回一个int64类型数据的方法.其实现如下:

// FormValueAsInt64 获取表单中给定键的值 并将该值转换为int64类型返回
func (c *Context) FormValueAsInt64(key string) (int64Value int64, err error) {
	err = c.Req.ParseForm()
	if err != nil {
		return 0, errors.New("web绑定错误: 解析表单失败: " + err.Error())
	}

	value := c.Req.FormValue(key)
	return strconv.ParseInt(value, 10, 64)
}

那么问题来了:要不要实现这种(注意是这种不是这个)API?

如果要提供的话,至少要提供3个:

  • 获取表单中给定键的值,并将该值转换为int64类型返回

  • 获取表单中给定键的值,并将该值转换为uint64类型返回

  • 获取表单中给定键的值,并将该值转换为float64类型返回

用户拿到这3种类型的数据,再自己向下转型(强制类型转换).

但其实不需要提供.这里因为其他处理输入的API也会遇到类似的问题,后边统一讲这个事情.

Last updated 9 months ago

注意,二者的返回值类型都是,实际上就是map[string][]string

注意,这里只返回同名参数中的第1个值,是参照了方法的返回.

甚至可以不要前边对于http.Request.ParseForm()方法返回值的错误判断部分.但不建议这么做.因为虽然方法会进行解析并返回解析错误,但它返回的错误被方法在调用时给丢弃掉了,没有处理.所以最好还是主动自己解析一下,以防万一.

这里有一种观点认为在GIN中,字段是没有必要设计的,因为原生的http.Request.Form和http.Request.PostForm字段本身就起到了缓存的效果

url.Values
http.Request.FormValue()
http.Request.ParseMultipartForm()
http.Request.FormValue()
http.Request.ParseForm()方法的注释说明
仅在http.Request.PostForm字段值不为nil时解析
仅在http.Request.Form字段值不为nil时解析
gin.Context.formCache
url参数与表单参数
url参数与表单参数的响应