Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,4 @@ _site/
.sass-cache/
.jekyll-cache/
.jekyll-metadata
.DS_Store
46 changes: 46 additions & 0 deletions http/spring-boot-starter-web-cloudevents/README.md
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>

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

<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) {
Copy link

@metacosm metacosm Jul 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: recieve -> receive 😄

Suggested change
public String recieveCloudEvent(@RequestHeader Map<String, String> headers, @RequestBody Object body) {
public String receiveCloudEvent(@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()

Choose a reason for hiding this comment

The 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);

```
52 changes: 52 additions & 0 deletions http/spring-boot-starter-web-cloudevents/pom.xml
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);
}


}
49 changes: 49 additions & 0 deletions http/spring-boot-starter-webflux-cloudevents/README.md
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();
```
52 changes: 52 additions & 0 deletions http/spring-boot-starter-webflux-cloudevents/pom.xml
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

}
2 changes: 2 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@
<module>formats/json-jackson</module>
<module>http/vertx</module>
<module>http/restful-ws</module>
<module>http/spring-boot-starter-web-cloudevents</module>
<module>http/spring-boot-starter-webflux-cloudevents</module>
<module>kafka</module>
</modules>

Expand Down