Skip to content

Commit

Permalink
feat: Java library
Browse files Browse the repository at this point in the history
  • Loading branch information
ngyewch committed Jul 22, 2024
1 parent cdbf347 commit 7c2c538
Show file tree
Hide file tree
Showing 4 changed files with 177 additions and 1 deletion.
24 changes: 23 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,30 @@
plugins {
`java-library`
`maven-publish`
signing
id("com.gradleup.nmcp") version "0.0.8"
id("com.gradleup.nmcp") version "0.0.9"
id("io.github.ngyewch.protoc.plugin")
id("com.autonomousapps.dependency-analysis") version "1.32.0"
id("com.diffplug.spotless") version "6.25.0"
id("com.github.ben-manes.versions") version "0.51.0"
id("se.ascp.gradle.gradle-versions-filter") version "0.1.16"
}

repositories {
mavenCentral()
}

dependencies {
api(platform("com.google.protobuf:protobuf-bom:4.27.2"))
api(platform("io.helidon:helidon-bom:2.6.7"))

api("com.google.protobuf:protobuf-java")
implementation("com.google.protobuf:protobuf-java-util")
api("io.helidon.common:helidon-common-http")
implementation("io.helidon.common:helidon-common-reactive")
implementation("io.helidon.media:helidon-media-common")
implementation("io.helidon.webclient:helidon-webclient")
api("io.helidon.webserver:helidon-webserver")
}

publishing {
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/io/github/ngyewch/twirp/Constants.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.github.ngyewch.twirp;

public class Constants {
public static final String PROTOBUF_CONTENT_TYPE = "application/protobuf";
public static final String JSON_CONTENT_TYPE = "application/json";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package io.github.ngyewch.twirp.helidon.client;

import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import io.github.ngyewch.twirp.Constants;
import io.helidon.common.http.MediaType;
import io.helidon.webclient.WebClient;
import java.util.concurrent.ExecutionException;

public abstract class AbstractService {
private static final MediaType PROTOBUF_MEDIA_TYPE =
MediaType.parse(Constants.PROTOBUF_CONTENT_TYPE);
private static final MediaType JSON_MEDIA_TYPE = MediaType.parse(Constants.JSON_CONTENT_TYPE);

private final WebClient webClient;
private final MediaType contentType;

protected AbstractService(String baseUri, MediaType contentType) {
super();

webClient = WebClient.builder().baseUri(baseUri).build();
this.contentType = contentType;
}

protected void doRequest(String path, Message input, Message.Builder outputBuilder) {
if (contentType.equals(PROTOBUF_MEDIA_TYPE)) {
doProtobufRequest(path, input, outputBuilder);
} else if (contentType.equals(JSON_MEDIA_TYPE)) {
doJsonRequest(path, input, outputBuilder);
} else {
throw new IllegalArgumentException("unsupported content type");
}
}

private void doProtobufRequest(String path, Message input, Message.Builder outputBuilder) {
try {
webClient
.post()
.path(path)
.contentType(PROTOBUF_MEDIA_TYPE)
.submit(input.toByteArray(), byte[].class)
.map(
responseBytes -> {
try {
return outputBuilder.mergeFrom(responseBytes);
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
})
.get();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}

private void doJsonRequest(String path, Message input, Message.Builder outputBuilder) {
try {
final String requestJson = JsonFormat.printer().print(input);
webClient
.post()
.path(path)
.contentType(JSON_MEDIA_TYPE)
.submit(requestJson, String.class)
.map(
responseJson -> {
try {
JsonFormat.parser().merge(responseJson, outputBuilder);
return outputBuilder;
} catch (InvalidProtocolBufferException e) {
throw new RuntimeException(e);
}
})
.get();
} catch (InterruptedException | InvalidProtocolBufferException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e.getCause());
}
}
}
65 changes: 65 additions & 0 deletions src/main/java/io/github/ngyewch/twirp/helidon/server/Handler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package io.github.ngyewch.twirp.helidon.server;

import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import io.github.ngyewch.twirp.Constants;
import io.helidon.common.http.Http;
import io.helidon.common.http.MediaType;
import io.helidon.webserver.ServerRequest;
import io.helidon.webserver.ServerResponse;
import java.util.function.Supplier;

public class Handler {
private static final MediaType PROTOBUF_MEDIA_TYPE =
MediaType.parse(Constants.PROTOBUF_CONTENT_TYPE);
private static final MediaType JSON_MEDIA_TYPE = MediaType.parse(Constants.JSON_CONTENT_TYPE);

public static void handleTwirp(
ServerRequest req,
ServerResponse res,
Message.Builder messageBuilder,
Supplier<Message> serviceInvoker) {
if (req.headers().contentType().isEmpty()) {
res.status(Http.Status.BAD_REQUEST_400).send("Content-Type not specified");
return;
}
final MediaType contentType = req.headers().contentType().get();
if (contentType.equals(PROTOBUF_MEDIA_TYPE)) {
req.content()
.as(byte[].class)
.thenAccept(
contentBytes -> {
try {
messageBuilder.mergeFrom(contentBytes);
final Message response = serviceInvoker.get();
res.headers().contentType(contentType);
res.send(response.toByteArray());
} catch (InvalidProtocolBufferException e) {
res.status(Http.Status.BAD_REQUEST_400).send("Malformed content");
} catch (Exception e) {
res.status(Http.Status.INTERNAL_SERVER_ERROR_500).send(e.toString());
}