Apache Camel REST 端点不返回最终主体
Apache Camel REST endpoint not returning the final body
我已经声明了一个 REST 端点,它使用 direct
调用另一个路由。在第二条路线的末尾,我正在记录正文,但返回到浏览器的不是同一个正文。
这是一个重现该行为的小示例(我使用的是带 Spring 引导的 Apache Camel):
@Component
public class EntidadeRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
restConfiguration().bindingMode(json);
rest("/entidade")
.get("/{id}")
.to("direct:search-by-id");
from("direct:search-by-id")
.routeId("search-by-id")
.setProperty("idEntidade", simple("${header.id}"))
.pollEnrich("file:files/mock/dados?noop=true&idempotent=false")
.unmarshal().json(JsonLibrary.Jackson)
.split(jsonpath("$"))
.filter(jsonpath("$[?(@.id == ${property.idEntidade})]"))
.marshal().json(JsonLibrary.Jackson)
.log("${body}");
}
}
我使用 URL 从浏览器调用它:
http://localhost:8080/camel/entidade/1 .
在文件夹 files/mock/dados
上,我有一个名为 entidades.json
的文件,其中有一个 JSON 数组(见下文)。
我知道拆分和过滤器正在工作,因为我在最后一行代码中记录正文,这就是日志中显示的内容:
2021-04-28 18:15:15.707 INFO 3905542 --- [nio-8080-exec-1] search-by-id : {"id":"1","tipo":"PF","nome":"João Maria"}
但这是返回给浏览器的内容(entidades.json
文件的确切内容):
[{"id":"1","tipo":"PF","nome":"João Maria"},{"id":"2","tipo":"PF","nome":"Maria João"},{"id":"3","tipo":"PF","nome":"João Silva"},{"id":"4","tipo":"PF","nome":"José Souza"}]
为什么登录的正文与浏览器中显示的不一样并修复它?
PS:如果我删除那些编组和解组调用,我会在浏览器中收到以下错误:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.apache.camel.component.file.FileBinding and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.apache.camel.component.file.GenericFile["binding"])
没有输入文件并且不知道您调用的具体 URI(包括路径变量的给定值 {id}
),我只能怀疑如下问题:
- 您是否在 GET 调用结束时提供了 id?
- 为什么要转换 JSON?
- 您测试了正确的拆分吗?你又聚合了吗?
- 是否要记录每条消息?
REST 端点
您将端点指定为 GET /entidada/{id}
。
{id}
是路径变量。
因此,假设您使用 1 作为 ID 调用 GET /entidata/1
。
然后您的 JSON 文件被轮询(读取),解组为 ... ?
JSONunmarshal/marshal
这些 unmarshal
和 marshal
方法要么用于在不同数据格式之间转换,要么用于从数据表示转换为 Java-对象 (POJO),如果它们在内部使用(例如传递给处理器 Bean 等)。
我想文件 dados
包含文本数据 JSON。
因此,您可以简单地将此文本读入(文本!)消息正文(如文本消息,比较 JMS 等)并使用它:(a)按 JSON-path 拆分,(b)过滤JSON-路径,(c) 记录此 JSON 结果,(d) 将其作为 HTTP 响应发送回调用客户端(例如您的浏览器)。
拆分会发生什么?
在此之后你尝试拆分(假设你有一个 JSON-array):
// incoming:
// a single enriched message, in body: JSON-array with 4 elements
.split(jsonpath("$")) // what do you expect as output ?
// here the split usually should be ended using `.end` DSL method
.filter(jsonpath("$[?(@.id == ${property.idEntidade})]")) // each array element of JSON body matched against id, e.g. 1
我已将您的 HTTP 响应(JSON 包含 4 个人的数组)填入在线 JSON-path evaluator。 $
的评估不是拆分,而是单个元素(在结果数组内):完全是原始数组(有 4 个人)。
- 为什么文件没有分成 4 条消息,每条消息都包含一个人物对象?
- 因为您的 JSON-路径
$
仅表示 根元素 .
加:通常在 .split()
后跟一个 .end()
再次聚合它们。
你把它漏掉了。我想这也是一个问题。
过滤器按预期工作
稍后您过滤了 REST 给定的 id:
.filter(jsonpath("$[?(@.id == ${property.idEntidade})]"))`
这导致记录的元素:
{"id":"1","tipo":"PF","nome":"João Maria"}
过滤器成功运行:只留下一个 ID 为 1
的过滤器。
登录 Camel
登录 EIP
将.log()
添加到路由链时,这意味着您正在使用Log EIP。在其文档中给出警告提示:
Logging message body with streamed messages:
If the message body is stream based, then logging the message body, may cause the message body to be empty afterwards. See this FAQ. For streamed messages you can use Stream caching to allow logging the message body and be able to read the message body afterwards again.
因此,您的空日志消息可能是由使用此功能记录基于流的消息正文时的副作用引起的。
日志方式的区别
在第 Difference between log in the DSL and Log component 节中解释:
The log DSL is much lighter and meant for logging human logs such as Starting to do …
etc.
下面的例子(根据您的 REST 路由调整)说明了它的用法:
rest("/entidade")
.get("/{id}")
.log("Processing ${id}")
.to("bean:foo");
日志组件
我建议使用标准 Log 组件,只需使用 .to()
DSL 传递基于架构 log:
的 URI 字符串以及所需的参数 loggerName.
.to("log:filtered-json")
这里 Log 组件的 URI 前缀是 log:
。使用 loggerName filtered-json
.
记录流的每条消息
错误是我需要将 AggregationStrategy
传递给 split
。我还需要停止记录正文,因为它正在消耗 InputStream。之后,我可以安全地删除那些 marshal 和 unmarshal 调用。
这是最终代码:
@Component
public class EntidadeRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
restConfiguration().bindingMode(json);
rest("/entidade")
.get("/{id}")
.to("direct:search-by-id");
from("direct:search-by-id")
.routeId("search-by-id")
.setProperty("idEntidade", simple("${header.id}"))
.pollEnrich("file:files/mock/dados?noop=true&idempotent=false")
.split(jsonpath("$"), takeFirst(Exchange.FILTER_MATCHED))
.filter(jsonpath("$[?(@.id == ${property.idEntidade})]")).stop()
.end();
}
private AggregationStrategy takeFirst(String matchProperty) {
return ((oldExchange, newExchange) -> {
if (oldExchange == null && newExchange.getProperty(matchProperty, Boolean.class)) {
oldExchange = newExchange;
}
return oldExchange;
});
}
}
我已经声明了一个 REST 端点,它使用 direct
调用另一个路由。在第二条路线的末尾,我正在记录正文,但返回到浏览器的不是同一个正文。
这是一个重现该行为的小示例(我使用的是带 Spring 引导的 Apache Camel):
@Component
public class EntidadeRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
restConfiguration().bindingMode(json);
rest("/entidade")
.get("/{id}")
.to("direct:search-by-id");
from("direct:search-by-id")
.routeId("search-by-id")
.setProperty("idEntidade", simple("${header.id}"))
.pollEnrich("file:files/mock/dados?noop=true&idempotent=false")
.unmarshal().json(JsonLibrary.Jackson)
.split(jsonpath("$"))
.filter(jsonpath("$[?(@.id == ${property.idEntidade})]"))
.marshal().json(JsonLibrary.Jackson)
.log("${body}");
}
}
我使用 URL 从浏览器调用它: http://localhost:8080/camel/entidade/1 .
在文件夹 files/mock/dados
上,我有一个名为 entidades.json
的文件,其中有一个 JSON 数组(见下文)。
我知道拆分和过滤器正在工作,因为我在最后一行代码中记录正文,这就是日志中显示的内容:
2021-04-28 18:15:15.707 INFO 3905542 --- [nio-8080-exec-1] search-by-id : {"id":"1","tipo":"PF","nome":"João Maria"}
但这是返回给浏览器的内容(entidades.json
文件的确切内容):
[{"id":"1","tipo":"PF","nome":"João Maria"},{"id":"2","tipo":"PF","nome":"Maria João"},{"id":"3","tipo":"PF","nome":"João Silva"},{"id":"4","tipo":"PF","nome":"José Souza"}]
为什么登录的正文与浏览器中显示的不一样并修复它?
PS:如果我删除那些编组和解组调用,我会在浏览器中收到以下错误:
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.apache.camel.component.file.FileBinding and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: org.apache.camel.component.file.GenericFile["binding"])
没有输入文件并且不知道您调用的具体 URI(包括路径变量的给定值 {id}
),我只能怀疑如下问题:
- 您是否在 GET 调用结束时提供了 id?
- 为什么要转换 JSON?
- 您测试了正确的拆分吗?你又聚合了吗?
- 是否要记录每条消息?
REST 端点
您将端点指定为 GET /entidada/{id}
。
{id}
是路径变量。
因此,假设您使用 1 作为 ID 调用 GET /entidata/1
。
然后您的 JSON 文件被轮询(读取),解组为 ... ?
JSONunmarshal/marshal
这些 unmarshal
和 marshal
方法要么用于在不同数据格式之间转换,要么用于从数据表示转换为 Java-对象 (POJO),如果它们在内部使用(例如传递给处理器 Bean 等)。
我想文件 dados
包含文本数据 JSON。
因此,您可以简单地将此文本读入(文本!)消息正文(如文本消息,比较 JMS 等)并使用它:(a)按 JSON-path 拆分,(b)过滤JSON-路径,(c) 记录此 JSON 结果,(d) 将其作为 HTTP 响应发送回调用客户端(例如您的浏览器)。
拆分会发生什么?
在此之后你尝试拆分(假设你有一个 JSON-array):
// incoming:
// a single enriched message, in body: JSON-array with 4 elements
.split(jsonpath("$")) // what do you expect as output ?
// here the split usually should be ended using `.end` DSL method
.filter(jsonpath("$[?(@.id == ${property.idEntidade})]")) // each array element of JSON body matched against id, e.g. 1
我已将您的 HTTP 响应(JSON 包含 4 个人的数组)填入在线 JSON-path evaluator。 $
的评估不是拆分,而是单个元素(在结果数组内):完全是原始数组(有 4 个人)。
- 为什么文件没有分成 4 条消息,每条消息都包含一个人物对象?
- 因为您的 JSON-路径
$
仅表示 根元素 .
加:通常在 .split()
后跟一个 .end()
再次聚合它们。
你把它漏掉了。我想这也是一个问题。
过滤器按预期工作
稍后您过滤了 REST 给定的 id:
.filter(jsonpath("$[?(@.id == ${property.idEntidade})]"))`
这导致记录的元素:
{"id":"1","tipo":"PF","nome":"João Maria"}
过滤器成功运行:只留下一个 ID 为 1
的过滤器。
登录 Camel
登录 EIP
将.log()
添加到路由链时,这意味着您正在使用Log EIP。在其文档中给出警告提示:
Logging message body with streamed messages:
If the message body is stream based, then logging the message body, may cause the message body to be empty afterwards. See this FAQ. For streamed messages you can use Stream caching to allow logging the message body and be able to read the message body afterwards again.
因此,您的空日志消息可能是由使用此功能记录基于流的消息正文时的副作用引起的。
日志方式的区别
在第 Difference between log in the DSL and Log component 节中解释:
The log DSL is much lighter and meant for logging human logs such as
Starting to do …
etc.
下面的例子(根据您的 REST 路由调整)说明了它的用法:
rest("/entidade")
.get("/{id}")
.log("Processing ${id}")
.to("bean:foo");
日志组件
我建议使用标准 Log 组件,只需使用 .to()
DSL 传递基于架构 log:
的 URI 字符串以及所需的参数 loggerName.
.to("log:filtered-json")
这里 Log 组件的 URI 前缀是 log:
。使用 loggerName filtered-json
.
错误是我需要将 AggregationStrategy
传递给 split
。我还需要停止记录正文,因为它正在消耗 InputStream。之后,我可以安全地删除那些 marshal 和 unmarshal 调用。
这是最终代码:
@Component
public class EntidadeRoute extends RouteBuilder {
@Override
public void configure() throws Exception {
restConfiguration().bindingMode(json);
rest("/entidade")
.get("/{id}")
.to("direct:search-by-id");
from("direct:search-by-id")
.routeId("search-by-id")
.setProperty("idEntidade", simple("${header.id}"))
.pollEnrich("file:files/mock/dados?noop=true&idempotent=false")
.split(jsonpath("$"), takeFirst(Exchange.FILTER_MATCHED))
.filter(jsonpath("$[?(@.id == ${property.idEntidade})]")).stop()
.end();
}
private AggregationStrategy takeFirst(String matchProperty) {
return ((oldExchange, newExchange) -> {
if (oldExchange == null && newExchange.getProperty(matchProperty, Boolean.class)) {
oldExchange = newExchange;
}
return oldExchange;
});
}
}