Compartilhar via


Carregar arquivos em um diretório WebDav do IIS usando a API WinINet

Este artigo descreve como carregar arquivos programaticamente em um diretório WebDav hospedado no IIS (Serviços de Informações da Internet) da Microsoft usando a API WinINet.

Versão original do produto: Serviços de Informações da Internet
Número original do KB: 2001156

Sintomas

Seu aplicativo está carregando arquivos programaticamente para um diretório WebDav hospedado no IIS, usando a API WinINet para enviar um verbo HTTP (Hypertext Transfer Protocol). PUT Você observa que seu aplicativo funciona corretamente no IIS e no Windows Server, mas pode falhar ao carregar arquivos no diretório WebDav do IIS, mesmo que nenhuma alteração de código tenha sido feita no aplicativo WinINet.

Causa

A causa desse problema é devido a uma alteração de design para WebDav em execução no IIS. O WebDav em execução no IIS agora requer autenticação e não funcionará se apenas a autenticação anônima for usada. Por isso, seu aplicativo que usa a sequência da API WinINet de HttpSendRequestEx, InternetWriteFile, HttpEndRequest experimentará que a chamada para HttpEndRequest retorna FALSE e GetLastError() indicará um código de erro de 12032 - ERROR_INTERNET_FORCE_RETRY.

Solução

A solução para esse problema é repetir a mesma sequência de operações, a saber:

  1. HttpSendRequestEx
  2. InternetWriteFile
  3. HttpEndRequest

Until HttpEndRequest não retorna FALSE e GetLastError() não relata 12032 (ou há algum outro erro). Se o IIS receber informações de autenticação incorretas, o IIS continuará retornando um erro HTTP 401 para cada nova tentativa. Portanto, você precisará acompanhar quantas vezes a função retorna o HttpEndRequest erro 12032 e evitar a execução de um loop infinito.

Se houver a Autenticação NTLM do Windows, HttpEndRequest retornará o erro 12032 por no máximo duas vezes para atender ao handshake NTLM de três vias. O primeiro erro 12032 indicará uma resposta de erro HTTP 401 do servidor e o segundo erro 12032 indicará a mensagem de handshake NTLM Tipo 2 do servidor, que, se informações de autenticação válidas forem passadas para o IIS, o usuário será autenticado corretamente e o upload será bem-sucedido.

Quando você usa uma lógica de repetição para chamar as funções acima em um loop, observe que a chamada para InternetWriteFile está sendo feita várias vezes. O que isso significa é que a chamada para InternetWriteFile acabará gravando os dados pela rede e isso causará um desperdício de largura de banda. Para evitar que isso aconteça, você pode enviar uma solicitação HTTP HEAD fictícia ao servidor, que pré-autenticará a solicitação e fará com que a chamada posterior não HttpSendRequest envie o conteúdo HTTP quando InternetWriteFile for chamado. Se você estiver familiarizado com o log do Monitor de Rede ou do WinINet, verá que a primeira PUT solicitação enviada ao servidor terá um Content-Length de zero, o que impede a transferência de conteúdo e o conteúdo não será transferido até que o handshake NTLM seja concluído.

O recurso WebDav não exige que você use a Autenticação do Windows; você pode configurar seu servidor WebDav para usar a autenticação básica sobre SSL, o que garantirá a segurança do upload de dados. Quando a autenticação básica é configurada, você pode injetar diretamente uma cadeia de caracteres de senha de nome de usuário codificada em base 64 válida na solicitação de saída, o que impedirá que o IIS retorne um erro HTTP 401 e é por isso que HttpEndRequest não retornará o erro 12032. Você pode adicionar as informações de autenticação básica à solicitação de saída chamando a API do WinINet:

HttpAddRequestHeaders(hRequest, "Authorization: Basic <<valid base-64 encoded username:password string>>\r\n", -1, HTTP_ADDREQ_FLAG_ADD);

Faça isso antes de chamar HttpSendRequestEx para injetar diretamente o cabeçalho Authorization na solicitação HTTP de saída.

O exemplo de código a seguir mostra como você pode usar a lógica de repetição para lidar com a resposta de retorno do erro 12032 do HttpEndRequest. Este exemplo não aborda a solicitação de pré-autenticação discutida acima. Para pré-autenticar, tudo o que você precisa fazer é chamar HttpOpenRequest, HttpSendRequest com o verbo HTTP HEAD para o servidor de destino antes de chamar o HttpSendRequestEx código abaixo.

Exemplo de código

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

Mais informações