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

Commit 8def947

Browse files
authored
fix: Fix handling of null responses in rest transport (#1668)
Null response should not ever happen, but apparently it is possible in cases when errors occur somewhere on network/authentication level
1 parent 4750384 commit 8def947

File tree

4 files changed

+69
-22
lines changed

4 files changed

+69
-22
lines changed

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,16 @@ public void onMessage(T message) {
130130
@Override
131131
public void onClose(int statusCode, HttpJsonMetadata trailers) {
132132
if (!future.isDone()) {
133-
future.setException(trailers.getException());
133+
if (trailers == null || trailers.getException() == null) {
134+
future.setException(
135+
new HttpJsonStatusRuntimeException(
136+
statusCode,
137+
"Exception during a client call closure",
138+
new NullPointerException(
139+
"Both response message and response exception were null")));
140+
} else {
141+
future.setException(trailers.getException());
142+
}
134143
} else if (statusCode < 200 || statusCode >= 400) {
135144
LOGGER.log(
136145
Level.WARNING, "Received error for unary call after receiving a successful response");

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,9 @@ public void run() {
131131
result.setStatusCode(e.getStatusCode());
132132
result.setResponseHeaders(HttpJsonMetadata.newBuilder().setHeaders(e.getHeaders()).build());
133133
result.setResponseContent(
134-
new ByteArrayInputStream(e.getContent().getBytes(StandardCharsets.UTF_8)));
134+
e.getContent() != null
135+
? new ByteArrayInputStream(e.getContent().getBytes(StandardCharsets.UTF_8))
136+
: null);
135137
trailers.setStatusMessage(e.getStatusMessage());
136138
trailers.setException(e);
137139
} catch (Throwable e) {

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

Lines changed: 49 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -138,14 +138,7 @@ public void testSuccessfulUnaryResponse() throws ExecutionException, Interrupted
138138

139139
Field request;
140140
Field expectedResponse;
141-
request =
142-
expectedResponse =
143-
Field.newBuilder() // "echo" service
144-
.setName("imTheBestField")
145-
.setNumber(2)
146-
.setCardinality(Cardinality.CARDINALITY_OPTIONAL)
147-
.setDefaultValue("blah")
148-
.build();
141+
request = expectedResponse = createTestMessage();
149142

150143
MOCK_SERVICE.addResponse(expectedResponse);
151144

@@ -164,27 +157,65 @@ public void testErrorUnaryResponse() throws InterruptedException {
164157

165158
HttpJsonCallContext callContext = HttpJsonCallContext.createDefault().withChannel(channel);
166159

167-
Field request;
168-
request =
169-
Field.newBuilder() // "echo" service
170-
.setName("imTheBestField")
171-
.setNumber(2)
172-
.setCardinality(Cardinality.CARDINALITY_OPTIONAL)
173-
.setDefaultValue("blah")
174-
.build();
175-
176160
ApiException exception =
177161
ApiExceptionFactory.createException(
178162
new Exception(), FakeStatusCode.of(Code.NOT_FOUND), false);
179163
MOCK_SERVICE.addException(exception);
180164

181165
try {
182-
callable.futureCall(request, callContext).get();
166+
callable.futureCall(createTestMessage(), callContext).get();
183167
Assert.fail("No exception raised");
184168
} catch (ExecutionException e) {
185169
HttpResponseException respExp = (HttpResponseException) e.getCause();
186170
assertThat(respExp.getStatusCode()).isEqualTo(400);
187171
assertThat(respExp.getContent()).isEqualTo(exception.toString());
188172
}
189173
}
174+
175+
@Test
176+
public void testErrorNullContentSuccessfulResponse() throws InterruptedException {
177+
HttpJsonDirectCallable<Field, Field> callable =
178+
new HttpJsonDirectCallable<>(FAKE_METHOD_DESCRIPTOR);
179+
180+
HttpJsonCallContext callContext = HttpJsonCallContext.createDefault().withChannel(channel);
181+
182+
MOCK_SERVICE.addNullResponse();
183+
184+
try {
185+
callable.futureCall(createTestMessage(), callContext).get();
186+
Assert.fail("No exception raised");
187+
} catch (ExecutionException e) {
188+
HttpJsonStatusRuntimeException respExp = (HttpJsonStatusRuntimeException) e.getCause();
189+
assertThat(respExp.getStatusCode()).isEqualTo(200);
190+
assertThat(respExp.getCause().getMessage())
191+
.isEqualTo("Both response message and response exception were null");
192+
}
193+
}
194+
195+
@Test
196+
public void testErrorNullContentFailedResponse() throws InterruptedException {
197+
HttpJsonDirectCallable<Field, Field> callable =
198+
new HttpJsonDirectCallable<>(FAKE_METHOD_DESCRIPTOR);
199+
200+
HttpJsonCallContext callContext = HttpJsonCallContext.createDefault().withChannel(channel);
201+
MOCK_SERVICE.addNullResponse(400);
202+
203+
try {
204+
callable.futureCall(createTestMessage(), callContext).get();
205+
Assert.fail("No exception raised");
206+
} catch (ExecutionException e) {
207+
HttpResponseException respExp = (HttpResponseException) e.getCause();
208+
assertThat(respExp.getStatusCode()).isEqualTo(400);
209+
assertThat(respExp.getContent()).isNull();
210+
}
211+
}
212+
213+
private Field createTestMessage() {
214+
return Field.newBuilder() // "echo" service
215+
.setName("imTheBestField")
216+
.setNumber(2)
217+
.setCardinality(Cardinality.CARDINALITY_OPTIONAL)
218+
.setDefaultValue("blah")
219+
.build();
220+
}
190221
}

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,15 @@ public synchronized void addResponse(Object response) {
8787
responseHandlers.add(new MessageResponseFactory(endpoint, serviceMethodDescriptors, response));
8888
}
8989

90+
/** Add an expected null response (empty HTTP response body) with a custom status code. */
91+
public synchronized void addNullResponse(int statusCode) {
92+
responseHandlers.add(
93+
(httpMethod, targetUrl) -> new MockLowLevelHttpResponse().setStatusCode(statusCode));
94+
}
95+
9096
/** Add an expected null response (empty HTTP response body). */
9197
public synchronized void addNullResponse() {
92-
responseHandlers.add(
93-
(httpMethod, targetUrl) -> new MockLowLevelHttpResponse().setStatusCode(200));
98+
addNullResponse(200);
9499
}
95100

96101
/** Add an Exception to the response queue. */

0 commit comments

Comments
 (0)