ResponseStatusException associated headers
A ResponseStatus exception now exposes extra method to return headers for the response. This is used in ResponseStatusExceptionHandler to apply the headers to the response. Closes gh-23741
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,12 +19,15 @@ package org.springframework.web.server;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Exception for errors that fit response status 405 (method not allowed).
|
||||
@@ -37,7 +40,7 @@ public class MethodNotAllowedException extends ResponseStatusException {
|
||||
|
||||
private final String method;
|
||||
|
||||
private final Set<HttpMethod> supportedMethods;
|
||||
private final Set<HttpMethod> httpMethods;
|
||||
|
||||
|
||||
public MethodNotAllowedException(HttpMethod method, Collection<HttpMethod> supportedMethods) {
|
||||
@@ -51,10 +54,21 @@ public class MethodNotAllowedException extends ResponseStatusException {
|
||||
supportedMethods = Collections.emptySet();
|
||||
}
|
||||
this.method = method;
|
||||
this.supportedMethods = Collections.unmodifiableSet(new HashSet<>(supportedMethods));
|
||||
this.httpMethods = Collections.unmodifiableSet(new HashSet<>(supportedMethods));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a Map with an "Allow" header.
|
||||
* @since 5.1.11
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String> getHeaders() {
|
||||
return !CollectionUtils.isEmpty(this.httpMethods) ?
|
||||
Collections.singletonMap("Allow", StringUtils.collectionToDelimitedString(this.httpMethods, ", ")) :
|
||||
Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the HTTP method for the failed request.
|
||||
*/
|
||||
@@ -66,6 +80,7 @@ public class MethodNotAllowedException extends ResponseStatusException {
|
||||
* Return the list of supported HTTP methods.
|
||||
*/
|
||||
public Set<HttpMethod> getSupportedMethods() {
|
||||
return this.supportedMethods;
|
||||
return this.httpMethods;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -18,9 +18,11 @@ package org.springframework.web.server;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Exception for errors that fit response status 406 (not acceptable).
|
||||
@@ -51,6 +53,17 @@ public class NotAcceptableStatusException extends ResponseStatusException {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return a Map with an "Accept" header, or an empty map.
|
||||
* @since 5.1.11
|
||||
*/
|
||||
@Override
|
||||
public Map<String, String> getHeaders() {
|
||||
return !CollectionUtils.isEmpty(this.supportedMediaTypes) ?
|
||||
Collections.singletonMap("Accept", MediaType.toString(this.supportedMediaTypes)) :
|
||||
Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the list of supported content types in cases when the Accept
|
||||
* header is parsed but not supported, or an empty list otherwise.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.web.server;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.core.NestedExceptionUtils;
|
||||
import org.springframework.core.NestedRuntimeException;
|
||||
import org.springframework.http.HttpStatus;
|
||||
@@ -72,12 +75,21 @@ public class ResponseStatusException extends NestedRuntimeException {
|
||||
|
||||
|
||||
/**
|
||||
* The HTTP status that fits the exception (never {@code null}).
|
||||
* Return the HTTP status associated with this exception.
|
||||
*/
|
||||
public HttpStatus getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return response headers associated with the exception, possibly required
|
||||
* for the given status code (e.g. "Allow", "Accept").
|
||||
* @since 5.1.11
|
||||
*/
|
||||
public Map<String, String> getHeaders() {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* The reason explaining the exception (potentially {@code null} or empty).
|
||||
*/
|
||||
@@ -86,6 +98,7 @@ public class ResponseStatusException extends NestedRuntimeException {
|
||||
return this.reason;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getMessage() {
|
||||
String msg = this.status + (this.reason != null ? " \"" + this.reason + "\"" : "");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -22,6 +22,7 @@ import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.server.reactive.ServerHttpRequest;
|
||||
import org.springframework.http.server.reactive.ServerHttpResponse;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.springframework.web.server.ServerWebExchange;
|
||||
@@ -62,8 +63,7 @@ public class ResponseStatusExceptionHandler implements WebExceptionHandler {
|
||||
|
||||
@Override
|
||||
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
|
||||
HttpStatus status = resolveStatus(ex);
|
||||
if (status == null || !exchange.getResponse().setStatusCode(status)) {
|
||||
if (!updateResponse(exchange.getResponse(), ex)) {
|
||||
return Mono.error(ex);
|
||||
}
|
||||
|
||||
@@ -86,16 +86,25 @@ public class ResponseStatusExceptionHandler implements WebExceptionHandler {
|
||||
return "Resolved [" + reason + "] for HTTP " + request.getMethod() + " " + path;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private HttpStatus resolveStatus(Throwable ex) {
|
||||
private boolean updateResponse(ServerHttpResponse response, Throwable ex) {
|
||||
boolean result = false;
|
||||
HttpStatus status = determineStatus(ex);
|
||||
if (status == null) {
|
||||
Throwable cause = ex.getCause();
|
||||
if (cause != null) {
|
||||
status = resolveStatus(cause);
|
||||
if (status != null) {
|
||||
if (response.setStatusCode(status)) {
|
||||
if (ex instanceof ResponseStatusException) {
|
||||
((ResponseStatusException) ex).getHeaders()
|
||||
.forEach((name, value) -> response.getHeaders().add(name, value));
|
||||
}
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
return status;
|
||||
else {
|
||||
Throwable cause = ex.getCause();
|
||||
if (cause != null) {
|
||||
result = updateResponse(response, cause);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2018 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,18 +17,27 @@
|
||||
package org.springframework.web.server.handler;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import reactor.core.publisher.Mono;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpRequest;
|
||||
import org.springframework.mock.http.server.reactive.test.MockServerHttpResponse;
|
||||
import org.springframework.mock.web.test.server.MockServerWebExchange;
|
||||
import org.springframework.web.server.MethodNotAllowedException;
|
||||
import org.springframework.web.server.NotAcceptableStatusException;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ResponseStatusExceptionHandler}.
|
||||
@@ -67,6 +76,26 @@ public class ResponseStatusExceptionHandlerTests {
|
||||
assertEquals(HttpStatus.BAD_REQUEST, this.exchange.getResponse().getStatusCode());
|
||||
}
|
||||
|
||||
@Test // gh-23741
|
||||
public void handleMethodNotAllowed() {
|
||||
Throwable ex = new MethodNotAllowedException(HttpMethod.PATCH, Arrays.asList(HttpMethod.POST, HttpMethod.PUT));
|
||||
this.handler.handle(this.exchange, ex).block(Duration.ofSeconds(5));
|
||||
|
||||
MockServerHttpResponse response = this.exchange.getResponse();
|
||||
assertEquals(HttpStatus.METHOD_NOT_ALLOWED, response.getStatusCode());
|
||||
assertThat(response.getHeaders().getAllow(), contains(HttpMethod.POST, HttpMethod.PUT));
|
||||
}
|
||||
|
||||
@Test // gh-23741
|
||||
public void handleResponseStatusExceptionWithHeaders() {
|
||||
Throwable ex = new NotAcceptableStatusException(Arrays.asList(MediaType.TEXT_PLAIN, MediaType.TEXT_HTML));
|
||||
this.handler.handle(this.exchange, ex).block(Duration.ofSeconds(5));
|
||||
|
||||
MockServerHttpResponse response = this.exchange.getResponse();
|
||||
assertEquals(HttpStatus.NOT_ACCEPTABLE, response.getStatusCode());
|
||||
assertThat(response.getHeaders().getAccept(), contains(MediaType.TEXT_PLAIN, MediaType.TEXT_HTML));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void unresolvedException() {
|
||||
Throwable expected = new IllegalStateException();
|
||||
|
||||
Reference in New Issue
Block a user