0

DefaultResponseErrorHandler has some methods, but I would like to point out only a few of them. From source code:

    /**
     * Template method called from {@link #hasError(ClientHttpResponse)}.
     * <p>The default implementation checks {@link HttpStatusCode#isError()}.
     * Can be overridden in subclasses.
     * @param statusCode the HTTP status code
     * @return {@code true} if the response indicates an error; {@code false} otherwise
     * @see HttpStatusCode#isError()
     */
    protected boolean hasError(HttpStatusCode statusCode) {
        return statusCode.isError();
    }

    /**
     * Handle the error based on the resolved status code.
     *
     * <p>The default implementation delegates to
     * {@link HttpClientErrorException#create} for errors in the 4xx range, to
     * {@link HttpServerErrorException#create} for errors in the 5xx range,
     * or otherwise raises {@link UnknownHttpStatusCodeException}.
     * @since 5.0
     * @see HttpClientErrorException#create
     * @see HttpServerErrorException#create
     */
    protected void handleError(ClientHttpResponse response, HttpStatusCode statusCode) throws IOException {
        String statusText = response.getStatusText();
        HttpHeaders headers = response.getHeaders();
        byte[] body = getResponseBody(response);
        Charset charset = getCharset(response);
        String message = getErrorMessage(statusCode.value(), statusText, body, charset);

        RestClientResponseException ex;
        if (statusCode.is4xxClientError()) {
            ex = HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset);
        }
        else if (statusCode.is5xxServerError()) {
            ex = HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset);
        }
        else {
            ex = new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset);
        }

        if (!CollectionUtils.isEmpty(this.messageConverters)) {
            ex.setBodyConvertFunction(initBodyConvertFunction(response, body));
        }

        throw ex;
    }

    /**
     * Read the body of the given response (for inclusion in a status exception).
     * @param response the response to inspect
     * @return the response body as a byte array,
     * or an empty byte array if the body could not be read
     * @since 4.3.8
     */
    protected byte[] getResponseBody(ClientHttpResponse response) {
        try {
            return FileCopyUtils.copyToByteArray(response.getBody());
        }
        catch (IOException ex) {
            // ignore
        }
        return new byte[0];
    }

The reason I created this thread is that I can't understand how the getResponseBody method works...

OK - to the point. I've extended this class to create my implementation of ResponseErrorHandler interface. Here it is:

public class RestTemplateErrorHandler extends DefaultResponseErrorHandler {

    @Override
    protected boolean hasError(ClientHttpResponse response) {
        getResponseBody(response);
        return super.hasError(response);
    }

    @Override
    protected void handleError(ClientHttpResponse response) {
        String statusText = response.getStatusText();
        HttpHeaders headers = response.getHeaders();
        byte[] body = "Dummy message".getBytes(); // <--- ONLY CHANGE COMPARING TO DefaultResponseErrorHandler.handleError
        Charset charset = getCharset(response);
        String message = getErrorMessage(statusCode.value(), statusText, body, charset);

        RestClientResponseException ex;
        if (statusCode.is4xxClientError()) {
            ex = HttpClientErrorException.create(message, statusCode, statusText, headers, body, charset);
        }
        else if (statusCode.is5xxServerError()) {
            ex = HttpServerErrorException.create(message, statusCode, statusText, headers, body, charset);
        }
        else {
            ex = new UnknownHttpStatusCodeException(message, statusCode.value(), statusText, headers, body, charset);
        }

        if (!CollectionUtils.isEmpty(this.messageConverters)) {
            ex.setBodyConvertFunction(initBodyConvertFunction(response, body));
        }

        throw ex;
    }
}

As you noticed, I've only added getResponseBody(response) method call in overriden hasError and changed body in the handleError. When I make a RestTemplate call, it results in StreamClosedException of Apache HttpClient (under-the-hood HTTP client for RestTemplate).

I have no idea why that happens. Why hasError does not have an "access" to the InputStream of response.getBody()? Why is the stream closed there, but works perfectly fine in the next method? Also, in Spring Boot's source code we can also see:

    /**
     * Handle the given response, performing appropriate logging and
     * invoking the {@link ResponseErrorHandler} if necessary.
     * <p>Can be overridden in subclasses.
     * @param url the fully-expanded URL to connect to
     * @param method the HTTP method to execute (GET, POST, etc.)
     * @param response the resulting {@link ClientHttpResponse}
     * @throws IOException if propagated from {@link ResponseErrorHandler}
     * @since 4.1.6
     * @see #setErrorHandler
     */
    protected void handleResponse(URI url, HttpMethod method, ClientHttpResponse response) throws IOException {
        ResponseErrorHandler errorHandler = getErrorHandler();
        boolean hasError = errorHandler.hasError(response);
        if (logger.isDebugEnabled()) {
            try {
                HttpStatusCode statusCode = response.getStatusCode();
                logger.debug("Response " + statusCode);
            }
            catch (IOException ex) {
                logger.debug("Failed to obtain response status code", ex);
            }
        }
        if (hasError) {
            errorHandler.handleError(url, method, response);
        }
    }

HasError is the 1st method to be called, after the request has been made and response retrieved... and the stream is closed. But if the if statement if(hasError) is executed, then handleErrors DO have an access to the getResponseBody: stream is not closed and everything works fine. In both cases, it works on the same ClientHttpResponse object.

What's going on here?

IceMajor
  • 37
  • 5

0 Answers0