Skip to content

Commit 81ad5bc

Browse files
committed
feat(gax): allow non-json HttpContent in HttpRequestFormatter and HttpRequestRunnable
1 parent d4df69d commit 81ad5bc

3 files changed

Lines changed: 84 additions & 23 deletions

File tree

sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpRequestFormatter.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@
2929
*/
3030
package com.google.api.gax.httpjson;
3131

32+
import com.google.api.client.http.ByteArrayContent;
33+
import com.google.api.client.http.EmptyContent;
34+
import com.google.api.client.http.HttpContent;
3235
import com.google.api.pathtemplate.PathTemplate;
36+
import com.google.common.base.Strings;
3337
import java.util.Collections;
3438
import java.util.List;
3539
import java.util.Map;
@@ -56,4 +60,16 @@ public interface HttpRequestFormatter<MessageFormatT> {
5660
default List<PathTemplate> getAdditionalPathTemplates() {
5761
return Collections.emptyList();
5862
}
63+
64+
/**
65+
* Return {@link HttpContent} representing the request body. Defaults to converting {@link
66+
* #getRequestBody(Object)} to JSON, or {@link EmptyContent} if the body is empty.
67+
*/
68+
default HttpContent getHttpContent(MessageFormatT apiMessage) {
69+
String requestBody = getRequestBody(apiMessage);
70+
if (!Strings.isNullOrEmpty(requestBody)) {
71+
return ByteArrayContent.fromString("application/json; charset=utf-8", requestBody);
72+
}
73+
return new EmptyContent();
74+
}
5975
}

sdk-platform-java/gax-java/gax-httpjson/src/main/java/com/google/api/gax/httpjson/HttpRequestRunnable.java

Lines changed: 5 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -29,26 +29,21 @@
2929
*/
3030
package com.google.api.gax.httpjson;
3131

32-
import com.google.api.client.http.EmptyContent;
3332
import com.google.api.client.http.GenericUrl;
3433
import com.google.api.client.http.HttpContent;
35-
import com.google.api.client.http.HttpMediaType;
3634
import com.google.api.client.http.HttpMethods;
3735
import com.google.api.client.http.HttpRequest;
3836
import com.google.api.client.http.HttpRequestFactory;
3937
import com.google.api.client.http.HttpResponse;
4038
import com.google.api.client.http.HttpResponseException;
4139
import com.google.api.client.http.HttpTransport;
42-
import com.google.api.client.http.json.JsonHttpContent;
4340
import com.google.api.client.json.JsonFactory;
4441
import com.google.api.client.json.JsonObjectParser;
4542
import com.google.api.client.json.gson.GsonFactory;
46-
import com.google.api.client.util.GenericData;
4743
import com.google.api.gax.tracing.ApiTracer;
4844
import com.google.auth.Credentials;
4945
import com.google.auth.http.HttpCredentialsAdapter;
5046
import com.google.auto.value.AutoValue;
51-
import com.google.common.base.Strings;
5247
import java.io.ByteArrayInputStream;
5348
import java.io.IOException;
5449
import java.io.InputStream;
@@ -154,8 +149,6 @@ public void run() {
154149
}
155150

156151
HttpRequest createHttpRequest() throws IOException {
157-
GenericData tokenRequest = new GenericData();
158-
159152
HttpRequestFormatter<RequestT> requestFormatter = methodDescriptor.getRequestFormatter();
160153

161154
HttpRequestFactory requestFactory;
@@ -166,20 +159,8 @@ HttpRequest createHttpRequest() throws IOException {
166159
requestFactory = httpTransport.createRequestFactory();
167160
}
168161

169-
JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
170162
// Create HTTP request body.
171-
String requestBody = requestFormatter.getRequestBody(request);
172-
HttpContent jsonHttpContent;
173-
if (!Strings.isNullOrEmpty(requestBody)) {
174-
jsonFactory.createJsonParser(requestBody).parse(tokenRequest);
175-
jsonHttpContent =
176-
new JsonHttpContent(jsonFactory, tokenRequest)
177-
.setMediaType((new HttpMediaType("application/json; charset=utf-8")));
178-
} else {
179-
// Force underlying HTTP lib to set Content-Length header to avoid 411s.
180-
// See EmptyContent.java.
181-
jsonHttpContent = new EmptyContent();
182-
}
163+
HttpContent httpContent = requestFormatter.getHttpContent(request);
183164

184165
// Populate URL path and query parameters.
185166
String normalizedEndpoint = normalizeEndpoint(endpoint);
@@ -196,20 +177,21 @@ HttpRequest createHttpRequest() throws IOException {
196177
tracer.requestUrlResolved(url.build());
197178
}
198179

199-
HttpRequest httpRequest = buildRequest(requestFactory, url, jsonHttpContent);
180+
HttpRequest httpRequest = buildRequest(requestFactory, url, httpContent);
200181

201182
for (Map.Entry<String, Object> entry : headers.getHeaders().entrySet()) {
202183
HttpHeadersUtils.setHeader(
203184
httpRequest.getHeaders(), entry.getKey(), (String) entry.getValue());
204185
}
205186

187+
JsonFactory jsonFactory = GsonFactory.getDefaultInstance();
206188
httpRequest.setParser(new JsonObjectParser(jsonFactory));
207189

208190
return httpRequest;
209191
}
210192

211193
private HttpRequest buildRequest(
212-
HttpRequestFactory requestFactory, GenericUrl url, HttpContent jsonHttpContent)
194+
HttpRequestFactory requestFactory, GenericUrl url, HttpContent httpContent)
213195
throws IOException {
214196
// A workaround to support PATCH request. This assumes support of "X-HTTP-Method-Override"
215197
// header on the server side, which GCP services usually do.
@@ -235,7 +217,7 @@ private HttpRequest buildRequest(
235217
if (HttpMethods.PATCH.equals(actualHttpMethod)) {
236218
actualHttpMethod = HttpMethods.POST;
237219
}
238-
HttpRequest httpRequest = requestFactory.buildRequest(actualHttpMethod, url, jsonHttpContent);
220+
HttpRequest httpRequest = requestFactory.buildRequest(actualHttpMethod, url, httpContent);
239221
if (originalHttpMethod != null && !originalHttpMethod.equals(actualHttpMethod)) {
240222
HttpHeadersUtils.setHeader(
241223
httpRequest.getHeaders(), "X-HTTP-Method-Override", originalHttpMethod);

sdk-platform-java/gax-java/gax-httpjson/src/test/java/com/google/api/gax/httpjson/HttpRequestRunnableTest.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,19 +31,24 @@
3131

3232
import static org.mockito.Mockito.mock;
3333

34+
import com.google.api.client.http.ByteArrayContent;
3435
import com.google.api.client.http.EmptyContent;
36+
import com.google.api.client.http.HttpContent;
3537
import com.google.api.client.http.HttpRequest;
3638
import com.google.api.client.testing.http.MockHttpTransport;
3739
import com.google.api.gax.tracing.ApiTracer;
40+
import com.google.api.pathtemplate.PathTemplate;
3841
import com.google.common.truth.Truth;
3942
import com.google.longrunning.ListOperationsRequest;
43+
import com.google.protobuf.ByteString;
4044
import com.google.protobuf.Empty;
4145
import com.google.protobuf.Field;
4246
import com.google.protobuf.util.JsonFormat;
4347
import java.io.ByteArrayOutputStream;
4448
import java.io.IOException;
4549
import java.nio.charset.StandardCharsets;
4650
import java.util.Arrays;
51+
import java.util.Collections;
4752
import java.util.HashMap;
4853
import java.util.LinkedHashMap;
4954
import java.util.List;
@@ -326,4 +331,62 @@ void testUpdateRunnableTimeout_shouldUpdate() throws IOException {
326331
Truth.assertThat(httpRequest.getReadTimeout()).isEqualTo(30000L);
327332
Truth.assertThat(httpRequest.getConnectTimeout()).isEqualTo(30000L);
328333
}
334+
335+
@Test
336+
void testNonJsonHttpContent() throws IOException {
337+
ByteString rawPayload = ByteString.copyFromUtf8("binary \0 raw \1 payload");
338+
HttpRequestFormatter<Field> binaryRequestFormatter =
339+
new HttpRequestFormatter<Field>() {
340+
@Override
341+
public Map<String, List<String>> getQueryParamNames(Field apiMessage) {
342+
return Collections.emptyMap();
343+
}
344+
345+
@Override
346+
public String getRequestBody(Field apiMessage) {
347+
return "";
348+
}
349+
350+
@Override
351+
public HttpContent getHttpContent(Field apiMessage) {
352+
return new ByteArrayContent("application/octet-stream", rawPayload.toByteArray());
353+
}
354+
355+
@Override
356+
public String getPath(Field apiMessage) {
357+
return "/upload";
358+
}
359+
360+
@Override
361+
public PathTemplate getPathTemplate() {
362+
return PathTemplate.create("{+path}");
363+
}
364+
};
365+
366+
ApiMethodDescriptor<Field, Empty> methodDescriptor =
367+
ApiMethodDescriptor.<Field, Empty>newBuilder()
368+
.setFullMethodName("upload.binary")
369+
.setHttpMethod("POST")
370+
.setRequestFormatter(binaryRequestFormatter)
371+
.setResponseParser(responseParser)
372+
.build();
373+
374+
HttpRequestRunnable<Field, Empty> httpRequestRunnable =
375+
new HttpRequestRunnable<>(
376+
requestMessage,
377+
methodDescriptor,
378+
ENDPOINT,
379+
HttpJsonCallOptions.newBuilder().build(),
380+
new MockHttpTransport(),
381+
HttpJsonMetadata.newBuilder().build(),
382+
(result) -> {});
383+
384+
HttpRequest httpRequest = httpRequestRunnable.createHttpRequest();
385+
Truth.assertThat(httpRequest.getContent()).isInstanceOf(ByteArrayContent.class);
386+
Truth.assertThat(httpRequest.getContent().getType()).isEqualTo("application/octet-stream");
387+
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
388+
httpRequest.getContent().writeTo(out);
389+
Truth.assertThat(out.toByteArray()).isEqualTo(rawPayload.toByteArray());
390+
}
391+
}
329392
}

0 commit comments

Comments
 (0)