在 GitHub 上的 gofiber/fiber 项目中,issue #3175 提出了在 requestid 中间件中添加对 UserContext 支持的建议。该提议旨在方便地从 UserContext 中获取 requestid 的值。具体实现方案是在中间件中将生成的 requestid 添加到 UserContext 中,代码如下:
go// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
// Set default config
cfg := configDefault(config...)
// Return new handler
return func(c fiber.Ctx) error {
// Don't execute middleware if Next returns true
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
// Get id from request, else we generate one
rid := c.Get(cfg.Header)
if rid == "" {
rid = cfg.Generator()
}
// Set new id to response header
c.Set(cfg.Header, rid)
// Add the request ID to locals
c.Locals(requestIDKey, rid)
// Add the request ID to UserContext
ctx := context.WithValue(c.UserContext(), cfg.Header, rid)
c.SetUserContext(ctx)
// Continue stack
return c.Next()
}
}
该提议的目的是在某些场景下使用 UserContext 作为上下文时,仍能携带 requestid,以便于获取。目前,该提议已被标记为 Feature 和 Proposal,并分配给了开发者 JIeJaitt 进行实现。
从您上传的图片代码中可以看出,目前 requestid.go 中已经有一些代码实现了 requestid 的功能。要实现支持 UserContext 的功能,我们可以在 New 函数中为 UserContext 添加 requestid。
在您上传的 requestid.go 文件的代码中,可以在 New 函数的 return 语句前添加以下代码:
go// Add the request ID to UserContext
ctx := context.WithValue(c.UserContext(), requestIDKey, rid)
c.SetUserContext(ctx)
这个代码片段的作用是将生成的 requestid 添加到当前的 UserContext 中,然后使用 SetUserContext 将其设置到当前请求的上下文中。
contextKey 是唯一的,并且不会与其他 context key 冲突。您已经定义了 contextKey 类型和 requestIDKey 常量,这样可以保证 UserContext 的键不会重复。New 函数中更新 UserContext:在生成 requestid 后,将 requestid 添加到 UserContext 中,代码如下:
goctx := context.WithValue(c.UserContext(), requestIDKey, rid) c.SetUserContext(ctx)
UserContext 中获取 requestid:如果您需要在其他地方从 UserContext 获取 requestid,可以创建一个辅助函数,例如 FromUserContext 函数,用来简化获取操作:
gofunc FromUserContext(ctx context.Context) string {
if rid, ok := ctx.Value(requestIDKey).(string); ok {
return rid
}
return ""
}
以下是 New 函数的修改版本:
gofunc New(config ...Config) fiber.Handler {
cfg := configDefault(config...)
return func(c fiber.Ctx) error {
if cfg.Next != nil && cfg.Next(c) {
return c.Next()
}
rid := c.Get(cfg.Header)
if rid == "" {
rid = cfg.Generator()
}
c.Set(cfg.Header, rid)
c.Locals(requestIDKey, rid)
// 将 requestID 添加到 UserContext
ctx := context.WithValue(c.UserContext(), requestIDKey, rid)
c.SetUserContext(ctx)
return c.Next()
}
}
这样修改之后,requestid 将自动添加到 UserContext 中。在后续处理中,可以通过 FromUserContext 函数来获取当前请求的 requestid。
希望这可以帮助您实现对 UserContext 的支持。
第一个问题就是这里有个Locals的ctx和UserContext两个人共用一个key不会出问题吗? 第二个问题就是Locals里面的requestID和我们的UserContext有什么区别?如何在实际开发种使用,能写出例子给我看看吗
很高兴您提出这些问题!让我来详细解释。
Locals 和 UserContext 共用一个 Key 是否会引起冲突?Locals 和 UserContext 是 Fiber 框架中的两个不同的概念,它们有各自的存储机制,因此不会相互冲突。
Locals 是一个针对单个请求生命周期的本地存储,由 Fiber 框架管理。它的作用域仅限于当前请求,并且只在 Fiber 内部存在。通过 c.Locals("key", value) 可以将数据存入 Locals,随后使用 c.Locals("key") 来读取。
UserContext 是一个 Go 原生的 context.Context,允许将数据与请求的上下文一起传递到其他 Goroutine(例如跨中间件或异步处理),使用场景更广。通过 context.WithValue 可以在 UserContext 中存储数据,而在其他地方使用 ctx.Value("key") 获取该数据。
因为这两者有独立的存储方式,互不干扰,即使它们使用相同的 key 也不会引起冲突。
Locals 里的 requestID 和 UserContext 里的 requestID 有什么区别?虽然 Locals 和 UserContext 中都可以存储 requestID,但它们的使用场景有所不同。
Locals 的 requestID:主要在 Fiber 内部的同步处理流程中使用。因为 Locals 的数据与请求绑定,适合在同一请求的上下文中读取和写入值,典型的用法是在不同的中间件和处理函数之间传递数据。
UserContext 的 requestID:适合在跨 Goroutine 的场景中传递,比如需要将 requestID 从 Fiber 的请求上下文传递到异步任务或外部服务调用中,这时使用 UserContext 更方便,因为 context.Context 是一种更为标准的跨包、跨 Goroutine 数据传递方式。
假设我们希望在 Fiber 应用中为每个请求生成一个 requestID,并且将其传递到后续的中间件或异步操作中。
gopackage main
import (
"context"
"fmt"
"github.com/gofiber/fiber/v3"
"log"
)
// 定义一个 contextKey 类型,以避免 key 冲突
type contextKey int
const requestIDKey contextKey = iota
// 辅助函数,从 UserContext 获取 requestID
func FromUserContext(ctx context.Context) string {
if rid, ok := ctx.Value(requestIDKey).(string); ok {
return rid
}
return ""
}
func main() {
app := fiber.New()
// Middleware: 为每个请求生成 requestID
app.Use(func(c *fiber.Ctx) error {
// 生成 requestID,可以使用 UUID 或其他生成器
requestID := "some-unique-id"
// 将 requestID 存入 Locals 和 UserContext
c.Locals("requestID", requestID)
ctx := context.WithValue(c.UserContext(), requestIDKey, requestID)
c.SetUserContext(ctx)
return c.Next()
})
// 中间件:记录请求信息
app.Use(func(c *fiber.Ctx) error {
// 从 Locals 获取 requestID
requestID := c.Locals("requestID").(string)
log.Printf("Handling request with ID: %s", requestID)
return c.Next()
})
// 处理异步任务的示例
app.Get("/async", func(c *fiber.Ctx) error {
go func(ctx context.Context) {
// 在 Goroutine 中,通过 UserContext 获取 requestID
requestID := FromUserContext(ctx)
log.Printf("Asynchronous operation for request ID: %s", requestID)
// 模拟异步操作
}(c.UserContext())
return c.SendString("Asynchronous task started")
})
// 常规请求处理示例
app.Get("/", func(c *fiber.Ctx) error {
// 从 Locals 获取 requestID
requestID := c.Locals("requestID").(string)
return c.SendString(fmt.Sprintf("Hello, your request ID is: %s", requestID))
})
log.Fatal(app.Listen(":3000"))
}
生成 requestID 并存入 Locals 和 UserContext:
requestID,并分别存入 Locals 和 UserContext。Locals 便于在 Fiber 内部的同步处理中使用,而 UserContext 便于在需要跨 Goroutine 时传递。在日志中记录 requestID:
Locals 获取 requestID,并将其用于请求的日志记录中。异步任务的场景:
/async 路由处理程序中,通过 c.UserContext() 获取 UserContext,并传递给异步 Goroutine。然后在 Goroutine 中调用 FromUserContext 函数,从 UserContext 中获取 requestID,从而使 requestID 能在异步任务中被访问。普通请求的场景:
Locals 中获取 requestID 并返回给用户,这种情况适合在同步请求处理中使用 Locals 传递 requestID。c.Locals("requestID") 可以快速获取当前请求的 requestID,适合在 Fiber 内部中间件和处理程序中使用。c.UserContext() 和 context.WithValue 将 requestID 存入 UserContext,可以方便地传递上下文给异步任务或其他 Goroutine,避免显式传递变量的麻烦。这种实现方式使得 requestID 可以在整个请求链中访问,同时兼顾同步和异步需求。
本文作者:JIeJaitt
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!