You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将HandlerAspect的CtxOut对象传递至下游Handler?

在ZIO-HTTP中传递HandlerAspect的CtxOut值到下游Handler

核心问题:如何将ZIO-HTTP中HandlerAspect生成的BearerToken(CtxOut)传递给下游Handler?之前尝试ZIO.service[BearerToken]、ZIO.serviceWith[BearerToken]、zio.http.withContext均失败,因为BearerToken是动态生成的字符串包装类,无法通过静态Layer提供;也不想将其作为Endpoint输入(等同于弃用Middleware)。

最简解决方案

问题出在错误地使用ZIO.service获取BearerToken——ZIO.service用于从ZLayer中获取静态服务,而HandlerAspect注入的CtxOut属于Handler的动态环境,应通过ZIO.access或ZIO.environment获取。

修改要点

  • 替换ZIO.service[BearerToken]为ZIO.access[BearerToken](_.value),从Handler环境中直接提取动态生成的BearerToken。
  • 修正Middleware中BearerToken的构造逻辑,去掉Authorization header中的"Bearer "前缀。

修改后的完整代码

import zio.{ ZIO, ZIOAppDefault, ZLayer }
import zio.http.{ string, Handler, HandlerAspect, Request, Response, RoutePattern, Routes, Server }
import zio.http.endpoint.Endpoint
import zio.schema.DeriveSchema

object CarShop extends ZIOAppDefault {
  // 数据模型
  case class Car(brand: String)
  object Car {
    implicit val schema = DeriveSchema.gen[Car]
  }

  // 服务层
  object CarService {
    val layer: ZLayer[Any, Nothing, CarService] =
      ZLayer.succeed(CarService("Renault"))
  }

  case class CarService(brand: String) {
    def listCar: List[Car] =
      List(
        Car("Toyota"),
        Car("Honda"),
      )

    def getCar(brand: String): Car =
      Car(s"$brand (${this.brand})")
  }

  // Endpoints定义
  val listCarsEndpoint = Endpoint(RoutePattern.GET / "cars")
    .out[List[Car]]

  def listCarsHandler: Handler[CarService, Nothing, Unit, List[Car]] = Handler.fromFunctionZIO { _: Unit =>
    ZIO.serviceWith[CarService](_.listCar)
  }

  val getCarEndpoint = Endpoint(RoutePattern.GET / "car" / string("brand"))
    .out[Car]

  // 修正后的Handler:从环境中获取BearerToken
  def getCarHandler: Handler[CarService with BearerToken, Nothing, String, Car] =
    Handler.fromFunctionZIO { brand: String =>
      for {
        carService <- ZIO.service[CarService]
        bearer     <- ZIO.access[BearerToken](_.value) // 正确获取动态注入的BearerToken
      } yield carService.getCar(s"$brand (${bearer.value})")
    }

  case class BearerToken(value: String)

  // 修正后的Middleware:去掉Bearer前缀
  object BearerAuthMiddleware {
    def handler: HandlerAspect[Any, BearerToken] =
      HandlerAspect.interceptIncomingHandler {
        Handler.fromFunctionZIO[Request] { req =>
          req.headers.get("Authorization") match {
            case Some(auth) if auth.startsWith("Bearer ") =>
              ZIO.succeed((req, BearerToken(auth.drop(7)))) // 去掉"Bearer "前缀
            case _                                        =>
              ZIO.fail(Response.unauthorized)
          }
        }
      }
  }

  val routes: Routes[CarService with BearerToken, Nothing] =
    Routes(
      listCarsEndpoint.implementHandler(listCarsHandler),
      getCarEndpoint.implementHandler(getCarHandler),
    ) @@ BearerAuthMiddleware.handler

  def run =
    Server
      .serve(routes)
      .provide(
        CarService.layer,
        Server.default,
      )
}

对应的build.sbt

name := "car-shop"

libraryDependencies ++= Seq(
  "dev.zio" %% "zio" % "2.1.18",
  "dev.zio" %% "zio-http" % "3.3.3"
)

内容的提问来源于stack exchange,提问作者Biketire

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 12:44:50