-
Notifications
You must be signed in to change notification settings - Fork 167
Spring Boot Web and Webflux Helpers/Binding for CloudEvents #201
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -40,3 +40,4 @@ _site/ | |
| .sass-cache/ | ||
| .jekyll-cache/ | ||
| .jekyll-metadata | ||
| .DS_Store | ||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||
| # HTTP Protocol Binding for Spring Boot Web (Reactive Stack) | ||||||
|
|
||||||
| For Maven based projects, use the following to configure the CloudEvents Spring Boot Binding: | ||||||
|
|
||||||
| ```xml | ||||||
| <dependency> | ||||||
| <groupId>io.cloudevents</groupId> | ||||||
| <artifactId>spring-boot-starter-web-cloudevents</artifactId> | ||||||
| <version>2.0.0-milestone1</version> | ||||||
| </dependency> | ||||||
| ``` | ||||||
|
|
||||||
| ## Receiving CloudEvents | ||||||
| Below is a sample on how to read and write CloudEvents from a Post Request Headers and Body: | ||||||
|
|
||||||
| ```java | ||||||
| @PostMapping | ||||||
| public String recieveCloudEvent(@RequestHeader Map<String, String> headers, @RequestBody Object body) { | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo:
Suggested change
|
||||||
| // Create a CloudEvent from Header and Body coming in the request | ||||||
| CloudEvent cloudEvent = CloudEventsHelper.parseFromRequest(headers, body); | ||||||
|
|
||||||
|
|
||||||
| } | ||||||
| ``` | ||||||
|
|
||||||
|
|
||||||
| ## Sending CloudEvents | ||||||
|
|
||||||
|
|
||||||
| Below is a sample on how to use the client to send a CloudEvent: | ||||||
|
|
||||||
| ```java | ||||||
| // Create the CloudEvent with the builder | ||||||
| final CloudEvent myCloudEvent = CloudEventBuilder.v03() | ||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hiding the version of the spec or the version of the builder (v03, V1, ....) is perhaps not a great idea. I would prefer that this version is part of the GAV consumed by the starter to make it available publicly to all the users. |
||||||
| .withId("ABC-123") | ||||||
| .withType("my-first-cloud-event") | ||||||
| .withSource(URI.create("knative-event-producer.default.svc.cluster.local")) | ||||||
| .withData(SerializationUtils.serialize("{\"name\" : \"" + name + "-" + UUID.randomUUID().toString() + "\" }")) | ||||||
| .withDataContentType("application/json") | ||||||
| .build(); | ||||||
|
|
||||||
| // Using RestTemplate | ||||||
| RestTemplate restTemplate = new RestTemplate(); | ||||||
| ResponseEntity<String> response = CloudEventsHelper.createPostCloudEvent(restemplate, <HOST>, cloudEvent); | ||||||
|
|
||||||
| ``` | ||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
|
|
||
| <parent> | ||
| <groupId>io.cloudevents</groupId> | ||
| <artifactId>cloudevents-parent</artifactId> | ||
| <version>2.0.0-SNAPSHOT</version> | ||
| <relativePath>../../</relativePath> | ||
| </parent> | ||
|
|
||
| <artifactId>spring-boot-starter-web-cloudevents</artifactId> | ||
| <name>CloudEvents - Spring Boot Web Http Binding</name> | ||
| <description>Simple Helper for Cloud Events in Spring Boot Web</description> | ||
|
|
||
| <properties> | ||
| <java.version>11</java.version> | ||
| <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | ||
| <maven.compiler.source>${java.version}</maven.compiler.source> | ||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
| <maven.compiler.target>${java.version}</maven.compiler.target> | ||
| </properties> | ||
|
|
||
| <dependencyManagement> | ||
| <dependencies> | ||
| <dependency> | ||
| <!-- Import dependency management from Spring Boot --> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-dependencies</artifactId> | ||
| <version>2.2.4.RELEASE</version> | ||
| <type>pom</type> | ||
| <scope>import</scope> | ||
| </dependency> | ||
| </dependencies> | ||
| </dependencyManagement> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-web</artifactId> | ||
| <scope>provided</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>io.cloudevents</groupId> | ||
| <artifactId>cloudevents-core</artifactId> | ||
| <version>${project.version}</version> | ||
| </dependency> | ||
|
|
||
| </dependencies> | ||
|
|
||
| </project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| package io.cloudevents.http.springboot.web.helper; | ||
|
|
||
| import io.cloudevents.CloudEvent; | ||
| import io.cloudevents.core.builder.CloudEventBuilder; | ||
|
|
||
| import org.springframework.http.HttpEntity; | ||
| import org.springframework.http.HttpHeaders; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.util.SerializationUtils; | ||
| import org.springframework.web.client.RestTemplate; | ||
|
|
||
| import java.net.URI; | ||
| import java.time.ZonedDateTime; | ||
| import java.util.Map; | ||
|
|
||
| public class CloudEventsHelper { | ||
|
|
||
| public static final String CE_ID = "Ce-Id"; | ||
| public static final String CE_TYPE = "Ce-Type"; | ||
| public static final String CE_SOURCE = "Ce-Source"; | ||
| public static final String CE_SPECVERSION = "Ce-Specversion"; | ||
| public static final String CE_TIME = "Ce-Time"; | ||
|
|
||
| public static final String APPLICATION_JSON = "application/json"; | ||
| public static final String CONTENT_TYPE = "Content-Type"; | ||
|
|
||
|
|
||
| public static CloudEvent parseFromRequest(Map<String, String> headers, Object body) throws IllegalStateException { | ||
| if (headers.get(CE_ID) == null || (headers.get(CE_SOURCE) == null || headers.get(CE_TYPE) == null)) { | ||
| throw new IllegalStateException("Cloud Event required fields are not present."); | ||
| } | ||
|
|
||
| return CloudEventBuilder.v03() | ||
| .withId(headers.get(CE_ID)) | ||
| .withType(headers.get(CE_TYPE)) | ||
| .withSource((headers.get(CE_SOURCE) != null) ? URI.create(headers.get(CE_SOURCE)) : null) | ||
| .withTime((headers.get(CE_TIME) != null) ? ZonedDateTime.parse(headers.get(CE_TIME)) : null) | ||
| .withData(SerializationUtils.serialize(body)) | ||
| .withDataContentType((headers.get(CONTENT_TYPE) != null) ? headers.get(CONTENT_TYPE) : APPLICATION_JSON) | ||
| .build(); | ||
| } | ||
|
|
||
|
|
||
| public static ResponseEntity<String> createPostCloudEvent(RestTemplate restTemplate, String host, CloudEvent cloudEvent) { | ||
|
|
||
| HttpHeaders headers = new HttpHeaders(); | ||
| headers.add(CE_ID, cloudEvent.getId()); | ||
| headers.add(CE_SPECVERSION, cloudEvent.getSpecVersion().name()); | ||
| headers.add(CONTENT_TYPE, APPLICATION_JSON); | ||
| headers.add(CE_TYPE, cloudEvent.getType()); | ||
| headers.add(CE_TIME, cloudEvent.getTime().toString()); | ||
| headers.add(CE_SOURCE, cloudEvent.getSource().toString()); | ||
|
|
||
| for(String key : cloudEvent.getExtensionNames()) { | ||
| headers.add(key, cloudEvent.getExtension(key).toString()); | ||
| } | ||
|
|
||
| HttpEntity<String> request = new HttpEntity<String>(SerializationUtils.deserialize(cloudEvent.getData()).toString(), headers); | ||
|
|
||
| return restTemplate.postForEntity(host, request, String.class); | ||
| } | ||
|
|
||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # HTTP Protocol Binding for Spring Boot Webflux (Reactive Stack) | ||
|
|
||
| For Maven based projects, use the following to configure the CloudEvents Spring Boot Binding: | ||
|
|
||
| ```xml | ||
| <dependency> | ||
| <groupId>io.cloudevents</groupId> | ||
| <artifactId>spring-boot-starter-web-cloudevents</artifactId> | ||
| <version>2.0.0-milestone1</version> | ||
| </dependency> | ||
| ``` | ||
|
|
||
| ## Receiving CloudEvents | ||
| Below is a sample on how to read and write CloudEvents from a Post Request Headers and Body: | ||
|
|
||
| ```java | ||
| @PostMapping | ||
| public String recieveCloudEvent(@RequestHeader Map<String, String> headers, @RequestBody Object body) { | ||
| // Create a CloudEvent from Header and Body coming in the request | ||
| CloudEvent cloudEvent = CloudEventsHelper.parseFromRequest(headers, body); | ||
|
|
||
|
|
||
| } | ||
| ``` | ||
|
|
||
|
|
||
| ## Sending CloudEvents | ||
|
|
||
|
|
||
| Below is a sample on how to use the client to send a CloudEvent: | ||
|
|
||
| ```java | ||
| // Create the CloudEvent with the builder | ||
| final CloudEvent myCloudEvent = CloudEventBuilder.v03() | ||
| .withId("ABC-123") | ||
| .withType("my-first-cloud-event") | ||
| .withSource(URI.create("knative-event-producer.default.svc.cluster.local")) | ||
| .withData(SerializationUtils.serialize("{\"name\" : \"" + name + "-" + UUID.randomUUID().toString() + "\" }")) | ||
| .withDataContentType("application/json") | ||
| .build(); | ||
|
|
||
| // Use Reactive WebClient | ||
| WebClient webClient = WebClient.builder().baseUrl(HOST).filter(logRequest()).build(); | ||
| // Use the Helper to Create the Post Request with your CloudEvent in it | ||
| WebClient.ResponseSpec postCloudEvent = CloudEventsHelper.createPostCloudEvent(webClient, myCloudEvent); | ||
|
|
||
| postCloudEvent.bodyToMono(String.class).doOnError(t -> t.printStackTrace()) | ||
| .doOnSuccess(s -> System.out.println("Result -> " + s)).subscribe(); | ||
| ``` |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||
| xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||
| <modelVersion>4.0.0</modelVersion> | ||
|
|
||
| <parent> | ||
| <groupId>io.cloudevents</groupId> | ||
| <artifactId>cloudevents-parent</artifactId> | ||
| <version>2.0.0-SNAPSHOT</version> | ||
| <relativePath>../../</relativePath> | ||
| </parent> | ||
|
|
||
| <artifactId>spring-boot-starter-webflux-cloudevents</artifactId> | ||
| <name>CloudEvents - Spring Boot Webflux (Reactive) Http Binding</name> | ||
| <description>Simple Helper for Cloud Events in Spring Boot Webflux</description> | ||
|
|
||
| <properties> | ||
| <java.version>11</java.version> | ||
| <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> | ||
| <maven.compiler.source>${java.version}</maven.compiler.source> | ||
| <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> | ||
| <maven.compiler.target>${java.version}</maven.compiler.target> | ||
| </properties> | ||
|
|
||
| <dependencyManagement> | ||
| <dependencies> | ||
| <dependency> | ||
| <!-- Import dependency management from Spring Boot --> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-dependencies</artifactId> | ||
| <version>2.2.4.RELEASE</version> | ||
| <type>pom</type> | ||
| <scope>import</scope> | ||
| </dependency> | ||
| </dependencies> | ||
| </dependencyManagement> | ||
|
|
||
| <dependencies> | ||
| <dependency> | ||
| <groupId>org.springframework.boot</groupId> | ||
| <artifactId>spring-boot-starter-webflux</artifactId> | ||
| <scope>provided</scope> | ||
| </dependency> | ||
| <dependency> | ||
| <groupId>io.cloudevents</groupId> | ||
| <artifactId>cloudevents-core</artifactId> | ||
| <version>${project.version}</version> | ||
| </dependency> | ||
|
|
||
| </dependencies> | ||
|
|
||
| </project> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package io.cloudevents.http.springboot.webflux.helper; | ||
|
|
||
| import io.cloudevents.CloudEvent; | ||
| import io.cloudevents.Extension; | ||
| import io.cloudevents.core.builder.CloudEventBuilder; | ||
| import org.springframework.util.SerializationUtils; | ||
| import org.springframework.web.reactive.function.BodyInserters; | ||
| import org.springframework.web.reactive.function.client.WebClient; | ||
|
|
||
| import java.net.URI; | ||
| import java.time.ZonedDateTime; | ||
| import java.util.Map; | ||
|
|
||
| public class CloudEventsHelper { | ||
|
|
||
| public static final String CE_ID = "Ce-Id"; | ||
| public static final String CE_TYPE = "Ce-Type"; | ||
| public static final String CE_SOURCE = "Ce-Source"; | ||
| public static final String CE_SPECVERSION = "Ce-Specversion"; | ||
| public static final String CE_TIME = "Ce-Time"; | ||
| public static final String CE_SUBJECT = "Ce-Subject"; | ||
|
|
||
| public static final String APPLICATION_JSON = "application/json"; | ||
| public static final String CONTENT_TYPE = "Content-Type"; | ||
|
|
||
|
|
||
| public static CloudEvent parseFromRequest(Map<String, String> headers, Object body) throws IllegalStateException { | ||
| return parseFromRequestWithExtension(headers, body, null); | ||
| } | ||
|
|
||
|
|
||
| public static CloudEvent parseFromRequestWithExtension(Map<String, String> headers, Object body, Extension extension) { | ||
| if (headers.get(CE_ID) == null || (headers.get(CE_SOURCE) == null || headers.get(CE_TYPE) == null)) { | ||
| throw new IllegalStateException("Cloud Event required fields are not present."); | ||
| } | ||
|
|
||
| CloudEventBuilder builder = CloudEventBuilder.v03() | ||
| .withId(headers.get(CE_ID)) | ||
| .withType(headers.get(CE_TYPE)) | ||
| .withSource((headers.get(CE_SOURCE) != null) ? URI.create(headers.get(CE_SOURCE)) : null) | ||
| .withTime((headers.get(CE_TIME) != null) ? ZonedDateTime.parse(headers.get(CE_TIME)) : null) | ||
| .withData(SerializationUtils.serialize(body)) | ||
| .withSubject(headers.get(CE_SUBJECT)) | ||
| .withDataContentType((headers.get(CONTENT_TYPE) != null) ? headers.get(CONTENT_TYPE) : APPLICATION_JSON); | ||
|
|
||
| if (extension != null) { | ||
| builder = builder.withExtension(extension); | ||
| } | ||
| return builder.build(); | ||
| } | ||
|
|
||
| public static WebClient.ResponseSpec createPostCloudEvent(WebClient webClient, CloudEvent cloudEvent) { | ||
| return createPostCloudEvent(webClient, "", cloudEvent); | ||
| } | ||
|
|
||
| public static WebClient.ResponseSpec createPostCloudEvent(WebClient webClient, String uriString, CloudEvent cloudEvent) { | ||
| WebClient.RequestBodySpec uri = webClient.post().uri(uriString); | ||
| WebClient.RequestHeadersSpec<?> headersSpec = uri.body(BodyInserters.fromValue(cloudEvent.getData())); | ||
| WebClient.RequestHeadersSpec<?> header = headersSpec | ||
| .header(CE_ID, cloudEvent.getId()) | ||
| .header(CE_SPECVERSION, cloudEvent.getSpecVersion().name()) | ||
| .header(CONTENT_TYPE, APPLICATION_JSON) | ||
| .header(CE_TYPE, cloudEvent.getType()) | ||
| .header(CE_TIME, cloudEvent.getTime().toString()) | ||
| .header(CE_SOURCE, cloudEvent.getSource().toString()) | ||
| .header(CE_SUBJECT, cloudEvent.getSubject()); | ||
|
|
||
|
|
||
| //@TODO: improve extensions handling, at least now we will have a string version of the extension | ||
| for (String key : cloudEvent.getExtensionNames()) { | ||
| header.header(key, cloudEvent.getExtension(key).toString()); | ||
| } | ||
| return header.retrieve(); | ||
| } | ||
|
|
||
|
|
||
| //@TODO: create a print CLOUD EVENT helper | ||
|
|
||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Who is gonna to release/maintain such a starter like also the WebFlux one ? @salaboy