0

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.

  • It might be possible that your production configuration is different. Could you please try to run locally with environment production? – Puneet Behl Dec 07 '20 at 20:00
  • Hey @PuneetBehl, I am able to run locally in prod mode (via java -jar on the war file) and it works fine. I have been experimenting with configuring a multipartResolver as: multipartResolver(org.springframework.web.multipart.commons.CommonsMultipartResolver) { bean -> System.out.println("multipartResolver IN") maxInMemorySize=26214400 maxUploadSize=26214400 maxUploadSizePerFile=26214400 } This results in the following exception: FileUploadBase$IOFileUploadException: Processing of multipart/form-data request failed. Stream ended unexpectedly. – Jason Astorquia Dec 08 '20 at 00:13
  • FWIW, I have now implemented a CustomMultipartResolver that extends CommonsMultipartResolver. I override the resolveMultipart(HttpServletRequest) method and inspect the parts. Running in my local environment, I have an entry in the parts collection. Running this in the production, there are 0 parts in the collection. Any ideas on what could be upstream of this that is failing to populate the parts collection? Could this possibly be permissions related with the webserver not being able to write the file bytes to the /tmp file location (I have ensured that the user has permissions)? – Jason Astorquia Dec 08 '20 at 01:03
  • I have now run a curl call both from a machine external to the lightsail instance as well as from on the virtual instance via ssh. When external, no body parts are available on the request object. If I make the curl post of a file on the virtual machine, body parts are found on the request. So, I suspect that there is something happening, possible mimetype restrictions, within the lightsail firewall. I have found no documentation indicating that there is anything like this going on, though. If there are any lightsail gurus out there, please let me know. Feedback/input appreciated. Thanks – Jason Astorquia Dec 08 '20 at 03:22
  • This should be resolved once you update to Grails 4.0.5 – Puneet Behl Dec 11 '20 at 14:58

1 Answers1

0

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.