跳到主要内容

解析请求和处理响应

前言

在这篇文章中,我们将探讨Gin的请求参数解析和响应机制,通过具体的代码示例,了解Gin如何处理各式各样的请求参数和响应。

主要内容:

  • 处理响应:
    • 响应纯文本
    • 响应JSON结构体
    • 响应XML结构体
    • 响应文本
    • 响应重定向
    • 自定义响应头
    • 响应cookie
  • 解析请求参数
    • 路径参数
    • 查询参数
    • 表单参数
    • JSON结构体参数
    • XML结构体参数
    • 请求头参数
    • cookie
    • 文件上传(单文件和多文件)

处理响应

响应纯文本

package main

import (
"net/http"

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

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

router.GET("/text", func (c *gin.Context) () {
c.String(http.StatusOK, "Hello World")
})

if err := router.Run("127.0.0.1:8000"); err != nil {
panic(err)
}
}

响应JSON结构体

func RespJSON(c *gin.Context) {
var v struct {
Name string `json:"name"`
Age int `json:"age"`
}
v.Name = "zhangsan"
v.Age = 18

c.JSON(http.StatusOK, v)
}

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

router.GET("/json", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Hello World"})
})

router.GET("/json2", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 200,
"message": "success",
"data": map[string]any{
"name": "zhangsan",
"age": 18,
},
})
})

router.GET("/json3", RespJSON)

if err := router.Run("127.0.0.1:8000"); err != nil {
panic(err)
}
}

对于常见的响应JSON,还可以直接返回一个结构体,也便于统一响应格式。

package main

import (
"net/http"

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

type Reponse struct {
Code int `json:"code"`
Msg string `json:"msg"`
Data any `json:"data"`
}

func IndexHandler(c *gin.Context) {
c.JSON(http.StatusOK, Reponse{
Code: http.StatusOK,
Msg: "ok",
Data: map[string]any{
"messages": "hello world",
"nest1": map[string]any{
"k1": "v1",
"k2": "v2",
},
},
})
}

func main() {
router := gin.Default()
router.GET("/", IndexHandler)

router.Run("127.0.0.1:8000")
}

响应XML结构体

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

router.GET("/xml", func(c *gin.Context) {
c.XML(http.StatusOK, gin.H{"message": "Hello World"})
})

if err := router.Run("127.0.0.1:8000"); err != nil {
panic(err)
}
}

响应文件

func ResponseFileHandler(c *gin.Context) {
filePath := "README.md"
if _, err := os.Stat(filePath); os.IsNotExist(err) {
c.JSON(http.StatusNotFound, gin.H{
"msg": filePath + " not found",
})
}

// c.File() 是最基本的文件响应方式, 浏览器一般会在页面显示
// 如果要直接下载, 应该用 c.FileAttachment()
c.File(filePath)
}

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

router.GET("/file", ResponseFileHandler)
router.GET("/file2", func(c *gin.Context) {
// c.FileAttachment() 可以让浏览器直接下载文件
// 实际上就是在 c.File() 基础上添加响应头 content-disposition: attachment; filename="README.md"
c.FileAttachment("README.md", "README.md")
})
router.GET("/file3", func(c *gin.Context) {
// c.FileFromFS() 可以从不同文件系统中响应文件, 比如从OSS中返回文件
c.FileFromFS("README.md", http.Dir("."))
})

router.GET("/file4", func(c *gin.Context) {
// 对于特殊文件类型需要设置相对应的响应头
// 比如如果想要在浏览器直接打开pdf文件, 则需要指定Content-Type
c.Header("Content-Type", "application/pdf")
c.File("./hello.pdf")
})

if err := router.Run("127.0.0.1:8000"); err != nil {
panic(err)
}
}

响应重定向

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

router.GET("/redirect", func(c *gin.Context) {
// 301: 永久重定向, http.StatusMovedPermanently
// 302: 临时重定向, http.StatusFound
// 307: 临时重定向, 但是不会改变请求方法, http.StatusTemporaryRedirect
// 308: 永久重定向, 但是不会改变请求方法, http.StatusPermanentRedirect
c.Redirect(http.StatusTemporaryRedirect, "https://baidu.com")
})

if err := router.Run("127.0.0.1:8000"); err != nil {
panic(err)
}
}

自定义响应头

在上面的例子中已经展示了如何自定义响应头

func main() {
router.GET("/file4", func(c *gin.Context) {
// 对于特殊文件类型需要设置相对应的响应头
// 比如如果想要在浏览器直接打开pdf文件, 则需要指定Content-Type
c.Header("Content-Type", "application/pdf")
c.File("./hello.pdf")
})
}

响应cookie

func main() {
router.GET("/cookie", func(c *gin.Context) {
// key name, key value, 有效期(秒), 路径, 域名, 是否https-only, 是否http-only
c.SetCookie("userid", "123456", 30, "/", "localhost", false, true)
c.String(http.StatusOK, "set cookie ok")
})
}

解析请求

路径参数

package main

import (
"net/http"
"strings"

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

func ReqUrlHandler(c *gin.Context) {
name := c.Param("name")
action := c.Param("action")
action = strings.Trim(action, "/")

c.JSON(http.StatusOK, gin.H{
"msg": name + " is " + action,
})
}

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

req_rg := router.Group("/req")
{
req_rg.GET("/urlarg/:name/*action", ReqUrlHandler) // url路径参数, *action表示通配 /usrarg/:name 后面的剩余路径
}
router.Run("127.0.0.1:8000")
}

测试

$ curl http://127.0.0.1:8000/req/urlarg/zhangsan/running
{"msg":"zhangsan is running"}

$ curl http://127.0.0.1:8000/req/urlarg/zhangsan/running?name=zhangsan
{"msg":"zhangsan is running"}

$ curl http://127.0.0.1:8000/req/urlarg/zhangsan/running/qweasd
{"msg":"zhangsan is running/qweasd"}

$ curl http://127.0.0.1:8000/req/urlarg/zhangsan/running/qweasd?name=lisi
{"msg":"zhangsan is running/qweasd"}

查询参数

package main

import (
"net/http"

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

func ReqQueryHandler(c *gin.Context) {
name := c.Query("name")
c.JSON(http.StatusOK, gin.H{
"msg": "query arg. hello " + name,
})
}

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

req_rg := router.Group("/req")
{
req_rg.GET("/query", ReqQueryHandler)
}
router.Run("127.0.0.1:8000")
}

测试

$ curl http://127.0.0.1:8000/req/query?name=zhangsan
{"msg":"query arg. hello zhangsan"}

表单参数

package main

import (
"net/http"

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

func ReqFormHandler(c *gin.Context) {
username := c.PostForm("username")
password := c.PostForm("password")

c.JSON(http.StatusOK, gin.H{
"msg": "form arg",
"data": gin.H{
"username": username,
"password": password,
},
})
}

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

req_rg := router.Group("/req")
{
req_rg.POST("/form", ReqFormHandler)
}
router.Run("127.0.0.1:8000")
}

测试

curl -X POST http://127.0.0.1:8000/req/form -d 'username=zhangsan&password=123456'

JSON结构体

package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

// 定义接收JSON数据的结构体
type Login struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}

func f1(c *gin.Context) {
var json Login
// 将数据解析到结构体中
if err := c.ShouldBindJSON(&json); err != nil {
// 返回错误信息
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}

c.JSON(http.StatusOK, gin.H{
"Username": json.Username,
"Password": json.Password,
})
}

func f2(c *gin.Context) {
// 解析未知结构的json请求体
var jsonData map[string]any
if err := c.ShouldBindJSON(&jsonData); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
// 使用 jsonData 处理数据
c.JSON(http.StatusOK, jsonData)
}

func main() {
r := gin.Default()
v1 := r.Group("/v1")
{
v1.POST("/login", f1)
v1.POST("/dynamic", f2)
}

r.Run("127.0.0.1:8000")
}

测试

$ curl -X POST http://127.0.0.1:8000/v1/dynamic -d '{"name": "zhangsan"}'
{"name":"zhangsan"}

$ curl -X POST http://127.0.0.1:8000/v1/login -d '{"username": "zhangsan", "password": "123456"}'
{"Password":"123456","Username":"zhangsan"}

XML结构体

package main

import (
"net/http"

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

// 定义接收JSON数据的结构体
type Login struct {
Username string `xml:"username" binding:"required"`
Password string `xml:"password" binding:"required"`
}

func f1(c *gin.Context) {
// xml数据解析与绑定
var xml Login
// 将数据解析到结构体中
if err := c.ShouldBindXML(&xml); err != nil {
// 返回错误信息
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
return
}

c.JSON(http.StatusOK, gin.H{
"Username": xml.Username,
"Password": xml.Password,
})
}

func main() {
r := gin.Default()
r.POST("/xml", f1)

r.Run("127.0.0.1:8000")
}

测试

import requests

data_xml = """
<?xml version="1.0" encoding="utf-8"?>
<server>
<username>lisi</username>
<password>123456</password>
</server>
"""

url = "http://127.0.0.1:8000/xml"

resp = requests.post(url, data = data_xml)
print(resp.text)

文件上传

package main

import (
"fmt"
"net/http"

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

func ReqUploadOneFileHandler(c *gin.Context) {
file, err := c.FormFile("file")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
fmt.Println(file.Filename)

dst := file.Filename // 文件保存路径
c.SaveUploadedFile(file, dst)
c.JSON(http.StatusOK, gin.H{
"code": 200,
"msg": "upload successfully",
"data": gin.H{
"file": file.Filename,
"path": dst,
},
})
}

func ReqUploadMultiFileHandler(c *gin.Context) {
form, err := c.MultipartForm()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"code": 400,
"msg": "upload failed",
"data": gin.H{
"error": err.Error(),
},
})
return
}
files := form.File["files"]
for _, file := range files {
fmt.Println(file.Filename)
dst := file.Filename
c.SaveUploadedFile(file, dst)
}
c.JSON(http.StatusOK, gin.H{
"code": http.StatusOK,
"msg": "upload successfully",
"data": gin.H{
"files": len(files),
},
})
}

func main() {
r := gin.Default()
r.MaxMultipartMemory = 8 << 20 // 设置文件大小限制为 8MB, 8 * 2^20
r.POST("/upload", ReqUploadOneFileHandler) // 上传单个文件
r.POST("/uploads", ReqUploadMultiFileHandler) // 上传多个文件

r.Run("127.0.0.1:8000")
}

测试

$ curl -X POST "http://127.0.0.1:8000/upload" -H "Content-Type: multipart/form-data" -F "file=@./cron.log"
{"code":200,"data":{"file":"cron.log","path":"cron.log"},"msg":"upload successfully"}


curl -X POST "http://127.0.0.1:8000/uploads" -H "Content-Type: multipart/form-data" -F "files=@./cron.log" -F "files=@./crontest.log"
{"code":200,"data":{"files":2},"msg":"upload successfully"}

请求头参数

func main() {
router.GET("/header", func(c *gin.Context) {
// 如果请求头不存在, c.GetHeader() 将会返回一个空字符串
userAgent := c.GetHeader("User-Agent")
c.String(http.StatusOK, userAgent)
})
}

cookie参数

func main() {
router.GET("/cookie", func(c *gin.Context) {
cookie, err := c.Cookie("username")
if err != nil {
c.String(http.StatusOK, "cookie not found")
} else {
c.String(http.StatusOK, cookie)
}
})
}