この記事では、WinINet API を使用して、Microsoft インターネット インフォメーション サービス (IIS) でホストされている WebDav ディレクトリにファイルをプログラムでアップロードする方法について説明します。
元の製品バージョン: インターネット インフォメーション サービス
元の KB 番号: 2001156
現象
アプリケーションは、WinINet API を使用してハイパーテキスト転送プロトコル (HTTP) PUT
動詞を送信することで、IIS でホストされている WebDav ディレクトリにプログラムによってファイルをアップロードしています。 アプリケーションは IIS と Windows Server で正しく動作しますが、WinINet アプリケーションにコード変更が加えられなかった場合でも、IIS WebDav ディレクトリへのファイルのアップロードに失敗する可能性があります。
原因
この問題の原因は、IIS で実行されている WebDav の設計変更が原因です。 IIS で実行されている WebDav には認証が必要になり、匿名認証のみが使用されている場合は機能しません。 このため、HttpSendRequestEx
、InternetWriteFile
、HttpEndRequest
の WinINet API シーケンスを使用するアプリケーションでは、HttpEndRequest
の呼び出しによって FALSE が返されGetLastError()
は 12032 - ERROR_INTERNET_FORCE_RETRY
のエラー コードを示します。
解決方法
この問題の解決策は、次の操作の同じシーケンスを再試行することです。
HttpSendRequestEx
InternetWriteFile
HttpEndRequest
HttpEndRequest
FALSE が返されないまでGetLastError()
は 12032 を報告しません (または、他のエラーが発生します)。 IIS に間違った認証情報が表示された場合、IIS は再試行ごとに HTTP エラー 401 を返し続けます。 そのため、 HttpEndRequest
関数がエラー 12032 を返す回数を追跡し、無限ループに陥らないようにする必要があります。
Windows NTLM 認証がある場合、 HttpEndRequest
は、3 方向 NTLM ハンドシェイクを満たすために最大 2 回エラー 12032 を返します。 最初のエラー 12032 はサーバーからの HTTP エラー 401 応答を示し、2 番目のエラー 12032 はサーバーからの Type-2 NTLM ハンドシェイク メッセージを示します。有効な認証情報が IIS に渡されると、ユーザーは正しく認証され、アップロードが成功します。
再試行ロジックを使用してループ内で上記の関数を呼び出すと、 InternetWriteFile
の呼び出しが複数回行われていることに注意してください。 つまり、 InternetWriteFile
の呼び出しによって最終的にネットワーク経由でデータが書き込まれるので、帯域幅が無駄になります。 これを回避するには、サーバーにダミーの HTTP HEAD
要求を送信します。これにより、要求が事前に認証され、後で呼び出された HttpSendRequest
が呼び出されたときに HTTP ペイロードが送信されなくなります InternetWriteFile
。 ネットワーク モニターまたは WinINet のログ記録に慣れている場合は、サーバーに送信された最初の PUT
要求の Content-Length が 0 であることがわかります。これにより、ペイロードの転送が妨げられるため、NTLM ハンドシェイクが完了するまでペイロードは転送されません。
WebDav 機能では、Windows 認証を使用する必要はありません。SSL 経由の基本認証を使用するように WebDav サーバーを構成できます。これにより、データアップロードの安全性が確保されます。 基本認証が構成されている場合は、有効な base-64 でエンコードされたユーザー名パスワード文字列を送信要求に直接挿入できます。これにより、IIS は HTTP エラー 401 を返さなくなり、 HttpEndRequest
はエラー 12032 を返しません。 WinINet API を呼び出すことで、基本認証情報を送信要求に追加できます。
HttpAddRequestHeaders(hRequest, "Authorization: Basic <<valid base-64 encoded username:password string>>\r\n", -1, HTTP_ADDREQ_FLAG_ADD);
これを行う前に、 HttpSendRequestEx
を呼び出して、送信 HTTP 要求に Authorization ヘッダーを直接挿入します。
次のコード サンプルは、再試行ロジックを使用して、 HttpEndRequest
からのエラー 12032 戻り応答を処理する方法を示しています。 このサンプルでは、上記で説明した事前認証要求については説明しません。 事前認証を行うには、次のHttpSendRequestEx
コードを呼び出す前に、ターゲット サーバーに対して HTTP HEAD
動詞を使用してHttpSendRequest
HttpOpenRequest
を呼び出す必要があります。
コード サンプル
BOOL UseHttpSendReqEx(HINTERNET hRequest, DWORD dwPostSize)
{
INTERNET_BUFFERS BufferIn;
DWORD dwBytesWritten;
int iChunkCtr;
BYTE pBuffer[1024];
BOOL bRet;
BufferIn.dwStructSize = sizeof( INTERNET_BUFFERS ); // Must be set or you will get an error
BufferIn.Next = NULL;
BufferIn.lpcszHeader = NULL;
BufferIn.dwHeadersLength = 0;
BufferIn.dwHeadersTotal = 0;
BufferIn.lpvBuffer = NULL;
BufferIn.dwBufferLength = 0;
BufferIn.dwBufferTotal = dwPostSize; // This is the only member used other than dwStructSize
BufferIn.dwOffsetLow = 0;
BufferIn.dwOffsetHigh = 0;
// The following variable will keep track of the number of times HttpSendRequestEx is called
int iNumTrials = 0;
bool bRetVal = FALSE;
// The retry goto is to re-try the operation when HttpEndRequest returns error 12032.
while(1)
{
if(!HttpSendRequestEx( hRequest, &BufferIn, NULL, 0, 0))
{
printf( "Error on HttpSendRequestEx %d\n",GetLastError());
return FALSE;
}
FillMemory(pBuffer, 1024, 'D'); // Fill buffer with data
bRet=TRUE;
for(iChunkCtr=1; iChunkCtr<=(int)dwPostSize/1024 && bRet; iChunkCtr++)
{
dwBytesWritten = 0;
if(bRet=InternetWriteFile( hRequest, pBuffer, 1024, &dwBytesWritten))
printf( "\r%d bytes sent.", iChunkCtr*1024);
}
if(!bRet)
{
printf( "\nError on InternetWriteFile %lu\n",GetLastError());
return FALSE;
}
if(!HttpEndRequest(hRequest, NULL, 0, 0))
{
int iLastError = GetLastError();
printf( "Error on HttpEndRequest %lu \n", iLastError);
// Use the following logic to "retry" after receiving error 12032 from HttpEndRequest
//
// Error 12032 = ERROR_INTERNET_FORCE_RETRY means that you just need to send the request again
//
// Sending request again means that you simply need to call:
//
// HttpSendRequest, InternetWriteFile, HttpEndRequest until HttpEndRequest does not return
// back error 12032.
//
// Since NTLM is a 3-way handshake protocol, it will happen that HttpEndRequest will return
// error 12032 two times and hence the following check.
//
// If error 12032 is returned 3 or more number of times, then there is some Other error.
if(iLastError == 12032 && iNumTrials < 3) {
iNumTrials++;
continue; // This will retry HttpSendRequestEx...
}
return FALSE;
}
return TRUE;
}
}