GoWeb开发的gin框架学习 part two

Posted by OuterCyrex on June 2, 2024

二.请求

一.查询函数

I.查询参数

gin框架的查询函数常见的有QueryGetQueryQueryArray。 如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main
import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func _query(c *gin.Context) {
	fmt.Println(c.Query("user"))
	fmt.Println(c.GetQuery("user"))
	fmt.Println(c.QueryArray("user"))
}

func main() {
	router := gin.Default()
	router.GET("/query", _query)
	_ = router.Run(":8080")
}

返回值:

1
2
3
4
//输入localhost:8080/query?user=Outer&&user=Cyrex
Outer
Outer true
[Outer Cyrex]

三者的区别基于源码可以体现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Query会返回GetQuery的字符串部分
func (c *Context) Query(key string) (value string) {
	value, _ = c.GetQuery(key)
	return
}
//GetQuery会返回GetQueryArray返回的切片的第一个元素以及一个bool值
func (c *Context) GetQuery(key string) (string, bool) {
	if values, ok := c.GetQueryArray(key); ok {
		return values[0], ok
	}
	return "", false
}
//GetQueryArray用于返回URL的查询字符串部分,将其结果以切片的形式返回,并附带一个bool值
func (c *Context) GetQueryArray(key string) (values []string, ok bool) {
	c.initQueryCache()
	values, ok = c.queryCache[key]
	return
}

II.查询动态参数

上述的查询参数过程需要输入URL的时候附带查询字符串

此过程可以通过动态参数来解决

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import (
	"fmt"
	"github.com/gin-gonic/gin"
)
func _param(c *gin.Context) {
	fmt.Println(c.Param("user_id"))
	fmt.Println(c.Param("language"))
}
func main() {
	router := gin.Default()
	router.GET("/param/:user_id", _param)
	router.GET("/param/:user_id/:language", _param)
	_ = router.Run(":8080")
}

返回值:

1
2
3
4
5
//输入localhost:8080/param/Outer
Outer
//输入localhost:8080/param/Outer/GO
Outer
GO

通过param函数,我们可以将URL的特定字段认定为查询字符串并返回其

III.表单查询

客户端通过POST指令向服务端输入表单数据时,需要PostForm函数来输入

表单参数(Form Parameters)是在HTTP请求中用于传递表单数据的键值对集合。当用户在网页上填写表单并提交时,浏览器会将表单中的数据编码为特定的格式(通常是application/x-www-form-urlencodedmultipart/form-data),并通过HTTP请求发送给服务器。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func _form(c *gin.Context) {
	fmt.Println(c.PostForm("name"))
	fmt.Println(c.PostFormArray("name"))
	fmt.Println(c.DefaultPostForm("name", "Cyrex"))
	forms, err := c.MultipartForm()
	fmt.Println(forms, err)
}
func main() {
	router := gin.Default()
	router.POST("/form", _form)
	_ = router.Run(":8080")
}

返回值:

1
2
3
4
5
//向localhost:8080/form发送键值对"name"="Outer","name"="Cyrex","pic":"[0xc00002e300]"
Outer
[Outer Cyrex]
Outer
&{map[name:[Outer Cyrex]] map[pic:[0xc000112c00]]} <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
//Postform返回GetPostForm的字符串部分,忽略错误值
func (c *Context) PostForm(key string) (value string) {
	value, _ = c.GetPostForm(key)
	return
}
//PostformArray返回GetPostFormArray的字符串部分,将表单数据的所有内容转化为一个切片并输出,忽略错误值
func (c *Context) PostFormArray(key string) (values []string) {
	values, _ = c.GetPostFormArray(key)
	return
}
//DefaultPostForm在没有表单数据被接受到时会返回一个defaultValue字符串
func (c *Context) DefaultPostForm(key, defaultValue string) string {
	if value, ok := c.GetPostForm(key); ok {
		return value
	}
	return defaultValue
}
//MultipartForm函数接受所有的form参数,包括文件,返回一个装有text的映射和file的映射,且为数组形式
func (c *Context) MultipartForm() (*multipart.Form, error)
//其返回的form参数是一个结构体multipart.Form,内容为:
type Form struct {
	Value map[string][]string
	File  map[string][]*FileHeader //存有文件的相关信息
}

IV.原始参数

什么是原始参数?

原始参数(Raw Parameters)通常指的是从客户端(如Web前端、移动应用等)发送到服务器端的未经处理或未经解析的数据。这些数据可以是各种格式,包括但不限于表单数据JSON数据XML数据等。

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

import (
    "encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
)
func _raw(c *gin.Context) {
	body, _ := c.GetRawData()
	contentType := c.GetHeader("Content-Type") 
    //Content-Type是HTTP Header的一个字段,记录了其数据序列化格式类型
	switch contentType {
	case "application/json": //若类型为json,则将其反序列化为一个结构体形式
		type User struct {
			Name string `json:"name"`
			Age  int    `json:"age"`
		}
		var user_Content User
		err := json.Unmarshal(body, &user_Content) 
        //json.Unmarshal函数将json文件反序列化并返回一个err值
		if err != nil {
			fmt.Println(err.Error())
		} else {
			fmt.Println(user_Content)
		}
	}
}
func main() {
	router := gin.Default()
	router.POST("/form", _form)
	_ = router.Run(":8080")
}

此时,若输入json原始数据,则将返回其对应的键值对内容

返回值:

1
2
3
4
5
6
7
//输入json原生数据:
{
    "name":"Outer",
    "age":18
}
//返回值:
{Outer 18}

以上过程在函数内部同时完成了原始数据读取反序列化

也可以通过将函数封装,来实现特殊的需求

如需要同时处理多种键值对不同的json原始数据

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
//将json反序列化的过程进行封装
func bindJson(c *gin.Context, obj any) (err error) {
	body, _ := c.GetRawData()
	contentType := c.GetHeader("Content-Type")
	switch contentType {
	case "application/json":
		err := json.Unmarshal(body, &obj)
		if err != nil {
			fmt.Println(err.Error())
			return err
		}
	}
	return nil
}
//此时可以自定义结构体类型并将其输入至上方的bindJson函数中实现反序列化
func _raw(c *gin.Context) {
	type User struct {
		Name string `json:"name"`
		Age  int    `json:"age"`
	}
	var user User
	err := bindJson(c, &user)
	if err != nil {
		fmt.Println(err)
	}
	fmt.Println(user)
}

V.补充

HTTP Header的内容很多,以下是一些节选:


通用头部(General Headers)

  • Cache-Control:控制缓存的行为。
  • Connection:控制是否保持网络连接,如 keep-alive
  • Date:消息发送的日期和时间。
  • Pragma:包含实现特定的指令,但通常被 Cache-Control 替代。
  • Trailer:用来说明在消息体后跟随的头部列表。
  • Transfer-Encoding:指定消息体的传输编码方式,如 chunked
  • Upgrade:要求客户端升级到另一个协议。
  • Via:显示消息经过的代理或网关。
  • Warning:显示与消息内容相关的警告信息。

请求头部(Request Headers)

  • Accept:指定客户端能够接收的内容类型,如 text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
  • Accept-Charset:浏览器可以接受的字符编码集。
  • Accept-Encoding:浏览器能够进行解码的数据编码方式,如 gzip, deflate
  • Accept-Language:浏览器所希望的语言种类。
  • Authorization:包含了用户认证信息的凭证,通常用于 HTTP 基本认证或摘要认证。
  • Expect:期望服务器的特定行为。
  • From:发送请求的用户的电子邮箱地址。
  • Host:指定请求的服务器的域名和端口号。
  • If-Match:仅当请求的资源与给定的 ETag 相匹配时才处理请求。
  • If-Modified-Since:如果请求的资源在指定的日期之后被修改才处理该请求。
  • If-None-Match:如果请求的资源与给定的 ETag 不匹配时才处理请求。
  • If-Range:如果请求的资源在指定的日期之后被修改或 ETag 不匹配时才处理请求(用于范围请求)。
  • If-Unmodified-Since:如果请求的资源在指定的日期之后未被修改才处理请求。
  • Max-Forwards:限制请求经过的代理或网关数量。
  • Proxy-Authorization:向代理服务器发送的认证信息。
  • Range:请求资源的某个范围。
  • Referer:指示请求来自哪个页面。
  • TE:传输编码的优先级。
  • User-Agent:发送请求的客户端的用户代理信息,如浏览器类型和版本。

响应头部(Response Headers)

  • Accept-Ranges:表明服务器是否支持范围请求,以及支持哪些范围单位。
  • Age:资源在代理缓存中存在的时间长度(以秒为单位),仅当缓存资源被发送时才包含此头部。
  • ETag:资源的唯一标识符,通常用于缓存控制。
  • Location:用于重定向接收者到非请求 URL 的位置来完成请求或识别新的资源。
  • Proxy-Authenticate:要求客户端使用代理进行认证。
  • Retry-After:如果资源不可用,此头部会告知客户端多久后可以再次尝试。
  • Server:服务器软件的名称和版本。
  • Vary:告知缓存代理服务器如何对将来的请求头进行匹配,以决定请求的资源是否可用。
  • WWW-Authenticate:服务器对客户端的认证要求。

实体头部(Entity Headers)

  • Allow:指定服务器支持的请求方法(如 GET, POST, PUT)。
  • Content-Encoding:资源的编码方式,如 gzip
  • Content-Language:资源所用的自然语言。
  • Content-Length:资源的长度(以字节为单位)。
  • Content-Location:资源的备用位置。
  • Content-MD5:资源的 MD5 散列值(不常用,因为与 HTTP/1.1 的分块传输编码不兼容)。
  • Content-Range:如果是部分响应,此头部会告知请求资源的范围。
  • Content-Type:资源的媒体类型,如 application/jsontext/html
  • Expires:资源过期的日期和时间。
  • Last-Modified:资源最后修改的日期和时间。

二.RESTful风格

RESTful风格即网络应用中一种资源定位资源操作的风格。

GET:从服务器中取出资源(一项或多项)

POST:在服务器中新建一个资源

PUT:在服务器中更新资源(客户端提供完整的资源数据)

PATCH:在服务器中更新资源(客户端提供需要修改的资源数据)

DELETE:从服务器中删除资源

三.请求Header数据

1.如何请求

Web开发中,需要分析请求的Header数据

可以通过GetHeader函数来获取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
)

func main() {
	router := gin.Default()
	router.GET("/", func(c *gin.Context) {
		fmt.Println(c.GetHeader("User-Agent"))
		fmt.Println(c.Request.Header)
        fmt.Println(c.Request.Header.Get("User-Agent"))
		fmt.Println(c.Request.Header["User-Agent"])
	})
	_ = router.Run(":8080")
}

Postman创建Post请求时的返回结果:

1
2
3
4
PostmanRuntime/7.39.0
map[Accept:[*/*] Accept-Encoding:[gzip, deflate, br] Connection:[keep-alive] Content-Length:[39] Content-Type:[application/json] Postman-Token:[d37baa25-fd1b-4b4e-bf8f-ba5567cd8bc1] User-Agent:[PostmanRuntime/7.39.0]]
PostmanRuntime/7.39.0
[PostmanRuntime/7.39.0]

从上述函数的源码中分析其返回值:

1
2
3
4
5
6
7
8
9
//1.GetHeader可以返回Header数据中对应关键词对应的值
func (c *Context) GetHeader(key string) string {
	return c.requestHeader(key)
}
//2.Request.Header则返回Header数据的所有键值对
//3.可以通过Request.Header.Get来获取关键词对应的值
//4.或直接将Request.Header当做一个映射,取其关键词对应的值
type Header map[string][]string
//注:方法1、2、3都可以不区分首字母大小写,但方法4需要严格保证首字母大写

2.反爬虫

一般而言,通过python爬虫发送的请求会在User-Agent字段带有python字样

我们需要检测器是否含有python字样来达到反爬虫的目的

1
2
3
4
5
6
7
8
router.GET("/index", func(c *gin.Context) {
		userAgent := c.Request.Header.Get("User-Agent")
		if strings.Contains(userAgent, "python") {  //如果User-Agent字段内含有python字段
			c.JSON(200, gin.H{"data": "Error,python"}) //返回"Error,python"信息
			return
		}
		c.JSON(200, gin.H{"data": "OK,user"}) //若未含有该字段,则返回正常输出"OK,user"
	})

3.设置响应头

通过Header指令可以将Header的特定字段更改

下述代码实现了将json格式转化为text格式的操作

1
2
3
4
router.GET("/res", func(c *gin.Context) {
		c.Header("Content-Type", "application/text; charset=utf-8")
		c.JSON(200, gin.H{"data": "Header Changed"})
	})

运行结果:

1
2
3
4
//操作前:
"Header":"application/json; charset=utf-8"
//操作后:
"Header":"application/text; charset=utf-8"