Envoi des fichiers depuis Salesforce à un Webservice Externe

Salesforce est une plateforme qui offre beaucoup de possibilités aux développeurs mais avec beaucoup de contraintes aussi. Ces contraintes rendent plus passionnant le développement pour des développeurs qui sont toujours à la recherche de nouveaux défis.

Voici un exemple :

Service tiers :
Nous disposons un webservice qui consomme du multipart comme type de contenu. C’est à dire un mixte de texte et de binaire.

Les données attendues sont :

  • Un champ message qui contient un texte libre.
  • Un champ attachedFile qui contient un fichier binaire.

Salesforce :
Depuis la Console Service, nous disposons un formulaire développé en LWC qui permet à l’utilisateur de renseigner un message et attacher un fichier (image, pdf, …) puis de soumettre.

La question est maintenant, comment faire côté Apex pour envoyer les informations à notre service externe ?

Proposition de solution :

Transformer le contenu du fichier en base64 depuis le composant LWC

    handleUploadFinished(event) {
        this.loading = true;
        // Get the list of uploaded files
        const uploadedFiles = event.detail.files;
        const reader = new FileReader();
        reader.onload = (readerEvt) => {
            let data = readerEvt.target.result;
            this.attachedFile = data;
            this.loading = false;
        }
        reader.readAsDataURL(uploadedFiles[0]);
    }

Restitution du contenu binaire côté Apex

    private static final String RN = '\r\n';

    public static String contact(String left, String right) {
        Blob bytesLeft = EncodingUtil.base64Decode(left);
        Blob bytesRight = EncodingUtil.base64Decode(right);
        String combined = EncodingUtil.convertToHex(bytesLeft) + EncodingUtil.convertToHex(bytesRight);
        return EncodingUtil.base64Encode(EncodingUtil.convertFromHex(combined));
    }

    public static String createDatagram(String boundary, String field, String message) {
        return createDatagram(boundary, field, message, false);
    }

    public static String createDatagram(String boundary, String field, String message, boolean footer) {
        return createBase64Datagram(boundary, field, EncodingUtil.base64Encode(Blob.valueOf(message)), null, footer);
    }

    public static String createBase64Datagram(String boundary, String field, String encodedMessage, String filename, boolean footer) {
        String header = 'Content-Disposition: form-data; name="' + field + '"';
        if(String.isNotBlank(filename)) {
            header += '; filename="' + filename + '"';
        }

        String frn = '';
        if (encodedMessage.endsWith('==')) {
            encodedMessage = encodedMessage.substring(0, encodedMessage.length() - 2) + '0K';
        } else if (encodedMessage.endsWith('=')) {
            encodedMessage = encodedMessage.substring(0, encodedMessage.length() - 1) + 'N';
            frn = '\n';
        } else {
            frn = RN;
        }

        String headerPart = '--' +
                            boundary +
                            RN +
                            header;
        String encodedHeader = EncodingUtil.base64Encode(Blob.valueOf(headerPart));
        while (encodedHeader.endsWith('=')) {
            headerPart += ' ';
            encodedHeader = EncodingUtil.base64Encode(Blob.valueOf(headerPart));
        }
        String part1 = headerPart +
                RN +
                RN;
        String temp = contact(EncodingUtil.base64Encode(Blob.valueOf(part1)), encodedMessage);
        String lastPart = frn + (footer ? ('--' + boundary + '--') : '');
        return contact(temp, EncodingUtil.base64Encode(Blob.valueOf(lastPart)));
    }
  • La méthode public static String contact(String left, String right) permet de concaténer deux textes en base64. Nous en avons besoin pour construire facilement notre requête.
  • La méthode public static String createBase64Datagram(String boundary, String field, String encodedMessage, String filename, boolean footer) permet de créer une unité de donnée. Par exemple la partie du message et celle du fichier.
  • 0K (zéro K) correspond à l’encodage base64 de \r\n
  • N correspond à l’encodage de \r

La construction de la requête sous forme de base64.

    private String toBodyString(String boundary) {
        String message = this.message;
        String messageDatagram = createDatagram(boundary, 'message', message, String.isBlank(this.attachedFile));
        String attachedFileDatagram = null;
        if (String.isNotBlank(this.attachedFile)) {
            String base64 = this.attachedFile;
            String extension = 'png';
            if(String.isNotBlank(base64)) {
                if(base64.startsWith('data')) {
                    Integer index = base64.indexOf(',');
                    String preString = base64.substring(0, index);
                    base64 = base64.substring(index + 1);
                    String part1 = preString.split(';')[0];
                    extension = part1.split('/')[1];
                }
            }
            String fileName = this.caseNumber + '-' + this.notificationId + '.' + extension;
            attachedFileDatagram = createBase64Datagram(boundary, 'attachedFile', base64, fileName, true);
        }
        String bodyEncoded = messageDatagram;
        if(attachedFileDatagram != null) {
            bodyEncoded = contact(messageDatagram, attachedFileDatagram);
        }
        return bodyEncoded;
    }

L’envoi des données au webservice

    String boundary = '--------------------' + Integer.valueof((Math.random() * 1000)) + '-' + Integer.valueof((Math.random() * 1000));
    String body = toBodyString(boundary);
    Blob bodyBlob = EncodingUtil.base64Decode(body);
    HttpRequest req = new HttpRequest();
    req.setEndpoint('callout:mywebserviceNamedCredential/' + this.notificationId);
    req.setHeader('Authorization', 'Bearer ' + token.access_token);
    req.setHeader(CONTENT_TYPE, MULTIPART_FORM_DATA + '; boundary='+boundary);
    req.setHeader(CASE_ID, this.caseId);
    req.setMethod(POST);
    req.setBodyAsBlob(bodyBlob);