VBAからGoogle Apps Script(GAS)のWebアプリを呼ぶと、GETは通るのにPOSTだけ405になることがあります。私も、業務で使っているGASへのPOSTで、通信オブジェクトを替えるたびに結果が変わり、原因を追うのに苦労しました。
当時は、「POSTはMSXML2.XMLHTTP、GETはWinHttpが安定する」と整理していました。ただ、その根拠は確かめられる形で残していませんでした。そこで、同じGASに3種類の通信オブジェクトでGETとPOSTを5回ずつ送り、結果を実測しました。
想定読者
- VBAからGAS Webアプリへ送受信している人
- POSTで405やアクセス拒否が出て、原因が分からない人
用語の確認:405・302・307
本文で使うHTTPの用語を、先に短く整理します。
- GET・POST:GETはデータを取得する要求、POSTはデータを送る要求です
- 200:成功です
- 405(Method Not Allowed):そのURLは、送ったメソッド(GETやPOSTなど)に対応していません。レスポンスの
Allowヘッダに、使えるメソッドが入ります - 302(Found):このURLは一時的に移動している、という応答です。移動先のURLは
Locationヘッダに入り、通信側が自動で追いかけます(リダイレクト) - 307(Temporary Redirect):302と同じ一時的な移動ですが、メソッドを変えずに移動先へ送り直すよう求めます
302のあと、POSTをGETに変えて送り直すことは、仕様上許されています。変えるかどうかは、通信するオブジェクトによって違いました(後述)。詳しくは、302 Found(MDN)を参照してください。
先に結論
今回の環境では、WinHttp.WinHttpRequest.5.1だけがGET・POSTとも通りました。使い分けは不要でした。
| オブジェクト | GET | POST |
|---|---|---|
MSXML2.XMLHTTP | 失敗(アクセス拒否) | 失敗(アクセス拒否) |
MSXML2.ServerXMLHTTP | 200 | 405 |
WinHttp.WinHttpRequest.5.1 | 200 | 200 |
各5回とも同じ結果でした。XMLHTTPの失敗は、エラー番号-2147024891(アクセス拒否)です。検証環境はExcel 16.0(64bit Office)、Windows(64bit)、コードページ932です。別のPCやMac版、32bit Officeでは確かめていません。
ServerXMLHTTPで405になる原因は、次の2つの組み合わせでした。GASが302でGET専用のURLへ飛ばすことと、ServerXMLHTTPが302のあともPOSTを送り直すことです。
検証の方法
- 検証用に、受け取った内容を返すだけのGASを用意しました
- GETは
?q=abcを付けて、POSTは小さなJSONを送りました - 判定は、ステータスと、GASが返したメソッド名・受信文字数で行いました
- 302のあとの動きは、自分のPCで立てた検証サーバーでも確かめました(通信先はこのPC内だけ)
- 通信先のURLは、記事にもコードにも載せていません
- GASは、doGet・doPostが何回動いたかを数える形に差し替え、実行回数も確かめました。XMLHTTPの切り分けには、自分のサイトへのhttps通信も使いました(各5回)。
405になる理由
WinHttpで自動追従を切ってGASのWebアプリにPOSTすると、5回とも302が返りました。Locationのホストはscript.googleusercontent.comでした。
このLocationに対して、POSTすると、XMLHTTP・ServerXMLHTTPのどちらでも、WinHttpで手動で再POSTしても、405になりました。GETすると200で、POSTの結果が返りました。405のレスポンスのAllowヘッダはHEAD, GETでした。Location側はGETしか受け付けません。
では、なぜオブジェクトによって結果が分かれるのか。302のあとの動きの違いを、自分のPCで立てた検証サーバーで確かめました。POSTを受けると302で別のURLへ飛ばし、飛び先はGETなら200、POSTなら405を返すサーバーです。
| オブジェクト | 302のあとに飛び先へ送ったメソッド | 結果 |
|---|---|---|
MSXML2.XMLHTTP | GET | 200 |
MSXML2.ServerXMLHTTP | POST | 405 |
WinHttp.WinHttpRequest.5.1 | GET | 200 |
各3回とも同じ結果でした。同じホスト名への302でも、別のホスト名への302でも同じでした。メソッドを変えない転送(307)では、3つともPOSTのまま送られて405になりました。GETは、302のあともGETのままで200でした。
ServerXMLHTTPは、302のあともPOSTを送り直します。そのため、GET専用のLocationで405になります。XMLHTTPとWinHttpはGETに切り替えるので、200になります。GASで確認した結果(ServerXMLHTTPのPOSTは405、WinHttpの手動再POSTも405)は、この動きと合います。GAS側では、doPostとdoGetの実行回数で確かめました。ServerXMLHTTPのPOSTが405になったときも、doPostは1回のままで、doGetは動きませんでした。追従を切ったPOSTのLocationをGETしても、doGetは動かず、doPostも1回のままでした。405を返したLocationの先では、GASのコードは動いていません。ServerXMLHTTPがPOSTを送り直すことは、検証サーバーで確かめた結果です。
XMLHTTPは、GASの元のURLでは失敗しました。GAS側では、失敗したPOSTでもdoPostが、失敗したGETでもdoGetが1回ずつ動いていて、リクエストはGASまで届いています。そのうえでSendがエラー-2147024891(アクセス拒否)になり、Statusは0でした。送り先を変えて、XMLHTTPで試した結果は次のとおりです。
| 送り先 | 結果 |
|---|---|
| 自分のサイトのhttps(リダイレクトなし) | 200(5回) |
| 自分のサイトのhttps→httpsの301(同じホスト) | 200(5回) |
| 自分のサイトのhttp→httpsの301 | 失敗(アクセス拒否)(5回) |
| GASのLocationへ直接GET(httpsの別ホスト、リダイレクトなし) | 200(3回) |
| 検証サーバーのhttpの302(同じホスト・別のホスト) | 200(各3回) |
| GASの元URL(GET・POST) | 失敗(アクセス拒否)(各5回) |
リダイレクトのない通信は、httpsでも別のホストでも通りました。失敗したのは、GASの元URLと、httpからhttpsへの301でした。XMLHTTPのProgIDをMSXML2.XMLHTTP.6.0・.3.0・Microsoft.XMLHTTPに替えても、GASの元URLは失敗し、自分のサイトのhttps GETは200で、同じでした。
使い分けの目安
GAS Webアプリが相手なら、WinHttpにそろえます。
- GET:WinHttp(ServerXMLHTTPも通りました)
- POST:WinHttp
ServerXMLHTTPは、302のあともPOSTを送り直すため、飛び先がGET専用だとPOSTは通りません。XMLHTTPは、今回の環境ではGET・POSTとも通りませんでした(条件別の結果は、405になる理由の節のとおりです)。WinHttpの仕様は、WinHttpRequest object(Microsoft Learn)を参照してください。
よくある失敗
1. ステータスだけを見て、オブジェクトを替える
ServerXMLHTTPの405は、AllowがHEAD, GETでした。ヘッダーを読むと、「POSTを受け付けないURLに送っている」と分かります。
2. Locationへ手動で再POSTする
自動追従を切って、302のLocationへPOSTし直すと、5回とも405でした。POSTの結果が欲しいときは、自動追従に任せます。
3. 日本語をURLにそのまま入れる
WinHttpとServerXMLHTTPでは、未エンコードの「テスト」を送っても200でしたが、GASには9文字(UTF-8のバイト値)で届きました。UTF-8でパーセントエンコードすると、3文字で正しく届きました。XMLHTTPは400でした。エンコードの組み立て方は、【VBA】ScriptControlが64bit Officeで使えない時の代替コードを参照してください。
JSONの本文は、UTF-8のバイト列にして送れば、日本語の「テスト」は3文字で届きました。Content-Typeにcharsetを付けた場合も、付けない場合も同じでした。
応用例:GAS宛の送受信関数
動作を確認した通信部分を、関数にまとめます。
Option Explicit
Public Function GasPost(ByVal url As String, ByVal json As String, _
ByRef status As Long) As String
Dim http As Object
Set http = CreateObject("WinHttp.WinHttpRequest.5.1")
http.SetTimeouts 5000, 5000, 30000, 30000
http.Open "POST", url, False
http.SetRequestHeader "Content-Type", "application/json"
http.Send json
status = http.Status
GasPost = http.ResponseText
End Function
Public Function GasGet(ByVal url As String, ByRef status As Long) As String
Dim http As Object
Set http = CreateObject("WinHttp.WinHttpRequest.5.1")
http.SetTimeouts 5000, 5000, 30000, 30000
http.Open "GET", url, False
http.Send
status = http.Status
GasGet = http.ResponseText
End Function
同じGASへ、WinHttpのGET・POSTと、ServerXMLHTTPのPOSTを1回ずつ送った結果です。

受け側のGASがエラーをJSONで返す作りなら、ステータスが200でも応答にエラーが入ります。ステータスだけでなく、本文も確認します。
関連する別の現象
POSTのStatusが0になる場合は、405とは別の現象です。3つの通信オブジェクトとサイズ別の結果は、【VBA】GAS宛POSTのStatus 0をサイズ別に実測で扱っています。
受け側のGASがシートへ書き込む方式の違いは、【VBA】GASへの書き込み方の違い|全消しと追記のみを実測で扱っています。
まとめ
- GAS宛なら、WinHttpでGET・POSTとも通りました
- ServerXMLHTTPは、302のあともPOSTを送り直すため、GET専用の飛び先で405になりました
- XMLHTTPは、GASの元URLでは失敗しました。リダイレクトのないhttps通信は通りました
- 405はAllowヘッダを見ると原因を絞れます
- 日本語はUTF-8でエンコードしてから送ります


コメント