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

为所有ZIO-HTTP声明式Endpoint统一添加/api前缀时遇到路径解码错误的问题

所有ZIO-HTTP声明式Endpoint统一添加/api前缀时遇到路径解码错误的问题

我完全懂你碰到的这个坑——想用统一方式给所有声明式Endpoint加上/api前缀,不用一个个改路径,但用literal("api")把整个路由包起来后,反而触发了路径解码错误,这确实挺闹心的。

先给你拆解下问题原因:
ZIO-HTTP的声明式Endpoint是和你定义的路径强绑定的。你写GET / "activities"的时候,这个Endpoint已经把它的匹配路径固定成了/activities。当你用literal("api") / routes给整个路由集合加前缀,请求路径变成了/api/activities,但Endpoint依然在找/activities——它解析路径时,先匹配了api这个段,剩下的路径就空了,自然会报错说“Expected path segment 'activities' but found end of path”。而你那个sanity测试能正常工作,是因为它是普通路由,没有Endpoint那种强绑定的路径解码逻辑,只是简单的字符串匹配,所以加前缀没问题。

接下来给你几个靠谱的解决方案,按推荐程度排序:


1. 用EndpointGroup统一管理前缀(最推荐)

ZIO-HTTP专门给声明式Endpoint提供了分组功能,可以直接给整个组加前缀,所有组内的Endpoint都会自动带上这个前缀,完全不用逐个修改:

import zio.http._
import zio.http.endpoint._

// 创建带/api前缀的Endpoint组
val apiGroup = EndpointGroup(prefix = Path("api"))

// 在组内定义Endpoint,路径自动包含/api前缀
val getActivities = apiGroup.endpoint(GET / "activities")
  .out[CollectionResponse[ActivitySummary]]
  .outError[ErrorResponse](Status.InternalServerError)
  .examplesOut(("Response", exampleActivitySummaryResponse()))

// 组合路由,直接用就行,前缀已经自动加上了
val routes = Routes(
  getActivities.implement(_ => handleGetAll())
)

这种方式最符合ZIO-HTTP声明式Endpoint的设计理念,后续要改前缀也只需要改apiGroup的定义就行,非常灵活。


2. 给每个Endpoint的实现Route单独加前缀

如果不想重构现有的Endpoint定义,你可以先把每个Endpoint转换成Route,再给单个Route加前缀,最后组合成Routes:

// 先实现Endpoint得到Route
val getActivitiesRoute = getActivities.implement(_ => handleGetAll())

// 给单个Route加前缀
val apiGetActivitiesRoute = literal("api") / getActivitiesRoute

// 组合所有带前缀的Route
val routes = Routes(apiGetActivitiesRoute)

如果有多个Endpoint,你可以把所有实现后的Route收集到一个列表里,统一处理前缀:

val allEndpointsRoutes = List(
  getActivities.implement(_ => handleGetAll()),
  // 其他Endpoint的实现Route
)

// 给所有Route统一加前缀后组合
val apiRoutes = Routes(allEndpointsRoutes.map(literal("api") / _): _*)

3. 用统一常量定义前缀(最直接但繁琐)

如果你不想用上面两种方式,也可以定义一个前缀常量,手动给每个Endpoint的路径加上:

val API_PREFIX = "api"

val getActivities = Endpoint((GET / API_PREFIX / "activities"))
  .out[CollectionResponse[ActivitySummary]]
  .outError[ErrorResponse](Status.InternalServerError)
  .examplesOut(("Response", exampleActivitySummaryResponse()))

这种方式虽然直接,但需要修改每个Endpoint的路径定义,后续改前缀也得逐个改,适合Endpoint数量不多的场景。


最后再验证下:用前两种方式,请求/api/activities时,Endpoint会正确识别带前缀的路径,不会再出现解码错误,同时还能保留声明式Endpoint的所有特性(比如自动生成文档)。

备注:内容来源于stack exchange,提问作者dbrown428

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 17:53:00