001/* 002 * Copyright 2002-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.client; 018 019import java.io.IOException; 020import java.nio.charset.Charset; 021 022import org.springframework.http.HttpHeaders; 023import org.springframework.http.HttpStatus; 024import org.springframework.http.MediaType; 025import org.springframework.http.client.ClientHttpResponse; 026import org.springframework.util.FileCopyUtils; 027 028/** 029 * Spring's default implementation of the {@link ResponseErrorHandler} interface. 030 * 031 * <p>This error handler checks for the status code on the {@link ClientHttpResponse}: 032 * Any code with series {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR} 033 * or {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR} is considered to be 034 * an error; this behavior can be changed by overriding the {@link #hasError(HttpStatus)} 035 * method. Unknown status codes will be ignored by {@link #hasError(ClientHttpResponse)}. 036 * 037 * @author Arjen Poutsma 038 * @author Rossen Stoyanchev 039 * @author Juergen Hoeller 040 * @since 3.0 041 * @see RestTemplate#setErrorHandler 042 */ 043public class DefaultResponseErrorHandler implements ResponseErrorHandler { 044 045 /** 046 * Delegates to {@link #hasError(HttpStatus)} (for a standard status enum value) or 047 * {@link #hasError(int)} (for an unknown status code) with the response status code. 048 * @see ClientHttpResponse#getRawStatusCode() 049 * @see #hasError(HttpStatus) 050 * @see #hasError(int) 051 */ 052 @Override 053 public boolean hasError(ClientHttpResponse response) throws IOException { 054 int rawStatusCode = response.getRawStatusCode(); 055 for (HttpStatus statusCode : HttpStatus.values()) { 056 if (statusCode.value() == rawStatusCode) { 057 return hasError(statusCode); 058 } 059 } 060 return hasError(rawStatusCode); 061 } 062 063 /** 064 * Template method called from {@link #hasError(ClientHttpResponse)}. 065 * <p>The default implementation checks if the given status code is 066 * {@link HttpStatus.Series#CLIENT_ERROR CLIENT_ERROR} or 067 * {@link HttpStatus.Series#SERVER_ERROR SERVER_ERROR}. 068 * Can be overridden in subclasses. 069 * @param statusCode the HTTP status code as enum value 070 * @return {@code true} if the response indicates an error; {@code false} otherwise 071 * @see HttpStatus#is4xxClientError() 072 * @see HttpStatus#is5xxServerError() 073 */ 074 protected boolean hasError(HttpStatus statusCode) { 075 return (statusCode.is4xxClientError() || statusCode.is5xxServerError()); 076 } 077 078 /** 079 * Template method called from {@link #hasError(ClientHttpResponse)}. 080 * <p>The default implementation checks if the given status code is 081 * {@code HttpStatus.Series#CLIENT_ERROR CLIENT_ERROR} or 082 * {@code HttpStatus.Series#SERVER_ERROR SERVER_ERROR}. 083 * Can be overridden in subclasses. 084 * @param unknownStatusCode the HTTP status code as raw value 085 * @return {@code true} if the response indicates an error; {@code false} otherwise 086 * @since 4.3.21 087 * @see HttpStatus.Series#CLIENT_ERROR 088 * @see HttpStatus.Series#SERVER_ERROR 089 */ 090 protected boolean hasError(int unknownStatusCode) { 091 int seriesCode = unknownStatusCode / 100; 092 return (seriesCode == HttpStatus.Series.CLIENT_ERROR.value() || 093 seriesCode == HttpStatus.Series.SERVER_ERROR.value()); 094 } 095 096 /** 097 * This default implementation throws a {@link HttpClientErrorException} if the response status code 098 * is {@link org.springframework.http.HttpStatus.Series#CLIENT_ERROR}, a {@link HttpServerErrorException} 099 * if it is {@link org.springframework.http.HttpStatus.Series#SERVER_ERROR}, 100 * and a {@link RestClientException} in other cases. 101 */ 102 @Override 103 public void handleError(ClientHttpResponse response) throws IOException { 104 HttpStatus statusCode = getHttpStatusCode(response); 105 switch (statusCode.series()) { 106 case CLIENT_ERROR: 107 throw new HttpClientErrorException(statusCode, response.getStatusText(), 108 response.getHeaders(), getResponseBody(response), getCharset(response)); 109 case SERVER_ERROR: 110 throw new HttpServerErrorException(statusCode, response.getStatusText(), 111 response.getHeaders(), getResponseBody(response), getCharset(response)); 112 default: 113 throw new UnknownHttpStatusCodeException(statusCode.value(), response.getStatusText(), 114 response.getHeaders(), getResponseBody(response), getCharset(response)); 115 } 116 } 117 118 /** 119 * Determine the HTTP status of the given response. 120 * <p>Note: Only called from {@link #handleError}, not from {@link #hasError}. 121 * @param response the response to inspect 122 * @return the associated HTTP status 123 * @throws IOException in case of I/O errors 124 * @throws UnknownHttpStatusCodeException in case of an unknown status code 125 * that cannot be represented with the {@link HttpStatus} enum 126 * @since 4.3.8 127 */ 128 protected HttpStatus getHttpStatusCode(ClientHttpResponse response) throws IOException { 129 try { 130 return response.getStatusCode(); 131 } 132 catch (IllegalArgumentException ex) { 133 throw new UnknownHttpStatusCodeException(response.getRawStatusCode(), response.getStatusText(), 134 response.getHeaders(), getResponseBody(response), getCharset(response)); 135 } 136 } 137 138 /** 139 * Read the body of the given response (for inclusion in a status exception). 140 * @param response the response to inspect 141 * @return the response body as a byte array, 142 * or an empty byte array if the body could not be read 143 * @since 4.3.8 144 */ 145 protected byte[] getResponseBody(ClientHttpResponse response) { 146 try { 147 return FileCopyUtils.copyToByteArray(response.getBody()); 148 } 149 catch (IOException ex) { 150 // ignore 151 } 152 return new byte[0]; 153 } 154 155 /** 156 * Determine the charset of the response (for inclusion in a status exception). 157 * @param response the response to inspect 158 * @return the associated charset, or {@code null} if none 159 * @since 4.3.8 160 */ 161 protected Charset getCharset(ClientHttpResponse response) { 162 HttpHeaders headers = response.getHeaders(); 163 MediaType contentType = headers.getContentType(); 164 return (contentType != null ? contentType.getCharset() : null); 165 } 166 167}