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}