1. 第一个Gin服务中的路由
我们的第一个gin服务源码如下:
packagemainimport("net/http"// 导入gin框架"github.com/gin-gonic/gin")funcmain(){// 创建默认的gin路由router:=gin.Default()// 定义一个简单的GET端点router.GET("/ping",func(c*gin.Context){// 返回JSON数据c.JSON(http.StatusOK,gin.H{"message":"pong",})})// 启动服务(默认端口8080)err:=router.Run()iferr!=nil{return}}代码中,我们使用router.GET方法为我们的gin服务定义了一个简单的/ping路由。
2. 设置路由Method
除了GET方式的路由外,Gin框架支持定义各种HTTP Method的路由!
2.1. GET、POST、PUT…
Gin框架直接提供了大部分HTTP Method的路由定义方法:
- GET
- POST
- PUT
- DELETE
- PATCH
- OPTIONS
- HEAD
示例:
funcNewHttpRouter()*gin.Engine{router:=gin.Default()router.GET("/ping",handler.GetPing)router.POST("/ping",handler.PostPing)router.PUT("/ping",handler.PutPing)router.DELETE("/ping",handler.DeletePing)router.PATCH("/ping",handler.PatchPing)router.OPTIONS("/ping",handler.OptionsPing)router.HEAD("/ping",handler.HeadPing)returnrouter}注意:使用这些方法定义的路由,只能使用对应的HTTP Method访问。如果使用错误的HTTP Method访问接口,默认会404。
2.2. 匹配所有Methods(Any)
Gin框架提供了Any方法来定义匹配所有HTTP Methods的路由。
源码:
func(group*RouterGroup)Any(relativePathstring,handlers...HandlerFunc)IRoutes{// 可以看到,实际上是为anyMethods中定义的所有HTTP Method都注册了路由for_,method:=rangeanyMethods{group.handle(method,relativePath,handlers)}returngroup.returnObj()}示例:
funcNewHttpRouter()*gin.Engine{router:=gin.Default()router.Any("/ping/any",handler.AnyPing)returnrouter}2.3. 更灵活的Method(Handle)
Handle方法以指定的路径和method注册路由。
- 对于 GET、POST、PUT、PATCH 和 DELETE 请求,可以使用相应的快捷函数。
Handle方法旨在用于批量加载,且它运行使用不常用的、非标准化的或者自定义的HTTP Method。
Handle方法源码如下:
func(group*RouterGroup)Handle(httpMethod,relativePathstring,handlers...HandlerFunc)IRoutes{// 可以看到,http method只做了简单的正则校验ifmatched:=regEnLetter.MatchString(httpMethod);!matched{panic("http method "+httpMethod+" is not valid")}returngroup.handle(httpMethod,relativePath,handlers)}示例:
funcNewHttpRouter()*gin.Engine{router:=gin.Default()// look,这里的HTTP Method竟然是HELLOrouter.Handle("HELLO","/ping",handler.HelloPing)returnrouter}2.4. 匹配多种Methods(Match)
Match方法用于定义匹配多种HTTP Methods的路由。
源码:
// 可以看到methods参数是一个数组func(group*RouterGroup)Match(methods[]string,relativePathstring,handlers...HandlerFunc)IRoutes{for_,method:=rangemethods{group.handle(method,relativePath,handlers)}returngroup.returnObj()}示例:
funcNewHttpRouter()*gin.Engine{router:=gin.Default()// 这里匹配GET和POSTrouter.Match([]string{"GET","POST"},"/match",handler.MatchPing)returnrouter}2.5. MethodNotAllowed处理
2.5.1. 默认行为404
在上面的章节中,我们介绍了各种定义路由HTTP Method的方式。
当我们定义了一个路由应该使用的HTTP Method后,如果请求的HTTP Method不匹配,默认会应答404.
示例:
router.GET("/ping",handler.GetPing)// 使用GET方式访问:正常应答// 使用POST方式访问:应答4042.5.2. HandleMethodNotAllowed配置
gin.Engine中有一个HandleMethodNotAllowed属性,可以用于修改Method不匹配时的默认行为。
// HandleMethodNotAllowed if enabled, the router checks if another method is allowed for the// current route, if the current request can not be routed.// If this is the case, the request is answered with 'Method Not Allowed'// and HTTP status code 405.// If no other Method is allowed, the request is delegated to the NotFound// handler.HandleMethodNotAllowedbool- 当HandleMethodNotAllowed开启时,如果请求的路径存在但HTTP方法不匹配,gin会检查该路径是否注册了其他HTTP方法。如果存在其他方法,则返回405(Method Not Allowed);如果该路径没有任何方法注册,则仍然返回404(Not Found)。
示例:
router.HandleMethodNotAllowed=truerouter.GET("/ping",handler.GetPing)// GET方式访问/ping:正常应答// POST方式访问/ping:应答4052.5.3 自定义MethodNotAllowed处理
上文提到,如果请求路径正确但请求Method不匹配(MethodNotAllowed场景):
- 如果HandleMethodNotAllowed=false:默认应答404(Not Found)。
- 如果HandleMethodNotAllowed=true:默认应答405(Method Not Allowed)。
除此之外,gin还提供了NoMethod方法来自定义处理逻辑:
// 用于设置当 Engine.HandleMethodNotAllowed = true 时调用的处理程序。func(engine*Engine)NoMethod(handlers...HandlerFunc){engine.noMethod=handlers engine.rebuild405Handlers()}示例:
funcNewHttpRouter()*gin.Engine{router:=gin.Default()// 开启HandleMethodNotAllowedrouter.HandleMethodNotAllowed=truerouter.GET("/ping",handler.GetPing)// 定义处理函数router.NoMethod(handler.NoMethodPing)returnrouter}funcNoMethodPing(c*gin.Context){// 通常应返回405状态码,但可根据需求自定义c.JSON(http.StatusMethodNotAllowed,gin.H{"message":"method not allowed",})}// GET方式访问/ping:正常应答// POST方式访问/ping:no method pong3. 路由组
3.1. 路由组
gin框架支持路由分组:即将具有相同路径前缀或者需要相同中间件的路由放在同一个路由组中。
Group方法用于路由分组,其源码如下:
// Group creates a new router group. You should add all the routes that have common middlewares or the same path prefix.// For example, all the routes that use a common middleware for authorization could be grouped.func(group*RouterGroup)Group(relativePathstring,handlers...HandlerFunc)*RouterGroup{return&RouterGroup{Handlers:group.combineHandlers(handlers),basePath:group.calculateAbsolutePath(relativePath),engine:group.engine,}}Group方法用于创建一个新的路由组:
- 参数
relativePath用于定义组中路由的通用路径。 - 参数
handlers用于定义组中路由的通用中间件。 Group方法创建一个新的路由组,我们可以将具有相同路径前缀或者要使用相同中间件的路由划分为一个组。
示例:
funcNewHttpRouter()*gin.Engine{router:=gin.Default()testRouter:=router.Group("/test"){testRouter.Any("/ping",handler.TestPing)testRouter.Any("/hello",handler.TestHello)}platRouter:=router.Group("/plat",middleware.AuthMiddleware()){platRouter.Any("/login",handler.PlatLogin)platRouter.Any("/logout",handler.PlatLogout)}returnrouter}// 上面接口的访问路径分别为// /test/ping// /test/hello// /plat/login// /plat/logout3.2. 嵌套路由组
Gin框架支持多级路由组嵌套,允许更细粒度的路由组织。例如:
funcNewHttpRouter()*gin.Engine{router:=gin.Default()api:=router.Group("/api"){v1:=api.Group("/v1"){v1.GET("/users",handler.GetUsers)v1.POST("/users",handler.CreateUser)}v2:=api.Group("/v2"){v2.GET("/users",handler.GetUsersV2)}}returnrouter}这样,/api/v1/users和/api/v2/users将分别由不同的处理函数处理。
此外,父路由组的中间件会自动应用到所有子路由组。例如,如果api组配置了认证中间件,则v1和v2组的所有路由都将应用该中间件。
4. 最佳实践
在实际项目开发中,合理使用Gin框架的路由和路由组功能能够大大提高代码的可维护性和可扩展性。以下是一些推荐的最佳实践:
4.1. 按业务模块划分路由组
将不同业务模块的路由划分到不同的路由组中,有助于保持代码结构清晰:
funcSetupRoutes(router*gin.Engine){// 用户相关路由userGroup:=router.Group("/user"){userGroup.POST("/register",user.Register)userGroup.POST("/login",user.Login)userGroup.GET("/profile",authMiddleware(),user.GetProfile)}// 订单相关路由orderGroup:=router.Group("/order"){orderGroup.GET("/:id",order.GetOrder)orderGroup.POST("/",authMiddleware(),order.CreateOrder)orderGroup.PUT("/:id",authMiddleware(),order.UpdateOrder)}// 管理后台路由adminGroup:=router.Group("/admin",adminAuthMiddleware()){adminGroup.GET("/dashboard",admin.Dashboard)adminGroup.GET("/users",admin.ListUsers)adminGroup.DELETE("/users/:id",admin.DeleteUser)}}4.2. 合理使用中间件
在路由组上应用共享的中间件,避免重复代码:
// API版本控制v1:=router.Group("/api/v1")v1.Use(loggingMiddleware(),corsMiddleware())authGroup:=v1.Group("/auth")authGroup.Use(rateLimitMiddleware())// 特定路由组的额外中间件{authGroup.POST("/login",auth.Login)authGroup.POST("/register",auth.Register)}userGroup:=v1.Group("/user",authRequiredMiddleware())// 需要认证的路由组{userGroup.GET("/profile",user.GetProfile)userGroup.PUT("/profile",user.UpdateProfile)}4.3. 统一错误处理
通过NoRoute和NoMethod统一处理404和405错误:
funcSetupRouter()*gin.Engine{router:=gin.Default()// 全局404处理router.NoRoute(func(c*gin.Context){c.JSON(http.StatusNotFound,gin.H{"error":"Page not found","path":c.Request.URL.Path,})})// 全局405处理router.HandleMethodNotAllowed=truerouter.NoMethod(func(c*gin.Context){c.JSON(http.StatusMethodNotAllowed,gin.H{"error":"Method not allowed","method":c.Request.Method,"path":c.Request.URL.Path,})})// 路由定义...returnrouter}4.4. RESTful风格设计
遵循RESTful API设计原则,合理使用HTTP方法:
// 推荐的RESTful风格路由设计api:=router.Group("/api/v1"){// 资源集合操作api.GET("/posts",post.ListPosts)// 获取文章列表api.POST("/posts",post.CreatePost)// 创建新文章// 单个资源操作api.GET("/posts/:id",post.GetPost)// 获取特定文章api.PUT("/posts/:id",post.UpdatePost)// 完整更新文章api.PATCH("/posts/:id",post.PatchPost)// 部分更新文章api.DELETE("/posts/:id",post.DeletePost)// 删除文章// 子资源操作api.GET("/posts/:id/comments",comment.ListComments)// 获取文章评论api.POST("/posts/:id/comments",comment.CreateComment)// 添加评论}4.5. 路由版本控制
通过路由组实现API版本控制:
funcSetupRoutes(router*gin.Engine){// v1版本APIv1:=router.Group("/api/v1"){v1.GET("/users",userHandler.ListUsersV1)v1.GET("/users/:id",userHandler.GetUserV1)}// v2版本APIv2:=router.Group("/api/v2"){v2.GET("/users",userHandler.ListUsersV2)v2.GET("/users/:id",userHandler.GetUserV2)v2.GET("/users/:id/profile",userHandler.GetUserProfileV2)}}4.6. 参数验证和路由分离
将路由定义与业务逻辑分离,使代码更加清晰:
// routes/setup.gofuncSetupUserRoutes(router*gin.Engine){userGroup:=router.Group("/users"){userGroup.GET("/",listUsers)userGroup.POST("/",createUser)userGroup.GET("/:id",getUser)userGroup.PUT("/:id",updateUser)userGroup.DELETE("/:id",deleteUser)}}// handlers/user_handler.gofunclistUsers(c*gin.Context){// 处理获取用户列表逻辑}funccreateUser(c*gin.Context){// 处理创建用户逻辑}funcgetUser(c*gin.Context){id:=c.Param("id")// 处理获取单个用户逻辑}funcupdateUser(c*gin.Context){id:=c.Param("id")// 处理更新用户逻辑}funcdeleteUser(c*gin.Context){id:=c.Param("id")// 处理删除用户逻辑}