Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
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:
HttpSendRequestExInternetWriteFileHttpEndRequest
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;
}
}