0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

WinHTTP APIを用いたWebアクセスの概要

Last updated at Posted at 2025-02-13

WinHTTP APIを用いたWebアクセスに関する関数の説明やサンプルコード、その他使えるテクニックを記載しています。

オーバービュー

HTTP サーバーと対話するときに WinHTTP 関数が通常呼び出される順序を示しています。
網かけのボックスは HINTERNET ハンドルを生成する関数を表し、プレーン ボックスはそれらのハンドルを使用する関数を表します。

関数解説

必要最低限な関数について解説を記載します。
なお、流れは以下の通りです。

  1. WinHTTPの初期化(WinHttpOpen
  2. サーバーへの接続を準備(WinHttpConnect
  3. HTTPリクエストの作成(WinHttpOpenRequest
  4. HTTPリクエストを送信(WinHttpSendRequest
  5. サーバーからのレスポンスを受信(WinHttpReceiveResponse
  6. 受信したデータサイズを取得(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);

WinHttpOpen 関数 (winhttp.h) - Win32 apps

サーバーへの接続を準備

WinHttpConnect関数を用いてHTTPサーバーへ接続するための準備を行います。
失敗した場合はNULLが返ってきます。

// サーバに接続するための準備
HINTERNET hConnect = WinHttpConnect(
    hSession,                      // WinHttpOpenで作成したセッション
    L"www.example.com",            // サーバーのホスト部を指定する
    INTERNET_DEFAULT_HTTPS_PORT,   // ポート番号を指定する, 一部は定義されている
    0);

WinHttpConnect 関数 (winhttp.h) - Win32 apps

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接続を行う場合は設定

WinHttpOpenRequest 関数 (winhttp.h) - Win32 apps

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);                             // コールバック関数に渡されるアプリケーション定義

WinHttpSendRequest 関数 (winhttp.h) - Win32 apps

サーバーからのレスポンスを受信

WinHttpReceiveResponse関数を用いてHTTPリクエストに対するレスポンスを受信します。
失敗した場合はFALSEが返ってきます。

なおWinHttpReceiveResponseはブロッキング動作を行うためデータが届くまで待機します。

BOOL bReceived = WinHttpReceiveResponse(
    hRequest,   // WinHttpOpenRequestで作成したハンドル
    NULL);      // NULLを指定する必要あり(パラメーター自体が予約値)

WinHttpReceiveResponse 関数 (winhttp.h) - Win32 apps

受信したデータサイズを取得

WinHttpQueryDataAvailable関数を用いて受信したレスポンスの読み取ることができるデータのサイズをバイト単位で取得します。
失敗した場合はFALSEが返ってきます。また、データサイズは第二引数の変数に格納されます。

DWORD dwSize = 0;
BOOL bAvailable = WinHttpQueryDataAvailable(
    hRequest,   // WinHttpOpenRequestで作成したハンドル
    &dwSize);   // バイト数を受け取る変数へのポインタ

WinHttpQueryDataAvailable 関数 (winhttp.h) - Win32 apps

受信したデータを読み取る

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);    // 読み取られたデータサイズ(バイト単位)

WinHttpReadData 関数 (winhttp.h) - Win32 apps

その他テクニック

プロキシ情報を取得して利用する

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);

オプション フラグ (Winhttp.h) - Win32 apps

ファイルをダウンロードする

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 );

WinHTTP セッションの概要 | Web からリソースをダウンロードする - Win32 apps

ファイルのダウンロード

データサイズが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;
}
0
0
0

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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?