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 の正しい使い方だとは思わないでくださいね!