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

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Web/DB プログラミング徹底解説