【问题标题】:Intercept @RequestHeader exception for missing header为缺少的标头拦截 @RequestHeader 异常
【发布时间】:2014-09-28 20:24:14
【问题描述】:

我在控制器中有一个方法,例如有参数

@RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
@ResponseStatus(HttpStatus.CREATED)
public void post(@RequestHeader("ETag") int etag)

如果请求中没有 ETag 标头 - 客户端得到 400 (BAD_REQUEST),这没有任何信息。 我需要以某种方式处理此异常并将我自己的异常发送给客户端(为此我使用 JSON)。 我知道我可以通过@ExceptionHandler 拦截异常,但在这种情况下,所有 HTTP 400 请求都将被处理,但我希望标头中缺少 ETag。 有什么想法吗?

【问题讨论】:

    标签: java spring spring-mvc servlets


    【解决方案1】:

    在 Spring 5+ 中就是这么简单。 ErrorResponse 是你自己要返回的对象

    @RestControllerAdvice
    public class ControllerExceptionHandler {
    
    @ExceptionHandler(MissingRequestHeaderException.class)
    public ResponseEntity<ErrorResponse> handleException(MissingRequestHeaderException ex) {
        log.error("Error due to: " + ex.getMessage());
    
        ErrorResponse errorResponse = new ErrorResponse();
    
        return new ResponseEntity<>(errorResponse, HttpStatus.BAD_REQUEST);
    }
    

    }

    【讨论】:

      【解决方案2】:

      您可以创建自定义异常类,例如InvalidRequestHeaderException.java。您可以在此处自定义您的异常消息。

      @ResponseStatus(HttpStatus.BAD_REQUEST)
      public class InvalidRequestHeaderException extends RuntimeException {
      
        public InvalidRequestHeaderException() {
          super("Invalid request header provided.");
        }
      }
      

      在您的控制器中,如果提供的标头无效,您可以抛出异常。

      @RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
      @ResponseStatus(HttpStatus.CREATED)
      public void post(@RequestHeader("ETag") int etag) {
        // some code
        
        if (!isSupportedPlatform(platform)) {
          throw new InvalidRequestHeaderException();
        }
        
        // some code
      }
      

      然后您可以创建一个 ValidationHandler.java 来处理这些异常。

      @RestControllerAdvice
      public class ValidationHandler extends ResponseEntityExceptionHandler {
      
        @ExceptionHandler(value = {
            MissingRequestHeaderException.class,
            InvalidRequestHeaderException.class
        })
        protected ResponseEntity<Object> handleRequestHeaderException(Exception ex) {
          log.error(ex.getMessage());
          return ResponseEntity.badRequest().body(ErrorResponse.builder()
              .status(String.valueOf(HttpStatus.BAD_REQUEST.value()))
              .reason(ex.getMessage()).build());
        }
      
      
        @AllArgsConstructor
        @Getter
        @Builder
        public static class ErrorResponse {
      
          private String status;
          private String reason;
        }
      }
      

      通过使用MissingRequestHeaderException,如果您使用@RequestHeader 注释的内容丢失,它将引发异常,因此您将收到如下异常:

      缺少类型 int 的方法参数的请求标头“Etag”

      当请求标头存在但无效时,将抛出此异常:

      提供的请求标头无效。

      【讨论】:

        【解决方案3】:

        您可以在此请求参数中添加@Nullable,如果不存在,请求仍然进入控制器而不抛出MissingRequestHeaderException,并且您添加手动验证以在控制器中抛出您喜欢的任何内容并在ExceptionHandler中处理。

        【讨论】:

          【解决方案4】:

          如果 Spring 版本为 5+,那么您需要处理的确切异常是 MissingRequestHeaderException。如果您的全局异常处理程序类扩展了ResponseEntityExceptionHandler,那么为ServletRequestBindingException 添加@ExceptionHandler 将不起作用,因为MissingRequestHeaderException 扩展了ServletRequestBindingException,而后者在handleExceptionResponseEntityExceptionHandler 方法中处理。如果你尝试,你会得到Ambiguous @ExceptionHandler method mapped for ... 异常。

          【讨论】:

          • 你在这里救了我的命...非常感谢。
          【解决方案5】:

          你也可以在不扩展ResponseEntityExceptionHandler的情况下拦截异常:

          @ControllerAdvice
          public class ControllerExceptionHandler {
          
              @ExceptionHandler(ServletRequestBindingException.class)
              @ResponseBody
              @ResponseStatus(HttpStatus.BAD_REQUEST)
              public ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex) {
                  // return a ResponseEntity<Object> object here.
              }
          }
          

          【讨论】:

            【解决方案6】:

            您也可以通过使用 spring 中的注解 @ControllerAdvice 来实现。

            @ControllerAdvice
            public class ExceptionHandler extends ResponseEntityExceptionHandler{
            
                /**
                 * Handle ServletRequestBindingException. Triggered when a 'required' request
                 * header parameter is missing.
                 *
                 * @param ex      ServletRequestBindingException
                 * @param headers HttpHeaders
                 * @param status  HttpStatus
                 * @param request WebRequest
                 * @return the ResponseEntity object
                 */
                @Override
                protected ResponseEntity<Object> handleServletRequestBindingException(ServletRequestBindingException ex,
                        HttpHeaders headers, HttpStatus status, WebRequest request) {
                    return new ResponseEntity<>(ex.getMessage(), headers, status);
                }
            
            }
            

            当您在没有所需请求标头的情况下访问您的 API 时的响应是:

            String 类型的方法参数缺少请求标头“授权”

            和这个异常一样,您可以自定义所有其他异常。

            【讨论】:

              【解决方案7】:

              使用注解@ExceptionHandler 编写一个方法并使用ServletRequestBindingException.class,因为如果缺少标头会引发此异常

              例如:

              @ExceptionHandler(ServletRequestBindingException.class)
              public ResponseEntity<ResponseObject> handleHeaderError(){
              
                  ResponseObject responseObject=new ResponseObject();
                  responseObject.setStatus(Constants.ResponseStatus.FAILURE.getStatus());
                  responseObject.setMessage(header_missing_message);
              
                  ResponseEntity<ResponseObject> responseEntity=new ResponseEntity<ResponseObject>(responseObject, HttpStatus.BAD_REQUEST);
                  return responseEntity;
              }
              

              【讨论】:

                【解决方案8】:

                这个比较简单。声明两种处理程序方法,一种在@RequestMappingheaders 属性中声明适当的标头,另一种不声明。 Spring 会注意根据请求的内容调用适当的。

                @RequestMapping(value = "/{blabla}", method = RequestMethod.POST, headers = "ETag")
                @ResponseStatus(HttpStatus.CREATED)
                public void postWith(@RequestHeader("ETag") int etag) {
                    // has it
                }
                
                @RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
                @ResponseStatus(HttpStatus.CREATED)
                public void postWithout() {
                    // no dice
                    // custom failure response
                }
                

                【讨论】:

                  【解决方案9】:

                  您应该使用@ExceptionHandler 方法来查看 ETag 标头是否存在并采取适当的措施:

                  @ExceptionHandler(UnsatisfiedServletRequestParameterException.class)
                  public onErr400(@RequestHeader(value="ETag", required=false) String ETag,
                          UnsatisfiedServletRequestParameterException ex) {
                      if(ETag == null) {
                          // Ok the problem was ETag Header : give your informational message
                      } else {
                          // It is another error 400  : simply say request is incorrect or use ex
                      }
                  }
                  

                  【讨论】:

                  • 是的,我选择了这个解决方案,几乎没有修改,但在你写下来之前:)
                  【解决方案10】:

                  如果您不想在请求映射中处理此问题,则可以创建一个 Servlet 过滤器并在过滤器中查找 ETag 标头。如果不存在,则抛出异常。这仅适用于与过滤器的 URL 映射匹配的请求。

                  public final class MyEtagFilter extends OncePerRequestFilter {
                      @Override
                      protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
                          String etag = request.getHeader("ETag");
                  
                          if(etag == null)
                              throw new MissingEtagHeaderException("...");
                  
                          filterChain.doFilter(request, response);
                      }
                  }
                  

                  您必须实现自己的 MissingEtagHeaderException,或使用其他一些现有异常。

                  【讨论】:

                    【解决方案11】:

                    有两种方法可以实现您的目标

                    首先使用@RequestHeaderrequired false

                    @RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
                    @ResponseStatus(HttpStatus.CREATED)
                    public void post(@RequestHeader(value="ETag", required=false) String ETag) {
                        if(ETag == null) {
                            // Your JSON Error Handling
                        } else {
                            // Your Processing
                        }
                    }
                    

                    第二次使用HttpServletRequest而不是@RequestHeader

                    @RequestMapping(value = "/{blabla}", method = RequestMethod.POST)
                    @ResponseStatus(HttpStatus.CREATED)
                    public void post(HttpServletRequest request) {
                        String ETag = request.getHeader("ETag");
                        if(ETag == null) {
                            // Your JSON Error Handling
                        } else {
                            // Your Processing
                        }
                    }
                    

                    【讨论】:

                    • 1) 我需要这个标题,所以我不能做非必需的。 2)这正是我想要的,但以更一般的方式,对于方法的数量。我认为有更干净的方法来完成这项工作,然后复制/粘贴“if(ETag == null)”。
                    • 嗨@shazin。是否可以在某处的基类中捕获此@request 标头并在其他任何地方以单个方法访问?到处写这段代码似乎效率低下
                    • @RuslanIslamov 将 required 设置为 false 并不是说​​您不需要它,它只是让方法在不存在时不会抛出异常。您仍然可以检查该值并检查它是否为空,然后在调用省略它时按照通常的方式继续。
                    猜你喜欢
                    • 1970-01-01
                    • 2011-05-13
                    • 1970-01-01
                    • 1970-01-01
                    • 2010-09-18
                    • 2016-03-14
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多