mirror of
https://github.com/spring-projects/spring-boot.git
synced 2026-10-06 01:09:02 +00:00
Merge pull request #47180 from siva-sai-udaygiri
* pr/47180: Polish 'Handle HTTP 407 with clear error message' Handle HTTP 407 with clear error message Closes gh-47180
This commit is contained in:
+28
-2
@@ -17,8 +17,12 @@
|
||||
package org.springframework.boot.buildpack.platform.docker.transport;
|
||||
|
||||
import java.net.URI;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
@@ -26,6 +30,7 @@ import org.springframework.util.StringUtils;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Scott Frederick
|
||||
* @author Siva Sai Udayagiri
|
||||
* @since 2.3.0
|
||||
*/
|
||||
public class DockerEngineException extends RuntimeException {
|
||||
@@ -38,9 +43,26 @@ public class DockerEngineException extends RuntimeException {
|
||||
|
||||
private final Message responseMessage;
|
||||
|
||||
/**
|
||||
* Create a new {@link DockerEngineException}.
|
||||
* @param host the host
|
||||
* @param uri the URI being called
|
||||
* @param statusCode the status code
|
||||
* @param reasonPhrase the reason phrase
|
||||
* @param errors the errors or {@code null}
|
||||
* @param responseMessage the response message
|
||||
* @deprecated since 3.4.12 for removal in 4.0.0 since the exception should only be
|
||||
* thrown by the transport.
|
||||
*/
|
||||
@Deprecated(since = "3.4.12", forRemoval = true)
|
||||
public DockerEngineException(String host, URI uri, int statusCode, String reasonPhrase, Errors errors,
|
||||
Message responseMessage) {
|
||||
super(buildMessage(host, uri, statusCode, reasonPhrase, errors, responseMessage));
|
||||
this(host, uri, statusCode, reasonPhrase, errors, responseMessage, null);
|
||||
}
|
||||
|
||||
DockerEngineException(String host, URI uri, int statusCode, String reasonPhrase, Errors errors,
|
||||
Message responseMessage, byte[] content) {
|
||||
super(buildMessage(host, uri, statusCode, reasonPhrase, errors, responseMessage, content));
|
||||
this.statusCode = statusCode;
|
||||
this.reasonPhrase = reasonPhrase;
|
||||
this.errors = errors;
|
||||
@@ -82,7 +104,7 @@ public class DockerEngineException extends RuntimeException {
|
||||
}
|
||||
|
||||
private static String buildMessage(String host, URI uri, int statusCode, String reasonPhrase, Errors errors,
|
||||
Message responseMessage) {
|
||||
Message responseMessage, byte[] content) {
|
||||
Assert.notNull(host, "Host must not be null");
|
||||
Assert.notNull(uri, "URI must not be null");
|
||||
StringBuilder message = new StringBuilder(
|
||||
@@ -93,6 +115,10 @@ public class DockerEngineException extends RuntimeException {
|
||||
if (responseMessage != null && StringUtils.hasLength(responseMessage.getMessage())) {
|
||||
message.append(" and message \"").append(responseMessage.getMessage()).append("\"");
|
||||
}
|
||||
else if (statusCode == HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED && !ObjectUtils.isEmpty(content)) {
|
||||
String contentString = new String(content, StandardCharsets.UTF_8);
|
||||
message.append(" and content \"").append(contentString.trim()).append("\"");
|
||||
}
|
||||
if (errors != null && !errors.isEmpty()) {
|
||||
message.append(" ").append(errors);
|
||||
}
|
||||
|
||||
+1
-1
@@ -166,7 +166,7 @@ abstract class HttpClientTransport implements HttpTransport {
|
||||
Errors errors = (statusCode != 500) ? deserializeErrors(content) : null;
|
||||
Message message = deserializeMessage(content);
|
||||
throw new DockerEngineException(this.host.toHostString(), request.getUri(), statusCode,
|
||||
response.getReasonPhrase(), errors, message);
|
||||
response.getReasonPhrase(), errors, message, content);
|
||||
}
|
||||
return new HttpClientResponse(response);
|
||||
}
|
||||
|
||||
+5
-5
@@ -31,7 +31,7 @@ import org.springframework.boot.buildpack.platform.docker.DockerApi.ImageApi;
|
||||
import org.springframework.boot.buildpack.platform.docker.DockerApi.VolumeApi;
|
||||
import org.springframework.boot.buildpack.platform.docker.TotalProgressPullListener;
|
||||
import org.springframework.boot.buildpack.platform.docker.configuration.DockerConfiguration;
|
||||
import org.springframework.boot.buildpack.platform.docker.transport.DockerEngineException;
|
||||
import org.springframework.boot.buildpack.platform.docker.transport.TestDockerEngineException;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.Binding;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ContainerReference;
|
||||
import org.springframework.boot.buildpack.platform.docker.type.ContainerStatus;
|
||||
@@ -313,12 +313,12 @@ class BuilderTests {
|
||||
any(), isNull()))
|
||||
.willAnswer(withPulledImage(runImage));
|
||||
given(docker.image().inspect(eq(ImageReference.of(BuildRequest.DEFAULT_BUILDER_IMAGE_REF))))
|
||||
.willThrow(
|
||||
new DockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null, null))
|
||||
.willThrow(new TestDockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null,
|
||||
null, null))
|
||||
.willReturn(builderImage);
|
||||
given(docker.image().inspect(eq(ImageReference.of("docker.io/cloudfoundry/run:base-cnb"))))
|
||||
.willThrow(
|
||||
new DockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null, null))
|
||||
.willThrow(new TestDockerEngineException("docker://localhost/", new URI("example"), 404, "NOT FOUND", null,
|
||||
null, null))
|
||||
.willReturn(runImage);
|
||||
Builder builder = new Builder(BuildLog.to(out), docker, null);
|
||||
BuildRequest request = getTestRequest().withPullPolicy(PullPolicy.IF_NOT_PRESENT);
|
||||
|
||||
+20
-8
@@ -18,6 +18,7 @@ package org.springframework.boot.buildpack.platform.docker.transport;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -56,20 +57,20 @@ class DockerEngineExceptionTests {
|
||||
@Test
|
||||
void createWhenHostIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new DockerEngineException(null, null, 404, null, NO_ERRORS, NO_MESSAGE))
|
||||
.isThrownBy(() -> new DockerEngineException(null, null, 404, null, NO_ERRORS, NO_MESSAGE, null))
|
||||
.withMessage("Host must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenUriIsNullThrowsException() {
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> new DockerEngineException(HOST, null, 404, null, NO_ERRORS, NO_MESSAGE))
|
||||
.isThrownBy(() -> new DockerEngineException(HOST, null, 404, null, NO_ERRORS, NO_MESSAGE, null))
|
||||
.withMessage("URI must not be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
void create() {
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", ERRORS, MESSAGE);
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", ERRORS, MESSAGE, null);
|
||||
assertThat(exception.getMessage()).isEqualTo(
|
||||
"Docker API call to 'docker://localhost/example' failed with status code 404 \"missing\" and message \"response message\" [code: message]");
|
||||
assertThat(exception.getStatusCode()).isEqualTo(404);
|
||||
@@ -80,7 +81,7 @@ class DockerEngineExceptionTests {
|
||||
|
||||
@Test
|
||||
void createWhenReasonPhraseIsNull() {
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, null, ERRORS, MESSAGE);
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, null, ERRORS, MESSAGE, null);
|
||||
assertThat(exception.getMessage()).isEqualTo(
|
||||
"Docker API call to 'docker://localhost/example' failed with status code 404 and message \"response message\" [code: message]");
|
||||
assertThat(exception.getStatusCode()).isEqualTo(404);
|
||||
@@ -91,7 +92,7 @@ class DockerEngineExceptionTests {
|
||||
|
||||
@Test
|
||||
void createWhenErrorsIsNull() {
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", null, MESSAGE);
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", null, MESSAGE, null);
|
||||
assertThat(exception.getMessage()).isEqualTo(
|
||||
"Docker API call to 'docker://localhost/example' failed with status code 404 \"missing\" and message \"response message\"");
|
||||
assertThat(exception.getErrors()).isNull();
|
||||
@@ -99,7 +100,8 @@ class DockerEngineExceptionTests {
|
||||
|
||||
@Test
|
||||
void createWhenErrorsIsEmpty() {
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", NO_ERRORS, MESSAGE);
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", NO_ERRORS, MESSAGE,
|
||||
null);
|
||||
assertThat(exception.getMessage()).isEqualTo(
|
||||
"Docker API call to 'docker://localhost/example' failed with status code 404 \"missing\" and message \"response message\"");
|
||||
assertThat(exception.getStatusCode()).isEqualTo(404);
|
||||
@@ -109,7 +111,7 @@ class DockerEngineExceptionTests {
|
||||
|
||||
@Test
|
||||
void createWhenMessageIsNull() {
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", ERRORS, null);
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", ERRORS, null, null);
|
||||
assertThat(exception.getMessage()).isEqualTo(
|
||||
"Docker API call to 'docker://localhost/example' failed with status code 404 \"missing\" [code: message]");
|
||||
assertThat(exception.getResponseMessage()).isNull();
|
||||
@@ -117,10 +119,20 @@ class DockerEngineExceptionTests {
|
||||
|
||||
@Test
|
||||
void createWhenMessageIsEmpty() {
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", ERRORS, NO_MESSAGE);
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 404, "missing", ERRORS, NO_MESSAGE,
|
||||
null);
|
||||
assertThat(exception.getMessage()).isEqualTo(
|
||||
"Docker API call to 'docker://localhost/example' failed with status code 404 \"missing\" [code: message]");
|
||||
assertThat(exception.getResponseMessage()).isSameAs(NO_MESSAGE);
|
||||
}
|
||||
|
||||
@Test
|
||||
void createWhenProxyAuthFailureWithTextContent() {
|
||||
DockerEngineException exception = new DockerEngineException(HOST, URI, 407, "Proxy Authentication Required",
|
||||
null, null, "Badness".getBytes(StandardCharsets.UTF_8));
|
||||
assertThat(exception.getMessage())
|
||||
.isEqualTo("Docker API call to 'docker://localhost/example' failed with status code 407 "
|
||||
+ "\"Proxy Authentication Required\" and content \"Badness\"");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+14
@@ -33,6 +33,7 @@ import org.apache.hc.core5.http.ClassicHttpResponse;
|
||||
import org.apache.hc.core5.http.HttpEntity;
|
||||
import org.apache.hc.core5.http.HttpHeaders;
|
||||
import org.apache.hc.core5.http.HttpHost;
|
||||
import org.apache.hc.core5.http.HttpStatus;
|
||||
import org.assertj.core.api.ThrowingConsumer;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -322,6 +323,19 @@ class HttpClientTransportTests {
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnErrorsAndConentIfProxyAuthError() throws IOException {
|
||||
givenClientWillReturnResponse();
|
||||
given(this.entity.getContent()).willReturn(getClass().getResourceAsStream("proxy-error.txt"));
|
||||
given(this.response.getCode()).willReturn(HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED);
|
||||
assertThatExceptionOfType(DockerEngineException.class).isThrownBy(() -> this.http.get(this.uri))
|
||||
.satisfies((ex) -> {
|
||||
assertThat(ex.getErrors()).isNull();
|
||||
assertThat(ex.getResponseMessage()).isNull();
|
||||
assertThat(ex.getMessage()).contains("Some kind of procy auth problem!");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void executeWhenClientThrowsIOExceptionRethrowsAsDockerException() throws IOException {
|
||||
given(this.client.executeOpen(any(HttpHost.class), any(HttpUriRequest.class), isNull()))
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2012-present 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.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.boot.buildpack.platform.docker.transport;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
/**
|
||||
* Subclass of {@link DockerEngineException} for testing.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class TestDockerEngineException extends DockerEngineException {
|
||||
|
||||
public TestDockerEngineException(String host, URI uri, int statusCode, String reasonPhrase, Errors errors,
|
||||
Message responseMessage, byte[] content) {
|
||||
super(host, uri, statusCode, reasonPhrase, errors, responseMessage, content);
|
||||
}
|
||||
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
Some kind of procy auth problem!
|
||||
|
||||
Reference in New Issue
Block a user