Always try to deserialize message in case of Docker transport errors
Before this commit, if the status code was 4xx or 500, we tried to read the errors object, consuming the http entity. When we tried to deserialize the message, the http entity was already consumed, an IOException has been thrown and null is returned for the message. Now, we read the content in a byte[] and deserialize the errors and the message from that. This ensures that we can read both the errors and the message. Closes gh-44628
This commit is contained in:
parent
c849cafc1b
commit
a807a07a59
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -34,7 +34,6 @@ import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.io.entity.AbstractHttpEntity;
|
||||
import org.apache.hc.core5.http.message.StatusLine;
|
||||
|
||||
import org.springframework.boot.buildpack.platform.io.Content;
|
||||
import org.springframework.boot.buildpack.platform.io.IOConsumer;
|
||||
@ -49,6 +48,7 @@ import org.springframework.util.StringUtils;
|
||||
* @author Phillip Webb
|
||||
* @author Mike Smithson
|
||||
* @author Scott Frederick
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
abstract class HttpClientTransport implements HttpTransport {
|
||||
|
||||
@ -147,12 +147,12 @@ abstract class HttpClientTransport implements HttpTransport {
|
||||
ClassicHttpResponse response = this.client.executeOpen(this.host, request, null);
|
||||
int statusCode = response.getCode();
|
||||
if (statusCode >= 400 && statusCode <= 500) {
|
||||
HttpEntity entity = response.getEntity();
|
||||
Errors errors = (statusCode != 500) ? getErrorsFromResponse(entity) : null;
|
||||
Message message = getMessageFromResponse(entity);
|
||||
StatusLine statusLine = new StatusLine(response);
|
||||
byte[] content = readContent(response);
|
||||
response.close();
|
||||
Errors errors = (statusCode != 500) ? deserializeErrors(content) : null;
|
||||
Message message = deserializeMessage(content);
|
||||
throw new DockerEngineException(this.host.toHostString(), request.getUri(), statusCode,
|
||||
statusLine.getReasonPhrase(), errors, message);
|
||||
response.getReasonPhrase(), errors, message);
|
||||
}
|
||||
return new HttpClientResponse(response);
|
||||
}
|
||||
@ -161,19 +161,38 @@ abstract class HttpClientTransport implements HttpTransport {
|
||||
}
|
||||
}
|
||||
|
||||
private Errors getErrorsFromResponse(HttpEntity entity) {
|
||||
private byte[] readContent(ClassicHttpResponse response) throws IOException {
|
||||
HttpEntity entity = response.getEntity();
|
||||
if (entity == null) {
|
||||
return null;
|
||||
}
|
||||
try (InputStream stream = entity.getContent()) {
|
||||
if (stream == null) {
|
||||
return null;
|
||||
}
|
||||
return stream.readAllBytes();
|
||||
}
|
||||
}
|
||||
|
||||
private Errors deserializeErrors(byte[] content) {
|
||||
if (content == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return SharedObjectMapper.get().readValue(entity.getContent(), Errors.class);
|
||||
return SharedObjectMapper.get().readValue(content, Errors.class);
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Message getMessageFromResponse(HttpEntity entity) {
|
||||
private Message deserializeMessage(byte[] content) {
|
||||
if (content == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return (entity.getContent() != null)
|
||||
? SharedObjectMapper.get().readValue(entity.getContent(), Message.class) : null;
|
||||
Message message = SharedObjectMapper.get().readValue(content, Message.class);
|
||||
return (message.getMessage() != null) ? message : null;
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return null;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2023 the original author or authors.
|
||||
* Copyright 2012-2025 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -57,6 +57,7 @@ import static org.mockito.BDDMockito.then;
|
||||
* @author Phillip Webb
|
||||
* @author Mike Smithson
|
||||
* @author Scott Frederick
|
||||
* @author Moritz Halbritter
|
||||
*/
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class HttpClientTransportTests {
|
||||
@ -309,6 +310,18 @@ class HttpClientTransportTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorsAndMessage() throws IOException {
|
||||
givenClientWillReturnResponse();
|
||||
given(this.entity.getContent()).willReturn(getClass().getResourceAsStream("message-and-errors.json"));
|
||||
given(this.response.getCode()).willReturn(404);
|
||||
assertThatExceptionOfType(DockerEngineException.class).isThrownBy(() -> this.http.get(this.uri))
|
||||
.satisfies((ex) -> {
|
||||
assertThat(ex.getErrors()).hasSize(2);
|
||||
assertThat(ex.getResponseMessage().getMessage()).contains("test message");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeWhenClientThrowsIOExceptionRethrowsAsDockerException() throws IOException {
|
||||
given(this.client.executeOpen(any(HttpHost.class), any(HttpUriRequest.class), isNull()))
|
||||
|
@ -0,0 +1,15 @@
|
||||
{
|
||||
"message": "test message",
|
||||
"errors": [
|
||||
{
|
||||
"code": "TEST1",
|
||||
"message": "Test One",
|
||||
"detail": 123
|
||||
},
|
||||
{
|
||||
"code": "TEST2",
|
||||
"message": "Test Two",
|
||||
"detail": "fail"
|
||||
}
|
||||
]
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user