处理HTTP请求

这篇笔记我们深入学习如何使用Gin框架读取HTTP请求信息以及如何返回响应信息。

获取请求参数

读取URL参数

对于URL参数,我们可以使用Query()方法获取。

engine.GET("/getStudent", func(context *gin.Context) {
    id := context.Query("id")
    fmt.Printf("%v\n", id)
})

如果参数不存在,会返回空字符串。

读取路径参数

对于路径参数,我们可以使用Param()方法获取。

engine.GET("/getStudent/:id", func(context *gin.Context) {
    id := context.Param("id")
    fmt.Printf("%v\n", id)
})

不过和URL参数不同,如果路径参数不存在,这条路由信息是无法匹配成功的。

获取请求头信息

读取请求头信息,我们可以使用GetHeader()方法。

engine.GET("/getStudent", func(context *gin.Context) {
    id := context.GetHeader("id")
    fmt.Printf("%v\n", id)
})

如果请求头不存在,会返回空字符串。

获取表单请求

表单请求位于POST或PUT的请求体中,我们可以使用PostForm()方法读取表单字段。

engine.POST("/addStudent", func(context *gin.Context) {
    name := context.PostForm("name")
    age := context.PostForm("age")

    fmt.Printf("%v %v\n", name, age)
})

注意表单格式实际上有form-datax-www-form-urlencoded两种格式,Gin对这两种格式的表单进行了统一的支持。

此外,Gin也支持将表单参数绑定到结构体,下面是一个例子。

type Student struct {
    Name string `form:"name"`
    Age  string `form:"age"`
}
engine.POST("/addStudent", func(context *gin.Context) {
    stu := Student{}
    err := context.ShouldBind(&stu)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Printf("%v %v\n", stu.Name, stu.Age)
})

代码中我们使用了ShouldBind()方法将请求表单绑定到了stu结构体实例上,结构体的定义中使用了Tag语法指定字段和表单Key的对应关系。绑定时,请求表单多字段或是少字段实际上不会报错,但如果发生类型不匹配等情况,就会抛出异常。

解析JSON请求体

Gin对于JSON请求体也封装了自动绑定功能,下面是一个例子:

type Student struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
engine.POST("/addStudent", func(context *gin.Context) {
    stu := Student{}
    err := context.ShouldBindJSON(&stu)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Printf("%v %v\n", stu.Name, stu.Age)
})

类似于表单绑定,我们首先在结构体的定义中使用了Tag语法标注了结构体字段和JSON字段的映射关系,对于JSON数据我们使用了ShouldBindJSON()方法进行绑定,这里如果JSON解析出错则会抛出异常。此外对于XML、YAML、TOML也是类似的,Gin对这几种常见数据交换格式都进行了支持。

当然,我们其实也可以字符串的形式取出请求数据,然后手动对JSON进行反序列化和结构体映射,但这样做代码就比较冗长了,一般不推荐这样写。

处理文件上传

HTTP协议的文件上传一般采用form-data格式的表单请求,Gin对于文件上传进行了完善的封装,我们不必自己操作流,直接调用相应的API保存文件即可,下面是一个例子。

engine.POST("/addStudent", func(context *gin.Context) {
    file, err := context.FormFile("file")
    if err != nil {
        fmt.Println(err)
        return
    }

    err = context.SaveUploadedFile(file, file.Filename)
    if err != nil {
        fmt.Println(err)
        return
    }
})

代码中,我们首先使用FormFile()获取表单中的文件,它返回一个*multipart.FileHeader对象,其中包含上传文件的信息如文件名、文件大小等;随后我们调用Gin提供的SaveUploadedFile()方法将上传的文件保存到本地,该方法接收2个参数,第1个参数为*multipart.FileHeader指针,第2个参数为文件名。

返回响应数据

返回JSON数据

Gin框架返回JSON十分简单,如果我们有一个结构体对象,直接使用context.JSON()方法返回即可,Gin框架会为对象进行序列化。

type Student struct {
    Name string `json:"name"`
    Age  int    `json:"age"`
}
engine.GET("/student", func(context *gin.Context) {
    s := Student{"Tom", 18}
    context.JSON(200, s)
})

如果我们还需要对JSON进行一些加工,可以直接使用Gin.H这个快捷类型对JSON数据进行封装。

engine.GET("/student", func(context *gin.Context) {
    s := Student{"Tom", 18}
    context.JSON(200, gin.H{
        "code":    200,
        "message": "success",
        "data":    s,
    })
})

这里可能很多人对gin.H比较费解,它其实就是一个自定义类型,其真实的类型是map[string]interface{},即键为string类型,值为任何类型的Map结构(Go语言中空接口interface{}可以代表任何类型)。这个类型我们可以将其理解为类似Java的Map<String, Object>

设置响应头

Gin框架在返回值中设置Header十分简单,调用Header()方法即可,下面是一个例子。

engine.GET("/student", func(context *gin.Context) {
    context.Header("Token", "abc123")
})

处理文件下载

Gin框架支持响应静态文件,静态文件的配置在前面路由的章节已经介绍过了,这里就不再赘述了。那么如何动态的返回一些二进制数据作为文件下载请求的结果呢?这就需要我们手动向输出流写数据了,下面是一个例子。

engine.GET("/image", func(context *gin.Context) {
    context.Header("Content-Disposition", "attachment; filename=data.jpg")
    file, err := os.Open("data.jpg")
    if err != nil {
        fmt.Println(err)
        return
    }

    bytes, err := io.ReadAll(file)
    if err != nil {
        fmt.Println(err)
        return
    }

    defer func(file *os.File) {
        err := file.Close()
        if err != nil {
            fmt.Println(err)
            return
        }
    }(file)

    _, err = context.Writer.Write(bytes)
    if err != nil {
        fmt.Println(err)
        return
    }

    context.Writer.Flush()
})

代码逻辑其实很简单,我们读取了文件data.jpg的二进制数据,并将其写入到输出流中。这里由于文件较小,我们是将数据全部读入内存,然后再写入输出流的,如果文件较大,则需要循环写入和读取。

作者:Gacfox
版权声明:本网站为非盈利性质,文章如非特殊说明均为原创,版权遵循知识共享协议CC BY-NC-ND 4.0进行授权,转载必须署名,禁止用于商业目的或演绎修改后转载。
Copyright © 2017-2024 Gacfox All Rights Reserved.
Build with NextJS | Sitemap