WinHTTPの使い方

# WinHTTPについて
WinHTTPはWindows用のHTTP通信を行うためのコンポーネントです。
本記事ではWin32APIからWinHTTPを使うサンプルを記載しています。

# 動作環境
WinHTTPの対応OSについては以下を参照してください。
　[WinHTTP Versions](https://docs.microsoft.com/en-us/windows/desktop/winhttp/winhttp-versions)

Windows 2000はService Pack 3以上が必要です。

Windows XPはService Pack 1 (SP1)以上が必要です。
Windows XPでTLS 1.1とTLS 1.2を使うためには別途対応が必要です。
例えば以下の方法
　[TLS 1.2 · sayurin/ffftp Wiki · GitHub](https://github.com/sayurin/ffftp/wiki/TLS-1.2)

:::note warn
ただし、この対応をしてもTLS 1.2は正常に動作しない様子。
:::

Windows 7でTLS 1.1とTLS 1.2を使うには以下の対応が必要です。
　[技術情報 3140245 WinHTTP が Windows での既定のセキュリティで保護されたプロトコルとして TLS 1.1 および TLS 1.2 を有効にする更新プログラム](https://support.microsoft.com/ja-jp/help/3140245/update-to-enable-tls-1-1-and-tls-1-2-as-a-default-secure-protocols-in)

# WinHTTPの使い方
WinHTTPを使うには「winhttp.lib」をリンクして「winhttp.h」をインクルードする必要があります。
WinHTTPはUNICODE用のAPIのみで、マルチバイト用のAPIはありません。

以下はGETリクエストの最低限のサンプルです。
ヘッダーと本文を受信してコンソールに出力します。

```c
#include <windows.h>
#include <stdio.h>
#include <winhttp.h>

#pragma comment (lib, "winhttp.lib")

int main()
{
	WCHAR *url = L"https://xxx/";
	HINTERNET hSession, hConnect, hRequest;
	URL_COMPONENTS urlComponents = { 0 };
	WCHAR szHostName[256], szUrlPath[2048];
	WCHAR *header;
	DWORD ret = 0;
	DWORD dwSize;
	DWORD dwStatusCode;

	// WinHTTPの初期化
	hSession = WinHttpOpen(L"UserAgent/1.0",
		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
		WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
	if (hSession == NULL) {
		return -1;
	}

	// URL解析
	urlComponents.dwStructSize = sizeof(URL_COMPONENTS);
	urlComponents.lpszHostName = szHostName;
	urlComponents.dwHostNameLength = sizeof(szHostName) / sizeof(WCHAR);
	urlComponents.lpszUrlPath = szUrlPath;
	urlComponents.dwUrlPathLength = sizeof(szUrlPath) / sizeof(WCHAR);
	if (!WinHttpCrackUrl(url, wcslen(url), 0, &urlComponents)) {
		WinHttpCloseHandle(hSession);
		return -1;
	}

	// HTTPの開始
	hConnect = WinHttpConnect(hSession, szHostName, urlComponents.nPort, 0);
	if (hConnect == NULL) {
		WinHttpCloseHandle(hSession);
		return -1;
	}
	hRequest = WinHttpOpenRequest(hConnect, L"GET", szUrlPath,
		NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
		(INTERNET_SCHEME_HTTPS == urlComponents.nScheme) ? WINHTTP_FLAG_SECURE : 0);
	if (hRequest == NULL) {
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
	if (WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
		WINHTTP_NO_REQUEST_DATA, 0,
		WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0) == FALSE) {
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
	if (WinHttpReceiveResponse(hRequest, NULL) == FALSE) {
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}

	// ヘッダー取得
	dwSize = 0;
	if (WinHttpQueryHeaders(hRequest,
		WINHTTP_QUERY_RAW_HEADERS_CRLF,
		WINHTTP_HEADER_NAME_BY_INDEX,
		WINHTTP_NO_OUTPUT_BUFFER, &dwSize,
		WINHTTP_NO_HEADER_INDEX) == FALSE) {
		if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
			WinHttpCloseHandle(hRequest);
			WinHttpCloseHandle(hConnect);
			WinHttpCloseHandle(hSession);
			return -1;
		}
	}
	header = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, dwSize);
	if (header == NULL) {
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
	if (WinHttpQueryHeaders(hRequest,
		WINHTTP_QUERY_RAW_HEADERS_CRLF,
		WINHTTP_HEADER_NAME_BY_INDEX,
		header, &dwSize, WINHTTP_NO_HEADER_INDEX) == FALSE) {
		HeapFree(GetProcessHeap(), 0, header);
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
	wprintf(L"%s", header);
	HeapFree(GetProcessHeap(), 0, header);

	// レスポンスコードの確認
	dwSize = sizeof(DWORD);
	WinHttpQueryHeaders(hRequest,
		WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
		WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, &dwSize,
		WINHTTP_NO_HEADER_INDEX);
	if (dwStatusCode != HTTP_STATUS_OK) {
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}

	// 本文の受信
	while (1) {
		DWORD dwSize = 0;
		if (WinHttpQueryDataAvailable(hRequest, &dwSize) == FALSE) {
			ret = -1;
			break;
		}
		if (dwSize == 0) {
			break;
		}
		BYTE *buf = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, (dwSize + 1) * sizeof(BYTE));
		if (buf == NULL) {
			ret = -1;
			break;
		}
		if (WinHttpReadData(hRequest, buf, dwSize, NULL) == FALSE) {
			HeapFree(GetProcessHeap(), 0, buf);
			ret = -1;
			break;
		}
		buf[dwSize] = '\0';
		printf("%s", buf);
		HeapFree(GetProcessHeap(), 0, buf);
	}
	WinHttpCloseHandle(hRequest);
	WinHttpCloseHandle(hConnect);
	WinHttpCloseHandle(hSession);
	return ret;
}
```

# ヘッダーの追加
リクエストにヘッダー項目を追加する場合はWinHttpSendRequest()に指定します。
複数のヘッダーを追加するには改行(\r\n)で区切ります。
長さは -1L を指定すると第2引数の L'\0' までが長さとなります。

```c
	if (WinHttpSendRequest(hRequest,
		L"Accept: */*\r\nPragma: no-cache\r\nCache-Control: no-cache\r\n",
		-1L,
		WINHTTP_NO_REQUEST_DATA, 0,
		WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0) == FALSE) {
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
```

# エラーの取得
WinHTTPのエラーはGetLastError()で取得でき、FormatMessage()でwinhttp.dllからエラーメッセージを取得できます。

```c
	DWORD err;
	TCHAR *msg = NULL;

	// エラーコードの取得
	err = GetLastError();
	// エラーメッセージの取得
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_HMODULE |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		GetModuleHandle(TEXT("winhttp.dll")), err,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&msg, 0, NULL);
	_tprintf(TEXT("Error: %s\n"), msg);
	// エラーメッセージの解放
	LocalFree(msg);
```

ヘッダー追加とエラーメッセージ出力を追加したソースは以下になります。

```c
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <winhttp.h>

#pragma comment (lib, "winhttp.lib")

static void PrintHttpError(const TCHAR *func)
{
	DWORD err;
	TCHAR *msg = NULL;

	// エラーコードの取得
	err = GetLastError();
	// エラーメッセージの取得
	FormatMessage(
		FORMAT_MESSAGE_ALLOCATE_BUFFER |
		FORMAT_MESSAGE_FROM_HMODULE |
		FORMAT_MESSAGE_IGNORE_INSERTS,
		GetModuleHandle(TEXT("winhttp.dll")), err,
		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&msg, 0, NULL);
	if (msg != NULL) {
		_tprintf(TEXT("Error: %s: (%u) %s\n"), func, err, msg);
		LocalFree(msg);
	} else {
		_tprintf(TEXT("Error: %s: (%u)\n"), func, err);
	}
}

int main()
{
	WCHAR *url = L"https://xxx/";
	HINTERNET hSession, hConnect, hRequest;
	URL_COMPONENTS urlComponents = { 0 };
	WCHAR szHostName[256], szUrlPath[2048];
	WCHAR *header;
	DWORD ret = 0;
	DWORD dwSize;
	DWORD dwStatusCode;

	// WinHTTPの初期化
	hSession = WinHttpOpen(L"UserAgent/1.0",
		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
		WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
	if (hSession == NULL) {
		PrintHttpError("WinHttpOpen");
		return -1;
	}

	// URL解析
	urlComponents.dwStructSize = sizeof(URL_COMPONENTS);
	urlComponents.lpszHostName = szHostName;
	urlComponents.dwHostNameLength = sizeof(szHostName) / sizeof(WCHAR);
	urlComponents.lpszUrlPath = szUrlPath;
	urlComponents.dwUrlPathLength = sizeof(szUrlPath) / sizeof(WCHAR);
	if (!WinHttpCrackUrl(url, wcslen(url), 0, &urlComponents)) {
		PrintHttpError(TEXT("WinHttpCrackUrl"));
		WinHttpCloseHandle(hSession);
		return -1;
	}

	// HTTPの開始
	hConnect = WinHttpConnect(hSession, szHostName, urlComponents.nPort, 0);
	if (hConnect == NULL) {
		PrintHttpError(TEXT("WinHttpConnect"));
		WinHttpCloseHandle(hSession);
		return -1;
	}
	hRequest = WinHttpOpenRequest(hConnect, L"GET", szUrlPath,
		NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
		(INTERNET_SCHEME_HTTPS == urlComponents.nScheme) ? WINHTTP_FLAG_SECURE : 0);
	if (hRequest == NULL) {
		PrintHttpError(TEXT("WinHttpOpenRequest"));
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
	if (WinHttpSendRequest(hRequest,
		L"Accept: */*\r\nPragma: no-cache\r\nCache-Control: no-cache\r\n", -1L,
		WINHTTP_NO_REQUEST_DATA, 0, WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0) == FALSE) {
		PrintHttpError(TEXT("WinHttpSendRequest"));
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
	if (WinHttpReceiveResponse(hRequest, NULL) == FALSE) {
		PrintHttpError(TEXT("WinHttpReceiveResponse"));
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}

	// ヘッダー取得
	dwSize = 0;
	if (WinHttpQueryHeaders(hRequest,
		WINHTTP_QUERY_RAW_HEADERS_CRLF,
		WINHTTP_HEADER_NAME_BY_INDEX,
		WINHTTP_NO_OUTPUT_BUFFER, &dwSize,
		WINHTTP_NO_HEADER_INDEX) == FALSE) {
		if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
			PrintHttpError(TEXT("WinHttpQueryHeaders(Size)"));
			WinHttpCloseHandle(hRequest);
			WinHttpCloseHandle(hConnect);
			WinHttpCloseHandle(hSession);
			return -1;
		}
	}
	header = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, dwSize);
	if (header == NULL) {
		PrintHttpError(TEXT("WinHttpQueryHeaders(HeapAlloc)"));
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
	if (WinHttpQueryHeaders(hRequest,
		WINHTTP_QUERY_RAW_HEADERS_CRLF,
		WINHTTP_HEADER_NAME_BY_INDEX,
		header, &dwSize, WINHTTP_NO_HEADER_INDEX) == FALSE) {
		PrintHttpError(TEXT("WinHttpQueryHeaders"));
		HeapFree(GetProcessHeap(), 0, header);
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
	wprintf(L"%s", header);
	HeapFree(GetProcessHeap(), 0, header);

	// レスポンスコードの確認
	dwSize = sizeof(DWORD);
	WinHttpQueryHeaders(hRequest,
		WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
		WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, &dwSize,
		WINHTTP_NO_HEADER_INDEX);
	if (dwStatusCode != HTTP_STATUS_OK) {
		_tprintf(TEXT("Error: WinHttpQueryHeaders: %d\n"), dwStatusCode);
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}

	// 本文の受信
	while (1) {
		DWORD dwSize = 0;
		if (WinHttpQueryDataAvailable(hRequest, &dwSize) == FALSE) {
			PrintHttpError(TEXT("WinHttpQueryDataAvailable"));
			ret = -1;
			break;
		}
		if (dwSize == 0) {
			break;
		}
		BYTE *buf = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, (dwSize + 1) * sizeof(BYTE));
		if (buf == NULL) {
			PrintHttpError(TEXT("WinHttpReadData(HeapAlloc)"));
			ret = -1;
			break;
		}
		if (WinHttpReadData(hRequest, buf, dwSize, NULL) == FALSE) {
			PrintHttpError(TEXT("WinHttpReadData"));
			HeapFree(GetProcessHeap(), 0, buf);
			ret = -1;
			break;
		}
		buf[dwSize] = '\0';
		printf("%s", buf);
		HeapFree(GetProcessHeap(), 0, buf);
	}
	WinHttpCloseHandle(hRequest);
	WinHttpCloseHandle(hConnect);
	WinHttpCloseHandle(hSession);
	return ret;
}
```

# POSTリクエストについて
POSTリクエストを送信するにはWinHttpOpenRequest()の第2引数に「POST」を指定し、WinHttpSendRequest()でPOSTデータを送信します。
以下はPOSTリクエストを送信するサンプルです。

```c
#include <windows.h>
#include <stdio.h>
#include <winhttp.h>

#pragma comment (lib, "winhttp.lib")

int main()
{
	WCHAR *url = L"https://xxx/";
	HINTERNET hSession, hConnect, hRequest;
	URL_COMPONENTS urlComponents = { 0 };
	WCHAR szHostName[256], szUrlPath[2048];
	WCHAR *header;
	DWORD ret = 0;
	DWORD dwSize;
	DWORD dwStatusCode;

	// WinHTTPの初期化
	hSession = WinHttpOpen(L"UserAgent/1.0",
		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
		WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
	if (hSession == NULL) {
		return -1;
	}

	// URL解析
	urlComponents.dwStructSize = sizeof(URL_COMPONENTS);
	urlComponents.lpszHostName = szHostName;
	urlComponents.dwHostNameLength = sizeof(szHostName) / sizeof(WCHAR);
	urlComponents.lpszUrlPath = szUrlPath;
	urlComponents.dwUrlPathLength = sizeof(szUrlPath) / sizeof(WCHAR);
	if (!WinHttpCrackUrl(url, wcslen(url), 0, &urlComponents)) {
		WinHttpCloseHandle(hSession);
		return -1;
	}

	// HTTPの開始
	hConnect = WinHttpConnect(hSession, szHostName, urlComponents.nPort, 0);
	if (hConnect == NULL) {
		WinHttpCloseHandle(hSession);
		return -1;
	}
	hRequest = WinHttpOpenRequest(hConnect, L"POST", szUrlPath,
		NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES,
		(INTERNET_SCHEME_HTTPS == urlComponents.nScheme) ? WINHTTP_FLAG_SECURE : 0);
	if (hRequest == NULL) {
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}

	// POSTデータの送信
	char *sendData = "id=1&name=aaa";
	if (WinHttpSendRequest(hRequest, L"Content-Type: application/x-www-form-urlencoded\r\n", -1,
		sendData, strlen(sendData), strlen(sendData), 0) == FALSE) {
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
	if (WinHttpReceiveResponse(hRequest, NULL) == FALSE) {
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}

	// ヘッダー取得
	dwSize = 0;
	if (WinHttpQueryHeaders(hRequest,
		WINHTTP_QUERY_RAW_HEADERS_CRLF,
		WINHTTP_HEADER_NAME_BY_INDEX,
		WINHTTP_NO_OUTPUT_BUFFER, &dwSize,
		WINHTTP_NO_HEADER_INDEX) == FALSE) {
		if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
			WinHttpCloseHandle(hRequest);
			WinHttpCloseHandle(hConnect);
			WinHttpCloseHandle(hSession);
			return -1;
		}
	}
	header = (WCHAR *)HeapAlloc(GetProcessHeap(), 0, dwSize);
	if (header == NULL) {
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
	if (WinHttpQueryHeaders(hRequest,
		WINHTTP_QUERY_RAW_HEADERS_CRLF,
		WINHTTP_HEADER_NAME_BY_INDEX,
		header, &dwSize, WINHTTP_NO_HEADER_INDEX) == FALSE) {
		HeapFree(GetProcessHeap(), 0, header);
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
	wprintf(L"%s", header);
	HeapFree(GetProcessHeap(), 0, header);

	// レスポンスコードの確認
	dwSize = sizeof(DWORD);
	WinHttpQueryHeaders(hRequest,
		WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
		WINHTTP_HEADER_NAME_BY_INDEX, &dwStatusCode, &dwSize,
		WINHTTP_NO_HEADER_INDEX);
	if (dwStatusCode != HTTP_STATUS_OK) {
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}

	// 本文の受信
	while (1) {
		DWORD dwSize = 0;
		if (WinHttpQueryDataAvailable(hRequest, &dwSize) == FALSE) {
			ret = -1;
			break;
		}
		if (dwSize == 0) {
			break;
		}
		BYTE *buf = (LPBYTE)HeapAlloc(GetProcessHeap(), 0, (dwSize + 1) * sizeof(BYTE));
		if (buf == NULL) {
			ret = -1;
			break;
		}
		if (WinHttpReadData(hRequest, buf, dwSize, NULL) == FALSE) {
			HeapFree(GetProcessHeap(), 0, buf);
			ret = -1;
			break;
		}
		buf[dwSize] = '\0';
		printf("%s", buf);
		HeapFree(GetProcessHeap(), 0, buf);
	}
	WinHttpCloseHandle(hRequest);
	WinHttpCloseHandle(hConnect);
	WinHttpCloseHandle(hSession);
	return ret;
}
```

REST APIなどでJSONデータを送信する場合は以下のように設定します。

```c
	// POSTデータの送信
	char *sendData = "{\"id\": 1, \"name\": \"aaa\"}";
	if (WinHttpSendRequest(hRequest, L"Content-Type: application/json\r\n", -1,
		sendData, strlen(sendData), strlen(sendData), 0) == FALSE) {
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return -1;
	}
```