Skip to content

Commit 0921c6c

Browse files
Update to version 2.0.0-milestone4 of the CloudEvents SDK. (GoogleCloudPlatform#75)
The principal changes are that the payload of an event is now `CloudEventData` instead of `byte[]`, which can save on copying; and that there is now better support for reading and writing HTTP events. A workaround was needed for a bug that has since been fixed, in cloudevents/sdk-java#259.
1 parent 25a3f3f commit 0921c6c

File tree

8 files changed

+45
-157
lines changed

8 files changed

+45
-157
lines changed

functions-framework-api/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
<dependency>
5353
<groupId>io.cloudevents</groupId>
5454
<artifactId>cloudevents-api</artifactId>
55-
<version>2.0.0-milestone2</version>
55+
<version>2.0.0-milestone4</version>
5656
</dependency>
5757
</dependencies>
5858

invoker/core/pom.xml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,18 @@
5151
</dependency>
5252
<dependency>
5353
<groupId>io.cloudevents</groupId>
54-
<artifactId>cloudevents-api</artifactId>
55-
<version>2.0.0-milestone2</version>
54+
<artifactId>cloudevents-core</artifactId>
55+
<version>2.0.0-milestone4</version>
5656
</dependency>
5757
<dependency>
5858
<groupId>io.cloudevents</groupId>
59-
<artifactId>cloudevents-core</artifactId>
60-
<version>2.0.0-milestone2</version>
59+
<artifactId>cloudevents-http-basic</artifactId>
60+
<version>2.0.0-milestone4</version>
6161
</dependency>
6262
<dependency>
6363
<groupId>io.cloudevents</groupId>
6464
<artifactId>cloudevents-json-jackson</artifactId>
65-
<version>2.0.0-milestone2</version>
65+
<version>2.0.0-milestone4</version>
6666
</dependency>
6767
<dependency>
6868
<groupId>com.google.code.gson</groupId>

invoker/core/src/main/java/com/google/cloud/functions/invoker/BackgroundFunctionExecutor.java

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,20 @@
2727
import com.google.gson.TypeAdapter;
2828
import io.cloudevents.CloudEvent;
2929
import io.cloudevents.core.message.MessageReader;
30-
import io.cloudevents.core.message.impl.GenericStructuredMessageReader;
31-
import io.cloudevents.core.message.impl.MessageUtils;
32-
import io.cloudevents.core.message.impl.UnknownEncodingMessageReader;
30+
import io.cloudevents.http.HttpMessageFactory;
3331
import java.io.BufferedReader;
3432
import java.io.IOException;
3533
import java.io.Reader;
3634
import java.lang.reflect.Type;
3735
import java.time.OffsetDateTime;
3836
import java.time.format.DateTimeFormatter;
37+
import java.util.ArrayList;
3938
import java.util.Arrays;
4039
import java.util.Collections;
4140
import java.util.List;
4241
import java.util.Map;
4342
import java.util.Optional;
43+
import java.util.TreeMap;
4444
import java.util.logging.Level;
4545
import java.util.logging.Logger;
4646
import javax.servlet.http.HttpServlet;
@@ -256,7 +256,9 @@ void serviceLegacyEvent(Event legacyEvent) throws Exception {
256256
@Override
257257
void serviceCloudEvent(CloudEvent cloudEvent) throws Exception {
258258
Context context = contextFromCloudEvent(cloudEvent);
259-
String jsonData = cloudEvent.getData() == null ? "{}" : new String(cloudEvent.getData(), UTF_8);
259+
String jsonData = (cloudEvent.getData() == null)
260+
? "{}"
261+
: new String(cloudEvent.getData().toBytes(), UTF_8);
260262
function.accept(jsonData, context);
261263
}
262264
}
@@ -286,7 +288,7 @@ void serviceLegacyEvent(Event legacyEvent) throws Exception {
286288
@Override
287289
void serviceCloudEvent(CloudEvent cloudEvent) throws Exception {
288290
if (cloudEvent.getData() != null) {
289-
String data = new String(cloudEvent.getData(), UTF_8);
291+
String data = new String(cloudEvent.getData().toBytes(), UTF_8);
290292
T payload = new Gson().fromJson(data, type);
291293
Context context = contextFromCloudEvent(cloudEvent);
292294
function.accept(payload, context);
@@ -345,22 +347,27 @@ private enum CloudEventKind {BINARY, STRUCTURED}
345347
private <CloudEventT> void serviceCloudEvent(HttpServletRequest req) throws Exception {
346348
@SuppressWarnings("unchecked")
347349
FunctionExecutor<CloudEventT> executor = (FunctionExecutor<CloudEventT>) functionExecutor;
348-
Map<String, List<String>> headers = CloudEventsServletBinaryMessageReader.headerMap(req);
349350
byte[] body = req.getInputStream().readAllBytes();
350-
List<String> listOfNull = Collections.singletonList(null);
351-
MessageReader reader = MessageUtils.parseStructuredOrBinaryMessage(
352-
() -> headers.getOrDefault("content-type", listOfNull).get(0),
353-
format -> new GenericStructuredMessageReader(format, body),
354-
() -> headers.getOrDefault("ce-specversion", listOfNull).get(0),
355-
unusedSpecVersion -> CloudEventsServletBinaryMessageReader.from(req, body),
356-
UnknownEncodingMessageReader::new);
351+
MessageReader reader = HttpMessageFactory.createReaderFromMultimap(headerMap(req), body);
357352
// It's important not to set the context ClassLoader earlier, because MessageUtils will use
358353
// ServiceLoader.load(EventFormat.class) to find a handler to deserialize a binary CloudEvent
359354
// and if it finds something from the function ClassLoader then that something will implement
360355
// the EventFormat interface as defined by that ClassLoader rather than ours. Then ServiceLoader.load
361356
// will throw ServiceConfigurationError. At this point we're still running with the default
362357
// context ClassLoader, which is the system ClassLoader that has loaded the code here.
363-
runWithContextClassLoader(() -> executor.serviceCloudEvent(reader.toEvent()));
358+
runWithContextClassLoader(() -> executor.serviceCloudEvent(reader.toEvent(data -> data)));
359+
// The data->data is a workaround for a bug fixed since Milestone 4 of the SDK, in
360+
// https://github.com/cloudevents/sdk-java/pull/259.
361+
}
362+
363+
private static Map<String, List<String>> headerMap(HttpServletRequest req) {
364+
Map<String, List<String>> headerMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
365+
for (String header : Collections.list(req.getHeaderNames())) {
366+
for (String value : Collections.list(req.getHeaders(header))) {
367+
headerMap.computeIfAbsent(header, unused -> new ArrayList<>()).add(value);
368+
}
369+
}
370+
return headerMap;
364371
}
365372

366373
private void serviceLegacyEvent(HttpServletRequest req) throws Exception {

invoker/core/src/main/java/com/google/cloud/functions/invoker/CloudEventsServletBinaryMessageReader.java

Lines changed: 0 additions & 69 deletions
This file was deleted.

invoker/core/src/test/java/com/google/cloud/functions/invoker/GcfEventsTest.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ public void pubSubBinaryData() throws IOException {
207207
public void pubSubWrapping() throws IOException {
208208
Event legacyEvent = legacyEventForResource("legacy_pubsub.json");
209209
CloudEvent cloudEvent = GcfEvents.convertToCloudEvent(legacyEvent);
210-
assertThat(new String(cloudEvent.getData(), UTF_8))
210+
assertThat(new String(cloudEvent.getData().toBytes(), UTF_8))
211211
.isEqualTo("{\"message\":{\"@type\":\"type.googleapis.com/google.pubsub.v1.PubsubMessage\","
212212
+ "\"attributes\":{\"attribute1\":\"value1\"},"
213213
+ "\"data\":\"VGhpcyBpcyBhIHNhbXBsZSBtZXNzYWdl\"}}");
@@ -221,7 +221,8 @@ public void pubSubWrapping() throws IOException {
221221
public void firestoreWildcards() throws IOException {
222222
Event legacyEvent = legacyEventForResource("firestore_simple.json");
223223
CloudEvent cloudEvent = GcfEvents.convertToCloudEvent(legacyEvent);
224-
JsonObject payload = new Gson().fromJson(new String(cloudEvent.getData(), UTF_8), JsonObject.class);
224+
JsonObject payload =
225+
new Gson().fromJson(new String(cloudEvent.getData().toBytes(), UTF_8), JsonObject.class);
225226
JsonObject wildcards = payload.getAsJsonObject("wildcards");
226227
assertThat(wildcards.keySet()).containsExactly("doc");
227228
assertThat(wildcards.getAsJsonPrimitive("doc").getAsString()).isEqualTo("2Vm2mI1d0wIaK2Waj5to");
@@ -236,7 +237,7 @@ private Event legacyEventForResource(String resourceName) throws IOException {
236237
}
237238

238239
private static Map<String, Object> cloudEventDataJson(CloudEvent cloudEvent) {
239-
String data = new String(cloudEvent.getData(), UTF_8);
240+
String data = new String(cloudEvent.getData().toBytes(), UTF_8);
240241
@SuppressWarnings("unchecked")
241242
Map<String, Object> map = new Gson().fromJson(data, Map.class);
242243
return map;

invoker/core/src/test/java/com/google/cloud/functions/invoker/IntegrationTest.java

Lines changed: 12 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,11 @@
2929
import com.google.gson.Gson;
3030
import com.google.gson.JsonObject;
3131
import io.cloudevents.CloudEvent;
32-
import io.cloudevents.SpecVersion;
3332
import io.cloudevents.core.builder.CloudEventBuilder;
3433
import io.cloudevents.core.format.EventFormat;
35-
import io.cloudevents.core.message.MessageWriter;
36-
import io.cloudevents.core.message.impl.MessageUtils;
3734
import io.cloudevents.core.provider.EventFormatProvider;
35+
import io.cloudevents.http.HttpMessageFactory;
3836
import io.cloudevents.jackson.JsonFormat;
39-
import io.cloudevents.rw.CloudEventWriter;
4037
import java.io.BufferedReader;
4138
import java.io.File;
4239
import java.io.IOException;
@@ -63,6 +60,7 @@
6360
import java.util.concurrent.Executors;
6461
import java.util.concurrent.Future;
6562
import java.util.concurrent.TimeUnit;
63+
import java.util.concurrent.atomic.AtomicReference;
6664
import java.util.regex.Pattern;
6765
import org.eclipse.jetty.client.HttpClient;
6866
import org.eclipse.jetty.client.api.ContentProvider;
@@ -353,11 +351,13 @@ private void backgroundTest(String target) throws Exception {
353351

354352
// A CloudEvent using the "binary content mode", where the metadata is in HTTP headers and the
355353
// payload is the body of the HTTP request.
356-
BinaryWriter binaryWriter = new BinaryWriter();
357-
Map<String, String> headers = binaryWriter.writeBinary(sampleCloudEvent(snoopFile));
354+
Map<String, String> headers = new TreeMap<>();
355+
AtomicReference<byte[]> bodyRef = new AtomicReference<>();
356+
HttpMessageFactory.createWriter(headers::put, bodyRef::set)
357+
.writeBinary(sampleCloudEvent(snoopFile));
358358
TestCase cloudEventsBinaryTestCase = TestCase.builder()
359359
.setSnoopFile(snoopFile)
360-
.setRequestText(new String(binaryWriter.body, UTF_8))
360+
.setRequestText(new String(bodyRef.get(), UTF_8))
361361
.setHttpContentType(headers.get("Content-Type"))
362362
.setHttpHeaders(ImmutableMap.copyOf(headers))
363363
.setExpectedJson(cloudEventExpectedJson)
@@ -388,11 +388,13 @@ public void nativeCloudEvent() throws Exception {
388388

389389
// A CloudEvent using the "binary content mode", where the metadata is in HTTP headers and the
390390
// payload is the body of the HTTP request.
391-
BinaryWriter binaryWriter = new BinaryWriter();
392-
Map<String, String> headers = binaryWriter.writeBinary(cloudEvent);
391+
Map<String, String> headers = new TreeMap<>();
392+
AtomicReference<byte[]> bodyRef = new AtomicReference<>();
393+
HttpMessageFactory.createWriter(headers::put, bodyRef::set)
394+
.writeBinary(sampleCloudEvent(snoopFile));
393395
TestCase cloudEventsBinaryTestCase = TestCase.builder()
394396
.setSnoopFile(snoopFile)
395-
.setRequestText(new String(binaryWriter.body, UTF_8))
397+
.setRequestText(new String(bodyRef.get(), UTF_8))
396398
.setHttpContentType(headers.get("Content-Type"))
397399
.setHttpHeaders(ImmutableMap.copyOf(headers))
398400
.setExpectedJson(cloudEventJsonObject)
@@ -688,57 +690,4 @@ private void monitorOutput(
688690
throw new UncheckedIOException(e);
689691
}
690692
}
691-
692-
// I might be missing something, but as far as I can tell the V2 SDK forces us to go through all this
693-
// rigmarole just so we can tell what HTTP headers should be set for a Binary CloudEvent. With the
694-
// V1 SDK it was much simpler.
695-
// https://github.com/cloudevents/sdk-java/issues/212
696-
private static class BinaryWriter
697-
implements MessageWriter<CloudEventWriter<Map<String, String>>, Map<String, String>> {
698-
699-
private static final Map<String, String> ATTRIBUTES_TO_HEADERS =
700-
MessageUtils.generateAttributesToHeadersMapping(v ->
701-
v.equals("datacontenttype") ? "Content-Type" : ("ce-" + v));
702-
703-
final Map<String, String> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
704-
byte[] body;
705-
706-
@Override
707-
public CloudEventWriter<Map<String, String>> create(SpecVersion version) {
708-
headers.put("ce-specversion", version.toString());
709-
return new EventWriter();
710-
}
711-
712-
@Override
713-
public Map<String, String> setEvent(EventFormat format, byte[] bytes) {
714-
throw new UnsupportedOperationException("Only binary events supported, not structured");
715-
}
716-
717-
private class EventWriter implements CloudEventWriter<Map<String, String>> {
718-
@Override
719-
public Map<String, String> end(byte[] bytes) {
720-
body = bytes;
721-
return headers;
722-
}
723-
724-
@Override
725-
public Map<String, String> end() {
726-
return end(new byte[0]);
727-
}
728-
729-
@Override
730-
public void setAttribute(String name, String value) {
731-
if (ATTRIBUTES_TO_HEADERS.containsKey(name)) {
732-
headers.put(ATTRIBUTES_TO_HEADERS.get(name), value);
733-
} else {
734-
throw new IllegalArgumentException("Unknown attribute: " + name);
735-
}
736-
}
737-
738-
@Override
739-
public void setExtension(String name, String value) {
740-
headers.put("ce-" + name, value);
741-
}
742-
}
743-
}
744693
}

invoker/core/src/test/java/com/google/cloud/functions/invoker/testfunctions/CloudEventSnoop.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
public class CloudEventSnoop implements ExperimentalCloudEventsFunction {
1515
@Override
1616
public void accept(CloudEvent event) throws Exception {
17-
String payloadJson = new String(event.getData(), UTF_8);
17+
String payloadJson = new String(event.getData().toBytes(), UTF_8);
1818
Gson gson = new Gson();
1919
JsonObject jsonObject = gson.fromJson(payloadJson, JsonObject.class);
2020
String targetFile = jsonObject.get("targetFile").getAsString();

invoker/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
<dependency>
4343
<groupId>com.google.cloud.functions</groupId>
4444
<artifactId>functions-framework-api</artifactId>
45-
<version>1.0.2</version>
45+
<version>1.0.3-SNAPSHOT</version>
4646
</dependency>
4747
</dependencies>
4848
</dependencyManagement>

0 commit comments

Comments
 (0)