Refine SyncInvocableHandlerMethod error handling

Ensure the error is wrapped as ServerErrorException
This commit is contained in:
Rossen Stoyanchev 2018-03-31 12:03:03 -04:00
parent 912c270f2b
commit d9e17a62ce
4 changed files with 60 additions and 16 deletions

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -19,11 +19,12 @@ package org.springframework.web.server;
import org.springframework.core.MethodParameter; import org.springframework.core.MethodParameter;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod;
/** /**
* Exception for errors that fit response status 500 (bad request) for use in * Exception for an {@link HttpStatus#INTERNAL_SERVER_ERROR} that exposes extra
* Spring Web applications. The exception provides additional fields (e.g. * information about a controller method that failed, or a controller method
* an optional {@link MethodParameter} if related to the error). * argument that could not be resolved.
* *
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @since 5.0 * @since 5.0
@ -31,6 +32,9 @@ import org.springframework.lang.Nullable;
@SuppressWarnings("serial") @SuppressWarnings("serial")
public class ServerErrorException extends ResponseStatusException { public class ServerErrorException extends ResponseStatusException {
@Nullable
private final HandlerMethod handlerMethod;
@Nullable @Nullable
private final MethodParameter parameter; private final MethodParameter parameter;
@ -39,27 +43,60 @@ public class ServerErrorException extends ResponseStatusException {
* Constructor with an explanation only. * Constructor with an explanation only.
*/ */
public ServerErrorException(String reason) { public ServerErrorException(String reason) {
this(reason, null, null); super(HttpStatus.INTERNAL_SERVER_ERROR, reason, null);
this.handlerMethod = null;
this.parameter = null;
} }
/** /**
* Constructor for a 500 error linked to a specific {@code MethodParameter}. * Constructor with a reason and root cause.
* @since 5.0.5
*/ */
public ServerErrorException(String reason, MethodParameter parameter) { public ServerErrorException(String reason, Throwable cause) {
this(reason, parameter, null); super(HttpStatus.INTERNAL_SERVER_ERROR, reason, cause);
this.handlerMethod = null;
this.parameter = null;
}
/**
* Constructor for a 500 error with a {@link MethodParameter}.
*/
public ServerErrorException(String reason, MethodParameter parameter, @Nullable Throwable cause) {
super(HttpStatus.INTERNAL_SERVER_ERROR, reason, cause);
this.handlerMethod = null;
this.parameter = parameter;
} }
/** /**
* Constructor for a 500 error with a root cause. * Constructor for a 500 error with a root cause.
*/ */
public ServerErrorException(String reason, @Nullable MethodParameter parameter, @Nullable Throwable cause) { public ServerErrorException(String reason, HandlerMethod handlerMethod, @Nullable Throwable cause) {
super(HttpStatus.INTERNAL_SERVER_ERROR, reason, cause); super(HttpStatus.INTERNAL_SERVER_ERROR, reason, cause);
this.parameter = parameter; this.handlerMethod = handlerMethod;
this.parameter = null;
}
/**
* Constructor for a 500 error linked to a specific {@code MethodParameter}.
* @deprecated in favor of {@link #ServerErrorException(String, MethodParameter, Throwable)}
*/
@Deprecated
public ServerErrorException(String reason, MethodParameter parameter) {
this(reason, parameter, null);
} }
/** /**
* Return the {@code MethodParameter} associated with this error, if any. * Return the controller method associated with the error, if any.
* @since 5.0.5
*/
@Nullable
public HandlerMethod getHandlerMethod() {
return this.handlerMethod;
}
/**
* Return the controller method argument associated with this error, if any.
*/ */
@Nullable @Nullable
public MethodParameter getMethodParameter() { public MethodParameter getMethodParameter() {

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -21,6 +21,7 @@ import org.springframework.ui.Model;
import org.springframework.validation.support.BindingAwareConcurrentModel; import org.springframework.validation.support.BindingAwareConcurrentModel;
import org.springframework.web.bind.support.WebBindingInitializer; import org.springframework.web.bind.support.WebBindingInitializer;
import org.springframework.web.bind.support.WebExchangeDataBinder; import org.springframework.web.bind.support.WebExchangeDataBinder;
import org.springframework.web.server.ServerErrorException;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
/** /**
@ -75,6 +76,7 @@ public class BindingContext {
* @param target the object to create a data binder for * @param target the object to create a data binder for
* @param name the name of the target object * @param name the name of the target object
* @return the created data binder * @return the created data binder
* @throws ServerErrorException if {@code @InitBinder} method invocation fails
*/ */
public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, @Nullable Object target, String name) { public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, @Nullable Object target, String name) {
WebExchangeDataBinder dataBinder = new WebExchangeDataBinder(target, name); WebExchangeDataBinder dataBinder = new WebExchangeDataBinder(target, name);
@ -86,6 +88,7 @@ public class BindingContext {
/** /**
* Initialize the data binder instance for the given exchange. * Initialize the data binder instance for the given exchange.
* @throws ServerErrorException if {@code @InitBinder} method invocation fails
*/ */
protected WebExchangeDataBinder initDataBinder(WebExchangeDataBinder binder, ServerWebExchange exchange) { protected WebExchangeDataBinder initDataBinder(WebExchangeDataBinder binder, ServerWebExchange exchange) {
return binder; return binder;
@ -97,6 +100,7 @@ public class BindingContext {
* @param exchange the current exchange * @param exchange the current exchange
* @param name the name of the target object * @param name the name of the target object
* @return the created data binder * @return the created data binder
* @throws ServerErrorException if {@code @InitBinder} method invocation fails
*/ */
public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, String name) { public WebExchangeDataBinder createDataBinder(ServerWebExchange exchange, String name) {
return createDataBinder(exchange, null, name); return createDataBinder(exchange, null, name);

View File

@ -29,6 +29,7 @@ import org.springframework.lang.Nullable;
import org.springframework.web.method.HandlerMethod; import org.springframework.web.method.HandlerMethod;
import org.springframework.web.reactive.BindingContext; import org.springframework.web.reactive.BindingContext;
import org.springframework.web.reactive.HandlerResult; import org.springframework.web.reactive.HandlerResult;
import org.springframework.web.server.ServerErrorException;
import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.ServerWebExchange;
/** /**
@ -95,6 +96,7 @@ public class SyncInvocableHandlerMethod extends HandlerMethod {
* @param bindingContext the binding context to use * @param bindingContext the binding context to use
* @param providedArgs optional list of argument values to match by type * @param providedArgs optional list of argument values to match by type
* @return Mono with a {@link HandlerResult}. * @return Mono with a {@link HandlerResult}.
* @throws ServerErrorException if method argument resolution or method invocation fails
*/ */
@Nullable @Nullable
public HandlerResult invokeForHandlerResult(ServerWebExchange exchange, public HandlerResult invokeForHandlerResult(ServerWebExchange exchange,
@ -104,9 +106,10 @@ public class SyncInvocableHandlerMethod extends HandlerMethod {
this.delegate.invoke(exchange, bindingContext, providedArgs).subscribeWith(processor); this.delegate.invoke(exchange, bindingContext, providedArgs).subscribeWith(processor);
if (processor.isTerminated()) { if (processor.isTerminated()) {
Throwable error = processor.getError(); Throwable ex = processor.getError();
if (error != null) { if (ex != null) {
throw (RuntimeException) error; throw (ex instanceof ServerErrorException ? (ServerErrorException) ex :
new ServerErrorException("Failed to invoke: " + getShortLogMessage(), this, ex));
} }
return processor.peek(); return processor.peek();
} }

View File

@ -92,7 +92,7 @@ public class PathVariableMethodArgumentResolver extends AbstractNamedValueSyncAr
@Override @Override
protected void handleMissingValue(String name, MethodParameter parameter) { protected void handleMissingValue(String name, MethodParameter parameter) {
throw new ServerErrorException(name, parameter); throw new ServerErrorException(name, parameter, null);
} }
@Override @Override