如何在Golang中使用Gin web框架将参数传递给路由处理程序?

huangapple go评论89阅读模式
英文:

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) {
    // 做一些事情
}

func main() {
    router := gin.Default()
    router.GET("/test", testRouteHandler)
    router.Run(":8080")
}

我的问题是如何将参数传递给testRouteHandler函数?例如,一个常见的数据库连接可能是希望在多个路由之间重用的内容。

最好的方法是将其放在全局变量中吗?还是Go中有一种方法可以将额外的变量传递给testRouteHandler函数?Go中的函数是否支持可选参数?

PS. 我刚开始学习Go,可能有一些显而易见的东西我还不知道 如何在Golang中使用Gin web框架将参数传递给路由处理程序?

英文:

I'm using Gin, https://gin-gonic.github.io/gin/, to build a simple RESTful JSON API with Golang.

The routes are setup with something like this:

func testRouteHandler(c *gin.Context) {
    // do smth
}

func main() {
    router := gin.Default()
    router.GET("/test", testRouteHandler)
    router.Run(":8080")
}

My question is how can I pass down an argument to the testRouteHandler function? For example a common database connection could be something that one wants to reuse among routes.

Is the best way to have this in a global variable? Or is there some way in Go to pass along an extra variable to the testRouteHandler function? Are there optional arguments for functions in Go?

PS. I'm just getting started in learning Go, so could be something obvious that I'm missing 如何在Golang中使用Gin web框架将参数传递给路由处理程序?

答案1

得分: 55

我会避免将“应用程序范围”的依赖项(例如DB连接池)放入请求上下文中。你有两个“最简单”的选项:

  1. 将其设为全局变量。对于较小的项目来说,这是可以接受的,*sql.DB 是线程安全的。
  2. 在闭包中显式传递它,以使返回类型满足 gin.HandlerFunc 的要求。

例如:

// SomeHandler 返回一个 `func(*gin.Context)`,以满足 Gin 的路由方法
// db 可以变成一个“Env”结构体,封装了所有应用程序的依赖项 - 例如 DB、日志记录器、环境变量等等
func SomeHandler(db *sql.DB) gin.HandlerFunc {
    fn := func(c *gin.Context) {
        // 在这里编写你的处理程序代码 - 例如
        rows, err := db.Query(...)

        c.String(200, results)
    }

    return gin.HandlerFunc(fn)
}

func main() {
    db, err := sql.Open(...)
    // 处理错误
    
    router := gin.Default()
    router.GET("/test", SomeHandler(db))
    router.Run(":8080")
}
英文:

I would avoid stuffing 'application scoped' dependencies (e.g. a DB connection pool) into a request context. Your two 'easiest' options are:

  1. Make it a global. This is OK for smaller projects, and *sql.DB is thread-safe.
  2. Pass it explicitly in a closure so that the return type satisfies gin.HandlerFunc

e.g.

// 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")
}

答案2

得分: 43

使用我在评论中发布的链接,我创建了一个简单的示例。

package main

import (
	"log"

	"github.com/gin-gonic/gin"
	"github.com/jinzhu/gorm"
	_ "github.com/mattn/go-sqlite3"
)

// ApiMiddleware将数据库连接添加到上下文中
func ApiMiddleware(db gorm.DB) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("databaseConn", db)
		c.Next()
	}
}

func main() {
	r := gin.New()

	// 在这个示例中,我将在这里打开数据库连接...
	// 在你的代码中,你可能会在其他地方打开数据库连接
	db, err := gorm.Open("sqlite3", "./example.db")
	if err != nil {
		log.Fatal(err)
	}

	r.Use(ApiMiddleware(db))

	r.GET("/api", func(c *gin.Context) {
		// 从上下文中获取连接时不要忘记类型断言。
		dbConn, ok := c.MustGet("databaseConn").(gorm.DB)
		if !ok {
			// 在这里处理错误...
		}

		// 在这里做你的事情...
	})

	r.Run(":8080")
}

这只是一个简单的概念验证。但我相信这是一个开始。希望对你有所帮助。

英文:

Using the link i posted on comments, I have created a simple example.

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")
}

This is just a simple POC. But i believe it's a start.
Hope it helps.

答案3

得分: 8

迟到了,到目前为止,这是我的提议。将方法封装到具有私有/公共变量的对象中:

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)
	// 在这里执行你的操作...
}

func main() {
	r := gin.New()

	// 初始化,应该分开,但对于这个示例来说可以这样:
	db, err := gorm.Open("sqlite3", "./example.db")
	if err != nil {
		log.Fatal(err)
	}

	Obj := new(HandlerA)
	Obj.Db = db // 或在对象内部初始化

	r := gin.New()

	Group := r.Group("api/v1/")
	{
		Group.GET("/storage", Obj.Get)
	}

	r.Run(":8080")
}
英文:

Late to the party, so far here is my proposal. Encapsulate methods into the object with private/public vars in it:

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")
}

答案4

得分: 3

处理程序闭包是一个不错的选择,但只有在该处理程序独自使用参数时才能发挥最佳效果。

如果你有路由组或者长的处理程序链,在多个地方都需要相同的参数,你应该将值设置到 Gin 上下文中。

你可以使用函数字面量或者返回 gin.HandlerFunc 的命名函数来以清晰的方式实现。

例如,将配置注入到路由组中:

中间件包:

func Configs(conf APIV1Config) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("configKey", conf) // key 可以是一个未导出的结构体,以确保唯一性
	}
}

路由器:

conf := APIV1Config{/* 一些 API 配置 */}

// 使 conf 在该组中的所有路由中可用
g := r.Group("/api/v1", middleware.Configs(conf))
{
    // ... 所有需要 API V1 配置的路由
}

这也很容易进行单元测试。假设你测试单个处理程序,你可以将必要的值设置到模拟上下文中:

w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("configKey", /* 模拟配置 */)

apiV1FooHandler(c)

现在,对于应用程序范围的依赖项(数据库连接、远程客户端等),我同意直接将它们设置到 Gin 上下文中是一个不好的解决方案。

那么,你应该使用上面概述的模式将提供者注入到 Gin 上下文中:

中间件包:

// provider 可以是一个接口,方便进行模拟
func DBProvider(provider database.Provider) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("providerKey", provider)
	}
}

路由器:

dbProvider := /* 使用数据库连接初始化提供者 */

r.Use(DBProvider(dbProvider)) // 全局中间件
// 或者
g := r.Group("/users", DBProvider(dbProvider)) // 仅限 users 组

处理程序(你可以通过将这些上下文获取器放在某个辅助函数中,大大减少样板代码):

// 辅助函数
func GetDB(c *gin.Context) *sql.DB {
   provider := c.MustGet("providerKey").(database.Provider)
   return provider.GetConn()
}

func createUserHandler(c *gin.Context) {
    db := GetDB(c) // 在所有其他处理程序中也是一样的
    // ...
}
英文:

Handler closures are a good option, but that works best when the argument is used in that handler alone.

If you have route groups, or long handler chains, where the same argument is needed in multiple places, you should set values into the Gin context.

You can use function literals, or named functions that return gin.HandlerFunc to do that in a clean way.

Example injecting configs into a router group:

Middleware package:

func Configs(conf APIV1Config) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("configKey", conf) // key could be an unexported struct to ensure uniqueness
	}
}

Router:

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
}

This is also easily unit-testable. Assuming that you test the single handlers, you can set the necessary values into the mock context:

w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)
c.Set("configKey", /* mock configs */)

apiV1FooHandler(c)

Now in the case of application-scoped dependencies (db connections, remote clients, ...), I agree that setting these directly into the Gin context is a poor solution.

What you should do then, is to inject providers into the Gin context, using the pattern outlined above:

Middleware package:

// provider could be an interface for easy mocking
func DBProvider(provider database.Provider) gin.HandlerFunc {
	return func(c *gin.Context) {
		c.Set("providerKey", provider)
	}
}

Router:

dbProvider := /* init provider with db connection */

r.Use(DBProvider(dbProvider)) // global middleware
// or
g := r.Group("/users", DBProvider(dbProvider)) // users group only

Handler (you can greatly reduce the boilerplate code by putting these context getters in some helper function):

// 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
    // ...
}

答案5

得分: 1

我喜欢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)
    // 在这里执行你的操作...
}

func main() {
    r := gin.New()

    // 初始化,应该分开,但对于这个示例来说可以这样:
    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")
}
英文:

I like wildneuro's example but would do a one liner to setup the handler

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")
}

答案6

得分: 0

好的,我给你提供一个简单的例子。它应该可以工作。你可以根据需要进行扩展。

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

英文:

Alright, I have given you a simple example. It should work. You can extend it as per your need

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")
}

Now you will have to call your handler as below
http://localhost:8080/test/1/myname

答案7

得分: 0

让我尝试详细解释一下,以免让你感到困惑。

  1. 根据传入的路由,你想要调用一个控制器函数。假设你的传入路由是 /books,你的控制器是 BooksController
  2. 你的 BooksController 将尝试从数据库中获取图书并返回一个响应。

现在,你想要在 BooksController 中创建一个处理程序,以便你可以访问数据库。

我会这样做。假设你正在使用 DynamoDB,AWS SDK 提供了 *dynamodb.DynamoDB。根据你的数据库,修改这个变量。

  1. 创建如下的结构体。
type serviceConnection struct {
    db *dynamoDB.DynamoDB
    // 在这里声明你想要在控制器中使用的所有服务
}
  1. 在你的主函数中,获取数据库连接信息。假设你已经有一个函数 initDatabaseConnection,它返回一个数据库处理程序,类似于下面的代码。

db := initDatabaseConnection() -> 返回 *dynamodb.DynamoDB

  1. db 设置为结构体变量。
conn := new(serviceConnection)
conn.db = db
  1. 使用接收器处理程序调用 gin 请求方法,代码如下。
r := gin.Default()
r.GET("/books", conn.BooksController)

如你所见,gin 处理程序是一个控制器方法,它以你的结构体实例作为接收器。

  1. 现在,创建一个带有 serviceConnection 结构体接收器的控制器方法。
func (conn *serviceConnection) BooksController(c *gin.Context) {
    books := getBooks(conn.db)
}

如你所见,你可以访问所有的 serviceConnection 结构体变量,并在控制器中使用它们。

英文:

Let me try to explain in detail so that you won't get confused.

  1. Depending on the incoming route, you want to call a controller function. Lets say your incoming route is /books and your controller is BooksController
  2. Your BooksController will try to fetch the books from the database and returns a response.

Now, you want this a handler within your BooksController so that you can access database.

I would do something like this. Let's assume that you are using dynamoDB and the aws sdk provides *dynamodb.DynamoDB. Depending on your db, change this variable.

  1. Create a struct as below.
type serviceConnection struct {
    db *dynamoDB.DynamoDB
    // You can have all services declared here 
    // which you want to use it in your controller
}
  1. In your main function, get the db connection information. Let's say you already have a function initDatabaseConnection which returns a handler to db, something like below.

db := initDatabaseConnection() -> returns *dynamodb.DynamoDB

  1. Set db to a struct variable.
conn := new(serviceConnection)
conn.db = db
  1. Call the gin request method with a receiver handler as below.
r := gin.Default()
r.GET("/books", conn.BooksController)

As you see, the gin handler is a controller method which has your struct instance as a receiver.

  1. Now, create a controller method with serviceConnection struct receiver.
func (conn *serviceConnection) BooksController(c *gin.Context) {
    books := getBooks(conn.db)
}

As you see here, you have access to all the serviceConnection struct variables and you can use them in your controller.

huangapple
  • 本文由 发表于 2015年12月2日 23:13:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/34046194.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定