使用 REST 和 Rest DSL
Rest DSL
Apache Camel 提供了一种 REST 风格的 DSL。
其目的是让最终用户能够以 REST 风格(使用 get、post、delete 等动词)来定义由 Camel 托管的 REST 服务。
| 从 Camel 4.6 开始,Rest DSL 引入了使用原生 OpenAPI 规范文件的契约优先(contract-first)方式,功能得到了增强。相关内容记录在 Rest DSL with OpenAPI contract first 页面中。当前页面介绍的是 Camel 长期以来提供的代码优先(code-first)Rest DSL。 |
|---|
工作原理
Rest DSL 是一个门面(facade),它构建 Rest 端点,作为 Camel 路由的消费者。实际的 REST 传输则由支持原生 REST 集成的 Camel REST 组件来承担,例如 Netty HTTP、Servlet 等。
支持 Rest DSL 的组件
以下 Camel 组件支持 Rest DSL:
- camel-rest 必需,包含 Rest DSL 所需的基础 rest 组件
- camel-netty-http
- camel-jetty
- camel-platform-http(推荐)
- camel-servlet
- camel-undertow
Rest DSL
让我们定义一个具有以下 URL 映射的 REST 服务:
| 基础路径 | URI 模板 | 动词 | 消费类型(Consumes) |
|---|---|---|---|
/say | /hello | get | 全部 |
/say | /bye | get | application/json |
/say | /bye | post | 全部 |
- Java
- XML
- YAML
要在 Java DSL 中使用 Rest DSL,只需像编写常规 Camel 路由一样,继承 RouteBuilder 并在 configure 方法中定义路由即可。
一个简单的 REST 服务可以如下定义,我们使用 rest() 来定义服务,如下面所示:
@Override
public void configure() throws Exception {
rest("/say")
.get("/hello").to("direct:hello")
.get("/bye").consumes("application/json").to("direct:bye")
.post("/bye").to("mock:update");
from("direct:hello")
.transform().constant("Hello World");
from("direct:bye")
.transform().constant("Bye World");
}注意,在 REST 服务中,我们使用 to() 直接路由到一个 Camel 端点。这是因为 Rest DSL 提供了一种简写方式,可以直接使用 to() 路由到端点。
一个简单的 REST 服务可以如下定义,我们使用 <rest> 来定义这些服务:
<rest path="/say">
<get path="/hello">
<to uri="direct:hello"/>
</get>
<get path="/bye" consumes="application/json">
<to uri="direct:bye"/>
</get>
<post path="/bye">
<to uri="mock:update"/>
</post>
</rest>
<route>
<from uri="direct:hello"/>
<transform>
<constant>Hello World</constant>
</transform>
</route>
<route>
<from uri="direct:bye"/>
<transform>
<constant>Bye World</constant>
</transform>
</route>一个简单的 REST 服务可以按如下方式定义,其中我们使用 - rest: 来定义服务,如下所示:
- rest:
path: "/say"
get:
- path: "/hello"
to: "direct:hello"
- path: "/bye"
consumes: "application/json"
to: "direct:bye"
post:
- path: "/bye"
to: "direct:update"
- route:
from:
uri: direct:hello
steps:
- transform:
expression:
constant:
expression: Hello World
- route:
from:
uri: direct:bye
steps:
- transform:
expression:
constant:
expression: Bye World使用基础路径
REST DSL 允许定义基础路径,以帮助践行*"不要重复自己"*(DRY)原则。例如,要定义客户相关的路径,我们可以在 rest("/customer") 中设置基础路径,然后在各个动词中提供 uri 模板,如下所示:
- Java
- XML
- YAML
rest("/customers/")
.get("/{id}").to("direct:customerDetail")
.get("/{id}/orders").to("direct:customerOrders")
.post("/neworder").to("direct:customerNewOrder");<rest path="/customers/">
<get path="/{id}">
<to uri="direct:customerDetail"/>
</get>
<get path="/{id}/orders">
<to uri="direct:customerOrders"/>
</get>
<post path="/neworder">
<to uri="direct:customerNewOrder"/>
</post>
</rest>- rest:
path: "/customers/"
get:
- path: "/{id}}"
to: "direct:customerDetails"
- path: "/{id}/orders}"
to: "direct:customerOrders"
post:
- path: "/neworder"
to: "direct:customerNewOrder"Rest DSL 会处理使用基础路径(base path)和 URI 模板时出现的重复路径分隔符。在上面的示例中,rest 基础路径以斜杠 / 结尾,而动词以斜杠 / 开头。Camel 会处理这种情况,去掉重复的斜杠。 |
|---|
基础路径和 URI 模板并非都必须使用。你可以省略基础路径,只在各个动词中定义基础路径和 URI 模板。上面的示例可以定义为:
- Java
- XML
- YAML
rest()
.get("/customers/{id}").to("direct:customerDetail")
.get("/customers/{id}/orders").to("direct:customerOrders")
.post("/customers/neworder").to("direct:customerNewOrder");<rest>
<get path="/customers/{id}">
<to uri="direct:customerDetail"/>
</get>
<get path="/customers/{id}/orders">
<to uri="direct:customerOrders"/>
</get>
<post path="/customers/neworder">
<to uri="direct:customerNewOrder"/>
</post>
</rest>- rest:
get:
- path: "/customers/{id}}"
to: "direct:customerDetails"
- path: "/customers/{id}/orders}"
to: "direct:customerOrders"
post:
- path: "/customers/neworder"
to: "direct:customerNewOrder"你可以组合路径参数来构建复杂的表达式。
例如,下面的定义使用类似文件语法的方式来获取某个条目:
- Java
- XML
- YAML
rest("items/")
.get("{id}/{filename}.{content-type}")
.to("direct:item");<rest path="items/">
<get path="{id}/{filename}.{content-type}">
<to uri="direct:item"/>
</get>
</rest>- rest:
path: "items/"
get:
- path: "{id}/{filename}.{content-type}"
to: "direct:item"管理 Rest 服务
每个 Rest 服务都会变成一个 Camel 路由,因此在第一个示例中,我们有 2 个 get 和 1 个 post REST 服务,它们各自都会成为一个 Camel 路由。
这样,Apache Camel 管理和运行这些服务的方式就完全相同,因为它们只是普通的 Camel 路由。这意味着当今任何处理 Camel 路由的工具和 API,同样也适用于这些 REST 服务。
要在 Camel 中使用 JMX,必须在类路径中包含 camel-management JAR。 |
|---|
这意味着你可以使用 JMX 来停止/启动路由,还可以获取路由的 JMX 指标,例如已处理的消息数量及其性能统计数据。
还有一个 Rest Registry JMX MBean,其中包含了所有已定义 REST 服务的注册表。
将 Rest DSL 内联为单一路由
| Camel 4.4 及更早版本默认禁用内联路由,Camel 4.5 及更高版本默认启用内联路由。 |
|---|
每个 Rest 服务都会变成一个 Camel 路由,这意味着如果 Rest 服务通过 direct 调用另一个 Camel 路由(这是一种常见的做法),那么每个 Rest 服务就会变成两条路由。如果你有很多 Rest 服务,这会变得更难管理。
当你使用 direct 端点时,可以启用 Rest DSL 自动将 direct 路由内联到 rest 路由中,也就是说每个 Rest 服务只对应一条路由。
使用内联路由时,每个 REST 端点应 1:1 关联到一个唯一的 direct 端点。被关联的 direct 路由会被内联,因此不再作为独立路由存在,也无法从其他常规 Camel 路由中调用。换句话说,被内联的路由实际上已经移入了 rest-dsl 内部,不再以路由的形式存在。更多详情见下文。 |
|---|
要实现这一点,你必须使用 direct 端点,并且每个服务中的端点名称必须唯一。同时必须启用 inlineRoutes 选项。
例如,在下面的 Java DSL 中,我们启用了内联路由,每个 Rest 服务都使用具有唯一名称的 direct 端点。
- Java
- XML
- YAML
restConfiguration().inlineRoutes(true);
rest("/customers/")
.get("/{id}").to("direct:customerDetail")
.get("/{id}/orders").to("direct:customerOrders")
.post("/neworder").to("direct:customerNewOrder");<restConfiguration inlineRoutes="true"/>
<rest>
<get path="/customers/{id}">
<to uri="direct:customerDetail"/>
</get>
<get path="/customers/{id}/orders">
<to uri="direct:customerOrders"/>
</get>
<post path="/customers/neworder">
<to uri="direct:customerNewOrder"/>
</post>
</rest>- restConfiguration:
inlineRoutes: true
- rest:
get:
- path: "/customers/{id}}"
to: "direct:customerDetails"
- path: "/customers/{id}/orders}"
to: "direct:customerOrders"
post:
- path: "/customers/neworder"
to: "direct:customerNewOrder"如果你使用 Camel Main、Camel Spring Boot、Camel Quarkus 或 Camel CLI,也可以在 application.properties 中启用该功能,例如:
camel.rest.inline-routes = true注意,上面的每个 REST 服务都各自使用一个唯一的、与之 1:1 绑定的 direct 端点(direct:customerDetail、direct:customerOrders、direct:customerNewOrder)。这意味着你无法从其他路由调用这些路由,例如下面这样是无法正常工作的:
- Java
- XML
- YAML
from("kafka:new-order")
.to("direct:customerNewOrder");<route>
<from uri="kafka:new-order"/>
<to uri="direct:customerNewOrder"/>
</route>- route:
from:
uri: kafka:new-order
steps:
- to:
uri: direct:customerNewOrder因此,如果你希望既能从 Rest DSL 又能从其他常规 Camel 路由中调用公共路由,请将这些路由拆分为独立的路由,如下所示:
- Java
- XML
- YAML
restConfiguration().inlineRoutes(true);
rest("/customers/")
.get("/{id}").to("direct:customerDetail")
.get("/{id}/orders").to("direct:customerOrders")
.post("/neworder").to("direct:customerNewOrder");
from("direct:customerNewOrder")
// do some stuff here
.to("direct:commonCustomerNewOrder"); // call common route
from("direct:commonCustomerNewOrder")
// do stuff here
.log("Created new order");
from("kafka:new-order")
.to("direct:commonCustomerNewOrder"); // make sure to call the common route<restConfiguration inlineRoutes="true"/>
<rest path="/customers/">
<get path="/{id}">
<to uri="direct:customerDetail"/>
</get>
<get path="/{id}/orders">
<to uri="direct:customerOrders"/>
</get>
<post path="/neworder">
<to uri="direct:customerNewOrder"/>
</post>
</rest>
<routes>
<route>
<from uri="direct:customerNewOrder"/>
<to uri="direct:commonCustomerNewOrder"/>
</route>
<route>
<from uri="direct:commonCustomerNewOrder"/>
<log message="Created new order"/>
</route>
<route>
<from uri="kafka:new-order"/>
<to uri="direct:commonCustomerNewOrder"/>
</route>
</routes>- restConfiguration:
inlineRoutes: true
- rest:
path: "/customers/"
get:
- path: "/{id}}"
to: "direct:customerDetails"
- path: "/{id}/orders}"
to: "direct:customerOrders"
post:
- path: "/neworder"
to: "direct:customerNewOrder"
- route:
from:
uri: direct:customerNewOrder
steps:
- to:
uri: direct:commonCustomerNewOrder
- route:
from:
uri: direct:commonCustomerNewOrder
steps:
- log:
message: Created new order
- route:
from:
uri: kafka:new-order
steps:
- to:
uri: direct:commonCustomerNewOrder注意公共共享路由是如何被拆分为 direct:commonCustomerNewOrder 路由的。它既可以从 Rest DSL 调用,也可以从常规 Camel 路由调用。
禁用 REST 服务
在使用 Rest DSL 开发 REST 服务时,你可能希望临时禁用某些 REST 端点,这可以通过 disabled 来实现,如下所示。
- Java
- XML
- YAML
rest("/customers/")
.get("/{id}").to("direct:customerDetail")
.get("/{id}/orders").to("direct:customerOrders").disabled("{{ordersEnabled}}")
.post("/neworder").to("direct:customerNewOrder").disabled();<rest>
<get path="/customers/{id}">
<to uri="direct:customerDetail"/>
</get>
<get path="/customers/{id}/orders" disabled="{{ordersEnabled}}">
<to uri="direct:customerOrders"/>
</get>
<post path="/customers/neworder" disabled="true">
<to uri="direct:customerNewOrder"/>
</post>
</rest>- rest:
get:
- path: "/customers/{id}}"
to: "direct:customerDetails"
- path: "/customers/{id}/orders}"
to: "direct:customerOrders"
disabled: "{{ordersEnabled}}"
post:
- path: "/customers/neworder"
to: "direct:customerNewOrder"
disabled: "true"在此示例中,最后两个 REST 端点通过 disabled 进行配置。你可以使用属性占位符,让外部配置来决定 REST 端点是否被禁用。在此示例中,/customers/{id}/orders 端点通过占位符被禁用,而最后一个 REST 端点则被硬编码为禁用状态。
更多信息
有关特定主题的详细文档,请参阅以下子页面:
- 绑定与配置 — POJO 绑定、Jackson JSON 特性、CORS、OAuth Bearer 令牌验证
- 错误处理与校验 — 自定义错误消息、请求/响应校验、参数默认值
- 基于 OpenAPI 的契约优先开发 — 在 Rest DSL 中使用原生 OpenAPI 规范文件
评论
登录后参与评论
KnowForge