5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

VBAで本格的なHTTPクライアントを作った ― 並行リクエスト、1GBストリーミング、そしてAIエージェントによる開発

5
Posted at

VBAでHTTP通信を行う場合、WinHttp.WinHttpRequest.5.1 や MSXML2.XMLHTTP を直接使ったことがある方は多いと思います。

GETやPOSTを送るだけであれば、それでも十分です。

一方、実際の業務アプリケーションでHTTP通信を扱おうとすると、だんだん要求が増えてきます。

たとえば、

  • 複数のAPIをできるだけ短時間で呼び出したい
  • 一時的な通信障害なら自動で再試行したい
  • Retry-Afterを正しく扱いたい
  • 処理時間の上限やキャンセルを扱いたい
  • 数百MB~数GBのファイルをメモリに丸ごと載せず転送したい
  • プロキシや認証を扱いたい
  • HTTP/2を利用したい
  • 通信を何千回繰り返しても、ハンドルやメモリが漏れないことを確認したい

といった問題が出てきます。

そこで今回、Windows版Excel/VBA向けのHTTPクライアントライブラリ VBA-HTTP を作りました。

ひとことで言えば、

VBAで、他の言語にあるような本格的なHTTPクライアントをどこまで実現できるか

をかなり真面目に追求したライブラリです。

VBA-HTTPとは

基本的な使い方はシンプルです。

Dim client As HttpClient
Dim response As HttpResponse

Set client = VBAHttp.CreateClient()
Set response = client.GetResponse("https://example.com/api")

response.RaiseForStatus

Debug.Print response.StatusCode
Debug.Print response.Text

もう少し細かくリクエストを組み立てることもできます。

Dim client As HttpClient
Dim request As HttpRequest
Dim response As HttpResponse

Set client = VBAHttp.CreateClient()
Set request = VBAHttp.CreateRequest()

request.Method = "GET"
request.Url = "https://example.com/items"
request.Query.Add "page", 1
request.Query.Add "limit", 100
request.Headers.SetValue "Accept", "application/json"

Set response = client.Execute(request)

response.RaiseForStatus
Debug.Print response.Text

通常のHTTP通信には、Windows標準の

WinHttp.WinHttpRequest.5.1

を利用します。

一方、大容量ファイルの転送やHTTP/2の制御など、より低い層まで触る必要がある機能については、Windowsのwinhttp.dllをVBAから直接呼び出す方式も用意しています。

現在は主に次の機能を備えています。

  • 同時実行数を制限した複数リクエストの並行処理
  • 通信失敗時の自動再試行
  • 処理時間の上限
  • キャンセル
  • 大容量ファイルのストリーミング送受信
  • プロキシ
  • 各種認証
  • Cookie管理
  • 通信状況の診断
  • HTTP/2の利用と確認

単なるWinHttpRequestの薄いラッパーではなく、もう少し本格的なHTTP通信基盤を目指しています。

複数のHTTPリクエストを同時進行させる

VBA-HTTPで特に実用性が高いと思っている機能の一つが、複数リクエストの並行処理です。

たとえば複数のURLへアクセスする場合、次のように書けます。

Dim urls As New Collection
Dim options As New HttpBatchOptions
Dim result As HttpBatchResult

urls.Add "https://example.com/a"
urls.Add "https://example.com/b"
urls.Add "https://example.com/c"

options.MaxConcurrency = 8

Set result = client.GetMany(urls, options)

Debug.Print result.SuccessCount
Debug.Print result.FailureCount

MaxConcurrency = 8 とした場合、同時に処理するリクエスト数を最大8件までに制限します。

無制限に通信を投げるのではなく、上限を決めながら複数の通信を進めます。

実際にどのくらい速くなるのか

ローカルに用意した試験用HTTPサーバーで、それぞれ100ms待機するリクエストを100件処理して比較しました。

逐次実行             11.04秒
同時実行数16          0.86秒

約12.86倍

もちろん、これは通信待ち時間の影響が大きくなるよう意図的に作った性能試験です。

そのため、

VBA-HTTPを使えば、どんな処理でも12.86倍速くなる

という意味ではありません。

ただし、数十件、数百件のAPIを順番に呼び出すような処理では、通信待ちの時間が大きな割合を占めます。

そうした処理では、同時実行による効果がかなり大きくなります。

性能測定の元データもリポジトリに保存しています。

1GBのファイルを1GBのByte配列にしない

もう一つ力を入れたのが、大容量ファイルの転送です。

VBAでファイルをHTTP送信する場合、単純に実装すると、一度ファイル全体を次のような配列へ読み込む構造になりがちです。

Dim body() As Byte

小さなファイルなら問題ありません。

しかし1GBのファイルを送るために、1GB級のByte()をExcelの中へ確保する設計はかなり厳しいです。

そこでVBA-HTTPでは、ファイル全体を一度にメモリへ読み込まず、少しずつ読み書きしながら転送します。

Dim client As HttpClient
Dim result As HttpDownloadResult

Set client = VBAHttp.CreateNativeClient()

Set result = client.DownloadFile( _
    "https://example.com/large.bin", _
    "C:\Temp\large.bin")

result.RaiseForStatus

実際に1GiBのファイルをダウンロードする試験も行っています。

記録済みのExcel x64環境での測定では、1GiBのダウンロード中に増加したプライベートメモリの最大値は約19MB でした。

アップロードについても同様で、1GiBのファイルを少しずつ読み込みながら送信できます。

複数項目を含むmultipart形式のアップロードにも対応しています。

Windows APIを直接呼んで通信処理を最適化する

大容量転送では、WinHttp.WinHttpRequest.5.1だけではなく、Windows標準のwinhttp.dllを直接呼び出しています。

最近では通信の内部処理についても最適化を行い、

  • 64KiBの固定バッファを使い回す
  • WinHttpReadDataから直接読み込む
  • 受信サイズが分かっている場合は先に必要な領域を確保する
  • VBAで1バイトずつコピーする処理をなくす

といった改善を入れています。

ただし、いわゆる「黒魔術」に寄せすぎないようにもしています。

たとえば、

  • 実行中に機械語を生成する
  • 実行可能なメモリ領域を作る
  • VBAの内部構造を書き換える

といった方法は使っていません。

VBA-HTTPの低レベル処理で利用しているのは、Microsoftが公開しているWindows APIです。

性能だけを追求した実験ではなく、実際に使えるライブラリであることを優先しています。

再試行も「失敗したらもう一度送る」だけではない

HTTP通信では、失敗時の再試行も意外と難しい問題です。

単純に、

失敗
 ↓
3回やり直す

だけでは危険です。

たとえばPOSTを不用意に再送すると、同じ処理がサーバー側で二重に実行される可能性があります。

そこでVBA-HTTPでは、

  • 再送しても問題のないHTTPメソッドか
  • Retry-Afterが返されたか
  • 再試行回数
  • 再試行までの待ち時間
  • 待ち時間へのランダムな揺らぎ
  • リクエスト単位の制限時間
  • 処理全体の制限時間
  • キャンセル
  • DNS、接続、タイムアウトなど一時的な通信障害

などを考慮します。

GETやHEADなどは比較的安全に再試行できますが、POSTなどについては明示的な設定なしでは簡単に再送しないようにしています。

このあたりも、「とりあえずHTTP通信できる」ではなく、

「 HTTPクライアントとして安全に使える 」

ことを重視した部分です。

VBAライブラリだけど、大量の自動テストを書いた

今回、機能そのものと同じくらい力を入れたのがテストです。

VBAのライブラリでは、

サンプルのExcelファイルで動作確認しました

という形で終わることも珍しくありません。

VBA-HTTPでは、できるだけ他の言語でライブラリを開発するときと同じように、自動テストによって動作を確認しています。

開発の流れとしては、

実装
 ↓
静的解析
 ↓
コンパイル
 ↓
単体テスト
 ↓
結合テスト
 ↓
負荷試験

という形です。

現在のリポジトリでは、たとえば次のような試験を行っています。

  • 各クラスや機能の単体テスト
  • ローカルHTTP/HTTPSサーバーを使った結合テスト
  • 1GiBダウンロードとハッシュ値の確認
  • 1GiBアップロードとハッシュ値の確認
  • 10,000回のHTTP通信後にリソースが漏れていないかの確認
  • キャンセルやタイムアウトを何度も繰り返す試験
  • 同時実行数が設定どおり守られるかの試験
  • 再試行処理の試験
  • プロキシと認証の試験
  • HTTP/2が実際に使われたかの確認
  • 配布ファイルのチェックサム確認
  • 改ざんされた配布物を拒否できるかの確認
  • 実際のExcel/VBEでコンパイルできるかの確認

HTTPクライアントでは、

1回通信に成功した

だけでは不十分です。

  • たとえば10,000回通信したあとにWindowsのハンドルが漏れていないか
  • 途中でキャンセルしても一時ファイルが残らないか
  • 1GiB転送したあとに送信元と送信先のハッシュ値が一致するか

そういった壊れやすい部分まで自動試験しています。

これは「VBAでも他言語レベルのテスト基盤を構築できる」という、一種パフォーマンス的な側面も狙って実装しています。

そして、私は実装コードを1行も手書きしていない

このプロジェクトにはもう一つのテーマがあります。

VBA-HTTPの実装コードについて、私は1行も直接書いていません。

もちろん、

  • 何を作るか
  • 全体構成
  • 必要な機能
  • 公開APIの設計
  • 合格条件
  • 性能目標
  • セキュリティ上どこまで許容するか
  • どの問題を修正するか

といった判断は私が行っています。

一方、実際のコード実装はAIのコーディングエージェント (Codex) に任せました。

その開発で使ったのが、私が以前から開発している xlflow です。

xlflowはVBAをモダンな開発環境に引き上げるための統合ツールチェーンです。詳しくはZennの方で解説しているので気になる方は見てみてください。

VBAでもAIエージェントに「普通の開発環境」を与える

AIにVBAを書かせるだけなら、それほど難しくありません。

.basファイルへVBAコードを書かせること自体は、現在のAIなら十分できます。

問題は、その後です。

そのコードは本当にExcelでコンパイルできるのか?
 ↓
テストは通るのか?
 ↓
実行時エラーは起きないのか?
 ↓
静的解析で問題は出ていないか?
 ↓
問題があったらAI自身が原因を調べて修正できるのか?

通常のVBA開発環境は、こうした作業をAIエージェントから自動操作しやすい形では提供していません。

そこでxlflowでは、VBAを通常のソースファイルとして管理します。

xlflowは モジュールファイルをフォルダ分けして扱える機能 があるので、このように責務ごとにファイルを配置することで人間にとってもAIエージェントにとっても保守しやすいプロジェクトを構築できます。

image.png

AIエージェントはこれらのファイルを直接編集し、コマンドから、

xlflow lint
xlflow analyze
xlflow test
xlflow build

といった処理を実行できます。

VBA-HTTP自身も、開発中にxlflow lintやxlflow analyzeによる静的解析を実行しています。

さらに実際のExcelを起動して、VBE上でコンパイルできることも確認します。

つまりAIから見ると、

コードを書く
 ↓
静的解析する
 ↓
Excelでコンパイルする
 ↓
テストする
 ↓
失敗内容を読む
 ↓
修正する
 ↓
もう一度テストする

という、PythonやGo、TypeScriptなどでは当たり前の開発サイクルに近い環境になります。

AIにコードを書かせるなら、むしろテストが重要だった

VBA-HTTPを作っていて特に感じたのがこれです。

AIを使った開発では、

「 AIが一発で正しいコードを書けること 」

よりも、

「 間違えたとき、自分で間違いを検出して修正できること 」

の方が重要でした。

たとえば、

1GiBをアップロード
 ↓
サーバー側でハッシュ値が一致しない
 ↓
テスト失敗
 ↓
AIが原因を調査
 ↓
バッファ処理を修正
 ↓
もう一度テスト

という流れを作っておけば、人間が細かい実装を逐一確認しなくても、AI自身が修正を繰り返せます。

逆にテストがなければ、AIが

実装しました

と言ったあとに、その実装が本当に正しいのかを人間がすべて確認しなければなりません。

今回やっていたことは、「AIにVBAを書かせる」というより、

「 AIがVBAでソフトウェア開発をできる環境を作る 」

ことに近かったと思います。

休暇中にも開発が進んでいた

今回の開発では、かなりの部分をAIエージェントに任せています。

実際、私が休暇中で直接コードを触っていない間にも、AIエージェントが、

実装
 ↓
テスト
 ↓
失敗
 ↓
原因調査
 ↓
修正
 ↓
再テスト

を繰り返しながら開発を進めていました。

具体的には、Codexで/goalコマンドで指示を出したあと、22時間ほど無人で動き続けて、私が夏休みを楽しんでる間に勝手に完成してました。この間私は全く介入していません。

人間側が細かいコードを書くのではなく、

「 何を満たせば完成なのか 」

をテストや合格条件として与える。

するとAIエージェントがそこへ向かって修正を繰り返す。

今回のVBA-HTTPは、この開発方法が比較的大きなVBAプロジェクトでも成立するのかを試す良い題材になりました。

まとめ

今回作ったVBA-HTTPでは、

  • 他の言語のHTTPクライアントに近いAPI
  • 複数リクエストの並行処理
  • Windows標準WinHTTPの直接利用
  • 数GB級ファイルのストリーミング転送
  • 再試行
  • 制限時間とキャンセル
  • 認証
  • プロキシ
  • Cookie管理
  • HTTP/2
  • 1GiB転送試験
  • 10,000回通信によるリソース確認

まで実装しました。

さらにもう一つの実験として、

この規模のVBAプロジェクトを、AIエージェントだけでどこまで開発できるか

も試しました。

結果として、

ソース管理
+
静的解析
+
実際のExcelでのコンパイル
+
自動テスト
+
結合テスト
+
負荷試験
+
性能測定

という仕組みをAIエージェントへ与えることで、かなり複雑なVBAプロジェクトまで開発できました。

VBA-HTTP:

xlflow:

VBA-HTTPについては、実際のAPI、社内プロキシ、認証環境などで使ってみて、うまく動かないケースがあればIssueをいただけると嬉しいです。

5
4
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?