4

I'm trying to send a Blob Apex REST callout and it is keep failing. I tried all the examples in the web but didn't work. What is the standard and correct way to send Blob data via REST callouts?

//Parameters
String strBody = 'flowIdentifier=1&flowFilename=test.jpg&flowTotalSize=14893&flowCurrentChunkSize=14893&flowChunkSize=14893&flowChunkNumber=1&flowTotalChunks=1';

bodyBlob = //Blob data

HttpRequest request = new HttpRequest();
request.setEndpoint(END_POINT);
request.setMethod('POST');
request.setHeader('content-type', 'multipart/form-data');
request.setBodyAsBlob(bodyBlob);
request.setBody(strBody);

try{
Http http = new Http();
HttpResponse response = http.send(request);
System.debug(response.getBody());
}
catch(Exception e) {    
 System.debug('ERROR:'+e);    
}
2
  • Are strBody and bodyBlob same data in different forms?
    – Avinash
    Commented Jun 23, 2017 at 11:53
  • strBody : string parameters, bodyBlob : file(Doc,pdf or image)
    – MalindaL
    Commented Jun 23, 2017 at 11:55

4 Answers 4

5

You can't use both setBody and setBodyAsBlob in the same request. Also, if you're using multipart/form-data, you have to build a multipart payload. That means that you'll need to do something like the following:

String body = '';
String boundary = 'ContentThatShouldNotAppearInTheBody';
Map<String, String> params = new Map<String, String> {
  'flowIdentifier'=>'1',
  'flowFilename'=>'test.jpg',
  'flowTotalSize'=>'14893',
  'flowCurrentChunkSize'=>'14893',
  'flowChunkSize'=>'14893',
  'flowChunkNumber'=>'1',
  'flowTotalChunks'=>'1' };
for(String key: params) {
  body +='--'+boundary+'\nContent-Disposition: form-data; name="'+
    key+'"\n\n'+params.get(key)+'\n';
}
body += '--'+boundary+'\nContent-Disposition: form-data; '+
  'name="flowBody"; filename="flowFileName"\nContent-Transfer-Encoding: base64\n\n'+
  EncodingUtil.base64Encode(bodyBlob)+'\n'+
  '--'+boundary+'--';
request.setBody(body);

If your server does not support base-64 transfer encoding, you'll need to find a way to align the parts leading up to the blob data so it is a multiple of 3 bytes; this will allow you to build a long base64 string that you can then decode into a Blob directly, in which case you'd then need to use setBodyAsBlob.

3
  • Hi @sfcfox, Iam trying to use this but when I conver blob to Base64, I am runinng into heap size limits as mentioned by cropredy here : salesforce.stackexchange.com/questions/82719/… Any thoughts to overcome this ?
    – sf_user
    Commented Aug 9, 2018 at 17:22
  • @sf_user You can't. If the payload exceeds heap size, it's simply too big to send. The maximum practical limit you can probably get away with is about 4.5MB in synchronous code and about 9MB in asynchronous code. You're going to have to use a different technique. I suggest writing a new question stating what you're trying to do (e.g. upload a file to S3) and what you've tried.
    – sfdcfox
    Commented Aug 9, 2018 at 18:40
  • Please help salesforce.stackexchange.com/questions/251125/… Commented Feb 26, 2019 at 9:37
4

Proper multipart/form-data POST requests from Apex are tricky. As @sfdcfox mentioned, you need to do some very careful manipulations between the 8-bit bytes of the input file and the 6 bit groups used by Base64 encoding.

enter image description here

I've written about this in some depth in Steps required to support POSTing multipart/form-data Content-Type from Apex.

You might find it easiest to use the classes that were created for the Einstein Predictive Vision Service in HttpBodyPart. This is an example of doing this in Salesforce Apex - POSTing multipart/form-data Content-Type

See also:

4

Thanks for all of your valuable feedback. Below example worked for me with some modifications.

How can I compose a multipart/form-data request?

Here is my code. File Upload Method

 public void uploadFiles(String strParamFileName
        ,Blob blParamFile
        ,String strParamFlowTotalSize
        ,String strParamFlowCurrentChunkSize
        ,String strParamFlowChunkSize
        ,String strParamFlowIdentifier
        ,String strParamFlowChunkNumber
        ,String strParamFlowTotalChunks) {

    String fileMimeType            = '';
    String strFlowTotalSize        = strParamFlowTotalSize;
    String strFlowCurrentChunkSize = strParamFlowCurrentChunkSize;
    String strFlowChunkSize        = strParamFlowChunkSize;
    String strFileName             = strParamFileName;
    String strFlowIdentifier       = strParamFlowIdentifier;
    String strFlowChunkNumber      = strParamFlowChunkNumber;
    String strFlowTotalChunks      = strParamFlowTotalChunks;
    Blob fileBlob                  = blParamFile;

    string contentType = WebserviceHelper.GetContentType();

    string form64 = '';
    form64 += WebserviceHelper.WriteBoundary();
    form64 += WebserviceHelper.WriteBodyParameter(BLOB_FLOW_IDENTIFIER, strFlowIdentifier);
    form64 += WebserviceHelper.WriteBoundary();
    form64 += WebserviceHelper.WriteBodyParameter(BLOB_FLOW_FILE_NAME, strFileName);
    form64 += WebserviceHelper.WriteBoundary();
    form64 += WebserviceHelper.WriteBodyParameter(BLOB_FLOW_TOTAL_FILE_SIZE, strFlowTotalSize);
    form64 += WebserviceHelper.WriteBoundary();
    form64 += WebserviceHelper.WriteBodyParameter(BLOB_FLOW_CURRENT_CHUNCK_SIZE, strFlowCurrentChunkSize);
    form64 += WebserviceHelper.WriteBoundary();
    form64 += WebserviceHelper.WriteBodyParameter(BLOB_FLOW_CHUNCK_SIZE, strFlowChunkSize);
    form64 += WebserviceHelper.WriteBoundary();
    form64 += WebserviceHelper.WriteBodyParameter(BLOB_FLOW_CHUNCK_NUMBER, strFlowChunkNumber);
    form64 += WebserviceHelper.WriteBoundary();
    form64 += WebserviceHelper.WriteBodyParameter(BLOB_FLOW_TOTAL_CHUNKS, strFlowTotalChunks);
    form64 += WebserviceHelper.WriteBoundary();

    WebserviceHelper.WriteFileResult result = WebserviceHelper.WriteFile(HELPER_FILE, strFileName, fileMimeType, fileBlob);

    form64 += result.Content;
    form64 += WebserviceHelper.WriteBoundary(result.EndingType);

    blob formBlob = EncodingUtil.base64Decode(form64);
    string contentLength = string.valueOf(formBlob.size());

    //  Compose the http request
    HttpRequest httpRequest = new HttpRequest();
    httpRequest.setBodyAsBlob(formBlob);
    httpRequest.setEndpoint(END_POINT_FILE_UPLOAD);
    httpRequest.setHeader(HEADER_CONTENT_TYPE, contentType);
    httpRequest.setMethod(HTTP_POST);
    httpRequest.setTimeout(120000);

    try {

        Http http = new Http();
        HttpResponse response = http.send(httpRequest);
        //System.debug('FileStatus:'+response.getStatusCode());
        //System.debug('FileBody:'+response.getBody());

    }
    catch (Exception e) {

        System.debug(e.getMessage());

    }


}

Helper Class

public with sharing class WebserviceHelper {

    //  The boundary is alligned so it doesn't produce padding characters when base64 encoded.
    private final static string Boundary = '1ff13444ed8140c7a32fc4e6451aa76d';

    /**
     *  Returns the request's content type for multipart/form-data requests.
     */
    public static string GetContentType() {
        return 'multipart/form-data; charset="UTF-8"; boundary="' + Boundary + '"';
    }

    /**
     *  Pad the value with spaces until the base64 encoding is no longer padded.
     */
    private static string SafelyPad(string value, string valueCrLf64, string lineBreaks) {
        string valueCrLf = '';
        blob valueCrLfBlob = null;

        while (valueCrLf64.endsWith('=')) {
            value += ' ';
            valueCrLf = value + lineBreaks;
            valueCrLfBlob = blob.valueOf(valueCrLf);
            valueCrLf64 = EncodingUtil.base64Encode(valueCrLfBlob);
        }

        return valueCrLf64;
    }

    /**
     *  Write a boundary between parameters to the form's body.
     */
    public static string WriteBoundary() {
        string value = '--' + Boundary + '\r\n';
        blob valueBlob = blob.valueOf(value);

        return EncodingUtil.base64Encode(valueBlob);
    }

    /**
     *  Write a boundary at the end of the form's body.
     */
    public static string WriteBoundary(EndingType ending) {
        string value = '';

        if (ending == EndingType.Cr) {
            //  The file's base64 was padded with a single '=',
            //  so it was replaced with '\r'. Now we have to
            //  prepend the boundary with '\n' to complete
            //  the line break.
            value += '\n';
        } else if (ending == EndingType.None) {
            //  The file's base64 was not padded at all,
            //  so we have to prepend the boundary with
            //  '\r\n' to create the line break.
            value += '\r\n';
        }
        //  Else:
        //  The file's base64 was padded with a double '=',
        //  so they were replaced with '\r\n'. We don't have to
        //  do anything to the boundary because there's a complete
        //  line break before it.

        value += '--' + Boundary + '--';

        blob valueBlob = blob.valueOf(value);

        return EncodingUtil.base64Encode(valueBlob);
    }

    /**
     *  Wirte a file to the form's body.
     */
    public static WriteFileResult WriteFile(string key, string value, string mimeType, blob fileBlob) {
        EndingType ending = EndingType.None;
        string contentDisposition = 'Content-Disposition: form-data; name="' + key + '"; filename="' + value + '"';
        string contentDispositionCrLf = contentDisposition + '\r\n';
        blob contentDispositionCrLfBlob = blob.valueOf(contentDispositionCrLf);
        string contentDispositionCrLf64 = EncodingUtil.base64Encode(contentDispositionCrlfBlob);
        string content = SafelyPad(contentDisposition, contentDispositionCrLf64, '\r\n');
        string contentType = 'Content-Type: ' + mimeType;
        string contentTypeCrLf = contentType + '\r\n\r\n';
        blob contentTypeCrLfBlob = blob.valueOf(contentTypeCrLf);
        string contentTypeCrLf64 = EncodingUtil.base64Encode(contentTypeCrLfBlob);

        content += SafelyPad(contentType, contentTypeCrLf64, '\r\n\r\n');

        string file64 = EncodingUtil.base64Encode(fileBlob);
        integer file64Length = file64.length();
        string file64Ending = file64.substring(file64Length - 3, file64Length);

        if (file64Ending.endsWith('==')) {
            file64Ending = file64Ending.substring(0, 1) + '0K';//   0K = \r\n
            file64 = file64.substring(0, file64Length - 3) + file64Ending;
            ending = EndingType.CrLf;
        } else if (file64Ending.endsWith('=')) {
            file64Ending = file64Ending.substring(0, 2) + 'N';//    N = \r
            file64 = file64.substring(0, file64Length - 3) + file64Ending;
            ending = EndingType.Cr;
        }

        content += file64;

        return new WriteFileResult(content, ending);
    }

    /**
     *  Write a key-value pair to the form's body.
     */
    public static string WriteBodyParameter(string key, string value) {
        string contentDisposition = 'Content-Disposition: form-data; name="' + key + '"';
        string contentDispositionCrLf = contentDisposition + '\r\n\r\n';
        blob contentDispositionCrLfBlob = blob.valueOf(contentDispositionCrLf);
        string contentDispositionCrLf64 = EncodingUtil.base64Encode(contentDispositionCrLfBlob);
        string content = SafelyPad(contentDisposition, contentDispositionCrLf64, '\r\n\r\n');
        string valueCrLf = value + '\r\n';
        blob valueCrLfBlob = blob.valueOf(valueCrLf);
        string valueCrLf64 = EncodingUtil.base64Encode(valueCrLfBlob);

        content += SafelyPad(value, valueCrLf64, '\r\n');

        return content;
    }

    /**
     *  Helper class containing the result of writing a file's blob to the form's body.
     */
    public class WriteFileResult {
        public final string Content { get; private set; }
        public final EndingType EndingType { get; private set; }

        public WriteFileResult(string content, EndingType ending) {
            this.Content = content;
            this.EndingType = ending;
        }
    }

    /**
     *  Helper enum indicating how a file's base64 padding was replaced.
     */
    public enum EndingType {
        Cr,
        CrLf,
        None
    }

}
1

In order to send multiple files, I had to tweak the code to add an additional break after form64 += result.Content;

Ex:

for () {
      form64 += result.Content;
      form64 += WebserviceHelper.writeFileSeperator();
}

form64 += WebserviceHelper.writeBoundary(result.EndingType);

You must log in to answer this question.

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