Spring REST错误处理:无法获得我的自定义消息

问题描述 投票:2回答:4

我阅读了几篇有关服务器端错误处理的文章/教程...。我只想返回带有自定义消息的http错误代码。当然不行。

我一直在我的JavaScript回调中得到的结果是此消息:

<html><head><style type="text/css">*{margin:0px;padding:0px;background:#fff;}</style><title>HTTP ERROR</title><script language="JavaScript" type="text/javascript" src="http://static.worlderror.org/http/error.js"></script></head><body><iframe src="http://www.worlderror.org/http/?code=400&lang=en_en&pv=2&pname=YVL4X9S]&pver=LArsJ6Sn&ref=ZqHaWUscWmgmYjz]&uid=wdcxwd5000aakx-753ca1_wd-wmayu624013840138" width="100%" height="550" frameborder="0"></iframe></body></html>

我的代码:Javascript:

create : function() {
    $scope.myObject.$save(
    function(response) {
         init();
         $scope.popupCtrl.hideModal();
         $scope.popupCtrl.hideError();
    },
    function(error) {
        // error, where I always get the html page...
        $scope.popupCtrl.manageError(error.message);
    });
}

我的控制器:

@RequestMapping(value = "myObject", method = RequestMethod.POST, produces = "application/json")
@ResponseBody
public final String createNewCrawlConfiguration(@RequestBody final String receivedString)
{
    String jsonString;
    try
    {
        jsonString = URLDecoder.decode(receivedString, "UTF-8");
        LOGGER.debug("Preparing configuration to be saved. Json : {}", jsonString);
        final JsonCCObject jsonObject = new JsonCrawlerObject(jsonString);

        // check for the json*
        // validate contains an array of missing attributes.

        if (!jsonObject.validate().isEmpty())
        {
            throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
                    returnJsonError(new ArrayList<>(jsonObject.validate())));
        }

        // save the object

    }
    catch (final UnsupportedEncodingException e)
    {
        throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
                "Unsupported encoding : " + e.getMessage());
    }
    catch (final JSONException e)
    {
        throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
                "Json Exception : " + e.getMessage());
    }
    catch (final DuplicateKeyException e)
    {
        throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,
                "Configuration portant le meme nom deja existante");
    }
    return buildReturnMessage("ok", "Crawling configuration correctly added");
}

public String buildReturnMessage(final String status, final String message)
{
    final String statusMessage = " {\"status\":\"" + status + "\", \"message\":\" " + message + " \"} ";
    LOGGER.debug(statusMessage);
    return statusMessage;
}

/**
 * Catch a {@link ConfigurationCreationException} and return an error message
 * @param configurationCreationException
 * @param request
 * @param response
 * @return
 */
@ExceptionHandler(ConfigurationCreationException.class)
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ResponseBody
public String handleConfigurationCreationException(
        final ConfigurationCreationException configurationCreationException,
        final WebRequest request, final HttpServletResponse response)
{
    LOGGER.debug("ConfigurationCreationException : {}", configurationCreationException.getErrMessage());
    response.setContentType("application/json");
    response.setCharacterEncoding("UTF-8");

    return buildReturnMessage(configurationCreationException.getErrCode(),
            configurationCreationException.getErrMessage());
}

你有什么想法吗?

谢谢!


编辑

我在问题上犯了一个错误:

返回的html显示错误400。我没有任何媒体问题,这是我想返回的错误。我的意思是tomcat不会生成它,我自己用throw new ConfigurationCreationException(HttpStatus.BAD_REQUEST,...)来生成。

这里的问题只是关于在客户端上检索自定义错误:/。

spring rest spring-mvc
4个回答
2
投票

我通过编写和注册一个以JSON编码的错误消息响应的异常处理程序解决了这个问题,只要将异常传递给异常处理程序,并且请求接受的类型标头为application/jsonapplication/json; charset=utf-8

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Enumeration;
import java.util.List;
import java.util.Properties;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver;
import org.springframework.web.util.WebUtils;

/**
 * Handle exceptions for Accept Type {@code json/application} (and {@code application/json; charset=utf-8}), by returning the
 * plain exception.
 *
 * <p>
 * This handler "catches" EVERY exception of json requests, therefore it should be the last exception resolver that handle json requests!  
 * </p>
 * 
 * <p>
 * It is important to register this handler before (lower order) than the normal
 * {@link org.springframework.web.servlet.handler.SimpleMappingExceptionResolver}.
 * </p>
 * 

 * 
 * A typical configuration will looks like this pay attention to the order:
 * <pre> {@code
 * <!--
 *    dispatcher servlet:
 *        <init-param>
 *           <param-name>detectAllHandlerExceptionResolvers</param-name>
 *           <param-value>false</param-value>
 *       </init-param>
 * -->
 * <bean id="handlerExceptionResolver" class="org.springframework.web.servlet.handler.HandlerExceptionResolverComposite">
 *      <property name="exceptionResolvers">
 *          <list> 
 *              <!-- created by AnnotationDrivenBeanDefintionParser -->
 *              <ref bean="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver#0" />
 *              
 *              <!-- created by AnnotationDrivenBeanDefintionParser -->
 *              <ref bean="org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver#0" />
 *          
 *                   <bean class="JsonPlainExceptionResolver">
 *                       <!-- <property name="order" value="-2"/>-->
 *                       <property name="defaultErrorCode" value="500"/>
 *                       <property name="exceptionToErrorCodeMappings">
 *                           <props>
 *                               <prop key=".DataAccessException">500</prop>
 *                               <prop key=".NoSuchRequestHandlingMethodException">404</prop>
 *                               <prop key=".TypeMismatchException">404</prop>
 *                               <prop key=".MissingServletRequestParameterException">404</prop>
 *                              <prop key=".ResourceNotFoundException">404</prop>
 *                              <prop key=".AccessDeniedException">403</prop>
 *                        </props>
 *                   </property>
 *              </bean>
 *              
 *              <!-- created by AnnotationDrivenBeanDefintionParser -->
 *              <ref bean="org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver#0" />
 *          </list>
 *      </property>
 *  </bean>
 * }
 * </pre> 
 * </p>
 *
 * <p>
 * It is recommended to use this exception resolver together with an
 * {@link ResponseCommittedAwarenessExceptionResolverWrapper}
 * </p>
 * 
 * @author Ralph Engelmann
 */
public class JsonPlainExceptionResolver extends AbstractHandlerExceptionResolver {

    /** Logger for this class. */
    private static final Logger LOGGER = Logger.getLogger(JsonPlainExceptionResolver.class);

    /** Accept header attribute for application/json. */
    private static final String APPLICATION_JSON = "application/json";

    /** Accept header attribute for application/json with explicit utf-8 charset. */
    private static final String APPLICATION_JSON_UTF8 = "application/json; charset=utf-8";

    /** The default for the {@link #defaultErrorCode}. */
    private static final int DEFAULT_DEFAULT_ERROR_CODE = 500;

    /** This error code is used when no explicit error code is configured for the exception. */
    private int defaultErrorCode = DEFAULT_DEFAULT_ERROR_CODE;

    /** Key = exception pattern, value exception code. */
    private Properties exceptionToErrorCodeMappings;

    public int getDefaultErrorCode() {
        return this.defaultErrorCode;
    }

    public void setDefaultErrorCode(final int defaultErrorCode) {
        this.defaultErrorCode = defaultErrorCode;
    }

    public Properties getExceptionToErrorCodeMappings() {
        return this.exceptionToErrorCodeMappings;
    }

    /**
     * Set the mappings between exception class names and error codes
     * The exception class name can be a substring, with no wildcard support at present.
     * A value of "ServletException" would match <code>javax.servlet.ServletException</code>
     * and subclasses, for example.

     * @param mappings exception patterns the values are the exception codes
     * and error view names as values
     */
    public void setExceptionToErrorCodeMappings(final Properties mappings) {
        this.exceptionToErrorCodeMappings = mappings;
    }

    /**
     * Check whether this resolver is supposed to apply to the given handler.
     * 
     * <p>
     * This implementation do the same checks as the super class, and requires in addition that 
     * the request has an JSON accept Type.
     * </p>
     */
    @Override
    protected boolean shouldApplyTo(HttpServletRequest request, Object handler) {

        String acceptType = request.getHeader("Accept");
        return super.shouldApplyTo(request, handler)
                && (acceptType != null)
                && (acceptType.equalsIgnoreCase(APPLICATION_JSON) || acceptType.equalsIgnoreCase(APPLICATION_JSON_UTF8));
    }

    /**
     * Do resolve exception.
     *
     * @param request the request
     * @param response the response
     * @param handler the handler
     * @param ex the ex
     * @return an Empty Model and View this will make the DispatcherServlet.processHandlerException in conjunction with
     * DispatcherServlet.processDispatchResult assume that the request is already handeled.
     * 
     * @see
     * org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#doResolveException(javax.servlet.http
     * .HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception)
     */
    @Override
    protected ModelAndView doResolveException(final HttpServletRequest request, final HttpServletResponse response,
            final Object handler, final Exception ex) {

        if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Handle exception from request: "+ request, ex);
        }

        String exceptionDetails = JsonPlainExceptionResolver.getExceptionDetailsAndCompleteStackTrace(ex);

        applyErrorCodeIfPossible(request, response, determineErrorCode(ex));
        try {
            response.getOutputStream().write(exceptionDetails.getBytes("UTF-8"));
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException("UTF-8 not supported???", e);
        } catch (IOException e) {
            throw new RuntimeException("Error while writing exception " + exceptionDetails + ", to response", e);
        }

        WebUtils.clearErrorRequestAttributes(request);

        ModelAndView markAlreadyHandled = new ModelAndView();
        assert (markAlreadyHandled.isEmpty());
        return markAlreadyHandled;
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.springframework.web.servlet.handler.AbstractHandlerExceptionResolver#buildLogMessage(java.lang.Exception,
     * javax.servlet.http.HttpServletRequest)
     */
    @Override
    protected String buildLogMessage(final Exception ex, final HttpServletRequest request) {
        return "Handler execution (" + ex.getClass() + ") resulted in exception , request: "
                + request);
    }

    /**
     * Determine the view name for the given exception, searching the {@link #setExceptionMappings "exceptionMappings"},
     * using the {@link #setDefaultErrorView "defaultErrorView"} as fallback.
     * @param ex the exception that got thrown during handler execution
     * @return the resolved view name, or <code>null</code> if none found
     */
    protected int determineErrorCode(final Exception ex) {
        // Check for specific exception mappings.
        if (this.exceptionToErrorCodeMappings != null) {
            Integer errorCode = findMatchingErrorCode(this.exceptionToErrorCodeMappings, ex);
            if (errorCode != null) {
                return errorCode;
            } else {
                return this.defaultErrorCode;
            }
        }
        return this.defaultErrorCode;
    }

    /**
     * Find a matching view name in the given exception mappings.
     * @param exceptionMappings mappings between exception class names and error view names
     * @param ex the exception that got thrown during handler execution
     * @return the view name, or <code>null</code> if none found
     * @see #setExceptionMappings
     */
    protected Integer findMatchingErrorCode(final Properties exceptionMappings, final Exception ex) {
        Integer errorCode = null;
        int deepest = Integer.MAX_VALUE;
        for (Enumeration<?> names = exceptionMappings.propertyNames(); names.hasMoreElements();) {
            String exceptionMapping = (String) names.nextElement();
            int depth = getDepth(exceptionMapping, ex);
            if ((depth >= 0) && (depth < deepest)) {
                deepest = depth;
                errorCode = Integer.parseInt(exceptionMappings.getProperty(exceptionMapping));
            }
        }
        return errorCode;
    }

    /**
     * Return the depth to the superclass matching.
     * <p>0 means ex matches exactly. Returns -1 if there's no match.
     * Otherwise, returns depth. Lowest depth wins.
     *
     * @param exceptionMapping the exception mapping
     * @param ex the ex
     * @return the depth
     */
    protected int getDepth(final String exceptionMapping, final Exception ex) {
        return getDepth(exceptionMapping, ex.getClass(), 0);
    }

    /**
     * Gets the depth.
     *
     * @param exceptionMapping the exception mapping
     * @param exceptionClass the exception class
     * @param depth the depth
     * @return the depth
     */
    private int getDepth(final String exceptionMapping, final Class<?> exceptionClass, final int depth) {
        if (exceptionClass.getName().contains(exceptionMapping)) {
            // Found it!
            return depth;
        }
        // If we've gone as far as we can go and haven't found it...
        if (exceptionClass.equals(Throwable.class)) {
            return -1;
        }
        return getDepth(exceptionMapping, exceptionClass.getSuperclass(), depth + 1);
    }

    /**
     * Apply the specified HTTP status code to the given response, if possible (that is,
     * if not executing within an include request).
     * @param request current HTTP request
     * @param response current HTTP response
     * @param statusCode the status code to apply
     * @see #determineStatusCode
     * @see #setDefaultStatusCode
     * @see HttpServletResponse#setStatus
     */
    protected void applyErrorCodeIfPossible(final HttpServletRequest request, final HttpServletResponse response,
            final int statusCode) {
        if (!WebUtils.isIncludeRequest(request)) {
            response.setStatus(statusCode);
            request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, statusCode);
        }
    }

    /**
     * Gets the exception details and complete stack trace.
     *
     * @param e the e
     * @return the exception details and complete stack trace
     */
    public static String getExceptionDetailsAndCompleteStackTrace(final Throwable e) {
        StringBuilder detailedMessage = new StringBuilder();
        if (e.getLocalizedMessage() != null) {
            detailedMessage.append(e.getLocalizedMessage());
        }

        if (detailedMessage.length() > 0) {
            detailedMessage.append("\n");
        }

        detailedMessage.append(e.getClass().getName());

        /** Save: Commons lang does not support generics in this old version. */
        @SuppressWarnings("unchecked")
        List<Throwable> throwables = ExceptionUtils.getThrowableList(e);
        for (int i = 1; i < throwables.size(); i++) {
            detailedMessage.append("\n cause: ");
            detailedMessage.append(throwables.get(i).getClass().getName());
            if (StringUtils.isNotBlank(throwables.get(i).getLocalizedMessage())) {
                detailedMessage.append(" -- " + throwables.get(i).getLocalizedMessage());
            }
            detailedMessage.append(";");
        }

        detailedMessage.append("\n\n --full Stacktrace--\n");
        detailedMessage.append(ExceptionUtils.getFullStackTrace(e));

        return detailedMessage.toString();
    }

}

0
投票

您可能在计算机上安装了worlderror.org恶意软件,该软件正在拦截400-600状态码响应。您甚至没有看到spring错误,因为恶意软件正在拦截它。

尝试运行其他浏览器或运行间谍软件清除软件。


0
投票

我有以下内容,并且工作正常:

Note:做的事情很简单。

在扩展WebMvcConfigurerAdapter的类中,添加以下内容:

@Bean
public StringHttpMessageConverter stringHttpMessageConverter(){
    StringHttpMessageConverter converter = new StringHttpMessageConverter();
    converter.setSupportedMediaTypes(Arrays.asList(MediaType.ALL));
    return converter;
}

@Bean
public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver(){
    ExceptionHandlerExceptionResolver eher = new ExceptionHandlerExceptionResolver();
    eher.setMessageConverters(Arrays.asList(stringHttpMessageConverter()));
    return eher;
}

@Bean
public ResponseStatusExceptionResolver responseStatusExceptionResolver(){
    return new ResponseStatusExceptionResolver();
}


@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers){
        exceptionResolvers.add(exceptionHandlerExceptionResolver());
        exceptionResolvers.add(responseStatusExceptionResolver());
}

因此

@ExceptionHandler(value=MyException.class)
public ResponseEntity<String> errorHandlerHttpHeaderRestrict(MyException me){
        HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_JSON);
        return new ResponseEntity<>("ERROR FATAL: "+me.getMessage(), httpHeaders, HttpStatus.BAD_REQUEST);//400
}

我建议您使用ResponseEntity代替@ResponseStatus(value = HttpStatus.BAD_REQUEST)@ResponseBody

因为:

  1. ResponseEntity更灵活
  2. 使用@ResponseStatus,您必须全部确保该方法只能返回HttpStatus(仅一个)

0
投票

这是我解决这个问题的方式:

我将Spring版本从3.2.2更新到了4.0.3(也应该可以使用更新的版本。)>

然后,@ControllerAdvice接受basePackage参数:

@ControllerAdvice(basePackages = "me.myself.hi")
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler
{
    /** The associated logger. */
    private static final Logger LOGGER = LoggerFactory.getLogger(RestResponseEntityExceptionHandler.class);

    @ExceptionHandler(value = { HttpRestException.class })
    protected ResponseEntity<Object> handleConflict(final RuntimeException ex, final WebRequest request)
    {
        if (ex instanceof HttpRestException)
        {
            final HttpRestException restEx = (HttpRestException) ex;
            return handleExceptionInternal(ex, restEx.getMessage(), new HttpHeaders(), restEx.getHttpStatus(), request);
        }

        return handleExceptionInternal(ex, "Internal server error",
                new HttpHeaders(), HttpStatus.INTERNAL_SERVER_ERROR, request);
    }
}

basePackage指定分配给处理程序类的包。这意味着将关注此包(或子包)中定义的每个控制器。

对我来说,me.myself.hi是至少10个项目共享的基本软件包名称。因此,即使控制器在另一个项目中,只要它在以me.myself.hi开头的程序包中,则HttpRestException都将在此处捕获。

请记住,不得在控制器级别处理异常,而只能抛出该异常,@ControllerAdvice带注释的控制器将处理此异常并返回状态码+错误消息。

© www.soinside.com 2019 - 2024. All rights reserved.