Multipart Request with MultipartFile as Optional Field - Spring MVC - java

I am using Spring MVC on a J2EE Web application.
I have created a method that bounds the request body to a model like the above
#RequestMapping(value = "/", method = RequestMethod.POST, produces = "application/json")
public AModel createEntity(#Valid #ModelAttribute MyInsertForm myInsertForm) {
// coding..
}
Everything are working great and when i include a property of type MultipartFile in the MyEntityForm, then i have to make the request with content type "multipart/form-data".
Also, everything are working great with this scenario too.
The problem i am facing is that i would like to have the MultipartFile property as optional.
When a client request include a file my method works great but when a client request does not include a file spring throws a
HTTP Status 500 - Request processing failed; nested exception is org.springframework.web.multipart.MultipartException: Could not parse multipart servlet request; nested exception is org.apache.commons.fileupload.FileUploadException: Stream ended unexpectedly
Is there any way to solve this issue without creating two methods on my controller (one with a MultipartFile and another without)?

I had the same issue and just adding the required=false worked for me; because, I don't send a file all the time. Please find the sample code below,
#RequestMapping(value = "/", method = RequestMethod.POST, produces = "application/json")
public AModel createEntity(#Valid #ModelAttribute MyInsertForm myInsertForm, #RequestParam(value ="file", required=false) MultipartFile file) {
// coding..
}

Give a try by adding
(required=false)
to multipart property in method signature.

When you wish to send one or more files using HTTP, you have to use multipart request. This means that the body of the request will be like the above,
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="text"
text default
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file1"; filename="a.txt"
Content-Type: text/plain
Content of a.txt.
-----------------------------9051914041544843365972754266
Content-Disposition: form-data; name="file2"; filename="a.html"
Content-Type: text/html
When you wish to send only data (and not files) you can send them as json, key-value pairs etc.
Spring framework uses the #ModelAttribute annotation when you wish to map a multipart request to an object.
When you have a normal key-value request, you use the #RequestBody annotation.
Thus, you can't have the MultipartFile optional, because you have to use different annotations. Using two different methods, one per request type, solves the issue. Example,
#RequestMapping(value = "/withFile", method = RequestMethod.POST, produces = "application/json")
public ReturnModel updateFile(#ModelAttribute RequestModel rm) {
// do something.
}
#RequestMapping(value = "/noFile", method = RequestMethod.PUT, produces = "application/json")
public ReturnModel updateJson(#RequestBody RequestModel rm) {
// do something else.
}

Related

Setting default Content-Type for testing

I'm working on some Pact Contract tests, using RestPactRunner, in the provider side, and I have the following problem. The endpoint return an "application/json" Content-type header when body is present like
ResponseEntity<>(anyValidBody, HttpStatus.OK)
But when no body is present, like this response
ResponseEntity<>(HttpStatus.FORBIDDEN)
an "Content-Type:text/plain" header is sent to the Client by default, even when produces = APPLICATION_JSON_VALUE is present in the method signature (and Contract Test fails because It's expecting an application/json header)
I was looking for a way to set the default content-type header in test level, but didn't found anything useful.
Any ideas? thanks in advance
The response type for an HttpResponse is usually specified in the request mapping
eg:-
#RequestMapping(value = "/url", method = RequestMethod.GET,
produces = "application/json; charset=utf-8")
If its not specified spring would provide defaults in this case spring would provide application/json if any data is present and text/plain when there is not data.There is a method with following signature in which you can provide the headers for the response, you can set the content type using this method
public ResponseEntity(T body, MultiValueMap<String, String> headers, HttpStatus status)

Http Put request with content type application/x-www-form-urlencoded not working in Spring

I have a spring-boot application that the only thing it does is receive http requests.
This is my spring cotroller:
#RestController
public class WebController {
#Autowired
private CallRecording callRecording;
#PutMapping(path = "/cdrpostbox/callrecording/{hostedAccountId}/{mp3FileName}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public ResponseEntity<Object> callRecording(#PathVariable("hostedAccountId") String hostedAccountId, #PathVariable("mp3FileName") String mp3FileName, MultipartFile file) {
return ResponseEntity.status(callRecording.service(hostedAccountId, mp3FileName, file)).body(null);
}
}
I'm using Postman to send the request. The request that my spring application receives can't be changed, because the code is not maintained by my team.
request headers
request body
I found quite a few questions in here about the similar problems, but not quite the same problem that I have to solve. I tried adding and removing #RequestBody , replacing #RequestBody with #RequestParam, I tried the MultiValueMap, but it keeps returning the same error.
java.lang.IllegalArgumentException: URLDecoder: Illegal hex characters in escape (%) pattern - For input string: "x"
I can't even debug the code because it fails before it reaches the controller.
What am I missing?
The content-type in the header is urlencoded, but you send binary data.
--- edit
If you want to get the file as a MultipartFile, then send the file as a form-data from postman, set the name of the data to file and add #RequestParam to the file argument in your method.
After a bit more investigation, I found the solution.
#RestController
#EnableWebMvc
public class WebController {
#Autowired
private CallRecording callRecording;
#PutMapping(path = "/cdrpostbox/callrecording/{hostedAccountId}/{mp3FileName:.+}")
public ResponseEntity<Object> callRecording(#PathVariable("hostedAccountId") String hostedAccountId, #PathVariable("mp3FileName") String mp3FileName, #RequestBody byte[] requestBody) {
return ResponseEntity.status(callRecording.service(hostedAccountId, mp3FileName, requestBody)).body(null);
}
It was missing the #EnableWebMvc. I also changed the mapping {mp3FileName:.+} because it was truncating the end of the request, you can check the SO question here .
Thanks #Selindek for the help

POST doesn't work Spring java

I have an web application and I'm trying to creat a simple POSt method that will have a value inside the body request:
#RequestMapping(value = "/cachettl", method = RequestMethod.POST)
#CrossOrigin(origins = "http://localhost:3000")
public #ResponseBody String updateTtl(#RequestBody long ttl) {
/////Code
}
My request which I call from some rest client is:
POST
http://localhost:8080/cachettl
Body:
{
"ttl": 5
}
In the response I get 403 error "THE TYPE OF THE RESPONSE BODY IS UNKNOWN
The server did not provide the mandatory "Content-type" header."
Why is that happening? I mention that other GET requests are working perfectly.
Thanks!
Edit:
When I tried it with postman the error message I got is "Invalid CORS request".
Spring application just doesn't know how to parse your message's body.
You should provide "header" for your POST request to tell Spring how to parse it.
"Content-type: application/json" in your case.
You can read more about http methods here: https://developer.mozilla.org/en-US/docs/Learn/HTML/Forms/Sending_and_retrieving_form_data
Updated:
Just in case of debug, remove useless annotations to test only POST mechanism. Also, change types of arg and return type. And try to use case-sensitive header.
#RequestMapping(value = "/cachettl", method = RequestMethod.POST)
public void updateTtl(#RequestBody String ttl) {
System.out.println("i'm working");
}
Since the error is about the response type, you should consider adding a produces attribute, i.e :
#RequestMapping(value = "/cachettl", method = RequestMethod.POST, produces=MediaType.APPLICATION_JSON_VALUE)
Since you are also consuming JSON, adding a consumes attribute won't hurt either :
#RequestMapping(value = "/cachettl", method = RequestMethod.POST, consumes=MediaType.APPLICATION_JSON_VALUE, produces=MediaType.APPLICATION_JSON_VALUE)
The error message is slightly misleading. Your server code is not being hit due an authentication error.
Since you say spring-security is not in play then I suspect you're being bounced by a CORS violation maybe due to a request method restriction. The response body generated by this failure (if any at all) is automatic and will not be of the application/json type hence the client failure. I suspect if you hit the endpoint with something that doesn't care for CORS such as curl then it will work.
Does your browser REST client allow you to introspect the CORS preflight requests to see what it's asking for?

Content-type and #ResponseBody in spring

It could be very simple but it will be very helpful for me to understand...
I used #ResponseBody in my restcontroller to return String value to browser. The response string is successfully received in browser.
ie:
#RequestMapping(value="/foo", method=RequestMethod.GET)
#ResponseBody
public String foo() {
return "bar";
}
What is the content-type of above response? If this is going to be like writing setAttribute in servlet response what could the attribute name?
If the browser accept only "application/json" how spring will treat the response?
Submitted code produces text/html, as do all mapped Controller methods by default. If you want to produce application/json, you have to change your RequestMapping to
#RequestMapping(value="/foo", method=RequestMethod.GET, produces = "application/json")
However this is not a valid Json String, you would have to change it because the method you submitted would return empty body. The submitted example would be valid text/plain.
When the request contains header "Accept: application/json" and other content type is returned, Spring returns Json-type response explaining that HttpMediaTypeNotAcceptableException was thrown.
Regarding the servlet analogy - please explain, I don't fully understand what you mean. The String is returned as response body, it's very different from request attributes. What would you like to achieve?
I assume the content type will be plain/text. If the request sets accept to "application/json" it depends on your browser/tool. Most rest clients won't display it as it is not application/json. If you invoke the API directly I would assume it is displayed due to browser content sniffing (can be disabled via a header).

Spring controller - logging request and response body

I have a spring application which exchanges JSON with the mobile.
Spring controller looks like this:
#RequestMapping(value = "/register", method = RequestMethod.POST, headers = {"Content-type=application/json"})
public String register(#RequestBody #Valid UserRegistrationRequest urf, BindingResult bindingResult) {
return toJson(someResponse);
}
I wonder, what is the best way to log http request body and response body?
At the moment, I have custom json message converter and it logs a request body, before creating a bean out of json. and I use CustomTraceInterceptor to log a response body. Unfortunately, CustomTraceInterceptor doesn't allow to log request body.
Any advice for better solutions would be highly appreciated!
Thank you in advance.
Extend HandlerInterceptorAdapter, and override postHandle. Which has request and response injected into it.
You can also use new HttpServletResponseWrapper((HttpServletResponse) response) which has a more friendly api, and spring probably has even nicer wrapper as well ...

Categories

Resources