【VBA】GAS宛POSTのStatus 0をサイズ別に実測

【VBA】GAS宛POSTのStatus 0をサイズ別に実測のアイキャッチ。VBEのコードウィンドウでSendのエラーを握りつぶし、Statusが0になるコードをハイライトした画像 VBA

VBAからGASのWeb Appへ、約10万字のJSONをまとめてPOSTしたところ、Statusが0で返ってきたことがあります。エラーの内容は分からず、レスポンスも空でした。当時のコードはOn Error Resume Nextでエラーを握りつぶしていたためです。短いJSONを送ったときは200が返りました。

当時は、JSONが大きいことが原因ではないかという提案を受けて、1件ずつ送る形に変えました。1件ずつにすると送れましたが、時間はかかりました。

そこで、通信オブジェクトとサイズを変えて、Status 0が出る条件を実測しました。GASへのPOSTが405になる件は、【VBA】GASへのPOSTが405になる原因と通信オブジェクト比較で扱っています。この記事では405は扱いません。

検証した環境

  • Excel 16.0、64bit Office、Windows 64bit
  • 受け側はGASのWeb App。doPostは受け取った本文の文字数と件数を返すだけで、シートには書きません
  • JSONはUTF-8のバイト列にして送りました
  • サイズは約1万・5万・10万・50万字(UTF-8で約16KB〜813KB)。各5回送っています
  • 比較したのは、WinHttp.WinHttpRequest.5.1、MSXML2.ServerXMLHTTP、MSXML2.XMLHTTPの3つです

結果1:サイズ別のStatus

通信オブジェクト1万字5万字10万字50万字
WinHttp200(5/5回)200(5/5回)200(5/5回)200(5/5回)
ServerXMLHTTP405(5/5回)405(5/5回)405(5/5回)405(5/5回)
XMLHTTPSendでエラー、Status 0(5/5回)Sendでエラー、Status 0(5/5回)Sendでエラー、Status 0(5/5回)Sendでエラー、Status 0(5/5回)

WinHttpは、GASが受け取った文字数・日本語の文字数・件数が、5回とも送った内容と一致しました。GASが受け取った本文のバイト数も、送ったUTF-8のバイト数と同じです(10万字で162,349バイト)。ServerXMLHTTPの405は、前の記事と同じ結果です。

結果2:Status 0が出たのはXMLHTTP

XMLHTTPは、サイズに関係なくSendでエラー0x80070005(アクセスが拒否されました)になりました。300字・1,000字・3,000字・5,000字・10万字の各5回で、すべて同じです。小さくしても直りませんでした。

エラーのあとにStatusを読むと、エラーにならず0が返り、レスポンスは空でした。On Error Resume Nextでエラーを握りつぶすと、見えるのは「Status 0」だけです。当時の症状(Status 0・レスポンス空・エラーが見えない)と同じ形です。

一方、WinHttpは50万字まで200で、大きいことによる失敗は出ませんでした。この環境でStatus 0を再現できたのは、XMLHTTPだけです。当時の環境は今は確認できないため、当時の原因はここでは決めません。

再現用の最小コード

Open・setRequestHeader・Sendのエラー番号と、Statusを別々に控えます。

Option Explicit

Private Const GAS_URL As String = ""   ' Web AppのURLを貼る

Public Sub Demo31()
    Dim body As Variant
    body = Utf8Bytes("[{""id"":1}]")
    Probe "XMLHTTP", "MSXML2.XMLHTTP", body
    Probe "WinHttp", "WinHttp.WinHttpRequest.5.1", body
End Sub

Private Sub Probe(ByVal label As String, ByVal progId As String, ByVal body As Variant)
    Dim http As Object
    Dim eOpen As Long, eHdr As Long, eSend As Long, st As Long, desc As String
    Set http = CreateObject(progId)
    On Error Resume Next
    http.Open "POST", GAS_URL, False
    eOpen = Err.Number: Err.Clear
    http.setRequestHeader "Content-Type", "application/json; charset=utf-8"
    eHdr = Err.Number: Err.Clear
    http.Send body
    eSend = Err.Number
    desc = Replace(Replace(Err.Description, vbCr, ""), vbLf, "")
    Err.Clear
    st = http.Status
    On Error GoTo 0
    Debug.Print label & " : Open=" & eOpen & " / Header=" & eHdr & " / Send=" & eSend & " / Status=" & st
    If eSend <> 0 Then Debug.Print "  " & desc
End Sub

Private Function Utf8Bytes(ByVal s As String) As Variant
    Dim st As Object
    Set st = CreateObject("ADODB.Stream")
    st.Type = 2
    st.Charset = "utf-8"
    st.Open
    st.WriteText s
    st.Position = 0
    st.Type = 1
    st.Position = 3
    Utf8Bytes = st.Read
    st.Close
End Function

実行結果です。OpenとHeaderは0で、エラーはSendで出ています。XMLHTTPはStatusが0、WinHttpは200です。

イミディエイトウィンドウに、XMLHTTPはSend=-2147024891でStatus=0、WinHttpはSend=0でStatus=200と表示された画面
再現用の最小コードの実行結果

エラー時の後片付けは、【VBA】エラー時も設定を元に戻す|ScreenUpdating等の後片付けでも扱っています。

タイムアウトを短くすると

WinHttpの受信タイムアウトを短くして、10万字を5回ずつ送りました。

受信タイムアウト失敗した回数200が返った回数
1ms2回(再計測では1回)3回(再計測では4回)
10ms2回3回
100ms1回4回
1,000ms0回5回

失敗したときのエラーは0x80072EE2で、Statusを読むこと自体がエラーになりました。XMLHTTPと違い、0は返りません。On Error Resume Nextの下では、変数が初期値のまま残ります。タイムアウトを短くしても、毎回失敗するわけではありませんでした。公式の説明では、受信タイムアウトは応答データのパケットを1つ受信するごとの待ち時間とされています(IWinHttpRequest::SetTimeouts method)。送信タイムアウトを1msにした場合は、5回とも200でした。

doPostが何も返さない場合も試しました。WinHttpは5回とも200で、応答は5,493文字の文字列でした。Status 0にはなりませんでした。

一括送信と1件ずつ送信の所要時間

WinHttpで、同じ件数を1回で送る場合と、1件ずつ送る場合を比べました。1件ずつは、毎回オブジェクトを作る形です。受け側は件数を返すだけの処理です。

件数一括(1回)1件ずつ(件数分の回数)倍率
10件977ms9,666ms9.9倍
30件927ms26,573ms28.7倍
60件949ms71,527ms※75.4倍※

※60件の計測では、1件ずつ送信のどれかで失敗が混ざりました(どの送信かは記録していません)。同じ条件で再計測した180回(60件×3回)は、失敗が0回、1回あたり平均971ms、最大11秒でした。

一括は、10件でも60件でもほぼ1秒です。50万字(1,633件)を一括で送っても、1.8秒で200が返りました。1件ずつは、件数に比例して長くなります。

受け側のシートへの書き込み方(毎回消す・追記など)による違いは、【VBA】GASへの書き込み方の違い|全消しと追記のみを実測で扱っています。

手元で確認できないこと

32bit Officeと、別のPC・ネットワーク(プロキシのある社内環境など)では確かめていません。

まとめ

  • この環境では、XMLHTTPは300字でもSendに失敗し、見えるのはStatus 0だけでした。サイズは関係ありませんでした
  • WinHttpは、1万〜50万字まで200で、一括で約1〜1.8秒でした
  • 1件ずつ送ると、件数に比例して時間がかかります
  • Status 0を見たら、通信オブジェクトを確認し、Sendの直後にエラー番号を控えてください

コメント

タイトルとURLをコピーしました