ストレージ関係の REST API を暇を見つけながらちろちろやっている最中ですが、とりあえずアップロード(ただし条件付き)ができるところまではできましたので、恒例のアクティビティ化をしてみました。
条件付き、というのは本来ストレージへアップロードを行う場合には何点か考慮しなくてはいけないポイントがあり、現時点ではその考慮点の一部にしか対応できていないためです。何を考える必要があるかと言うと、次の点になります。
- アップロードするファイルの容量
Windows Azure のストレージには大きくわけて 2 つの方式が提供されています。一つは BlockBlob と呼ばれる種類で、これは 64MB までの一つのファイル、または 4MB 単位に分割したファイル群という形でアップロードが可能です。分割するメリットは同時に複数セッションを利用してアップロード速度を向上させたり、エラー発生時のリトライ処理量を減らすなどがあたります。もう一つは PageBlob と呼ばれる種類でこちらは 1TB までのファイルを扱う事が可能な点と、バイト単位(イメージとしてはHDD等のセクタ)にて読み書きの処理が行える点がポイントとなります。現在ベータ版として提供されている Azure Drive (ストレージを通常の NTFS ドライブに見せかけて利用できる)は PageBlob をベースに作成されていると思われます。
これらの仕様があるので、アップロードするファイル容量により BlockBlob か PageBlob かを分ける必要がまず発生します。そして BlockBlob を利用する際はどのような単位でブロックを区切るか、ブロックを束ねるためのブロックリストの作成、といった追加処理が必要となります。
今回はまだ実装途中というのもあり、単純に指定したファイルを BlockBlob に分割なしでアップロードするところまでになっています。REST API としては PutBlob、PutBlock、PutBlockList とそれぞれ別の API を利用する事になりますが、アクティビティとして考えた場合これらは1つにまとめられていなければ使い勝手が悪いと考えられますので、最終的には全てをまとめた形にまで実装する予定です。
長くなりますが一気にソースを掲載します。
1: Imports System.ComponentModel
2: Imports System.Activities.Presentation.PropertyEditing
3: Imports System.Text
4: Imports System.Net
5:
6: Public Class PutAzureBlobsActivity
7: Inherits AzureStorageActivityBase
8:
9: ''' <summary>1ブロックの最大サイズ</summary>
10: Private Const BLOCKSIZEBYTE As Long = 4194304 '4MB
11: ''' <summary>ブロブの1ファイル通常時最大サイズ</summary>
12: Private MAX_BLOBSIZE As Long = 67108864 '64MB
13: ''' <summary>ブロブの1ファイル最大サイズ</summary>
14: Private Const MAX_BLOBSIZEUSEBLOCK As Long = 214748364800 '200GB
15:
16: <Category("アップロード設定")>
17: <DisplayName("ページ形式を利用")>
18: Public Property UsePage As Boolean = False
19:
20: <Category("アップロード設定")>
21: <DisplayName("アップロードするファイル")>
22: <Editor(GetType(AnyFileBrowserDialogPropertyValueEditor), GetType(DialogPropertyValueEditor))>
23: Public Property UploadFilename As String
24: <Category("アップロード設定")>
25: <DisplayName("ブロブ上でのファイル名")>
26: Public Property BlobFileName As String
27:
28: <Category("メタデータ")>
29: <DisplayName("メタデータ種類名")>
30: Public Property MetadataName As String
31: <Category("メタデータ")>
32: <DisplayName("メタデータ値")>
33: Public Property MetadataValue As String
34:
35: Public Sub New()
36: Me.DisplayName = "Azure ブロブへアップロード"
37: End Sub
38:
39: Protected Overrides Sub AddRequestHeader(wq As System.Net.WebRequest)
40: Dim content As String = Me.GetContentType
41: wq.ContentType = content
42: wq.Headers.Add("x-ms-blob-content-type", content)
43:
44: '対象ファイルのサイズを取得
45: Dim flSize As Long = My.Computer.FileSystem.GetFileInfo(Me.UploadFilename).Length
46: If Me.UsePage Then
47: 'Page Blob を利用
48: wq.ContentLength = 0
49: wq.Headers.Add("x-ms-blob-content-length", flSize.ToString)
50: wq.Headers.Add("x-ms-blob-type", "PageBlob")
51: Else
52: 'Block Blob
53: wq.ContentLength = flSize
54: wq.Headers.Add("x-ms-blob-type", "BlockBlob")
55: End If
56: 'メタデータの設定
57: If (Me.MetadataName IsNot Nothing) AndAlso (Me.MetadataName.Trim <> "") Then
58: wq.Headers.Add("x-ms-meta-" + Me.MetadataName, Web.HttpUtility.UrlEncode(Me.MetadataValue))
59: End If
60:
61: MyBase.AddRequestHeader(wq)
62: End Sub
63:
64: Protected Overrides Sub SetHttpMethod(wq As System.Net.WebRequest)
65: wq.Method = "PUT"
66: End Sub
67:
68: Protected Overrides Function CreateApiPath() As System.Uri
69: Return New Uri(String.Format(STORAGE_API_PATH, Me.Account, Me.ResourceName) _
70: + "/" + Me.BlobFileName)
71: End Function
72:
73: Private Overloads Function CreateBodyMessage() As Byte()
74: Return My.Computer.FileSystem.ReadAllBytes(Me.UploadFilename)
75: End Function
76:
77: Protected Overrides Function GetWebData() As String()
78: Dim resultStrings As String = ""
79: Dim resultStatus As String = ""
80: Dim resultRequestID As String = ""
81: Dim resultHeaders As New StringBuilder
82: Try
83: Dim requestUri As Uri = Me.CreateApiPath()
84: Dim localWebRequest = TryCast(HttpWebRequest.Create(requestUri), HttpWebRequest)
85: Me.SetHttpMethod(localWebRequest)
86:
87: Dim bodyMessageBytes = Me.CreateBodyMessage()
88: If bodyMessageBytes IsNot Nothing Then
89: localWebRequest.ContentLength = bodyMessageBytes.Length
90: End If
91: Me.AddRequestHeader(localWebRequest)
92:
93: If bodyMessageBytes IsNot Nothing Then
94: Using reqStream = localWebRequest.GetRequestStream
95: reqStream.Write(bodyMessageBytes, 0, bodyMessageBytes.Length)
96: End Using
97: End If
98:
99: Using webResponse = TryCast(localWebRequest.GetResponse(), HttpWebResponse)
100: For Each child In webResponse.Headers.AllKeys
101: resultHeaders.Append(child + ":" + webResponse.Headers(child).ToString + ControlChars.NewLine)
102: Next
103: resultStatus = webResponse.StatusCode.ToString
104: resultRequestID = webResponse.Headers("x-ms-request-id")
105: Using responseStream = webResponse.GetResponseStream()
106: Using reader As New System.IO.StreamReader(responseStream)
107: resultStrings = reader.ReadToEnd
108: End Using
109: End Using
110: End Using
111:
112: Catch ex As Exception
113: resultStrings = ex.Message
114: End Try
115: Return New String() {resultStrings, resultStatus, resultRequestID, resultHeaders.ToString}
116: End Function
117:
118: ''' <summary>拡張子による Content-Type の取得</summary>
119: Private Function GetContentType() As String
120: If Not System.IO.File.Exists(Me.UploadFilename) Then Return Nothing
121: Dim resultType As String = ""
122: Dim fileExt As String = System.IO.Path.GetExtension(Me.UploadFilename)
123: Select Case fileExt.ToLower.Replace("."c, "")
124: Case "txt" : resultType = "text/plain" 'テキスト文書 .txt text/plain
125: Case "csv" : resultType = "text/csv" 'CSVファイル .csv text/csv
126: Case "tsv" : resultType = "text/tab-separated-values" 'TSVファイル .tsv text/tab-separated-values
127: Case "doc", "docx" : resultType = "application/msword" 'ワード文書 .doc application/msword
128: Case "xls", "xlsx" : resultType = "application/vnd.ms-excel" 'エクセルシート .xls application/vnd.ms-excel
129: Case "ppt", "pptx" : resultType = "application/vnd.ms-powerpoint" 'パワーポイント .ppt application/vnd.ms-powerpoint
130: Case "pdf" : resultType = "application/pdf" 'PDF文書 .pdf application/pdf
131: Case "htm", "html" : resultType = "text/html" 'HTML文書 .html .htm text/html
132: Case "css" : resultType = "text/css" 'スタイルシート .css text/css
133: Case "js" : resultType = "text/javascript" 'JavaScriptファイル .js text/javascript
134: Case "jpg", "jpeg" : resultType = "image/jpeg" 'JPEG .jpg .jpeg image/jpeg
135: Case "png" : resultType = "image/png" 'PNG .png image/png
136: Case "gif" : resultType = "image/gif" 'GIF .gif image/gif
137: Case "bmp" : resultType = "image/bmp" 'ビットマップ .bmp image/bmp
138: Case "mp3" : resultType = "audio/mpeg" 'MP3 .mp3 audio/mpeg
139: Case "mp4" : resultType = "audio/mp4" 'MP4 .m4a audio/mp4
140: Case "wav" : resultType = "audio/x-wav" 'WAV .wav audio/x-wav
141: Case "midi" : resultType = "audio/midi" 'MIDI .mid .midi audio/midi
142: Case "mpeg" : resultType = "video/mpeg" 'MPEG .mpg .mpeg video/mpeg
143: Case "wmv" : resultType = "video/x-ms-wmv" 'WMV .wmv video/x-ms-wmv
144: Case "swf" : resultType = "application/x-shockwave-flash" 'Flash (Shockwave) .swf application/x-shockwave-flash
145: Case "3g2" : resultType = "video/3gpp2" '3GPP2 .3g2 video/3gpp2
146: Case "zip" : resultType = "application/zip" 'ZIP形式 .zip application/zip
147: Case "lzh", "lha" : resultType = "application/x-lzh" 'LZH形式 .lha .lzh application/x-lzh
148: Case "tar", "tgz" : resultType = "application/x-tar" 'tar / tar+gzip形式 .tar .tgz application/x-tar
149: Case "exe", "com" : resultType = "application/octet-stream" '実行ファイル .exe application/octet-stream
150: Case Else : resultType = "application/octet-stream"
151: End Select
152: Return resultType
153: End Function
154:
155: End Class
※ベースとなるクラスは過去の記事にて紹介したソース一式に含まれています。
実装上のポイントは次の点です。
- リクエストヘッダにて x-ms-blob-type で Blob の種類を指定する必要がある
実際にはソース上で指定している、x-ms-blob-content-type とか x-ms-blob-content-length とかは必須項目ではないので省略しても構わないのですが、ブラウザから参照する際など Content-Type が指定されていなければ色々と問題がありますので、できる限りは指定するようにしています。その際の Content-Type 判定ですが、拡張子から行う方法を採っています。楽なのでw
その点以外には特に難しいポイントはなく、HttpWebRequest から RequestStorm を利用してバイナリ形式で書き込んであげればアップロードは完了です。このあたりは webEDI とかを扱った事のある方でしたら特に悩むこともないと思います。
ワークフロー上で利用する場合はこのような形で設定します。これだけで Azure のストレージにファイルがアップロードできます。
実行後に Storage Explorer にて実際のストレージの中身を確認すると上記のように表示されます。
ちゃんと Content-Type も指定されていますので、ブラウザで直接アクセスを行った場合も問題なく扱われています。
0 件のコメント:
コメントを投稿