ホーム DoRuby EXCEL(VBA)から、HTTP通信でファイルをアップロードしてみよう。
EXCEL(VBA)から、HTTP通信でファイルをアップロードしてみよう。
 

EXCEL(VBA)から、HTTP通信でファイルをアップロードしてみよう。

この記事はアピリッツの技術ブログ「DoRuby」から移行した記事です。情報が古い可能性がありますのでご注意ください。

はじめに

EXCEL(VBA)で、JSONデータをHTTP通信する方法に関しては、前回の記事を参考して下さい。
EXCELでファイルリストを管理し、PCに格納しているファイルもアップロード出来れば、作業効率が図れると思ったのがキッカケです。
それを実現する簡単な方法をご紹介します。

※実際は、そんなに簡単ではありませんでした(汗)

参考にさせて頂いたサイト
・WSH + VBScriptでのサンプルを参照して下さい。
・boundaryに関してのこちらの仕様を参照して下さい。

ファイルを送信するには

1.まずHTMLを使ったファイルを送信する処理を確認してみる

ファイルを送信するHTMLは、以下のようになります。
enctype="multipart/form-data" を付ける事でファイルを送信することが可能となるのです。

<form action="http://localhost:8080/" method="POST" enctype="multipart/form-data">
  <input type="text" name="id" /><br />
  <input type="text" name="description" /><br />
  <input type="file" name="file" /><br />
  <input type="submit" name="submit" value="send"/>
</form>

HTMLイメージ

HTMLイメージ

選択しているテキストファイルの内容

選択しているテキストファイルの内容
※使用しているサクラエディタは、こちら

2.HTTP通信の内容はどうなるの?

HTMLのフォームを使ったファイルアップロードの仕様はRFC1867を参照して下さい。
実際に送信し、proxyツールfiddler を使った、HTTP通信内容を確認した様子が以下の図となります。
※HTTPの構文に関しては、こちらのページが参考になります。

リクエスト内容

HTTPヘッダ部分の content-Type が、 multipart/form-data となっていますね。
ここでポイントとなるのは、 boundary= に設定されている”境界線”です。

3.multipart/form-data の構造

詳しくはこちらのサイトを参照して下さい。その中の一部を抜粋して説明します。

■HTTPヘッダ部

Content-Typeの内容

boundaryは、1文字以上70文字以下でなければなりません。
ブラウザによっては、40文字程度の場合もあります。

■ボディ部

ボディ部は、以下の3種類に分類されています。

フォームデータ(赤枠参照)

boundary は、先頭に--HTTPヘッダのboundaryCRLF

テキストのパラメタ

ファイルデータ(バイナリデータも同様)(赤枠参照)

boundary は、先頭に--HTTPヘッダのboundaryCRLF

ファイルを選択時のパラメタ

フッタ(赤枠参照)

boundary は、先頭に--HTTPヘッダのboundary+後尾に--CRLF

フッタ内容

実際にEXCEL(VBA)からHTTP通信にて画像等のファイルを送信

実際簡単ではなかったポイントが、送信するファイルが、テキストではなく画像やEXCELファイル等のバイナリデータの場合です。
送信するmultipart/form-dataパラメタが、テキストであればString型の結合で対応できるのですが、画像やEXCELファイルはByte型になり、容易にはString型Byte型の結合ができませんでした。


解決方法として、上部でも記載さしている参考サイトと同様に、ファイル操作等に使用するADODB.Streamをパラメタ生成用の領域として使用することで、String型Byte型結合を実現しています。


ちなみに、今回もJSONを生成している部分は、こちらを使用させて頂いて居ます。

■サンプルプログラム

①メイン処理

Const adTypeBinary = 1
Const adTypeText = 2

Const adBTypeContent = 1
Const adBTypeBody = 2
Const adBTypeFooter = 3

Public Function UploadFile() As Boolean


    Dim FilePath As String: FilePath = "d:\証明写真サンプル.jpg"

    Dim strMethod As String: strMethod = "POST"
    Dim strUri As String: strUri = "http://localhost"
    Dim strResult As String

    '---------------------------------
    ' リクエストパラメタ用の領域を生成
    '---------------------------------
    Dim tempParamStream As Object
    Set tempParamStream = CreateObject("ADODB.Stream")
    tempParamStream.Open

    '---------------------------------
    ' リクエストパラメタ作成
    '---------------------------------
    Dim FileName As String
    FileName = Dir(FilePath)

    Dim JsonObject As Object
    Set JsonObject = New Dictionary

    JsonObject.Add "name", FileName
    JsonObject.Add "parent", New Dictionary
    JsonObject("parent").Add "id", 0

    If SetNomarlParameter(tempParamStream, "attributes", JsonConverter.ConvertToJson(JsonObject)) Then
    End If

    If SetFileParmater(tempParamStream, "file", FilePath, "application/octet-stream") Then
    End If

    If SetEndParameter(tempParamStream) Then
    End If

    '---------------------------------
    ' リクエストパラメタ取得
    '---------------------------------
    Dim snedParameter As Variant
    GetSendParameter snedParameter, tempParamStream

    '---------------------------------
    ' リクエスト
    '---------------------------------
    Dim objHTTP As Object
    Set objHTTP = CreateObject("msxml2.xmlhttp")
    objHTTP.Open strMethod, strUri, False
    objHTTP.setRequestHeader "Content-Type", "multipart/form-data; boundary=" + getBoundy(adBTypeContent)
    objHTTP.send snedParameter

    statusCode = objHTTP.status

    strResult = StrConv(objHTTP.responsebody, vbUnicode)
    Set objHTTP = Nothing

    UploadFile = True
End Function

②データフォームのパラメタ設定

Private Function SetNomarlParameter( _
                    ByRef tempParamStream As Object, _
                    ByVal fname As String, _
                    ByVal fvalue As String) As Boolean

    If fvalue <> "" Then

        ChangeStreamType tempParamStream, adTypeText

        Dim params As String
        params = ""
        params = params + getBoundy(adBTypeBody)
        params = params + "Content-Disposition: form-data; name=""" + fname + """" + vbCrLf
        params = params + vbCrLf
        params = params + fvalue + vbCrLf

        tempParamStream.WriteText params

    End If

    SetNomarlParameter = True
End Function

③ファイル(バイナリデータ)のパラメタ設定

Private Function SetFileParmater( _
                            ByRef tempParamStream As Object, _
                            ByVal fname As String, _
                            ByVal fvalue As String, _
                            ByVal fct As String) As Boolean

    '-------------------------------------
    ' テキストデータ
    '-------------------------------------
    ChangeStreamType tempParamStream, adTypeText

    Dim params As String
    params = ""
    params = params + getBoundy(adBTypeBody)
    params = params + "Content-Disposition: form-data; name=""" + fname + """; filename=""" + fvalue + """" + vbCrLf
    params = params + "Content-Type: " + fct + vbCrLf
    params = params + vbCrLf

    tempParamStream.WriteText params


    '-------------------------------------
    ' バイナリデータ
    '-------------------------------------
    ChangeStreamType tempParamStream, adTypeBinary

    Dim fileStream As Object
    Set fileStream = CreateObject("ADODB.Stream")
    fileStream.Type = adTypeBinary
    fileStream.Open
    fileStream.LoadFromFile fvalue

    tempParamStream.Write fileStream.Read()

    fileStream.Close
    Set fileStream = Nothing

    SetFileParmater = True
End Function

④フッタのパラメタ設定

Private Function SetEndParameter( _
                    ByRef tempParamStream As Object) As Boolean

    ChangeStreamType tempParamStream, adTypeText
    tempParamStream.WriteText getBoundy(adBTypeFooter)

    SetEndParameter = True
End Function

⑤送信するパラメタを取得

Private Function GetSendParameter( _
                    ByRef parameter As Variant, _
                    ByRef stream As Object) As Boolean

    ChangeStreamType stream, adTypeBinary
    stream.Position = 0
    parameter = stream.Read

    stream.Close
    Set stream = Nothing

    GetSendParameter = True
End Function

⑥パラメタ用の領域の状態を変更する

最初に、p = stream.Positionで現在のポジションを取得しているのは、状態を変更したことでポジションが変わってしまうためです。

Private Function ChangeStreamType( _
                    ByRef stream As Object, _
                    ByVal adType As Integer) As Boolean
    Dim p As Long
    p = stream.Position
    stream.Position = 0
    stream.Type = adType

    If adType = adTypeText Then
        stream.Charset = "UTF-8"
    End If

    stream.Position = p

    ChangeStreamType = True
End Function

⑦Boundy 情報取得

Boundyは、同じ文字列を使用することになるので、変数をstaticにすることで、1度生成した文字列を使って、用途に合わせたBoundyデータを復帰するようにしています。
各種参考させて頂いたサイトでは、Boundyデータは固定の文字列としていますが、折角なのでランダム文字列を生成年月日時分秒までを追加した文字列生成するようにしてみました。

Private Function getBoundy(ByVal adType As Integer) As String

    Static sBoundy As String

    If sBoundy = "" Then

        Dim multipartChars As String: multipartChars = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
        Dim boundary As String: boundary = "--------------------"

        Dim i, point As Integer

        For i = 1 To 20
            Randomize
            point = Int(Len(multipartChars) * Rnd + 1)
            boundary = boundary + Mid(multipartChars, point, 1)
        Next

        sBoundy = boundary + Format(Now, "yyyymmddHHMMSS")

    End If

    Select Case adType
    Case adBTypeContent
        getBoundy = sBoundy
    Case adBTypeBody
        getBoundy = "--" + sBoundy + vbCrLf
    Case adBTypeFooter
        getBoundy = vbCrLf + "--" + sBoundy + "--" + vbCrLf
    End Select

End Function

送信パラメタの生成の経過

サンプルプログラムでは、"d:\証明写真サンプル.jpg" と画像ファイルを指定していますが
説明しやすいように、別のバイナリファイル(d:\test.bin)を用意して生成過程を確認してみます。

テキストデータを設定する場合には、tempParamStreamをテキスト状態に変更
ファイル(バイナリデータ)を結合する場合には、tempParamStreamをバイナリ状態に変更することで結合(パラメタを生成)を実現します。

test.bin の内容

テスト用バイナリデータ

テキストパラメタ設定時

    If SetNomarlParameter(tempParamStream, "attributes", JsonConverter.ConvertToJson(JsonObject)) Then
    End If

上記を実行したタイミングでは、tempParamStreamのは以下の通り
※先頭の0xEF 0xBB 0xBF はBOM付き(UTF-8)の場合を表すコードとなります。

テキストパラメタまでの送信パラメタ

ファイルパラメタ設定時

    If SetFileParmater(tempParamStream, "file", FilePath, "application/octet-stream") Then
    End If

上記を実行したタイミングでは、tempParamStreamのは以下の通り

バイナリデータ設定までの送信パラメタ

フッタまで設定時

    If SetEndParameter(tempParamStream) Then
    End If

上記を実行したタイミングでは、tempParamStreamのは以下の通り

フッタを追加した時の送信パラメタ

まとめ

BOX API にて、もしもEXCEL(VBA)でファイルをアップロードさせるには今回の作成した処理を使って送信することが出来るはずです。
という事で、次回は、またBOX API に戻りPOST送信する処理を考えてみようと思っています。

記事を共有

最近人気な記事