为你自己学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语法简介
  • 1.1 为什么有http.Request但是没有http.Response?
  • 1.2 http.Request
  • 附录
  1. PART05.Context

5.01 Context-简介

Last updated 9 months ago

PART1. HTTP语法简介

先来看一个最基本的HTTP HandleFunc示例:

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/", helloHandler)
	http.ListenAndServe(":8080", nil)
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Hello, world!")
}

1.1 为什么有http.Request但是没有http.Response?

type ResponseWriter interface {
	Header() Header

	Write([]byte) (int, error)

	// WriteHeader 该方法用于写HTTP响应码
	WriteHeader(statusCode int)
}

1.2 http.Request

这些不用背,靠IDE的代码提示就行了.核心原则:API不需要背的.

1.2.1 http.Request.Body属性

package main

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

func main() {
	http.HandleFunc("/readBodyOnce", readBodyOnce)
	http.ListenAndServe(":8091", nil)
}

func readBodyOnce(w http.ResponseWriter, r *http.Request) {
	// 先读取一次body
	body, err := io.ReadAll(r.Body)
	if err != nil {
		fmt.Fprintf(w, "read body failed: %v", err)
		return
	}
	fmt.Fprintf(w, "read body: %s\n", string(body))

	// 再读取一次body
	body, err = io.ReadAll(r.Body)
	if err != nil {
		fmt.Fprintf(w, "read body one more time got error: %v", err)
		return
	}
	fmt.Fprintf(w, "read body one more time: [%s] and read body length %d \n", string(body), len(body))
}

注:此处老师上课说GET方法没有http.Request.Body,但实际上通过POSTMAN测试可以读取到,具体原因见附录.

通过这个demo我们可以看到:http.Request.Body是只能读取1次的.

该接口组合了io.Reader接口和io.Closer接口.

// ReadCloser is the interface that groups the basic Read and Close methods.
type ReadCloser interface {
	Reader
	// Closer 表示一个可关闭的资源
	Closer
}

这里多提一句,实际上完成读取的是http.body.src.Read()方法,而http.body.src真正的类型是io.LimitedReader,而io.LimitedReader.R的真正类型是bufio.Reader,这也就是为什么http.Request.Body只能被读取1次的原因.因为bufio.Reader的缓冲区中的数据一旦被读取,它就不会再存储在缓冲区中了,这意味着数据被消费了.

这种设计比较像JAVA中的InputStream和OutputStream.意思就是,你可以从中源源不断地读取,但你不能重复读取.

1.2.2 http.Request.GetBody属性

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/getBodyIsNil", getBodyIsNil)
	http.ListenAndServe(":8091", nil)
}

func getBodyIsNil(w http.ResponseWriter, r *http.Request) {
	if r.GetBody == nil {
		fmt.Fprintf(w, "GetBody is nil\n")
	} else {
		fmt.Fprintf(w, "GetBody is not nil\n")
	}
}

因此在使用http.Request.GetBody之前,最好像demo中那样,先做一个判空,确保其在不为空的前提下再使用.

通常的使用就是将http.Request.Body中的内容读取出来之后,使用闭包写入到http.Request.GetBody中.

1.2.3 http.Request.URL.Query()方法

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/queryParams", queryParams)
	http.ListenAndServe(":8091", nil)
}

func queryParams(w http.ResponseWriter, r *http.Request) {
	values := r.URL.Query()
	fmt.Fprintf(w, "query is: %v\n", values)
}

注意url.Values的类型,其key为string,value为[]string,而非string.

再注意url.Values.Get()方法:

func (v Values) Get(key string) string {
	if v == nil {
		return ""
	}
	vs := v[key]
	if len(vs) == 0 {
		return ""
	}
	return vs[0]
}

注意方法中的return vs[0],只是拿了同名参数的第1个值.

还需要注意,所有的参数值都被解释为了string,所以还需要自己解析为其他类型.

1.2.4 http.Request.Header属性

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/header", header)
	http.ListenAndServe(":8091", nil)
}

func header(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "header is: %v\n", r.Header)
}

Header大体上分为2类,一类是HTTP预定义的,另一类是自己定义的.

GO会自动将Header的名字转换为标准名字,即大小写调整.

标准名字:每一个单词的首字母大写,单词与单词之间用-连接.

通常用X开头来表明一个请求头字段是自定义的,例如X-My-Company-Your=header

所以实际上url.Values和http.Request.Header是可以互相转换的,只不过实操上很少这么干而已.

1.2.4 http.Request.Form属性

package main

import (
	"fmt"
	"net/http"
)

func main() {
	http.HandleFunc("/form", form)
	http.ListenAndServe(":8091", nil)
}

func form(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Before ParseForm: %v\n", r.Form)
	err := r.ParseForm()
	if err != nil {
		fmt.Fprintf(w, "ParseForm err: %v\n", err)
		return
	}
	fmt.Fprintf(w, "After ParseForm: %v\n", r.Form)
}

从demo中可以看到,使用http.Request.Form之前,需要先调用http.Request.ParseForm()方法.否则http.Request.Form值为nil.

另外,如果是表单提交,记得指定请求头中的Content-Type为application/x-www-form-urlencoded.

附录

上文demo中的,是一个接口.真正在运行时,这个w参数的类型是,很明显这是一个私有的结构体,我们从外边是拿不到的.

接口实际上只有3个方法,看起来还是比较简单的:

复杂的是,其方法如下两图示:

http.Request的API-1
http.Request的API-2
读取2次Request.Body

的类型为:

在运行时,这个http.Request.Body的类型是(这个类型也是io.Reader接口的实现).真正实现读取是http.body.Read()方法.

http.Request.GetBody.png

属性:原则上可以多次读取,但是在原生的http.Request中,其值恒为nil.所以有一些web框架在收到请求之后,第一件事就是给http.Request.GetBody赋值.注意其类型为func() (io.ReadCloser, error).

http.Request.URL.Query

注意,的返回值类型为.

http.Request.Header

由函数完成将请求头中的字段名转换为标准名称.

注意的类型,可以看到和url.Values的类型其实是相同的,都是map[string][]string.

http.Request.Form-表单参数
http.Request.Form-请求头

的类型同样为.

http.ResponseWriter
http.response
http.ResponseWriter
http.Request
http.Request.Body
io.ReadCloser接口
http.body
http.Request.GetBody
http.Request.URL.Query()方法
url.Values
CanonicalHeaderKey()
http.Request.Header
http.Request.Form
url.Values
GET请求发送Body
Context处理输入输出