Merge pull request #14 from matzew/http_vertx
WIP: vertx http transport
This commit is contained in:
commit
ce597a93fa
|
|
@ -25,6 +25,19 @@ import java.util.Optional;
|
|||
*
|
||||
*/
|
||||
public interface CloudEvent<T> {
|
||||
|
||||
// required
|
||||
String EVENT_TYPE_KEY = "ce-eventType";
|
||||
String CLOUD_EVENTS_VERSION_KEY = "ce-cloudEventsVersion";
|
||||
String SOURCE_KEY = "ce-source";
|
||||
String EVENT_ID_KEY = "ce-eventID";
|
||||
|
||||
// none-required
|
||||
String EVENT_TYPE_VERSION_KEY = "ce-eventTypeVersion";
|
||||
String EVENT_TIME_KEY = "ce-eventTime";
|
||||
String SCHEMA_URL_KEY = "ce-schemaURL";
|
||||
String HEADER_PREFIX = "ce-x-";
|
||||
|
||||
/**
|
||||
* Type of occurrence which has happened. Often this property is used for routing, observability, policy enforcement, etc.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -154,4 +154,20 @@ public class DefaultCloudEventImpl<T> implements CloudEvent<T>, Serializable {
|
|||
void setData(T data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "DefaultCloudEventImpl{" +
|
||||
"cloudEventsVersion='" + cloudEventsVersion + '\'' +
|
||||
", extensions=" + extensions +
|
||||
", eventType='" + eventType + '\'' +
|
||||
", source=" + source +
|
||||
", eventID='" + eventID + '\'' +
|
||||
", eventTypeVersion='" + eventTypeVersion + '\'' +
|
||||
", eventTime=" + eventTime +
|
||||
", schemaURL=" + schemaURL +
|
||||
", contentType='" + contentType + '\'' +
|
||||
", data=" + data +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
# HTTP Transport Util for Eclipse Vert.x
|
||||
|
||||
## Receiving CloudEvents
|
||||
|
||||
Below is a sample on how to read CloudEvents from an HttpRequest:
|
||||
|
||||
```java
|
||||
import io.vertx.core.AbstractVerticle;
|
||||
public class Server extends AbstractVerticle {
|
||||
public void start() {
|
||||
vertx.createHttpServer().requestHandler(req -> {
|
||||
|
||||
CeVertx.readFromRequest(req, reply -> {
|
||||
|
||||
if (reply.succeeded()) {
|
||||
|
||||
final CloudEvent<?> receivedEvent = reply.result();
|
||||
// access the attributes:
|
||||
System.out.println(receivedEvent.getEventID());
|
||||
...
|
||||
});
|
||||
|
||||
req.response()
|
||||
.putHeader("content-type", "text/plain")
|
||||
.end("Got a CloudEvent!");
|
||||
}).listen(8080);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Sending CloudEvents
|
||||
|
||||
Below is a sample on how to use the client to send a CloudEvent:
|
||||
|
||||
```java
|
||||
final HttpClientRequest request = vertx.createHttpClient().post(7890, "localhost", "/");
|
||||
|
||||
CeVertx.writeToHttpClientRequest(cloudEvent, request);
|
||||
request.handler(resp -> {
|
||||
context.assertEquals(resp.statusCode(), 200);
|
||||
});
|
||||
request.end();
|
||||
```
|
||||
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright 2018 The CloudEvents Authors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<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 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-parent</artifactId>
|
||||
<version>0.0.3-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>http-vertx</artifactId>
|
||||
<name>CloudEvents - Vertx-transport-http</name>
|
||||
<version>0.0.3-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.cloudevents</groupId>
|
||||
<artifactId>cloudevents-api</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-core</artifactId>
|
||||
<version>${vert.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>junit</groupId>
|
||||
<artifactId>junit</artifactId>
|
||||
<version>4.12</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>3.10.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.vertx</groupId>
|
||||
<artifactId>vertx-unit</artifactId>
|
||||
<version>${vert.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<properties>
|
||||
<vert.version>3.6.0.CR2</vert.version>
|
||||
<jackson.version>2.9.6</jackson.version>
|
||||
<maven.compiler.source>1.8</maven.compiler.source>
|
||||
<maven.compiler.target>1.8</maven.compiler.target>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
</project>
|
||||
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* Copyright 2018 The CloudEvents Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudevents.http.vertx;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventBuilder;
|
||||
import io.vertx.core.AsyncResult;
|
||||
import io.vertx.core.Future;
|
||||
import io.vertx.core.Handler;
|
||||
import io.vertx.core.MultiMap;
|
||||
import io.vertx.core.buffer.Buffer;
|
||||
import io.vertx.core.http.HttpClientRequest;
|
||||
import io.vertx.core.http.HttpHeaders;
|
||||
import io.vertx.core.http.HttpServerRequest;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static io.cloudevents.CloudEvent.CLOUD_EVENTS_VERSION_KEY;
|
||||
import static io.cloudevents.CloudEvent.EVENT_ID_KEY;
|
||||
import static io.cloudevents.CloudEvent.EVENT_TIME_KEY;
|
||||
import static io.cloudevents.CloudEvent.EVENT_TYPE_KEY;
|
||||
import static io.cloudevents.CloudEvent.EVENT_TYPE_VERSION_KEY;
|
||||
import static io.cloudevents.CloudEvent.HEADER_PREFIX;
|
||||
import static io.cloudevents.CloudEvent.SCHEMA_URL_KEY;
|
||||
import static io.cloudevents.CloudEvent.SOURCE_KEY;
|
||||
|
||||
public final class CeVertx {
|
||||
|
||||
private CeVertx() {
|
||||
// no-op
|
||||
}
|
||||
|
||||
public static void writeToHttpClientRequest(final CloudEvent<?> ce, final HttpClientRequest request) {
|
||||
|
||||
// setting the right content-length:
|
||||
if (ce.getData().isPresent()) {
|
||||
request.putHeader(HttpHeaders.CONTENT_LENGTH, HttpHeaders.createOptimized(String.valueOf(ce.getData().get().toString().length())));
|
||||
} else {
|
||||
request.putHeader(HttpHeaders.CONTENT_LENGTH, HttpHeaders.createOptimized("0"));
|
||||
}
|
||||
|
||||
// read required headers
|
||||
request
|
||||
.putHeader(HttpHeaders.CONTENT_TYPE, HttpHeaders.createOptimized("application/json"))
|
||||
.putHeader(HttpHeaders.createOptimized(CLOUD_EVENTS_VERSION_KEY), HttpHeaders.createOptimized(ce.getCloudEventsVersion()))
|
||||
.putHeader(HttpHeaders.createOptimized(EVENT_TYPE_KEY), HttpHeaders.createOptimized(ce.getEventType()))
|
||||
.putHeader(HttpHeaders.createOptimized(SOURCE_KEY), HttpHeaders.createOptimized(ce.getSource().toString()))
|
||||
.putHeader(HttpHeaders.createOptimized(EVENT_ID_KEY), HttpHeaders.createOptimized(ce.getEventID()));
|
||||
|
||||
// read optional headers
|
||||
ce.getEventTypeVersion().ifPresent(eventTypeVersion -> {
|
||||
request.putHeader(HttpHeaders.createOptimized(EVENT_TYPE_VERSION_KEY), HttpHeaders.createOptimized(eventTypeVersion));
|
||||
});
|
||||
|
||||
ce.getEventTime().ifPresent(eventTime -> {
|
||||
request.putHeader(HttpHeaders.createOptimized(EVENT_TIME_KEY), HttpHeaders.createOptimized(eventTime.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)));
|
||||
});
|
||||
|
||||
ce.getSchemaURL().ifPresent(schemaUrl -> {
|
||||
request.putHeader(HttpHeaders.createOptimized(SCHEMA_URL_KEY), HttpHeaders.createOptimized(schemaUrl.toString()));
|
||||
});
|
||||
|
||||
ce.getData().ifPresent(data -> {
|
||||
request.write(data.toString());
|
||||
});
|
||||
}
|
||||
|
||||
public static void readFromRequest(final HttpServerRequest request, final Handler<AsyncResult<CloudEvent>> resultHandler) {
|
||||
|
||||
final MultiMap headers = request.headers();
|
||||
final CloudEventBuilder builder = new CloudEventBuilder();
|
||||
|
||||
try {
|
||||
// just check, no need to set the version
|
||||
readRequiredHeaderValue(headers, CLOUD_EVENTS_VERSION_KEY);
|
||||
|
||||
builder
|
||||
// set required values
|
||||
.eventType(readRequiredHeaderValue(headers, EVENT_TYPE_KEY))
|
||||
.source(URI.create(readRequiredHeaderValue(headers ,SOURCE_KEY)))
|
||||
.eventID(readRequiredHeaderValue(headers, EVENT_ID_KEY))
|
||||
|
||||
// set optional values
|
||||
.eventTypeVersion(headers.get(EVENT_TYPE_VERSION_KEY))
|
||||
.contentType(headers.get(HttpHeaders.CONTENT_TYPE));
|
||||
|
||||
final String eventTime = headers.get(EVENT_TIME_KEY);
|
||||
if (eventTime != null) {
|
||||
builder.eventTime(ZonedDateTime.parse(eventTime, DateTimeFormatter.ISO_OFFSET_DATE_TIME));
|
||||
}
|
||||
|
||||
final String schemaURL = headers.get(SCHEMA_URL_KEY);
|
||||
if (schemaURL != null) {
|
||||
builder.schemaURL(URI.create(schemaURL));
|
||||
}
|
||||
|
||||
// get the extensions
|
||||
final Map<String, String> extensions =
|
||||
headers.entries().stream()
|
||||
.filter(header -> header.getKey().startsWith(HEADER_PREFIX))
|
||||
.collect(Collectors.toMap(h -> h.getKey(), h -> h.getValue()));
|
||||
|
||||
builder.extensions(extensions);
|
||||
request.bodyHandler((Buffer buff) -> {
|
||||
|
||||
if (buff.length()>0) {
|
||||
builder.data(buff.toJsonObject().toString());
|
||||
}
|
||||
resultHandler.handle(Future.succeededFuture(builder.build()));
|
||||
});
|
||||
} catch (Exception e) {
|
||||
resultHandler.handle(Future.failedFuture(e));
|
||||
}
|
||||
}
|
||||
|
||||
private static String readRequiredHeaderValue(final MultiMap headers, final String headerName) {
|
||||
return requireNonNull(headers.get(headerName));
|
||||
}
|
||||
|
||||
private static String requireNonNull(final String val) {
|
||||
if (val == null) {
|
||||
throw new IllegalArgumentException();
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
/**
|
||||
* Copyright 2018 The CloudEvents Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudevents.http.vertx;
|
||||
|
||||
import io.cloudevents.CloudEvent;
|
||||
import io.cloudevents.CloudEventBuilder;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.http.HttpClientRequest;
|
||||
import io.vertx.core.http.HttpHeaders;
|
||||
import io.vertx.core.http.HttpServer;
|
||||
import io.vertx.ext.unit.Async;
|
||||
import io.vertx.ext.unit.TestContext;
|
||||
import io.vertx.ext.unit.junit.VertxUnitRunner;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.URI;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static io.cloudevents.CloudEvent.CLOUD_EVENTS_VERSION_KEY;
|
||||
import static io.cloudevents.CloudEvent.EVENT_TYPE_KEY;
|
||||
|
||||
@RunWith(VertxUnitRunner.class)
|
||||
public class CloudEventsVertxTest {
|
||||
|
||||
private final static Logger logger = Logger.getLogger(CloudEventsVertxTest.class.getName());
|
||||
|
||||
private HttpServer server;
|
||||
private Vertx vertx;
|
||||
private int port;
|
||||
|
||||
@Before
|
||||
public void setUp(TestContext context) throws IOException {
|
||||
vertx = Vertx.vertx();
|
||||
ServerSocket socket = new ServerSocket(0);
|
||||
port = socket.getLocalPort();
|
||||
socket.close();
|
||||
server = vertx.createHttpServer();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown(TestContext context) {
|
||||
vertx.close(context.asyncAssertSuccess());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cloudEventWithPayload(TestContext context) {
|
||||
final Async async = context.async();
|
||||
|
||||
// Create the actuak CloudEvents object;
|
||||
final CloudEvent<String> cloudEvent = new CloudEventBuilder<String>()
|
||||
.source(URI.create("http://knative-eventing.com"))
|
||||
.eventID("foo-bar")
|
||||
.eventType("pushevent")
|
||||
.data("{\"foo\":\"bar\"}}")
|
||||
.build();
|
||||
|
||||
// set up the server and add a handler to check the values
|
||||
server.requestHandler(req -> {
|
||||
|
||||
CeVertx.readFromRequest(req, reply -> {
|
||||
|
||||
if (reply.succeeded()) {
|
||||
|
||||
final CloudEvent<?> receivedEvent = reply.result();
|
||||
context.assertEquals(receivedEvent.getEventID(), cloudEvent.getEventID());
|
||||
context.assertEquals(receivedEvent.getSource().toString(), cloudEvent.getSource().toString());
|
||||
context.assertEquals(receivedEvent.getEventType(), cloudEvent.getEventType());
|
||||
context.assertEquals(receivedEvent.getData().isPresent(), Boolean.TRUE);
|
||||
}
|
||||
});
|
||||
|
||||
req.response().end();
|
||||
}).listen(port, ar -> {
|
||||
if (ar.failed()) {
|
||||
context.fail("could not start server");
|
||||
} else {
|
||||
// sending it to the test-server
|
||||
final HttpClientRequest request = vertx.createHttpClient().post(port, "localhost", "/");
|
||||
|
||||
request.handler(response -> {
|
||||
context.assertEquals(response.statusCode(), 200);
|
||||
|
||||
async.complete();
|
||||
});
|
||||
CeVertx.writeToHttpClientRequest(cloudEvent, request);
|
||||
request.end();
|
||||
}
|
||||
});
|
||||
logger.info("running on port: " + port);
|
||||
|
||||
async.awaitSuccess(1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cloudEventWithoutPayload(TestContext context) {
|
||||
final Async async = context.async();
|
||||
|
||||
// Create the actuak CloudEvents object;
|
||||
final CloudEvent<String> cloudEvent = new CloudEventBuilder<String>()
|
||||
.source(URI.create("http://knative-eventing.com"))
|
||||
.eventID("foo-bar")
|
||||
.eventType("pushevent")
|
||||
.build();
|
||||
|
||||
// set up the server and add a handler to check the values
|
||||
server.requestHandler(req -> {
|
||||
|
||||
CeVertx.readFromRequest(req, reply -> {
|
||||
|
||||
if (reply.succeeded()) {
|
||||
|
||||
final CloudEvent<?> receivedEvent = reply.result();
|
||||
context.assertEquals(receivedEvent.getEventID(), cloudEvent.getEventID());
|
||||
context.assertEquals(receivedEvent.getSource().toString(), cloudEvent.getSource().toString());
|
||||
context.assertEquals(receivedEvent.getEventType(), cloudEvent.getEventType());
|
||||
context.assertEquals(receivedEvent.getData().isPresent(), Boolean.FALSE);
|
||||
}
|
||||
});
|
||||
|
||||
req.response().end();
|
||||
}).listen(port, ar -> {
|
||||
if (ar.failed()) {
|
||||
context.fail("could not start server");
|
||||
} else {
|
||||
// sending it to the test-server
|
||||
final HttpClientRequest request = vertx.createHttpClient().post(port, "localhost", "/");
|
||||
|
||||
|
||||
request.handler(resp -> {
|
||||
context.assertEquals(resp.statusCode(), 200);
|
||||
async.complete();
|
||||
|
||||
});
|
||||
CeVertx.writeToHttpClientRequest(cloudEvent, request);
|
||||
request.end();
|
||||
|
||||
}
|
||||
});
|
||||
logger.info("running on port: " + port);
|
||||
|
||||
async.awaitSuccess(1000);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void incompleteCloudEvent(TestContext context) {
|
||||
final Async async = context.async();
|
||||
|
||||
// set up the server and add a handler to check the values
|
||||
server.requestHandler(req -> {
|
||||
|
||||
CeVertx.readFromRequest(req, reply -> {
|
||||
|
||||
if (reply.succeeded()) {
|
||||
|
||||
context.fail("request was not complete");
|
||||
} else {
|
||||
context.assertEquals(reply.failed(), Boolean.TRUE);
|
||||
}
|
||||
});
|
||||
|
||||
req.response().end();
|
||||
}).listen(port, ar -> {
|
||||
|
||||
if (ar.failed()) {
|
||||
context.fail("could not start server");
|
||||
} else {
|
||||
// fire the request
|
||||
// sending it to the test-server
|
||||
final HttpClientRequest request = vertx.createHttpClient().post(port, "localhost", "/");
|
||||
// create incomplete CloudEvent request
|
||||
request.putHeader(HttpHeaders.createOptimized(CLOUD_EVENTS_VERSION_KEY), HttpHeaders.createOptimized("0.1"));
|
||||
request.putHeader(HttpHeaders.createOptimized(EVENT_TYPE_KEY), HttpHeaders.createOptimized("pushevent"));
|
||||
request.putHeader(HttpHeaders.CONTENT_LENGTH, HttpHeaders.createOptimized("0"));
|
||||
|
||||
|
||||
request.handler(resp -> {
|
||||
context.assertEquals(resp.statusCode(), 200);
|
||||
|
||||
async.complete();
|
||||
});
|
||||
request.end();
|
||||
}
|
||||
});
|
||||
logger.info("running on port: " + port);
|
||||
|
||||
async.awaitSuccess(1000);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue