EternalWindows
WinHTTP / Webサービスの使用

これまで、WinHTTPを使用してWebページのHTMLを取得してきましたが、 このような行為が実際に必要となることは稀といえるでしょう。 ブラウザのようなアプリケーションならば、取得したHTMLを基にレンダリング処理を行う用途がありますが、 そうでないアプリケーションにとっては、HTMLデータの用途はそこまで多いものではありません。 しかしながら、HTMLを取得するためにWinHTTPを使用するという視点から、 Webサービスを使用するためにWinHTTPを使用するという視点に変えた場合、 WinHTTPにはとても役に立つAPIに変化します。

Webサービスが何であるかを理解するためには、クライアントとHTTPサーバーの通信について考えるのがよいでしょう。 まず通常のHTTP通信というのは、クライアントがHTMLファイルの名前をURLに含めるようにしてリクエストを発行し、 サーバーがそのHTMLファイルの中身を返すというものです。 しかし、どのようなデータを返すかどうかは究極的にはサーバー側に権利がありますから、 HTTPリクエストを受けながらもHTML以外のデータを返すことも事実上は可能なわけです。 ということは、サーバーのサービス(機能)を使用するための文字列をURLに含めてリクエストを発行し、 サーバーはそれを基に処理を実行して結果を返すことも当然成立するはずです。 このように、Webを通じてサーバーのサービスを使用することがWebサービスになります。 クライアントがサーバーのサービスを使用するためには、サービスとして使用できるURLが公開されていなければならず、 たとえばgoogleでは次のURLをサービスとして使用できるようになっています。

http://google.com/complete/search?output=toolbar&hl=ja&q=keyword

上記のURLにはHTMLファイルが含まれていませんが、このURLはgoogleの検索候補を取得するためのものであるため問題ありません。 ?以降の部分には、data=valueの形を&で区切ってつなげることができ、 たとえばhl=jaは検索候補として日本語を考慮することを意味しています。 q=の部分には検索するキーワードを指定することができ、 サーバーはこのキーワードを基に検索候補を作成して、これをXML形式のデータでクライアントに返します。 XML形式のデータがどのようなものを知りたい場合は、ブラウザのアドレスバーに上記のURLを入力するとよいでしょう。 そうすれば、ブラウザ上にサーバーから返されたXML形式のデータが表示されることになります。 ちなみに、サービスをHTTPリクエストで要求し、結果をXML形式で受け取る通信はRESTと呼ばれることがあります。

Webサービスは、クライアントがサーバーの機能を使用するというAPI的や役割を果たすことから、 Web APIやWebサービス APIと呼ばれることもあります。 現に、先に示したURLはGoogle Suggest APIと一般的に呼ばれています。 開発者がWeb APIに対しても視野を向けるようになれば、 アプリケーションがサポートできる機能は大幅に増えることになるでしょう。 つまり、必要としている機能がWindows APIに実装されていなくても、 Web上に実装されているのであればそれを使用できるということです。 たとえば、Windows APIには日本語を英語に変換するような翻訳APIは存在しないと思われますが、 Web上にはこうしたAPIが公開されていますから、それを使用するようになればよいでしょう。 そして、こうしたWeb APIをアプリケーションが使用するためには、 HTTP通信をラッピングしたWinHTTPが適任ということになります。

今回のプログラムは、Google Suggest APIを使用して検索候補を取得します。 XMLの解析にXmlLiteというAPIを使用するため、xmllite.hとインクルードとxmllite.libへのリンクが必要になります。

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

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

void ShowXmlData(LPBYTE lp, DWORD dwSize);

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://google.com/complete/search?output=toolbar&hl=ja&q=abc";
	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"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) {
		DWORD dwRead;
		WinHttpReadData(hRequest, buffer, sizeof(buffer), &dwRead);
		ShowXmlData(buffer, dwRead);
	}
	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;
}

void ShowXmlData(LPBYTE lp, DWORD dwSize)
{
	IXmlReader  *pReader;
	XmlNodeType nodeType;
	LPCWSTR     lpszName, lpszValue;
	HGLOBAL     hglobal;
	IStream     *pStream;
	
	CoInitialize(NULL);
	
	hglobal = GlobalAlloc(GPTR, dwSize);
	CopyMemory(hglobal, lp, dwSize);
	CreateStreamOnHGlobal(hglobal, FALSE, &pStream);

	CreateXmlReader(IID_PPV_ARGS(&pReader), NULL);
	pReader->SetInput(pStream);
	
	while (pReader->Read(&nodeType) == S_OK) {
		if (nodeType == XmlNodeType_Element) {
			pReader->MoveToFirstAttribute();
			pReader->GetLocalName(&lpszName, NULL);
			if (lstrcmpW(lpszName, L"data") == 0) {
				pReader->GetValue(&lpszValue, NULL);
				MessageBox(NULL, lpszValue, TEXT("OK"), MB_OK);
			}
		}
	}
	
	pReader->Release();
	pStream->Release();
	GlobalFree(hglobal);
	CoUninitialize();
}

szUrlにGoogle Suggest APIを使用する旨を指定していることから、 WinHttpReadDataで取得できるデータは、検索候補を含んだXMLデータになります。 ShowXmlDataはこのデータをXmlLiteで解析し、検索候補の文字列を1つずつ表示します。 XmlLiteを使用するためには、CreateXmlReaderで取得したIXmlReaderに解析対象のデータをSetInputで渡さなければなりません。 このデータはIStreamで識別されている必要があるため、CreateStreamOnHGlobalなどでこれを行っています。 IXmlReader::Readは1つの要素を取得することができ、GetLocalNameを呼び出せばその要素の名前を取得することができます。 ただし、検索候補のデータは要素の中身としてではなく、要素の属性として格納されているため、 属性を参照できるようにMoveToFirstAttributeを呼び出しておきます。 属性がdata属性である場合、GetValueで取得できる文字列が検索候補を表しています。


戻る