Skip to content

Commit

Permalink
feat: initial work on Apache HttpClient
Browse files Browse the repository at this point in the history
  • Loading branch information
ngyewch committed Oct 1, 2024
1 parent 12653c4 commit 2fb772a
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 0 deletions.
85 changes: 85 additions & 0 deletions java/twirp-apache-httpclient/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
plugins {
`java-library`
`maven-publish`
signing
id("com.diffplug.spotless")
id("com.gradleup.nmcp")
}

java {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11

withJavadocJar()
withSourcesJar()
}

repositories {
mavenCentral()
}

dependencies {
api(platform(project(":twirp-bom")))

implementation(project(":twirp-common"))

api("com.google.protobuf:protobuf-java")
implementation("com.google.protobuf:protobuf-java-util")
implementation("org.apache.httpcomponents:httpclient:4.5.14")
}

publishing {
publications {
create<MavenPublication>("maven") {
groupId = project.group as String
artifactId = project.name
version = project.version as String

from(components["java"])

pom {
name = project.name
description = "Twirp Unirest client."
url = "https://github.com/ngyewch/protoc-gen-twirp-java"
licenses {
license {
name = "MIT License"
url = "https://github.com/ngyewch/protoc-gen-twirp-java/blob/main/LICENSE"
}
}
scm {
connection = "scm:git:git@github.com:ngyewch/protoc-gen-twirp-java.git"
developerConnection = "scm:git:git@github.com:ngyewch/protoc-gen-twirp-java.git"
url = "https://github.com/ngyewch/protoc-gen-twirp-java"
}
developers {
developer {
id.set("ngyewch")
name.set("Nick Ng")
}
}
}
}
}
}

signing {
useGpgCmd()
sign(publishing.publications["maven"])
}

spotless {
java {
googleJavaFormat("1.23.0").reflowLongStrings().skipJavadocFormatting()
formatAnnotations()
targetExclude("build/**")
}
}

nmcp {
publish("maven") {
username = project.properties["mavenCentralUsername"] as String?
password = project.properties["mavenCentralPassword"] as String?
publicationType = "AUTOMATIC"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package io.github.ngyewch.twirp.httpclient.client;

import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.Message;
import com.google.protobuf.util.JsonFormat;
import io.github.ngyewch.twirp.*;
import java.io.IOException;
import java.net.URI;
import org.apache.http.HttpEntity;
import org.apache.http.HttpStatus;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public abstract class AbstractService {
public static final ContentType PROTOBUF_CONTENT_TYPE =
ContentType.create("application/protobuf");
public static final ContentType JSON_CONTENT_TYPE = ContentType.create("application/json");

private final URI baseUri;
private final CloseableHttpClient httpClient;
private final ContentType contentType;

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

this.baseUri = URI.create(baseUri);
this.httpClient = HttpClients.createDefault();
this.contentType = contentType;
}

protected void doRequest(String path, Message input, Message.Builder outputBuilder) {
final URI uri = baseUri.resolve(path);
try {
if (contentType == PROTOBUF_CONTENT_TYPE) {
doProtobufRequest(uri, input, outputBuilder);
} else if (contentType == JSON_CONTENT_TYPE) {
doJsonRequest(uri, input, outputBuilder);
} else {
throw new IllegalArgumentException("unsupported content type");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}

private void doProtobufRequest(URI uri, Message input, Message.Builder outputBuilder)
throws IOException {
final HttpEntity requestBody = new ByteArrayEntity(input.toByteArray(), PROTOBUF_CONTENT_TYPE);
final HttpPost request = new HttpPost(uri);
request.setEntity(requestBody);
try (final CloseableHttpResponse response = httpClient.execute(request)) {
final HttpEntity responseBody = response.getEntity();
try {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
expectContentType(responseBody, Constants.PROTOBUF_CONTENT_TYPE);
mergeProtobuf(outputBuilder, EntityUtils.toByteArray(responseBody));
} else {
expectContentType(responseBody, Constants.JSON_CONTENT_TYPE);
throw new TwirpException(TwirpError.fromJson(EntityUtils.toString(responseBody)));
}
} finally {
EntityUtils.consume(responseBody);
}
}
}

private void doJsonRequest(URI uri, Message input, Message.Builder outputBuilder)
throws IOException {
final HttpEntity requestBody =
new StringEntity(JsonFormat.printer().print(input), ContentType.APPLICATION_JSON);
final HttpPost request = new HttpPost(uri);
request.setEntity(requestBody);
try (final CloseableHttpResponse response = httpClient.execute(request)) {
final HttpEntity responseBody = response.getEntity();
try {
if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
expectContentType(responseBody, Constants.JSON_CONTENT_TYPE);
mergeProtobufJson(outputBuilder, EntityUtils.toString(responseBody));
} else {
expectContentType(responseBody, Constants.JSON_CONTENT_TYPE);
throw new TwirpException(TwirpError.fromJson(EntityUtils.toString(responseBody)));
}
} finally {
EntityUtils.consume(responseBody);
}
}
}

private void expectContentType(HttpEntity responseBody, String expectedContentType) {
final String contentType =
(responseBody.getContentType() != null) ? responseBody.getContentType().getValue() : null;
if (contentType == null) {
throw new TwirpException(TwirpErrorCode.INVALID_ARGUMENT, "content type not specified", null);
} else if (!contentType.equals(expectedContentType)) {
throw new TwirpException(
TwirpErrorCode.INVALID_ARGUMENT,
"unexpected content type",
new Meta().set("Content-Type", contentType).get());
}
}

private Message.Builder mergeProtobufJson(Message.Builder messageBuilder, String json) {
try {
JsonFormat.parser().merge(json, messageBuilder);
return messageBuilder;
} catch (InvalidProtocolBufferException e) {
throw new TwirpException(TwirpErrorCode.MALFORMED, e, true);
}
}

private Message.Builder mergeProtobuf(Message.Builder messageBuilder, byte[] data) {
try {
return messageBuilder.mergeFrom(data);
} catch (InvalidProtocolBufferException e) {
throw new TwirpException(TwirpErrorCode.MALFORMED, e, true);
}
}
}

0 comments on commit 2fb772a

Please sign in to comment.