如何使用 Gin Web 框架将参数传递给 Golang 中的路由器处理程序?
How to pass arguments to router handlers in Golang using Gin web framework?
我正在使用 Gin,https://gin-gonic.github.io/gin/,用 Golang 构建一个简单的 RESTful JSON API。
路由设置如下:
func testRouteHandler(c *gin.Context) {
// do smth
}
func main() {
router := gin.Default()
router.GET("/test", testRouteHandler)
router.Run(":8080")
}
我的问题是如何将参数传递给 testRouteHandler 函数?例如,一个公共数据库连接可能是人们想要在路由之间重用的东西。
在全局变量中使用它是最好的方法吗?或者在 Go 中是否有某种方法可以将一个额外的变量传递给 testRouteHandler 函数? Go 中的函数有可选参数吗?
PS。我刚刚开始学习 Go,所以我可能缺少一些明显的东西:)
使用我在评论中发布的 link,我创建了一个简单的示例。
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
// ApiMiddleware will add the db connection to the context
func ApiMiddleware(db gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("databaseConn", db)
c.Next()
}
}
func main() {
r := gin.New()
// In this example, I'll open the db connection here...
// In your code you would probably do it somewhere else
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
r.Use(ApiMiddleware(db))
r.GET("/api", func(c *gin.Context) {
// Don't forget type assertion when getting the connection from context.
dbConn, ok := c.MustGet("databaseConn").(gorm.DB)
if !ok {
// handle error here...
}
// do your thing here...
})
r.Run(":8080")
}
这只是一个简单的 POC。但我相信这是一个开始。
希望对你有帮助。
我会避免将 'application scoped' 依赖项(例如数据库连接池)填充到请求上下文中。您的两个 'easiest' 选项是:
- 让它成为一个全球性的。这适用于较小的项目,并且
*sql.DB
是线程安全的。
- 在闭包中显式传递它,使 return 类型满足
gin.HandlerFunc
例如
// SomeHandler returns a `func(*gin.Context)` to satisfy Gin's router methods
// db could turn into an 'Env' struct that encapsulates all of your
// app dependencies - e.g. DB, logger, env vars, etc.
func SomeHandler(db *sql.DB) gin.HandlerFunc {
fn := func(c *gin.Context) {
// Your handler code goes in here - e.g.
rows, err := db.Query(...)
c.String(200, results)
}
return gin.HandlerFunc(fn)
}
func main() {
db, err := sql.Open(...)
// handle the error
router := gin.Default()
router.GET("/test", SomeHandler(db))
router.Run(":8080")
}
好了,我给你举了一个简单的例子。它应该工作。您可以根据需要扩展它
func main() {
router := gin.Default()
router.GET("/test/:id/:name", testRouteHandler)
router.Run(":8080")
}
func testRouteHandler(c *gin.Context) {
id := c.Params.ByName("id")
name := c.Params.ByName("name")
}
现在您必须按如下方式调用您的处理程序
http://localhost:8080/test/1/myname
聚会迟到了,我的提议到此为止。将方法封装到对象中,其中包含 private/public 个变量:
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
type HandlerA struct {
Db gorm.DB
}
func (this *HandlerA) Get(c *gin.Context) {
log.Info("[%#f]", this.Db)
// do your thing here...
}
func main() {
r := gin.New()
// Init, should be separate, but it's ok for this sample:
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
Obj := new(HandlerA)
Obj.Db = db // Or init inside Object
r := gin.New()
Group := r.Group("api/v1/")
{
Group.GET("/storage", Obj.Get)
}
r.Run(":8080")
}
我喜欢 wildneuro 的示例,但会做一个单行来设置处理程序
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
type HandlerA struct {
Db gorm.DB
}
func (this *HandlerA) Get(c *gin.Context) {
log.Info("[%#f]", this.Db)
// do your thing here...
}
func main() {
r := gin.New()
// Init, should be separate, but it's ok for this sample:
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
r := gin.New()
Group := r.Group("api/v1/")
{
Group.GET("/storage", (&HandlerA{Db: db}).Get)
}
r.Run(":8080")
}
让我试着详细解释一下,以免您感到困惑。
- 根据传入路由,你想调用一个控制器函数。假设您的传入路线是
/books
,您的控制器是 BooksController
- 您的
BooksController
将尝试从数据库中获取图书并 returns 响应。
现在,您希望在 BooksController
中有一个处理程序,以便您可以访问数据库。
我会做这样的事情。假设您正在使用 dynamoDB 并且 aws sdk 提供 *dynamodb.DynamoDB
。根据您的数据库,更改此变量。
- 创建如下结构。
type serviceConnection struct {
db *dynamoDB.DynamoDB
// You can have all services declared here
// which you want to use it in your controller
}
- 在您的主函数中,获取数据库连接信息。假设您已经有一个函数
initDatabaseConnection
,它 returns 是数据库的处理程序,如下所示。
db := initDatabaseConnection()
-> returns *dynamodb.DynamoDB
- 将
db
设置为结构变量。
conn := new(serviceConnection)
conn.db = db
- 使用接收处理程序调用 gin 请求方法,如下所示。
r := gin.Default()
r.GET("/books", conn.BooksController)
如您所见,gin 处理程序是一个控制器方法,它将您的结构实例作为接收者。
- 现在,使用
serviceConnection
struct receiver 创建一个控制器方法。
func (conn *serviceConnection) BooksController(c *gin.Context) {
books := getBooks(conn.db)
}
正如您在此处看到的,您可以访问所有 serviceConnection
结构变量,并且可以在控制器中使用它们。
处理程序闭包是一个不错的选择,但当参数单独用于该处理程序时效果最佳。
如果您有路由组或长处理程序链,其中多个地方需要相同的参数,您应该将值设置到 Gin 上下文中。
您可以使用函数字面值,或 return gin.HandlerFunc
的命名函数以干净的方式做到这一点。
将配置注入路由器组的示例:
中间件包:
func Configs(conf APIV1Config) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("configKey", conf) // key could be an unexported struct to ensure uniqueness
}
}
路由器:
conf := APIV1Config{/* some api configs */}
// makes conf available to all routes in this group
g := r.Group("/api/v1", middleware.Configs(conf))
{
// ... routes that all need API V1 configs
}
这也很容易进行单元测试。假设您测试单个处理程序,您可以将必要的值设置到模拟上下文中:
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("configKey", /* mock configs */)
apiV1FooHandler(c)
现在对于应用程序范围的依赖项(数据库连接、远程客户端...),我同意将它们直接设置到 Gin 上下文中是一个糟糕的解决方案。
然后你应该做的是使用上面概述的模式将 providers 注入到 Gin 上下文中:
中间件包:
// provider could be an interface for easy mocking
func DBProvider(provider database.Provider) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("providerKey", provider)
}
}
路由器:
dbProvider := /* init provider with db connection */
r.Use(DBProvider(dbProvider)) // global middleware
// or
g := r.Group("/users", DBProvider(dbProvider)) // users group only
处理程序(您可以通过将这些上下文获取器放在一些辅助函数中来大大减少样板代码):
// helper function
func GetDB(c *gin.Context) *sql.DB {
provider := c.MustGet("providerKey").(database.Provider)
return provider.GetConn()
}
func createUserHandler(c *gin.Context) {
db := GetDB(c) // same in all other handlers
// ...
}
我正在使用 Gin,https://gin-gonic.github.io/gin/,用 Golang 构建一个简单的 RESTful JSON API。
路由设置如下:
func testRouteHandler(c *gin.Context) {
// do smth
}
func main() {
router := gin.Default()
router.GET("/test", testRouteHandler)
router.Run(":8080")
}
我的问题是如何将参数传递给 testRouteHandler 函数?例如,一个公共数据库连接可能是人们想要在路由之间重用的东西。
在全局变量中使用它是最好的方法吗?或者在 Go 中是否有某种方法可以将一个额外的变量传递给 testRouteHandler 函数? Go 中的函数有可选参数吗?
PS。我刚刚开始学习 Go,所以我可能缺少一些明显的东西:)
使用我在评论中发布的 link,我创建了一个简单的示例。
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
// ApiMiddleware will add the db connection to the context
func ApiMiddleware(db gorm.DB) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("databaseConn", db)
c.Next()
}
}
func main() {
r := gin.New()
// In this example, I'll open the db connection here...
// In your code you would probably do it somewhere else
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
r.Use(ApiMiddleware(db))
r.GET("/api", func(c *gin.Context) {
// Don't forget type assertion when getting the connection from context.
dbConn, ok := c.MustGet("databaseConn").(gorm.DB)
if !ok {
// handle error here...
}
// do your thing here...
})
r.Run(":8080")
}
这只是一个简单的 POC。但我相信这是一个开始。 希望对你有帮助。
我会避免将 'application scoped' 依赖项(例如数据库连接池)填充到请求上下文中。您的两个 'easiest' 选项是:
- 让它成为一个全球性的。这适用于较小的项目,并且
*sql.DB
是线程安全的。 - 在闭包中显式传递它,使 return 类型满足
gin.HandlerFunc
例如
// SomeHandler returns a `func(*gin.Context)` to satisfy Gin's router methods
// db could turn into an 'Env' struct that encapsulates all of your
// app dependencies - e.g. DB, logger, env vars, etc.
func SomeHandler(db *sql.DB) gin.HandlerFunc {
fn := func(c *gin.Context) {
// Your handler code goes in here - e.g.
rows, err := db.Query(...)
c.String(200, results)
}
return gin.HandlerFunc(fn)
}
func main() {
db, err := sql.Open(...)
// handle the error
router := gin.Default()
router.GET("/test", SomeHandler(db))
router.Run(":8080")
}
好了,我给你举了一个简单的例子。它应该工作。您可以根据需要扩展它
func main() {
router := gin.Default()
router.GET("/test/:id/:name", testRouteHandler)
router.Run(":8080")
}
func testRouteHandler(c *gin.Context) {
id := c.Params.ByName("id")
name := c.Params.ByName("name")
}
现在您必须按如下方式调用您的处理程序 http://localhost:8080/test/1/myname
聚会迟到了,我的提议到此为止。将方法封装到对象中,其中包含 private/public 个变量:
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
type HandlerA struct {
Db gorm.DB
}
func (this *HandlerA) Get(c *gin.Context) {
log.Info("[%#f]", this.Db)
// do your thing here...
}
func main() {
r := gin.New()
// Init, should be separate, but it's ok for this sample:
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
Obj := new(HandlerA)
Obj.Db = db // Or init inside Object
r := gin.New()
Group := r.Group("api/v1/")
{
Group.GET("/storage", Obj.Get)
}
r.Run(":8080")
}
我喜欢 wildneuro 的示例,但会做一个单行来设置处理程序
package main
import (
"log"
"github.com/gin-gonic/gin"
"github.com/jinzhu/gorm"
_ "github.com/mattn/go-sqlite3"
)
type HandlerA struct {
Db gorm.DB
}
func (this *HandlerA) Get(c *gin.Context) {
log.Info("[%#f]", this.Db)
// do your thing here...
}
func main() {
r := gin.New()
// Init, should be separate, but it's ok for this sample:
db, err := gorm.Open("sqlite3", "./example.db")
if err != nil {
log.Fatal(err)
}
r := gin.New()
Group := r.Group("api/v1/")
{
Group.GET("/storage", (&HandlerA{Db: db}).Get)
}
r.Run(":8080")
}
让我试着详细解释一下,以免您感到困惑。
- 根据传入路由,你想调用一个控制器函数。假设您的传入路线是
/books
,您的控制器是BooksController
- 您的
BooksController
将尝试从数据库中获取图书并 returns 响应。
现在,您希望在 BooksController
中有一个处理程序,以便您可以访问数据库。
我会做这样的事情。假设您正在使用 dynamoDB 并且 aws sdk 提供 *dynamodb.DynamoDB
。根据您的数据库,更改此变量。
- 创建如下结构。
type serviceConnection struct {
db *dynamoDB.DynamoDB
// You can have all services declared here
// which you want to use it in your controller
}
- 在您的主函数中,获取数据库连接信息。假设您已经有一个函数
initDatabaseConnection
,它 returns 是数据库的处理程序,如下所示。
db := initDatabaseConnection()
-> returns *dynamodb.DynamoDB
- 将
db
设置为结构变量。
conn := new(serviceConnection)
conn.db = db
- 使用接收处理程序调用 gin 请求方法,如下所示。
r := gin.Default()
r.GET("/books", conn.BooksController)
如您所见,gin 处理程序是一个控制器方法,它将您的结构实例作为接收者。
- 现在,使用
serviceConnection
struct receiver 创建一个控制器方法。
func (conn *serviceConnection) BooksController(c *gin.Context) {
books := getBooks(conn.db)
}
正如您在此处看到的,您可以访问所有 serviceConnection
结构变量,并且可以在控制器中使用它们。
处理程序闭包是一个不错的选择,但当参数单独用于该处理程序时效果最佳。
如果您有路由组或长处理程序链,其中多个地方需要相同的参数,您应该将值设置到 Gin 上下文中。
您可以使用函数字面值,或 return gin.HandlerFunc
的命名函数以干净的方式做到这一点。
将配置注入路由器组的示例:
中间件包:
func Configs(conf APIV1Config) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("configKey", conf) // key could be an unexported struct to ensure uniqueness
}
}
路由器:
conf := APIV1Config{/* some api configs */}
// makes conf available to all routes in this group
g := r.Group("/api/v1", middleware.Configs(conf))
{
// ... routes that all need API V1 configs
}
这也很容易进行单元测试。假设您测试单个处理程序,您可以将必要的值设置到模拟上下文中:
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("configKey", /* mock configs */)
apiV1FooHandler(c)
现在对于应用程序范围的依赖项(数据库连接、远程客户端...),我同意将它们直接设置到 Gin 上下文中是一个糟糕的解决方案。
然后你应该做的是使用上面概述的模式将 providers 注入到 Gin 上下文中:
中间件包:
// provider could be an interface for easy mocking
func DBProvider(provider database.Provider) gin.HandlerFunc {
return func(c *gin.Context) {
c.Set("providerKey", provider)
}
}
路由器:
dbProvider := /* init provider with db connection */
r.Use(DBProvider(dbProvider)) // global middleware
// or
g := r.Group("/users", DBProvider(dbProvider)) // users group only
处理程序(您可以通过将这些上下文获取器放在一些辅助函数中来大大减少样板代码):
// helper function
func GetDB(c *gin.Context) *sql.DB {
provider := c.MustGet("providerKey").(database.Provider)
return provider.GetConn()
}
func createUserHandler(c *gin.Context) {
db := GetDB(c) // same in all other handlers
// ...
}