• gin框架练习笔记


    本文主要记录一下自己学习gin框架过程中亲自写的一些练习与笔记,温故而知新。

    全部是参考这篇博客:https://www.liwenzhou.com/posts/Go/Gin_framework/#autoid-0-8-3

    另外,所有的代码均使用testing实现,需要将go文件的名称命名为xxx_test.go。

    使用gin.H发送嵌套字典的响应

    有时候需要发送字典嵌套的响应,像这样:

    实际上可以这样写返回的响应:

    // home路由
    func homeHandler(c *gin.Context){
        // 获取参数
        username := c.MustGet("username").(string)
    
        // 返回响应
        c.JSON(http.StatusOK, gin.H{
            "code": 2000,
            "msg": "success",
            "data": gin.H{"username":username},
        })
    }

    基本请求与响应的处理以及启动服务

    package t_ginProjets
    
    import (
        "github.com/gin-gonic/gin"
        "github.com/gin-gonic/gin/testdata/protoexample"
        "net/http"
        "testing"
    )
    
    // 视图函数 GET请求
    func hello(c *gin.Context) {
        // c.JSON,返回json格式的数据
        // TODO 返回JSON的方式一:自己拼接JSON
        c.JSON(http.StatusOK, gin.H{
            "name":    "whw",
            "message": "hello world!",
        })
    }
    
    // POST请求
    func postMethod(c *gin.Context) {
        // TODO 返回JSON的方式二:使用结构体
        var msg struct {
            Name    string `json:"post_name"` // 注意最终json的变量名是后面的!
            Age     int    `json:"post_age"`
            Message string `json:"post_message"`
        }
        // 创建一个结构体对象
        msg.Name = "whw"
        msg.Age = 18
        msg.Message = "666666"
        // TODO 直接将结构体对象传入参数中即可
        c.JSON(200, msg)
    }
    
    // PUT请求
    func putMethod(c *gin.Context) {
        c.JSON(200, gin.H{
            "name": "putMethod",
            "msg":  "hello put",
        })
    }
    
    // DELETE请求
    func deleteMethod(c *gin.Context) {
        c.JSON(200, gin.H{
            "name": "deleteMethod",
            "msg":  "hello delete",
        })
    }
    
    // TODO 简单的请求与响应处理 + json渲染 + protobuf渲染 + 启动服务
    func TestGinReq(t *testing.T) {
        // 路由引擎
        r := gin.Default()
        // GET
        r.GET("/hello", hello)
        // POST
        r.POST("/post", postMethod)
        // PUT 传入匿名函数的方式
        r.PUT("/put", func(c *gin.Context) {
            c.JSON(200, gin.H{
                "name": "myPutMethod",
                "msg":  "HELLO PUT",
            })
        })
        // DELETE
        r.DELETE("/delete", deleteMethod)
    
        // protobuf渲染
        r.GET("/someProtoBuf", func(c *gin.Context) {
            reps := []int64{int64(1), int64(2)}
            label := "test"
            // protobuf 的具体定义写在 testdata/protoexample 文件中。
            data := &protoexample.Test{
                Label: &label,
                Reps:  reps,
            }
            // 请注意,数据在响应中变为二进制数据
            // 将输出被 protoexample.Test protobuf 序列化了的数据
            c.ProtoBuf(http.StatusOK, data)
        })
    
        // 启动HTTP服务  什么都不写默认是 0.0.0.0:8080
        r.Run("127.0.0.1:9000")
    
    }
    View Code

    获取参数

    获取querystring、form、path中的参数

    package t_ginProjets
    
    import (
        "github.com/gin-gonic/gin"
        "net/http"
        "testing"
    )
    
    // TODO 1、获取querystring参数
    // /user/search?username=whw&address=China
    func queryString(c *gin.Context){
        username := c.DefaultQuery("username","")
        address := c.DefaultQuery("address","")
        // 将结果返回给调用方
        c.JSON(http.StatusOK,gin.H{
            "message": "OK",
            "username": username,
            "address": address,
        })
    }
    func TestQueryString(t *testing.T){
        // Default返回一个默认的路由引擎
        r := gin.Default()
    
        r.GET("/user/search",queryString)
    
        r.Run("127.0.0.1:9000")
    
    }
    
    // TODO 2、获取form参数
    // 请求的数据通过form表单来提交,例如向/user/search发送一个POST请求,获取请求数据的方式如下
    func getFormData(c *gin.Context){
        // DefaultPostForm取不到值时会返回指定的默认值
        username := c.DefaultPostForm("username","")
        password := c.DefaultPostForm("password","")
        // 返回结果给调用方
        c.JSON(http.StatusOK,gin.H{
            "message": "OK",
            "username": username,
            "password": password,
        })
    
    }
    func TestFormData(t *testing.T){
        // 默认引擎
        r := gin.Default()
    
        r.POST("/user/search",getFormData)
    
        r.Run("127.0.0.1:9000")
    
    }
    
    // TODO 3、获取path参数
    // 请求的参数通过URL路径传递,例如:/user/search/whw/China: 获取请求URL路径中的参数的方式如下。
    func GetPathParams(c *gin.Context){
        username := c.Param("username")
        address := c.Param("address")
        // 输出json
        c.JSON(http.StatusOK,gin.H{
            "msg":"OK",
            "username":username,
            "address":address,
        })
    }
    func TestGetParams(xxx *testing.T){
        // 引擎
        r := gin.Default()
    
        r.GET("/user/search/:username/:address",GetPathParams)
    
        r.Run("127.0.0.1:9000")
    
    }
    ginParams_test.go

    参数绑定 

    为了能够更方便的获取请求相关参数,提高开发效率。
    我们可以基于请求的Content-Type识别请求数据类型并利用反射机制自动提取请求中QueryString、form表单、JSON、XML等参数到结构体中。
    下面的示例代码演示了.ShouldBind()强大的功能,它能够基于请求自动提取JSON、form表单和QueryString类型的数据,
    并把值绑定到指定的结构体对象。
    shouldBind会按照下面的顺序解析请求中的数据完成绑定:
    1、如果是 GET 请求,只使用 Form 绑定引擎(query)。
    2、如果是 POST 请求,首先检查 content-type 是否为 JSON 或 XML,然后再使用 Form(form-data)。
    package t_ginProjets
    
    import (
        "fmt"
        "github.com/gin-gonic/gin"
        "net/http"
        "testing"
    )
    
    // TODO 参数绑定: ShouldBind
    type Login struct {
        User string `form:"user" json:"user" binding:"required"`
        Password string `form:"password" json:"password" binding:"required"`
    }
    
    // 1 绑定JSON的示例:{"user": "whw", "password": "123456"}
    func bindJsonTest(c *gin.Context){
        var login Login
    
        if err := c.ShouldBind(&login); err == nil{
            // 打印
            fmt.Printf("login info:%#v 
    ", login)
            // 返回给调用者
            c.JSON(http.StatusOK,gin.H{
                "user": login.User,
                "password": login.Password,
            })
        }else{
            c.JSON(http.StatusBadRequest,gin.H{
                "error": err.Error(),
            })
        }
    }
    
    // 2 绑定form表单的示例:user=whw&password=123456
    func bindFormTest(c *gin.Context){
        var login Login
    
        // ShouldBind()会根据请求的Content-Type自行选择绑定器
        if err := c.ShouldBind(&login); err == nil{
            c.JSON(http.StatusOK,gin.H{
                "user": login.User,
                "password": login.Password,
            })
        }else{
            c.JSON(http.StatusBadRequest,gin.H{
                "error": err.Error(),
            })
        }
    }
    
    // 3、绑定QueryString示例 (/loginQuery?user=q1mi&password=123456)
    func bindQueryStringTest(c *gin.Context){
        var login Login
    
        // ShouldBind()会根据请求的Content-Type自行选择绑定器
        if err := c.ShouldBind(&login); err == nil{
            c.JSON(http.StatusOK, gin.H{
                "user": login.User,
                "password": login.Password,
            })
        }else{
            c.JSON(http.StatusBadRequest, gin.H{
                "error": err.Error(),
            })
        }
    }
    
    func TestParams(t *testing.T){
        // 默认引擎
        r := gin.Default()
    
        // JSON的示例
        r.POST("/loginJson", bindJsonTest)
    
        // form表单示例
        r.POST("/loginForm", bindFormTest)
    
        // QueryString示例
        r.GET("/loginQueryString",bindQueryStringTest)
    
        r.Run("127.0.0.1:9090")
    }
    xxxx_test.go

    文件上传

    package t_ginProjets
    
    import (
        "fmt"
        "github.com/gin-gonic/gin"
        "log"
        "net/http"
        "testing"
    )
    
    // TODO 文件上传
    
    // TODO 单个文件上传
    
    // 前端代码
    /*
    <!DOCTYPE html>
    <html lang="zh-CN">
    <head>
        <title>上传文件示例</title>
    </head>
    <body>
    <form action="/upload" method="post" enctype="multipart/form-data">
        <input type="file" name="f1">
        <input type="submit" value="上传">
    </form>
    </body>
    </html>
    */
    
    func TestUploadSingleFile(t *testing.T) {
        router := gin.Default()
        // 处理multipart forms提交文件时默认的内存限制是32 MiB
        // 可以通过下面的方式修改
        // router.MaxMultipartMemory = 8 << 20  // 8 MiB
        router.POST("/upload", func(c *gin.Context) {
            // 单个文件
            file, err := c.FormFile("f1")
            if err != nil {
                c.JSON(http.StatusInternalServerError, gin.H{
                    "message": err.Error(),
                })
                return
            }
    
            log.Println(file.Filename)
            dst := fmt.Sprintf("~/Downloads/%s", file.Filename)
            // 上传文件到指定的目录
            c.SaveUploadedFile(file, dst)
            c.JSON(http.StatusOK, gin.H{
                "message": fmt.Sprintf("'%s' uploaded!", file.Filename),
            })
        })
        router.Run("127.0.0.1:9800")
    }
    
    
    // TODO 多个文件上传
    func TestUploadmutiFiles(t *testing.T) {
        router := gin.Default()
        // 处理multipart forms提交文件时默认的内存限制是32 MiB
        // 可以通过下面的方式修改
        // router.MaxMultipartMemory = 8 << 20  // 8 MiB
        router.POST("/upload", func(c *gin.Context) {
            // Multipart form
            form, _ := c.MultipartForm()
            files := form.File["file"]
    
            for index, file := range files {
                log.Println(file.Filename)
                dst := fmt.Sprintf("~/Downloads/%s_%d", file.Filename, index)
                // 上传文件到指定的目录
                c.SaveUploadedFile(file, dst)
            }
            c.JSON(http.StatusOK, gin.H{
                "message": fmt.Sprintf("%d files uploaded!", len(files)),
            })
        })
        router.Run()
    }
    View Code

    重定向

    package t_ginProjets
    
    import (
        "github.com/gin-gonic/gin"
        "net/http"
        "testing"
    )
    
    // TODO 1、HTTP重定向
    // HTTP 重定向很容易。 内部、外部重定向均支持。
    
    func TestHttpRedirect(t *testing.T){
        r := gin.Default()
    
        r.GET("/baidu",func(c *gin.Context){
            c.Redirect(http.StatusMovedPermanently, "https://www.baidu.com")
        })
    
        r.Run("127.0.0.1:9000")
    }
    
    // TODO 2、路由重定向
    // 路由重定向,使用HandleContext
    func TestRouterRedirect(t *testing.T){
        // 默认引擎
        r := gin.Default()
        // test1的路由
        r.GET("/test1",func(c *gin.Context){
            c.JSON(http.StatusOK,gin.H{
                "msg":"i am test1!",
            })
        })
        // test2的路由 重定向到test1
        r.GET("/test2",func(c *gin.Context){
            // 指定重定向的URL
            c.Request.URL.Path = "/test2"
            r.HandleContext(c)
        })
        // 启动服务
        r.Run("127.0.0.1:9001")
    }
    View Code

    gin路由相关的方法

    package t_ginProjets
    
    import (
        "github.com/gin-gonic/gin"
        "net/http"
        "testing"
    )
    
    // Gin的路由
    /*
    Gin框架中的路由使用的是httprouter(https://github.com/julienschmidt/httprouter)这个库。
    其基本原理就是构造一个路由地址的前缀树。
    */
    
    // TODO 1、普通路由
    /*
        r.GET("/index", func(c *gin.Context) {...})
        r.GET("/login", func(c *gin.Context) {...})
        r.POST("/login", func(c *gin.Context) {...})
    */
    
    // TODO 2、匹配所有请求的方法
    /*
        r.Any("/test", func(c *gin.Context) {...})
    */
    
    // TODO 3、为没有配置处理函数的路由添加处理程序,默认情况下它返回404代码,下面的代码为没有匹配到路由的请求都返回views/404.html页面。
    /*
        r.NoRoute(func(c *gin.Context) {
            c.HTML(http.StatusNotFound, "views/404.html", nil)
        })
    */
    
    // TODO *** 4、路由组: 通常我们将路由分组用在 "划分业务逻辑" 或 "划分API版本" 时。
    /*
    我们可以将拥有共同URL前缀的路由划分为一个路由组。
    习惯性一对{}包裹同组的路由,这只是为了看着清晰,你用不用{}包裹功能上没什么区别。
    */
    func TestRouterGroup(t *testing.T){
        // 定义默认引擎
        r := gin.Default()
    
        // 路由组 userGroup
        userGroup := r.Group("/user")
        {
            // /user/index
            userGroup.GET("/index",func(c *gin.Context){
                c.JSON(http.StatusOK,gin.H{
                    "msg":"user.index",
                })
            })
            // /user/login
            userGroup.GET("/login",func(c *gin.Context){
                c.JSON(http.StatusOK,gin.H{
                    "msg":"user.login",
                })
            })
        }
    
        // 路由组 shopGroup
        shopGroup := r.Group("/shop")
        {
            // shop/index
            shopGroup.GET("/index",func(c *gin.Context){
                c.JSON(http.StatusOK,gin.H{
                    "msg":"shop.index",
                })
            })
            // shop/login
            shopGroup.GET("/login",func(c *gin.Context){
                c.JSON(http.StatusOK,gin.H{
                    "msg":"shop.login",
                })
            })
        }
    
        // 启动服务
        r.Run("127.0.0.1:9900")
    }
    
    // TODO 5、嵌套路由组
    /*
    shopGroup := r.Group("/shop")
        {
            shopGroup.GET("/index", func(c *gin.Context) {...})
            shopGroup.GET("/cart", func(c *gin.Context) {...})
            shopGroup.POST("/checkout", func(c *gin.Context) {...})
            // TODO 嵌套路由组
            xx := shopGroup.Group("xx")
            xx.GET("/oo", func(c *gin.Context) {...})
        }
    */
    router_test.go

    gin的中间件

    Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。
    这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

    中间件注意事项

    1、默认中间件

    gin.Default()默认使用了Logger和Recovery中间件,其中:
    - Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。
    - Recovery中间件会recover任何panic。如果有panic的话,会写入500响应码。

    如果不想使用上面两个默认的中间件,可以使用gin.New()新建一个没有任何默认中间件的路由。

    2、gin中间件中使用goroutine
    当在中间件或handler中启动新的goroutine时,
    不能使用原始的上下文(c *gin.Context),必须使用其只读副本(c.Copy())。

    代码

    package t_ginProjets
    
    import (
        "github.com/gin-gonic/gin"
        "log"
        "net/http"
        "testing"
        "time"
    )
    
    // Gin 中间件
    
    
    // TODO 1、定义中间件
    // Gin中的中间件必须是一个gin.HandlerFunc类型。
    // 例如我们像下面的代码一样定义一个统计请求耗时的中间件。
    func StatCost() gin.HandlerFunc{
        return func(c *gin.Context){
            start := time.Now()
            // // 可以通过c.Set在请求上下文中设置值,后续的处理函数能够取到该值
            c.Set("name","naruto")
            // 调用该请求的剩余处理程序
            c.Next()
    
            // 不调用该请求的剩余处理程序!
            // 注意:加上的话,该中间件后的请求逻辑不会处理!
            // c.Abort()
    
            // 计算耗时
            cost := time.Since(start)
            log.Println("耗时: ",cost)
        }
    }
    
    // TODO 2、注册中间件
    // 在gin框架中,我们可以为每个路由添加任意数量的中间件。
    // TODO 2.1、为全局路由注册
    func TestRegisterGlobalMiddleWare(t *testing.T){
        // 新建一个没有任何中间件的路由
        r := gin.New()
        // 注册一个全局中间件
        r.Use(StatCost())
    
        r.GET("/middleware_test1", func(c *gin.Context){
            // 从上下文取值
            name := c.MustGet("name").(string)
            log.Println("上下文的name: ",name)
    
            // sleep 1秒
            time.Sleep(time.Second)
    
            c.JSON(http.StatusOK, gin.H{
                "message": "Hello World!",
            })
        })
    
        r.Run("127.0.0.1:9002")
    }
    
    // TODO 2.2、为某个路由单独注册中间件(当然可以注册多个)
    // 再定义一个只打印的中间件
    func JustPrint() gin.HandlerFunc {
        return func(c *gin.Context){
            // 设置另外一个值
            c.Set("middleware2",666)
            log.Println("自定义中间件 justPrint...")
        }
    }
    func TestRegisterRouterMiddleWare(t *testing.T){
        r := gin.New()
    
        // 给单独的路由注册中间件
        r.GET("/middleware_test2",StatCost(), JustPrint(),func(c *gin.Context){
            // 从上下文取值
            midd2 := c.MustGet("middleware2").(int)
            name := c.MustGet("name").(string)
            log.Println("midd2>>> ",midd2)
    
            // 返回响应
            c.JSON(http.StatusOK,gin.H{
                "middle2": midd2,
                "name": name,
            })
        })
    
        r.Run("127.0.0.1:9003")
    }
    
    // TODO 3、为路由组注册中间件
    // TODO 3.1、写法1:
    /*
        shopGroup := r.Group("/shop", StatCost())
        {
        shopGroup.GET("/index", func(c *gin.Context) {...})
        ...
        }
     */
    // TODO 3.2、写法2(在路由组中注册中间件):
    /*
        shopGroup := r.Group("/shop")
        shopGroup.Use(StatCost())
        {
            shopGroup.GET("/index", func(c *gin.Context) {...})
            ...
        }
    */
    middleware_test.go

    运行多个服务器

    package t_ginProjets
    
    import (
        "log"
        "net/http"
        "testing"
        "time"
        "github.com/gin-gonic/gin"
        "golang.org/x/sync/errgroup"
    )
    
    // TODO 运行多个服务
    //我们可以在多个端口启动服务
    
    
    var (
        g errgroup.Group
    )
    
    func router01() http.Handler {
        e := gin.New()
        e.Use(gin.Recovery())
        e.GET("/", func(c *gin.Context) {
            c.JSON(
                http.StatusOK,
                gin.H{
                    "code":  http.StatusOK,
                    "error": "Welcome server 01",
                },
            )
        })
    
        return e
    }
    
    func router02() http.Handler {
        e := gin.New()
        e.Use(gin.Recovery())
        e.GET("/", func(c *gin.Context) {
            c.JSON(
                http.StatusOK,
                gin.H{
                    "code":  http.StatusOK,
                    "error": "Welcome server 02",
                },
            )
        })
    
        return e
    }
    
    func TestRunServers(t *testing.T) {
        server01 := &http.Server{
            Addr:         "127.0.0.1:8080",
            Handler:      router01(),
            ReadTimeout:  5 * time.Second,
            WriteTimeout: 10 * time.Second,
        }
    
        server02 := &http.Server{
            Addr:         "127.0.0.1:8081",
            Handler:      router02(),
            ReadTimeout:  5 * time.Second,
            WriteTimeout: 10 * time.Second,
        }
        // 借助errgroup.Group或者自行开启两个goroutine分别启动两个服务
        g.Go(func() error {
            return server01.ListenAndServe()
        })
    
        g.Go(func() error {
            return server02.ListenAndServe()
        })
    
        if err := g.Wait(); err != nil {
            log.Fatal(err)
        }
    }
    runServers_test.go

    ~~~

  • 相关阅读:
    jQuery对象和DOM对象的互转
    ASP.NET MVC单元测试Controller时添加上下文的方式
    基于Mongodb进行分布式数据存储【转】
    .NET平台上的编译器不完全列表
    实现自己的O/R Mapping组件高效缓存的思考
    .NET的动态语言
    C++类成员和数据成员初始化总结
    ACE Singleton
    Wiresshark
    C++
  • 原文地址:https://www.cnblogs.com/paulwhw/p/14102821.html
Copyright © 2020-2023  润新知