10 changed files with 512 additions and 65 deletions
@ -0,0 +1,138 @@
@@ -0,0 +1,138 @@
|
||||
package org.springframework.web.client; |
||||
|
||||
import java.io.ByteArrayInputStream; |
||||
import java.io.IOException; |
||||
import java.io.InputStreamReader; |
||||
import java.io.Reader; |
||||
import java.net.URI; |
||||
import java.nio.CharBuffer; |
||||
import java.nio.charset.Charset; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import org.jetbrains.annotations.NotNull; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* Spring's default implementation of the {@link HttpErrorDetailsExtractor} interface. |
||||
* |
||||
* <p>This extractor will compose a short summary of the http error response, including: |
||||
* <ul> |
||||
* <li>request URI |
||||
* <li>request method |
||||
* <li>a 200-character preview of the response body, unformatted |
||||
* </ul> |
||||
* |
||||
* An example: |
||||
* <pre> |
||||
* 404 Not Found after GET http://example.com:8080/my-endpoint : [{'id': 123, 'message': 'my very long... (500 bytes)]</code>
|
||||
* </pre> |
||||
* |
||||
* @author Jerzy Krolak |
||||
* @since 5.1 |
||||
* @see DefaultResponseErrorHandler#setHttpErrorDetailsExtractor(HttpErrorDetailsExtractor) |
||||
*/ |
||||
public class DefaultHttpErrorDetailsExtractor implements HttpErrorDetailsExtractor { |
||||
|
||||
private static final int MAX_BODY_BYTES_LENGTH = 400; |
||||
|
||||
private static final int MAX_BODY_CHARS_LENGTH = 200; |
||||
|
||||
/** |
||||
* Assemble a short summary of the HTTP error response. |
||||
* @param rawStatusCode HTTP status code |
||||
* @param statusText HTTP status text |
||||
* @param responseBody response body |
||||
* @param responseCharset response charset |
||||
* @param url request URI |
||||
* @param method request method |
||||
* @return error details string. Example: <pre>404 Not Found after GET http://example.com:8080/my-endpoint : [{'id': 123, 'message': 'my very long... (500 bytes)]</code></pre>
|
||||
*/ |
||||
@Override |
||||
@NotNull |
||||
public String getErrorDetails(int rawStatusCode, String statusText, @Nullable byte[] responseBody, |
||||
@Nullable Charset responseCharset, @Nullable URI url, @Nullable HttpMethod method) { |
||||
|
||||
if (url == null || method == null) { |
||||
return getSimpleErrorDetails(rawStatusCode, statusText); |
||||
} |
||||
|
||||
return getCompleteErrorDetails(rawStatusCode, statusText, responseBody, responseCharset, url, method); |
||||
} |
||||
|
||||
@NotNull |
||||
private String getCompleteErrorDetails(int rawStatusCode, String statusText, @Nullable byte[] responseBody, |
||||
@Nullable Charset responseCharset, @Nullable URI url, @Nullable HttpMethod method) { |
||||
|
||||
StringBuilder result = new StringBuilder(); |
||||
|
||||
result.append(getSimpleErrorDetails(rawStatusCode, statusText)) |
||||
.append(" after ") |
||||
.append(method) |
||||
.append(" ") |
||||
.append(url) |
||||
.append(" : "); |
||||
|
||||
if (responseBody == null || responseBody.length == 0) { |
||||
result.append("[no body]"); |
||||
} |
||||
else { |
||||
result |
||||
.append("[") |
||||
.append(getResponseBody(responseBody, responseCharset)) |
||||
.append("]"); |
||||
} |
||||
|
||||
return result.toString(); |
||||
} |
||||
|
||||
@NotNull |
||||
private String getSimpleErrorDetails(int rawStatusCode, String statusText) { |
||||
return rawStatusCode + " " + statusText; |
||||
} |
||||
|
||||
private String getResponseBody(byte[] responseBody, @Nullable Charset responseCharset) { |
||||
Charset charset = getCharsetOrDefault(responseCharset); |
||||
if (responseBody.length < MAX_BODY_BYTES_LENGTH) { |
||||
return getCompleteResponseBody(responseBody, charset); |
||||
} |
||||
return getResponseBodyPreview(responseBody, charset); |
||||
} |
||||
|
||||
@NotNull |
||||
private String getCompleteResponseBody(byte[] responseBody, Charset responseCharset) { |
||||
return new String(responseBody, responseCharset); |
||||
} |
||||
|
||||
private String getResponseBodyPreview(byte[] responseBody, Charset responseCharset) { |
||||
try { |
||||
String bodyPreview = readBodyAsString(responseBody, responseCharset); |
||||
return bodyPreview + "... (" + responseBody.length + " bytes)"; |
||||
} |
||||
catch (IOException e) { |
||||
// should never happen
|
||||
throw new IllegalStateException(e); |
||||
} |
||||
} |
||||
|
||||
@NotNull |
||||
private String readBodyAsString(byte[] responseBody, Charset responseCharset) throws IOException { |
||||
|
||||
Reader reader = new InputStreamReader(new ByteArrayInputStream(responseBody), responseCharset); |
||||
CharBuffer result = CharBuffer.allocate(MAX_BODY_CHARS_LENGTH); |
||||
|
||||
reader.read(result); |
||||
reader.close(); |
||||
result.flip(); |
||||
|
||||
return result.toString(); |
||||
} |
||||
|
||||
private Charset getCharsetOrDefault(@Nullable Charset responseCharset) { |
||||
if (responseCharset == null) { |
||||
return StandardCharsets.ISO_8859_1; |
||||
} |
||||
return responseCharset; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,33 @@
@@ -0,0 +1,33 @@
|
||||
package org.springframework.web.client; |
||||
|
||||
import java.net.URI; |
||||
import java.nio.charset.Charset; |
||||
|
||||
import org.jetbrains.annotations.NotNull; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* Strategy interface used by the {@link DefaultResponseErrorHandler} to compose |
||||
* a summary of the http error. |
||||
* |
||||
* @author Jerzy Krolak |
||||
* @since 5.1 |
||||
*/ |
||||
public interface HttpErrorDetailsExtractor { |
||||
|
||||
/** |
||||
* Assemble HTTP error response details string, based on the provided response details. |
||||
* @param rawStatusCode HTTP status code |
||||
* @param statusText HTTP status text |
||||
* @param responseBody response body |
||||
* @param responseCharset response charset |
||||
* @param url request URI |
||||
* @param method request method |
||||
* @return error details string |
||||
*/ |
||||
@NotNull |
||||
String getErrorDetails(int rawStatusCode, String statusText, @Nullable byte[] responseBody, |
||||
@Nullable Charset responseCharset, @Nullable URI url, @Nullable HttpMethod method); |
||||
|
||||
} |
||||
@ -0,0 +1,97 @@
@@ -0,0 +1,97 @@
|
||||
package org.springframework.web.client; |
||||
|
||||
import java.net.URI; |
||||
|
||||
import com.google.common.base.Strings; |
||||
import org.junit.Test; |
||||
|
||||
import static java.nio.charset.StandardCharsets.*; |
||||
import static org.junit.Assert.*; |
||||
import static org.springframework.http.HttpMethod.*; |
||||
import static org.springframework.http.HttpStatus.*; |
||||
|
||||
public class DefaultHttpErrorDetailsExtractorTests { |
||||
|
||||
private final DefaultHttpErrorDetailsExtractor extractor = new DefaultHttpErrorDetailsExtractor(); |
||||
|
||||
@Test |
||||
public void shouldGetSimpleExceptionMessage() { |
||||
String actual = extractor.getErrorDetails(NOT_FOUND.value(), "Not Found", null, null, null, null); |
||||
|
||||
assertEquals("Should get a simple message", "404 Not Found", actual); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetCompleteMessageWithoutBody() { |
||||
String actual = extractor.getErrorDetails(NOT_FOUND.value(), "Not Found", null, null, URI.create("http://localhost:8080/my-endpoint"), GET); |
||||
|
||||
assertEquals("Should get a complete message without body", "404 Not Found after GET http://localhost:8080/my-endpoint : [no body]", actual); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetCompleteMessageWithShortAsciiBodyNoCharset() { |
||||
String responseBody = "my short response body"; |
||||
|
||||
String actual = extractor.getErrorDetails(NOT_FOUND.value(), "Not Found", responseBody.getBytes(), null, URI.create("http://localhost:8080/my-endpoint"), GET); |
||||
|
||||
assertEquals("Should get a simple message", "404 Not Found after GET http://localhost:8080/my-endpoint : [my short response body]", actual); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetCompleteMessageWithShortAsciiBodyUtfCharset() { |
||||
String responseBody = "my short response body"; |
||||
|
||||
String actual = extractor.getErrorDetails(NOT_FOUND.value(), "Not Found", responseBody.getBytes(), UTF_8, URI.create("http://localhost:8080/my-endpoint"), GET); |
||||
|
||||
assertEquals("Should get a simple message", "404 Not Found after GET http://localhost:8080/my-endpoint : [my short response body]", actual); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetCompleteMessageWithShortUtfBodyUtfCharset() { |
||||
String responseBody = "my short response body \u0105\u0119"; |
||||
|
||||
String actual = extractor.getErrorDetails(NOT_FOUND.value(), "Not Found", responseBody.getBytes(), UTF_8, URI.create("http://localhost:8080/my-endpoint"), GET); |
||||
|
||||
assertEquals("Should get a simple message", "404 Not Found after GET http://localhost:8080/my-endpoint : [my short response body \u0105\u0119]", actual); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetCompleteMessageWithShortUtfBodyNoCharset() { |
||||
String responseBody = "my short response body \u0105\u0119"; |
||||
|
||||
String actual = extractor.getErrorDetails(NOT_FOUND.value(), "Not Found", responseBody.getBytes(UTF_8), null, URI.create("http://localhost:8080/my-endpoint"), GET); |
||||
|
||||
assertEquals("Should get a simple message", "404 Not Found after GET http://localhost:8080/my-endpoint : [my short response body \u00c4\u0085\u00c4\u0099]", actual); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetCompleteMessageWithLongAsciiBodyNoCharset() { |
||||
String responseBody = Strings.repeat("asdfg", 100); |
||||
String expectedMessage = "404 Not Found after GET http://localhost:8080/my-endpoint : [" + Strings.repeat("asdfg", 40) + "... (500 bytes)]"; |
||||
|
||||
String actual = extractor.getErrorDetails(NOT_FOUND.value(), "Not Found", responseBody.getBytes(UTF_8), null, URI.create("http://localhost:8080/my-endpoint"), GET); |
||||
|
||||
assertEquals("Should get a simple message", expectedMessage, actual); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetCompleteMessageWithLongUtfBodyNoCharset() { |
||||
String responseBody = Strings.repeat("asd\u0105\u0119", 100); |
||||
String expectedMessage = "404 Not Found after GET http://localhost:8080/my-endpoint : [" + Strings.repeat("asd\u00c4\u0085\u00c4\u0099", 28) + "asd\u00c4... (700 bytes)]"; |
||||
|
||||
String actual = extractor.getErrorDetails(NOT_FOUND.value(), "Not Found", responseBody.getBytes(UTF_8), null, URI.create("http://localhost:8080/my-endpoint"), GET); |
||||
|
||||
assertEquals("Should get a simple message", expectedMessage, actual); |
||||
} |
||||
|
||||
@Test |
||||
public void shouldGetCompleteMessageWithLongUtfBodyUtfCharset() { |
||||
String responseBody = Strings.repeat("asd\u0105\u0119", 100); |
||||
String expectedMessage = "404 Not Found after GET http://localhost:8080/my-endpoint : [" + Strings.repeat("asd\u0105\u0119", 40) + "... (700 bytes)]"; |
||||
|
||||
String actual = extractor.getErrorDetails(NOT_FOUND.value(), "Not Found", responseBody.getBytes(UTF_8), UTF_8, URI.create("http://localhost:8080/my-endpoint"), GET); |
||||
|
||||
assertEquals("Should get a simple message", expectedMessage, actual); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue