Skip to content

中间件#

Huma 支持两种中间件变体:

  1. 路由器特定 - 在路由器级别工作,即在路由器无关中间件之前。您可以使用为您的路由器实现的任何中间件。
  2. 路由器无关 - 在 Huma 处理链中运行,即在调用路由器特定中间件之后。
graph LR
	Request([Request])
	RouterSpecificMiddleware[Router-Specific Middleware]
	HumaMiddleware[Huma Middleware]
	OperationHandler[Operation Handler]

	Request --> RouterSpecificMiddleware
	RouterSpecificMiddleware --> HumaMiddleware
	subgraph Huma
		HumaMiddleware --> OperationHandler
	end

路由器特定#

每个路由器实现都有自己的中间件,您可以在创建 Huma API 实例之前像往常一样使用它们。

Chi 路由器示例:

code.go
router := chi.NewMux()
router.Use(jwtauth.Verifier(tokenAuth))
api := humachi.New(router, huma.DefaultConfig("My API", "1.0.0"))

Fiber 路由器示例:

code.go
app := fiber.New()
app.Use(logger.New())
api := humafiber.New(app, huma.DefaultConfig("My API", "1.0.0"))

Huma v1

Huma v1 中间件与 Chi v4 兼容,因此如果您使用该路由器与 Huma v2,您可以继续使用 Huma v1 中间件。请参阅 humachi.NewV4

路由器无关#

您可以编写自己的 Huma 中间件,而无需依赖特定的路由器实现。这使用路由器无关的 huma.Context 接口,该接口将请求和响应属性暴露给您的中间件。

示例:

code.go
func MyMiddleware(ctx huma.Context, next func(huma.Context)) {
	// 在响应上设置自定义标头。
	ctx.SetHeader("My-Custom-Header", "Hello, world!")

	// 调用链中的下一个中间件。这最终也会调用操作处理程序。
	next(ctx)
}

func NewHumaAPI() huma.API {
	// ...
	api := humachi.New(router, config)
	api.UseMiddleware(MyMiddleware)

	// 在 UseMiddleware() 之后注册处理程序,以使中间件生效
	huma.Get(api, "/greeting/{name}", handler.GreetingGetHandler)
}

解包装#

虽然通常不推荐,但如果您需要访问底层路由器特定的请求和响应对象,您可以使用创建 API 实例时使用的路由器特定适配器包来 Unwrap() 它们(例如,对于 Chi 使用 humachi.Unwrap(),对于 Go 的 http 包使用 humago.Unwrap()):

code.go
func MyMiddleware(ctx huma.Context, next func(huma.Context)) {
	// 解包装请求和响应对象。
	r, w := humago.Unwrap(ctx)

	// 对请求和响应对象执行某些操作。
	otherMiddleware(func (_ http.Handler) {
		// 注意,这假设请求/响应是在原地修改的。
		next(ctx)
	}).ServeHTTP(w, r)
}

这在将大型现有项目迁移到 Huma 时可能很有用,因为您可以通过在 huma.Operation.Middleware 字段上对单个操作应用路由器特定中间件来实现路由器无关中间件。

上下文值#

huma.Context 接口提供 Context() 方法来检索底层请求 context.Context 值。这可用于在中间件和操作处理程序中检索上下文值,例如请求范围的日志记录器、指标或用户信息。

code.go
if v, ok := ctx.Context().Value("some-key").(string); ok {
	// 对 `v` 执行某些操作!
}

您还可以包装 huma.Context 以提供额外的或覆盖功能。提供了一些实用工具,包括 huma.WithValue

code.go
func MyMiddleware(ctx huma.Context, next func(huma.Context)) {
	// 包装上下文以添加一个值。
	ctx = huma.WithValue(ctx, "some-key", "some-value")

	// 调用链中的下一个中间件。这最终也会调用操作处理程序。
	next(ctx)
}

然后您可以在处理程序上下文中获取该值:

handler.go
huma.Get(api, "/greeting/{name}", func(ctx context.Context, input *struct{
		Name string `path:"name" maxLength:"30" example:"world" doc:"Name to greet"`
	}) (*GreetingOutput, error) {
		// "some-value"
		ctx.Value("some-key")
		resp := &GreetingOutput{}
		resp.Body.Message = fmt.Sprintf("Hello, %s!", input.Name)
		return resp, nil
	})

您可以使用 huma.Context 接口以及 huma.ReadCookiehuma.ReadCookies 从中间件中访问 Cookie,并且也可以通过在响应中添加 Set-Cookie 标头来写入 Cookie:

func MyMiddleware(ctx huma.Context, next func(huma.Context)) {
	// 按名称读取 Cookie。
	sessionCookie := huma.ReadCookie(ctx, "session")
	fmt.Println(sessionCookie)

	// 从请求中读取所有 Cookie。
	cookies := huma.ReadCookies(ctx)
	fmt.Println(cookies)

	// 在响应中设置 Cookie。使用 `ctx.AppendHeader` 不会覆盖任何现有标头,例如如果其他中间件也可能设置标头,或者如果此代码在 `next` 调用之后移动并且操作可能设置相同的标头。您还可以多次调用 `ctx.AppendHeader` 来写入多个 Cookie。
	cookie := http.Cookie{
		Name:  "session",
		Value: "123",
	}
	ctx.AppendHeader("Set-Cookie", cookie.String())

	// 调用链中的下一个中间件。这最终也会调用操作处理程序。
	next(ctx)
}

错误#

如果您的中间件遇到错误,您可以跳过对 next 的调用并写入错误响应,从而停止后续中间件或操作处理程序的处理。

huma.WriteErr(api, ctx, status, message, ...error) 函数可用于写入符合客户端驱动的内容协商的结构化错误响应:

code.go
func MyMiddleware(ctx huma.Context, next func(ctx huma.Context)) {
	// 如果查询参数 "error=true",则返回错误
	if ctx.Query("error") == "true" {
		huma.WriteErr(api, ctx, http.StatusInternalServerError,
			"Some friendly message", fmt.Errorf("error detail"),
		)
		return
	}

	// 否则,继续正常处理。
	next(ctx)
})

Error Details

huma.ErrorDetail 结构体可用于提供有关错误的更多信息,例如错误的位置和看到的错误值。

操作#

您还可以通过设置 huma.Operation.Middlewares 字段为单个操作添加路由器无关中间件。此中间件将在路由器特定中间件之后和操作处理程序之前运行。

code.go
func MyMiddleware(ctx huma.Context, next func(huma.Context)) {
	// 调用链中的下一个中间件。这最终也会调用操作处理程序。
	next(ctx)
}

func main() {
	// ...
	api := humachi.New(router, config)

	huma.Register(api, huma.Operation{
		OperationID: "demo",
		Method:      http.MethodGet,
		Path:        "/demo",
		Middlewares: huma.Middlewares{MyMiddleware},
	}, func(ctx context.Context, input *MyInput) (*MyOutput, error) {
		// TODO: implement handler...
		return nil, nil
	})
}

全局中间件也可以通过在中间件中检查请求上下文的 URL 或使用类似 huma.Operation.Metadata 的事物来仅为某些路径运行,以使用自定义设置触发中间件逻辑。如何结构化您的中间件和操作由您决定。

深入了解#