I have a simple Spring program, the backend is Spring MVC with Restful web service, the front end is pure HTML + ajax.
My problem is, when I try to use the following to map a HTTP request params to a pojo, it always fails:
#RequestMapping(value = "/books", method = RequestMethod.PUT)
public #ResponseBody
String updateBook(BookInfo book)
Here I use PUT method, because it's a modification operation. There's no exception, but I get nothing injected into book object.
With same HTTP request parameters, if I change the method to POST, and client send it via a POST, it would be success:
#RequestMapping(value = "/books", method = RequestMethod.POST)
public ResponseEntity<String> addBook(BookInfo book)
This time book will always get the filled.
Why there's difference between PUT and POST? or it's the issue of return type? (one is ResponseBody, the other is ResponseEntity)? Or, if you use PUT, then the pojo must be in persistent context?
How should I investigate the issue?
I think its not the problem with your configuration or code.
In Spring Framework there is a filter provided named HiddenHttpMethodFilter that serves all the method but initially it will do the POST request but with a hidden _method form field. And this filter reads this hidden field value and then changes the method value accordingly. Please refere this link to know more about this. I think configuring with this filter will resolve your problem.
Hope this helps you. Cheers.
Related
#RequestMapping(value = "/something", method = RequestMethod.POST, produces = "application/json")
public void triggerInvitations(#RequestBody String postBody)
Which Spring class does the actual task of reading the input stream and setting the request body in the parameter "postBody"?
Actually, I'm just getting the response status as 400 Bad Request and unable to see the actual cause. As such, I wanted to debug the Spring code and know exactly the reason why its a bad request. (If I don't have a #RequestBody parameter in the controller signature, control is able to reach the method)
I'm using Spring Boot 1.5.8
There are different HTTP Message converters used by Spring. This article will give you an overview of this and also list different message converters.
Turn on your springframework debugging in your application.properties:
logging.level.org.springframework = DEBUG
It should start to show you why it's not matching the request to your RequestMapping.
Most of my experience with creating controllers with Spring are for REST controllers that consume JSON formatted requests. I've been searching for documentation on how to do testing for form submission, and so far this is how I understand it should go using MockMvc:
MvcResult result = mockMvc.perform(post("/submit")
.param('title', 'test title')
.param('description', 'test description'))
.andReturn()
However, I'm not sure how to map the form parameters to a model object. I've seen the #ModelAttribute annotation pop up in my searches but I can't figure out how it should be used for mapping. In addition, this quick start guide from the official documentation does not elaborate on how things like th:object and th:field translate to HTML and subsequently to URL encoded form.
I have my controller code similar to the following:
#PostMapping('/submit')
def submit(#ModelAttribute WriteUp writeUp) {
//do something with writeUp object
'result'
}
I discovered through trial and error that my specific problem might have been Groovy specific. There test code and the controller code, it turns out, have no issues. To reiterate, for testing form submission, use the param method through perform method of MockMvcRequestBuilders. Another thing to note is that this doesn't seem to work if content type is not specified. Here's a sample test code that works for me:
MvcResult result = webApp.perform(post("/submit")
.contentType(APPLICATION_FORM_URLENCODED) //from MediaType
.param('title', 'test title')
.param('description', 'test description'))
.andReturn()
As you can see, it's not much different from what I posted originally. The controller code is pretty much just the same, with #ModelAttribute working just fine.
The problem with my setup though was that since I was using Groovy, I assumed that getters and setters were automatically generated in my WriteUp class. Here's how the WriteUp class looked originally:
class WriteUp {
private String title
private String description
}
I haven't written code in Groovy for a while, and the last time I did, classes like the one above can be assumed to have getters and setters implicitly. However, it turns out that is not the case. To solve my specific issue, I updated the access modifier in the fields to be default (package level):
class WriteUp {
String title
String description
}
I've seen the #ModelAttribute annotation pop up in my searches but I
can't figure out how it should be used for mapping.
When you mark your writeUp object with #ModelAttribute, then the Spring container populates the parameters (like title, description, etc..) from HttpServletRequest object & injects the object to the controller method, when the request comes to the server from the client (could be a Browser or MockMvc unit test client or anything else).
Also, few other basic points for your quick understanding:
(1) Controller methods are mapped to an URI and RequestMethod (like POST/GET/DELETE/PUT et..) like shown below:
#RequestMapping(value="/submit", method=RequestMethod.POST)
public String submit(#ModelAttribute WriteUp writeUp) {
//Call the service and Save the details
model.addAttribute("Writeup details added successfully");
return "writeUpResult"; //Returns to the View (JSP)
}
(2) #ModelAttribute will be mapped to an object (like your writeUp) for http POST/PUT requests where the html formd data is part of http body.
(3) #RequestParam or #PathParam will be used for http GET requests where the parameters are part of URL (i.e., not part of http body).
You can look here for understanding the DispatcherServlet request handling & Spring MVC basic web flow.
In my current Spring Boot application i seem to hit a wall when trying to implement a REST request filter. My goal with the request filter was to read the header and body part and validate the incoming data and check if it meets the HMAC construction we are using.
So the request filter seemed not to work an alternative solutions is to use #ControllerAdvice.
Then the request validation can be implemented very easy. But i am not sure if it normally seen as an incorrect usage of the #ControllerAdvice annotation.
#ControllerAdvice
public class GenericWebControllerAdvice {
#ModelAttribute
public void authenticationFilter(#RequestHeader(value = "Authorization") String authHeader, #RequestBody String payload) {
// process authentication based on header info and body content
// calculate the hash and check if meets the security settings
// if the hash fails throw an exception that returns a http status code
}
}
Any comments on the solution or alternatives that are better?
No you should do the validation in the controller (ie method with #RequestMapping).
Spring supports JSR 303/349 bean validation. Thus if your request body is a POJO and you have the correct annotation Spring will automatically do the validation for you. There is a tutorial of that here:
http://www.leveluplunch.com/java/tutorials/017-validate-spring-rest-webservice-request/
As for request parameter validation (ie not bean validation) I have had to make my own transfer objects and exception handling. How you do global exception handling is covered in the Spring Reference guide but generally you extend and/or register a org.springframework.web.servlet.handler.SimpleMappingExceptionResolver. Ironically #ControllerAdvice can be used for exception handling but I find it better to extend and register an Exception Resolver. More info can be found here:
https://spring.io/blog/2013/11/01/exception-handling-in-spring-mvc
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#mvc-exceptionhandlers
Edit based on OP comments and edits:
If your doing authentication or some other request based validation/authorization its probably best to use an Interceptor. Reference doc. #ControllerAdvice will probably not work as the request handling is too far a long. That is you want something before databinding happens.
Hi I need to pass a json object in POST request of the SPRING DATA REST. Is it possible to pass directly and make it process with save(iterable) with any Jackson script or we have to use a Controller with #RequestBodyand process the Iterableand save it using repository function??
Now I am doing,
#RequestMapping(value = "batchInsert", method = RequestMethod.POST)
#ResponseBody
public String batchInsert(#RequestBody List<Test> test){
testRepo.save(test);
return "loaded";
}
and implements Serilizable in DAO objectand my doubt whether there is any default format to pass whole json without using any controller as CRUD operates normally. Please help me find solution. Am new to springs and I am unable to use the same url to get request in spring-data-rest API, if I use batchInsertin controller and in rest api. Fortunate to use different api calls now for inserting and searching purpose. Thanks in advance.
Have you tried specifying the consumable media type?
#RequestMapping(value = "batchInsert", method = RequestMethod.POST, consumes="application/json")
It's quite common to pass JSON objects to Spring controllers, so it should work...
I'm working on simple Spring-MVC application and I love new Spring REST features. I'd like to use the same method to process regular form and JSON data. It seems to be a little tricky, however. For example, method
public #ResponseBody String process(#RequestBody Bean bean);
will work for JSON request (Content-type: application/json), and
public #ResponseBody String process(Bean bean);
will match request with Content-type: application/x-www-form-urlencoded.
These methods are obviously will have almost the same content, so I'd prefer to avoid such duplication. With Jersey it's possible with #Consumes annotations, but I can't figure out how to do it with Spring.
First, the above declaration won't compile, because you are having duplicate signature.
Btw, #Consumes wouldn't help, I think, because it only designates what content type the method can handle.
In spring you can specify the content-type with
#RequestMapping(headers="Content-Type=application/json")
Just add #RestController annotation for the controller class.