WinHTTP API を使った単純な HTTP GET リクエストの送信
ネットワークプログラミングのうちで、Web サーバに問い合わせを行う API やライブラリはたくさんあります。
.NET Framework で言えば以前紹介した HttpWebRequest を用いる方法 がありますし、古くは WinInet API や WinHTTP API を用いて VBScript から HTTP リクエストを送信する方法 などもあります。
あ、その他、もちろん WinSock を使って自分で HTTP を書いても OK です。
ちなみに、いろんなテクノロジがありますが、ここで紹介する WinHTTP API は WinInet を発展させたという位置づけになりますから、 WinHTTP をわかっていれば、WinInet を使用する理由はありません。
WinInet は実はマルチスレッド環境で安定していなくて、例えばサーバー側でさらに HTTP リクエストを送信するような場合に、 デッドロックを起こしてしまうような問題が知られていた (と思います)。
WinHTTP API は基本的に C/C++ から利用するものなので、ここでは C/C++ の簡単な例を示します。
サンプルコード
このコードでは www.google.com に対して GET リクエストを送信して、 その結果を受け取り、ヘッダとボディを出力します。
www.google.com などの URL 指定の箇所はご自身の管理するサーバに書き換えてテストしてください。
#include <windows.h> #include <tchar.h> #include <stdio.h> #include <winhttp.h> #include <assert.h> ///////////////////////////////////////////////////////////////////// void PrintHeaders(HINTERNET hRequest) { BOOL bResults; WCHAR* pwszHeader; DWORD dwIndex = 0; DWORD dwBuffSize = sizeof(WCHAR) * (256+1); DWORD dwGle; // // Print All the headers. // bResults = WinHttpQueryHeaders( hRequest, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &dwBuffSize, WINHTTP_NO_HEADER_INDEX); dwGle = GetLastError(); if(ERROR_INSUFFICIENT_BUFFER != dwGle) { printf("*** Unexpected Error @ PrintHeadres ***\n"); return; } // メモリの割り当て pwszHeader = new WCHAR[dwBuffSize/sizeof(WCHAR)]; if(!pwszHeader) { printf("*** Couldn't allocate memory ***\n"); return; } // ヘッダ値を取得 bResults = WinHttpQueryHeaders( hRequest, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, pwszHeader, &dwBuffSize, WINHTTP_NO_HEADER_INDEX); // 表示 if(bResults) { printf("%S\n----------\n", pwszHeader); } else { printf("WinHttpQueryHeaders Failed (%d).\n", GetLastError()); } // clean up the memory. delete [] pwszHeader; } ///////////////////////////////////////////////////////////////////// int main(int argc, char* argv[]) { DWORD dwSize = 0; DWORD dwDownloaded = 0; LPSTR pszOutBuffer; BOOL bResults = FALSE; HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL; // WinHTTP をサポートしているか確認 if(!WinHttpCheckPlatform()) { printf("WinHTTP not supprted.\n"); } // セッションハンドルを取得 hSession = WinHttpOpen( L"WinHTTP Example/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); assert(hSession); // HTTP サーバの指定 hConnect = WinHttpConnect( hSession, L"www.google.com", INTERNET_DEFAULT_HTTP_PORT, 0); assert(hConnect); // HTTP リクエストハンドルを作成 hRequest = WinHttpOpenRequest( hConnect, L"GET", L"/", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0); assert(hRequest); // Send a request. bResults = WinHttpSendRequest( hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0); assert(bResults); // リクエスト終了 bResults = WinHttpReceiveResponse( hRequest, NULL); assert(bResults); // // レスポンスヘッダを読み込み表示 // PrintHeaders( hRequest ); // // Read HTTP Body // do { // 利用可能なデータがあるかチェックする dwSize = 0; if (!WinHttpQueryDataAvailable( hRequest, &dwSize)) { printf("Error %u in WinHttpQueryDataAvailable.\n",GetLastError()); } // バッファの割り当て 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); delete [] pszOutBuffer; } } while (dwSize>0); // // クリーンアップ // if (hRequest) WinHttpCloseHandle(hRequest); if (hConnect) WinHttpCloseHandle(hConnect); if (hSession) WinHttpCloseHandle(hSession); }
makefile は次の通りです。上記のコードを client.cpp として保存したことを想定しています。
TARGETNAME=client OUTDIR=.\chk LINK32=link.exe ALL : "$(OUTDIR)\$(TARGETNAME).exe" CPPFLAGS=\ /nologo\ /MT\ /W3\ /Fo"$(OUTDIR)\\"\ /Fd"$(OUTDIR)\\"\ /c\ /Zi\ /DWIN32 LINK32_FLAGS=\ winhttp.lib\ /nologo\ /subsystem:console\ /pdb:"$(OUTDIR)\$(TARGETNAME).pdb"\ /machine:I386\ /out:"$(OUTDIR)\$(TARGETNAME).exe"\ /DEBUG LINK32_OBJS= \ "$(OUTDIR)\$(TARGETNAME).obj" "$(OUTDIR)\$(TARGETNAME).exe" : "$(OUTDIR)" $(LINK32_OBJS) $(LINK32) $(LINK32_FLAGS) $(LINK32_OBJS) "$(OUTDIR)" : if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" .c{$(OUTDIR)}.obj: $(CPP) $(CPPFLAGS) $< .cpp{$(OUTDIR)}.obj: $(CPP) $(CPPFLAGS) $<
簡単に、といっても VBScript から使う場合よりは面倒に感じるかもしれませんね。
ちなみに、エラー処理は行わず、エラーの時は assert を使って単に処理を止めています。 自分で書いてて言うのもなんですが、こんな使い方が assert の正しい使い方だとは思わないでくださいね!