WinHTTP APIを用いたWebアクセスに関する関数の説明やサンプルコード、その他使えるテクニックを記載しています。
オーバービュー
HTTP サーバーと対話するときに WinHTTP 関数が通常呼び出される順序を示しています。
網かけのボックスは HINTERNET ハンドルを生成する関数を表し、プレーン ボックスはそれらのハンドルを使用する関数を表します。
関数解説
必要最低限な関数について解説を記載します。
なお、流れは以下の通りです。
- WinHTTPの初期化(
WinHttpOpen
) - サーバーへの接続を準備(
WinHttpConnect
) - HTTPリクエストの作成(
WinHttpOpenRequest
) - HTTPリクエストを送信(
WinHttpSendRequest
) - サーバーからのレスポンスを受信(
WinHttpReceiveResponse
) - 受信したデータサイズを取得(
WinHttpQueryDataAvailable
)
ライブラリやヘッダの利用
WinHTTPはワイド文字(Unicode)のみサポートしているため注意が必要です。
#include <winhttp.h>
#pragma comment (lib, "winhttp.lib")
WinHTTPの初期化
WinHttpOpen
関数を用いてWinHTTPの初期化を行います。
失敗した場合はNULL
が返ってきます。
// WinHTTPオブジェクトの作成
HINTERNET hSession = WinHttpOpen(
L"UserAgent/WinHTTP 1.0",
WINHTTP_ACCESS_TYPE_NO_PROXY, // プロキシなしですべてのホスト名を直接解決する
WINHTTP_NO_PROXY_NAME, // winhttp.hではdefineディレクティブでNULLがトークンとして定義されている
WINHTTP_NO_PROXY_BYPASS, // winhttp.hではdefineディレクティブでNULLがトークンとして定義されている
0);
サーバーへの接続を準備
WinHttpConnect
関数を用いてHTTPサーバーへ接続するための準備を行います。
失敗した場合はNULL
が返ってきます。
// サーバに接続するための準備
HINTERNET hConnect = WinHttpConnect(
hSession, // WinHttpOpenで作成したセッション
L"www.example.com", // サーバーのホスト部を指定する
INTERNET_DEFAULT_HTTPS_PORT, // ポート番号を指定する, 一部は定義されている
0);
HTTPリクエストの作成
WinHttpOpenRequest
関数を用いてHTTPリクエストのハンドルを作成します。
失敗した場合はNULL
が返ってきます。
// HTTPリクエストの作成
HINTERNET hRequest = WinHttpOpenRequest(
hConnect, // WinHttpConnectで作成したハンドル
L"GET", // リクエストメソッド
L"/index.html", // URLのパス部
NULL, // HTTPのバージョン(NULLの場合はHTTP/1.1になる)
WINHTTP_NO_REFERER, // リファラー
WINHTTP_DEFAULT_ACCEPT_TYPES, // Acceptヘッダー
WINHTTP_FLAG_SECURE); // HTTPS接続を行う場合は設定
HTTPリクエストを送信
WinHttpSendRequest
関数を用いてHTTPリクエストを送信します。
失敗した場合はFALSE
が返ってきます。
// HTTPリクエストを送信
BOOL bSent = WinHttpSendRequest(
hRequest, // WinHttpOpenRequestで作成したハンドル
WINHTTP_NO_ADDITIONAL_HEADERS, // リクエストに追加するヘッダー
0, // リクエストに追加するヘッダーの長さ(文字数)
WINHTTP_NO_REQUEST_DATA, // リクエストヘッダー直後に送信するデータへのポインタ(POST, PUTの時のみ)
0, // リクエストヘッダー直後に送信するデータのサイズ(バイト単位)
0, // 送信された合計データの長さ(バイト単位)、0で自動計算される
0); // コールバック関数に渡されるアプリケーション定義
サーバーからのレスポンスを受信
WinHttpReceiveResponse
関数を用いてHTTPリクエストに対するレスポンスを受信します。
失敗した場合はFALSE
が返ってきます。
なおWinHttpReceiveResponse
はブロッキング動作を行うためデータが届くまで待機します。
BOOL bReceived = WinHttpReceiveResponse(
hRequest, // WinHttpOpenRequestで作成したハンドル
NULL); // NULLを指定する必要あり(パラメーター自体が予約値)
受信したデータサイズを取得
WinHttpQueryDataAvailable
関数を用いて受信したレスポンスの読み取ることができるデータのサイズをバイト単位で取得します。
失敗した場合はFALSE
が返ってきます。また、データサイズは第二引数の変数に格納されます。
DWORD dwSize = 0;
BOOL bAvailable = WinHttpQueryDataAvailable(
hRequest, // WinHttpOpenRequestで作成したハンドル
&dwSize); // バイト数を受け取る変数へのポインタ
受信したデータを読み取る
WinHttpReadData
関数を用いてHTTPレスポンスのデータを読み取ります。
失敗した場合はFALSE
が返ってきます。
// WinHttpQueryDataAvailable関数で算出したバイト数+NULL終端文字分のサイズの配列を作成
char* buffer = new char[dwSize + 1];
SecureZeroMemory(buffer, dwSize + 1);
DWORD BytesRead = 0;
BOOL bReadData = WinHttpReadData(
hRequest, // WinHttpOpenRequestで作成したハンドル
buffer.data(), // 読み取ったデータの格納先バッファーのポインタ、 vectorで定義した場合は.data()で先頭アドレスを返す
dwSize, // 読み取るデータサイズ(バイト単位)
&BytesRead); // 読み取られたデータサイズ(バイト単位)
その他テクニック
プロキシ情報を取得して利用する
WinHttpGetIEProxyConfigForCurrentUser
関数を用いてWINHTTP_CURRENT_USER_IE_PROXY_CONFIG
型構造体に取得したデータを格納します。
その後三項演算子を用いてproxyConfig.lpszProxy
メンバーがNULL
でなければWINHTTP_ACCESS_TYPE_NAMED_PROXY
を設定し、NULL
であればWINHTTP_ACCESS_TYPE_DEFAULT_PROXY
を設定します。
proxyConfig.lpszProxy ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
// 現在ユーザのプロキシ情報を取得
WINHTTP_CURRENT_USER_IE_PROXY_CONFIG proxyConfig;
ZeroMemory(&proxyConfig, sizeof(WINHTTP_CURRENT_USER_IE_PROXY_CONFIG));
WinHttpGetIEProxyConfigForCurrentUser(&proxyConfig);
// WinHTTPオブジェクトの作成
HINTERNET hSession = WinHttpOpen(L"UserAgent/WinHTTP 1.0",
proxyConfig.lpszProxy ? WINHTTP_ACCESS_TYPE_NAMED_PROXY : WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
proxyConfig.lpszProxy, proxyConfig.lpszProxyBypass,
0);
WinHttpGetIEProxyConfigForCurrentUser 関数 (winhttp.h) - Win32 apps
HTTPサーバーの証明書エラーを無視する
オプションフラグを設定しWinHttpSetOption
関数を用いてリクエスト送信時の挙動を設定します。
// HTTPリクエストの作成
HINTERNET hRequest = WinHttpOpenRequest(
hConnect, L"GET", L"/index.html", NULL,WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
// 証明書エラーを無視するオプションを作成する
// SECURITY_FLAG_IGNORE_CERT_CN_INVALID: 証明書で無効なCommon Nameを許可
// SECURITY_FLAG_IGNORE_CERT_DATE_INVALID: 無効な証明書の日付 (有効期限が切れているか、まだ有効でない証明書) を許可
// SECURITY_FLAG_IGNORE_UNKNOWN_CA: 無効な証明機関を許可
DWORD dwFlags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA;
// 接続のオプションを設定する
WinHttpSetOption(hRequest, WINHTTP_OPTION_SECURITY_FLAGS, &dwFlags, sizeof(dwFlags));
// HTTPリクエストを送信
BOOL bSent = WinHttpSendRequest(
hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
ファイルをダウンロードする
C++のメリットであるvector
クラスを利用して動的にバッファを確保、操作する方式を採用したものが以下となります。
なお、vector
クラスを利用しない場合は一定サイズのchar型配列のバッファを用意し、初期化、格納、ファイルへ追記を繰り返すことにより実現可能です。
// データ受信
// 最終的なデータ用バッファ
vector<BYTE> dataBuffer;
DWORD dwSize = 0;
DWORD byteRead = 0;
// データの受信はチャンク単位での読み込みを行うためループする必要がある
do {
// 受信
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
wcout << L"Failed to WinHttpQueryDataAvailable: " << to_wstring(GetLastError()) << endl;
break;
}
if (dwSize == 0) {
break;
}
// 一時的なバッファ
vector<BYTE> buffer(dwSize);
if (!WinHttpReadData(hRequest, buffer.data(), dwSize, &byteRead)) {
wcout << "Failed to WinHttpReadData: " << to_wstring(GetLastError()) << endl;
break;
}
// 最終的なデータ用バッファに一時的なバッファの中身を追加する
dataBuffer.insert(dataBuffer.end(), buffer.begin(), buffer.begin()+byteRead);
} while (dwSize > 0);
wcout << L"Total received data size: " << dataBuffer.size() << L" bytes" << endl;
// バッファからファイルに保存する
HANDLE hFile = CreateFile(filepath, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
wcerr << L"Failed to create file: " << filepath << endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}
// ファイルの書き込み
if (!WriteFile(hFile, dataBuffer.data(), (DWORD)dataBuffer.size(), NULL, NULL)) {
wcerr << L"Failed to write to file." << endl;
}
// 後処理
CloseHandle(hFile);
サンプルコード
Webサイト閲覧
下記コードはHTTPS接続でwww.microsoft.com
にGETリクエストを送信し、受信したデータを読み取るサンプルプログラムです。
小さいサイズの処理であれば下記サンプルコードを活用するのがよいでしょう。
DWORD dwSize = 0;
DWORD dwDownloaded = 0;
LPSTR pszOutBuffer;
BOOL bResults = FALSE;
HINTERNET hSession = NULL,
hConnect = NULL,
hRequest = NULL;
// Use WinHttpOpen to obtain a session handle.
hSession = WinHttpOpen( L"WinHTTP Example/1.0",
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS, 0 );
// Specify an HTTP server.
if( hSession )
hConnect = WinHttpConnect( hSession, L"www.microsoft.com",
INTERNET_DEFAULT_HTTPS_PORT, 0 );
// Create an HTTP request handle.
if( hConnect )
hRequest = WinHttpOpenRequest( hConnect, L"GET", NULL,
NULL, WINHTTP_NO_REFERER,
WINHTTP_DEFAULT_ACCEPT_TYPES,
WINHTTP_FLAG_SECURE );
// Send a request.
if( hRequest )
bResults = WinHttpSendRequest( hRequest,
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0,
0, 0 );
// End the request.
if( bResults )
bResults = WinHttpReceiveResponse( hRequest, NULL );
// Keep checking for data until there is nothing left.
if( bResults )
{
do
{
// Check for available data.
dwSize = 0;
if( !WinHttpQueryDataAvailable( hRequest, &dwSize ) )
printf( "Error %u in WinHttpQueryDataAvailable.\n",
GetLastError( ) );
// Allocate space for the buffer.
pszOutBuffer = new char[dwSize+1];
if( !pszOutBuffer )
{
printf( "Out of memory\n" );
dwSize=0;
}
else
{
// Read the data.
ZeroMemory( pszOutBuffer, dwSize+1 );
if( !WinHttpReadData( hRequest, (LPVOID)pszOutBuffer,
dwSize, &dwDownloaded ) )
printf( "Error %u in WinHttpReadData.\n", GetLastError( ) );
else
printf( "%s", pszOutBuffer );
// Free the memory allocated to the buffer.
delete [] pszOutBuffer;
}
} while( dwSize > 0 );
}
// Report any errors.
if( !bResults )
printf( "Error %d has occurred.\n", GetLastError( ) );
// Close any open handles.
if( hRequest ) WinHttpCloseHandle( hRequest );
if( hConnect ) WinHttpCloseHandle( hConnect );
if( hSession ) WinHttpCloseHandle( hSession );
ファイルのダウンロード
データサイズが4Kバイト以上の場合は下記サンプルコードのようにループ処理を実装する必要があります。
#include <windows.h>
#include <winhttp.h>
#include <iostream>
#include <string>
#include <vector>
#include <shlwapi.h>
#pragma comment (lib, "winhttp.lib")
#pragma comment(lib, "Shlwapi.lib")
using namespace std;
int main() {
const wchar_t* url = L"download.sysinternals.com";
const wchar_t* path = L"files/ProcessMonitor.zip";
const wchar_t* user_agent = L"UserAgent/WinHTTP 1.0";
const wchar_t* filepath = L"C:\\Users\\user\\";
// WinHTTPオブジェクトの作成
HINTERNET hSession = WinHttpOpen(user_agent, WINHTTP_ACCESS_TYPE_NO_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession == NULL) {
wcout << L"Failed to WinHttpOpen: " << to_wstring(GetLastError()) << endl;
return 1;
}
// サーバーに接続するための準備
HINTERNET hConnect = WinHttpConnect(hSession, url, INTERNET_DEFAULT_HTTPS_PORT, 0);
if (hConnect == NULL) {
wcout << L"Failed to WinHttpConnect: " << to_wstring(GetLastError()) << endl;
WinHttpCloseHandle(hSession);
return 1;
}
// HTTPリクエストの作成
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", path, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (hRequest == NULL) {
wcout << L"Failed to WinHttpOpenRequest: " << to_wstring(GetLastError()) << endl;
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}
// HTTPリクエストの送信
BOOL bSent = WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
if (!bSent) {
wcout << L"Failed to WinHttpSendRequest: " << to_wstring(GetLastError()) << endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}
// レスポンスの受信
BOOL bReceived = WinHttpReceiveResponse(hRequest, NULL);
if (!bReceived) {
wcout << L"Failed to WinHttpReceiveResponse: " << to_wstring(GetLastError()) << endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}
// データ受信
// 最終的なデータ用バッファ
vector<BYTE> dataBuffer;
DWORD dwSize = 0;
DWORD byteRead = 0;
// データの受信はチャンク単位での読み込みを行うためループする必要がある
do {
// 受信
if (!WinHttpQueryDataAvailable(hRequest, &dwSize)) {
wcout << L"Failed to WinHttpQueryDataAvailable: " << to_wstring(GetLastError()) << endl;
break;
}
if (dwSize == 0) {
break;
}
// 一時的なバッファ
vector<BYTE> buffer(dwSize);
if (!WinHttpReadData(hRequest, buffer.data(), dwSize, &byteRead)) {
wcout << "Failed to WinHttpReadData: " << to_wstring(GetLastError()) << endl;
break;
}
// 最終的なデータ用バッファに一時的なバッファの中身を追加する
dataBuffer.insert(dataBuffer.end(), buffer.begin(), buffer.begin()+byteRead);
} while (dwSize > 0);
// 受信した合計データサイズを出力
wcout << L"Total received data size: " << dataBuffer.size() << L" bytes" << endl;
// URLからファイル名を取得
wstring fileName = PathFindFileNameW(path);
wstring fullPath = filepath + fileName;
// バッファからファイルに保存する
HANDLE hFile = CreateFile(fullPath.c_str(), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
wcerr << L"Failed to create file: " << filepath << endl;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}
// ファイルの書き込み
if (!WriteFile(hFile, dataBuffer.data(), (DWORD)dataBuffer.size(), NULL, NULL)) {
wcerr << L"Failed to write to file." << endl;
}
// 後処理
CloseHandle(hFile);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 0;
}