I have been attempting to solve an issue with a prod environment deployment of a grails 4 application for about a week now. When I run my application locally, via run-app or java -jar warfile.war, I am able to upload files and have the multipart content available in my controller. When I deploy run the application on a lightsail ubuntu 20 server, for some reason the parts are simply not there.
UI form code:
<form id="file-form-${attrs.id}" enctype="multipart/form-data">
<input style="display:none;" id="${attrs.id}-file" type="file" name="file"
accept="image/png, image/jpeg, image/tiff"/>
</form>
UI ajax/js code:
var jForm = new FormData($('#file-form-'+id)[0]);
jForm.append("uploadField", $("#file-form-"+id).find('input[type="file"]').get(0).files[0]);
$.ajax({
url: '/files/uploadImage',
type: "POST",
data: jForm,
enctype: 'multipart/form-data',
processData: false,
cache: false,
contentType: false
}).done(function(data) {
// ...
}).fail(function(jqXHR, textStatus) {
// ...
});
Controller (please note that all of the log statements are here to get clarity, it is pretty ugly as written and will be cleaned up once clarity is achieved):
def uploadImage() {
try {
log.debug("FilesController.uploadImage: IN for user ${session.user?.id}")
if (request instanceof MultipartHttpServletRequest) {
MultipartHttpServletRequest rqst = (MultipartHttpServletRequest) request
def myFile = params['uploadField']
params.each { k, v ->
log.debug("params k ${k}, v ${v}")
}
request.requestHeaders.each {k, v ->
log.debug("headers k ${k}, v ${v}")
}
def f
log.debug("parts " + request.parts?.size())
request.parts.each {part ->
log.debug("part ${part.name}")
if (f == null) f = part
}
log.debug("fileNames: ${request.getFileNames()}")
request.getFileNames().each { String fileName ->
log.debug("fileName: ${fileName}")
}
request.fileMap.each { String name, MultipartFile file ->
log.debug("Looping fileMap: name: ${name} and file ${file}")
if (f == null) f = file
}
log.debug('file: ' + f)
if (f == null) f = myFile
log.debug('file: ' + f)
log.debug("FilesController.uploadImage: calling imageService for user ${session.user?.id} ")
log.debug("imageService " + imageService)
File file = imageService.saveImage(f, session.user)
render text: file?.id
return
}
} catch (Exception e) {
log.error("FilesController.uploadImage: error ${e.getMessage()}")
}
render text: ''
}
Logs (in prod):2020-12-07 18:11:51.977 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : FilesController.uploadImage: IN for user uuid-xxxxx 2020-12-07 18:11:51.978 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : params k controller, v files 2020-12-07 18:11:51.978 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : params k format, v null 2020-12-07 18:11:51.978 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : params k action, v uploadImage 2020-12-07 18:11:51.980 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k host, v [xxx.com] 2020-12-07 18:11:51.980 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k connection, v [keep-alive] 2020-12-07 18:11:51.980 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k content-length, v [3477461] 2020-12-07 18:11:51.980 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k accept, v [*/*] 2020-12-07 18:11:51.981 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k x-requested-with, v [XMLHttpRequest] 2020-12-07 18:11:51.981 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k user-agent, v [Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36] 2020-12-07 18:11:51.981 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k content-type, v [multipart/form-data; boundary=----WebKitFormBoundaryNqkRdP2KDJ3aJYMb] 2020-12-07 18:11:51.981 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k origin, v [https://example.com] 2020-12-07 18:11:51.981 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k sec-fetch-site, v [same-origin] 2020-12-07 18:11:51.982 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k sec-fetch-mode, v [cors] 2020-12-07 18:11:51.982 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k sec-fetch-dest, v [empty] 2020-12-07 18:11:51.982 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k referer, v [https://example.com/toolsList/list?toolSetID=2c9a8202763005520176300c0254001d] 2020-12-07 18:11:51.982 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k accept-encoding, v [gzip, deflate, br] 2020-12-07 18:11:51.982 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k accept-language, v [en-US,en;q=0.9] 2020-12-07 18:11:51.982 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : headers k cookie, v [pvisitor=ff1209b4-edb0-4246-b877-8e42fe356908; JSESSIONID=C8C338FA7AC3DA7F0A967492864939BF] 2020-12-07 18:11:51.983 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : parts 0 2020-12-07 18:11:51.983 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : fileNames: java.util.LinkedHashMap$LinkedKeyIterator@5f7cb675 2020-12-07 18:11:51.983 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : file: null 2020-12-07 18:11:51.983 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : file: null 2020-12-07 18:11:51.984 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : FilesController.uploadImage: calling imageService for user uuid-xxx 2020-12-07 18:11:51.984 DEBUG --- [nio-8443-exec-2] xxx.files.FilesController : imageService xxx.files.ImageService@5c93194d 2020-12-07 18:11:51.985 INFO --- [nio-8443-exec-2] xxx.files.ImageService : FilesService.saveImage: IN for uuid-xxx named null 2020-
Locally, there is a file there.
In my application.yml:
grails:
disableCommonsMultipart: false
web:
disable:
multipart: false
I have no idea why the file content is being received in my local environment but not in the production environment. I cannot imagine that aws lightsail would be stripping content from the https request. Locally, I am using http rather than https, not sure why that would matter.
Any ideas? Thank you in advance for any insight you could provide.
Ultimately, the problem turned out to be ssl related. The grails version I am using is 4.0.3. For that version, the embedded tomcat dependency is org.springframework.boo:spring-boot-starter-tomcat and the version being pulled is 2.1.13.RELEASE. That spring boot library leverages tomcat version 9.0.31. Once I located this article, Multipart file upload using spring boot with tomcat version 9.0.31 is failing, it was a matter of specifying a tomcat version which fixes the issue with parsing multipart files on ssl:
compile "org.apache.tomcat.embed:tomcat-embed-websocket:9.0.33"
compile "org.apache.tomcat.embed:tomcat-embed-core:9.0.33"
compile "org.apache.tomcat.embed:tomcat-embed-el:9.0.33"
compile "org.apache.tomcat:tomcat-juli:9.0.33"
compile "org.apache.tomcat:tomcat-annotations-api:9.0.33"
I hope it saves someone else some time. Peace.