Adding #ModelAttribute results in 400 (Bad Request) in Delete Request - spring

I can submit a delete request fine with the following:
#RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public ResponseEntity<Result> deleteTest(#PathVariable String id) {
return new ResponseEntity<>(Result.Success("Hi " + id + "!!!", null), HttpStatus.OK);
}
However, when I add an #ModelAttribute variable, I get 400 (Bad Request) as the http response code:
#RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
public ResponseEntity<Result> deleteTest(#PathVariable String id, #ModelAttribute("authUser") User authUser) {
return new ResponseEntity<>(Result.Success("Hi " + id + "!!!", null), HttpStatus.OK);
}
This #ModelAttribute is working fine with a put request handler I have in my #RestController but not in this delete request.
Here's the #ModelAttribute code:
#ModelAttribute("authUser")
public User authUser(#AuthenticationPrincipal SpringAuthUser springAuthUser) throws Exception {
User user = ConstantsHome.userprofileMgr.getUserByUserId(springAuthUser.getUsername(), true, true);
user.updateRights(null);
request.getSession().setAttribute(ConstantsHome.USEROBJECT_KEY, user);
return user;
}
Why would adding #ModelAttribute cause a delete request to return a 400 (Bad Request) http response?
I'm using spring-web-4.1.4 & spring-security-4.0.3

I digged a little and found that specifying a #PathVariable of "id" somehow attaches it to the #ModelAttribute variable (as a Long(!) instead of a String as I specified). I then came across this post that lead me to different ways to resolve the issue :
Values of #PathVariable and #ModelAttribute overlapping.
Ended up with this as a method declaration (replaced "id" with "userId"):
#RequestMapping(value = "/{userId}", method = RequestMethod.DELETE)
public ResponseEntity<Result> deleteUser(#PathVariable String userId,
#ModelAttribute("authUser") User authUser) {
...
}
Hopefully this will help someone else quickly that might run into this issue instead of spending a day trying to figure it out...

Related

Spring POST method not supported

My spring application has GET Method working. Any try of creating POST method ends with the error below:
org.springframework.web.servlet.PageNotFound.handleHttpRequestMethodNotSupported Request method 'POST' not supported.
Now I'm trying to create request as simple as it can.
#RequestMapping(value="/post/", method = RequestMethod.POST)
public ResponseEntity<String> newReport(#RequestBody String aa) {
System.out.println(aa);
return new ResponseEntity<String>("User created", HttpStatus.CREATED);
}
my controller
#CrossOrigin("*")
#RestController
#RequestMapping({"/api"})
public class ReportsController
I've checked many threads of this problem, but none solves it.
You must delete '/' from the end of the service name:
#RequestMapping(value="/post", method = RequestMethod.POST)
public ResponseEntity<String> newReport(#RequestBody String aa) {
System.out.println(aa);
return new ResponseEntity<String>("User created", HttpStatus.CREATED);
}

Spring WebFlux + thymeleaf: Post request redirect Get page returns the 303 see other status

I just used SpringBoot + WebFlux + thymeleaf to write the controller.
#RequestMapping(value = "/create", method = RequestMethod.GET)
public String createCityForm(Model model) {
model.addAttribute("city", new City());
model.addAttribute("action", "create");
return CITY_FORM_PATH_NAME;
}
#RequestMapping(value = "/create", method = RequestMethod.POST)
public String postCity(#ModelAttribute City city) {
cityService.saveCity(city);
return REDIRECT_TO_CITY_URL;
}
I witre thymeleaf page to receive the form, and redirect/return the get method page, But the browser give the 303 see other status.
Also, the delete resources also doesn't work.
The SEE_OTHER status is actually the default status of the RedirectView when invoked without explicitly specifying the HTTP code (like the ThymeleafReactiveViewResolver does).
If you want to override this status, return the RedirectView directly instead of letting Thymeleaf do it when it matches the redirect: pattern in the view name:
#RequestMapping(value = "/create", method = RequestMethod.GET)
public RedirectView createCityForm(Model model) {
model.addAttribute("city", new City());
model.addAttribute("action", "create");
return new RedirectView("/target_url", HttpStatus.MOVED_PERMANENTLY);
}

How to keep request parameters after redirect?

I'm trying to resolve a bug when I send a form with an empty input.
This is my methode:
#RequestMapping(value = "/modifier.html", method = RequestMethod.POST)
public String modifier(ModelMap map, #ModelAttribute("FormObject") FormObject formObject, BindingResult result, HttpServletRequest req) {
formObject.setModif(true);
String idParam = req.getParameter("idTypeOuverture");
if (result.hasErrors()) {
return "redirect:/gestion.html?section=Configuration&panel=4&ouvrir=modifier";
} else {
//Instructions
}
When there are errors (empty input) the controller redirects to this link to tell user to correct errors. The problem is when I check parameters here they look correct (id, name ...), but the id becomes null in the following method:
#Override
public ModelAndView dispatcher(HttpServletRequest request, HttpServletResponse response) throws RorException {
Map<String, Object> myModel = (Map<String, Object>) request.getAttribute(EnumParam.R_MY_MODEL.getKey());
Enumeration<?> keys = request.getParameterNames();
while (keys.hasMoreElements()) {
String paramName = (String) keys.nextElement();
String value = request.getParameter(paramName);
myModel.put(paramName, value);
}
GlobalSession globalSession = (GlobalSession) getApplicationContext().getBean(Utilities.GLOBALSESSION_BEAN_REF);
myModel.put("module", globalSession.getModule().getKeyMessage());
String section = request.getParameter("section");
// This instruction returns null
String idForm = request.getParameter("id");
id = Integer.parseInt(idForm);
// This instruction returns NumberFormatException
ObjectForm of = getForm(id);
// ...
}
Well, I don't know why parameter id changed after redericting? do you have any idea? I tried to redifine parameters in the first method but still got the same NFE.
Thank you in advance.
Thank you
Although the previous answer is accepted, I am adding this answer just for your information.
You can also use RedirectAttributes with and without FlashAttributes also Before issuing redirect, post method should take RedirectAttributes as argument These attributes will be passed as request parameters Look at my code example and see if its helpful.
Way 1 :
#RequestMapping(value={"/requestInfo.html"}, method=RequestMethod.POST)
public String requestInfoPost1(
#ModelAttribute("requestInfoData") RequestInfoData requestInfoData,
BindingResult result,
RedirectAttributes redirectAttributes,
SessionStatus status
) {
// some logic
redirectAttributes.addAttribute("name", requestInfoData.getName());
redirectAttributes.addAttribute("age", requestInfoData.getAge());
// some logic
return "redirect:requestInfoSuccessRedirect";
}
#RequestMapping("requestInfoSuccessRedirect")
public String requestInfoSuccessRedirect()
{
return "requestInfoSuccess";
}
Way 2:
Whatever data is added in flash attribute will be added in session It will be in session only till redirect is successful On redirect, data is retrieved from session and added to Model for new Request. Only after redirect is successful, data is removed
#RequestMapping(value={"/requestInfo.htm"}, method=RequestMethod.POST)
public String requestInfoPost(
#ModelAttribute("requestInfoData") RequestInfoData requestInfoData,
BindingResult result,
RedirectAttributes redirectAttributes,
SessionStatus status
) {
// some logic
redirectAttributes.addFlashAttribute("requestInfoData",
requestInfoData);
// some logic
return "redirect:requestInfoSuccessRedirect";
}
#RequestMapping("requestInfoSuccessRedirect")
public String requestInfoSuccessRedirect()
{
return "requestInfoSuccess";
}
The request parameter is only for one request.
You make a redirect, it means that you make another new "request".
You should add it to the redirect:
return "redirect:/gestion.html?section=Configuration&panel=4&ouvrir=modifier&idTypeOuverture="+idParam;

Spring MVC - #RequestMapping GET and POST #RequestMethod

I understand this question has been asked previously, I am learning Spring following along Spring Petclinic Sample project. There is no problem with processCreationForm, when a redirect is done to showOwner using GET it works as expected, but when I experiment it by using POST it throws HTTP Status 405 - Request method 'GET' not supported. Is it because processCreationForm is doing a redirect to showOwner I am unable to grab it as POST request?
#RequestMapping(value = "/owners/new", method = RequestMethod.POST)
public String processCreationForm(#Valid Owner owner,
BindingResult result) {
if(result.hasErrors()) {
return "owners/ownerForm";
} else {
this.clinicService.saveOwner(owner);
return "redirect:/owners/" + owner.getId();
}
}
#RequestMapping(value = "/owners/{ownerId}", method = RequestMethod.POST)
public ModelAndView showOwner(#PathVariable("ownerId") int ownerId) {
ModelAndView mav = new ModelAndView("owners/ownerDetails");
mav.addObject(this.clinicService.findOwnerById(ownerId));
return mav;
}
Any helpful comments are appreciated.
You're redirecting to /owners/{ownerId} url, but you didn't define a GET handler for that endpoint, hence Spring MVC complains with:
HTTP Status 405 - Request method 'GET' not supported.
Using RequestMethod.GET will solve your problem:
#RequestMapping(value = "/owners/{ownerId}", method = RequestMethod.GET)
public ModelAndView showOwner(#PathVariable("ownerId") int ownerId) { ... }
Is it because processCreationForm is doing a redirect to showOwner I
am unable to grab it as POST request?
Since your POST handler on /owners/new is redirecting to /owners/{ownerId}, does not mean that redirection will be a POST request. Redirections are always GET requests.

When use ResponseEntity<T> and #RestController for Spring RESTful applications

I am working with Spring Framework 4.0.7, together with MVC and Rest
I can work in peace with:
#Controller
ResponseEntity<T>
For example:
#Controller
#RequestMapping("/person")
#Profile("responseentity")
public class PersonRestResponseEntityController {
With the method (just to create)
#RequestMapping(value="/", method=RequestMethod.POST)
public ResponseEntity<Void> createPerson(#RequestBody Person person, UriComponentsBuilder ucb){
logger.info("PersonRestResponseEntityController - createPerson");
if(person==null)
logger.error("person is null!!!");
else
logger.info("{}", person.toString());
personMapRepository.savePerson(person);
HttpHeaders headers = new HttpHeaders();
headers.add("1", "uno");
//http://localhost:8080/spring-utility/person/1
headers.setLocation(ucb.path("/person/{id}").buildAndExpand(person.getId()).toUri());
return new ResponseEntity<>(headers, HttpStatus.CREATED);
}
to return something
#RequestMapping(value="/{id}", method=RequestMethod.GET)
public ResponseEntity<Person> getPerson(#PathVariable Integer id){
logger.info("PersonRestResponseEntityController - getPerson - id: {}", id);
Person person = personMapRepository.findPerson(id);
return new ResponseEntity<>(person, HttpStatus.FOUND);
}
Works fine
I can do the same with:
#RestController (I know it is the same than #Controller + #ResponseBody)
#ResponseStatus
For example:
#RestController
#RequestMapping("/person")
#Profile("restcontroller")
public class PersonRestController {
With the method (just to create)
#RequestMapping(value="/", method=RequestMethod.POST)
#ResponseStatus(HttpStatus.CREATED)
public void createPerson(#RequestBody Person person, HttpServletRequest request, HttpServletResponse response){
logger.info("PersonRestController - createPerson");
if(person==null)
logger.error("person is null!!!");
else
logger.info("{}", person.toString());
personMapRepository.savePerson(person);
response.setHeader("1", "uno");
//http://localhost:8080/spring-utility/person/1
response.setHeader("Location", request.getRequestURL().append(person.getId()).toString());
}
to return something
#RequestMapping(value="/{id}", method=RequestMethod.GET)
#ResponseStatus(HttpStatus.FOUND)
public Person getPerson(#PathVariable Integer id){
logger.info("PersonRestController - getPerson - id: {}", id);
Person person = personMapRepository.findPerson(id);
return person;
}
My questions are:
when for a solid reason or specific scenario one option must be used mandatorily over the other
If (1) does not matter, what approach is suggested and why.
ResponseEntity is meant to represent the entire HTTP response. You can control anything that goes into it: status code, headers, and body.
#ResponseBody is a marker for the HTTP response body and #ResponseStatus declares the status code of the HTTP response.
#ResponseStatus isn't very flexible. It marks the entire method so you have to be sure that your handler method will always behave the same way. And you still can't set the headers. You'd need the HttpServletResponse.
Basically, ResponseEntity lets you do more.
To complete the answer from Sotorios Delimanolis.
It's true that ResponseEntity gives you more flexibility but in most cases you won't need it and you'll end up with these ResponseEntity everywhere in your controller thus making it difficult to read and understand.
If you want to handle special cases like errors (Not Found, Conflict, etc.), you can add a HandlerExceptionResolver to your Spring configuration. So in your code, you just throw a specific exception (NotFoundException for instance) and decide what to do in your Handler (setting the HTTP status to 404), making the Controller code more clear.
According to official documentation: Creating REST Controllers with the #RestController annotation
#RestController is a stereotype annotation that combines #ResponseBody
and #Controller. More than that, it gives more meaning to your
Controller and also may carry additional semantics in future releases
of the framework.
It seems that it's best to use #RestController for clarity, but you can also combine it with ResponseEntity for flexibility when needed (According to official tutorial and the code here and my question to confirm that).
For example:
#RestController
public class MyController {
#GetMapping(path = "/test")
#ResponseStatus(HttpStatus.OK)
public User test() {
User user = new User();
user.setName("Name 1");
return user;
}
}
is the same as:
#RestController
public class MyController {
#GetMapping(path = "/test")
public ResponseEntity<User> test() {
User user = new User();
user.setName("Name 1");
HttpHeaders responseHeaders = new HttpHeaders();
// ...
return new ResponseEntity<>(user, responseHeaders, HttpStatus.OK);
}
}
This way, you can define ResponseEntity only when needed.
Update
You can use this:
return ResponseEntity.ok().headers(responseHeaders).body(user);
A proper REST API should have below components in response
Status Code
Response Body
Location to the resource which was altered(for example, if a resource was created, client would be interested to know the url of that location)
The main purpose of ResponseEntity was to provide the option 3, rest options could be achieved without ResponseEntity.
So if you want to provide the location of resource then using ResponseEntity would be better else it can be avoided.
Consider an example where a API is modified to provide all the options mentioned
// Step 1 - Without any options provided
#RequestMapping(value="/{id}", method=RequestMethod.GET)
public #ResponseBody Spittle spittleById(#PathVariable long id) {
return spittleRepository.findOne(id);
}
// Step 2- We need to handle exception scenarios, as step 1 only caters happy path.
#ExceptionHandler(SpittleNotFoundException.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
public Error spittleNotFound(SpittleNotFoundException e) {
long spittleId = e.getSpittleId();
return new Error(4, "Spittle [" + spittleId + "] not found");
}
// Step 3 - Now we will alter the service method, **if you want to provide location**
#RequestMapping(
method=RequestMethod.POST
consumes="application/json")
public ResponseEntity<Spittle> saveSpittle(
#RequestBody Spittle spittle,
UriComponentsBuilder ucb) {
Spittle spittle = spittleRepository.save(spittle);
HttpHeaders headers = new HttpHeaders();
URI locationUri =
ucb.path("/spittles/")
.path(String.valueOf(spittle.getId()))
.build()
.toUri();
headers.setLocation(locationUri);
ResponseEntity<Spittle> responseEntity =
new ResponseEntity<Spittle>(
spittle, headers, HttpStatus.CREATED)
return responseEntity;
}
// Step4 - If you are not interested to provide the url location, you can omit ResponseEntity and go with
#RequestMapping(
method=RequestMethod.POST
consumes="application/json")
#ResponseStatus(HttpStatus.CREATED)
public Spittle saveSpittle(#RequestBody Spittle spittle) {
return spittleRepository.save(spittle);
}

Resources