Gin框架的请求和响应
字段作用典型值 / 示例Host目标域名+端口User-Agent客户端软件信息Accept期望响应格式期望语言zh-CN,en;q=0.9期望压缩算法请求 Body 格式Body 字节数123身份令牌Cookie浏览器自动带上的键值对Origin浏览器跨域请求的来源域Referer从哪个页面跳转过来链路追踪唯一号经过代理后的真实客户端 IP缓存时间戳。
1.请求
1.1参数相关
1.查询参数(Query String)
来源:URL 中 ?
后面的键值对
注意:
-
自动解析,无需关心 Content-Type
-
空格/中文会被浏览器 URL-encode,Gin 自动 decode
// 查询参数
func query(c *gin.Context) {
//单值
fmt.Println(c.Query("user"))
fmt.Println(c.GetQuery("user"))
//多值
fmt.Println(c.QueryArray("user"))
//默认值
fmt.Println(c.DefaultQuery("addr", "beijing"))
}
2.动态参数(Router Parameter )
来源:路由规则里写死的“占位符”
注意:
-
名字必须和路由定义完全一致,区分大小写
-
不支持“可选”占位符,必须段段对应
// 动态参数
func param(c *gin.Context) {
fmt.Println(c.Param("user"))
fmt.Println(c.Param("book"))
}
r.GET("/param/:user/:book", param)
3.表单参数(Form / PostForm)
来源:
① application/x-www-form-urlencoded
(普通表单)
② multipart/form-data
(带文件上传)
注意:
-
只能出现在 POST/PUT/PATCH 且 Body 未被读走;一旦
c.ShouldBindJSON
读了 Body,后续再读 Form 会为空 -
文件上传别忘了
enctype="multipart/form-data"
// 表单参数
func form(c *gin.Context) {
//单值
fmt.Println(c.PostForm("name"))
//多值
fmt.Println(c.PostFormArray("name"))
//默认值
fmt.Println(c.DefaultPostForm("addr", "default"))
//文件
file, _ := c.FormFile("avatar") // 单文件
files := c.MultipartForm() // 多文件
}
4.原始参数(Raw Body,字节流)
来源:客户端把 JSON/XML/二进制直接写在 Body 里
注意:
-
c.Request.Body
是io.ReadCloser
,只能读一次;读完就空了 -
若想多次使用,需要先
c.Set("rawBody", bodyBytes)
存起来,或自己封装一个TeeReader
-
常见 Content-Type:
application/json
,application/xml
,text/plain
,application/octet-stream
// 使用GetRawData
func raw(c *gin.Context) {
type Person struct {
Name string `json:"name"`
Age int `json:"age"`
}
var person Person
err := bindJSON(c, &person)
if err != nil {
fmt.Printf(err.Error())
}
fmt.Println(person)
}
func bindJSON(c *gin.Context, obj any) error {
data, _ := c.GetRawData()
//立即把 body 写回去,后续逻辑还能读
c.Request.Body = io.NopCloser(bytes.NewBuffer(data))
contentType := c.GetHeader("Content-Type")
switch contentType {
case "application/json":
err := json.Unmarshal(data, obj)
if err != nil {
fmt.Printf(err.Error())
return err
}
case xxx:
xxxxxxxxxxx
default:
xxxxxxxxxxx
}
return nil
}
------------------------------------------------------
//不使用GetRawData
// 一次性读成字节
bodyBytes, _ := io.ReadAll(c.Request.Body)
// 常见:绑定到结构体
var user User
if err := c.ShouldBindJSON(&user); err != nil { … }
1.2请求方式
方法 | Gin 注册函数 | 典型用途 | 幂等* | 安全* | 有无 Body |
---|---|---|---|---|---|
GET | r.GET() |
读资源(查询) | ✅ | ✅ | ❌ |
POST | r.POST() |
新建资源 | ❌ | ❌ | ✅ |
PUT | r.PUT() |
整表替换/更新 | ✅ | ❌ | ✅ |
PATCH | r.PATCH() |
局部更新 | ❌ | ❌ | ✅ |
DELETE | r.DELETE() |
删除资源 | ✅ | ❌ | 可有可无 |
HEAD | r.HEAD() |
只拿响应头 | ✅ | ✅ | ❌ |
OPTIONS | r.OPTIONS() |
CORS 预检 | ✅ | ✅ | 可有可无 |
ANY | r.Any() |
一次性捕获全部方法 | — | — | — |
*幂等:重复执行结果不变;安全:不修改服务器数据。
读取 GET,新增 POST,整改 PUT,局部 PATCH,删除 DELETE,探活 HEAD,预检 OPTIONS,调试通吃 Any。
type Article struct {
Title string `json:"title"`
Content string `json:"content"`
}
type Result[T any] struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data T `json:"data"`
}
func main() {
r := gin.Default()
r.GET("/articles", getList) //查所有
r.GET("/articles/:id", getDetail) //查单个
r.POST("/articles", create) //增
r.PUT("/articles/:id", update) //改
r.DELETE("/articles/:id", remove) //删
r.Run(":8080")
}
func getList(c *gin.Context) {
articleList := []Article{
{"Go语言入门", "这篇文章是《Go语言入门》"},
{"python语言入门", "这篇文章是《python语言入门》"},
{"JavaScript语言入门", "这篇文章是《JavaScript语言入门》"},
}
c.JSON(200, Result[[]Article]{200, "success", articleList})
}
func getDetail(c *gin.Context) {
fmt.Println(c.Param("id"))
article := Article{"Go语言入门", "这篇文章是《Go语言入门》"}
c.JSON(200, Result[Article]{200, "success", article})
}
func create(c *gin.Context) {
var article Article
err := bindJSON(c, &article)
if err != nil {
c.JSON(400, Result[string]{400, "解析错误", err.Error()})
return
}
c.JSON(200, Result[Article]{200, "success", article})
}
func update(c *gin.Context) {
fmt.Println(c.Param("id"))
var article Article
err := bindJSON(c, &article)
if err != nil {
c.JSON(400, Result[string]{400, "解析错误", err.Error()})
return
}
c.JSON(200, Result[Article]{200, "success", article})
}
func remove(c *gin.Context) {
fmt.Println(c.Param("id"))
c.JSON(200, Result[string]{0, "success", "删除成功"})
}
func bindJSON(c *gin.Context, obj any) error {
data, _ := c.GetRawData()
contentType := c.GetHeader("Content-Type")
switch contentType {
case "application/json":
err := json.Unmarshal(data, &obj)
if err != nil {
fmt.Printf(err.Error())
return err
}
}
return nil
}
1.3请求头
请求头(Request Header)是浏览器 / 客户端在 HTTP 请求行之后、Body 之前 发送的一组 键值对,用来告诉服务器“我是谁、我要什么、我能接受什么”。
1.字段介绍
字段 | 作用 | 典型值 / 示例 |
---|---|---|
Host | 目标域名+端口 | api.example.com |
User-Agent | 客户端软件信息 | Mozilla/5.0 ... |
Accept | 期望响应格式 | application/json |
Accept-Language | 期望语言 | zh-CN,en;q=0.9 |
Accept-Encoding | 期望压缩算法 | gzip, deflate, br |
Content-Type | 请求 Body 格式 | application/json |
Content-Length | Body 字节数 | 123 |
Authorization | 身份令牌 | Bearer <token> / Basic <base64> |
Cookie | 浏览器自动带上的键值对 | sessionid=abc |
Origin | 浏览器跨域请求的来源域 | https://spa.example.com |
Referer | 从哪个页面跳转过来 | https://google.com |
X-Request-ID | 链路追踪唯一号 | uuid-abc-123 |
X-Forwarded-For | 经过代理后的真实客户端 IP | 118.123.3.22, 10.0.0.1 |
If-Modified-Since | 缓存时间戳 | Wed, 21 Oct 2015 07:28:00 GMT |
Host 找主机,User-Agent 报身份,Accept 谈格式,Authorization 带令牌,Cookie 维持会话,X-Forwarded-For 追真 IP。
2.请求头获取
请求头的获取可以获取单个请求头(c.GetHeader(key
))
获取所有请求头(c.Request.Header)
获取原始请求头字符串(c.Request.Header.Get(key
) //使用少)
func main() {
r := gin.Default()
//请求头
r.GET("/request", func(c *gin.Context) {
//此种方式获取的时候对,字母大小写不区分
fmt.Println(c.GetHeader("User-Agent"))
fmt.Println(c.GetHeader("user-Agent"))
fmt.Println(c.GetHeader("user-agEnT"))
fmt.Println(c.GetHeader("user-agent"))
fmt.Println(c.Request.Header)
//此方式获取的时候,使用Get获取的时候不区分大小写,使用map获取的时候区分大小写
fmt.Println(c.Request.Header.Get("User-Agent"))
fmt.Println(c.Request.Header["User-Agent"])
fmt.Println(c.Request.Header["user-Agent"])
fmt.Println(c.Request.Header.Get("Token"))
fmt.Println(c.Request.Header.Get("token"))
fmt.Println(c.Request.Header["Token"])
fmt.Println(c.Request.Header["token"])
c.JSON(200, gin.H{"msg": "success"})
})
r.Run(":8080")
}
2.响应
2.1响应状态码
类别 | 码 | 英文 | Gin 常量 | 一句话场景 |
---|---|---|---|---|
成功 | 200 | OK | http.StatusOK |
正常返回数据 |
201 | Created | http.StatusCreated |
POST 新建成功,返回新资源 | |
204 | No Content | http.StatusNoContent |
删除/更新成功,但无 Body | |
重定向 | 301 | Moved Permanently | http.StatusMovedPermanently |
永久跳转,SEO 用 |
302 | Found | http.StatusFound |
临时跳转,登录后回首页 | |
304 | Not Modified | http.StatusNotModified |
资源未改,走缓存 | |
客户端错 | 400 | Bad Request | http.StatusBadRequest |
参数格式不对 |
401 | Unauthorized | http.StatusUnauthorized |
未登录/令牌失效 | |
403 | Forbidden | http.StatusForbidden |
登录了但无权限 | |
404 | Not Found | http.StatusNotFound |
资源不存在 | |
服务端错 | 500 | Internal Server Error | http.StatusInternalServerError |
代码 panic/未知异常 |
2xx 成功,3xx 跳转,4xx 客户端错误,5xx 服务端错误
2.2响应数据类型
1.返回字符串
r.GET("/txt",func(c *gin.Context){
c.String(200,"hello world")
})
2.返回json
r.GET("/json", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "hello world",
"status": 200,
})
})
r.GET("/moreJson", func(c *gin.Context) {
type Msg struct {
Name string `json:"name"`
Message string
Number int
}
msg := Msg{
Name: "小王子",
Message: "hello world",
Number: 123,
}
c.JSON(http.StatusOK, msg)
})
3.返回xml
r.GET("/xml", func(c *gin.Context) {
c.XML(200, gin.H{
"message": "pong",
"status": "ok",
})
})
4.返回yaml
r.GET("/yml", func(c *gin.Context) {
c.YAML(200, gin.H{
"message": "pong",
"status": "ok",
})
})
5.返回html
//导入模板
r.LoadHTMLGlob("Gin/templates/*")
//定义接口
r.GET("/html", func(c *gin.Context) {
//index.html为自己导入的模板的名字,gin.H{"name": "xiaobai"}为自己传输的数据
c.HTML(http.StatusOK, "index.html", gin.H{"name": "xiaobai"})
})
2.3文件响应
func main() {
r := gin.Default()
//静态文件托管
//单个文件,前者为访问路径,后者为文件路径
r.StaticFile("/test", "Gin/static/test.png")
//多个文件,前者为访问路径的前缀(后面跟具体的文件名),后者为文件路径
r.StaticFS("/static", http.Dir("Gin/static/static"))
r.Run(":8080")
}
2.4重定向
场景 | Gin 方法 | HTTP 码 | 浏览器表现 |
---|---|---|---|
临时跳转(默认) | c.Redirect(http.StatusFound, "/new") |
302 | 地址栏会变,下次还访问原 URL |
永久跳转 | c.Redirect(http.StatusMovedPermanently, "/new") |
301 | 地址栏会变,浏览器缓存永久指向新地址 |
跳外部网址 | c.Redirect(302, "https://github.com/gin-gonic/gin") |
302 | 同 302,可站外 |
内部转发(服务端) | c.Request.URL.Path = "/new" r.HandleContext(c) |
200 | 浏览器无感知,URL 不变 |
重定向用 Redirect,301 永久 302 临时
func main() {
r := gin.Default()
r.GET("/redirect", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com/")
})
r.Run(":8080")
}
2.5响应头
字段 | 作用 | 典型值 / 示例 |
---|---|---|
Content-Type | Body 格式 | application/json; charset=utf-8 |
Content-Disposition | 下载时指定文件名/方式 | attachment; filename="report.pdf" |
Access-Control-Allow-Origin | CORS 白名单 | * 或 https://spa.com |
Access-Control-Allow-Methods | CORS 允许方法 | GET, POST, PUT, DELETE |
Access-Control-Allow-Headers | CORS 允许头 | Content-Type, Authorization |
Cache-Control | 缓存策略 | no-cache , max-age=3600 |
Location | 配合 3xx 重定向 | /new-path |
Set-Cookie | 种 Cookie | session=abc; Path=/; HttpOnly |
X-Content-Type-Options | 防 MIME 嗅探 | nosniff |
X-Frame-Options | 防点击劫持 | DENY |
先写Header再写Body,下载加 Disposition,跨域加 CORS,缓存加 Cache-Control
r.GET("/response", func(c *gin.Context) {
c.Header("Token", "6SmNfK3z9bG5iT8vL0nP4qR7tH1uJ2wX3yZ6")
c.Header("Content-Type", "application/text; charset=utf-8")
c.JSON(200, gin.H{"data": "看看响应头"})
})
更多推荐
所有评论(0)