Skip to content

Commit bbf680a

Browse files
oscerdclaude
andauthored
CAMEL-24084: Apply header filter strategy to structured-mode CloudEvent extension headers (#24724)
The Knative consumer filters inbound HTTP headers through KnativeHttpHeaderFilterStrategy, but structured content mode (application/cloudevents+json) mapped the remaining event fields (extensions) onto the message headers without a HeaderFilterStrategy, inconsistent with the binary/HTTP-header path. Route structured-mode extension fields through a HeaderFilterStrategy so Camel* headers (matched case-insensitively) are filtered consistently on both content modes. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2ff8c7d commit bbf680a

4 files changed

Lines changed: 94 additions & 13 deletions

File tree

components/camel-knative/camel-knative-component/src/main/java/org/apache/camel/component/knative/ce/AbstractCloudEventProcessor.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,31 @@
1919
import java.io.InputStream;
2020
import java.time.ZonedDateTime;
2121
import java.time.format.DateTimeFormatter;
22+
import java.util.Locale;
2223
import java.util.Map;
2324
import java.util.Objects;
2425
import java.util.function.Supplier;
2526

2627
import org.apache.camel.Exchange;
28+
import org.apache.camel.Message;
2729
import org.apache.camel.Processor;
2830
import org.apache.camel.cloudevents.CloudEvent;
2931
import org.apache.camel.component.knative.KnativeEndpoint;
3032
import org.apache.camel.component.knative.spi.Knative;
3133
import org.apache.camel.component.knative.spi.KnativeResource;
34+
import org.apache.camel.spi.HeaderFilterStrategy;
35+
import org.apache.camel.support.DefaultHeaderFilterStrategy;
3236
import org.apache.camel.util.StringHelper;
3337
import org.slf4j.Logger;
3438
import org.slf4j.LoggerFactory;
3539

3640
abstract class AbstractCloudEventProcessor implements CloudEventProcessor {
3741
private final CloudEvent cloudEvent;
3842

43+
// Filters internal Camel headers (Camel*/camel*) from structured-mode CloudEvent extensions, keeping the
44+
// extension-to-header mapping consistent with the binary content-mode path (see KnativeHttpHeaderFilterStrategy).
45+
private final HeaderFilterStrategy headerFilterStrategy = new DefaultHeaderFilterStrategy();
46+
3947
protected AbstractCloudEventProcessor(CloudEvent cloudEvent) {
4048
this.cloudEvent = cloudEvent;
4149
}
@@ -72,6 +80,17 @@ public Processor consumer(KnativeEndpoint endpoint, KnativeResource service) {
7280

7381
protected abstract void decodeStructuredContent(Exchange exchange, Map<String, Object> content);
7482

83+
/**
84+
* Maps a structured-mode CloudEvent extension field to a message header, applying the header filter strategy so
85+
* that internal Camel ({@code Camel*}) headers are filtered consistently with the binary content-mode path.
86+
*/
87+
protected void mapExtensionAsHeader(Message message, Exchange exchange, String key, Object value) {
88+
final String headerName = key.toLowerCase(Locale.US);
89+
if (!headerFilterStrategy.applyFilterToExternalHeaders(headerName, value, exchange)) {
90+
message.setHeader(headerName, value);
91+
}
92+
}
93+
7594
@Override
7695
public Processor producer(KnativeEndpoint endpoint, KnativeResource service) {
7796
final CloudEvent ce = cloudEvent();

components/camel-knative/camel-knative-component/src/main/java/org/apache/camel/component/knative/ce/CloudEventProcessors.java

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616
*/
1717
package org.apache.camel.component.knative.ce;
1818

19-
import java.util.Locale;
2019
import java.util.Map;
2120
import java.util.Objects;
2221

@@ -52,11 +51,10 @@ protected void decodeStructuredContent(Exchange exchange, Map<String, Object> co
5251
}
5352

5453
//
55-
// Map every remaining field as it is (extensions).
54+
// Map every remaining field as it is (extensions), applying the header filter
55+
// strategy so internal Camel headers are filtered consistently with binary mode.
5656
//
57-
content.forEach((key, val) -> {
58-
message.setHeader(key.toLowerCase(Locale.US), val);
59-
});
57+
content.forEach((key, val) -> mapExtensionAsHeader(message, exchange, key, val));
6058
}
6159
}),
6260
v1_0_1(new AbstractCloudEventProcessor(CloudEvents.v1_0_1) {
@@ -79,11 +77,10 @@ protected void decodeStructuredContent(Exchange exchange, Map<String, Object> co
7977
}
8078

8179
//
82-
// Map every remaining field as it is (extensions).
80+
// Map every remaining field as it is (extensions), applying the header filter
81+
// strategy so internal Camel headers are filtered consistently with binary mode.
8382
//
84-
content.forEach((key, val) -> {
85-
message.setHeader(key.toLowerCase(Locale.US), val);
86-
});
83+
content.forEach((key, val) -> mapExtensionAsHeader(message, exchange, key, val));
8784
}
8885
}),
8986
v1_0_2(new AbstractCloudEventProcessor(CloudEvents.v1_0_2) {
@@ -106,11 +103,10 @@ protected void decodeStructuredContent(Exchange exchange, Map<String, Object> co
106103
}
107104

108105
//
109-
// Map every remaining field as it is (extensions).
106+
// Map every remaining field as it is (extensions), applying the header filter
107+
// strategy so internal Camel headers are filtered consistently with binary mode.
110108
//
111-
content.forEach((key, val) -> {
112-
message.setHeader(key.toLowerCase(Locale.US), val);
113-
});
109+
content.forEach((key, val) -> mapExtensionAsHeader(message, exchange, key, val));
114110
}
115111
});
116112

components/camel-knative/camel-knative-http/src/test/java/org/apache/camel/component/knative/http/KnativeHttpTest.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,61 @@ void testConsumeStructuredContent(CloudEvent ce) throws Exception {
379379
mock.assertIsSatisfied();
380380
}
381381

382+
@ParameterizedTest
383+
@EnumSource(CloudEvents.class)
384+
void testConsumeStructuredContentFiltersInternalHeaders(CloudEvent ce) throws Exception {
385+
configureKnativeComponent(
386+
context,
387+
ce,
388+
sourceEndpoint(
389+
"myEndpoint",
390+
Map.of(
391+
Knative.KNATIVE_CLOUD_EVENT_TYPE, "org.apache.camel.event",
392+
Knative.CONTENT_TYPE, "text/plain")));
393+
394+
RouteBuilder.addRoutes(context, b -> {
395+
b.from("knative:endpoint/myEndpoint")
396+
.to("mock:ce");
397+
});
398+
399+
context.start();
400+
401+
MockEndpoint mock = context.getEndpoint("mock:ce", MockEndpoint.class);
402+
mock.expectedBodiesReceived("test");
403+
mock.expectedMessageCount(1);
404+
// a regular CloudEvent extension must still be propagated as a message header
405+
mock.expectedHeaderReceived("myextension", "myvalue");
406+
// an extension that resolves to an internal Camel header must be filtered out and must not
407+
// be able to inject a framework control header (headers are matched case-insensitively)
408+
mock.expectedMessagesMatches(e -> e.getMessage().getHeader(Exchange.FILE_NAME) == null);
409+
410+
if (Objects.equals(CloudEvents.v1_0.version(), ce.version())
411+
|| Objects.equals(CloudEvents.v1_0_1.version(), ce.version())
412+
|| Objects.equals(CloudEvents.v1_0_2.version(), ce.version())) {
413+
given()
414+
.contentType(Knative.MIME_STRUCTURED_CONTENT_MODE)
415+
.body(
416+
Map.of(
417+
"specversion", ce.version(),
418+
"type", "org.apache.camel.event",
419+
"id", "myEventID",
420+
"source", "/somewhere",
421+
"datacontenttype", "text/plain",
422+
"myextension", "myvalue",
423+
"camelfilename", "injected.txt",
424+
"data", "test"),
425+
ObjectMapperType.JACKSON_2)
426+
.when()
427+
.post()
428+
.then()
429+
.statusCode(200);
430+
} else {
431+
throw new IllegalArgumentException("Unknown CloudEvent spec: " + ce.version());
432+
}
433+
434+
mock.assertIsSatisfied();
435+
}
436+
382437
@ParameterizedTest
383438
@EnumSource(CloudEvents.class)
384439
void testConsumeContent(CloudEvent ce) throws Exception {

docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_22.adoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,17 @@ the message, consistent with the inbound header filtering already performed by t
227227
Ordinary application headers are unaffected. If a route relied on `Camel*` headers being propagated from the MIME
228228
content, set them explicitly after unmarshalling.
229229

230+
=== camel-knative - structured-mode CloudEvent header filtering
231+
232+
When consuming a CloudEvent in structured content mode (`application/cloudevents+json`), the Knative component now
233+
applies a `HeaderFilterStrategy` to the event fields (extensions) mapped from the payload onto the Camel message.
234+
Camel-internal headers (the `Camel*` namespace, matched case-insensitively) present as structured-event fields are
235+
no longer mapped onto the message, consistent with the inbound header filtering already performed on the binary
236+
content-mode / HTTP header path.
237+
238+
Ordinary CloudEvent extension attributes are unaffected. If a route relied on `Camel*`-named fields being
239+
propagated from the structured payload, set them explicitly after consuming the event.
240+
230241
=== camel-pinecone
231242

232243
The `tls` endpoint option now correctly documents its default as `false` (previously the catalog

0 commit comments

Comments
 (0)