DIME Attachments S3 Bucket Browser 拡張
S3で1MB以上のデータを転送するためには、DIME という仕様を使う必要があります。AmazonのResourceCenterにあるサンプルS3 Bucket BrowserではDIMEを使用してデータのやり取りを行っていないので、今回はそこを拡張しようと思います。
まず、.NET FrameworkのWebサービスではDIMEを標準ではサポートしていないので、DIME Attachmentを使用するため、WSEをインストールします。ただし、最新のWSEではDIME Attachmentの部分をMTOMという仕様で置き換えてしまっているので、今回はWSE 1.0(注)を使用します。
注:WSE 2.0を使用してS3にアクセスしようとすると少々問題があるので(WSEの中のWS-Addressingの実装を強制され、サーバ側でエラーが発生する)、WSE 1.0を使用しています。
WSE 1.0 SP1 インストール
WSE 1.0 SP1 をダウンロードし、インストールします。
WSE 1.0 を参照に加える
Microsoft.Web.Serviceを参照に加えます。
コードの変更
Webサービスプロキシの親クラスをMicrosoft.Web.Services.WebServicesClientProtocolに変更
まず、Visual Studioの[ソリューションエクスプローラ]で[すべてのファイルを表示]するようにします。
そうすると、[Web References]のcom.amazonaws.s3を展開できるようになるので、Refence.csというファイルを開きます。これはwsdlで作成されたプロキシクラスです。
このReference.csの中にあるプロキシクラスの親クラスをSystem.Web.Services.Protocols.SoapHttpClientProtocolからMicrosoft.Web.Services.WebServicesClientProtocolに変更します。
public partial class AmazonS3 : Microsoft.Web.Services.WebServicesClientProtocol
S3コンストラクタ
プロキシクラスの設定を行います。
public S3(string sAccessKeyId, string sSecretKey)
{
m_sAccessKeyId = sAccessKeyId;
m_sSecretAccessKey = sSecretKey;
m_objS3.RequestSoapContext.Path.MustUnderstand = false; // WS-Routing は使用しないので、falseにする。
}
S3.PutObject(string bucketName, string keyName, string fileName, long contentLength, string contentType)メソッド追加
内容はPutObjectInlineからコピってきます。基本的にはPutObjectInlineと一緒ですが、DIME Attachmentにアップロードするファイルを追加しましょう。
m_objS3.RequestSoapContext.Attachments.Add(new Microsoft.Web.Services.Dime.DimeAttachment(contentType, Microsoft.Web.Services.Dime.TypeFormatEnum.MediaType, fileName));
objS3PutObjectResult = m_objS3.PutObject(bucketName, keyName, aobjMetaDataEntries, contentLength, null, 0, false, AccessKeyId, timeStamp, true, sSignature, null);
S3.GetObjectメソッド修正
Objectを取得するときは、Webメソッドの引数でDIMEかInlineか選択できます。なので、S3.GetObjectメソッドにbool isOver1MBObjectというObjectのサイズが1MBを超えているかどうか判断できる引数を追加します。Webサービスから取得したDIME AttachmentのStreamはそのままファイルに焼き付けたほうが効率がいいのですが、今回はGetObjectResult.Dataにバイト配列として格納してしまいます。(コードの修正が少なくて済むので…)
public GetObjectResult GetObject(string bucketName, string keyName, bool isOver1MBObject)
{
(中略)
try
{
objS3GetObjectResult = m_objS3.GetObject(bucketName, keyName, true, true, !isOver1MBObject, AccessKeyId, timeStamp, true, sSignature, null);
m_bSoapError = false;
m_sSoapErrorMessage = "";
if (isOver1MBObject)
{
Stream stream = m_objS3.ResponseSoapContext.Attachments[0].Stream;
byte[] buffer = new byte[(int)stream.Length];
stream.Read(buffer, 0, (int)stream.Length);
objS3GetObjectResult.Data = buffer;
}
}
(中略)
return objS3GetObjectResult;
}
Objectのサイズの取得
ファイルをアップロードしようとする場合はFileInfoなどで確認すれば良いのですが、ファイルをダウンロードしようとする場合は、listView1.Items[n].SubItems[3]から取得します。
コンパイルと実行
細々とした部分は省略しましたが、後はコンパイルして実行すれば1MB以上のデータをやりとりできるようになります。ちなみに、1MB以上のデータを画面右にドロップすると数秒間固まります(笑)。
今回はWSEを使用してDIME Attachmentを使用してファイルを送受信するようにS3 Bucket Browserを拡張しましたが、Webサービス関連は調べれば調べるほど恐ろしくなってきますね…まさに伏魔殿って感じです。最近の仕様もWSE関連は複雑化の一途を辿っている様に見えますし、もっと簡単にならないのかな…日本語のドキュメントももっと作って欲しいなーなんて思ってますw
# WSE 2.0 を使った場合についてですが、実は自分を中継サーバと勘違いさせ(m_objS3.Pipeline.IsIntermediary = true;)、パイプライン処理の最後でWS-Addressing等の情報を削除させることができます。これでいけることはいけるのですが、あまりにも強引な方法なので使わない方が良いと思います。それよりも、「Discussions in Web Services Enhancements」というマイクロソフトのニュースグループでWS-Addressingを無効にする話題に上っていて、パイプライン処理にカスタムクラスを差し込めばなんとかなりそうなことが書かれていました。ちょっとメールで問い合わせ中です。