How can I validate the accept headers in spring and return a custom message if the accept header is not application/json. Currently I am doing it this way(below) but I am wondering if there is another way of doing this?
I know I can make custom exceptions and throw the exception based on what went wrong but is there a different way of doing this?
#GetMapping(value = "/get", produces = {MediaType.APPLICATION_JSON_UTF8_VALUE})
public ResponseEntity<String> get(final HttpEntity<String> httpEntity) {
HttpHeaders responseHeaders = new HttpHeaders();
String acceptString = httpEntity.getHeaders().getFirst("Accept");
if (acceptString == null || acceptString.isEmpty()) {
return new ResponseEntity<String>("empty accept header", HttpStatus.BAD_REQUEST);
}
if ((MediaType.APPLICATION_JSON_VALUE).equalsIgnoreCase(acceptString)) {
responseHeaders.setContentType(MediaType.APPLICATION_JSON_UTF8);
}
else {
return new ResponseEntity<String>("Not valid accept value", HttpStatus.BAD_REQUEST);
}
....
Within your #Getmapping you can add headers="Accept=application/json" this way your method will only handle calls with that header type.
You can then add a method which processes all the calls that are ignored by your specific AcceptHeader method. This way you only need one method to validate the headers.
You can also write a custom validator and use anotation to add this to your method like here:
conditional validation
Related
I have a Spring-Boot controller application that will be called by the front-end. The Spring-boot #PostMapping would accept the XML and JSON. I want to call different methods based on the Content-Type.
Is there a way to check what is the incoming content type?
#CrossOrigin(origins = "*")
#RestController
#RequestMapping("/api")
public class MyController {
#PostMapping(value = "/generator", consumes = {"application/json", "application/xml"}, produces = "application/json")
public String generate(#RequestBody String input) {
try {
System.out.println("INPUT CONTENT TYPE : ");
if(contentType == "application/xml")
{
//Call Method-1
}else if(contentType == "application/json"){
//Call Method-2
}
} catch (Exception exception) {
System.out.println(exception.getMessage());
}
}
}
As we can see the RestController method accepts XML and JSON. I want to check whats the incoming Content-type is based on its need to make different decisions. Can someone please explain to me how to do it?
Please Note:
I am aware that I can create different methods to handle XML and JSON but I would like to do it in a single method so it would be easy and efficient.
Add RequestHeader with its name Content-type:
public String generate(#RequestBody String input, #RequestHeader("Content-type") String contentType)
Annotation which indicates that a method parameter should be bound to a web request header.
You can use
#RequestHeader Map<String, String> headers
inside param of your generate() methode for get all Header come from the client.
After that, just check the
Content-Type
value
I have a Spring-Boot (v2.0.2) application with a RestController with 2 methods which only differ by the Accept header. A simplified version of the code is this:
#RestController
#RequestMapping("/myapp")
public class FooController {
#GetMapping(value = "/foo/{id}", headers = "Accept=application/json", produces = "application/json;charset=UTF-8")
public ResponseEntity<String> fooJson(#PathVariable id) {
return foo(pageId, true);
}
#GetMapping(value = "/foo/{id}", headers = "Accept=application/ld+json", produces = "application/ld+json;charset=UTF-8")
public ResponseEntity<String> fooJsonLd(#PathVariable id) {
return foo(pageId, false);
}
private ResponseEntity<String> foo(String id, boolean isJson) {
String result = generateBasicResponse(id);
if (isJson) {
return result
}
return addJsonLdContext(result);
}
This works fine. If we sent a request with accept header such as application/json;q=0.5,application/ld+json;q=0.6 for example it will return a json-ld response as it should.
My problem is that if we sent a request with no accept header, an empty accept header or a wildcard */* then it will by default always return a json response whereas I want the default response to be json-ld.
I've tried various things to make the json-ld request mapping take priority over the json one:
Reversing the order in which the mappings are declared.
Adding an #Order annotation to both methods (with value 1 for json-ld and value 2 for the json method)
Creating different classes and putting the #Order annotation at class-level
Adding Accept=*/* as a second accept header to the json-ld mapping does work in giving it preference but has the unwanted side-affect that all accept headers are accepted, even unsupported types as application/xml for example.
The only solution I can think of is creating one request-mapping method that accepts both headers and then processing the accept header ourselves, but I don't really like that solution. Is there a better, easier way to give preference to json-ld?
After some more searching this question on configuring custom MediaTypes pointed me in the right direction.
The WebMvcConfigurerAdapter (Spring 3 or 4) or WebMvcConfigurer (Spring 5) allows you to set a default mediatype like this:
public static final String MEDIA_TYPE_JSONLD = "application/ld+json";
#EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
#Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.defaultContentType(MediaType.valueOf(MEDIA_TYPE_JSONLD));
}
}
This works great for requests with no or an empty accept header, as well as accept: */*. However when you combine an unsupported type with the wildcard, for example accept: */*,text/plain it will return json instead of json-ld!? I suspect this is a bug in Spring.
I solved the issue using the consumes in the #GetMapping annotation. According to the official documentation:
The format is a single media type or a sequence of media types, with a request only mapped if the Content-Type matches one of these media types. Expressions can be negated by using the "!" operator, as in "!text/plain", which matches all requests with a Content-Type other than "text/plain".
In the solution bellow, note that I've added the consumes array to the normal json request mapping, making the client only be able to use the json endpoint if it have the correct Content-Type. Other requests go to the ld+json endpoint.
#GetMapping(value = "/json", headers = "Accept=application/json", consumes = {"application/json"})
#ResponseBody
public String testJson() {
return "{\"type\":\"json\"}";
}
#GetMapping(value = "/json", headers = "Accept=application/ld+json")
#ResponseBody
public String textLDJson() {
return "{\"type\":\"ld\"}";
}
In a Spring application, I have an endpoint which normally returns an image (produces = MediaType.IMAGE_PNG_VALUE).
I also have #ExceptionHandler functions to handle various functions.
I'm trying to find a way to determine, from within the #ExceptionHandler, if the client will accept text/plain or text/json so in the event of an error I can return back one of those, or omit it if they are only expecting image/png.
How can I determine what acceptable content types I can return for a given request?
You can access the request to inspect headers and return an appropriate response. It is standard Content Negotiation.
Here's an example:
#ControllerAdvice
public class MyExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {RuntimeException.class})
protected ResponseEntity<Object> handleMyException(RuntimeException ex, WebRequest request) {
List<String> acceptableMimeTypes = Arrays.asList(request.getHeaderValues(HttpHeaders.ACCEPT));
if (acceptableMimeTypes.contains(MediaType.TEXT_PLAIN_VALUE)) {
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_PLAIN_VALUE)
.body("hello");
}
throw ex;
}
}
There are some arguments that spring-mvc can automagically inject into controller methods, and WebRequest (which is spring's representation of an http request) is one of those. If the client has sent an Accept : text/plain header with the request, the above example returns the string hello if there's a RuntimeException. If there's no exception, this logic won't get triggered at all, so the endpoint will just return whatever it normally returns. You can read more about #ControllerAdvice and #ExceptionHandler here.
Of course, be sure to think about the exact exception types you want to handle, and semantically appropriate status codes to return so that the clients know how to correctly interpret the response.
This is the answer I came up with. It's similar to YoungSpice's, but it is a little more flexible and uses MediaType directly (which means it'll handle wildcard types like text/* and the like):
private ResponseEntity<String> buildResponse(WebRequest request, HttpStatus status, String message) {
HttpHeaders httpHeader = new HttpHeaders();
List<MediaType> acceptHeader =
MediaType.parseMediaTypes(Arrays.asList(request.getHeaderValues(HttpHeaders.ACCEPT)));
if (acceptHeader.stream().anyMatch(mediaType -> mediaType.isCompatibleWith(MediaType.APPLICATION_JSON))) {
httpHeader.setContentType(MediaType.APPLICATION_JSON);
return new ResponseEntity<>("{ \"error\": \"" + message + "\" }", httpHeader, status);
} else if (acceptHeader.stream().anyMatch(mediaType -> mediaType.isCompatibleWith(MediaType.TEXT_PLAIN))) {
httpHeader.setContentType(MediaType.TEXT_PLAIN);
return new ResponseEntity<>(message, httpHeader, status);
} else {
return ResponseEntity.status(status).body(null);
}
}
Basically, it uses MediaType.parseMediaTypes() to parse the Accept header, then I stream through them and use the mediaType.isCompatibleWith() function to check if my target is acceptable. This will let it handle if the header has something like application/* instead of application/json directly.
It also seems like if Accept isn't explicitly provided in the request, there is an implied */*, which seems to work as intended.
long story short: I'm creating API that is supposed to be 100% REST.
I'm trying to overwrite default response for the following case:
I've got a method in my #RestController that has #RequestBody as an attribute
#RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public Resource<User> registerClient(#RequestBody User user, HttpServletRequest request)
and the method is working just fine if I send a proper request. But there is a problem when I don't. When a request has empty body, I get a generic Tomcat error page for status 400 and I need it to send just a string or a JSON object instead.
So far I tried to add Exception Handlers in my RestControllerAdvice for all Spring exceptions from package org.springframework.web.binding, but it didn't work either.
I'm already aware that for some security-related errors one have to create handlers in configuration, but I don't know if this is the case.
Did anyone face similar issues? Is there something I'm missing?
The solution was to simply put required = false in RequestBody annotation. After that, I could easily add some logic to throw custom exception and handle it in ControllerAdvice.
#RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public Resource<User> registerClient(#RequestBody(required = false) User user, HttpServletRequest request){
logger.debug("addClient() requested from {}; registration of user ({})", getClientIp(request), user);
if(user == null){
throw new BadRequestException()
.setErrorCode(ErrorCode.USER_IS_NULL.toString())
.setErrorMessage("Wrong body or no body in reqest");
} (...)
Firstly I suggest you to use BindingResult as a parameter of the POST call and check if it returns an error or not.
#RequestMapping(value = {"register"}, method = RequestMethod.POST, produces = "application/hal+json")
public ResponseEntity<?> registerClient(#RequestBody User user, HttpServletRequest request, BindingResult brs)
if (!brs.hasErrors()) {
// add the new one
return new ResponseEntity<User>(user, HttpStatus.CREATED);
}
return new ResponseEntity<String>(brs.toString(), HttpStatus.BAD_REQUEST);
}
Secondly, the call can throw some of errors, a good practice is to carch them and return them itself or transform them to your own exception object. The advantage is it secures a call of all the update/modify methods (POST, PUT, PATCH)
#ExceptionHandler(MethodArgumentNotValidException.class)
#ResponseBody
public ResponseEntity<?> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
return new ResponseEntity<List<MethodArgumentNotValidException>>(e, HttpStatus.BAD_REQUEST);
}
#ExceptionHandler({HttpMessageNotReadableException.class})
#ResponseBody
public ResponseEntity<?> handleHttpMessageNotReadableException(HttpMessageNotReadableException e) {
return new ResponseEntity<List<HttpMessageNotReadableException>>(e, HttpStatus.BAD_REQUEST);
}
Your control will never reach to your request method under normal circumstances.
If you want a looking good page you can make use of web.xml and configure it to produce your answer.
<error-page>
<error-code>404</error-code>
<location>/pages/resource-not-found.html</location>
</error-page>
Generally, if you want to go past this 400 problem, you will have to add a few annotiations to your User.java to avoid any unknown fields while de-serializing.
In a Spring RestController I have an input validation of the RequestBody simply by annotating the corresponding method parameter as #Valid or #Validated. Some other validations can only be performed after some processing of the incoming data. My question is, what type of exceptions should I use, so that it resembles the exception thrown by the #Valid annotation, and how do I construct this exception from the validation result. Here is an example:
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody #Validated(InputChecks.class) Order order) {
// Some processing of the Order goes here
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
// What to do now with the validation errors?
orders.put(order);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/" + order.getId()).build().toUri());
return new ResponseEntity<>(null, headers, HttpStatus.CREATED);
}
To me the simplest way looks like validating the object with an errors object, and use it in a MethodArgumentNotValidException.
#RequestMapping(method = RequestMethod.POST)
public ResponseEntity<?> createOrder(#RequestBody #Validated(InputChecks.class) Order order)
throws NoSuchMethodException, SecurityException, MethodArgumentNotValidException {
// Some processing of the Order goes here
SpringValidatorAdapter v = new SpringValidatorAdapter(validator);
BeanPropertyBindingResult errors = new BeanPropertyBindingResult(order, "order");
v.validate(order, errors, FinalChecks.class);
if (errors.hasErrors()) {
throw new MethodArgumentNotValidException(
new MethodParameter(this.getClass().getDeclaredMethod("createOrder", Order.class), 0),
errors);
}
orders.put(order);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ServletUriComponentsBuilder.fromCurrentRequest().path("/" + order.getId()).build().toUri());
return new ResponseEntity<>(null, headers, HttpStatus.CREATED);
}
This way the errors found during the second validation step have exactly the same structure as the errors found during the input validation on the #validated parameters.
For handling validation errors in the second run, i can think of three different approaches. First, you can extract validation error messages from Set of ConstraintViolations and then return an appropriate HTTP response, say 400 Bad Request, with validation error messages as the response body:
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
if (!violations.isEmpty()) {
Set<String> validationMessages = violations
.stream()
.map(ConstraintViolation::getMessage)
.collect(Collectors.toSet());
return ResponseEntity.badRequest().body(validationMessages);
}
// the happy path
This approach is suitable for situations when the double validation is a requirement for a few controllers. Otherwise, it's better to throw a brand new Exception or reuse spring related exceptions, say MethodArgumentNotValidException, and define a ControllerAdvice that handle them universally:
Set<ConstraintViolation<Order>> violations = validator.validate(order, FinalChecks.class);
if (!violations.isEmpty()) {
throw new ValidationException(violations);
}
And the controller advice:
#ControllerAdvice
public class ValidationControllerAdvice {
#ExceptionHandler(ValidationException.class)
public ResponseEntity handleValidtionErrors(ValidationException ex) {
return ResponseEntity.badRequest().body(ex.getViolations().stream()...);
}
}
You can also throw one of spring exceptions like MethodArgumentNotValidException. In order to do so, you need to convert the Set of ConstraintViolations to an instance of BindingResult and pass it to the MethodArgumentNotValidException's constructor.