spring-cloud / spring-cloud/spring-cloud-function
Structured CloudEvent fails to convert to `io.cloudevents.CloudEvent` in the functional model (data lost / "Unknown encoding")
まだ誰も着手していません。
- 主要言語
- Java
- スター
- 1.1k
- フォーク
- 641
- 平均マージ
- 11時間 2分
- マージ済み PR(30日)
- 8
説明
Describe the bug
When a structured-mode CloudEvent (Content-Type: application/cloudevents+json, whole envelope in the body) is consumed by a function whose target type is the CNCF io.cloudevents.CloudEvent, message conversion fails.
CloudEventMessageUtils.toCanonical(...) transforms the structured message into a binary-shaped message (data extracted to the payload, attributes moved to ce-* headers), but:
- it does not reset the
contentTypeheader — the outgoing message keepsapplication/cloudevents+jsoneven though the payload is no longer the serialized envelope; and - it leaves the extracted
dataas a deserializedMap, not serialized bytes.
Downstream, the CNCF io.cloudevents.spring.messaging.CloudEventMessageConverter re-decides structured-vs-binary purely from contentType. Because the stale application/cloudevents+json remains, it takes the structured reader and tries to re-parse the (already extracted) payload as the envelope:
java.lang.IllegalArgumentException: argument "src" is null
at io.cloudevents.jackson.JsonFormat.deserialize(JsonFormat.java:158)
at io.cloudevents.spring.messaging.CloudEventMessageConverter.fromMessage(CloudEventMessageConverter.java:45)
at org.springframework.cloud.function.context.config.SmartCompositeMessageConverter.fromMessage(...)
at ...SimpleFunctionRegistry$FunctionInvocationWrapper.convertInputMessageIfNecessary(...)
and
io.cloudevents.rw.CloudEventRWException: Could not parse. Unknown encoding. Invalid content type or spec version
at io.cloudevents.core.message.impl.MessageUtils.parseStructuredOrBinaryMessage(MessageUtils.java:69)
at io.cloudevents.spring.messaging.CloudEventMessageConverter.createMessageReader(CloudEventMessageConverter.java:60)
at io.cloudevents.spring.messaging.CloudEventMessageConverter.fromMessage(CloudEventMessageConverter.java:45)
at org.springframework.cloud.function.context.config.SmartCompositeMessageConverter.fromMessage(SmartCompositeMessageConverter.java:137)
at org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry$FunctionInvocationWrapper.convertInputMessageIfNecessary(SimpleFunctionRegistry.java:1488)
Even after the contentType is corrected so the binary reader is selected, a second symptom remains: the CNCF binary reader only reads byte[]/String payloads (CloudEventMessageConverter#getBinaryData returns null for anything else), so the Map payload is dropped and the delivered cloudEvent.getData() is null while the attributes are present.
This only affects the io.cloudevents.CloudEvent target type. A plain POJO target works, because Spring's own message converters can bind a Map payload to a POJO — which is why the existing CloudEventFunctionTests#testStructuredPojoToPojoDefaultOutputAttributeProvider passes and this case is not covered.
Affected versions
spring-cloud-function-context:4.3.x.CloudEventMessageUtils.toCanonical/buildBinaryMessageFromStructuredMapare byte-identical across these.io.cloudevents:cloudevents-spring:2.2.1(the optional dependency declared byspring-cloud-function-context);- Java: 21.
Sample CloudEvent (structured mode)
Content-Type: application/cloudevents+json, body:
{
"specversion" : "1.0",
"type" : "org.springframework",
"source" : "https://spring.io/",
"id" : "A234-1234-1234",
"datacontenttype" : "application/json",
"data" : {
"version" : "1.0",
"releaseName" : "Spring Framework",
"releaseDate" : "24-03-2004"
}
}
Steps to reproduce
Minimal function with an io.cloudevents.CloudEvent input type (the cloudevents-spring converter is auto-registered by ContextFunctionCatalogAutoConfiguration$CloudEventsMessageConverterConfiguration):
@EnableAutoConfiguration
@Configuration
static class TestConfiguration {
@Bean
Function<Message<CloudEvent>, Message<CloudEvent>> echoCloudEvent() {
return Function.identity();
}
}
@Test
void structuredToCloudEventTypedPayload() {
String payload = /* the structured JSON envelope above */;
Function<Object, Object> function = context.getBean(FunctionCatalog.class).lookup("echoCloudEvent");
Message<String> inputMessage = MessageBuilder
.withPayload(payload)
.setHeader(MessageHeaders.CONTENT_TYPE, "application/cloudevents+json")
.build();
// structured mode: attributes are in the body, not the headers
assertThat(CloudEventMessageUtils.isCloudEvent(inputMessage)).isFalse();
Message<CloudEvent> result = (Message<CloudEvent>) function.apply(inputMessage); // throws
}
Actual behavior
function.apply(...) throws CloudEventRWException: Could not parse. Unknown encoding during input conversion. (If contentType is corrected in isolation, the call succeeds but the delivered CloudEvent has getData() == null.)
Expected behavior
The function receives a fully-populated io.cloudevents.CloudEvent — correct specversion, type, source, id, datacontenttype, and non-null data — consistent with how the same structured event is handled when the target type is a POJO.
Root cause
In CloudEventMessageUtils.buildBinaryMessageFromStructuredMap(...), after extracting data and moving attributes to ce-* headers, the original headers are copied back onto the outgoing message — re-applying contentType=application/cloudevents+json — and the data remains a Map. The binary-mode branch of toCanonical already resets contentType to the data content-type; the structured branch does not, and it does not serialize the data.
Proposed fix
In buildBinaryMessageFromStructuredMap, after building the binary-mode message:
- reset
MessageHeaders.CONTENT_TYPEto the data content-type (mirroring the binary-mode branch), and - re-serialize the extracted
datato bytes using the data content-type when it is not alreadybyte[]/String.
With both applied, the structured event converts correctly to io.cloudevents.CloudEvent (attributes + data), and all existing CloudEvent tests still pass. Happy to open a PR with a CloudEventFunctionTests case covering the io.cloudevents.CloudEvent target type.
コントリビューションガイド
はじめの一歩
- issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
- 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
- リポジトリをフォークし、ブランチを切って変更します。
- issue 番号を参照したプルリクエストを送ります。
調査の方向性
レポートで構造化イベントを再構築している箇所として特定されている CloudEventMessageUtils.buildBinaryMessageFromStructuredMap から始め、比較のために toCanonical のバイナリ分岐を確認します。io.cloudevents.CloudEvent を対象とする構造化イベントについて CloudEventFunctionTests にカバレッジを追加し、その後 CloudEvent のテストを実行します。変換が期待される属性と non-null の data で成功すれば完了です。
索引モデルが issue の本文から書いたものです。
評価
- 技術スタック
- java, spring
- 領域
- backend
- issue の種類
- バグ
- 難易度
- 2/5
- 見積もり時間
- 1〜3時間
- 活発さ
- 活発
- 明瞭さ
- 明確に書かれている
- 初心者へのやさしさ
- 78/100