2015-07-31

Apexからmultipart/form-data形式で送信してみる。

Apexからmultipart/form-data形式でHTTPリクエストを行うときは

自前でBodyをゴリゴリ書いていく必要があるのですが、その際にファイルなどのバイナリデータを

含めるときの対応に関して書いていきます。

パターン1: HTTP Request Bodyをテキストで送信

受信側(API提供側)でContent-Transfer-Encoding: base64に対応しているケースで利用可能です。

Apexだと以下のようなコードになります(Boxのサンプル)

Attachment atch = [SELECT id, Body FROM Attachment WHERE id = '00PA000000gBAiI'];

HttpRequest req = new HttpRequest();
String tokenKey = 'Input Your Token';
String boundary = 'boundary';

req.setHeader('Authorization', 'Token ' + tokenKey);
req.setHeader('Content-Type','multipart/form-data; boundary=' + boundary);
req.setMethod('POST');
req.setEndpoint('https://upload.view-api.box.com/1/documents');

String body = '--'+boundary+'\r\nContent-Disposition: form-data; name="file"; filename="hoge.pdf";\r\nContent-Type: application/pdf\r\nContent-Transfer-Encoding: base64\r\n\r\n' + 
EncodingUtil.base64Encode(atch.Body) + '\r\n--' + boundary + '--';

req.setBody(body);
req.setTimeout(120000);

Http http = new Http();
HTTPResponse res = http.send(req);
System.debug(res.getBody());

multipart/form-dataの形式でBase64エンコードしたバイナリデータをBodyに載せただけで

非常にシンプルに実装できます。

パターン2: HTTP Request Bodyをバイナリとして送信

受信側がContent-Transfer-Encoding: base64に対応していないパターンになります。

Apexでは現時点ではバイナリの連結が出来ません。

なのでバイナリを連結するためにはBase64エンコードした文字列を”うまい具合”に連結したものを

最後にBase64デコードする必要があります。

ただし、Base64エンコードは6bit毎に4文字ずつ(=3byte)エンコーディングしていくので

余りが出る場合は=によるパディングがされて

base64エンコードした文字列の桁数は4の倍数になります。

そのためパディングも含めて連結してしまうと、デコーディングが出来なくなってしまいます。

つまり、Apexでバイナリ連結するためにはBase64エンコードした結果がパディングを含まない

つまり、もとのバイナリのバイト数が3の倍数である必要があります。

 

Apexのサンプルコードは以下になります。

getMultiPartBodyメソッドは参考URL丸パクリです。

参考URL→http://blog.enree.co/2013/01/salesforce-apex-post-mutipartform-data.html

public with sharing class MultipartHttpRequest {
    public String send(String reqEndpoint, String header, String boundary, Blob blobBody) {
        HttpRequest req = new HttpRequest();
        String tokenKey = 'Input Your Token';
        req.setHeader('Authorization', 'Token ' + tokenKey);
        req.setHeader('Content-Type','multipart/form-data; boundary=' + boundary);
        req.setMethod('POST');
        req.setEndpoint(reqEndPoint);
        req.setBodyAsBlob(this.getMultiPartBody(header, boundary, blobBody));
        req.setTimeout(120000);
 
        Http http = new Http();
        HTTPResponse res = http.send(req);
        return res.getBody();
    }
    
    public Blob getMultiPartBody(String header, String boundary, Blob file_body){
        String footer = '--'+boundary+'--';             
        String headerEncoded = EncodingUtil.base64Encode(Blob.valueOf(header+'\r\n\r\n'));
        while(headerEncoded.endsWith('=')){
            header+=' ';
            headerEncoded = EncodingUtil.base64Encode(Blob.valueOf(header+'\r\n\r\n'));
        }
        String bodyEncoded = EncodingUtil.base64Encode(file_body);
      
        Blob bodyBlob = null;
        String last4Bytes = bodyEncoded.substring(bodyEncoded.length()-4,bodyEncoded.length());
 
        if(last4Bytes.endsWith('==')) {
            last4Bytes = last4Bytes.substring(0,2) + '0K';
            bodyEncoded = bodyEncoded.substring(0,bodyEncoded.length()-4) + last4Bytes;
            // We have appended the \r\n to the Blob, so leave footer as it is.
            String footerEncoded = EncodingUtil.base64Encode(Blob.valueOf(footer));
            bodyBlob = EncodingUtil.base64Decode(headerEncoded+bodyEncoded+footerEncoded);
        } else if(last4Bytes.endsWith('=')) {
            last4Bytes = last4Bytes.substring(0,3) + 'N';
            bodyEncoded = bodyEncoded.substring(0,bodyEncoded.length()-4) + last4Bytes;
            // We have appended the CR e.g. \r, still need to prepend the line feed to the footer
            footer = '\n' + footer;
            String footerEncoded = EncodingUtil.base64Encode(Blob.valueOf(footer));
            bodyBlob = EncodingUtil.base64Decode(headerEncoded+bodyEncoded+footerEncoded);              
        } else {
            // Prepend the CR LF to the footer
            footer = '\r\n' + footer;
            String footerEncoded = EncodingUtil.base64Encode(Blob.valueOf(footer));
            bodyBlob = EncodingUtil.base64Decode(headerEncoded+bodyEncoded+footerEncoded);  
        }
        return bodyBlob;
    }   
}


Attachment atch = [SELECT id, Body FROM Attachment WHERE id = '00PA000000gBAiI'];
String boundary = 'boundary';
String header = '--'+boundary+'\r\nContent-Disposition: form-data; name="file"; filename="hoge.pdf";\nContent-Type: application/pdf';
MultipartHttpRequest req = new MultipartHttpRequest();
System.debug(req.send('https://upload.view-api.box.com/1/documents', header, boundary, atch.body));

見ての通り結構面倒ですorz

バイト数を合わせるために元の文字列をスペースでパディングしたり

後続の改行コード(\r\n)を含めたりしてバイト数を調整しているのがミソで

"0K"はbase64デコードすると"\r\n"に"K"は"\r"になるのでbase64エンコードした文字列に

それぞれの文字をアペンドしています。

このエントリーをはてなブックマークに追加