二.请求
一.查询函数
I.查询参数
gin
框架的查询函数常见的有Query
、GetQuery
和QueryArray
。
如:
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-urlencoded
或multipart/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/json
或text/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"