http协议

  • http:超文本传输协议Hyper Text Transfer Protocol。
  • http属于应用层协议,它在传输层用的是tcp协议。
  • 无状态,对事务处理没有记忆能力(对比TCP协议里的确认号)。如果要保存状态需要引用其他技术,如cookie。
  • 无连接,每次连接只处理一个请求。早期带宽和计算资源有限,这么做是为了追求传输速度快,后来通过Connection: Keep-Alive实现长连接。http1.1废弃了Keep-Alive,默认支持长连接。

请求方法

请求方法 解释
GET 请求获取Request-URI所标识的资源
POST 向URI提交数据(例如提交表单或上传数据)
HEAD 类似于GET,返回的响应中没有具体的内容,用于获取报头
PUT 对服务器上已存在的资源进行更新
DELETE 请求服务器删除指定的页面
CONNECT HTTP/1.1预留,能够将连接改为管道方式的代理服务器
OPTIONS 查看服务端性能
TRACE 回显服务器收到的请求,主要用于测试或诊断
PATCH 同PUT,可只对资源的一部分更新,资源不存在时会创建

  GET、POST和HEAD是http1.0就有的,后面的请求方法是http1.1新增的。客户端发起一个请求时,这个请求可能要穿过防火墙、代理、网关或其他一些应用程序。每个中间节点都可能会修改原始的HTTP请求。TRACE 方法允许客户端在 最终将请求发送给服务器时,看看它变成了什么样子。TRACE请求会在目的服务器端发起一个环回诊断。行程最后一站的服务器会弹回一条TRACE响应,并在响应主体中携带它收到的原始请求报文。这样客户端就可以查看在所有中间HTTP应用程序组成的请求/响应链上,原始报文是否,以及如何被毁坏或修改过。CONNECT方法是HTTP/1.1协议预留的,能够将连接改为管道方式的代理服务器。通常用于SSL加密服务器的链接与非加密的HTTP代理服务器的通信。OPTIONS方法请求Web服务器告知其支持的各种功能。通过使用OPTIONS,客户端可以在与服务器进行交互之前,确定服务器的能力,这样它就可以更方便地与具备不同特性的代理和服务器进行互操作了。
  实际中server对各种request method的处理方式可能不是按协义标准来的,比如server收到PUT请求时偏偏执行DELETE操作,同理仅用一个GET方法也能实现增删改查的全部功能。大多数浏览器只支持GET和POST。

URL

  • URI:uniform resource identifier,统一资源标识符,用来唯一的标识一个资源。
  • URL: uniform resource locator,统一资源定位器,它是一种具体的URI,指明了如何locate这个资源。
  • URL举例:

协议版本

  现在广泛应用的协议版本是HTTP/1.1。

请求头

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
    • 浏览器的原生form表单,如果不设置 Content-Type 属性,则默认以 application/x-www-form-urlencoded 方式传输数据
    • 正文例如:name=manu&message=this_is_great
  • multipart/form-data
    • 上传文件时使用multipart/form-data,支持多种文件格式
    • 正文例如: name="text"name=“file”; filename="chrome.png"Content-Type: image/png… content of chrome.png
  • application/json
    • 正文例如:{“title”:“test”,“sub”:[1,2,3]}
  • text/xml
    • 正文例如: examples.getStateName

请求正文

  GET请求没有请求正文。POST即可以把一部分参数放在url里,也可以把一部分参数放在请求正文里,如下是一个完整的POST请求

1
2
3
4
POST /post?id=1234&page=1 HTTP/1.1
Content-Type: application/x-www-form-urlencoded

name=manu&message=this_is_great

GET和POST的区别:

  1. get的请求参数全部在url里,参数变时url就变;post可以把参数放到请求正文里,参数变时url不变。
  2. 虽然http协议并没有对url和请求正文做长度限制,但在实际中浏览器对url的长度限制比请求正文要小很多,所以post可以提交的数据比get要大得多。
  3. get比post更容易受到攻击(源于get的参数直接暴露在url里)。

http response

响应状态及话术

code phrase 说明
200 Ok 请求成功
400 Bad Request 客户端有语法错误,服务端不理解
401 Unauthorized 请求未经授权
403 Forbidden 服务端拒绝提供服务
404 Not Found 请求资源不存在
500 Internal Server Error 服务器发生不可预期的错误
503 Server Unavailable 服务器当前有问题,过段时间可能恢复

响应头

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

  响应正文可以是html、json、xml、普通文本,等等。
完整http response举例:

1
2
3
4
5
6
7
8
9
10
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>

https

HTTP + 加密 + 认证 + 完整性保护 = HTTPS(HTTP Secure)

go语言http标准库

http_server.go

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
package main

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

func HelloHandler(w http.ResponseWriter, r *http.Request) {
/**
具体看一下http协议
*/
fmt.Println("---------------------------------------------------")
fmt.Printf("request method: %s\n", r.Method)
fmt.Printf("request host: %s\n", r.Host)
fmt.Printf("request url: %s\n", r.URL)
fmt.Printf("request proto: %s\n", r.Proto)
for key, values := range r.Header {
fmt.Printf("request header %s: %v\n", key, values)
}
for _, cookie := range r.Cookies() {
fmt.Printf("request cookie: name=%s vaue=%s\n", cookie.Name, cookie.Value)
}
BodyData, _ := io.ReadAll(r.Body)
fmt.Printf("request body:: %v\n", BodyData)
//把返回的内容写入http.ResponseWriter
fmt.Fprint(w, "Hello Boy")
}

func main() {
//路由,请求要目录时去执行HelloHandler
http.HandleFunc("/", HelloHandler)
//ListenAndServe如果不发生error会一直阻塞。为每一个请求单独创建一个协程去处理
if err := http.ListenAndServe(":5656", nil); err != nil {
panic(err)
}
}

运行结果:

request method: POST
request host: 127.0.0.1:5656
request url: /
request proto: HTTP/1.1
request header Accept-Encoding: [gzip]
request header User-Agent: [Go-http-client/1.1]
request header Content-Length: [12]
request header Content-Type: [text/plain]
request body:: Hello Server

http_client.go

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
func simpleGet() {
if resp, err := http.Get("http://127.0.0.1:5656"); err != nil {
panic(err)
} else {
//注意:一定要调用resp.Body.Close(),否则会协程泄漏(同时引发内存泄漏)
defer resp.Body.Close()
/**
具体看一下http协议
*/
fmt.Printf("response proto: %s\n", resp.Proto)
fmt.Printf("response status: %s\n", resp.Status)
fmt.Println("response header")
for key, values := range resp.Header {
fmt.Printf("%s: %v\n", key, values)
}
// 响应体
b, err2 := io.ReadAll(resp.Body)
if err2 != nil {
log.Fatal(err2)
}
fmt.Printf("response body: %v\n", string(b))
}
}

func simplePost() {
//把string转成io.Reader
reader := strings.NewReader("Hello Server")
//Content-Type为text/plain,表示一个朴素的字符串
if resp, err := http.Post("http://127.0.0.1:5656", "text/plain", reader); err != nil {
panic(err)
} else {
//注意:一定要调用resp.Body.Close(),否则会协程泄漏(同时引发内存泄漏)
defer resp.Body.Close()
/**
具体看一下http协议
*/
fmt.Printf("response proto: %s\n", resp.Proto)
fmt.Printf("response status: %s\n", resp.Status)
fmt.Println("response header")
for key, values := range resp.Header {
fmt.Printf("%s: %v\n", key, values)
}
fmt.Print("response body: ")
//两个io数据流的拷贝
io.Copy(os.Stdout, resp.Body)
os.Stdout.WriteString("\n")
}
}

func main() {
simpleGet()
simplePost()
}

运行结果:

response proto: HTTP/1.1
response status: 200 OK
response header
Date: [Fri, 13 Jan 2023 14:21:03 GMT]
Content-Length: [9]
Content-Type: [text/plain; charset=utf-8]
response body: Hello Boy

路由插件

  • 安装 go get -u github.com/julienschmidt/httprouter
  • Router实现了http.Handler接口。
  • 为各种request method提供了便捷的路由方式。
  • 支持restful请求方式。
  • 支持ServeFiles访问静态文件。
  • 可以自定义捕获panic的方法。
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
package main

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

"github.com/julienschmidt/httprouter"
)

func get(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
fmt.Printf("request method: %s\n", r.Method)
w.Write([]byte("Hi boy, you request get"))
}

func post(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
fmt.Printf("request method: %s\n", r.Method)
fmt.Printf("request body: ")
b, err := io.ReadAll(r.Body)
if err != nil {
log.Fatal(err)
}
fmt.Printf("request body: %v\n", string(b))
w.Write([]byte("post success"))
}

func restfulPost(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"))
}

func panic(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
var arr []int
_ = arr[1] //数组越界panic
}

func main() {
router := httprouter.New()
//通过recover捕获panic
router.PanicHandler = func(w http.ResponseWriter, r *http.Request, err interface{}) {
//设置response status
w.WriteHeader(http.StatusInternalServerError)
log.Println(err)
fmt.Fprintf(w, "内部错误")
}
router.GET("/panic", panic)
router.GET("/", get)
router.POST("/post", post)
// *只能有一个,且必须放path的末尾。
router.POST("/user/:name/:type/*addr", restfulPost)
// 静态文件必须以/*filepath结尾,因为要获取我们要访问的路径信息
router.ServeFiles("/file/*filepath", http.Dir("./static"))

//Router实现了ServerHTTP接口,所以它是一种http.Handler
http.ListenAndServe(":5656", router)
}

结合gin路由

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
package main

import (
"net/http"
"time"

"github.com/gin-gonic/gin"
)

func test(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "message": "hello test"})
}

func main() {
// 关闭debug
// gin.SetMode(gin.ReleaseMode)
r := gin.Default()
r.GET("/test", test)
server := http.Server{
ReadHeaderTimeout: 60 * time.Second,
ReadTimeout: 60 * time.Second,
WriteTimeout: 60 * time.Second,
IdleTimeout: 60 * time.Second,
MaxHeaderBytes: 1 << 20, // 1M
Addr: "127.0.0.1:8080",
Handler: r,
}
if err := server.ListenAndServe(); err != nil {
panic(err)
}
}

请求校验

  首先安装go get github.com/go-playground/validator

范围约束

  • 对于字符串、切片、数组和map,约束其长度。len=10, min=6, max=10, gt=10。
  • 对于数值,约束其取值。min, max, eq, ne, gt, gte, lt, lte, oneof=6 8。

跨字段约束

  • 跨字段就在范围约束的基础上加field后缀。
  • 如果还跨结构体(cross struct)就在跨字段的基础上在field前面加cs:范围约束 cs field。

字符串约束

  • contains包含子串。
  • containsany包含任意unicode字符, containsany=abcd。
  • containsrune包含rune字符, containsrune= ☻。
  • excludes不包含子串。
  • excludesall不包含任意的unicode字符,excludesall=abcd。
  • excludesrune不包含rune字符,excludesrune=☻。
  • startswith以子串为前缀。
  • endswith以子串为后缀。

唯一性uniq

  • 对于数组和切片,约束没有重复的元素。
  • 对于map,约束没的重复的value。
  • 对于元素类型为结构体的切片,unique约束结构体对象的某个字段不重复,通过unqiue=field指定这个字段名。
    Friends []User validate:"unique=Name"
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
74
75
76
77
78
79
80
81
82
83
package main

import (
"fmt"
"regexp"

"github.com/go-playground/validator"
)

var val = validator.New()

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的格式
}

type InnerRequest struct {
Pass string `validate:"min=6,max=12"` //密码长度[6, 12]
Email string `validate:"email"`
}

type OutterRequest struct {
PassWord string `validate:"eqcsfield=Nest.Pass"` //跨结构体相等校验
PassRepeat string `validate:"eqfield=PassWord"` //跨字段相等校验
Nest InnerRequest
}

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
}

func processErr(err error) {
if err == nil {
return
}

//给Validate.Struct()函数传了一个非法的参数
invalid, ok := err.(*validator.InvalidValidationError)
if ok {
fmt.Println("param error:", invalid)
return
}

//ValidationErrors是一个错误切片,它保存了每个字段违反的每个约束信息
validationErrs := err.(validator.ValidationErrors)
for _, validationErr := range validationErrs {
fmt.Printf("field %s 不满足条件 %s\n", validationErr.Field(), validationErr.Tag())
}
}

func main() {
//注册一个自定义的validator
val.RegisterValidation("email", validateEmail)

req := RegistRequest{
UserName: "zcy",
PassWord: "12345",
PassRepeat: "1234568",
Email: "123qq.com",
}
//Struct()返回的error分为两种类型:InvalidValidationError和ValidationErrors
processErr(val.Struct(req))
processErr(val.Struct(3))
fmt.Println("==============")

inreq := InnerRequest{
Pass: "1234567",
Email: "123qq.com",
}
outreq := OutterRequest{
PassWord: "123456",
PassRepeat: "1234568",
Nest: inreq,
}
processErr(val.Struct(outreq))
}

运行结果:

field PassWord 不满足条件 min
field PassRepeat 不满足条件 eqfield
field Email 不满足条件 email
param error: validator: (nil int)
==============
field PassWord 不满足条件 eqcsfield
field PassRepeat 不满足条件 eqfield
field Email 不满足条件 email

http中间件

  中间件的作用:将业务代码和非业务代码解耦。非业务代码指限流、超时控制、打日志等等。
  中间件的实现原理:传入一个http.Handler,外面套上一些非业务功能代码,再返回一个http.Handler。支持中间件层层嵌套。通过HandlerFunc把一个func(rw http.ResponseWriter, r *http.Request)函数转为Handler。

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package main

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

// 最多并发处理100个请求
var limitCh = make(chan struct{}, 100)

func getBoy(w http.ResponseWriter, r *http.Request) {
time.Sleep(150 * time.Millisecond)
w.Write([]byte("hi boy"))
}

func getGirl(w http.ResponseWriter, r *http.Request) {
time.Sleep(150 * time.Millisecond)
w.Write([]byte("hi girl"))
}

func timeMiddleWare(next http.Handler) http.Handler {
//通过HandlerFunc把一个func(rw http.ResponseWriter, r *http.Request)函数转为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())
})
}

func limitMiddleWare(next http.Handler) http.Handler {
//通过HandlerFunc返回一个Handler
return http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
//并发度达到100时就会阻塞
limitCh <- struct{}{}
log.Printf("concurrence %d\n", len(limitCh))
next.ServeHTTP(rw, r)
<-limitCh
})
}

/**
以下演示更优雅的中间件组织形式
*/

type middleware func(http.Handler) http.Handler

type Router struct {
middlewareChain []middleware
mux map[string]http.Handler //mux通常表示路由策略
}

func NewRouter() *Router {
return &Router{
middlewareChain: make([]middleware, 0, 10),
mux: make(map[string]http.Handler, 10),
}
}

func (self *Router) Use(m middleware) {
self.middlewareChain = append(self.middlewareChain, m)
}

func (self *Router) Add(path string, handler http.Handler) {
var mergedHandler = handler
for i := len(self.middlewareChain) - 1; i >= 0; i-- {
mergedHandler = self.middlewareChain[i](mergedHandler) //中间件层层嵌套
}
self.mux[path] = mergedHandler
}

func main() {
// http.Handle("/", timeMiddleWare(limitMiddleWare(http.HandlerFunc(getBoy)))) //中间层层嵌套
// http.Handle("/home", timeMiddleWare(limitMiddleWare(http.HandlerFunc(getGirl)))) //跟上面一行存在重复代码

router := NewRouter()
router.Use(limitMiddleWare)
router.Use(timeMiddleWare)
//以下演示了2个路径(还可以更多),每个路径都使用相同的middlewareChain
router.Add("/", http.HandlerFunc(getBoy))
router.Add("/home", http.HandlerFunc(getGirl))
for path, handler := range router.mux {
http.Handle(path, handler)
}

if err := http.ListenAndServe(":5656", nil); err != nil {
fmt.Println(err)
}
}

GIN

  Gin是一款高性能的、简单轻巧的http Web框架。安装方式go get -u github.com/gin-gonic/gin。

路由

  Gin的路由是基于httprouter做的,支持GET、POST、PUT、PATCH、DELETE、OPTIONS、HEAD。支持路由分组,不用重复写上级路径。

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
package main

import (
"net/http"

"github.com/gin-gonic/gin"
)

func boy(c *gin.Context) { //你所需要的东西全都封装在了gin.Context里面,包括http.Request和ResponseWriter
c.String(http.StatusOK, "hi boy") //通过gin.Context.String返回一个text/plain类型的正文
}

func girl(c *gin.Context) {
c.String(http.StatusOK, "hi girl")
}

func main() {
//发布模式,默认是Debug模式
// gin.SetMode(gin.ReleaseMode)
//默认的engine已自带了Logger和Recovery两个中间件
engine := gin.Default()
engine.GET("/", boy)
engine.POST("/", girl)

//路由分组
oldVersion := engine.Group("/v1")
oldVersion.GET("/student", boy) //http://localhost:5656/v1/student
oldVersion.GET("/teacher", boy) //http://localhost:5656/v1/teacher

newVersion := engine.Group("/v2")
newVersion.GET("/student", girl) //http://localhost:5656/v2/student
newVersion.GET("/teacher", girl) //http://localhost:5656/v2/teacher

engine.Run(":5656")
}

参数获取

  • c.Query() 从GET请求的URL中获取参数。
  • c.Param()从Restful风格的url中获取参数。
  • c.PostForm() 从post表单中获取参数。
  • c.FormFile() 获取上传的文件,消息类型为form-data。
  • c. MultipartForm() multipart/form-data可以上传多个form-data 并且用分隔符进行分割。
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
package main

import (
"fmt"
"net/http"
"strconv"

"github.com/gin-gonic/gin"
)

func get(ctx *gin.Context) {
// 从GET请求的URL中获取参数
name := ctx.Query("name")
//如果没传addr参数,则默认为China
addr := ctx.DefaultQuery("addr", "China")
ctx.String(http.StatusOK, name+" live in "+addr)
}

// 从Restful风格的url中获取参数
func restfulunc(ctx *gin.Context) {
name := ctx.Param("name")
addr := ctx.Param("addr")
ctx.String(http.StatusOK, name+" live in "+addr)
}

func student(ctx *gin.Context) {
// 从post表单中获取参数
name := ctx.PostForm("name")
//如果没传addr参数,则默认为China
addr := ctx.DefaultPostForm("addr", "China")
ctx.String(http.StatusOK, name+" live in "+addr)
}

// 上传单个文件
func upload_file(ctx *gin.Context) {
file, err := ctx.FormFile("file")
if err != nil {
fmt.Printf("get file error %v\n", err)
ctx.String(http.StatusInternalServerError, "upload file failed")
} else {
//把用户上传的文件存到data目录下
ctx.SaveUploadedFile(file, "./data/"+file.Filename)
ctx.String(http.StatusOK, file.Filename)
}
}

// 上传多个文件
func upload_multi_file(ctx *gin.Context) {
form, err := ctx.MultipartForm() //MultipartForm中不止包含多个文件
if err != nil {
ctx.String(http.StatusBadRequest, err.Error())
} else {
//从MultipartForm中获取上传的文件
files := form.File["files"]
for _, file := range files {
ctx.SaveUploadedFile(file, "./data/"+file.Filename) //把用户上传的文件存到data目录下

}
ctx.String(http.StatusOK, "upload "+strconv.Itoa(len(files))+" files")
}
}

type Student struct {
Name string `form:"username" json:"name" uri:"user" xml:"user" yaml:"user" binding:"required"`
Addr string `form:"addr" json:"addr" uri:"addr" xml:"addr" yaml:"addr" binding:"required"`
}

func formBind(ctx *gin.Context) {
var stu Student
//跟ShouldBind对应的是MustBind。MustBind内部会调用ShouldBind,如果ShouldBind发生error会直接c.AbortWithError(http.StatusBadRequest, err)
if err := ctx.ShouldBind(&stu); err != nil {
fmt.Println(err)
ctx.String(http.StatusBadRequest, "parse paramter failed")
} else {
ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
}
}

func jsonBind(ctx *gin.Context) {
var stu Student
if err := ctx.ShouldBindJSON(&stu); err != nil {
fmt.Println(err)
ctx.String(http.StatusBadRequest, "parse paramter failed")
} else {
ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
}
}

func uriBind(ctx *gin.Context) {
fmt.Println(ctx.Request.URL)
var stu Student
if err := ctx.ShouldBindUri(&stu); err != nil {
fmt.Println(err)
ctx.String(http.StatusBadRequest, "parse paramter failed")
} else {
ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
}
}

func xmlBind(ctx *gin.Context) {
var stu Student
if err := ctx.ShouldBindXML(&stu); err != nil {
fmt.Println(err)
ctx.String(http.StatusBadRequest, "parse paramter failed")
} else {
ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
}
}

func yamlBind(ctx *gin.Context) {
var stu Student
if err := ctx.ShouldBindYAML(&stu); err != nil {
fmt.Println(err)
ctx.String(http.StatusBadRequest, "parse paramter failed")
} else {
ctx.String(http.StatusOK, stu.Name+" live in "+stu.Addr)
}
}

func main() {
engine := gin.Default()

engine.GET("/student", get) //http://localhost:5656/student?name=zcy&addr=bj
engine.GET("/student/:name/*addr", restfulunc) //http://localhost:5656/student/zcy/bj/haidian
engine.POST("/student", student) //用postman模拟一个post请求,注意body类型选择x-www-form-urlencoded
//限制表单上传大小为8M,默认上限是32M
engine.MaxMultipartMemory = 8 << 20
engine.POST("/upload_file", upload_file) //用postman模拟一个post请求,注意body类型选择form-data,key在类型选择file

engine.POST("/upload_files", upload_multi_file) //用postman模拟一个post请求,注意body类型选择form-data,key在类型选择file。多个key可以都叫files,value对应不同的文件

engine.POST("/stu/form", formBind) //用postman提交 localhost:5656/stu/form?username=zcy&addr=bj
engine.POST("/stu/json", jsonBind) //用postman提交 body-->raw,json
engine.POST("/stu/uri/:user/:addr", uriBind) //http://localhost:5656/stu/uri/zcy/bj
engine.POST("/stu/xml", xmlBind) //用postman提交 body-->raw,xml
engine.POST("/stu/yaml", yamlBind) //用postman提交 body-->raw,text

engine.Run(":5656")
}

利用postman提交http请求

提交普通post请求
avatar

上传文件
avatar

提交json
avatar

提交xml
avatar

提交yaml
avatar

生成response

  • c.String() response Content-Type=text/plain。
  • c.JSON() response Content-Type= application/json。
  • c.XML() response Content-Type= application/xml。
  • c.HTML() 前端写好模板,后端往里面填值。
  • c.Redirect() 重定向。
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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
package main

import (
"net/http"

"github.com/gin-gonic/gin"
)

func text(engine *gin.Engine) {
engine.GET("/user/text", func(c *gin.Context) {
c.String(http.StatusOK, "hi boy") //response Content-Type:text/plain
})
}

func json1(engine *gin.Engine) {
engine.GET("/user/json1", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"name": "zcy", "addr": "bj"}) //response Content-Type:application/json
})
}

func json2(engine *gin.Engine) {
var stu struct { //匿名结构体
Name string
Addr string
}
stu.Name = "zcy"
stu.Addr = "bj"
engine.GET("/user/json2", func(c *gin.Context) {
c.JSON(http.StatusOK, stu) //response Content-Type:application/json
})
}

func jsonp(engine *gin.Engine) {

var stu struct {
Name string
Addr string
}
stu.Name = "zcy"
stu.Addr = "bj"
engine.GET("/user/jsonp", func(ctx *gin.Context) {
//如果请求参数里有callback=xxx,则response Content-Type为application/javascript,否则response Content-Type为application/json
ctx.JSONP(http.StatusOK, stu)
})
}

type student struct {
Name string
Addr string
}

func xml(engine *gin.Engine) {
var stu = student{Name: "zcy", Addr: "bj"}
engine.GET("/user/xml", func(c *gin.Context) {
c.XML(http.StatusOK, stu)
// c.XML(http.StatusOK, gin.H{"name": "zcy", "addr": "bj"}) //response Content-Type:application/xml
})
}

func yaml(engine *gin.Engine) {
var stu struct {
Name string
Addr string
}
stu.Name = "zcy"
stu.Addr = "bj"
engine.GET("/user/yaml", func(c *gin.Context) {
c.YAML(http.StatusOK, stu)
})
}

func html(engine *gin.Engine) {
engine.LoadHTMLFiles("http/static/template.html")
engine.GET("/user/html", func(c *gin.Context) {
//通过json往前端页面上传值
c.HTML(http.StatusOK, "template.html", gin.H{"title": "用户信息", "name": "zcy", "addr": "bj"})
})
}

func redirect(engine *gin.Engine) {
engine.GET("/not_exists", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://localhost:5656/user/html")
})
}

func main() {
engine := gin.Default()
text(engine) //http://localhost:5656/user/text
json1(engine) //http://localhost:5656/user/json1
json2(engine) //http://localhost:5656/user/json2
jsonp(engine) //http://localhost:5656/user/jsonp?callback=yyds
xml(engine) //http://localhost:5656/user/xml
yaml(engine) //http://localhost:5656/user/yaml
html(engine) //http://localhost:5656/user/html
redirect(engine) //http://localhost:5656/not_exists
engine.Run(":5656")
}

参数检验

  GIN的参数检验是基于go-playground/validator实现的。

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
package main

import (
"fmt"
"net/http"
"time"

"github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding"
"github.com/go-playground/validator/v10" //注意要用新版本v10
)

type Student struct {
Name string `form:"name" binding:"required"` //required:必须上传name参数
Score int `form:"score" binding:"gt=0"` //score必须为正数
Enrollment time.Time `form:"enrollment" binding:"required,before_today" time_format:"2006-01-02" time_utc:"8"` //自定义验证before_today,日期格式东8区
Graduation time.Time `form:"graduation" binding:"required,gtfield=Enrollment" time_format:"2006-01-02" time_utc:"8"` //毕业时间要晚于入学时间
}

// 自定义验证器
var beforeToday validator.Func = func(fl validator.FieldLevel) bool {
if date, ok := fl.Field().Interface().(time.Time); ok {
today := time.Now()
if date.Before(today) {
return true
} else {
return false
}
} else {
return false
}
}

func processErr(err error) {
if err == nil {
return
}

//给Validate.Struct()函数传了一个非法的参数
invalid, ok := err.(*validator.InvalidValidationError)
if ok {
fmt.Println("param error:", invalid)
return
}

//ValidationErrors是一个错误切片,它保存了每个字段违反的每个约束信息
validationErrs := err.(validator.ValidationErrors)
for _, validationErr := range validationErrs {
fmt.Printf("field %s 不满足条件 %s\n", validationErr.Field(), validationErr.Tag())
}
}

func main() {
engine := gin.Default()

//注册验证器
if v, ok := binding.Validator.Engine().(*validator.Validate); ok {
v.RegisterValidation("before_today", beforeToday)
}

engine.GET("/", func(ctx *gin.Context) {
var stu Student
if err := ctx.ShouldBind(&stu); err != nil {
processErr(err) //校验不符合时,打印出哪时不符合
ctx.String(http.StatusBadRequest, "parse parameter failed")
} else {
ctx.JSON(http.StatusOK, stu)
}
}) //http://localhost:5656?name=zcy&score=1&enrollment=2021-08-23&graduation=2021-09-23

engine.Run(":5656")
}

运行结果:

{“Name”:“zcy”,“Score”:1,“Enrollment”:“2021-08-23T00:00:00+08:00”,“Graduation”:“2021-09-23T00:00:00+08:00”}

中间件

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
package main

import (
"log"
"net/http"
"time"

"github.com/gin-gonic/gin"
)

var limitCh = make(chan struct{}, 100) //最多并发处理100个请求

func timeMiddleWare() gin.HandlerFunc {
return func(ctx *gin.Context) {
begin := time.Now()
ctx.Next() //执行业务逻辑
timeElapsed := time.Since(begin)
log.Printf("request %s use %d ms\n", ctx.Request.URL.Path, timeElapsed.Milliseconds())
}
}

func limitMiddleWare() gin.HandlerFunc {
return func(ctx *gin.Context) {
limitCh <- struct{}{} //并发度达到100时就会阻塞
log.Printf("concurrence %d\n", len(limitCh))
ctx.Next() //执行业务逻辑
<-limitCh
}
}

func main() {
engine := gin.Default()
engine.Use(timeMiddleWare()) //全局MiddleWare
engine.GET("/", func(ctx *gin.Context) {
ctx.String(http.StatusOK, "hi boy")
})
engine.GET("/girl", limitMiddleWare(), func(ctx *gin.Context) { //局部MiddleWare
ctx.String(http.StatusOK, "hi girl")
})
engine.Run(":5656")
}

  gin-gonic/contrib上提供了丰富的第三方中间件。

会话

  http是无状态的,即服务端不知道两次请求是否来自于同一个客户端。Cookie由服务端生成,发送给客户端,客户端保存在本地。客户端每次发起请求时把Cookie带上,以证明自己的身份。HTTP请求中的Cookie头只会包含name和value信息(服务端只能取到name和value),domain、path、expires等cookie属性是由浏览器使用的,对服务器来说没有意义。Cookie可以被浏览器禁用。
server_session.go

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
package main

import (
"encoding/base64"
"fmt"
"net/http"
"strconv"
"sync"
"time"

"github.com/gin-gonic/gin"
)

var (
authMap sync.Map
)

//cookie name需要符合规则,否则该cookie会被Gin框架默默地丢弃掉
func genCookieName(ctx *gin.Context) string {
return base64.StdEncoding.EncodeToString([]byte(ctx.Request.RemoteAddr))
}

//登录
func login(engine *gin.Engine) {
engine.POST("/login", func(ctx *gin.Context) {
//为客户端生成cookie
cookie_key := genCookieName(ctx)
cookie_value := strconv.Itoa(time.Now().Nanosecond())
//服务端维护所有客户端的cookie,用于对客户端进行认证
authMap.Store(cookie_key, cookie_value)
//把cookie发给客户端
ctx.SetCookie(cookie_key, cookie_value,
3000, //maxAge,cookie的有效时间,时间单位秒
"/", //path,cookie存放目录
"localhost", //cookie从属的域名
false, //是否只能通过https访问
true, //是否允许别人通过js获取自己的cookie
)
fmt.Printf("set cookie %s = %s to client\n", cookie_key, cookie_value)
ctx.String(http.StatusOK, "登录成功")
})
}

//用户中心
func userCenter(engine *gin.Engine) {
engine.POST("/center", authMiddleWare(), func(ctx *gin.Context) { //为"/center"加个认证中间件
ctx.String(http.StatusOK, "您已通过身份认证,这里是你的私人空间")
})
}

func authMiddleWare() gin.HandlerFunc {
return func(ctx *gin.Context) {
cookie_key := genCookieName(ctx)
var cookie_value string
//读取客户端的cookie
for _, cookie := range ctx.Request.Cookies() {
if cookie.Name == cookie_key {
cookie_value = cookie.Value
break
}
}

//验证Cookie Value是否正确
if v, ok := authMap.Load(cookie_key); !ok {
fmt.Printf("INVALID auth cookie %s = %s\n", cookie_key, cookie_value)
ctx.JSON(http.StatusForbidden, gin.H{cookie_key: cookie_value})
ctx.Abort() //验证不通过,调用Abort
} else {
if v.(string) == cookie_value {
ctx.Next() //本中间件顺利通过
} else {
fmt.Printf("INVALID auth cookie %s = %s\n", cookie_key, cookie_value)
ctx.JSON(http.StatusForbidden, gin.H{cookie_key: cookie_value})
ctx.Abort() //验证不通过,调用Abort
}
}
}
}

func main() {
engine := gin.Default()

//路由
login(engine)
userCenter(engine)

gin.SetMode(gin.ReleaseMode) //发布模式,屏蔽debug信息
engine.Run("127.0.0.1:5656") //测试方法,运行http/client/main.go里的authLogin()方法
}

client_session.go

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
func main() {
if resp, err := http.Post("http://127.0.0.1:5656/login", "text/plain", nil); err != nil {
panic(err)
} else {
fmt.Println("response body")
io.Copy(os.Stdout, resp.Body) //两个io数据流的拷贝
os.Stdout.WriteString("\n")
loginCookies := resp.Cookies() //读取服务端返回的Cookie
resp.Body.Close()
if req, err := http.NewRequest("POST", "http://127.0.0.1:5656/center", nil); err != nil {
panic(err)
} else {
//下次请求再带上cookie
for _, cookie := range loginCookies {
fmt.Printf("receive cookie %s = %s\n", cookie.Name, cookie.Value)
// cookie.Value += "1" //修改cookie后认证不通过
req.AddCookie(cookie)
}
client := &http.Client{}
if resp, err := client.Do(req); err != nil {
fmt.Println(err)
} else {
defer resp.Body.Close()
fmt.Println("response body")
io.Copy(os.Stdout, resp.Body) //两个io数据流的拷贝
os.Stdout.WriteString("\n")
}
}
}
}

Beego

  beego是一个大而全的http框架,用于快速开发go应用程序。bee工具提供诸多命令,帮助我们进行 beego 项目的创建、热编译、开发、测试、和部署。

1
2
3
4
5
6
7
go get github.com/astaxie/beego
go get github.com/beego/bee
cd $GOPATH/src
bee new myweb
cd myweb
go build -mod=mod
bee run

  beego的八大模块互相独立,高度解耦,开发者可任意选取:

  1. 日志模块
  2. ORM模块
  3. Context模块。封装了request和response
  4. Cache模块。封装了memcache、redis、ssdb
  5. Config模块。解析.ini、.yaml、.xml、.json、.env等配置文件
  6. httplib模块
  7. Session模块。session保存在服务端,用于标识客户身份,跟踪会话
  8. toolbox模块。健康检查、性能调试、访问统计、计划任务

MVC开发模式

  在Model层可以使用beego提供的ORM功能。

client

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
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
package main

import (
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"strings"
"time"
)

func simpleGet() {
if resp, err := http.Get("http://127.0.0.1:5656"); err != nil {
panic(err)
} else {
//注意:一定要调用resp.Body.Close(),否则会协程泄漏(同时引发内存泄漏)
defer resp.Body.Close()
/**
具体看一下http协议
*/
fmt.Printf("response proto: %s\n", resp.Proto)
fmt.Printf("response status: %s\n", resp.Status)
fmt.Println("response header")
for key, values := range resp.Header {
fmt.Printf("%s: %v\n", key, values)
}
// 响应体
b, err2 := io.ReadAll(resp.Body)
if err2 != nil {
log.Fatal(err2)
}
fmt.Printf("response body: %v\n", string(b))
}
}

func simplePost() {
//把string转成io.Reader
reader := strings.NewReader("Hello Server")
//Content-Type为text/plain,表示一个朴素的字符串
if resp, err := http.Post("http://127.0.0.1:5656/student", "text/plain", reader); err != nil {
panic(err)
} else {
//注意:一定要调用resp.Body.Close(),否则会协程泄漏(同时引发内存泄漏)
defer resp.Body.Close()
/**
具体看一下http协议
*/
fmt.Printf("response proto: %s\n", resp.Proto)
fmt.Printf("response status: %s\n", resp.Status)
fmt.Println("response header")
for key, values := range resp.Header {
fmt.Printf("%s: %v\n", key, values)
}
fmt.Print("response body: ")
//两个io数据流的拷贝
io.Copy(os.Stdout, resp.Body)
os.Stdout.WriteString("\n")
}
}

func restful() {
//Content-Type为text/plain,表示一个朴素的字符串
if resp, err := http.Post("http://127.0.0.1:5656/user/xiaoming/vip/bj/haidian", "text/plain", nil); err != nil {
panic(err)
} else {
defer resp.Body.Close()
}
}

func postForm() {
//通过form表单提交一些参数键值对
if resp, err := http.PostForm("http://127.0.0.1:5656/stu/form", url.Values{"name": []string{"zcy"}, "age": []string{"18"}}); err != nil {
panic(err)
} else {
//注意:一定要调用resp.Body.Close(),否则会协程泄漏(同时引发内存泄漏)
defer resp.Body.Close()
/**
具体看一下http协议
*/
fmt.Printf("response proto: %s\n", resp.Proto)
fmt.Printf("response status: %s\n", resp.Status)
fmt.Println("response header")
for key, values := range resp.Header {
fmt.Printf("%s: %v\n", key, values)
}
fmt.Println("response body")
io.Copy(os.Stdout, resp.Body) //两个io数据流的拷贝
os.Stdout.WriteString("\n")
}
}

func head() {
//HEAD类似于GET,但HEAD方法只能取到http response报文头部,取不到resp.Body
if resp, err := http.Head("http://127.0.0.1:5656"); err != nil {
panic(err)
} else {
defer resp.Body.Close() //注意:一定要调用resp.Body.Close(),否则会协程泄漏(同时引发内存泄漏)
/**
具体看一下http协议
*/
fmt.Printf("response proto: %s\n", resp.Proto)
fmt.Printf("response status: %s\n", resp.Status)
fmt.Println("response header")
for key, values := range resp.Header {
fmt.Printf("%s: %v\n", key, values)
}
fmt.Println("response body")
io.Copy(os.Stdout, resp.Body) //resp.Body为空
os.Stdout.WriteString("\n")
}
}

// (*http.Client).Do允许我们构造复杂的http request
func complexRequest() {
reader := strings.NewReader("Hello Server")
//HEAD、GET、POST 默认都属于简单请求 Simple Request,通过http.NewRequest可以支持全部的request method
if req, err := http.NewRequest("DELETE", "http://127.0.0.1:5656", reader); err != nil {
panic(err)
} else {
//自定义请求头
req.Header.Add("User-Agent", "花果山")
req.Header.Add("King", "孙悟空")
//自定义Cookie
//HTTP请求中的Cookie头只会包含name和value信息(服务端只能取到name和value),domain、path、expires等cookie属性是由浏览器使用的,对服务器来说没有意义
req.AddCookie(&http.Cookie{
Name: "auth",
Value: "pass",
Path: "/",
Domain: "localhost",
})
//设置请求超时
client := &http.Client{
Timeout: 500 * time.Millisecond,
}
if resp, err := client.Do(req); err != nil {
fmt.Println(err)
} else {
defer resp.Body.Close()
/**
具体看一下http协议
*/
fmt.Printf("response proto: %s\n", resp.Proto)
fmt.Printf("response status: %s\n", resp.Status)
fmt.Println("response header")
for key, values := range resp.Header {
fmt.Printf("%s: %v\n", key, values)
}
fmt.Println("response body")
io.Copy(os.Stdout, resp.Body) //两个io数据流的拷贝
os.Stdout.WriteString("\n")
}
}
}

func requestBook() {
if resp, err := http.Post("http://book.dianshang:5656", "text/plain", nil); err != nil { //Content-Type为text/plain,表示一个朴素的字符串
panic(err)
} else {
defer resp.Body.Close()
fmt.Println("response body")
io.Copy(os.Stdout, resp.Body) //两个io数据流的拷贝
os.Stdout.WriteString("\n")
}
}

func requestFood() {
if resp, err := http.Post("http://food.dianshang:5656", "text/plain", nil); err != nil { //Content-Type为text/plain,表示一个朴素的字符串
panic(err)
} else {
defer resp.Body.Close()
fmt.Println("response body")
io.Copy(os.Stdout, resp.Body) //两个io数据流的拷贝
os.Stdout.WriteString("\n")
}
}

func requestPanic() {
if resp, err := http.Get("http://127.0.0.1:5656/panic"); err != nil {
panic(err)
} else {
defer resp.Body.Close()
fmt.Printf("response status: %s\n", resp.Status)
fmt.Println("response header")
for key, values := range resp.Header {
fmt.Printf("%s: %v\n", key, values)
}
fmt.Println("response body")
io.Copy(os.Stdout, resp.Body) //两个io数据流的拷贝
os.Stdout.WriteString("\n")
}
}

func authLogin() {
if resp, err := http.Post("http://127.0.0.1:5656/login", "text/plain", nil); err != nil {
panic(err)
} else {
fmt.Println("response body")
io.Copy(os.Stdout, resp.Body) //两个io数据流的拷贝
os.Stdout.WriteString("\n")
loginCookies := resp.Cookies() //读取服务端返回的Cookie
resp.Body.Close()
if req, err := http.NewRequest("POST", "http://127.0.0.1:5656/center", nil); err != nil {
panic(err)
} else {
//下次请求再带上cookie
for _, cookie := range loginCookies {
fmt.Printf("receive cookie %s = %s\n", cookie.Name, cookie.Value)
cookie.Value += "1" //修改cookie后认证不通过
req.AddCookie(cookie)
}
client := &http.Client{}
if resp, err := client.Do(req); err != nil {
fmt.Println(err)
} else {
defer resp.Body.Close()
fmt.Println("response body")
io.Copy(os.Stdout, resp.Body) //两个io数据流的拷贝
os.Stdout.WriteString("\n")
}
}
}
}

func main() {
// simpleGet()
// simplePost()
// restful()
postForm()
// head()
// complexRequest()

// requestPanic()
// requestBook()
// requestFood()

//测试限流中间件
// const P = 130
// wg := sync.WaitGroup{}
// wg.Add(P)
// for i := 0; i < P; i++ {
// go func() {
// defer wg.Done()
// if resp, err := http.Get("http://127.0.0.1:5656"); err == nil {
// resp.Body.Close()
// }
// }()
// }
// wg.Wait()

// authLogin()
}

模板

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
package main

import (
"log"
"net/http"

"github.com/gin-gonic/gin"
)

type Student struct {
Name string `form:"username" json:"name" uri:"user" xml:"user" yaml:"user" binding:"required"`
Addr string `form:"addr" json:"addr" uri:"addr" xml:"addr" yaml:"addr" binding:"required"`
}

func get(ctx *gin.Context) {
var stu Student
if err := ctx.ShouldBind(&stu); err != nil {
log.Println(err)
ctx.String(http.StatusBadRequest, "parse paramter failed")
} else {
// 从GET请求的URL中获取参数
name := ctx.Query("name")
//如果没传addr参数,则默认为China
addr := ctx.DefaultQuery("addr", "China")
ctx.String(http.StatusOK, name+" live in "+addr)
}
}

func jsonBind(ctx *gin.Context) {
var stu Student
if err := ctx.ShouldBindJSON(&stu); err != nil {
log.Println(err)
ctx.String(http.StatusBadRequest, "parse paramter failed")
} else {
ctx.JSON(http.StatusOK, stu)
}
}

func main() {
engine := gin.Default()

engine.GET("/student", get)
engine.POST("/stu/json", jsonBind)

engine.Run(":5656")
}
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
package main

import (
"log"
"net/http"

"github.com/gin-gonic/gin"
)

type album struct {
Id int `json:id binding:"required"`
Title string `json:"title" binding:"required"`
Artist string `json:"artist" binding:"required"`
Price float64 `json:"price" binding:"required"`
}

var albums = []album{
{Id: 1, Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{Id: 2, Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{Id: 3, Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}

func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}

func postAlbums(c *gin.Context) {
var newAlbum album
if err := c.ShouldBindJSON(&newAlbum); err != nil {
log.Println(err.Error())
c.String(http.StatusBadRequest, err.Error())
} else {
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
}

func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.POST("/albums", postAlbums)
router.Run(":5656")

}