I am working on a large Spring Boot codebase in which I am trying to introduce request header validation.
This is my sample controller:
import javax.validation.constraints.Size;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
#Validated
#RequestMapping("/abc/test/v1")
public class TestController {
#GetMapping("/testMethod")
public String testMethod(#RequestHeader
#Size(min = 10, message = "Not valid header") String foo) {
return foo;
}
}
And this is the #ControllerAdvice error handler:
import java.util.LinkedList;
import java.util.List;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
import com.abc.test.exception.RequestParamValidationException;
#SuppressWarnings({ "unchecked", "rawtypes" })
#ControllerAdvice
#RequestMapping( produces = MediaType.APPLICATION_JSON_VALUE)
public class ValidationExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
public ResponseEntity<RequestParamValidationException> handleConstraintVoilationexception(ConstraintViolationException ex, WebRequest req) {
List<String> details = new LinkedList<>();
for(ConstraintViolation<?> violation : ex.getConstraintViolations()) {
details.add(violation.getMessage());
}
RequestParamValidationException exception = new RequestParamValidationException
("Request Param Validation Failed", details);
return new ResponseEntity(exception, HttpStatus.BAD_REQUEST);
}
}
This is a minimal reproducible example of my project, but I have no other error handlers present.
Even though I should be getting a ConstraintVoilationexception, but the MissingRequestHeaderException keeps being handled by a mysterious error handler which I have not declared, as I can see from my logs:
2020-06-08 10:49:10.655 WARN 3180 --- [0.0-8080-exec-1] .m.m.a.ExceptionHandlerExceptionResolver : Resolved [org.springframework.web.bind.MissingRequestHeaderException: Missing request header 'foo' for method parameter of type String]
It never reaches my exception handler.
How do I find out what is this mysterious exception handler is ?
Thanks.
First you need to define order of ValidationExceptionHandler
It's done with annotation #Order(Ordered.HIGHEST_PRECEDENCE)
Second, you need to override method because MissingRequestHeaderException is subclass of ServletRequestBindingException
#Override
protected ResponseEntity<Object> handleServletRequestBindingException(
final ServletRequestBindingException ex,
final HttpHeaders headers,
final HttpStatus status,
final WebRequest request
) {
return super.handleServletRequestBindingException(ex, headers, status, request);
}
MissingRequestHeaderException is a subclass of ServletRequestBindingException.Extending ResponseEntityExceptionHandler will automatically give you various methods to handle different exceptions by default.One of the method is handleServletRequestBindingException.
So your MissingRequestHeaderException is handled by the method of ResponseEntityExceptionHandler.
If you have to verify, go to the code of ResponseEntityExceptionHandler in your IDE.
Hope this helps.
Related
I'm building an app, that including service to upload images to S3 AWS, important to say that the bucket "NOT PUBLIC".
When I'm sending the request through the Postman desktop app, I'm getting 200OK...
BUT, when I'm sending the request through the Postman browser, I'm getting 500 error.
I tried with all the types of "Content-type", and I also tried to unmark the content type, it doesn't work...
Errors:
1)
"message": "Failed to parse multipart servlet request; nested exception is javax.servlet.ServletException: org.apache.tomcat.util.http.fileupload.impl.InvalidContentTypeException: the request doesn't contain a multipart/form-data or multipart/mixed stream, content type header is application/json"
(If I unmark the "Content type")
"message": "Content type 'application/octet-stream' not supported"
If I'm inserting "multipart/fromdata"
"message": "Failed to parse multipart servlet request; nested exception is java.io.IOException: org.apache.tomcat.util.http.fileupload.FileUploadException: the request was rejected because no multipart boundary was found"
Yes, and also tried with the boundary in the content type :)
Here is the code of the controller:
package com.package
import com.package.ImageDTO;
import com.package.model.DeletedImageDTO;
import com.package.service.ImageService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
/**
* Control's endPoints for authentication
*/
#CrossOrigin(origins = "*", maxAge = 3600)
#RestController
#RequestMapping("/api")
#RequiredArgsConstructor
#Slf4j
public class ImageController extends ValidationExceptionHandler {
private final ImageService imageService;
#GetMapping("/user/image")
public ResponseEntity<List<ImageDTO>> getUserImages() {
final List<ImagesDTO> allUserImages = imageService.getAllUserImages();
return ResponseEntity.status(HttpStatus.OK).body(allUserImages);
}
#PostMapping(value = "/user/images", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<List<ImageDTO>> saveUserImages(#RequestPart(name = "imageDTOS") ImageDTO[] imageDTOS,
#RequestPart(name = "deletedImageDTOs") DeletedImageDTO[] deletedImageDTOs,
MultipartHttpServletRequest request) throws IOException {
final List<ImageDTO> imageDTOSResponse = imageService.saveUserImages(Arrays.stream(imageDTOS).toList(),
Arrays.stream(deletedImageDTOs).toList(), request);
return ResponseEntity.status(HttpStatus.OK).body(imageDTOSResponse);
}
#DeleteMapping("/user/images/{id}")
public ResponseEntity<Void> deleteUserImage(#PathVariable(name = "id") final Long id) {
imageService.deleteImages(id);
return ResponseEntity.noContent().build();
}
}
The language of the backend is: Java, Spring boot
Everything has been mentioned above.
I have this spring boot app and I'm new to spring boot. I have this controller created but it throws 404 and says No mapping for GET /me in the console. I couldn’t find the issue and I need to get this as soon as possible.
Here's the log: https://pastebin.com/Qf5W6MZU
Any other endpoints in this controller also doesn’t work.
import org.sefglobal.scholarx.exception.BadRequestException;
import org.sefglobal.scholarx.exception.NoContentException;
import org.sefglobal.scholarx.exception.ResourceNotFoundException;
import org.sefglobal.scholarx.exception.UnauthorizedException;
import org.sefglobal.scholarx.model.Mentee;
import org.sefglobal.scholarx.model.Profile;
import org.sefglobal.scholarx.model.Program;
import org.sefglobal.scholarx.service.IntrospectionService;
import org.sefglobal.scholarx.util.EnrolmentState;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CookieValue;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
#RestController
#RequestMapping("/me")
public class AuthUserController {
private final IntrospectionService introspectionService;
public AuthUserController(IntrospectionService introspectionService) {
this.introspectionService = introspectionService;
}
#GetMapping
#ResponseStatus(HttpStatus.OK)
public Profile getLoggedInUser(#CookieValue(value = "profileId", defaultValue = "-1") long profileId)
throws ResourceNotFoundException, UnauthorizedException {
return introspectionService.getLoggedInUser(profileId);
}
#GetMapping("/programs/mentee")
#ResponseStatus(HttpStatus.OK)
public List<Program> getMenteeingPrograms(#CookieValue(value = "profileId") long profileId)
throws ResourceNotFoundException, NoContentException {
return introspectionService.getMenteeingPrograms(profileId);
}
#PutMapping("/mentor/{id}/confirmation")
#ResponseStatus(HttpStatus.OK)
public Mentee confirmMentor(#PathVariable long id,
#CookieValue(value = "profileId") long profileId)
throws ResourceNotFoundException, BadRequestException {
return introspectionService.confirmMentor(id, profileId);
}
}
I found the issue I haven’t included the package.
I have code like this below :
MyExceptionHandler {
#ExceptionHandler(Exception.class)
public Object handleMvc(Exception ex, org.springframework.http.server.ServerHttpRequest request) {
return request.getbody;
}
#ExceptionHandler(Exception.class)
public Object handleReactive(Exception ex, org.springframework.http.server.reactive.ServerHttpRequest request) {
return request.getBody();
}
}
The code above produces IllegalStateException: Ambiguous #ExceptionHandler method mapped for Exception error.
Is there a way to handle both MVC and reactive requests in the ControllerAdvice? I'm working on a common project that works across multiple projects using different httprequest type.
**In spring boot using this**
package com.example.demo.exception;
import java.util.HashMap;
import java.util.Map;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
#ControllerAdvice
public class UserCVExceptionController {
#ExceptionHandler(value = UserCVNotfoundException.class)
public ResponseEntity<Object> exceptionMsg(UserCVNotfoundException exception) {
Map<String, String> headers=new HashMap<String, String>();
headers.put("Status", "404");
headers.put("Message", "User Not found");
headers.put("User", exception.getEmail());
return new ResponseEntity<>(headers, HttpStatus.NOT_FOUND);
}
}
I have a java bean which has both multipart and String data. I am trying to pass it in a rest client call which takes this java bean input and processes it.
Below are my model class, controller and rest client.
On making a call from my rest client , I am getting this exception.
Exception in thread "main" org.springframework.web.client.RestClientException: Could not write request: no suitable HttpMessageConverter found for request type [com.techidiocy.models.NHPdfMergeRequest] and content type [multipart/form-data]
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:810)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:594)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:557)
at org.springframework.web.client.RestTemplate.postForEntity(RestTemplate.java:384)
Model Class
import org.springframework.web.multipart.MultipartFile;
public class Candidate {
private String firstName;
private String lastName;
private MultipartFile resume;
//getters and setters
}
Controller Class
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
#RestController
public class CandidateController {
#Autowired
private CandidateService candidateService;
#RequestMapping(method=RequestMethod.POST, path="/add")
public void add(#RequestBody Candidate request) {
// do some processing
String firstName = request.getFirstName();
String lastName = request.getLastName();
MultipartFile resume = request.getResume();
candidateService.add(firstName, lastName, resume);
}
}
Rest Client
import java.io.File;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockMultipartFile;
import org.springframework.web.client.RestTemplate;
public class CandidateClient {
public static void main(String[] args) throws IOException {
String serverURL = "http://localhost:8080/add";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
Candidate candidate = new Candidate();
candidate.setFirstName("John");
candidate.setLastName("Doe");
candidate.setResume(new MockMultipartFile("tmp.pdf", FileUtils.readFileToByteArray(new File("/home/john/resume/john.pdf"))));
HttpEntity<Candidate> httpEntity = new HttpEntity<Candidate>(candidate, headers);
RestTemplate client = new RestTemplate();
client.postForEntity(serverURL, httpEntity, Resource.class);
}
}
Note: I had also tried to set the header content type as json in rest client and then I am getting all the values as Null in the controller. headers.setContentType(MediaType.APPLICATION_JSON);
I had also searched over the internet for this kind of scenario but I am unable to find a solution for this.
I had also tried to pass all the parameters separately (not as part of java bean) then I am able to make it work.
I have this code :
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
#SpringBootApplication
public class Application {
public static void main(String args[]) {
SpringApplication.run(Application.class);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
RestTemplate restTemplate = builder.rootUri("http://login.xxx.com/").basicAuthorization("user", "pass").build();
return restTemplate;
}
#Bean
public CommandLineRunner run(RestTemplate restTemplate) throws Exception {
return args -> {
restTemplate.getForObject(
"http://login.xxx.com/ws/YY/{id}", YY.class,
"123");
};
}
}
but I'm getting this error :
Caused by: org.springframework.web.client.RestClientException: Could not extract response: no suitable HttpMessageConverter found for response type [class com.xxx.test.YY] and content type [application/xml;charset=ISO-8859-1]
How can I add MediaType.APPLICATION_JSON to the header and add the header to the restTemplate and do getForObject ?
You don't need to add the accept header when using any of the get methods, RestTemplate will do that automatically. If you look inside RestTemplate's constructor, you can see that it automatically checks the classpath and add common message converters. So you may need to check you're classpath (or step into the constructor to see which converters it autodetects.
If you need to add custom headers, like Bearer authentication, you can always use the exchange method to build the request exactly as you like. Here is an example that should work, I have added the Jackson message converter explicetly, so you should get an compilation error if it is not in your classpath.
import java.net.URI;
import java.util.Map;
import com.google.common.collect.Lists;
import org.springframework.http.*;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.client.RestTemplate;
public class Main {
public static void main(String[] args) throws Exception {
RestTemplate template = new RestTemplate();
template.setMessageConverters(Lists.newArrayList(new MappingJackson2HttpMessageConverter()));
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON));
ResponseEntity<Map> forEntity = template.exchange(new RequestEntity<>(null, headers, HttpMethod.GET, new URI("https://docs.tradable.com/v1/accounts")), Map.class);
System.out.println("forEntity.getBody() = " + forEntity.getBody());
}
}