Skip to content

Commit

Permalink
Add an HTTP example for protobuf serialization (#2107)
Browse files Browse the repository at this point in the history
Motivation:

HTTP users who work with protobuf payloads need an example how to use
`ProtobufSerializerFactory`.

Modifications:

- Add `servicetalk-examples-http-serialization-protobuf` module to
demonstrate use of `ProtobufSerializerFactory` with HTTP;
- Update docs to reference a new example;

Result:

Users can see how to use `ProtobufSerializerFactory` with HTTP.
  • Loading branch information
idelpivnitskiy authored Mar 7, 2022
1 parent 3dbd25a commit 2c310bb
Show file tree
Hide file tree
Showing 24 changed files with 818 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
** xref:{page-version}@servicetalk-examples::http/index.adoc#Debugging[Debugging]
** xref:{page-version}@servicetalk-examples::http/index.adoc#Timeout[Timeout]
** xref:{page-version}@servicetalk-examples::http/index.adoc#SerializationJson[Serialization: JSON]
** xref:{page-version}@servicetalk-examples::http/index.adoc#SerializationProtobuf[Serialization: Protobuf]
** xref:{page-version}@servicetalk-examples::http/index.adoc#JAXRS[JAX-RS]
** xref:{page-version}@servicetalk-examples::http/index.adoc#MetaData[MetaData]
** xref:{page-version}@servicetalk-examples::http/index.adoc#HTTP2[HTTP/2]
Expand Down
16 changes: 16 additions & 0 deletions servicetalk-examples/docs/modules/ROOT/pages/http/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,22 @@ with `Content-Type: application/json` and link:{source-root}/servicetalk-example
All serializers and deserializers defined in
link:{source-root}/servicetalk-examples/http/serialization/json/src/main/java/io/servicetalk/examples/http/serialization/json/SerializerUtils.java[SerializerUtils].

[#SerializationProtobuf]
== Serialization: Protobuf

An example similar to "Hello World" examples, which demonstrates
link:{source-root}/servicetalk-examples/http/serialization/protobuf/src/main/java/io/servicetalk/examples/http/serialization/protobuf/async[asynchronous-aggregated],
link:{source-root}/servicetalk-examples/http/serialization/protobuf/src/main/java/io/servicetalk/examples/http/serialization/protobuf/async/streaming[asynchronous-streaming],
link:{source-root}/servicetalk-examples/http/serialization/protobuf/src/main/java/io/servicetalk/examples/http/serialization/protobuf/blocking[blocking-aggregated], and
link:{source-root}/servicetalk-examples/http/serialization/protobuf/src/main/java/io/servicetalk/examples/http/serialization/protobuf/blocking/streaming[blocking-streaming]
client and server with Protobuf serialization of simple proto objects.

Client sends a `POST` request with a Protobuf payload `RequestMessage` and expects a response with
`Content-Type: application/protobuf` and `ResponseMessage` as a payload
(link:{source-root}/servicetalk-examples/http/serialization/protobuf/src/main/proto/message.proto[message.proto]).
All serializers and deserializers defined in
link:{source-root}/servicetalk-examples/http/serialization/protobuf/src/main/java/io/servicetalk/examples/http/serialization/protobuf/SerializerUtils.java[SerializerUtils].

[#JAXRS]
== JAX-RS

Expand Down
44 changes: 44 additions & 0 deletions servicetalk-examples/http/serialization/protobuf/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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.
*/

buildscript {
dependencies {
classpath "com.google.protobuf:protobuf-gradle-plugin:$protobufGradlePluginVersion"
}
}

apply plugin: "java"
apply from: "../../../gradle/idea.gradle"
apply plugin: "com.google.protobuf"

dependencies {
implementation project(":servicetalk-annotations")
implementation project(":servicetalk-data-protobuf")
implementation project(":servicetalk-http-netty")
implementation "com.google.protobuf:protobuf-java:$protobufVersion"

runtimeOnly "org.apache.logging.log4j:log4j-slf4j-impl:$log4jVersion"
}

protobuf {
protoc {
artifact = "com.google.protobuf:protoc:$protobufVersion"
}
}

clean {
delete protobuf.generatedFilesBaseDir
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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.servicetalk.examples.http.serialization.protobuf;

import io.servicetalk.data.protobuf.ProtobufSerializerFactory;
import io.servicetalk.examples.http.serialization.protobuf.ExampleProtos.RequestMessage;
import io.servicetalk.examples.http.serialization.protobuf.ExampleProtos.ResponseMessage;
import io.servicetalk.http.api.HttpHeaders;
import io.servicetalk.http.api.HttpSerializerDeserializer;
import io.servicetalk.http.api.HttpSerializers;
import io.servicetalk.http.api.HttpStreamingSerializerDeserializer;

import java.util.function.Consumer;
import java.util.function.Predicate;

import static io.servicetalk.buffer.api.CharSequences.newAsciiString;
import static io.servicetalk.data.protobuf.ProtobufSerializerFactory.PROTOBUF;
import static io.servicetalk.http.api.HeaderUtils.hasContentType;
import static io.servicetalk.http.api.HttpHeaderNames.CONTENT_TYPE;
import static io.servicetalk.http.api.HttpSerializers.serializer;
import static io.servicetalk.http.api.HttpSerializers.streamingSerializer;

/**
* Utilities to cache serializer instances for request/response protos.
* <p>
* {@link ProtobufSerializerFactory} produces protocol-agnostic serializer/deserializer. We use {@link HttpSerializers}
* utilities to concert it to HTTP-aware variants.
*/
public final class SerializerUtils {

private static final CharSequence APPLICATION_PROTOBUF = newAsciiString("application/protobuf");
private static final Consumer<HttpHeaders> CONTENT_TYPE_SETTER =
headers -> headers.set(CONTENT_TYPE, APPLICATION_PROTOBUF);
private static final Predicate<HttpHeaders> CONTENT_TYPE_VALIDATOR =
headers -> hasContentType(headers, APPLICATION_PROTOBUF, null);

public static final HttpSerializerDeserializer<RequestMessage> REQ_SERIALIZER =
serializer(PROTOBUF.serializerDeserializer(RequestMessage.parser()),
CONTENT_TYPE_SETTER, CONTENT_TYPE_VALIDATOR);

public static final HttpStreamingSerializerDeserializer<RequestMessage> REQ_STREAMING_SERIALIZER =
streamingSerializer(PROTOBUF.streamingSerializerDeserializer(RequestMessage.parser()),
CONTENT_TYPE_SETTER, CONTENT_TYPE_VALIDATOR);

public static final HttpSerializerDeserializer<ResponseMessage> RESP_SERIALIZER =
serializer(PROTOBUF.serializerDeserializer(ResponseMessage.parser()),
CONTENT_TYPE_SETTER, CONTENT_TYPE_VALIDATOR);

public static final HttpStreamingSerializerDeserializer<ResponseMessage> RESP_STREAMING_SERIALIZER =
streamingSerializer(PROTOBUF.streamingSerializerDeserializer(ResponseMessage.parser()),
CONTENT_TYPE_SETTER, CONTENT_TYPE_VALIDATOR);

private SerializerUtils() {
// No instances.
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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.servicetalk.examples.http.serialization.protobuf.async;

import io.servicetalk.examples.http.serialization.protobuf.ExampleProtos.RequestMessage;
import io.servicetalk.http.api.HttpClient;
import io.servicetalk.http.netty.HttpClients;

import static io.servicetalk.examples.http.serialization.protobuf.SerializerUtils.REQ_SERIALIZER;
import static io.servicetalk.examples.http.serialization.protobuf.SerializerUtils.RESP_SERIALIZER;

public final class ProtobufClient {
public static void main(String[] args) throws Exception {
try (HttpClient client = HttpClients.forSingleAddress("localhost", 8080).build()) {
client.request(client.post("/protobuf")
.payloadBody(RequestMessage.newBuilder().setMessage("value").build(), REQ_SERIALIZER))
.whenOnSuccess(resp -> {
System.out.println(resp.toString((name, value) -> value));
System.out.println(resp.payloadBody(RESP_SERIALIZER));
})
// This example is demonstrating asynchronous execution, but needs to prevent the main thread from exiting
// before the response has been processed. This isn't typical usage for an asynchronous API but is useful
// for demonstration purposes.
.toFuture().get();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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.servicetalk.examples.http.serialization.protobuf.async;

import io.servicetalk.examples.http.serialization.protobuf.ExampleProtos.RequestMessage;
import io.servicetalk.examples.http.serialization.protobuf.ExampleProtos.ResponseMessage;
import io.servicetalk.http.netty.HttpServers;

import static io.servicetalk.concurrent.api.Single.succeeded;
import static io.servicetalk.examples.http.serialization.protobuf.SerializerUtils.REQ_SERIALIZER;
import static io.servicetalk.examples.http.serialization.protobuf.SerializerUtils.RESP_SERIALIZER;
import static io.servicetalk.http.api.HttpHeaderNames.ALLOW;
import static io.servicetalk.http.api.HttpRequestMethod.POST;

public final class ProtobufServer {
public static void main(String[] args) throws Exception {
HttpServers.forPort(8080)
.listenAndAwait((ctx, request, responseFactory) -> {
if (!"/protobuf".equals(request.requestTarget())) {
return succeeded(responseFactory.notFound());
}
if (!POST.equals(request.method())) {
return succeeded(responseFactory.methodNotAllowed().addHeader(ALLOW, POST.name()));
}
RequestMessage req = request.payloadBody(REQ_SERIALIZER);
ResponseMessage resp = ResponseMessage.newBuilder().setLength(req.getMessage().length()).build();
return succeeded(responseFactory.created()
.payloadBody(resp, RESP_SERIALIZER));
})
.awaitShutdown();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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.servicetalk.examples.http.serialization.protobuf.async;

import io.servicetalk.examples.http.serialization.protobuf.ExampleProtos;
import io.servicetalk.http.api.HttpClient;
import io.servicetalk.http.netty.HttpClients;

import static io.servicetalk.examples.http.serialization.protobuf.SerializerUtils.REQ_SERIALIZER;
import static io.servicetalk.examples.http.serialization.protobuf.SerializerUtils.RESP_SERIALIZER;

public final class ProtobufUrlClient {
public static void main(String[] args) throws Exception {
try (HttpClient client = HttpClients.forMultiAddressUrl().build()) {
client.request(client.post("http://localhost:8080/protobuf")
.payloadBody(ExampleProtos.RequestMessage.newBuilder().setMessage("hello").build(), REQ_SERIALIZER))
.whenOnSuccess(resp -> {
System.out.println(resp.toString((name, value) -> value));
System.out.println(resp.payloadBody(RESP_SERIALIZER));
})
// This example is demonstrating asynchronous execution, but needs to prevent the main thread from exiting
// before the response has been processed. This isn't typical usage for an asynchronous API but is useful
// for demonstration purposes.
.toFuture().get();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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.
*/
@ElementsAreNonnullByDefault
package io.servicetalk.examples.http.serialization.protobuf.async;

import io.servicetalk.annotations.ElementsAreNonnullByDefault;
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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.servicetalk.examples.http.serialization.protobuf.async.streaming;

import io.servicetalk.examples.http.serialization.protobuf.ExampleProtos.RequestMessage;
import io.servicetalk.http.api.StreamingHttpClient;
import io.servicetalk.http.netty.HttpClients;

import static io.servicetalk.concurrent.api.Publisher.from;
import static io.servicetalk.examples.http.serialization.protobuf.SerializerUtils.REQ_STREAMING_SERIALIZER;
import static io.servicetalk.examples.http.serialization.protobuf.SerializerUtils.RESP_STREAMING_SERIALIZER;

public final class ProtobufStreamingClient {
public static void main(String[] args) throws Exception {
try (StreamingHttpClient client = HttpClients.forSingleAddress("localhost", 8080).buildStreaming()) {
client.request(client.post("/protobuf")
.payloadBody(from("value1", "value22", "value333")
.map(message -> RequestMessage.newBuilder().setMessage(message).build()),
REQ_STREAMING_SERIALIZER))
.beforeOnSuccess(response -> System.out.println(response.toString((name, value) -> value)))
.flatMapPublisher(resp -> resp.payloadBody(RESP_STREAMING_SERIALIZER))
.whenOnNext(System.out::println)
// This example is demonstrating asynchronous execution, but needs to prevent the main thread from exiting
// before the response has been processed. This isn't typical usage for an asynchronous API but is useful
// for demonstration purposes.
.toFuture().get();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright © 2022 Apple Inc. and the ServiceTalk project 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.servicetalk.examples.http.serialization.protobuf.async.streaming;

import io.servicetalk.examples.http.serialization.protobuf.ExampleProtos.ResponseMessage;
import io.servicetalk.http.netty.HttpServers;

import static io.servicetalk.concurrent.api.Single.succeeded;
import static io.servicetalk.examples.http.serialization.protobuf.SerializerUtils.REQ_STREAMING_SERIALIZER;
import static io.servicetalk.examples.http.serialization.protobuf.SerializerUtils.RESP_STREAMING_SERIALIZER;
import static io.servicetalk.http.api.HttpHeaderNames.ALLOW;
import static io.servicetalk.http.api.HttpRequestMethod.POST;

public final class ProtobufStreamingServer {
public static void main(String[] args) throws Exception {
HttpServers.forPort(8080)
.listenStreamingAndAwait((ctx, request, responseFactory) -> {
if (!"/protobuf".equals(request.requestTarget())) {
return succeeded(responseFactory.notFound());
}
if (!POST.equals(request.method())) {
return succeeded(responseFactory.methodNotAllowed().addHeader(ALLOW, POST.name()));
}
return succeeded(responseFactory.created()
.payloadBody(request.payloadBody(REQ_STREAMING_SERIALIZER)
.map(req -> ResponseMessage.newBuilder().setLength(req.getMessage().length()).build()),
RESP_STREAMING_SERIALIZER));
})
.awaitShutdown();
}
}
Loading

0 comments on commit 2c310bb

Please sign in to comment.