次の方法で共有


WinINet API を使用して IIS WebDav ディレクトリにファイルをアップロードする

この記事では、WinINet API を使用して、Microsoft インターネット インフォメーション サービス (IIS) でホストされている WebDav ディレクトリにファイルをプログラムでアップロードする方法について説明します。

元の製品バージョン: インターネット インフォメーション サービス
元の KB 番号: 2001156

現象

アプリケーションは、WinINet API を使用してハイパーテキスト転送プロトコル (HTTP) PUT 動詞を送信することで、IIS でホストされている WebDav ディレクトリにプログラムによってファイルをアップロードしています。 アプリケーションは IIS と Windows Server で正しく動作しますが、WinINet アプリケーションにコード変更が加えられなかった場合でも、IIS WebDav ディレクトリへのファイルのアップロードに失敗する可能性があります。

原因

この問題の原因は、IIS で実行されている WebDav の設計変更が原因です。 IIS で実行されている WebDav には認証が必要になり、匿名認証のみが使用されている場合は機能しません。 このため、HttpSendRequestExInternetWriteFileHttpEndRequestの WinINet API シーケンスを使用するアプリケーションでは、HttpEndRequestの呼び出しによって FALSE が返されGetLastError()は 12032 - ERROR_INTERNET_FORCE_RETRY のエラー コードを示します。

解決方法

この問題の解決策は、次の操作の同じシーケンスを再試行することです。

  1. HttpSendRequestEx
  2. InternetWriteFile
  3. 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 動詞を使用してHttpSendRequestHttpOpenRequestを呼び出す必要があります。

コード サンプル

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;
    }
}

詳細