Skip to content
This repository was archived by the owner on Sep 26, 2023. It is now read-only.

Commit 2c6ac24

Browse files
authored
fix!: Fix PATCH being unsupported (#1465)
1 parent dcfbd93 commit 2c6ac24

File tree

3 files changed

+94
-14
lines changed

3 files changed

+94
-14
lines changed

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

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import com.google.api.client.http.GenericUrl;
3434
import com.google.api.client.http.HttpContent;
3535
import com.google.api.client.http.HttpMediaType;
36+
import com.google.api.client.http.HttpMethods;
3637
import com.google.api.client.http.HttpRequest;
3738
import com.google.api.client.http.HttpRequestFactory;
3839
import com.google.api.client.http.HttpResponse;
@@ -112,15 +113,50 @@ HttpRequest createHttpRequest() throws IOException {
112113
}
113114
}
114115

115-
HttpRequest httpRequest =
116-
requestFactory.buildRequest(getApiMethodDescriptor().getHttpMethod(), url, jsonHttpContent);
116+
HttpRequest httpRequest = buildRequest(requestFactory, url, jsonHttpContent);
117+
117118
for (HttpJsonHeaderEnhancer enhancer : getHeaderEnhancers()) {
118119
enhancer.enhance(httpRequest.getHeaders());
119120
}
120121
httpRequest.setParser(new JsonObjectParser(getJsonFactory()));
121122
return httpRequest;
122123
}
123124

125+
private HttpRequest buildRequest(
126+
HttpRequestFactory requestFactory, GenericUrl url, HttpContent jsonHttpContent)
127+
throws IOException {
128+
// A workaround to support PATCH request. This assumes support of "X-HTTP-Method-Override"
129+
// header on the server side, which GCP services usually do.
130+
//
131+
// Long story short, the problems is as follows: gax-httpjson depends on NetHttpTransport class
132+
// from google-http-client, which depends on JDK standard java.net.HttpUrlConnection, which does
133+
// not support PATCH http method.
134+
//
135+
// It is a won't fix for JDK8: https://bugs.openjdk.java.net/browse/JDK-8207840.
136+
// A corresponding google-http-client issue:
137+
// https://github.com/googleapis/google-http-java-client/issues/167
138+
//
139+
// In JDK11 there is java.net.http.HttpRequest with PATCH method support but, gax-httpjson must
140+
// remain compatible with Java 8.
141+
//
142+
// Using "X-HTTP-Method-Override" header is probably the cleanest way to fix it. Other options
143+
// would be: hideous reflection hacks (not a safe option in a generic library, which
144+
// gax-httpjson is), writing own implementation of HttpUrlConnection (fragile and a lot of
145+
// work), depending on v2.ApacheHttpTransport (it has many extra dependencies, does not support
146+
// mtls etc).
147+
String actualHttpMethod = getApiMethodDescriptor().getHttpMethod();
148+
String originalHttpMethod = actualHttpMethod;
149+
if (HttpMethods.PATCH.equals(actualHttpMethod)) {
150+
actualHttpMethod = HttpMethods.POST;
151+
}
152+
HttpRequest httpRequest = requestFactory.buildRequest(actualHttpMethod, url, jsonHttpContent);
153+
if (originalHttpMethod != null && !originalHttpMethod.equals(actualHttpMethod)) {
154+
HttpHeadersUtils.setHeader(
155+
httpRequest.getHeaders(), "X-HTTP-Method-Override", originalHttpMethod);
156+
}
157+
return httpRequest;
158+
}
159+
124160
// This will be frequently executed, so avoiding using regexps if not necessary.
125161
private String normalizeEndpoint(String endpoint) {
126162
String normalized = endpoint;

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

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,10 +56,8 @@ public class HttpRequestRunnableTest {
5656
private static HttpJsonCallOptions fakeCallOptions;
5757
private static CatMessage catMessage;
5858
private static final String ENDPOINT = "https://www.googleapis.com/animals/v1/projects/";
59-
private static HttpRequestRunnable httpRequestRunnable;
6059
private static HttpRequestFormatter<CatMessage> catFormatter;
6160
private static HttpResponseParser<EmptyMessage> catParser;
62-
private static ApiMethodDescriptor<CatMessage, EmptyMessage> methodDescriptor;
6361
private static PathTemplate nameTemplate = PathTemplate.create("name/{name}");
6462
private static Set<String> queryParams =
6563
Sets.newTreeSet(Lists.newArrayList("food", "size", "gibberish"));
@@ -139,29 +137,29 @@ public String serialize(EmptyMessage response) {
139137
return null;
140138
}
141139
};
140+
}
142141

143-
methodDescriptor =
142+
@Test
143+
public void testRequestUrl() throws IOException {
144+
ApiMethodDescriptor<CatMessage, EmptyMessage> methodDescriptor =
144145
ApiMethodDescriptor.<CatMessage, EmptyMessage>newBuilder()
145146
.setFullMethodName("house.cat.get")
146147
.setHttpMethod(null)
147148
.setRequestFormatter(catFormatter)
148149
.setResponseParser(catParser)
149150
.build();
150151

151-
httpRequestRunnable =
152+
HttpRequestRunnable<CatMessage, EmptyMessage> httpRequestRunnable =
152153
HttpRequestRunnable.<CatMessage, EmptyMessage>newBuilder()
153154
.setHttpJsonCallOptions(fakeCallOptions)
154155
.setEndpoint(ENDPOINT)
155156
.setRequest(catMessage)
156157
.setApiMethodDescriptor(methodDescriptor)
157158
.setHttpTransport(new MockHttpTransport())
158159
.setJsonFactory(new GsonFactory())
159-
.setResponseFuture(SettableApiFuture.<EmptyMessage>create())
160+
.setResponseFuture(SettableApiFuture.create())
160161
.build();
161-
}
162162

163-
@Test
164-
public void testRequestUrl() throws IOException {
165163
HttpRequest httpRequest = httpRequestRunnable.createHttpRequest();
166164
Truth.assertThat(httpRequest.getContent()).isInstanceOf(EmptyContent.class);
167165
String expectedUrl = ENDPOINT + "name/feline" + "?food=bird&food=mouse&size=small";
@@ -170,21 +168,60 @@ public void testRequestUrl() throws IOException {
170168

171169
@Test
172170
public void testRequestUrlUnnormalized() throws IOException {
173-
httpRequestRunnable =
171+
ApiMethodDescriptor<CatMessage, EmptyMessage> methodDescriptor =
172+
ApiMethodDescriptor.<CatMessage, EmptyMessage>newBuilder()
173+
.setFullMethodName("house.cat.get")
174+
.setHttpMethod("PUT")
175+
.setRequestFormatter(catFormatter)
176+
.setResponseParser(catParser)
177+
.build();
178+
179+
HttpRequestRunnable<CatMessage, EmptyMessage> httpRequestRunnable =
180+
HttpRequestRunnable.<CatMessage, EmptyMessage>newBuilder()
181+
.setHttpJsonCallOptions(fakeCallOptions)
182+
.setEndpoint("www.googleapis.com/animals/v1/projects")
183+
.setRequest(catMessage)
184+
.setApiMethodDescriptor(methodDescriptor)
185+
.setHttpTransport(new MockHttpTransport())
186+
.setJsonFactory(new GsonFactory())
187+
.setResponseFuture(SettableApiFuture.create())
188+
.build();
189+
HttpRequest httpRequest = httpRequestRunnable.createHttpRequest();
190+
Truth.assertThat(httpRequest.getContent()).isInstanceOf(EmptyContent.class);
191+
String expectedUrl =
192+
"https://www.googleapis.com/animals/v1/projects/name/feline?food=bird&food=mouse&size=small";
193+
Truth.assertThat(httpRequest.getUrl().toString()).isEqualTo(expectedUrl);
194+
Truth.assertThat(httpRequest.getRequestMethod()).isEqualTo("PUT");
195+
Truth.assertThat(httpRequest.getHeaders().get("X-HTTP-Method-Override")).isNull();
196+
}
197+
198+
@Test
199+
public void testRequestUrlUnnormalizedPatch() throws IOException {
200+
ApiMethodDescriptor<CatMessage, EmptyMessage> methodDescriptor =
201+
ApiMethodDescriptor.<CatMessage, EmptyMessage>newBuilder()
202+
.setFullMethodName("house.cat.get")
203+
.setHttpMethod("PATCH")
204+
.setRequestFormatter(catFormatter)
205+
.setResponseParser(catParser)
206+
.build();
207+
208+
HttpRequestRunnable<CatMessage, EmptyMessage> httpRequestRunnable =
174209
HttpRequestRunnable.<CatMessage, EmptyMessage>newBuilder()
175210
.setHttpJsonCallOptions(fakeCallOptions)
176211
.setEndpoint("www.googleapis.com/animals/v1/projects")
177212
.setRequest(catMessage)
178213
.setApiMethodDescriptor(methodDescriptor)
179214
.setHttpTransport(new MockHttpTransport())
180215
.setJsonFactory(new GsonFactory())
181-
.setResponseFuture(SettableApiFuture.<EmptyMessage>create())
216+
.setResponseFuture(SettableApiFuture.create())
182217
.build();
183218
HttpRequest httpRequest = httpRequestRunnable.createHttpRequest();
184219
Truth.assertThat(httpRequest.getContent()).isInstanceOf(EmptyContent.class);
185220
String expectedUrl =
186221
"https://www.googleapis.com/animals/v1/projects/name/feline?food=bird&food=mouse&size=small";
187222
Truth.assertThat(httpRequest.getUrl().toString()).isEqualTo(expectedUrl);
223+
Truth.assertThat(httpRequest.getRequestMethod()).isEqualTo("POST");
224+
Truth.assertThat(httpRequest.getHeaders().get("X-HTTP-Method-Override")).isEqualTo("PATCH");
188225
}
189226

190227
// TODO(andrealin): test request body

‎gax-httpjson/src/test/java/com/google/api/gax/httpjson/testing/MockHttpService.java

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

32+
import com.google.api.client.http.HttpMethods;
3233
import com.google.api.client.http.LowLevelHttpRequest;
3334
import com.google.api.client.http.LowLevelHttpResponse;
3435
import com.google.api.client.testing.http.MockHttpTransport;
@@ -100,8 +101,14 @@ public MockLowLevelHttpResponse getHttpResponse(String httpMethod, String fullTa
100101
String relativePath = getRelativePath(fullTargetUrl);
101102

102103
for (ApiMethodDescriptor methodDescriptor : serviceMethodDescriptors) {
103-
if (!httpMethod.equals(methodDescriptor.getHttpMethod())) {
104-
continue;
104+
// Check the comment in com.google.api.gax.httpjson.HttpRequestRunnable.buildRequest()
105+
// method for details why it is needed.
106+
String descriptorHttpMethod = methodDescriptor.getHttpMethod();
107+
if (!httpMethod.equals(descriptorHttpMethod)) {
108+
if (!(HttpMethods.PATCH.equals(descriptorHttpMethod)
109+
&& HttpMethods.POST.equals(httpMethod))) {
110+
continue;
111+
}
105112
}
106113

107114
PathTemplate pathTemplate = methodDescriptor.getRequestFormatter().getPathTemplate();

0 commit comments

Comments
 (0)