4.2. 使用 REST DSL 定义服务

REST DSL 是一个常见问题解答

REST DSL 实际上是一个 facade,它提供了在 Java DSL 或 XML DSL(域特定语言)中定义 REST 服务的简化语法。REST DSL 并不实际提供 REST 实现,它只是一个围绕 现有 REST 实施(Apache Camel 中有几个)的打包程序。

REST DSL 的优点

REST DSL 打包程序层提供以下优点:

  • 现代易用的语法来定义 REST 服务。
  • 与多个不同的 Apache Camel 组件兼容。
  • OpenAPI 集成(通过 camel-openapi-java 组件)。

与 REST DSL 集成的组件

由于 REST DSL 不是实际的 REST 实现,所以您需要做的第一项一项是选择 Camel 组件以提供底层实施。以下 Camel 组件目前与 REST DSL 集成:

注意

Rest 组件( camelcore的一部分)并不是 REST 的实施。与 REST DSL 一样,Reest 组件是一个 facade,它提供了一个简化的语法来定义使用 URI 语法的 REST 服务。Rest 组件还需要底层 REST 的实施。

配置 REST DSL 以使用 REST 实现

要指定 REST 的实现,您可以使用 restConfiguration() 构建器(在 Java DSL 中)或 restConfiguration 元素(在 XML DSL 中)。例如,要将 REST DSL 配置为使用 Spark-Rest 组件,您可以在 Java DSL 中使用类似以下内容的构建器表达式:

restConfiguration().component("spark-rest").port(9091);

您会在 XML DSL 中使用类似如下的元素(作为 camelContext的子项):

<restConfiguration component="spark-rest" port="9091"/>

语法

定义 REST 服务的 Java DSL 语法如下:

rest("BasePath").Option().
    .Verb("Path").Option().[to() | route().CamelRoute.endRest()]
    .Verb("Path").Option().[to() | route().CamelRoute.endRest()]
    ...
    .Verb("Path").Option().[to() | route().CamelRoute];

其中 CamelRoute 是一个可选的嵌入式 Camel 路由(使用路由的标准 Java DSL 语法定义)。

REST 服务定义以 rest() 关键字开头,后跟处理特定 URL 路径片段的一个或多个 动词子。HTTP 动词可以是 get()、head() put()、 post()、 delete()、patch() verb() 之一。每个 verb 子句都可以使用以下语法之一:

  • 口头以 to() 关键字结尾的动词子。例如:

    get("...").Option()+.to("...")
  • 动词 子句以 route() 关键字结尾(用于嵌入 Camel 路由)。例如:

    get("...").Option()+.route("...").CamelRoute.endRest()

带有 Java 的 REST DSL

在 Java 中,若要使用 REST DSL 定义服务,请将 REST 定义放在 RouteBuilder.configure() 方法的正文中,就像常规的 Apache Camel 路由一样。例如,要使用带有 Spark-Rest 组件的 REST DSL 定义一个简单的 Hello World 服务,请定义以下 Java 代码:

restConfiguration().component("spark-rest").port(9091);

rest("/say")
    .get("/hello").to("direct:hello")
    .get("/bye").to("direct:bye");

from("direct:hello")
    .transform().constant("Hello World");
from("direct:bye")
    .transform().constant("Bye World");

前面的示例具有三种不同类型的构建器:

restConfiguration()
配置 REST DSL 以使用特定的 REST 实现(Spark-Rest)。
rest()
使用 REST DSL 定义服务。各个动词条款均通过 to() 关键字终止,该关键字可将传入的消息转发到 直接 端点( 直接 组件转换器,在同一应用程序内路由)。
from()
定义常规 Camel 路由。

使用 XML 的 REST DSL

在 XML 中,若要使用 XML DSL 定义服务,请将 rest 元素定义为 camelContext 元素的子级。例如,若要使用带有 Spark-Rest 组件的 REST DSL 定义一个简单的 Hello World 服务,请定义以下 XML 代码(在 Blueprint 中):

<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  <restConfiguration component="spark-rest" port="9091"/>

  <rest path="/say">
    <get uri="/hello">
      <to uri="direct:hello"/>
    </get>
    <get uri="/bye">
      <to uri="direct:bye"/>
    </get>
  </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>
</camelContext>

指定基本路径

rest() 关键字(Java DSL)或 rest 元素的 path 属性(XML DSL)允许您定义一个基本路径,然后是作为前缀放在所有 动词条款中的路径上。例如,给定以下 Java DSL 片段:

rest("/say")
    .get("/hello").to("direct:hello")
    .get("/bye").to("direct:bye");

或者给定以下 XML DSL 片段:

<rest path="/say">
  <get uri="/hello">
    <to uri="direct:hello"/>
  </get>
  <get uri="/bye" consumes="application/json">
    <to uri="direct:bye"/>
  </get>
</rest>

REST DSL 构建器为您提供了以下 URL 映射:

/say/hello
/say/bye

基本路径是可选的。如果您愿意,您可以在每个 动词条款中指定完整路径:

rest()
    .get("/say/hello").to("direct:hello")
    .get("/say/bye").to("direct:bye");

使用动态进行

REST DSL 支持使用 toD 动态参数。使用此参数指定 URI。

例如,在 JMS 中,可以通过以下方式定义动态端点 URI:

public void configure() throws Exception {
   rest("/say")
     .get("/hello/{language}").toD("jms:queue:hello-${header.language}");
}

在 XML DSL 中,同样的详情如下:

<rest uri="/say">
  <get uri="/hello//{language}">
    <toD uri="jms:queue:hello-${header.language}"/>
  </get>
<rest>

有关 toD 动态参数的更多信息,请参阅 “动态到”一节

URI 模板

在动词参数中,您可以指定一个 URI 模板,您可以在命名的属性中捕获特定路径片段(然后映射到 Camel 消息标头)。例如,如果您想要对 Hello World 应用进行个性化,以便它根据名称设置调用者,您可以定义一个 REST 服务,如下所示:

rest("/say")
    .get("/hello/{name}").to("direct:hello")
    .get("/bye/{name}").to("direct:bye");

from("direct:hello")
    .transform().simple("Hello ${header.name}");
from("direct:bye")
    .transform().simple("Bye ${header.name}");

URI 模板捕获 {name} 路径片段的文本,并将这个捕获的文本复制到 名称 消息标头中。如果您通过发送 URL 以 /say/hello/Joe 结尾的 GET HTTP 请求调用服务,则 HTTP 响应为 Hello Joe

嵌入式路由语法

您可以使用 to() 关键字(Java DSL)或 to 元素(XML DSL)来终止 verb 子句,而是可以选择直接使用 Apache Camel 路由到 REST DSL 中,使用 route() 关键字(Java DSL)或 路由 元素(XML DSL)。route() 关键字允许您将路由嵌入到 verb 子句中,语法如下:

RESTVerbClause.route("...").CamelRoute.endRest()

其中 endRest() 关键字(仅限 Java DSL)是一个必要的标点符号,可让您分隔操作动词子句(当 rest() 构建器中有多个动词子句时)。

例如,您可以重构 Hello World 示例以使用嵌入的 Camel 路由,如下为 Java DSL:

rest("/say")
    .get("/hello").route().transform().constant("Hello World").endRest()
    .get("/bye").route().transform().constant("Bye World");

如下在 XML DSL 中:

<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  ...
  <rest path="/say">
    <get uri="/hello">
      <route>
        <transform>
          <constant>Hello World</constant>
        </transform>
      </route>
    </get>
    <get uri="/bye">
      <route>
        <transform>
          <constant>Bye World</constant>
        </transform>
      </route>
    </get>
  </rest>
</camelContext>
注意

如果您在当前 CamelContext 中定义任何异常子句(使用 Exception ())或拦截器(使用拦截器()),这些异常子句和拦截器在嵌入式路由中也处于活动状态。

REST DSL 和 HTTP 传输组件

如果您没有显式配置 HTTP 传输组件,则 REST DSL 会自动发现通过检查类路径上的可用组件来使用的 HTTP 组件。REST DSL 查找任何 HTTP 组件的默认名称,并使用它找到的第一个名称。如果类路径中没有 HTTP 组件,并且您没有显式配置 HTTP 传输,则默认 HTTP 组件为 camel-http

指定请求和响应的内容类型

您可以使用 consume () 过滤 HTTP 请求和响应 的内容类型,并在 Java 中生成s() 选项,或者在 XML 中生成属性。 例如,一些常见内容类型(官方称为 互联网介质类型)如下:

  • text/plain
  • text/html
  • text/xml
  • application/json
  • application/xml

内容类型在 REST DSL 中以 verb 子句的形式提供。例如,若要将 verb 子句限制为仅接受 text/plain HTTP 请求,并且仅发送 文本/html HTTP 响应,您需要使用类似如下的 Java 代码:

rest("/email")
    .post("/to/{recipient}").consumes("text/plain").produces("text/html").to("direct:foo");

在 XML 中,您可以设置 使用 并生成 属性,如下所示:

<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  ...
  <rest path="/email">
    <post uri="/to/{recipient}" consumes="text/plain" produces="text/html">
      <to "direct:foo"/>
    </get>
  </rest>
</camelContext>

您还可以指定使用 ()的参数,或以逗号分隔列表的形式 生成。例如,使用 ("text/plain, application/json")

其他 HTTP 方法

一些 HTTP 服务器实施支持额外的 HTTP 方法,它们不是由 REST DSL 中标准动词集合提供的,即 get(), head() , put (), post (), delete (), patch().要访问其他 HTTP 方法,您可以在 XML DSL 中使用 generic 关键字 verb() 和通用元素 动词

例如,要在 Java 中实施 DIB HTTP 方法:

rest("/say")
    .verb("TRACE", "/hello").route().transform();

这里的 transform()IN 消息正文复制到 OUT 消息的正文,以回显 HTTP 请求。

在 XML 中实施 DIB HTTP 方法:

<camelContext xmlns="http://camel.apache.org/schema/blueprint">
  ...
  <rest path="/say">
    <verb uri="/hello" method="TRACE">
      <route>
        <transform/>
      </route>
    </get>
</camelContext>

定义自定义 HTTP 错误消息

如果您的 REST 服务需要发送错误消息作为其响应,您可以定义自定义 HTTP 错误消息,如下所示:

  1. 通过将 Exchange.HTTP_RESPONSE_CODE 标头密钥设置为错误代码值来指定 HTTP 错误代码(例如,400404 等)。此设置指示您要发送错误消息回复的 REST DSL,而不是定期响应。
  2. 将消息正文填充自定义错误消息。
  3. 如果需要,设置 Content-Type 标头。
  4. 如果您的 REST 服务被配置为为来自 Java 对象和从 Java 对象(启用 绑定Mode ),您应该确定启用了 skipBindingOnErrorCode 选项(默认为 )。这是为了确保 REST DSL 在发送响应时不会尝试解压缩消息正文。

    有关对象绑定的详情,请参阅 第 4.3 节 “摘要到 Java 对象以及”

以下 Java 示例演示了如何定义自定义错误消息:

// Java
// Configure the REST DSL, with JSON binding mode
restConfiguration().component("restlet").host("localhost").port(portNum).bindingMode(RestBindingMode.json);

// Define the service with REST DSL
rest("/users/")
    .post("lives").type(UserPojo.class).outType(CountryPojo.class)
        .route()
            .choice()
                .when().simple("${body.id} < 100")
                    .bean(new UserErrorService(), "idTooLowError")
                .otherwise()
                    .bean(new UserService(), "livesWhere");

在这个示例中,如果输入 ID 小于 100,则返回自定义错误消息,使用 UserErrorService bean,它实现:

// Java
public class UserErrorService {
    public void idTooLowError(Exchange exchange) {
        exchange.getIn().setBody("id value is too low");
        exchange.getIn().setHeader(Exchange.CONTENT_TYPE, "text/plain");
        exchange.getIn().setHeader(Exchange.HTTP_RESPONSE_CODE, 400);
    }
}

UserErrorService bean 中,我们定义自定义错误消息,并将 HTTP 错误代码设置为 400

参数默认值

可以为传入 Camel 消息的标头指定默认值。

您可以使用一个键单词(如 query 参数的 verbose )来指定默认值。例如,在下面的代码中,默认值为 false。这意味着,如果没有为带有 verbose 键的标头提供其他值,则会将 false 插入为默认值。

rest("/customers/")
    .get("/{id}").to("direct:customerDetail")
    .get("/{id}/orders")
      .param()
	.name("verbose")
	.type(RestParamType.query)
	.defaultValue("false")
	.description("Verbose order details")
      .endParam()
        .to("direct:customerOrders")
    .post("/neworder").to("direct:customerNewOrder");

将 JsonParserException 嵌套在自定义 HTTP 错误消息中

您可能希望返回自定义错误消息的一个常见情形是嵌套 JsonParserException 异常。例如,您可以方便地利用 Camel 异常处理机制来创建自定义 HTTP 错误消息,其 HTTP 错误代码 400,如下所示:

// Java
onException(JsonParseException.class)
    .handled(true)
    .setHeader(Exchange.HTTP_RESPONSE_CODE, constant(400))
    .setHeader(Exchange.CONTENT_TYPE, constant("text/plain"))
    .setBody().constant("Invalid json data");

REST DSL 选项

通常,可以直接将 REST DSL 选项应用到服务定义的基本部分(即,立即遵循 rest()),如下所示:

rest("/email").consumes("text/plain").produces("text/html")
    .post("/to/{recipient}").to("direct:foo")
    .get("/for/{username}").to("direct:bar");

在这种情形中,如果指定选项应用到所有从属动词子句。或者可将选项应用到每个单独的 verb 子句,如下所示:

rest("/email")
    .post("/to/{recipient}").consumes("text/plain").produces("text/html").to("direct:foo")
    .get("/for/{username}").consumes("text/plain").produces("text/html").to("direct:bar");

如果指定选项仅适用于相关的 动词子,则从基础部分覆盖任何设置。

表 4.1 “REST DSL 选项” 总结 REST DSL 支持的选项。

表 4.1. REST DSL 选项

Java DSLXML DSL描述

bindingMode()

@bindingMode

指定绑定模式,可用于将传入消息放入 Java 对象(以及可选的、unmarshal Java 对象到传出消息)。可以具有以下值: off (默认)、autojsonxmljson_xml

consumes()

@consumes

限制 verb 子句,以仅接受 HTTP 请求中的指定互联网介质类型(MIME 类型)。典型的值有: text/plain,text/http,text/xml,application/json,application/xml.

customId()

@customId

定义用于 JMX 管理的自定义 ID。

description()

description

记录 REST 服务或动词条款。对于 JMX 管理工具非常有用。

enableCORS()

@enableCORS

如果为 true,请在 HTTP 响应中启用 CORS(跨原始资源共享)标头。默认为 false

id()

@id

为 REST 服务定义唯一 ID,它可用于定义 JMX 管理和其他工具。

method()

@method

指定此动词子句处理的 HTTP 方法。通常与 generic verb() 关键字一起使用。

outType()

@outType

启用对象绑定(即启用 bindingMode 选项时),这个选项指定代表 HTTP 响应 消息的 Java 类型。

produces()

produces

限制 verb 子句,使其仅在 HTTP 响应中仅生成指定的互联网介质类型(MIME 类型)。典型的值有: text/plain,text/http,text/xml,application/json,application/xml.

type()

@type

启用对象绑定(即启用 bindingMode 选项时),这个选项指定代表 HTTP Request 消息的 Java 类型。

VerbURIArgument

@uri

指定路径片段或 URI 模板,作为动词的参数。例如,get(VerbURIArgument)

BasePathArgument

@path

指定 rest() 关键字(Java DSL)或 rest 元素(XML DSL)中的基本路径。