Intercept @RequestHeader exception for missing hea

2020-08-25 05:29发布

问题:

I have a method in controller with has parameter for example

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

If there is no ETag header in request - client gets 400 (BAD_REQUEST), which is not any informative. I need to somehow handle this exception and send my own exception to client (I use JSON for this purpose). I know that I can intercept exception via @ExceptionHandler, but in that case all HTTP 400 requests will be handled, but I want that have missing ETag in headers. Any ideas?

回答1:

You should user an @ExceptionHandler method that looks if ETag header is present and takes appropriate action :

@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
    }
}


回答2:

You can also achieve this by use of annotation @ControllerAdvice from spring.

@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);
    }

}

The response when you access your API without the required request header is:

Missing request header 'Authorization' for method parameter of type String

Like this exception, you can customise all other exceptions.



回答3:

There are two ways to achieve what you are trying

First using @RequestHeader with required 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
    }
}

Second using HttpServletRequest instead of @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
    }
}


回答4:

Write a method with the annotation @ExceptionHandler and use ServletRequestBindingException.class as this exception is thrown in case of missing header

For example :

@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;
}


回答5:

In case Spring version is 5+ then the exact exception you need to handle is the MissingRequestHeaderException. If your global exception handler class extends ResponseEntityExceptionHandler then adding an @ExceptionHandler for ServletRequestBindingException won't work because MissingRequestHeaderException extends ServletRequestBindingException and the latter is handled inside the handleException method of the ResponseEntityExceptionHandler. If you try you're going to get Ambiguous @ExceptionHandler method mapped for ... exception.



回答6:

If you don't want to handle this in your request mapping, then you could create a Servlet Filter and look for the ETag header in the Filter. If it's not there, then throw the exception. This would apply to only requests that match your filter's URL mapping.

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);
    }
}

You'll have to implement your own MissingEtagHeaderException, or use some other existing exception.



回答7:

This is relatively simple. Declare two handler methods, one that declares the appropriate header in the @RequestMapping headers attribute and one that doesn't. Spring will take care to invoke the appropriate one based on the content of the request.

@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
}


回答8:

You can also intercept the exception without extending 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.
    }
}