Skip to content

Commit 5099b31

Browse files
Dave Syerolegz
andauthored
HTTP converters for CloudEvent in Spring (#312)
Supports MVC and WebFlux (blocking and non-blocking) HTTP. User can work with `CloudEvent` as a `POJO` type and inject it into `@ReqestMapping` methods. Signed-off-by: Dave Syer <[email protected]> Co-authored-by: Oleg Zhurakousky <[email protected]>
1 parent bcc1434 commit 5099b31

File tree

13 files changed

+952
-0
lines changed

13 files changed

+952
-0
lines changed

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@
7474
<module>http/vertx</module>
7575
<module>http/restful-ws</module>
7676
<module>kafka</module>
77+
<module>spring</module>
7778
</modules>
7879

7980
<properties>

spring/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## Spring Support
2+
3+
### Introduction
4+
5+
This module provides classes and interfaces that can be used by [Spring frameworks](https://spring.io/) and integrations to assist with Cloud Event processing.
6+
7+
Given that Spring defines [Message](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/messaging/Message.html) abstraction,
8+
which perfectly maps to the structure defined by Cloud Events specification, one may say Cloud Events are already supported by any Spring framework that
9+
relies on `Message`. So this modules provides several utilities and strategies to simplify working with Cloud Events in the context of Spring
10+
frameworks and integrations (see individual component's javadocs for more details).
11+
12+
Please see individual samples in `examples/spring` directory of this SDK for more details.

spring/pom.xml

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Copyright 2018-Present The CloudEvents Authors
4+
~ <p>
5+
~ Licensed under the Apache License, Version 2.0 (the "License");
6+
~ you may not use this file except in compliance with the License.
7+
~ You may obtain a copy of the License at
8+
~ <p>
9+
~ http://www.apache.org/licenses/LICENSE-2.0
10+
~ <p>
11+
~ Unless required by applicable law or agreed to in writing, software
12+
~ distributed under the License is distributed on an "AS IS" BASIS,
13+
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
~ See the License for the specific language governing permissions and
15+
~ limitations under the License.
16+
~
17+
-->
18+
<project xmlns="http://maven.apache.org/POM/4.0.0"
19+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
20+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
21+
<modelVersion>4.0.0</modelVersion>
22+
23+
<parent>
24+
<groupId>io.cloudevents</groupId>
25+
<artifactId>cloudevents-parent</artifactId>
26+
<version>2.0.0-SNAPSHOT</version>
27+
</parent>
28+
29+
<artifactId>cloudevents-spring</artifactId>
30+
<name>CloudEvents - support for Spring</name>
31+
<packaging>jar</packaging>
32+
33+
<properties>
34+
<module-name>io.cloudevents.spring</module-name>
35+
<spring-boot.version>2.4.0</spring-boot.version>
36+
</properties>
37+
38+
<dependencyManagement>
39+
<dependencies>
40+
<dependency>
41+
<groupId>org.springframework.boot</groupId>
42+
<artifactId>spring-boot-dependencies</artifactId>
43+
<version>${spring-boot.version}</version>
44+
<type>pom</type>
45+
<scope>import</scope>
46+
</dependency>
47+
</dependencies>
48+
</dependencyManagement>
49+
50+
<dependencies>
51+
<dependency>
52+
<groupId>org.springframework</groupId>
53+
<artifactId>spring-webmvc</artifactId>
54+
<optional>true</optional>
55+
</dependency>
56+
<dependency>
57+
<groupId>org.springframework</groupId>
58+
<artifactId>spring-webflux</artifactId>
59+
<optional>true</optional>
60+
</dependency>
61+
<dependency>
62+
<groupId>io.cloudevents</groupId>
63+
<artifactId>cloudevents-core</artifactId>
64+
<version>${project.version}</version>
65+
</dependency>
66+
<dependency>
67+
<groupId>io.cloudevents</groupId>
68+
<artifactId>cloudevents-http-basic</artifactId>
69+
<version>${project.version}</version>
70+
<!-- This will be optional too if we want to support messaging -->
71+
</dependency>
72+
<dependency>
73+
<groupId>org.apache.tomcat.embed</groupId>
74+
<artifactId>tomcat-embed-core</artifactId>
75+
<optional>true</optional>
76+
</dependency>
77+
78+
<!-- Test deps -->
79+
<dependency>
80+
<groupId>io.cloudevents</groupId>
81+
<artifactId>cloudevents-json-jackson</artifactId>
82+
<version>${project.version}</version>
83+
<scope>test</scope>
84+
</dependency>
85+
<dependency>
86+
<groupId>io.cloudevents</groupId>
87+
<artifactId>cloudevents-core</artifactId>
88+
<classifier>tests</classifier>
89+
<type>test-jar</type>
90+
<version>${project.version}</version>
91+
<scope>test</scope>
92+
</dependency>
93+
<dependency>
94+
<groupId>org.springframework.boot</groupId>
95+
<artifactId>spring-boot-starter-web</artifactId>
96+
<scope>test</scope>
97+
</dependency>
98+
<dependency>
99+
<groupId>org.springframework.boot</groupId>
100+
<artifactId>spring-boot-starter-test</artifactId>
101+
<scope>test</scope>
102+
</dependency>
103+
</dependencies>
104+
</project>
105+
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2019-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.cloudevents.spring.http;
17+
18+
import java.util.function.Consumer;
19+
import java.util.function.Supplier;
20+
21+
import io.cloudevents.CloudEvent;
22+
import io.cloudevents.CloudEventContext;
23+
import io.cloudevents.core.CloudEventUtils;
24+
import io.cloudevents.core.builder.CloudEventBuilder;
25+
import io.cloudevents.core.message.MessageReader;
26+
import io.cloudevents.http.HttpMessageFactory;
27+
import io.cloudevents.http.impl.HttpMessageWriter;
28+
29+
import org.springframework.http.HttpHeaders;
30+
import org.springframework.http.ResponseEntity;
31+
32+
/**
33+
* Miscellaneous utility methods to assist with Cloud Events in the context of Spring Web
34+
* frameworks. Primarily intended for the internal use within Spring-based frameworks or
35+
* integrations.
36+
*
37+
* @author Dave Syer
38+
* @since 2.0
39+
*/
40+
public class CloudEventHttpUtils {
41+
42+
private CloudEventHttpUtils() {
43+
}
44+
45+
/**
46+
* Create a {@link MessageReader} to assist in conversion of an HTTP request to a
47+
* {@link CloudEvent}.
48+
* @param headers the HTTP request headers
49+
* @param body the HTTP request body as a byte array
50+
* @return a {@link MessageReader} representing the {@link CloudEvent}
51+
*/
52+
public static MessageReader toReader(HttpHeaders headers, Supplier<byte[]> body) {
53+
return HttpMessageFactory.createReaderFromMultimap(headers, body.get());
54+
}
55+
56+
/**
57+
* Create an {@link HttpMessageWriter} that can hand off a {@link CloudEvent} to an
58+
* HTTP response. Mainly useful in a blocking (not async) setting because the response
59+
* body has to be consumed directly.
60+
* @param headers the response headers (will be mutated)
61+
* @param sendBody a consumer for the response body that puts the bytes on the wire
62+
*/
63+
public static HttpMessageWriter toWriter(HttpHeaders headers, Consumer<byte[]> sendBody) {
64+
return HttpMessageFactory.createWriter(headers::set, sendBody);
65+
}
66+
67+
/**
68+
* Helper method for extracting {@link HttpHeaders} from a {@link CloudEvent}. Can,
69+
* for instance, be used in a <code>&#64;RequestMapping</code> to return a
70+
* {@link ResponseEntity} that has headers copied from a {@link CloudEvent}.
71+
* @param event the input {@link CloudEvent}
72+
* @return the response headers represented by the event
73+
*/
74+
public static HttpHeaders toHttp(CloudEventContext event) {
75+
HttpHeaders headers = new HttpHeaders();
76+
CloudEventUtils.toReader(CloudEventBuilder.fromContext(event).build()).read(toWriter(headers, bytes -> {
77+
}));
78+
return headers;
79+
}
80+
81+
/**
82+
* Helper method for converting {@link HttpHeaders} to a {@link CloudEvent}. The input
83+
* headers must represent a valid event in "binary" form, i.e. it must have headers
84+
* "ce-id", "ce-specversion" etc.
85+
* @param headers the input request headers
86+
* @return a {@link CloudEventBuilder} that can be used to create a new
87+
* {@link CloudEvent}
88+
*
89+
*/
90+
public static CloudEventBuilder fromHttp(HttpHeaders headers) {
91+
return CloudEventBuilder
92+
.fromContext(CloudEventUtils.toEvent(CloudEventHttpUtils.toReader(headers, () -> null)));
93+
}
94+
95+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright 2018-Present The CloudEvents Authors
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
package io.cloudevents.spring.http;
19+
20+
import java.util.Map;
21+
22+
import io.cloudevents.core.message.impl.MessageUtils;
23+
24+
import org.springframework.http.HttpHeaders;
25+
26+
public class CloudEventsHeaders {
27+
28+
public static final String CE_PREFIX = "ce-";
29+
30+
public static final Map<String, String> ATTRIBUTES_TO_HEADERS = MessageUtils
31+
.generateAttributesToHeadersMapping(v -> {
32+
if (v.equals("datacontenttype")) {
33+
return HttpHeaders.CONTENT_TYPE;
34+
}
35+
return CE_PREFIX + v;
36+
});
37+
38+
public static final String SPEC_VERSION = ATTRIBUTES_TO_HEADERS.get("specversion");
39+
40+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/**
2+
* Provides classes related to working with Cloud Events within the context of Spring and
3+
* HTTP.
4+
*/
5+
package io.cloudevents.spring.http;
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/*
2+
* Copyright 2019-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.cloudevents.spring.mvc;
17+
18+
import java.io.IOException;
19+
20+
import io.cloudevents.CloudEvent;
21+
import io.cloudevents.core.CloudEventUtils;
22+
import io.cloudevents.spring.http.CloudEventHttpUtils;
23+
24+
import org.springframework.http.HttpInputMessage;
25+
import org.springframework.http.HttpOutputMessage;
26+
import org.springframework.http.MediaType;
27+
import org.springframework.http.converter.AbstractHttpMessageConverter;
28+
import org.springframework.http.converter.HttpMessageConverter;
29+
import org.springframework.http.converter.HttpMessageNotReadableException;
30+
import org.springframework.http.converter.HttpMessageNotWritableException;
31+
import org.springframework.util.StreamUtils;
32+
33+
/**
34+
* An {@link HttpMessageConverter} for {@link CloudEvent CloudEvents}. Supports the use of
35+
* {@link CloudEvent} in a <code>&#64;RequestMapping</code> as either a method parameter
36+
* or a return value.
37+
*
38+
* @author Dave Syer
39+
*
40+
*/
41+
public class CloudEventHttpMessageConverter extends AbstractHttpMessageConverter<CloudEvent> {
42+
43+
public CloudEventHttpMessageConverter() {
44+
super(MediaType.APPLICATION_OCTET_STREAM, MediaType.ALL);
45+
}
46+
47+
@Override
48+
protected boolean supports(Class<?> clazz) {
49+
return CloudEvent.class.isAssignableFrom(clazz);
50+
}
51+
52+
@Override
53+
protected CloudEvent readInternal(Class<? extends CloudEvent> clazz, HttpInputMessage inputMessage)
54+
throws IOException, HttpMessageNotReadableException {
55+
byte[] body = StreamUtils.copyToByteArray(inputMessage.getBody());
56+
return CloudEventHttpUtils.toReader(inputMessage.getHeaders(), () -> body).toEvent();
57+
}
58+
59+
@Override
60+
protected void writeInternal(CloudEvent event, HttpOutputMessage outputMessage)
61+
throws IOException, HttpMessageNotWritableException {
62+
CloudEventUtils.toReader(event)
63+
.read(CloudEventHttpUtils.toWriter(outputMessage.getHeaders(), body -> copy(body, outputMessage)));
64+
}
65+
66+
private void copy(byte[] body, HttpOutputMessage outputMessage) {
67+
try {
68+
StreamUtils.copy(body, outputMessage.getBody());
69+
}
70+
catch (IOException e) {
71+
throw new IllegalStateException(e);
72+
}
73+
}
74+
75+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/**
2+
* Provides classes related to working with Cloud Events within the context of Spring MVC.
3+
*/
4+
package io.cloudevents.spring.mvc;

0 commit comments

Comments
 (0)