001/* 002 * Copyright 2002-2019 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.reactive.function.client; 018 019import java.nio.charset.Charset; 020import java.nio.charset.StandardCharsets; 021 022import org.springframework.http.HttpHeaders; 023import org.springframework.http.HttpRequest; 024import org.springframework.http.HttpStatus; 025import org.springframework.lang.Nullable; 026 027/** 028 * Exceptions that contain actual HTTP response data. 029 * 030 * @author Arjen Poutsma 031 * @since 5.0 032 */ 033public class WebClientResponseException extends WebClientException { 034 035 private static final long serialVersionUID = 4127543205414951611L; 036 037 038 private final int statusCode; 039 040 private final String statusText; 041 042 private final byte[] responseBody; 043 044 private final HttpHeaders headers; 045 046 private final Charset responseCharset; 047 048 @Nullable 049 private final HttpRequest request; 050 051 052 /** 053 * Constructor with response data only, and a default message. 054 * @since 5.1 055 */ 056 public WebClientResponseException(int statusCode, String statusText, 057 @Nullable HttpHeaders headers, @Nullable byte[] body, @Nullable Charset charset) { 058 059 this(statusCode, statusText, headers, body, charset, null); 060 } 061 062 /** 063 * Constructor with response data only, and a default message. 064 * @since 5.1.4 065 */ 066 public WebClientResponseException(int status, String reasonPhrase, 067 @Nullable HttpHeaders headers, @Nullable byte[] body, @Nullable Charset charset, 068 @Nullable HttpRequest request) { 069 070 this(initMessage(status, reasonPhrase, request), status, reasonPhrase, headers, body, charset, request); 071 } 072 073 private static String initMessage(int status, String reasonPhrase, @Nullable HttpRequest request) { 074 return status + " " + reasonPhrase + 075 (request != null ? " from " + request.getMethodValue() + " " + request.getURI() : ""); 076 } 077 078 /** 079 * Constructor with a prepared message. 080 */ 081 public WebClientResponseException(String message, int statusCode, String statusText, 082 @Nullable HttpHeaders headers, @Nullable byte[] responseBody, @Nullable Charset charset) { 083 this(message, statusCode, statusText, headers, responseBody, charset, null); 084 } 085 086 /** 087 * Constructor with a prepared message. 088 * @since 5.1.4 089 */ 090 public WebClientResponseException(String message, int statusCode, String statusText, 091 @Nullable HttpHeaders headers, @Nullable byte[] responseBody, @Nullable Charset charset, 092 @Nullable HttpRequest request) { 093 094 super(message); 095 096 this.statusCode = statusCode; 097 this.statusText = statusText; 098 this.headers = (headers != null ? headers : HttpHeaders.EMPTY); 099 this.responseBody = (responseBody != null ? responseBody : new byte[0]); 100 this.responseCharset = (charset != null ? charset : StandardCharsets.ISO_8859_1); 101 this.request = request; 102 } 103 104 105 /** 106 * Return the HTTP status code value. 107 * @throws IllegalArgumentException in case of an unknown HTTP status code 108 */ 109 public HttpStatus getStatusCode() { 110 return HttpStatus.valueOf(this.statusCode); 111 } 112 113 /** 114 * Return the raw HTTP status code value. 115 */ 116 public int getRawStatusCode() { 117 return this.statusCode; 118 } 119 120 /** 121 * Return the HTTP status text. 122 */ 123 public String getStatusText() { 124 return this.statusText; 125 } 126 127 /** 128 * Return the HTTP response headers. 129 */ 130 public HttpHeaders getHeaders() { 131 return this.headers; 132 } 133 134 /** 135 * Return the response body as a byte array. 136 */ 137 public byte[] getResponseBodyAsByteArray() { 138 return this.responseBody; 139 } 140 141 /** 142 * Return the response body as a string. 143 */ 144 public String getResponseBodyAsString() { 145 return new String(this.responseBody, this.responseCharset); 146 } 147 148 /** 149 * Return the corresponding request. 150 * @since 5.1.4 151 */ 152 @Nullable 153 public HttpRequest getRequest() { 154 return this.request; 155 } 156 157 /** 158 * Create {@code WebClientResponseException} or an HTTP status specific subclass. 159 * @since 5.1 160 */ 161 public static WebClientResponseException create( 162 int statusCode, String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset) { 163 164 return create(statusCode, statusText, headers, body, charset, null); 165 } 166 167 /** 168 * Create {@code WebClientResponseException} or an HTTP status specific subclass. 169 * @since 5.1.4 170 */ 171 public static WebClientResponseException create( 172 int statusCode, String statusText, HttpHeaders headers, byte[] body, 173 @Nullable Charset charset, @Nullable HttpRequest request) { 174 175 HttpStatus httpStatus = HttpStatus.resolve(statusCode); 176 if (httpStatus != null) { 177 switch (httpStatus) { 178 case BAD_REQUEST: 179 return new WebClientResponseException.BadRequest(statusText, headers, body, charset, request); 180 case UNAUTHORIZED: 181 return new WebClientResponseException.Unauthorized(statusText, headers, body, charset, request); 182 case FORBIDDEN: 183 return new WebClientResponseException.Forbidden(statusText, headers, body, charset, request); 184 case NOT_FOUND: 185 return new WebClientResponseException.NotFound(statusText, headers, body, charset, request); 186 case METHOD_NOT_ALLOWED: 187 return new WebClientResponseException.MethodNotAllowed(statusText, headers, body, charset, request); 188 case NOT_ACCEPTABLE: 189 return new WebClientResponseException.NotAcceptable(statusText, headers, body, charset, request); 190 case CONFLICT: 191 return new WebClientResponseException.Conflict(statusText, headers, body, charset, request); 192 case GONE: 193 return new WebClientResponseException.Gone(statusText, headers, body, charset, request); 194 case UNSUPPORTED_MEDIA_TYPE: 195 return new WebClientResponseException.UnsupportedMediaType(statusText, headers, body, charset, request); 196 case TOO_MANY_REQUESTS: 197 return new WebClientResponseException.TooManyRequests(statusText, headers, body, charset, request); 198 case UNPROCESSABLE_ENTITY: 199 return new WebClientResponseException.UnprocessableEntity(statusText, headers, body, charset, request); 200 case INTERNAL_SERVER_ERROR: 201 return new WebClientResponseException.InternalServerError(statusText, headers, body, charset, request); 202 case NOT_IMPLEMENTED: 203 return new WebClientResponseException.NotImplemented(statusText, headers, body, charset, request); 204 case BAD_GATEWAY: 205 return new WebClientResponseException.BadGateway(statusText, headers, body, charset, request); 206 case SERVICE_UNAVAILABLE: 207 return new WebClientResponseException.ServiceUnavailable(statusText, headers, body, charset, request); 208 case GATEWAY_TIMEOUT: 209 return new WebClientResponseException.GatewayTimeout(statusText, headers, body, charset, request); 210 } 211 } 212 return new WebClientResponseException(statusCode, statusText, headers, body, charset, request); 213 } 214 215 216 217 // Subclasses for specific, client-side, HTTP status codes 218 219 /** 220 * {@link WebClientResponseException} for status HTTP 400 Bad Request. 221 * @since 5.1 222 */ 223 @SuppressWarnings("serial") 224 public static class BadRequest extends WebClientResponseException { 225 226 BadRequest(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset, 227 @Nullable HttpRequest request) { 228 super(HttpStatus.BAD_REQUEST.value(), statusText, headers, body, charset, request); 229 } 230 231 } 232 233 /** 234 * {@link WebClientResponseException} for status HTTP 401 Unauthorized. 235 * @since 5.1 236 */ 237 @SuppressWarnings("serial") 238 public static class Unauthorized extends WebClientResponseException { 239 240 Unauthorized(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset, 241 @Nullable HttpRequest request) { 242 super(HttpStatus.UNAUTHORIZED.value(), statusText, headers, body, charset, request); 243 } 244 } 245 246 /** 247 * {@link WebClientResponseException} for status HTTP 403 Forbidden. 248 * @since 5.1 249 */ 250 @SuppressWarnings("serial") 251 public static class Forbidden extends WebClientResponseException { 252 253 Forbidden(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset, 254 @Nullable HttpRequest request) { 255 super(HttpStatus.FORBIDDEN.value(), statusText, headers, body, charset, request); 256 } 257 } 258 259 /** 260 * {@link WebClientResponseException} for status HTTP 404 Not Found. 261 * @since 5.1 262 */ 263 @SuppressWarnings("serial") 264 public static class NotFound extends WebClientResponseException { 265 266 NotFound(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset, 267 @Nullable HttpRequest request) { 268 super(HttpStatus.NOT_FOUND.value(), statusText, headers, body, charset, request); 269 } 270 } 271 272 /** 273 * {@link WebClientResponseException} for status HTTP 405 Method Not Allowed. 274 * @since 5.1 275 */ 276 @SuppressWarnings("serial") 277 public static class MethodNotAllowed extends WebClientResponseException { 278 279 MethodNotAllowed(String statusText, HttpHeaders headers, byte[] body, 280 @Nullable Charset charset, @Nullable HttpRequest request) { 281 super(HttpStatus.METHOD_NOT_ALLOWED.value(), statusText, headers, body, charset, 282 request); 283 } 284 } 285 286 /** 287 * {@link WebClientResponseException} for status HTTP 406 Not Acceptable. 288 * @since 5.1 289 */ 290 @SuppressWarnings("serial") 291 public static class NotAcceptable extends WebClientResponseException { 292 293 NotAcceptable(String statusText, HttpHeaders headers, byte[] body, 294 @Nullable Charset charset, @Nullable HttpRequest request) { 295 super(HttpStatus.NOT_ACCEPTABLE.value(), statusText, headers, body, charset, request); 296 } 297 } 298 299 /** 300 * {@link WebClientResponseException} for status HTTP 409 Conflict. 301 * @since 5.1 302 */ 303 @SuppressWarnings("serial") 304 public static class Conflict extends WebClientResponseException { 305 306 Conflict(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset, 307 @Nullable HttpRequest request) { 308 super(HttpStatus.CONFLICT.value(), statusText, headers, body, charset, request); 309 } 310 } 311 312 /** 313 * {@link WebClientResponseException} for status HTTP 410 Gone. 314 * @since 5.1 315 */ 316 @SuppressWarnings("serial") 317 public static class Gone extends WebClientResponseException { 318 319 Gone(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset, 320 @Nullable HttpRequest request) { 321 super(HttpStatus.GONE.value(), statusText, headers, body, charset, request); 322 } 323 } 324 325 /** 326 * {@link WebClientResponseException} for status HTTP 415 Unsupported Media Type. 327 * @since 5.1 328 */ 329 @SuppressWarnings("serial") 330 public static class UnsupportedMediaType extends WebClientResponseException { 331 332 UnsupportedMediaType(String statusText, HttpHeaders headers, byte[] body, 333 @Nullable Charset charset, @Nullable HttpRequest request) { 334 335 super(HttpStatus.UNSUPPORTED_MEDIA_TYPE.value(), statusText, headers, body, charset, 336 request); 337 } 338 } 339 340 /** 341 * {@link WebClientResponseException} for status HTTP 422 Unprocessable Entity. 342 * @since 5.1 343 */ 344 @SuppressWarnings("serial") 345 public static class UnprocessableEntity extends WebClientResponseException { 346 347 UnprocessableEntity(String statusText, HttpHeaders headers, byte[] body, 348 @Nullable Charset charset, @Nullable HttpRequest request) { 349 super(HttpStatus.UNPROCESSABLE_ENTITY.value(), statusText, headers, body, charset, 350 request); 351 } 352 } 353 354 /** 355 * {@link WebClientResponseException} for status HTTP 429 Too Many Requests. 356 * @since 5.1 357 */ 358 @SuppressWarnings("serial") 359 public static class TooManyRequests extends WebClientResponseException { 360 361 TooManyRequests(String statusText, HttpHeaders headers, byte[] body, 362 @Nullable Charset charset, @Nullable HttpRequest request) { 363 super(HttpStatus.TOO_MANY_REQUESTS.value(), statusText, headers, body, charset, 364 request); 365 } 366 } 367 368 369 370 // Subclasses for specific, server-side, HTTP status codes 371 372 /** 373 * {@link WebClientResponseException} for status HTTP 500 Internal Server Error. 374 * @since 5.1 375 */ 376 @SuppressWarnings("serial") 377 public static class InternalServerError extends WebClientResponseException { 378 379 InternalServerError(String statusText, HttpHeaders headers, byte[] body, 380 @Nullable Charset charset, @Nullable HttpRequest request) { 381 super(HttpStatus.INTERNAL_SERVER_ERROR.value(), statusText, headers, body, charset, 382 request); 383 } 384 } 385 386 /** 387 * {@link WebClientResponseException} for status HTTP 501 Not Implemented. 388 * @since 5.1 389 */ 390 @SuppressWarnings("serial") 391 public static class NotImplemented extends WebClientResponseException { 392 393 NotImplemented(String statusText, HttpHeaders headers, byte[] body, 394 @Nullable Charset charset, @Nullable HttpRequest request) { 395 super(HttpStatus.NOT_IMPLEMENTED.value(), statusText, headers, body, charset, request); 396 } 397 } 398 399 /** 400 * {@link WebClientResponseException} for status HTTP HTTP 502 Bad Gateway. 401 * @since 5.1 402 */ 403 @SuppressWarnings("serial") 404 public static class BadGateway extends WebClientResponseException { 405 406 BadGateway(String statusText, HttpHeaders headers, byte[] body, @Nullable Charset charset, 407 @Nullable HttpRequest request) { 408 super(HttpStatus.BAD_GATEWAY.value(), statusText, headers, body, charset, request); 409 } 410 } 411 412 /** 413 * {@link WebClientResponseException} for status HTTP 503 Service Unavailable. 414 * @since 5.1 415 */ 416 @SuppressWarnings("serial") 417 public static class ServiceUnavailable extends WebClientResponseException { 418 419 ServiceUnavailable(String statusText, HttpHeaders headers, byte[] body, 420 @Nullable Charset charset, @Nullable HttpRequest request) { 421 super(HttpStatus.SERVICE_UNAVAILABLE.value(), statusText, headers, body, charset, 422 request); 423 } 424 } 425 426 /** 427 * {@link WebClientResponseException} for status HTTP 504 Gateway Timeout. 428 * @since 5.1 429 */ 430 @SuppressWarnings("serial") 431 public static class GatewayTimeout extends WebClientResponseException { 432 433 GatewayTimeout(String statusText, HttpHeaders headers, byte[] body, 434 @Nullable Charset charset, @Nullable HttpRequest request) { 435 super(HttpStatus.GATEWAY_TIMEOUT.value(), statusText, headers, body, charset, 436 request); 437 } 438 } 439 440}