Configuring spring.http.multipart.max-file-size ignores size or returns stream closed - spring

I have a spring-boot application version 1.3.3.RELEASE running Spring 4.2.5.RELEASE
Below I have some of the items I have tried to resolve this. My questions:
Do I need to define my own multipart resolver?
Why is Spring/Tomcat not picking up my multipart settings?
I am trying to accept Multipart files over 1MB. I have configured what appears to be the appropriate config
# Spring http settings
spring.http.multipart.max-file-size=11MB
spring.http.multipart.max-request-size=11MB
Unfortunately this does not seem to have any affect:
org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeLimitExceededException:
The field files exceeds its maximum permitted size of 1048576 bytes.
It doesn't seem to be making it past Tomcat at this point. After a little research I tried to add my own multipart resolver.
#Bean
public CommonsMultipartResolver multipartResolver() {
CommonsMultipartResolver multipartResolver = new CommonsMultipartResolver();
multipartResolver.setMaxUploadSize(10 * 1024 * 1024);
multipartResolver.setMaxUploadSizePerFile(10 * 1024 * 1024);
return new CommonsMultipartResolver();
}
This returns a new error of which has me stumped:
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 closed
My request looks like this:
curl -X POST \
/application/75dc9981-61ac-4fa0-b316-790b764230a3/pending \
-H 'cache-control: no-cache' \
-H 'content-type: multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW' \
-F files=#/Users/paulscoder/Documents/sticky_phalcon_header.gif
And my Request mapping looks like this:
#RequestMapping(
value = "/application/{applicationId}/pending",
method = POST)
public ResponseEntity<?> uploadFiles(
#PathVariable("applicationId") UUID applicationId,
#RequestParam(value = "docId", required = false) String docId,
#RequestParam(value = "type", required = false) String type,
#RequestParam(value = "files") MultipartFile[] files) throws Exception {...

Related

x-www-form-urlencoded Array inconsistently populated in Spring REST call

I am attempting to send a PUT request to a Rest API using x-www-form-urlencoded content. My aim is to send a list of strings in the request similar to this article. I have the following REST controller defined in a Spring Boot application to allow for this:
#RestController
#RequestMapping(value = "/rest/api", produces = MediaType.APPLICATION_JSON_VALUE)
public class RestApiController {
#PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_JSON_VALUE)
private ReturnType putRestApiTypeJson(
#PathVariable("id") String id,
#ModelAttribute PutDataRequest request) {
System.out.println();
return null;
}
#PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
private ReturnType putRestApiTypeUrlEncoded(
#PathVariable("id") String id,
#ModelAttribute PutDataRequest request) {
System.out.println();
return null;
}
}
which leverages PutDataRequest defined by:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class PutDataRequest {
Set<String> characters = new HashMap<>();
Set<String> movies = new HashMap<>();
}
I try hitting the rest api controller via curl to perform testing. The Application JSON PUT request receives characters and movies no problem, however the form-urlencoded endpoint does so inconsistently:
// No data populated in PutDataRequest at debug time:
curl -X PUT 'http://localhost:some-port/rest/api' -d 'characters=Some%20Name%26movies=Some%20Title' -H 'Content-Type: application/x-www-form-urlencoded'
// Data populated in PutDataRequest at debug time:
curl -X PUT 'http://localhost:some-port/rest/api?characters=Some%20Name%26movies=Some%20Title' -H 'Content-Type: application/x-www-form-urlencoded'
Can anyone give an insight on why providing the key-value pairs via -d prevents the data from being forwarded to the form-urlencoded PUT endpoint? For context, I run this coded using spring version 5.2.3.RELEASE and spring boot version 2.2.4.RELEASE.
I decided to sidestep Spring in this situation. Instead of relying on Spring to figure out how to marshal the data I wanted, I added a HttpServletRequest to the form-urlencoded method signature and pulled the data out of the request:
#PutMapping(value = "/{id}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
private ReturnType putRestApiTypeUrlEncoded(
#PathVariable("id") String id,
#ModelAttribute PutDataRequest data,
HttpServletRequest request) {
String body = request.getRequest().lines()
.map(line -> URLDecoder.decode(line, Charset.defaultCharset()))
.collect(Collectors.joining(System.lineSeparator()));
// manipulate body content to extract desired data
}
I was inspired to do the above by this answer.
Also found another way to get around this error. Turns out PUT and DELETE requests aren't enabled by default, and you need to add an implementation for the formContentFilter method in your Application.java (or wherever you call SpringApplication.run(...) )
Once I added the following to Application.java, I ran again and it worked like magic:
#Bean
#ConditionalOnMissingBean(org.springframework.web.filter.FormContentFilter.class)
#ConditionalOnProperty(prefix="spring.mvc.formcontent.filter", name="enabled", matchIfMissing=true)
public OrderedFormContentFilter formContentFilter() {
return new OrderedFormContentFilter();
}

File-upload fails when called via a service i.e restTemplate.postForEntity

I have below springboot rest end point to upload files.
#RequestMapping(
method = RequestMethod.POST,
value = "/v1/file-upload",
consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Response> uploadFile (
#RequestParam(value = "multipartFile") MultipartFile multipartFile) throws IOException {
String str = storeFile(multipartFile);
return new ResponseEntity<>(new Response("successfully uploaded with name "+str), HttpStatus.OK);
}
and properties as below
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=30MB
this API when called via postman, working fine.
but when called via restTemplate from a service, it's throwing
{"timestamp":"2020-05-26T09:17:46.369+0000","status":500,"error":"Internal Server Error","message":"Maximum upload size exceeded; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.impl.FileSizeLimitExceededException: The field multipartFile exceeds its maximum permitted size of 1048576 bytes.","path":"/43c0800d-b992-45b6-8d25-9e81115539d0/Form/files/mock/api/v1/file-upload"}
exception.
my service calls as below
apiCallResponseObj = restClientUtil.postEntity(serviceUrl, Object.class, apiEndPoint.getFormData(), headers);
apiEndPoint.getFormData() has the multipart file data.
my question is, why am I getting an exception when called via a service?
using springboot 2.1.13
the issue was with api-gateway (kong) where the limitations were put.
the configurations given in the question works fine.

In Spring Boot - Can you make request mapping that receives the whole message body without any parsing/intervention?

I need to have a method that does not regard/parse the content of request message, just ... pass it along as input parameter to the #PostMapping method.
Is it possible? Because defining parameters like:
#RequestBody byte[] data
or
#RequestBody String text
tell the framework that it suppose to get some xml/json. and I want it to receive plain text + utf-8 encoding.
Some code to clarify:
#RestController
#RequestMapping(path="/abc", method = RequestMethod.POST)
public class NlpController {
#PostMapping(path="/def", consumes="text/plain; charset: UTF-8", produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> processText(#RequestBody String text)
{
...
return ResponseEntity.ok().body(object);
}
}
Trying also:
#RestController
#RequestMapping(path="/abc", method = RequestMethod.POST)
public class NlpController {
#PostMapping(path="/nlp", consumes=MediaType.APPLICATION_JSON_UTF8_VALUE, produces=MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Object> process(HttpServletRequest request)
{
....
return ResponseEntity.ok().body(article);
}
}
But I get 406 response...
using curl:
curl -v -s -X POST -H "Content-Type:" -H "Content-Type: application/json; charset: utf-8" --data-binary #article.txt localhost:8080/abc/def/
I think you should inject HttpServletRequest as controller method attribute, then you will have acces to request payload.
#PostMapping(path="/something")
public ResponseEntity<Object> processText(HttpServletRequest request) {
// do something with request
}
More info.
406 Not Acceptable
The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request.
https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/406
If i understand your question correctly you need to load text file directly as input param in spring boot rest call.
You need to modify your code and curl request , please use fllowing code as referance .
#RequestMapping(value = "/abc", method = RequestMethod.POST)
public String ResponseEntity<Object> processText(#RequestParam("file")
MultipartFile file) {
System.out.println("---------loading file----------");
/// Calculation and your logic
return ResponseEntity.ok().body(article);
}
Curl request :
curl -X POST localhost:8080/abc -F "file=#article.txt"
One more issue i can see in your curl request your mapping is abc and you are calling
localhost:8080/abc/def/
Using #RequestParam for multipartfile is a right way?
If using data in memory following code will work for you
#PostMapping(value = "/abc", consumes = "application/json", produces =
"application/json")
ResponseEntity<Object> processText( #RequestBody String input)
throws JSONException {
//
return ResponseEntity.ok().body(article);
}
Short answer: This is not a job for a full blown framework like spring boot. Better use something like spark that can do this with one liner and without any configurations. At least this is the best answer for my humble causes.
Long answer: I could not make spring boot to receive clean body text from a client, not even after many (failed) attempts to tweak the headers / media / consume flag / ... Guess this just (might) not be possible.

How to enable Spring Reactive Web MVC to handle Multipart-file?

I'm trying to use the new reactive web-mvc implementation in a spring boot 2.0 application. I'm trying to define a method which consume multipart file but do not succeed at making it working :( - I always get a 415 error.
On one hand I have a controller containing the following request mapping :
#RequestMapping(method = RequestMethod.POST, path = "/myPath/{param}/{param2}", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
#ResponseBody
public Mono<Void> postFile(
#RequestBody MultipartFile data,
#PathVariable("param") String param,
#PathVariable("param2") String param2,
#RequestHeader(name = HEADER_DATE, required = false) #DateTimeFormat(pattern = DATE_FORMAT) Instant instant
){
return fileService.handleData(Mono.just(data), param, param2, instant);
}
On the other hand I had to add a server on the top of the basic dependencies as it seems netty do not handle multipart files. I so added the spring-boot-starter-tomcatdependency which enabled the MultipartAutoConfiguration to be matched and satisfied on application auto configuration.
When posting something using a curl call :
curl 'Meta-Date: 20170101104532' --form "file=#file.bin" http://localhost:8082/myPath/foo/bar
while debug logs are activated (logging.level.org.springframework.web=DEBUG) I got this exception :
org.springframework.web.server.UnsupportedMediaTypeStatusException: Request failure [status: 415, reason: "Content type 'multipart/form-data;boundary=------------------------58fa43b8f1a26de4' not supported"]
This error is thrown by the RequestBodyArgumentResolver which has the the following supported media types : [*/*, text/xml, application/*+json;charset=UTF-8, application/xml, text/plain;charset=UTF-8, application/x-www-form-urlencoded, application/json;charset=UTF-8] provided by 9 DecoderHttpMessageReader.
Before posting I also took a look at :
Spring MultiPart MediaType Unsupported which seems to not be relevant here as my autoconf report contains the following entry : MultipartAutoConfiguration#multipartResolver matched
set content-type to utf-8 with angularjs $http Adding a header setting Content-Transfer-Encoding: binary didn't changed anything.
My understanding is that Spring web 5.0 uses a new request decoder system as I don't find these classes on a spring 4 spring boot application, and there is not yet any DecoderHttpMessageReader dealing with multipart file
Did I miss something ? Or should I wait one to be implemented ?
Okay, It seems this is just not implemented for now as it currently exists a pull request for this feature : Add reactive multipart request support #1201
Should have check this earlier...
[EDIT] : The issue has been solved and merged into Spring master branch. Should no longer be an issue.
#PutMapping(value="/{..}",consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Mono<Void> save(#RequestPart("file") FilePart multipartFormData,#RequestParam("fileName") String fileName,#PathVariable("..") String ..) throws IOException {
List<ByteBuffer> bytesList = new LinkedList<>();
multipartFormData.content().
subscribe(item->bytesList.add(item.asByteBuffer()));
int totalBytes = bytesList.stream().mapToInt(item->item.capacity()).sum();
ByteBuffer buffer = ByteBuffer.allocate(totalBytes);
bytesList.stream().forEach(byteBuff->buffer.put(byteBuff));
baseImageHandler.saveImage(buffer, fileName, baseItemId);
return Mono.empty();
}
Please note that it is a dev verison, but this is how I have managed to do it.

REST multipart mixed request (file+json) with Spring

I need to send a file alongside with a json to my Spring Controller. I have the following controller class:
#Controller
#RequestMapping("/perform")
public class PerformController {
...
#RequestMapping(path = "gopdf", method = RequestMethod.POST, consumes = { "multipart/mixed" })
#ResponseStatus(HttpStatus.CREATED)
public void handleFileUpload(#RequestPart("file") MultipartFile file, #RequestPart("map") String map, HttpServletResponse response) throws Exception {
...
}
}
But when I curl on my server with the following command :
curl -H "Content-Type: multipart/form-data" -F "map=#map.json; type=application/json" -F "content=#SMP.docx" -X POST localhost:9000/perform/gopdf-i -v
I get 415 unsupported Media Type !
Any clue ?
I've found the solution:
I need to use #RequestParam instead of #RequestPart
#RequestMapping(path = "gopdf", method = RequestMethod.POST, consumes = { "multipart/form-data" })
#ResponseStatus(HttpStatus.OK)
public void handleFileUpload2(#RequestParam("file") MultipartFile file, #RequestParam("map") String jsonMap,
HttpServletResponse response) throws Exceptio
The consumes thing in the other answers didn't do crap for me. The key was getting the specific multipart/* types I wanted to support onto some headers key in the RequestMapping. It was really difficult to figure out, mostly guess work and stare at the Spring source code. I'm kind-of underwhelmed with Spring's support for this, but I have managed to make it work in our Spring Boot App, but only with Tomcat?!? Something called the MultipartResolver chokes when you configure your Boot application to use Jetty...so long Jetty. But I digress...
In my Controller I set up for multipart/mixed or multipart/form-data like...
#RequestMapping(value = "/user/{userId}/scouting_activities", method = RequestMethod.POST,
headers = {"content-type=multipart/mixed","content-type=multipart/form-data"})
public ResponseEntity<String> POST_v1_scouting_activities(
#RequestHeader HttpHeaders headers,
#PathVariable String userId,
#RequestPart(value = "image", required = false) MultipartFile image,
#RequestPart(value = "scouting_activity", required = true) String scouting_activity_json) {
LOG.info("POST_v1_scouting_activities: headers.getContentType(): {}", headers.getContentType());
LOG.info("POST_v1_scouting_activities: userId: {}", userId);
LOG.info("POST_v1_scouting_activities: image.originalFilename: {}, image: {}",
(image!=null) ? image.getOriginalFilename() : null, image);
LOG.info("POST_v1_scouting_activities: scouting_activity_json.getType().getName(): {}, scouting_activity: {}",
scouting_activity_json.getClass().getName(), scouting_activity_json);
return new ResponseEntity<String>("POST_v1_scouting_activities\n", HttpStatus.OK);
}
That headers thing let it uniquely identify the multipart content types it was willing to take a shot at. This lets curls like...
curl -i -X POST 'http://localhost:8080/robert/v1/140218/scouting_activities' \
-H 'Content-type:multipart/mixed' \
-F 'image=#Smile_128x128.png;type=image/png' \
-F 'scouting_activity={
"field": 14006513,
"longitude": -93.2038253,
"latitude": 38.5203231,
"note": "This is the center of Dino Head.",
"scouting_date": "2017-01-19T22:56:04.836Z"
};type=application/json'
...or...
curl -i -X POST 'http://localhost:8080/robert/v1/140218/scouting_activities' \
-H 'Content-type:multipart/form-data' \
-F 'image=#Smile_128x128.png;type=image/png' \
-F 'scouting_activity=#scoutingFrackingCurl.json;type=application/json'
work.
The multipart/mixed for spring webflux(2.1.0) did not work for me. Here is an alternative approach that worked
Working - spring-boot-starter-web/Multipart[] - upload files where
one is the payload, another is a file itself. In my case since the payload was constant, it worked.
Not working - spring-boot-starter-webflux/Flux. The flux is empty. I tried this https://github.com/spring-projects/spring-boot/issues/13268, but it didn't work
It's maybe related to your request mapping annotation. I think accept value is missing to determine what service can accept :
Example :
#RequestMapping(path = "gopdf", method = RequestMethod.POST, consumes = { "multipart/mixed" }, accept = MediaType.MULTIPART_FORM_DATA_VALUE)
Import :
import org.springframework.http.MediaType;
Documentation/API : http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/MediaType.html

Resources