GO语言HTTP标准库
# 一、HTTP协议
# 1.1 初识
- http: 超文本传输协议(HyperText Transfer Protocol),它是基于TCP协议的应用层传输协议,简单来说就是客户端和服务端进行数据传输的一种规则。
- 无状态: HTTP协议本身不会对发送过的请求和相应的通信状态进行持久化处理。这样做的目的是为了保持HTTP协议的简单性,从而能够快速处理大量的事务, 提高效率,如果需要保存状态需要引用其他技术,如cookie。
- 无连接: 每次连接只处理一个请求,早期带宽和计算资源有限,这么做是为了追求传输速度快,后来通过Connection: Keep-Alive实现长连接。
# 1.2 HTTP Request
HTTP协议使用TCP协议进行传输,在应用层协议发起交互之前,首先是TCP的三次握手。完成TCP三次握手后,客户端会向Web服务器发出一个请求报文,客户端发送的HTTP请求到Web服务器时,请求消息主要包含四部分:请求行、请求头、空行、请求体
1
- HTTP请求消息格式
# 1.2.1 请求方法
- GET 请求获取Request-URI所标识的资源(资源放在uri)
GET /v1/562f25980001b1b106000338.jpg HTTP/1.1
Host www.hostname.com
User-Agent Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.106 Safari/537.36
Accept image/webp,image/*,*/*;q=0.8
Referer www.hostname.com
Accept-Encoding gzip, deflate, sdch
Accept-Language zh-CN,zh;q=0.8
1
2
3
4
5
6
7
2
3
4
5
6
7
- POST 向URI提交数据(例如提交表单或上传数据)资源在请求正文中
- HEAD
- PUT
- DELETE
- OPTIONS
- PATCH
1.4 URL
URI: uniform resource identifier,统一资源标识符,用来唯一的标识一个资源
URL: uniform resource locator,统一资源定位器,它是一种具体的URI,指明了如何locate这个资源
1
2
2
- URL举例: http://www.losinx.com:18080/news/tech/43253.html?id=432&name=f43s#pic
# 1.2.2 请求头信息(Headers)
Header | 解释 | 示例 |
---|---|---|
Accept | 指定客户端能够接收的内容类型 | Accept: text/plain, text/html |
Accept-Charset | 浏览器可以接受的字符编码集 | Accept-Charset: iso-8859-5 |
Accept-Encoding | 指定浏览器可以支持的web服务器返回内容压缩编码类型 | Accept-Encoding: compress, gzip |
Accept-Language | 浏览器可接受的语言 | Accept-Language: en,zh |
Authorization | HTTP授权的授权证书 | Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== |
Cache-Control | 指定请求和响应遵循的缓存机制 | Cache-Control: no-cache |
Connection | 表示是否需要持久连接(HTTP 1.1默认进行持久连接) | Connection: close |
Cookie | HTTP请求发送时,会把保存在该请求域名下的所有cookie值一起发送给web服务器 | Cookie: $Version=1; Skin=new; |
Content-Length | 请求的内容长度 | Content-Length: 348 |
Content-Type | 指定正文(body)的数据格式 | Content-Type: application/x-www-form-urlencoded |
User-Agent | 浏览器信息 | Mozilla/5.0 (Windows NT 6.1; Win64; x64) |
常见Content-Type
application/x-www-form-urlencoded
1) 浏览器的原生form表单,如果不设置 Content-Type 属性,则默认以 application/x-www-form-urlencoded 方式传输数据
2)正文例如:name=manu&message=this_is_great
multipart/form-data
1) 上传文件时使用multipart/form-data,支持多种文件格式
2) 正文例如: name="text"name="file"; filename="chrome.png"Content-Type: image/png... content of chrome.png
application/json
1) 正文例如:{"title":"test","sub":[1,2,3]}
text/xml
正文例如:<?xml version="1.0"?>
examples.getStateName
# 1.2.3 请求正文
GET请求没有请求正文
POST可以包含GET
POST /post?id=1234&page=1 HTTP/1.1 Content-Type: application/x-www-form-urlencoded name=manu&message=this_is_great
1
2
3
4
# 1.3 http response
# 1.3.1 常见状态码
code | phrase | 说明 |
---|---|---|
200 | Ok | 请求成功 |
400 | Bad Request | 客户端有语法错误,服务端不理解 |
401 | Unauthorized | 请求未经授权 |
403 | Forbidden | 服务端拒绝提供服务 |
404 | Not Found | 请求资源不存在 |
500 | Internal Server Error | 服务器发生不可预期的错误 |
503 | Server Unavailable | 服务器当前有问题,过段时间可能恢复 |
# 1.3.2 响应头
Header | 解释 | 示例 |
---|---|---|
Allow | 对某网络资源的有效的请求行为 | Allow: GET, HEAD |
Date | 原始服务器消息发出的时间 | Date: Tue, 15 Nov 2010 08:12:31 GMT |
Content-Encoding | 服务器支持的返回内容压缩编码类型 | Content-Encoding: gzip |
Content-Language | 响应体的语言 | Content-Language: en,zh |
Content-Length | 响应体的长度 | Content-Length: 348 |
Cache-Control | 指定请求和响应遵循的缓存机制 | Cache-Control: no-cache |
Content-Type | 返回内容的MIME类型 | Content-Type: text/html; charset=utf-8 |
# 1.3.3 响应正文
HTTP/1.1 200 OK -- 状态行
Date: Fri, 22 May 2009 06:07:21 GMT -- 消息爆头
Content-Type: text/html; charset=UTF-8
-- 空行
<html> -- 响应正文
<head></head>
<body>
<!--body goes here-->
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 二、GO语言HTTP标准库
# 2.1 http server and client演示
# 2.1.1 GET请求
- http server
import (
"net/http" // 导入go语言自带http包
"fmt"
)
// 固定函数签名
// 从http.Request 读出各种请求参数 ResponseWriter把内容写回到Pesponse
func HelloHandler(w http.ResponseWriter,r *http.Request) {
// 打印客户端信息
fmt.Printf("host %s\n",r.Host)
// 请求方法
fmt.Printf("method %s\n",r.Method)
// 请求头
fmt.Println("Header")
for k,v := range r.Header {
fmt.Printf("%s = %v\n",k,v)
}
fmt.Fprint(w,"Hello Boy") // 把返回的内容写入http.ResponseWriter
}
func main() {
// 请求路由
// HelloHandler 请求路由要执行的函数
http.HandleFunc("/",HelloHandler)
// 阻塞,会为每一个请求创建一个协成去处理
// nil 指的是使用默认的Handler
// 通过查看net/http的源码可以发现,Handler是一个接口,需要实现方法 ServeHTTP ,也就是说,只要传入任何实现了 ServerHTTP 接口的实例,所有的HTTP请求,就都交给了该实例处理了。
http.ListenAndServer(":18080",nil)
}
""" 客户端信息
服务端打印:
host 127.0.0.1:18080
method GET
Header
User-Agent = [Go-http-client/1.1]
Accept-Encoding = [gzip]
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
- http client(GET请求)
import (
"net/http"
"io"
"os"
)
func main(){
// resp 可以拿到相应相关信息
resp,err := http.Get("http://127.0.0.1:18080")
if err != nil {
panic(err)
} else {
// 一定要调用resp.Bode.Close(),因为每一个请求服务端都会创建一个协程去处理。
defer resp.Bode.Close()
// 打印出请求内容
// 请求协议
fmt.Printf("proto:%s\n",resp.Proto)
// 请求状态码
fmt.Printf("status:%s\n",resp.Status)
// 请求头信息
fmt.Println("请求头:Header")
for k,v := range resp.Header {
fmt.Printf("%s = %v\n",k,v)
}
// body 是一个io流,打印出body内容
io.Copy(os.Stdout,resp.Body)
}
}
""" 服务端信息
客户端打印:
proto:HTTP/1.1
status:200 OK
请求头:Header
Date = [Mon, 27 Dec 2021 09:17:05 GMT]
Content-Length = [6]
Content-Type = [text/plain; charset=utf-8]
Hi boy
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 2.1.2 POST请求
- http server
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func RootHandler(w http.ResponseWriter,r *http.Request) {
// 打印客户单信息
fmt.Printf("host %s\n",r.Host)
// 请求方法
fmt.Printf("method %s\n",r.Method)
// 请求头
fmt.Println("Header")
for k,v := range r.Header {
fmt.Printf("%s = %v\n",k,v)
}
// 打印出 客户单请求内容
io.Copy(os.Stdout,r.Body)
// 给客户端的响应信息
fmt.Fprint(w,"Hi boy")
}
func main() {
http.HandleFunc("/",RootHandler)
http.ListenAndServe(":18080",nil)
}
"""服务端打印
host 127.0.0.1:18080
method POST
Header
User-Agent = [Go-http-client/1.1]
Content-Length = [9]
Content-Type = [text/plain]
Accept-Encoding = [gzip]
Hi Server
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
- http client(POST请求)
import (
"net/http"
"io"
"os"
)
func main() {
reader := strings.NewReader("Hi Server")
// 需要三个参数,URL,Content-Type,提交内容(io.reader),在server端通过body取出
resp,err := http.Post("http://127.0.0.1:18080","text/plain",reader)
if err != nil {
fmt.Println(err)
}else {
defer resp.Body.Close()
fmt.Printf("proto:%s\n",resp.Proto)
// 请求状态码
fmt.Printf("status:%s\n",resp.Status)
// 请求头信息
fmt.Println("请求头:Header")
for k,v := range resp.Header {
fmt.Printf("%s = %v\n",k,v)
}
io.Copy(os.Stdout,resp.Body)
}
}
"""客户端打印
proto:HTTP/1.1
status:200 OK
请求头:Header
Date = [Mon, 27 Dec 2021 09:59:08 GMT]
Content-Length = [6]
Content-Type = [text/plain; charset=utf-8]
Hi boy
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 2.2 Form表单提交数据
- http client
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
)
func postForm() {
// PostForm 通过表单提交,url.Values 传递提交参数
resp,err := http.PostForm("http://127.0.0.1:18080",url.Values{"name":[]string{"tchua"},"age":[]string{"22"}})
if err != nil {
fmt.Println(err)
}else {
defer resp.Body.Close()
fmt.Printf("proto:%s\n",resp.Proto)
// 请求状态码
fmt.Printf("status:%s\n",resp.Status)
// 请求头信息
fmt.Println("请求头:Header")
for k,v := range resp.Header {
fmt.Printf("%s = %v\n",k,v)
}
io.Copy(os.Stdout,resp.Body)
}
}
func main() {
postForm()
}
"""客户端打印
服务端相应:
proto:HTTP/1.1
status:200 OK
请求头:Header
Date = [Tue, 28 Dec 2021 01:46:29 GMT]
Content-Length = [6]
Content-Type = [text/plain; charset=utf-8]
Hi boy
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
- http server
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func RootHandler(w http.ResponseWriter,r *http.Request) {
// 打印客户单信息
fmt.Printf("host %s\n",r.Host)
// 请求方法
fmt.Printf("method %s\n",r.Method)
// 请求头
fmt.Println("Header")
for k,v := range r.Header {
fmt.Printf("%s = %v\n",k,v)
}
// 打印出 客户单请求内容
io.Copy(os.Stdout,r.Body)
// 给客户端的响应信息
fmt.Fprint(w,"Hi boy")
}
func main() {
http.HandleFunc("/",RootHandler)
http.ListenAndServe(":18080",nil)
}
"""服务端打印
host 127.0.0.1:18080
method POST
Header
User-Agent = [Go-http-client/1.1]
Content-Length = [17]
// x-www-form-urlencoded 表示客户端通过表单提交
Content-Type = [application/x-www-form-urlencoded]
Accept-Encoding = [gzip]
// 客户端提交参数
age=22&name=tchua
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 2.3 自定义请求(do)
- http client
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
)
func complexRequest() {
reader := strings.NewReader("Hi Server")
// 通过http.NewRequest 构造请求方法
req,err := http.NewRequest("POST","http://127.0.0.1:18080",reader)
if err != nil {
fmt.Println(err)
} else {
// 配置客户端请求头添加内容
req.Header.Add("User-Agent","tchua-1110")
req.Header.Add("channel","qq")
// 配置客户端Cookie
req.AddCookie(&http.Cookie{
Name: "user",
Value: "tchua",
Path: "/",
Domain: "localhost",
})
// 配置client
client := &http.Client{
Timeout: 100*time.Millisecond,
}
// do 可以更加灵活的构造请求方法
if resp,err := client.Do(req);err != nil {
defer resp.Body.Close()
fmt.Printf("proto:%s\n",resp.Proto)
// 请求状态码
fmt.Printf("status:%s\n",resp.Status)
// 请求头信息
fmt.Println("请求头:Header")
for k,v := range resp.Header {
fmt.Printf("%s = %v\n",k,v)
}
io.Copy(os.Stdout,resp.Body)
}
}
}
func main() {
complexRequest()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
- http server
package main
import (
"fmt"
"io"
"net/http"
"os"
)
func RootHandler(w http.ResponseWriter,r *http.Request) {
// 打印客户单信息
fmt.Printf("host %s\n",r.Host)
// 请求方法
fmt.Printf("method %s\n",r.Method)
// 请求头
fmt.Println("Header")
for k,v := range r.Header {
fmt.Printf("%s = %v\n",k,v)
}
// Cookie
for _,cookie := range r.Cookies(){
fmt.Printf("Name=%s\n",cookie.Name)
fmt.Printf("Value=%s\n",cookie.Value)
fmt.Printf("Path=%s\n",cookie.Path)
fmt.Printf("Domain=%s\n",cookie.Domain)
}
io.Copy(os.Stdout,r.Body)
fmt.Fprint(w,"Hi boy")
}
func main() {
http.HandleFunc("/",RootHandler)
http.ListenAndServe(":18080",nil)
}
"""
host 127.0.0.1:18080
method POST
Header
User-Agent = [tchua-1110]
Content-Length = [9]
Channel = [qq]
Cookie = [user=tchua]
Accept-Encoding = [gzip]
Name=user
Value=tchua
Path=
Domain=
Hi Server
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# 三、HTTP ROUTER
# 3.1 介绍
httprouter 是一个高性能、可扩展的HTTP路由
1) 下载
go get -u github.com/julienschmidt/httprouter
2) Router 实现了http.Handler接口
`net/http` 启动需要指定Handler,
3) 为各种request method提供了更便捷的路由方式
4) 支持restful请求方式
支持参数类型URl(/user/:name),也支持通配符URL(/user/*addr)
5) 支持ServerFiles访问静态文件
6) 可以之定义捕获panic的方法
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 3.2 http server and client演示
# 3.2.1 GET POST 请求
- server
package main
import (
"fmt"
"io"
"net/http"
"os"
"github.com/julienschmidt/httprouter"
)
// 公共的方法 打印客户端请求方法,请求内容,响应内容
func handle(method string, w http.ResponseWriter, r *http.Request, params httprouter.Params) {
fmt.Printf("method %s\n", r.Method)
io.Copy(os.Stdout, r.Body)
fmt.Println()
// 写入响应内容
w.Write([]byte("Hi boy,you request" + method))
}
// get请求 httprouter固定函数签名
func get(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
handle("get", w, r, params)
}
// post请求 httprouter固定函数签名
func post(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
handle("post", w, r, params)
}
func main() {
// 初始化路由Router
router := httprouter.New()
router.GET("/", get)
router.POST("/", post)
// restful 方式请求 支持参数也通配符
// 客户单请求地址: http://10.1.1.70:18080/user/tchua/vip/sh/xuhui
// name=tchua,type=vip,addr=sh/xuhui
// ByName 从请求参数值获取对应的值
router.POST("/user/:name/:type/*addr", func(rw http.ResponseWriter, r *http.Request, p httprouter.Params) {
fmt.Printf("name=%s,type=%s,addr=%s\n", p.ByName("name"), p.ByName("type"), p.ByName("addr"))
})
http.ListenAndServe("0.0.0.0:18080", router)
}
"""
method GET
name=tchua,type=,addr=/sh/xuhui
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
- client
package main
import (
"fmt"
"io"
"net/http"
"net/url"
"os"
"strings"
"time"
)
func simpleGet(){
if resp,err := http.Get("http://10.1.1.70:18080");err != nil {
fmt.Println(err)
}else {
defer resp.Body.Close()
// 打印出请求内容
// 请求协议
fmt.Printf("proto:%s\n",resp.Proto)
// 请求状态码
fmt.Printf("status:%s\n",resp.Status)
// 请求头信息
fmt.Println("请求头:Header")
for k,v := range resp.Header {
fmt.Printf("%s = %v\n",k,v)
}
io.Copy(os.Stdout,resp.Body)
fmt.Println()
}
}
func resuful() {
//
if resp,err := http.Post("http://10.1.1.70:18080/user/tchua/vip/sh/xuhui","text/plain",nil);err != nil {
panic(err)
} else {
defer resp.Body.Close()
fmt.Printf("proto:%s\n",resp.Proto)
// 请求状态码
fmt.Printf("status:%s\n",resp.Status)
// 请求头信息
fmt.Println("请求头:Header")
for k,v := range resp.Header {
fmt.Printf("%s = %v\n",k,v)
}
io.Copy(os.Stdout,resp.Body)
}
}
// 函数调用
func main() {
simpleGet()
resuful()
}
"""
proto:HTTP/1.1
status:200 OK
请求头:Header
Date = [Tue, 28 Dec 2021 03:45:38 GMT]
Content-Length = [21]
Content-Type = [text/plain; charset=utf-8]
Hi boy,you requestget
proto:HTTP/1.1
status:200 OK
请求头:Header
Content-Length = [0]
Date = [Tue, 28 Dec 2021 03:45:38 GMT]
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# 3.2.2 捕获panic
package main
import (
"fmt"
"io"
"net/http"
"os"
"github.com/julienschmidt/httprouter"
)
func handle(method string, w http.ResponseWriter, r *http.Request, params httprouter.Params) {
fmt.Printf("method %s\n", r.Method)
io.Copy(os.Stdout, r.Body)
fmt.Println()
// 写入响应内容
w.Write([]byte("Hi boy,you request" + method))
}
func get(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
handle("get", w, r, params)
}
func post(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
handle("post", w, r, params)
}
// 手动触发异常函数
func pac(w http.ResponseWriter,r *http.Request,params httprouter.Params) {
var arr []int
_ = arr[2]
}
func main() {
router := httprouter.New()
router.GET("/", get)
router.PanicHandler = func(w http.ResponseWriter, r *http.Request, err interface{}) {
w.WriteHeader(http.StatusInternalServerError)
// 把错误的详细信息写入到响应的header里面
fmt.Fprintf(w,"err:%v",err)
}
// 自定义错误路由,当有错误发生时,会自动路由到上面的PanicHandler
router.GET("/pac",pac)
http.ListenAndServe("0.0.0.0:18080", router)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
浏览器访问时,如果发生异常则会走PanicHandler,把错误信息写到响应header中。
# 四、 请求参数校验(validator)
go get github.com/go-playground/validator
// 示例
type RegistRequest struct {
UserName string `validate:"gt=0"` // >0 长度大于0
PassWord string `validate:"min=6,max=12"` //密码长度[6, 12]
PassRepeat string `validate:"eqfield=PassWord"` //跨字段相等校验
Email string `validate:"email"` //需要满足email的格式
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# 4.1 常见约束
- 范围约束
1) 对于字符串、切片、数组和map,约束其长度。len=10,min=6,max=10,gt=10,oneof=yes no
2) 对于数值,约束其取值,min,max,eq,ne,gt,gte,lt,lte,oneof=6 8
1
2
2
- 跨字段约束
1) 跨字段就在范围约束基础上加field后缀
2) 如果还跨结构体(cross struct)就在跨字段的基础上在field前加cs
1
2
2
- 字符串约束
1) contains包含子串
2) containsany包含任意unicode字符,containsany=abcd(字符串至少包含abcd中的一个字母)
3) containsrun包含run字符
4) excludes不包含子串
5) excludesall 不包含任意的unicode字符,excludesall=abcd
6) excludesrune不包含rune字符
7) startswith以子串为前缀
8) endswitch以子串为后缀
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 唯一性约束
1) 对于数组和切片,约束没有重复的元素
2) 对于map,约束没有重复的value
3) 对于元素类型为结构体的切片,unique约束结构体对象的某个字段不重复,通过unqiue=field指定这个字段名
1
2
3
4
2
3
4
- 自定义约束
// 固定函数签名
func validateEmail(fl validator.FieldLevel) bool {
input := fl.Field().String()
if pass, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,})\.([a-z]{2,4})$`, input); pass {
return true
}
return false
}
//注册一个自定义的validator
val.RegisterValidation("my_email", validateEmail)
Email string `validate:"my_email"`
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4.2 检验示例
- 普通字段校验
package main
import (
"fmt"
"github.com/go-playground/validator/v10"
)
// 定义全局对象
var val = validator.New()
// 带校验的结构体
// 请求参数必须满足对应的参数校验才可通过
type RegestRequest struct {
UserName string `validate:"gt=6"`
Passwd string `validate:"min=6,max=12"`
PassRepeat string `validate:"eqfield=Passwd"` // 跨字段
Email string `validate:"email"`
}
func main() {
// 初始化 故意不按照参数约束初始化
req := RegestRequest{
UserName: "tch",
Passwd: "12345",
PassRepeat: "123",
Email: "1",
}
// 校验
if err := val.Struct(req);err != nil {
fmt.Println(err)
}
}
""" 运行后打印
Key: 'RegestRequest.UserName' Error:Field validation for 'UserName' failed on the 'gt' tag
Key: 'RegestRequest.Passwd' Error:Field validation for 'Passwd' failed on the 'min' tag
Key: 'RegestRequest.PassRepeat' Error:Field validation for 'PassRepeat' failed on the 'eqfield' tag
Key: 'RegestRequest.Email' Error:Field validation for 'Email' failed on the 'email' tag
"""
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
- 跨结构体校验
package main
import (
"fmt"
"github.com/go-playground/validator/v10"
)
// 定义全局对象
var val = validator.New()
// 待校验的结构体
type InnerRequest struct {
Passwd string `validate:"gte=6"`
Email string `validate:"email"`
}
// eqcsfield 跨结构体字段校验
type OutterRequest struct {
UserName string `validate:"gte=6"`
PassRepeat string `validate:"eqcsfield=Nest.Passwd"`
// 跨结构体校验需要 先把结构体引用进来
Nest InnerRequest
}
func main() {
// 初始化
inreq := InnerRequest{
Passwd: "1234567",
Email: "1@qq.com",
}
outreq := OutterRequest{
UserName: "tchuatchua",
PassRepeat: "1234567",
Nest: inreq,
}
// 校验
if err := val.Struct(outreq);err != nil {
fmt.Println(err)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# 4.3 自定义校验
package main
import (
"fmt"
"github.com/go-playground/validator/v10"
"regexp"
)
// 定义全局对象
var val = validator.New()
// 自定义校验规则函数
func validateEmail(fl validator.FieldLevel) bool {
input := fl.Field().String()
if pass, _ := regexp.MatchString(`^([\w\.\_]{2,10})@(\w{1,})\.([a-z]{2,4})$`, input); pass {
return true
}
return false
}
// 待校验结构体
type InnerRequest struct {
Passwd string `validate:"gte=6"`
Email string `validate:"my_email"`
}
type OutterRequest struct {
UserName string `validate:"gte=6"`
PassRepeat string `validate:"eqcsfield=Nest.Passwd"`
// 跨结构体校验需要 先把结构体引用进来
Nest InnerRequest
}
func main() {
// 把自定义规则函数注册
val.RegisterValidation("my_email",validateEmail)
// 初始化
inreq := InnerRequest{
Passwd: "1234567",
Email: "tchua@qq.com",
}
outreq := OutterRequest{
UserName: "tchuatchua",
PassRepeat: "1234567",
Nest: inreq,
}
// 校验
if err := val.Struct(outreq);err != nil {
fmt.Println(err)
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# 五、 中间件
# 5.1 中间件作用
1) 将业务代码和非业务代码解耦
2) 非业务代码:限流、超时控制、打日志等等
1
2
2
# 5.2 中间件原理
传入一个http.Handler,外面套上一些非业务功能代码,再返回一个http.Handler。支持中间件层层嵌套。通过HandlerFunc把一个func(rw http.ResponseWriter, r *http.Request)函数转为Handler
func timeMiddleWare(next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
begin := time.Now()
next.ServeHTTP(rw, r)
timeElapsed := time.Since(begin)
log.Printf("request %s use %d ms\n", r.URL.Path, timeElapsed.Milliseconds())
})
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# 5.3 中间件示例
# 5.3.1 请求耗时
package main
import (
"log"
"net/http"
"time"
)
// 中间件 函数
// http.Handler 是一个接口 包含ServeHTTP方法
// http.HandlerFunc 实现了ServeHTTP方法
func timeMiddleWare(inner http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter,r *http.Request) {
begin := time.Now()
inner.ServeHTTP(rw,r)
userTime := time.Since(begin)
log.Printf("use time %d ms\n",userTime.Milliseconds())
})
}
func getBoy(w http.ResponseWriter,r *http.Request) {
w.Write([]byte("Hi Boy"))
}
func main() {
// HandlerFunc 把业务函数转换为Handler
// https://www.jianshu.com/p/3b5c4fc0695c
http.Handle("/",timeMiddleWare(http.HandlerFunc(getBoy)))
http.ListenAndServe("0.0.0.0:18081",nil)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 5.3.2 限流
- server
package main
import (
"log"
"net/http"
"time"
)
// 定义一个容量大小为100的管道
// struct 空结构体不占用内存
var limitChan = make(chan struct{},100)
// 耗时中间件 函数
// http.Handler 是一个接口 包含ServeHTTP方法
// http.HandlerFunc 实现了ServeHTTP方法
func timeMiddleWare(inner http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter,r *http.Request) {
begin := time.Now()
inner.ServeHTTP(rw,r)
_ = time.Since(begin)
//log.Printf("use time %d ms\n",userTime.Milliseconds())
})
}
// 限流中间件
func limitMiddleWare(inner http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 当管道容量达到100,代码在此阻塞
limitChan <- struct{}{}
log.Printf("当前qps %d\n",len(limitChan))
inner.ServeHTTP(w,r)
// 当请求结束时,释放取出管道数据
<- limitChan
})
}
func getBoy(w http.ResponseWriter,r *http.Request) {
w.Write([]byte("Hi Boy"))
}
func main() {
// HandlerFunc 把业务函数转换为Handler
// https://www.jianshu.com/p/3b5c4fc0695c
http.Handle("/",limitMiddleWare(timeMiddleWare(http.HandlerFunc(getBoy))))
http.ListenAndServe("0.0.0.0:18081",nil)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
- 并发模拟
package main
import (
"fmt"
"net/http"
"sync"
)
// 并发请求示例
func qpsMax() {
const P = 120
wg := sync.WaitGroup{}
wg.Add(P)
for i := 0;i < P; i++ {
go func() {
defer wg.Done()
if resp,err := http.Get("http://10.1.1.70:18081");err == nil {
resp.Body.Close()
}
}()
}
wg.Wait()
}
func main() {
qpsMax()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# 六、如何打造自己WEB框架
# 6.1 前言
框架作用: 节省封装时间,统一个团队的编码风格,节省沟通和排查问题的时间
Web框架具备的功能:
1) request参数获取
2) 参数校验(validator)
3) 路由(httprouter)
4) response生成和渲染
5) 中间件
6) 会话管理
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
- 参考
Gorilla工具集:
mux: 一款强大的HTTP路由和URL匹配器
websocket: 一个快速且被广泛应用的WebSocket实现
sessions: 支持将会话跟踪信息保存到Cookie或者文件系统
handler: 为http服务提供很多有用的中间件
schema: 表单数据和go struct互相转换
csrf: 提供防止跨站点请求攻击的中间件
1
2
3
4
5
6
7
2
3
4
5
6
7
编辑 (opens new window)
上次更新: 2023/07/03, 15:10:18