Gin(二):使用路由

更多文章 狂点ISLAND

Gin(二):路由

经过上一章节的介绍,搭建一个简单的 Gin web 项目非常容易,同时也引入了一些新的概念,比如说:路由 Router

路由是一个非常重要的概念,所有的接口都要有路由来进行管理。

请求方法

Gin 的路由支持 GET , POST , PUT , DELETE , PATCH , HEAD , OPTIONS 请求,同时还有一个 Any 函数,可以同时支持以上的所有请求。

将上一章节的代码添加其他请求方式的路由,并编写单元测试。

// 省略其他代码
    // 添加 Get 请求路由
    router.GET("/", func(context *gin.Context) {
        context.String(http.StatusOK, "hello gin get method")
    })
    // 添加 Post 请求路由
    router.POST("/", func(context *gin.Context) {
        context.String(http.StatusOK, "hello gin post method")
    })
    // 添加 Put 请求路由 
    router.PUT("/", func(context *gin.Context) {
        context.String(http.StatusOK, "hello gin put method")
    })
    // 添加 Delete 请求路由
    router.DELETE("/", func(context *gin.Context) {
        context.String(http.StatusOK, "hello gin delete method")
    })
    // 添加 Patch 请求路由
    router.PATCH("/", func(context *gin.Context) {
        context.String(http.StatusOK, "hello gin patch method")
    })
    // 添加 Head 请求路由
    router.HEAD("/", func(context *gin.Context) {
        context.String(http.StatusOK, "hello gin head method")
    })
    // 添加 Options 请求路由
    router.OPTIONS("/", func(context *gin.Context) {
        context.String(http.StatusOK, "hello gin options method")
    })
// 省略其他代码

单元测试,只展示一个 Post 请求函数,基本与 Get 请求一致,其他代码详见文末 Github 地址。

// router("/") post 测试
func TestIndexPostRouter(t *testing.T) {
    router := initRouter.SetupRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest(http.MethodPost, "/", nil)
    router.ServeHTTP(w, req)
    assert.Equal(t, http.StatusOK, w.Code)
    assert.Equal(t, "hello gin post method", w.Body.String())
}

此时运行单元测试,所有测试完美通过。

但是也有一个问题,所有的请求对应的路由内函数基本一样,只是有细微的差别,但是我们却每个路由里都完完整整的写了一遍,所以我们要将公共逻辑抽取出来。

func retHelloGinAndMethod(context *gin.Context) {
    context.String(http.StatusOK, "hello gin "+strings.ToLower(context.Request.Method)+" method")
}

我们将方法的公共部分抽取出来,并通过 context.Request.Method 将请求的方法提取出来 ,并将其转化为小写。此时就可以改造我们的路由了,将原有的路由中的函数去掉换成我们所编写的新的函数。

    // 添加 Get 请求路由
    router.GET("/", retHelloGinAndMethod)
    // 添加 Post 请求路由
    router.POST("/", retHelloGinAndMethod)
    // 添加 Put 请求路由
    router.PUT("/", retHelloGinAndMethod)
    // 添加 Delete 请求路由
    router.DELETE("/", retHelloGinAndMethod)
    // 添加 Patch 请求路由
    router.PATCH("/", retHelloGinAndMethod)
    // 添加 Head 请求路由
    router.HEAD("/", retHelloGinAndMethod)
    // 添加 Options 请求路由
    router.OPTIONS("/", retHelloGinAndMethod)

此时运行单元测试,仍旧是完美通过。

Handler 处理器

经过上面简单的例子的演示和操作,现在我们大概可以了解到路由需要传入两个参数,一个为路径,另一个为路由执行的方法,我们叫做它处理器 Handler ,而且,该参数是可变长参数。也就是说,可以传入多个 handler,形成一条 handler chain 。

同时对 handler 该函数有着一些要求,该函数需要传入一个 Gin.Context 指针,同时要通过该指针进行值得处理。

Handler 函数可以对前端返回 字符串,Json,Html 等多种格式或形式文件,之后我们会慢慢逐一介绍。

获取路由路径中参数

知道了路由支持的方法和对应的处理器,那么接下来就应该了解如何从路由中获取参数。

编写一个新的路由,如下:

//省略其他代码    
    // 添加 user
    router.GET("/user/:name",handler.Save)
// 省略其他代码

此时我们发现,在原来只有 / 分隔符的情况下出现了 /: 该符号就表示后面的字符串为一个占位符,用于将要进行的传值。,此时我们的路由为 /user/{name}

我们没有必要把所有的 Handler 都写到一个文件夹中,那样会臃肿不堪,所以我们新建一个文件夹handler,在文件夹下建立 userHandler.go 文件,编写该文件。

package handler

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

func UserSave(context *gin.Context) {
    username := context.Param("name")
    context.String(http.StatusOK, "用户已经保存")
}

同样,我们用 context.Param 可以获取路由路径中的参数。

此时,就可以编写我们的单元测试。

新建立一个 user_test.go 文件。

func TestUserSave(t *testing.T) {
    username := "lisi"
    router := initRouter.SetupRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest(http.MethodGet, "/user/"+username, nil)
    router.ServeHTTP(w, req)
    assert.Equal(t, http.StatusOK, w.Code)
    assert.Equal(t, "用户"+username+"已经保存", w.Body.String())
}

运行单元测试,测试通过。同样我们可以运行我们的项目在浏览器中输入 localhost:8080/user/lisi 在浏览器页面上也可以看到 用户lisi已经保存

当然,获取参数的方法不止这一个。针对不同的路由,Gin 给出了不同的获取参数的方法,比如形如:/user?name=lisi&age=18

我们再次添加一个 Handler,做为处理。在 userHandler 中添加下面的方法。

// 通过 query 方法进行获取参数
func UserSaveByQuery(context *gin.Context) {
    username := context.Query("name")
    age := context.Query("age")
    context.String(http.StatusOK, "用户:"+username+",年龄:"+age+"已经保存")
}

同时对路由进行添加和完善。

    router.GET("/user", handler.UserSaveByQuery)

完成路由之后,就可以重新编写单元测试,完善项目。

func TestUserSaveQuery(t *testing.T) {
    username := "lisi"
    age := 18
    router := initRouter.SetupRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest(http.MethodGet, "/user?name="+username+"&age="+strconv.Itoa(age), nil)
    router.ServeHTTP(w, req)
    assert.Equal(t, http.StatusOK, w.Code)
    assert.Equal(t, "用户:"+username+",年龄:"+strconv.Itoa(age)+"已经保存", w.Body.String())
}

运行测试,测试通过。并且可以通过 浏览器访问localhost:8080/user?name=lisi,页面上打印出用户:lisi,年龄:18已经保存

当然,还可以通过 context.DefaultQuery 方法,在获取时,如果没有该值则赋给一个默认值。

重新修改获取年龄的代码,将其改为以下代码

    age := context.DefaultQuery("age", "20")

重新编写我们的单元测试,并运行。

func TestUserSaveWithNotAge(t *testing.T) {
    username := "lisi"
    router := initRouter.SetupRouter()
    w := httptest.NewRecorder()
    req, _ := http.NewRequest(http.MethodGet, "/user?name="+username, nil)
    router.ServeHTTP(w, req)
    assert.Equal(t, http.StatusOK, w.Code)
    assert.Equal(t, "用户:"+username+",年龄:20已经保存", w.Body.String())
}

同样也可以通过浏览器访问 /user?name=lisi 可以看到浏览器上显示 用户:lisi,年龄:20已经保存

当然,还提供了其他参数获取方法, QueryArray 获取数组和 QueryMap 获取 map。

路由分组

此时我们再次看 SetupRouter 方法时,里面的路由基本可以分为两大类 //user,如果日后在进行功能的添加,那么势必会出现大量的路由,所以我们需要对路由进行一下管理,Gin 给我们提供了路由分组。

先把 / 的路由写到一起,运行 index_test.go 单元测试。

    index := router.Group("/")
    {
        // 添加 Get 请求路由
        index.GET("", retHelloGinAndMethod)
        // 添加 Post 请求路由
        index.POST("", retHelloGinAndMethod)
        // 添加 Put 请求路由
        index.PUT("", retHelloGinAndMethod)
        // 添加 Delete 请求路由
        index.DELETE("", retHelloGinAndMethod)
        // 添加 Patch 请求路由
        index.PATCH("", retHelloGinAndMethod)
        // 添加 Head 请求路由
        index.HEAD("", retHelloGinAndMethod)
        // 添加 Options 请求路由
        index.OPTIONS("", retHelloGinAndMethod)
    }

通过 router.Group 返回一个新的分组路由,通过新的分组路由把之前的路由进行简单的修改。当然分组里面仍旧可以嵌套分组。

之前在请求方法中说到有一个 Any 函数可以通过任何请求,此时我们就可以把 index 里面所有的请求替换为 Any

    index := router.Group("/")
    {
        index.Any("", retHelloGinAndMethod)
    }

运行单元测试,测试都可以通过。

此时也发现了单元测试的好处,虽说之前花费了时间和经历编写了单元测试,但是日后的功能上修改,只需要进行运行单元测试就可以知道我们的功能是否正确,在后期的功能测试上大大减少了经历和时间。

同样我们把 user 也进行分组,分组不仅仅是将相同逻辑的代码放到一起,而且可以提供相同的路由前缀,修改后的路由仍旧和之前一致。运行单元测试 user_test.go ,单元测试可以完全通过,证明我们的代码没有问题。

    userRouter := router.Group("/user")
    {
        userRouter.GET("/:name", handler.UserSave)
        userRouter.GET("", handler.UserSaveByQuery)
    }

总结

通过简单的路由的使用,基本明白了路由在 Gin 中的地位,也对一些常见的使用方式有了一些直观的认识。

本章节代码

Github

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容