1.错误页注册:
404,即page not found ,这类业务错误处理,spring demo是通过error mapping 处理。有两种处理方式,其一是用过注册error controller。 其二是通过 ErrorPageRegistrar 注册ErrorPage
@Bean
public org.springframework.boot.web.server.ErrorPageRegistrar errorPageRegistrar() {
return registry -> {
registry.addErrorPages(new ErrorPage[] {new ErrorPage(HttpStatus.NOT_FOUND,"/common/404.html")});
};
}
// to 2019-05-14
spring boot 在spring boot autoconfigure 项目中,对错误页进行了默认配置。
仅webflux 项目而言:在webflux 中使用org.springframework.web.server.handler.WebHandlerDecorator装饰了WebHandler一个ExceptionHandlingWebHandler。在其中便使用了org.springframework.boot.autoconfigure.web.reactive.error.DefaultErrorWebExceptionHandler对异常通过HttpStatus进行异常页匹配。
/**
* Basic global {@link org.springframework.web.server.WebExceptionHandler}, rendering
* {@link ErrorAttributes}.
* <p>
* More specific errors can be handled either using Spring WebFlux abstractions (e.g.
* {@code @ExceptionHandler} with the annotation model) or by adding
* {@link RouterFunction} to the chain.
* <p>
* This implementation will render error as HTML views if the client explicitly supports
* that media type. It attempts to resolve error views using well known conventions. Will
* search for templates and static assets under {@code '/error'} using the
* {@link HttpStatus status code} and the {@link HttpStatus#series() status series}.
* <p>
* For example, an {@code HTTP 404} will search (in the specific order):
* <ul>
* <li>{@code '/<templates>/error/404.<ext>'}</li>
* <li>{@code '/<static>/error/404.html'}</li>
* <li>{@code '/<templates>/error/4xx.<ext>'}</li>
* <li>{@code '/<static>/error/4xx.html'}</li>
* <li>{@code '/<templates>/error/error'}</li>
* <li>{@code '/<static>/error/error.html'}</li>
* </ul>
* <p>
* If none found, a default "Whitelabel Error" HTML view will be rendered.
* <p>
* If the client doesn't support HTML, the error information will be rendered as a JSON
* payload.
*
* @author Brian Clozel
* @since 2.0.0
*/
public class DefaultErrorWebExceptionHandler extends AbstractErrorWebExceptionHandler {
//...
/**
* Render the error information as an HTML view.
* @param request the current request
* @return a {@code Publisher} of the HTTP response
*/
protected Mono<ServerResponse> renderErrorView(ServerRequest request) {
boolean includeStackTrace = isIncludeStackTrace(request, MediaType.TEXT_HTML);
Map<String, Object> error = getErrorAttributes(request, includeStackTrace);
HttpStatus errorStatus = getHttpStatus(error);
ServerResponse.BodyBuilder responseBody = ServerResponse.status(errorStatus)
.contentType(MediaType.TEXT_HTML);
return Flux
.just("error/" + errorStatus.value(),
"error/" + SERIES_VIEWS.get(errorStatus.series()), "error/error")
.flatMap((viewName) -> renderErrorView(viewName, responseBody, error))
.switchIfEmpty(this.errorProperties.getWhitelabel().isEnabled()
? renderDefaultErrorView(responseBody, error)
: Mono.error(getError(request)))
.next();
}
//...
}
2.POST 访问spring 静态资源 响应405 Method Not Allowed org.springframework.web.servlet.config.annotation.WebMvcConfigurer.addResourceHandlers(ResourceHandlerRegistry registry) 中注册的静态资源默认只支持GET、HEAD.这样做的主要原因是方便spring 响应静态资源时添加Cache-Control。
但在项目中,因为使用request.getRequestDispatcher(uri).forward(request, response) 重定向POST请求时。导致发生异常:
DefaultHandlerExceptionResolver : Resolved [org.springframework.web.HttpRequestMethodNotSupportedException: Request method 'POST' not supported]
定位异常原因:在ResourceHttpRequestHandler 检查this.supportedMethods 不包含 request methods。
stack:
关键代码
// O org.springframework.web.servlet.support.WebContentGenerator 376
/**
* Check the given request for supported methods and a required session, if any.
* @param request current HTTP request
* @throws ServletException if the request cannot be handled because a check failed
* @since 4.2
*/
protected final void checkRequest(HttpServletRequest request) throws ServletException {
// Check whether we should support the request method.
String method = request.getMethod();
if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
throw new HttpRequestMethodNotSupportedException(method, this.supportedMethods);
}
// Check whether a session is required.
if (this.requireSession && request.getSession(false) == null) {
throw new HttpSessionRequiredException("Pre-existing session required but none found");
}
}
this.supportedMethods 在ResourceHttpRequestHandler 赋值为GET和HEAD,因此使用POST方式访问spring mvc 静态资源会抛出异常。
// TO org.springframework.web.servlet.resource.ResourceHttpRequestHandler extends WebContentGenerator
public ResourceHttpRequestHandler() {
super(HttpMethod.GET.name(), HttpMethod.HEAD.name());
}
那么解决方式便只有将this.supportedMethods进行扩增POST等。
在目前编写的项目中,请求资源在业务处理失败时,并不是直接在当前servlet中进行反馈,而是进行一次重定向到异常处理的servlet。暂不谈论该机构是否值得,只能考虑对ResourceHttpRequestHandler对象进行改进。
我们都知道forward(request, response) 重定向的request和response共享的原理,因此请求Method不能进行修改为GET(不考虑包装|代理|反射修改request方式)。
ResourceHttpRequestHandler的注册通常在WebMvcConfigurer.addResourceHandlers(ResourceHandlerRegistry registry)中。实际是通过 HandlerMapping org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport.resourceHandlerMapping()进行注册为Bean。然后通过DispatcherServlet.initHandlerMappings(ApplicationContext context) 进行使用。
因此只需要将resourceHandlerMapping中注册的ResourceHttpRequestHandler修改this.supportedMethods即可完成通过POST方式访问静态资源。
即:
@Component
public final class ErcApplicationRunner implements ApplicationRunner{
@Resource(name = "resourceHandlerMapping")
HandlerMapping resourceHandlerMapping;
@Override
public void run(ApplicationArguments args) throws Exception {
if (resourceHandlerMapping instanceof org.springframework.web.servlet.handler.SimpleUrlHandlerMapping) {
org.springframework.web.servlet.handler.SimpleUrlHandlerMapping url = (SimpleUrlHandlerMapping) resourceHandlerMapping;
url.getUrlMap().values().forEach(v -> {
if (v instanceof ResourceHttpRequestHandler) {
ResourceHttpRequestHandler handler = (ResourceHttpRequestHandler) v;
handler.setSupportedMethods(ArrayUtils.add(handler.getSupportedMethods(), WebContentGenerator.METHOD_POST));
}
});
}
}
}
附:
- 静态资源配置图:
- 原:
- 新: