function readOnly(count){ }
Starting November 20, the site will be set to read-only. On December 4, 2023,
forum discussions will move to the Trailblazer Community.
+ Start a Discussion
Narender Singh(Nads)Narender Singh(Nads) 

Getting 'Internal Server Error'(Status Code 500) as response when making a callout to Einstein OCR with base64 content

In addition to what query title says, when making callout via URL it seems to be working. Please find the code below:
EinsteinOCRPredictionHandler.cls
public class EinsteinOCRPredictionHandler {

    final static String VISION_API = 'https://api.einstein.ai/v2/vision';
    final static String OCREndpoint = VISION_API + '/ocr';
    final static String cmdtDeveloperName = 'Einstein_Vision_API_JWT';
    
    public class EinsteinOCRPredictionHandlerException extends Exception {}

    public static EinsteinOCRPredictionResponseWrapper predictViaURL(String fileURL, String detectionType, String accessToken, String model) {
        String response = detectText(fileURL, detectionType, accessToken, model, false);
        return parseResponse(response);
    }

    public static EinsteinOCRPredictionResponseWrapper predictViaBase64(String base64String, String detectionType, String accessToken, String model) {
        String response = detectText(base64String, detectionType, accessToken, model, true);
        return parseResponse(response);
    }
    
    public static EinsteinOCRPredictionResponseWrapper predictViaBlob(Blob fileBlob, String detectionType, String accessToken, String model) {
        String response = detectText(EncodingUtil.base64Encode(fileBlob), detectionType, accessToken, model, true);
        return parseResponse(response);
    }
    
    public static String detectText(String sample, String detectionType, String accessToken, String model, boolean isBase64) {
        string contentType = HttpFormBuilder.GetContentType();
        //  Compose the form
        string form64 = '';
        form64 += HttpFormBuilder.WriteBoundary();
        form64 += HttpFormBuilder.WriteBodyParameter('modelId', EncodingUtil.urlEncode(model, 'UTF-8'));
        form64 += HttpFormBuilder.WriteBoundary();
        form64 += HttpFormBuilder.WriteBodyParameter('task', EncodingUtil.urlEncode(detectionType, 'UTF-8'));
        form64 += HttpFormBuilder.WriteBoundary();
        if(isBase64) {
            form64 += HttpFormBuilder.WriteBodyParameter('sampleBase64Content', sample);
        } else {
            form64 += HttpFormBuilder.WriteBodyParameter('sampleLocation', sample);
        }
        form64 += HttpFormBuilder.WriteBoundary(HttpFormBuilder.EndingType.CrLf);
        blob formBlob = EncodingUtil.base64Decode(form64);
        string contentLength = string.valueOf(formBlob.size());
        //  Compose the http request
        HttpRequest httpRequest = new HttpRequest();
        httpRequest.setBodyAsBlob(formBlob);
        httpRequest.setHeader('Connection', 'keep-alive');
        httpRequest.setHeader('Content-Length', contentLength);
        httpRequest.setHeader('Content-Type', contentType); 
        httpRequest.setMethod('POST');
        //httpRequest.setTimeout(120000);
        httpRequest.setHeader('Authorization','Bearer ' + accessToken);
        httpRequest.setEndpoint(OCREndpoint);
        
        Http http = new Http();
        HTTPResponse res = http.send(httpRequest);
        System.debug(res.getStatus());
        String responseBody;
        Integer statusCode = res.getStatusCode();
        String requestStatus = res.getStatus();
        if ( statusCode == 200) {
            responseBody = res.getBody();
        } else {
            throw new EinsteinOCRPredictionHandlerException('Callout unsuccessful! Status code: '+statusCode + '. Status: '+requestStatus);
        }
        return responseBody;
    }

    static EinsteinOCRPredictionResponseWrapper parseResponse(String response) {
        if(String.isEmpty(response)) {
            throw new EinsteinOCRPredictionHandlerException('Empty response received');
        }
        EinsteinOCRPredictionResponseWrapper obj = new EinsteinOCRPredictionResponseWrapper();
        obj = (EinsteinOCRPredictionResponseWrapper)JSON.deserialize(response, EinsteinOCRPredictionResponseWrapper.class);
        return obj;
    }
}
HttpFormBuilder.cls
public class HttpFormBuilder {
    //  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);
    }

    /**
     *  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 enum indicating how a file's base64 padding was replaced.
     */
    public enum EndingType {
        Cr,
        CrLf,
        None
    }
}


 
Best Answer chosen by Narender Singh(Nads)
Mohith Kumar ShrivastavaMohith Kumar Shrivastava
The issue seems to be related to https://salesforce.stackexchange.com/questions/24108/post-multipart-without-base64-encoding-the-body/33326

So i suggest take a look at the code here 

https://github.com/muenzpraeger/salesforce-einstein-platform-apex/blob/master/force-app/main/default/classes/Einstein_HttpBodyPart.cls

Enhance your class HttpFormBuilder class to include the below method
 
/**
     *  Write a key-value pair to the form's body for a blob.
     */
    public static string WriteBlobBodyParameter(string key, string file64, string fileName) {

        String mimeType = resolveMimeType(fileName);

        string contentDisposition = 'Content-Disposition: form-data; name="' + key + '"; filename="'+fileName+'"';
        string contentDispositionCrLf = contentDisposition + '\r\n';
        blob contentDispositionCrLfBlob = blob.valueOf(contentDispositionCrLf);
        string contentDispositionCrLf64 = EncodingUtil.base64Encode(contentDispositionCrLfBlob);
        string content = SafelyPad(contentDisposition, contentDispositionCrLf64, '\r\n');

        string contentTypeHeader = 'Content-Type: ' + mimeType;
        string contentTypeCrLf = contentTypeHeader + '\r\n\r\n';
        blob contentTypeCrLfBlob = blob.valueOf(contentTypeCrLf);
        string contentTypeCrLf64 = EncodingUtil.base64Encode(contentTypeCrLfBlob);
        content += SafelyPad(contentTypeHeader, contentTypeCrLf64, '\r\n\r\n');

        integer file64Length = file64.length();
        String last4Bytes = file64.substring(file64.length()-4,file64.length());

        // Avoid padding the file data with spaces, which SafelyPad does
        // http://salesforce.stackexchange.com/a/33326/102
        EndingType ending = EndingType.None;
        if (last4Bytes.endsWith('==')) {
            // The '==' sequence indicates that the last group contained only one 8 bit byte
            // 8 digit binary representation of CR is 00001101
            // 8 digit binary representation of LF is 00001010
            // Stitch them together and then from the right split them into 6 bit chunks
            // 0000110100001010 becomes 0000 110100 001010
            // Note the first 4 bits 0000 are identical to the padding used to encode the
            // second original 6 bit chunk, this is handy it means we can hard code the response in
            // The decimal values of 110100 001010 are 52 10
            // The base64 mapping values of 52 10 are 0 K
            // See http://en.wikipedia.org/wiki/Base64 for base64 mapping table
            // Therefore, we replace == with 0K
            // Note: if using \n\n instead of \r\n replace == with 'oK'
            last4Bytes = last4Bytes.substring(0,2) + '0K';
            file64 = file64.substring(0,file64.length()-4) + last4Bytes;
            // We have appended the \r\n to the Blob, so leave footer as it is.
            ending = EndingType.CrLf;
        } else if (last4Bytes.endsWith('=')) {
            // '=' indicates that encoded data already contained two out of 3x 8 bit bytes
            // We replace final 8 bit byte with a CR e.g. \r
            // 8 digit binary representation of CR is 00001101
            // Ignore the first 2 bits of 00 001101 they have already been used up as padding
            // for the existing data.
            // The Decimal value of 001101 is 13
            // The base64 value of 13 is N
            // Therefore, we replace = with N
            last4Bytes = last4Bytes.substring(0,3) + 'N';
        	file64 = file64.substring(0,file64.length()-4) + last4Bytes;
            // We have appended the CR e.g. \r, still need to prepend the line feed to the footer
            ending = EndingType.Cr;
        }

        content += file64;

        content += WriteBoundary(ending);
        return content;
    }

Let me know if you still run into issues with it!!

All Answers

Narender Singh(Nads)Narender Singh(Nads)
P.S. Authentication is not a problem. 
Mohith Kumar ShrivastavaMohith Kumar Shrivastava
The issue seems to be related to https://salesforce.stackexchange.com/questions/24108/post-multipart-without-base64-encoding-the-body/33326

So i suggest take a look at the code here 

https://github.com/muenzpraeger/salesforce-einstein-platform-apex/blob/master/force-app/main/default/classes/Einstein_HttpBodyPart.cls

Enhance your class HttpFormBuilder class to include the below method
 
/**
     *  Write a key-value pair to the form's body for a blob.
     */
    public static string WriteBlobBodyParameter(string key, string file64, string fileName) {

        String mimeType = resolveMimeType(fileName);

        string contentDisposition = 'Content-Disposition: form-data; name="' + key + '"; filename="'+fileName+'"';
        string contentDispositionCrLf = contentDisposition + '\r\n';
        blob contentDispositionCrLfBlob = blob.valueOf(contentDispositionCrLf);
        string contentDispositionCrLf64 = EncodingUtil.base64Encode(contentDispositionCrLfBlob);
        string content = SafelyPad(contentDisposition, contentDispositionCrLf64, '\r\n');

        string contentTypeHeader = 'Content-Type: ' + mimeType;
        string contentTypeCrLf = contentTypeHeader + '\r\n\r\n';
        blob contentTypeCrLfBlob = blob.valueOf(contentTypeCrLf);
        string contentTypeCrLf64 = EncodingUtil.base64Encode(contentTypeCrLfBlob);
        content += SafelyPad(contentTypeHeader, contentTypeCrLf64, '\r\n\r\n');

        integer file64Length = file64.length();
        String last4Bytes = file64.substring(file64.length()-4,file64.length());

        // Avoid padding the file data with spaces, which SafelyPad does
        // http://salesforce.stackexchange.com/a/33326/102
        EndingType ending = EndingType.None;
        if (last4Bytes.endsWith('==')) {
            // The '==' sequence indicates that the last group contained only one 8 bit byte
            // 8 digit binary representation of CR is 00001101
            // 8 digit binary representation of LF is 00001010
            // Stitch them together and then from the right split them into 6 bit chunks
            // 0000110100001010 becomes 0000 110100 001010
            // Note the first 4 bits 0000 are identical to the padding used to encode the
            // second original 6 bit chunk, this is handy it means we can hard code the response in
            // The decimal values of 110100 001010 are 52 10
            // The base64 mapping values of 52 10 are 0 K
            // See http://en.wikipedia.org/wiki/Base64 for base64 mapping table
            // Therefore, we replace == with 0K
            // Note: if using \n\n instead of \r\n replace == with 'oK'
            last4Bytes = last4Bytes.substring(0,2) + '0K';
            file64 = file64.substring(0,file64.length()-4) + last4Bytes;
            // We have appended the \r\n to the Blob, so leave footer as it is.
            ending = EndingType.CrLf;
        } else if (last4Bytes.endsWith('=')) {
            // '=' indicates that encoded data already contained two out of 3x 8 bit bytes
            // We replace final 8 bit byte with a CR e.g. \r
            // 8 digit binary representation of CR is 00001101
            // Ignore the first 2 bits of 00 001101 they have already been used up as padding
            // for the existing data.
            // The Decimal value of 001101 is 13
            // The base64 value of 13 is N
            // Therefore, we replace = with N
            last4Bytes = last4Bytes.substring(0,3) + 'N';
        	file64 = file64.substring(0,file64.length()-4) + last4Bytes;
            // We have appended the CR e.g. \r, still need to prepend the line feed to the footer
            ending = EndingType.Cr;
        }

        content += file64;

        content += WriteBoundary(ending);
        return content;
    }

Let me know if you still run into issues with it!!
This was selected as the best answer
Narender Singh(Nads)Narender Singh(Nads)
Hi Mohith,
It may sound weird but the issue got fixed on it's own, didn't make any changes to my code. 😅
Still marking your answer as best assuming it's a better version of old HttpFormBuilder class. :)