Upgrading to Tomcat 8 breaks MultipartFile upload - spring-boot

I'm using Spring Boot. The method I'm calling in my controller looks like this:
#RequestMapping(value = "/{customerId}/files/{id}/addFile", method = RequestMethod.POST,
produces = "application/json; charset=UTF-8")
#ResponseBody
public Response uploadFile(#PathVariable String customerId, #PathVariable String id,
#RequestParam("uploadedFile") MultipartFile file) throws IOException {
After upgrading to Tomcat 8 I'm getting the following error when calling this method. I set a breakpoint on the first line which is never reached.:
org.springframework.web.util.NestedServletException: Request
processing failed; nested exception is
org.springframework.web.multipart.MultipartException: Could not parse
multipart servlet request; nested exception is java.io.IOException:
org.apache.tomcat.util.http.fileupload.FileUploadException: Stream
closed
Has anyone come across this issue?

Try to add multipart.maxFileSize and multipart.maxRequestSize to your application.properties file. SpringBoot seems to has a default value of 128KB, which broke my upload.

Related

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.

Can not handle JDBCConnectionException in spring rest with custom exception handler

I use a global exception handler in my spring rest app and I would like to hide jdbc exceptions, but it doesn't work as expected. I shut down the database to force a connection exception and I can see the following exception in the log and I receive the default spring error response, but not the one I defined in the exception handler
java.lang.IllegalStateException: Could not resolve parameter [1] in public org.springframework.http.ResponseEntity<java.lang.Object> ...
throws java.io.IOException: No suitable resolver
Here's the code.
#ControllerAdvice
public class CustomGlobalExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler({JDBCConnectionException.class})
public ResponseEntity<Object> dbError(JDBCConnectionException exception,
HttpHeaders headers,
HttpStatus status,
WebRequest request) throws IOException
{
Map<String,Object> body = new HashMap<>();
body.put("errorId",Long.valueOf(201));
body.put("state",HttpStatus.SERVICE_UNAVAILABLE.value());
body.put("message", "internal failure");
body.put("time", new Date().toString());
return new ResponseEntity<>(body, headers, status);
}
Hope you can help me.
I've found the failure...spring can not resolve these two parameters, for that kind of exception.
HttpHeaders headers,
HttpStatus status
It's obviouse the exception mentioned paramter [1]
java.lang.IllegalStateException: Could not resolve parameter [1] in public org.springframework.http.ResponseEntity<java.lang.Object> ...
throws java.io.IOException: No suitable resolver
I removed these two parameters and the exception handler handles the exception.
This code works now
#ExceptionHandler(JDBCConnectionException.class)
public ResponseEntity<Object> dbError(Exception ex,
WebRequest request)
{
Map<String,Object> body = new HashMap<>();
body.put("errorId",Long.valueOf(201));
body.put("state",HttpStatus.SERVICE_UNAVAILABLE.value());
body.put("message", "internal failure");
body.put("time", new Date().toString());
return new ResponseEntity<Object>(body, HttpStatus.INTERNAL_SERVER_ERROR);
}
As the annotation implies #ControllerAdvice is used as an extension on your REST endpoints, these exception handlers will process the exception for the REST API and does not influence how it is logged in the console. It will instead determine how exceptions are reported to your end users and allow you to write concise error messages without leaking information about your program.
If you want to completely catch an exception and not only for the REST API take a look at this blog.
However I would not recommend doing this since this will greatly reduce the information available to you as a developer, this information cannot be seen by end users and therefore the REST API custom exception should provide enough abstraction.
I hope this helps you.

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.

How to handle HttpMediaTypeNotAcceptableException by writing error content to the response body using exception handler annotation?

When a client request for a resource producing application/json content with Accept Header of application/xml. The request fails with HttpMediaTypeNotAcceptableException exception and is wrapped into error message body in the response entity object by using exception handler annotation as mentioned in below code. However, we receive HttpMediaTypeNotAcceptableException again when return values are written to the response with HttpMessageConverter. It is because it checks the producible content type for the response with the acceptable request type, but this is exactly something we are trying to communicate to the client using error message. How do I workaround this issue ? Btw, all the other exceptions are parsing fine to error message. Please advise.
#ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleExceptionInternal(Exception ex, Object body,
HttpHeaders headers, HttpStatus status, WebRequest request) {
// Setting the response content type to json
headers.setContentType(MediaType.APPLICATION_JSON);
return ResponseEntity.status(status).headers(headers).body(body);
}
}
A few options come to my mind. One is that your controller method produces all content types and then you throw an exception in your method if the content type is not the one you are expecting, then the exception handler can take this exception and transform it. This is the only one that works with exception handlers, as exception handlers only deal with exceptions produced in the controller method.
The other options are:
Use an interceptor (but I'm not sure if this will work, as Spring might try to resolve first the controller method rather than invoking the interceptors).
Extend RequestMappingHandlerMapping to call the exception handler if it doesn't find a suitable method. You'll probably need to override the method handleNoMatch. In there you'll need to get a reference to the list of HandlerExceptionResolver
The first one is the simplest to understand, and the latest one might be the most 'extensible', but it also requires some understanding of the internals of Spring.
Resolved by setting different content negotiation strategy FixedContentNegotiationStrategy for ExceptionHandlerExceptionResolver and HeaderContentNegotiationStrategy for RequestResponseBodyMethodProcessor.
I have been using a serialized enum-based response (enum annotated with jackson #JsonFormat(shape = Shape.OBJECT) to standardize the error messages in my exception handler class and faced the same issue when it caught with a HttpMediaTypeNotAcceptableException.
The workaround is to set the media type you expect to return directly to the builder method available in the ResponseEntity.
The below code works fine for me.
#ExceptionHandler(HttpMediaTypeNotAcceptableException.class)
public final ResponseEntity<ResponseMessagesEnum> handleHttpMediaTypeNotAcceptableException(
HttpMediaTypeNotAcceptableException e, HttpServletRequest request) {
logger.error("No acceptable representation found for [{}] | supported {}", request.getHeader("Accept"), e.getSupportedMediaTypes());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).contentType(MediaType.APPLICATION_JSON)
.body(ResponseMessagesEnum.EX_001);
}

Spring MVC with ajax file upload and MultipartFile

I have an issue using Ajax upload with Spring 3 MVC. I understand that I have to configure multipartResolver bean in spring config, which I've done. Than I can have controller like this
#RequestMapping(value ="/settingsSim")
#ResponseBody
public Map uploadSimSettings(#RequestParam(value="qqfile", required=true) MultipartFile settings) {
Map<String, Object> ret = new HashMap<String, Object>();
return ret;
}
The problem is that when I actually send the request to the server (actually valums Ajax file upload does this for me), I get an Internal server error response and nothing is shown in the logs. I am really scratching my head now, as I cannot figure out the problem.
my solution:
#RequestMapping(value = "/create/upload", method = RequestMethod.POST, consumes="multipart/form-data", produces="application/json")
#ResponseBody()
public String handleImageUpload(#RequestParam(value="qqfile", required=true) MultipartFile[] files,
#ModelAttribute(value="files") List<MultipartFile> filesSession) throws IOException, FileUploadException {
if (files.length > 0) {
filesSession.addAll(Arrays.asList(files));
// store the bytes somewhere
return "{\"success\": true}";
}
else {
return "{\"success\": false}";
}
}
#RequestMapping(value = "/create/upload", method = RequestMethod.POST, consumes="application/octet-stream", produces="application/json")
#ResponseBody()
public String handleImageUploadApplication(HttpServletRequest request,
#ModelAttribute(value="files") List<MultipartFile> filesSession) throws IOException, FileUploadException {
if (request.getInputStream() != null) {
// creamos el fichero temporal
File file = File.createTempFile("file", "valumns",
RepositoryData.getRepositoryData());
FileOutputStream fos = new FileOutputStream(file);
// copiamos contenido
Streams.copy(request.getInputStream(), fos, true);
//TODO:
//filesSession.addAll(Arrays.asList(files));
// store the bytes somewhere
return "{\"success\": true}";
}
else {
return "{\"success\": true}";
}
}
#ExceptionHandler(Exception.class)
#ResponseStatus(value = HttpStatus.SERVICE_UNAVAILABLE)
public void handleException(Exception ex) {
log.error("Ocurrio un error en el album", ex);
}
I had the same problem with the fineuploader (valums), and I tried using request.getInputStream() but did not get it to work.
The #ResponseBody annotation worked but I got the whole body with headers. I thought processing that and stripping off the unwanted chunks was not very elegant.
I looked further and found the solution is this post:
problem with spring ajax file upload
Like it is said, I added the bean configuration for the multipart resolver to my spring configuration
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
</bean>
After that, I could easily retrieve my file using
public #ResponseBody Map ajaxUploadFile(#RequestParam MultipartFile qqfile) { ... }
Don't forget to add the Apache commons-io.jar and commons-fileupload.jar libraries in your project to get it to work
When using valums plugin I solved this problem by using #RequestBody Spring annotation.
You could rewrite your code as follows:
#RequestMapping(value ="/settingsSim",method=RequestMethod.POST)
#ResponseBody
public Map uploadSimSettings(#RequestBody String body) {
/*
some controller logic
*/
}
Note that the variable body will contain the contents of the uploaded file. Also there is no method declaration in your example which means that your method will be mapped to GET request.
P.S. I also had this "no multipart boundary" problem when parsing request with Apache Commons. HttpServletRequest#getParts() returns just an empty collection.
#Tomas I encountered same issue while using the same jquery plugin. Please change the Content-Type in the plugin code to xhr.setRequestHeader("Content-Type", "multipart/form-data"); on my plugin its line 1203, after this its now showing a stack trace, however I am encountering another issue where the logs are printing :
Sep 8, 2011 9:43:39 AM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet dispatcher threw exception
org.apache.commons.fileupload.FileUploadException: the request was rejected because no multipart boundary was found
As per my observation the file upload plugin does not send a multipart file but sends a stream. I could get it to work by declaring the controller method to accept filename as request param qqfile and the second parameter as httprequest. I then did further processing using request.getinputstream. Hope that helps!
Regards,
Pradyumna

Resources