EternalWindows
WinHTTP / POSTによる投稿

クライアントがサーバーにデータを要求する場合は、前節で取り上げたようにGETを使用するのが一般的です。 これに対して、クライアントがサーバーに何らかのデータを渡したい場合は、POSTが使用されることがよくあります。 たとえば、次のようなフォームを通じてサーバーにメッセージを送信するのは、 POSTの代表的な使われ方です。

送信ボタンをクリックすればページが切り替わり、入力されたメッセージを確認することができます。 これはどのような仕組みで行われているかというと、ブラウザがformタグのaction属性からphpファイルの名前を取得し、 このファイルに対してテキストボックスに入力された文字列を送信しています。 この送信の方法として何を使用するかを決定するために、ブラウザはformタグのmethod属性を確認し、 これがPOSTである場合はPOSTリクエストが発行されることになります。 phpは、通常のhtmlファイルと同じように情報を静的に記述できる一方で、 HTMLを動的に出力することも可能であるため、 クライアントの入力内容をHTMLに反映させることができます。

phpファイルにアクセスするために、上記のようなフォームを必ず介さなければならないわけではありません。 アプリケーションがPOST(GETでも一応可能)を使用してデータを渡すようになれば、 その渡したデータを考慮したHTMLがサーバーから返されることになるからです。 このデータの渡し方がGETとPOSTで異なるところであり、 GETがURLにデータを付加するのに対して、POSTはHTTPリクエストのボディとしてデータを含めます。 ボディの設定は、WinHttpSendRequestで行うことができます。

WCHAR szHeader[] = L"Content-Type: application/x-www-form-urlencoded\r\n";
CHAR  szData[] = "msg=abc";
DWORD dwHeaderLength = lstrlenW(szHeader);
DWORD dwDataLength = lstrlenA(szData);

WinHttpSendRequest(hRequest, szHeader, dwHeaderLength, szData, dwDataLength, dwDataLength, 0);

第4引数にボディとして含めたいデータを指定し、第5引数にそのデータのサイズを指定します。 第6引数はデータの合計サイズですが、後でWinHttpWriteDataを呼び出すつもりがない場合は、 第5引数の値と同一で構いません。 データの"msg=abc"についてですが、これはmsgにabcという値を設定するという意味になります。 msgというのは、テキストボックスのname属性の値になります。 これは、実際にこのページのソースを開いてみると分かるでしょう。 第2引数はHTTPのヘッダに追加したい文字列であり、 POSTを実行する場合は上記の文字列を指定するのが一般的です。 これによって、サーバーはデータがどのような形式で渡されたのかを理解することができます。

サーバーに送信したいデータのサイズが大きいような場合は、 WinHttpSendRequestにデータ全てを指定しないほうがよいかもしれません。 このような場合は、データをいくつかに分割して、WinHttpWriteDataを繰り返して呼び出す方法が考えられます。

BOOL WINAPI WinHttpWriteData(
  HINTERNET hRequest,
  LPCVOID lpBuffer,
  DWORD dwNumberOfBytesToWrite,
  LPDWORD lpdwNumberOfBytesWritten
);

hRequestは、リクエストハンドルを指定します。 lpBufferは、送信したいデータを格納したバッファを指定します。 dwNumberOfBytesToWriteは、送信したいデータのサイズを指定します。 lpdwNumberOfBytesWrittenは、送信されたデータのサイズを受け取る変数のアドレスを指定します。

今回のプログラムは、サーバーにPOSTリクエストを発行します。

#include <windows.h>
#include <winhttp.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HINTERNET      hSession, hConnect, hRequest;
	URL_COMPONENTS urlComponents;
	WCHAR          szHostName[256], szUrlPath[2048];
	WCHAR          szUrl[] = L"http://eternalwindows.jp/network/winhttp/sample.php";
	WCHAR          szHeader[] = L"Content-Type: application/x-www-form-urlencoded\r\n";
	CHAR           szData[] = "msg=abc";
	DWORD          dwHeaderLength = lstrlenW(szHeader);
	DWORD          dwDataLength = lstrlenA(szData);
	DWORD          dwTotalLength;
	DWORD          dwSize;
	DWORD          dwStatusCode;
	BYTE           buffer[4096];

	hSession = WinHttpOpen(L"Sample Application/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
	if (hSession == NULL)
		return 0;

	ZeroMemory(&urlComponents, sizeof(URL_COMPONENTS));
	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(szUrl, lstrlenW(szUrl), 0, &urlComponents)) {
		WinHttpCloseHandle(hSession);
		return 0;
	}
	
	hConnect = WinHttpConnect(hSession, szHostName, INTERNET_DEFAULT_PORT, 0);
	if (hConnect == NULL) {
		WinHttpCloseHandle(hSession);
		return 0;
	}

	hRequest = WinHttpOpenRequest(hConnect, L"POST", szUrlPath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
	if (hRequest == NULL) {
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return 0;
	}

	dwTotalLength = dwDataLength;
	WinHttpSendRequest(hRequest, szHeader, dwHeaderLength, WINHTTP_NO_REQUEST_DATA, 0, dwTotalLength, 0);
	WinHttpWriteData(hRequest, szData, dwDataLength, &dwSize);

	WinHttpReceiveResponse(hRequest, NULL);

	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) {
		WinHttpReadData(hRequest, buffer, sizeof(buffer), NULL);
		MessageBoxA(NULL, (LPSTR)buffer, "ボディ", MB_OK);
	}
	else {
		TCHAR szBuf[256];
		wsprintf(szBuf, TEXT("Status Code %d"), dwStatusCode);
		MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
	}

	WinHttpCloseHandle(hRequest);
	WinHttpCloseHandle(hConnect);
	WinHttpCloseHandle(hSession);
	
	return 0;
}

POSTリクエストを発行する場合は、WinHttpOpenRequestの第2引数に"POST"を指定するようにします。 WinHttpSendRequestの第4引数にはデータを指定することができますが、 今回はWinHttpWriteDataでデータを送るようにするため、WINHTTP_NO_REQUEST_DATAを指定します。 この場合、WinHttpSendRequestはHTTPのヘッダだけを送信することになります。 第6引数の値はWinHttpWriteDataで送信することになるデータの合計サイズであり、 WinHttpWriteDataを複数回呼び出す場合は、 それら呼び出しにおける第3引数の値を全て加算したものをdwTotalLengthに指定します。 ところで、POSTではデータを分割して送ることができるわけですが、 最後のデータが送られたことは、サーバーはどのように知ることができるのでしょうか。 実はこれはWinHttpReceiveResponseが行っています。 この関数はサーバーに送るべきデータがないことを伝え、 サーバーからレスポンスを受け取ります。 ちなみに、GETの場合はWinHttpSendRequestの時点でレスポンスは返っていますが、 そのような場合でもWinHttpReceiveResponseを呼び出すのが基本です。

今回のWinHttpQueryHeadersでは、第2引数にWINHTTP_QUERY_STATUS_CODEを指定して、 ヘッダ全体ではなくスタータスコードのみを取得しようとしています。 これによって、HTTPリクエストが成功したかどうかを確認しやすくなります。

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) {
	WinHttpReadData(hRequest, buffer, sizeof(buffer), NULL);
	MessageBoxA(NULL, (LPSTR)buffer, "ボディ", MB_OK);
}
else {
	TCHAR szBuf[256];
	wsprintf(szBuf, TEXT("Status Code %d"), dwStatusCode);
	MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
}

第2引数にWINHTTP_QUERY_FLAG_NUMBERを指定している点も重要です。 これにより、スタータスコードをDWORD型で受け取ることができます。 値がHTTP_STATUS_OKである場合はHTTPリクエストが成功したということで、 WinHttpReadDataによってボディを取得しています。 前節ではWinHttpQueryDataAvailableを呼び出してデータのサイズを取得していましたが、 今回は予め大きなサイズのバッファを用意する方法を使用しています。

GETによる投稿

今回はPOSTを使用してデータをサーバーに渡しましたが、 GETを使用してもサーバーにデータを渡すことができます。 次に例を示します。

#include <windows.h>
#include <winhttp.h>

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HINTERNET      hSession, hConnect, hRequest;
	URL_COMPONENTS urlComponents;
	WCHAR          szHostName[256], szUrlPath[2048];
	WCHAR          szUrl[] = L"http://eternalwindows.jp/network/winhttp/sample2.php?msg=abc";
	DWORD          dwSize;
	DWORD          dwStatusCode;
	BYTE           buffer[4096];

	hSession = WinHttpOpen(L"sample", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
	if (hSession == NULL)
		return 0;

	ZeroMemory(&urlComponents, sizeof(URL_COMPONENTS));
	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(szUrl, lstrlenW(szUrl), 0, &urlComponents)) {
		WinHttpCloseHandle(hSession);
		return 0;
	}	

	hConnect = WinHttpConnect(hSession, szHostName, INTERNET_DEFAULT_PORT, 0);
	if (hConnect == NULL) {
		WinHttpCloseHandle(hSession);
		return 0;
	}

	hRequest = WinHttpOpenRequest(hConnect, L"GET", szUrlPath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
	if (hRequest == NULL) {
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return 0;
	}

	if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) {
		WinHttpCloseHandle(hRequest);
		WinHttpCloseHandle(hConnect);
		WinHttpCloseHandle(hSession);
		return 0;
	}

	WinHttpReceiveResponse(hRequest, NULL);

	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) {
		WinHttpReadData(hRequest, buffer, sizeof(buffer), NULL);
		MessageBoxA(NULL, (LPSTR)buffer, "ボディ", MB_OK);
	}
	else {
		TCHAR szBuf[256];
		wsprintf(szBuf, TEXT("Status Code %d"), dwStatusCode);
		MessageBox(NULL, szBuf, NULL, MB_ICONWARNING);
	}

	WinHttpCloseHandle(hRequest);
	WinHttpCloseHandle(hConnect);
	WinHttpCloseHandle(hSession);
	
	return 0;
}

GETでデータを渡す場合はファイル名の後ろに?を指定し、その後に渡したいデータをdata=valueの形で指定します。 複数のデータを渡す場合は、&で結ぶことになります。 GETの場合はURLを使用するという関係上、渡すことのできるデータの長さには限りがあるためで、 長いデータの場合はHTTPのボディを使用するPOSTの方が適切であるといえます。



戻る