File upload from experience - max payload size

Hi there,
My experience users can upload a file and fill up some inputs using a form and I’m having problems with the size of the payload generated by the form submission. Here is what I get when I select a 800KB file (first response), and a 2MB file (second response):

{ “error”: “Request larger than max payload size” }

{“statusCode”:413,“error”:“Request Entity Too Large”,“message”:“Payload content length greater than maximum allowed: 1048576”}

The workflow is not triggered when I get these responses.
Is there a workaround?
Cheers
Jules

It sounds like you are running into the same issue at two different levels of the platform.

In the 800KB file case, the encoded file content being placed on the payload and then submitted for running within the workflow engine, but the encoded file exceeds the maximum starting payload size, which is leading to an error at that level.

For the 2MB file, our endpoint request handler is blocking the file submission before it ever hits the workflow engine and automatically replying with a 413 status code and appropriate error.

I have a workaround in mind but it will take a bit of work. Essentially it involves doing the following:

  1. Updating your form to submit asynchronously, if it is not doing so already.
  2. Submitting not the file itself to your endpoint, but the file’s name, size, and content type, which can be accessed using JavaScript’s File API.
  3. In your workflow that fires on submission, use the Losant API Node and the Files: Post method to get a signed URL for submitting the file directly to Amazon S3.
  4. Reply to the request with the signed URL.
  5. Immediately kick off a separate request to the signed URL and post the file to that.

I need to test this flow out but I think it would work; if successful, I’ll post a sample application here and probably also submit it to our Template Library.

@Jules_Huguenin this isn’t quite “template library ready” yet but I wanted to get it into your hands as soon as possible. You can import the attached application for a demonstration of how to allow users to submit larger files to an endpoint.

A couple notes:

  • This will add the uploads to a directory in your application files called “userUploads”, with subdirectories for each experience user ID (and “__anonymous__” for signed-out users).
  • This DOES NOT support re-uploading a new file of the same name for the same user.
  • There is some additional form cleanup I want to add, such as clearing the value of the input element on successful upload, but this illustrates the point.

If you have any questions or run into any issues, please let me know.

export-6059b9b69d7e3e000686f6b7-1616520127935-fileUploadTest.zip (6.1 KB)

Hi @Dylan_Schuster, this is great thanks,

I’m only having a glitch with your app, the file is uploaded to Losant but I’m not getting the uploadResult and the button state is not getting reset:
image

In your code, the problem happens in section 3:
const uploadResult = await uploadRequest.json();

I’m no developper so I can’t figure out what the problem is and I can’t see any obvious typo in the code.

Note that it works fine if the file already exists and we are expecting an error:
image

image

Any idea on what the problem might be with the upload response?
Cheers
Jules

I do know the issue. In fact I saw it yesterday, fixed it … and then forgot to upload the updated application here. :exploding_head:

Here is the version with that bug resolved. In case you’re curious, it’s because I originally built this application in an environment where we use a different storage mechanism than S3 for testing purposes. That storage engine returns a response in JSON format, where S3 does not, hence the JSON error. The attached version works regardless of storage mechanism.

Sorry for that. Let me know if you have any other questions.

export-605a281d62cec600060a1898-1616585771350-devLibraryExperience.zip (6.9 KB)

1 Like

Thanks @Dylan_Schuster, this is perfect

@Jules_Huguenin it sounds like you’re already squared away, but just to let you know, we did recently add the File Upload Form as an entry in our Template Library.

One bit of functionality added since we last spoke is the ability to overwrite an existing file - i.e., if User X uploads a file called foo.jpg, the user may now re-upload a file of the same name and it will replace the original foo.jpg.

1 Like

Hi Dylan, can you help me with the last step?
I wrote this in my client side code, doing the steps 1 to 5, but i dont know surely what i need to POST in the AWS signed URL.

document.getElementById("synopticUpload").addEventListener("change", async function (e) {
		let formData = new FormData();
		const file = e.target.files[0];
		formData.append("file", file);

		const res = await (await fetch(API_URL + "/machines/{{ pageData.machine.deviceId }}/upload-plant", {
			method: "POST",
			body: JSON.stringify({
				name: file.name,
				fileSize: file.size,
				contentType: file.type,
			})
		})).json();

		const awsRes = await fetch(res.url, {
			method: 'PUT',
			body: formData,
		});
	})

I get CORS error from the last fetch request response:

Access to fetch at ‘…’ from origin ‘…’ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled

What i’m trying to do: upload an image to the application files.

My workflow is basically the file upload form template from template library.

@Igo_Monteiro Good news, I got to the bottom of this …

First …

const awsRes = await fetch(res.url, {
	method: 'PUT',
	body: formData,
});

Your CORS error is because you are using the wrong method here. This needs to be POST.

Once you’re past the CORS issue, you will run into a 400 Bad Request response, because you are not including any of the credentials returned by your first request in the second request to upload the file to the bucket. So, between your two requests, you need to add …

Object.keys(res.fields || {}).forEach((key) => {
	formData.append(key, res.fields[key]);
});

Finally, the order of arguments in your second request matters, and the file property needs to come last. So you need to take the line formData.append("file", file); and move it below the Object.keys snippet I gave you above.

I also want to add that your solution is missing a great deal of the error handling you will find in the original template. What I’ve laid out here works in 99% of cases, but you really should add all of those error handling cases back in to ensure that you are catching any problems that arise between your user uploading a file and that file actually getting to the bucket.

1 Like