3

I'm using a @RemoteAction to upload files to Salesforce, saving them as ContentVersion (Chatter files). I'm uploading the base64 Blob of the file.
This works great for small (<1MB) files.
I am NOT using lightning components for this. This is our Angular app calling a remote action with a file that the user uploaded.
Specifically the error I am getting is:

POST https://<instance>.visual.force.com/apexremote net:: ERR_CONNECTION_RESET
Visualforce Remoting Exception: Unable to connect to the server (communication failure).

From what I can tell, this is because of the HTTP POST size limit of 1MB.
Now, if you use the Salesforce-y method (directly uploading a file or embedding Chatter in a VF page) then you're good for 2GB.
How can I do that from my Angular app? Do I need to chunkify the blob myself? Is there a way for me to create a ContentVersion piecemeal?

2 Answers 2

4

Well, I solved this.
Sort of.
My solution was to forgo the use of a @RemoteAction and instead use Salesforce's REST services.
For those following at home home, there are two ways to do this:
1. The easy way, use JSForce.
2. The stupid way (which I ended up using for reasons), roll your own XmlHttpRequest calls.
Both are essentially the same (behind the scenes JSForce uses XmlHttpRequest to do its thing), so the limit seems to be 25MB.
EDIT: For the adventurous, here's some boilerplate code.

let targetUrl = baseUrl + '/services/data/v37.0/sobjects/ContentVersion';
let xhr = new XMLHttpRequest();
xhr.open('POST', targetUrl, true);
xhr.setRequestHeader('Accept', '*/*');
xhr.setRequestHeader('Authorization', 'Bearer ' + sessionId);
xhr.setRequestHeader('Content-Type', 'application/json');
xhr.onload = myCallbackFunction;
xhr.send(JSON.stringify({PathOnClient : fileName, VersionData : base64data});

EDIT September 2019

While searching for a solution to a similar problem, I encountered this question, and can now add functionality to allow uploading large files (>25MB).
Essentially, the best thing to do would be to use a multipart/form-data request. However if you blithely do the following you'll encounter a problem:

let formData = new FormData()
let metadata = {
    PathOnClient: fileName, 
    ContentDocumentId: docId, 
    ReasonForChange : changeReason
    };
formData.append('entity_content', JSON.stringify(metadata));
formData.append('VersionData', fileField.files[0]);
...
xhr.send(formData);

You'll get a request built something like this:

--boundary_string
Content-Disposition: form-data; name="entity_content";

{
    "ContentDocumentId" : "069XXXXXXXXXXXX",
    "ReasonForChange" : "Marketing materials updated",
    "PathOnClient" : "Q1 Sales Brochure.pdf"
}

--boundary_string
Content-Type: application/octet-stream
Content-Disposition: form-data; name="VersionData"; filename="Q1 Sales  Brochure.pdf"

Binary data goes here.

--boundary_string--

Now that is very similar to Salesforce's example in the docs except that it's missing a crucial line: Content-Type: application/json in the first part.
You'll get an error like so: {message: "Multipart message must include a non-binary part", errorCode: "INVALID_MULTIPART_REQUEST"} After much searching and anguish it turns out that this isn't Salesforce's fault! It's a general problem with the FormData object.

The solution is to generate your own request and send it. Like so:

<input type="file" #fileField name="fileName" />

let boundary = '----boundaryString';
...
xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
xhr.send(new Blob([this.getRequestDataPart1(boundary), this.fileField.files[0], this.getRequestDataPart2(boundary)]));

getRequestDataPart1(boundary: string): string{
    let data =
    `--${boundary}
    Content-Disposition: form-data; name="entity_content";
    Content-Type: application/json

    {
        "ContentDocumentId" : "069XXXXXXXXXXXX", //leave blank for new ContentDocument
        "ReasonForChange" : "Marketing materials updated", // not actually required
        "PathOnClient" : "${this.fileName}"
    }

    --${boundary}
    Content-Type: ${this.mimeType}
    Content-Disposition: form-data; name="VersionData"; filename="${this.fileName}"

    `;
    return data;
  }

  getRequestDataPart2(boundary: string): string{
    let data =
    `

    ----${boundary}--
    `;
    return data;
  }

I really hope that this helps someone. Maybe me in a year's time.

2
  • I tried using JSForce. It seems limit is just 10lakh characters approx 3-4mb. I'm really stuck on this. Nov 7, 2020 at 15:39
  • Thank you for documenting this solution, very valuable answer you have provided for constructing the multipart request manually to support > 25MB! Nov 22, 2022 at 17:44
2

The solution proposed by AvailableName works but I have spent multiple hours debugging because of how salesforce handles end of line symbols. I was getting an error saying that my data is in an incorrect format (maybe because of the editor I was using). To solve this I had to build the request like so:

sendXHR: function(component, helper, files, url, sessionId) {
    var xhr = new XMLHttpRequest();
    xhr.open('POST', url, true);
    xhr.setRequestHeader('Accept', '*/*');
    xhr.setRequestHeader('Cache-Control', 'no-cache');      
    xhr.setRequestHeader('Content-Type', 'multipart/form-data; boundary="boundary_string"');
    xhr.setRequestHeader('Authorization',  sessionId);
    var payloadPart1 = "--boundary_string" +
    "\r\nContent-Disposition: form-data; name=\"entity_content\";" +
    "\r\nContent-Type: application/json" +
    "\r\n\r\n{ \"FirstPublishLocationId\" : \"" + component.get('v.Id') + "\", \"PathOnClient\" : \"" + file.name.replace(/\.[^/.]+$/, "") + '.' + file.name.substr(file.name.lastIndexOf('.') + 1) + "\"}" +
    "\r\n\r\n--boundary_string" +
    "\r\nContent-Type: application/octet-stream" +
    "\r\nContent-Disposition: form-data; name=\"VersionData\"; filename=\"" + file.name + " \"" +
    "\r\n\r\n"; 
    var payloadPart2 = "\r\n\r\n--boundary_string--";
    xhr.send(new Blob([payloadPart1, file, payloadPart2]));

}

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .