11

Have a SPA with a redux client and an express webapi. One of the use cases is to upload a single file from the browser to the express server. Express is using the multer middleware to decode the file upload and place it into an array on the req object. Everything works as expected when running on localhost.

However when the app is deployed to AWS, it does not function as expected. Deployment pushes the express api to an AWS Lambda function, and the redux client static assets are served by Cloudfront CDN. In that environment, the uploaded file does make it to the express server, is handled by multer, and the file does end up as the first (and only) item in the req.files array where it is expected to be.

The problem is that the file contains the wrong bytes. For example when I upload a sample image that is 2795 bytes in length, the file that ends up being decoded by multer is 4903 bytes in length. Other images I have tried always end up becoming larger by approximately the same factor by the time multer decodes and puts them into the req.files array. As a result, the files are corrupted and are not displaying as images.

The file is uploaded like so:

<input type="file" name="files" onChange={this.onUploadFileSelected} />
...
onUploadFileSelected = (e) => {
  const file = e.target.files[0]
  var formData = new FormData()
  formData.append("files", file)
  axios.post('to the url', formData, { withCredentials: true })
  .then(handleSuccessResponse).catch(handleFailResponse)
}

I have tried setting up multer with both MemoryStorage and DiskStorage. Both work, both on localhost and in the aws lambda, however both exhibit the same behavior -- the file is a larger size and corrupted in the store.

I have also tried setting up multer as both a global middleware (via app.use) and as a route-specific middleware on the upload route (via routes.post('the url', multerMiddlware, controller.uploadAction). Again, both exhibit the same behavior. Multer middleware is configured like so:

const multerMiddleware = multer({/* optionally set dest: '/tmp' */})
  .array('files')

One difference is that on localhost, both the client and express are served over http, whereas in aws, both the client and express are served over https. I don't believe this makes a difference, but I have yet been unable to test -- either running localhost over https, or running in aws over http.

Another peculiar thing I noticed was that when the multer middleware is present, other middlewares do not seem to function as expected. Rather than the next() function moving flow down to the controller action, instead, other middlewares will completely exit before the controller action invocation, and when the controller invocation exits, control does not flow back into the middlware after the next() call. When the multer middleware is removed, other middlewares do function as expected. However this observation is on localhost, where the entire end-to-end use case does function as expected.

What could be messing up the uploaded image file payload when deployed to the cloud, but not on localhost? Could it really be https making the difference?

Update 1

When I upload this file (11228 bytes)

Here is the HAR chrome is giving me for the local (expected) file upload:

"postData": {
  "mimeType": "multipart/form-data; boundary=----WebKitFormBoundaryC4EJZBZQum3qcnTL",
  "text": "------WebKitFormBoundaryC4EJZBZQum3qcnTL\r\nContent-Disposition: form-data; name=\"files\"; filename=\"danludwig.png\"\r\nContent-Type: image/png\r\n\r\n\r\n------WebKitFormBoundaryC4EJZBZQum3qcnTL--\r\n"
}

Here is the HAR chrome is giving me for the aws (corrupted) file upload:

"postData": {
  "mimeType": "multipart/form-data; boundary=----WebKitFormBoundaryoTlutFBxvC57UR10",
  "text": "------WebKitFormBoundaryoTlutFBxvC57UR10\r\nContent-Disposition: form-data; name=\"files\"; filename=\"danludwig.png\"\r\nContent-Type: image/png\r\n\r\n\r\n------WebKitFormBoundaryoTlutFBxvC57UR10--\r\n"
}

The corrupted image file that is saved is 19369 bytes in length.

Update 2

I created a text file with the text hello world that is 11 bytes long and uploaded it. It does NOT become corrupted in aws. This is the case even if I upload it with the txt or png suffix, it ends up as 11 bytes in length when persisted.

Update 3

Tried uploading with a much larger text file (12132 bytes long) and had the same result as in update 2 -- the file is persisted intact, not corrupted.

danludwig
  • 46,965
  • 25
  • 159
  • 237
  • Can you show the `har` of the upload request call? Also can you try uploading an text file with some text and see how it gets messed up? Do you have a minimal example we can setup on aws and test this? – Tarun Lalwani Jun 25 '18 at 14:00
  • @TarunLalwani have never generated a har, am looking into that and whether it will be useful from an https request. That is a really good idea about uploading a text file, wish I had thought of that. No minimal example (yet), code is from private repository. – danludwig Jun 25 '18 at 14:31
  • @TarunLalwani question updated. The issue does not occur when uploding text, regardless of what extension it has. Also posted har excerpts from successful and unsuccessful requests. – danludwig Jun 25 '18 at 16:10
  • `11` bytes is way too small. I would suggest have a bigger text file for testing similar to the image size, so we know if there is any kind of corruption – Tarun Lalwani Jun 25 '18 at 16:49
  • @TarunLalwani I just attempted again using a text file that is 12132 bytes long. With both .txt and .png extension. Same result, in aws, the file is exactly that size when persisted, and is identical when downloaded and viewed. – danludwig Jun 25 '18 at 17:27
  • Solved: https://stackoverflow.com/a/62255249/7745796 – yash Jun 08 '20 at 04:58

2 Answers2

2

Potential answers:

Found this https://forums.aws.amazon.com/thread.jspa?threadID=252327

API Gateway does not natively support multipart form data. It is possible to configure binary passthrough to then handle this multipart data in your integration (your backend integration or Lambda function).

It seems that you may need another approach if you are using API Gateway events in AWS to trigger the lambda that hosts your express server.

Or, you could configure API Gateway to work with binary payloads per https://stackoverflow.com/a/41770688/304832

Or, upload directly from your client to a signed s3 url (or a public one) and use that to trigger another lambda event.

Until we get a chance to try out different API Gateway settings, we found a temporary workaround: using FileReader to convert the file to a base64 text string, then submit that. The upload does not seem to have any issues as long as the payload is text.

danludwig
  • 46,965
  • 25
  • 159
  • 237
  • No I had to timebox this and have moved onto something else for now. Recording my findings here to share with the team in case this gets handed off to someone else. As of now, question is still unsolved / unanswered – danludwig Jun 25 '18 at 18:18
0

May be API Gateway setting

I got same error and I can have worked out the bugs. When I updated the binary-media-type setting in API Gateway, it worked correctly.

My composition is as follows

html multipart/form-data - API Gateway - lambda(express,multer)

The binary-media-type

You should set the binary-media-type to */*.

The next step is important. I forgot to deploy the API Gateway. Until deployed, the binary-media-type is not updated.

Select "Resources" from the menu on the left. Click on the "Action" drop-down menu. Choose 'deploy API'. Once deployed as described above, you're done.

  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jun 22 '23 at 22:01