Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 15e8db1

Browse files
committedApr 6, 2023
Make BasicErrorController respect problem details
Fix GH-34871
1 parent 2a52c47 commit 15e8db1

File tree

3 files changed

+121
-7
lines changed

3 files changed

+121
-7
lines changed
 

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/BasicErrorController.java

+26-5
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package org.springframework.boot.autoconfigure.web.servlet.error;
1818

19+
import java.net.URI;
1920
import java.util.Collections;
2021
import java.util.List;
2122
import java.util.Map;
@@ -24,12 +25,14 @@
2425
import jakarta.servlet.http.HttpServletResponse;
2526

2627
import org.springframework.boot.autoconfigure.web.ErrorProperties;
28+
import org.springframework.boot.autoconfigure.web.servlet.WebMvcProperties;
2729
import org.springframework.boot.web.error.ErrorAttributeOptions;
2830
import org.springframework.boot.web.error.ErrorAttributeOptions.Include;
2931
import org.springframework.boot.web.servlet.error.ErrorAttributes;
3032
import org.springframework.boot.web.servlet.server.AbstractServletWebServerFactory;
3133
import org.springframework.http.HttpStatus;
3234
import org.springframework.http.MediaType;
35+
import org.springframework.http.ProblemDetail;
3336
import org.springframework.http.ResponseEntity;
3437
import org.springframework.stereotype.Controller;
3538
import org.springframework.util.Assert;
@@ -49,6 +52,7 @@
4952
* @author Michael Stummvoll
5053
* @author Stephane Nicoll
5154
* @author Scott Frederick
55+
* @author Yanming Zhou
5256
* @since 1.0.0
5357
* @see ErrorAttributes
5458
* @see ErrorProperties
@@ -59,26 +63,33 @@ public class BasicErrorController extends AbstractErrorController {
5963

6064
private final ErrorProperties errorProperties;
6165

66+
private final WebMvcProperties webMvcProperties;
67+
6268
/**
6369
* Create a new {@link BasicErrorController} instance.
6470
* @param errorAttributes the error attributes
6571
* @param errorProperties configuration properties
72+
* @param webMvcProperties webMvc properties
6673
*/
67-
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
68-
this(errorAttributes, errorProperties, Collections.emptyList());
74+
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties,
75+
WebMvcProperties webMvcProperties) {
76+
this(errorAttributes, errorProperties, webMvcProperties, Collections.emptyList());
6977
}
7078

7179
/**
7280
* Create a new {@link BasicErrorController} instance.
7381
* @param errorAttributes the error attributes
7482
* @param errorProperties configuration properties
83+
* @param webMvcProperties webMvc properties
7584
* @param errorViewResolvers error view resolvers
7685
*/
7786
public BasicErrorController(ErrorAttributes errorAttributes, ErrorProperties errorProperties,
78-
List<ErrorViewResolver> errorViewResolvers) {
87+
WebMvcProperties webMvcProperties, List<ErrorViewResolver> errorViewResolvers) {
7988
super(errorAttributes, errorViewResolvers);
8089
Assert.notNull(errorProperties, "ErrorProperties must not be null");
8190
this.errorProperties = errorProperties;
91+
Assert.notNull(webMvcProperties, "WebMvcProperties must not be null");
92+
this.webMvcProperties = webMvcProperties;
8293
}
8394

8495
@RequestMapping(produces = MediaType.TEXT_HTML_VALUE)
@@ -92,13 +103,23 @@ public ModelAndView errorHtml(HttpServletRequest request, HttpServletResponse re
92103
}
93104

94105
@RequestMapping
95-
public ResponseEntity<Map<String, Object>> error(HttpServletRequest request) {
106+
public ResponseEntity<?> error(HttpServletRequest request) {
96107
HttpStatus status = getStatus(request);
97108
if (status == HttpStatus.NO_CONTENT) {
98109
return new ResponseEntity<>(status);
99110
}
100111
Map<String, Object> body = getErrorAttributes(request, getErrorAttributeOptions(request, MediaType.ALL));
101-
return new ResponseEntity<>(body, status);
112+
if (this.webMvcProperties.getProblemdetails().isEnabled()) {
113+
String detail = (body.get("message") != null) ? body.get("message").toString() : "";
114+
ProblemDetail pd = ProblemDetail.forStatusAndDetail(status, detail);
115+
if (body.get("path") != null) {
116+
pd.setInstance(URI.create(body.get("path").toString()));
117+
}
118+
return ResponseEntity.status(status).body(pd);
119+
}
120+
else {
121+
return new ResponseEntity<>(body, status);
122+
}
102123
}
103124

104125
@ExceptionHandler(HttpMediaTypeNotAcceptableException.class)

‎spring-boot-project/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/ErrorMvcAutoConfiguration.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
* @author Stephane Nicoll
8181
* @author Brian Clozel
8282
* @author Scott Frederick
83+
* @author Yanming Zhou
8384
* @since 1.0.0
8485
*/
8586
// Load before the main WebMvcAutoConfiguration so that the error View is available
@@ -103,9 +104,9 @@ public DefaultErrorAttributes errorAttributes() {
103104

104105
@Bean
105106
@ConditionalOnMissingBean(value = ErrorController.class, search = SearchStrategy.CURRENT)
106-
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes,
107+
public BasicErrorController basicErrorController(ErrorAttributes errorAttributes, WebMvcProperties webMvcProperties,
107108
ObjectProvider<ErrorViewResolver> errorViewResolvers) {
108-
return new BasicErrorController(errorAttributes, this.serverProperties.getError(),
109+
return new BasicErrorController(errorAttributes, this.serverProperties.getError(), webMvcProperties,
109110
errorViewResolvers.orderedStream().toList());
110111
}
111112

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.autoconfigure.web.servlet.error;
18+
19+
import java.io.IOException;
20+
import java.net.URI;
21+
22+
import jakarta.servlet.http.HttpServletResponse;
23+
import org.junit.jupiter.api.Test;
24+
25+
import org.springframework.beans.factory.annotation.Autowired;
26+
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
27+
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
28+
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
29+
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
30+
import org.springframework.boot.autoconfigure.web.servlet.ServletWebServerFactoryAutoConfiguration;
31+
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
32+
import org.springframework.boot.test.context.SpringBootTest;
33+
import org.springframework.boot.test.web.client.TestRestTemplate;
34+
import org.springframework.context.annotation.Configuration;
35+
import org.springframework.http.HttpHeaders;
36+
import org.springframework.http.HttpMethod;
37+
import org.springframework.http.HttpStatus;
38+
import org.springframework.http.MediaType;
39+
import org.springframework.http.ProblemDetail;
40+
import org.springframework.http.RequestEntity;
41+
import org.springframework.http.ResponseEntity;
42+
import org.springframework.web.bind.annotation.RequestMapping;
43+
import org.springframework.web.bind.annotation.RestController;
44+
45+
import static org.assertj.core.api.Assertions.assertThat;
46+
47+
/**
48+
* Tests for {@link BasicErrorController} producing
49+
* {@link org.springframework.http.ProblemDetail}
50+
*
51+
* @author Yanming Zhou
52+
*/
53+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT,
54+
properties = { "server.error.include-message=always", "spring.mvc.problemdetails.enabled=true" })
55+
class BasicErrorControllerProblemDetailTests {
56+
57+
@Autowired
58+
private TestRestTemplate testRestTemplate;
59+
60+
@Test
61+
void sendErrorShouldProduceProblemDetails() {
62+
String path = "/conflict";
63+
ResponseEntity<ProblemDetail> resp = this.testRestTemplate.exchange(
64+
RequestEntity.method(HttpMethod.GET, path).header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE).build(),
65+
ProblemDetail.class);
66+
assertThat(resp.getStatusCode()).isEqualTo(HttpStatus.CONFLICT);
67+
assertThat(resp.getHeaders().getContentType()).isEqualTo(MediaType.APPLICATION_PROBLEM_JSON);
68+
ProblemDetail expected = ProblemDetail.forStatusAndDetail(HttpStatus.CONFLICT, "Conflicts arise");
69+
expected.setTitle(HttpStatus.CONFLICT.getReasonPhrase());
70+
expected.setInstance(URI.create(path));
71+
assertThat(resp.getBody()).isEqualTo(expected);
72+
}
73+
74+
@Configuration(proxyBeanMethods = false)
75+
@ImportAutoConfiguration({ ServletWebServerFactoryAutoConfiguration.class, DispatcherServletAutoConfiguration.class,
76+
WebMvcAutoConfiguration.class, HttpMessageConvertersAutoConfiguration.class,
77+
ErrorMvcAutoConfiguration.class, PropertyPlaceholderAutoConfiguration.class })
78+
static class TestConfiguration {
79+
80+
@RestController
81+
public static class TestController {
82+
83+
@RequestMapping("/conflict")
84+
void home(HttpServletResponse response) throws IOException {
85+
response.sendError(HttpServletResponse.SC_CONFLICT, "Conflicts arise");
86+
}
87+
88+
}
89+
90+
}
91+
92+
}

0 commit comments

Comments
 (0)
Please sign in to comment.