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エンコードした文字列に
それぞれの文字をアペンドしています。