Spring错误控制器的响应是“Not Acceptable”。

20 浏览
0 Comments

Spring错误控制器的响应是“Not Acceptable”。

我建立了一个错误控制器,它应该是捕获Spring REST服务中异常的“最后一道防线”。但是似乎我无法将POJO作为响应类型返回。为什么Jackson不适用于这种情况?

我的类看起来像:

@RestController
public class CustomErrorController implements ErrorController
{
  private static final String PATH = "/error";
  @Override
  public String getErrorPath()
  {
     return PATH;
  }
  @RequestMapping (value = PATH)
  public ResponseEntity handleError(HttpStatus status, HttpServletRequest request)
  {
     WebErrorResponse response = new WebErrorResponse();
    // original requested URI
    String uri = String.valueOf(request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI));
    // status code
    String code = String.valueOf(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
    // status message
    String msg = String.valueOf(request.getAttribute(RequestDispatcher.ERROR_MESSAGE));
    response.title = "Internal Server Error";
    response.type = request.getMethod() + ": " + uri;
    response.code = Integer.valueOf(code);
    response.message = msg;
    // build headers
    HttpHeaders headers = new HttpHeaders();
    headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
    // build the response
    return new ResponseEntity<>(response, headers, status);
}
public class WebErrorResponse
{
/**
 * The error message.
 */
public String message;
/**
 * The status code.
 */
public int code;
/**
 * The error title.
 */
public String title;
/**
 * The error type.
 */
public String type;
}

这应该是有效的,但唯一的响应是一个Jetty错误消息,显示406-不可接受。

将响应实体主体类型更改为字符串可以完美地解决问题。错在哪里?也许这是一个bug?

P.S:使用Spring 4.2.8,Spring Boot 1.3.8操作。

admin 更改状态以发布 2023年5月21日
0
0 Comments

这是一个内容协商问题。基本上,请求正在请求特定格式的响应,而服务器则表示无法以该格式提供响应。

这里可能有几个问题。

  1. 您的请求没有针对Accept标头指定application/json
  2. 您的请求确实使用了值为application/jsonAccept标头,但是Spring Web MVC配置未设置为处理JSON内容类型。

在第一种情况下,明确指定请求者可以处理的类型是明智的。无论是否是您的确切问题,我都会这样做。

在第二种情况下,Spring默认通过XML进行内容协商。您可以通过向ApplicationContext添加WebMvcConfigurer来修改此默认行为:

public class WebConfig extends WebMvcConfigurerAdapter {
  @Override
  public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    configurer.defaultContentType(MediaType.APPLICATION_JSON);
  }
}

此外,通过更明确您的@RequestMapping注释是明智的。请确保使用consumesproduces的“提示”参数(这有助于通过AcceptsContent-type请求头映射请求)。

0
0 Comments

终极解决方案

在经过多次的试错和在Google上反复查找后,我终于找到了一种能够满足我的需求的解决方案。Spring错误处理的主要问题源于默认行为和文档较少。

只使用Spring而不是Spring Boot是没有问题的。但是如果同时使用两者来构建Web(REST)服务,就像是地狱一样。

因此,我想分享我的解决方案,帮助所有遇到同样麻烦的人......

所需的内容:

  • 一个Spring Java配置类
  • 一个Spring异常处理程序(使用@ControllerAdvice和扩展ResponseEntityExceptionHandler)
  • 一个错误控制器(使用@Controller和扩展AbstractErrorController)
  • 一个简单的POJO,通过Jackson生成错误响应(可选)

配置(剪裁重要部分)

@Configuration
public class SpringConfig extends WebMvcConfigurerAdapter
{
   // ... init stuff if needed
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
{
    // setup content negotiation (automatic detection of content types)
    configurer
            // use format parameter and extension to detect mimetype
            .favorPathExtension(true).favorParameter(true)
            // set default mimetype
            .defaultContentType(MediaType.APPLICATION_XML)
            .mediaType(...)
            // and so on ....
 }
 /**
 * Configuration of the {@link DispatcherServlet} bean.
 *
 * 

This is needed because Spring and Spring Boot auto-configuration override each other.

* * @see * Stackoverflow - Spring Boot REST service exception handling * * @param dispatcher dispatcher servlet instance */ @Autowired @SuppressWarnings ("SpringJavaAutowiringInspection") public void setupDispatcherServlet(DispatcherServlet dispatcher) { // FIX: for global REST error handling // enable exceptions if endpoint not found (instead of static error page) dispatcher.setThrowExceptionIfNoHandlerFound(true); } /** * Creates the error properties used to setup the global REST error controller. * *

Using {@link ErrorProperties} is compliant to base implementation if Spring Boot's * {@link org.springframework.boot.autoconfigure.web.BasicErrorController}.

* * * @return error properties */ @Bean public ErrorProperties errorProperties() { ErrorProperties properties = new ErrorProperties(); properties.setIncludeStacktrace(ErrorProperties.IncludeStacktrace.NEVER); properties.setPath("/error"); return properties; } // ... }

Spring异常处理程序:

@ControllerAdvice(annotations = RestController.class)
public class WebExceptionHandler extends ResponseEntityExceptionHandler
{
/**
 * This function handles the exceptions.
 *
 * @param e the thrown exception
 *
 * @return error message as XML-document
 */
@ExceptionHandler (Exception.class)
public ResponseEntity handleErrorResponse(Exception e)
{
    logger.trace("Catching Exception in REST API.", e);
    return handleExceptionInternal(e, null, null, null, null);
}
@Override
protected ResponseEntity handleExceptionInternal(Exception ex,
                                                         Object body,
                                                         HttpHeaders headers,
                                                         HttpStatus status,
                                                         WebRequest request)
{
    logger.trace("Catching Spring Exception in REST API.");
    logger.debug("Using " + getClass().getSimpleName() + " for exception handling.");
    // fatal, should not happen
    if(ex == null) throw new NullPointerException("empty exception");
    // set defaults
    String title = "API Error";
    String msg   = ex.getMessage();
    if(status == null) status = HttpStatus.BAD_REQUEST;
    // build response body
    WebErrorResponse response = new WebErrorResponse();
    response.type = ex.getClass().getSimpleName();
    response.title = title;
    response.message = msg;
    response.code = status.value();
    // build response headers
    if(headers == null) headers = new HttpHeaders();
    try {
        headers.setContentType(getContentType(request));
    }
    catch(NullPointerException e)
    {
        // ignore (empty headers will result in default)
    }
    catch(IllegalArgumentException e)
    {
        // return only status code
        return new ResponseEntity<>(status);
    }
    return new ResponseEntity<>(response, headers, status);
}
/**
 * Checks the given request and returns the matching response content type
 * or throws an exceptions if the requested content type could not be delivered.
 *
 * @param request current request
 *
 * @return response content type matching the request
 *
 * @throws NullPointerException     if the request does not an accept header field
 * @throws IllegalArgumentException if the requested content type is not supported
 */
private static MediaType getContentType(WebRequest request) throws NullPointerException, IllegalArgumentException
{
    String accepts = request.getHeader(HttpHeaders.ACCEPT);
    if(accepts==null) throw new NullPointerException();
    // XML
    if(accepts.contains(MediaType.APPLICATION_XML_VALUE) ||
       accepts.contains(MediaType.TEXT_XML_VALUE) ||
       accepts.contains(MediaType.APPLICATION_XHTML_XML_VALUE))
        return MediaType.APPLICATION_XML;
    // JSON
    else if(accepts.contains(MediaType.APPLICATION_JSON_VALUE))
        return MediaType.APPLICATION_JSON_UTF8;
    // other
    else throw new IllegalArgumentException();
}
}

Spring Boot的错误控制器:

@Controller
@RequestMapping("/error")
public class CustomErrorController extends AbstractErrorController
{
    protected final Logger logger = LoggerFactory.getLogger(getClass());
    /**
     * The global settings for this error controller.
     */
    private final ErrorProperties properties;
    /**
     * Bean constructor.
     *
     * @param properties global properties
     * @param attributes default error attributes
     */
    @Autowired
    public CustomErrorController(ErrorProperties properties, ErrorAttributes attributes)
    {
        super(attributes);
        this.properties = new ErrorProperties();
    }
    @Override
    public String getErrorPath()
    {
        return this.properties.getPath();
    }
    /**
     * Returns the configuration properties of this controller.
     *
     * @return error properties
     */
    public ErrorProperties getErrorProperties()
    {
        return this.properties;
    }
    /**
     * This function handles runtime and application errors.
     *
     * @param request the incorrect request instance
     *
     * @return error message as XML-document
     */
    @RequestMapping (produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_XML_VALUE})
    @ResponseBody
    public ResponseEntity handleError(HttpServletRequest request)
    {
        logger.trace("Catching Exception in REST API.");
        logger.debug("Using {} for exception handling." , getClass().getSimpleName());
        // original requested REST endpoint
        String endpoint = String.valueOf(request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI));
        // status code
        String code = String.valueOf(request.getAttribute(RequestDispatcher.ERROR_STATUS_CODE));
        // thrown exception
        Exception ex = ((Exception) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION));
        if(ex == null) {
            ex = new RuntimeException(String.valueOf(request.getAttribute(RequestDispatcher.ERROR_MESSAGE)));
        }
        // release nested exceptions (we want source exception only)
        if(ex instanceof NestedServletException && ex.getCause() instanceof Exception) {
            ex = (Exception) ex.getCause();
        }
        // build response body
        WebErrorResponse response = new WebErrorResponse();
        response.title   = "Internal Server Error";
        response.type    = ex.getClass().getSimpleName();
        response.code    = Integer.valueOf(code);
        response.message = request.getMethod() + ": " + endpoint+"; "+ex.getMessage();
        // build response headers
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(getResponseType(request));
        // build the response
        return new ResponseEntity<>(response, headers, getStatus(request));
    }
    /*@RequestMapping (produces = {MediaType.APPLICATION_JSON_UTF8_VALUE, MediaType.APPLICATION_XML_VALUE})
    public ResponseEntity> handleError(HttpServletRequest request)
    {
        Boolean stacktrace = properties.getIncludeStacktrace().equals(ErrorProperties.IncludeStacktrace.ALWAYS);
        Map r = getErrorAttributes(request, stacktrace);
        return new ResponseEntity>(r, getStatus(request));
    }*/
    /**
     * Extracts the response content type from the "Accept" HTTP header field.
     *
     * @param request request instance
     *
     * @return response content type
     */
    private MediaType getResponseType(HttpServletRequest request)
    {
        String accepts = request.getHeader(HttpHeaders.ACCEPT);
        // only XML or JSON allowed
        if(accepts.contains(MediaType.APPLICATION_JSON_VALUE))
            return MediaType.APPLICATION_JSON_UTF8;
        else return MediaType.APPLICATION_XML;
    }
}

所以,这就是全部内容,POJO WebErrorResponse是一个纯粹使用公共字符串和int字段的类。

上述类适用于支持XML和JSON的REST API。

它是如何工作的:

  • 来自控制器(自定义的和应用程序逻辑的)的异常将由Spring异常处理程序处理
  • 来自Spring的异常将由Spring异常处理程序处理(例如缺少参数)
  • 404(缺少端点)将由Spring Boot错误控制器处理
  • mimetype问题(例如请求图像/ png但抛出异常)将首先移动到Spring异常处理程序,然后重定向到Spring Boot错误控制器(由于mimetype异常)

我希望这将澄清其他像我一样困惑的人的疑惑。

此致

敬礼

Zipunrar

0