Upload archivos a un directorio WebDav de IIS mediante la API de WinINet

En este artículo se describe cómo cargar archivos mediante programación en un directorio WebDav hospedado en Microsoft Internet Information Services (IIS) mediante la API de WinINet.

Versión del producto original:   Internet Information Services
Número KB original:   2001156

Síntomas

La aplicación está cargando archivos mediante programación en un directorio WebDav hospedado en IIS, mediante la API de WinINet para enviar un verbo del Protocolo de transferencia de hipertexto PUT (HTTP). Observe que la aplicación funciona correctamente en IIS y Windows Server, pero puede que no pueda cargar archivos en el directorio WebDav de IIS, aunque no se hayan realizado cambios de código en la aplicación WinINet.

Causa

La causa de este problema se debe a un cambio de diseño para WebDav que se ejecuta en IIS. WebDav que se ejecuta en IIS ahora requiere autenticación y no funcionará si solo se usa autenticación anónima. Debido a esto, la aplicación que usa la secuencia de API de WinINet de , experimentará que la llamada a devuelve FALSE e indicará un código de HttpSendRequestEx InternetWriteFile error de HttpEndRequest HttpEndRequest GetLastError() 12032 - ERROR_INTERNET_FORCE_RETRY .

Solución

La solución a este problema es reintentar la misma secuencia de operaciones, es decir:

  1. HttpSendRequestEx
  2. InternetWriteFile
  3. HttpEndRequest

Hasta que no devuelve FALSE y no informa HttpEndRequest de GetLastError() 12032 (o hay algún otro error). Si IIS se presenta con información de autenticación incorrecta, IIS seguirá devolviendo un error HTTP 401 para cada reintento. Por lo tanto, tendrás que realizar un seguimiento de cuántas veces la función devuelve el error 12032 y evitar que se ejecute HttpEndRequest en un bucle infinito.

Si hay una Windows NTLM, devolverá el error 12032 durante un máximo de dos veces para satisfacer el protocolo de enlace NTLM de tres HttpEndRequest vías. El primer error 12032 indicará una respuesta de error HTTP 401 del servidor y el segundo error 12032 indicará el mensaje de enlace NTLM de tipo 2 del servidor, que, si se pasa información de autenticación válida a IIS, el usuario se autenticará correctamente y la carga se realizará correctamente.

Cuando use una lógica de reintentos para llamar a las funciones anteriores en un bucle, observe que la llamada a se realiza InternetWriteFile varias veces. Esto significa que la llamada a terminará escribiendo los datos a través de la red y esto provocará InternetWriteFile un desperdicio de ancho de banda. Para evitar que esto suceda, puede enviar una solicitud HTTP ficticia al servidor, que autenticará previamente la solicitud y hará que la llamada posterior no envíe la carga HTTP cuando se HEAD HttpSendRequest InternetWriteFile llame. Si está familiarizado con el monitor de red o el registro de WinINet, verá que la primera solicitud enviada al servidor tendrá una longitud de contenido de cero, lo que impide la transferencia de carga y la carga no se transferirá hasta que se complete el protocolo de enlace PUT NTLM.

La característica WebDav no requiere que use la autenticación de Windows; puede configurar el servidor WebDav para que use la autenticación básica sobre SSL, lo que garantizará la seguridad de la carga de datos. Cuando se configura la autenticación básica, puede insertar directamente una cadena de contraseña de nombre de usuario codificada base 64 válida en la solicitud saliente, lo que impedirá que IIS devuelva un error HTTP 401 y por eso no devolverá el HttpEndRequest error 12032. Puede agregar la información de autenticación básica a la solicitud saliente llamando a la API de WinINet:

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

Haga esto antes de llamar HttpSendRequestEx para insertar directamente el encabezado Authorization en la solicitud HTTP saliente.

En el ejemplo de código siguiente se muestra cómo se puede usar la lógica de reintentos para controlar la respuesta de devolución del error 12032 de HttpEndRequest . En este ejemplo no se trata la solicitud de autenticación previa descrita anteriormente. Para autenticarse previamente, todo lo que tendrá que hacer es llamar a , con el verbo HTTP al servidor de destino antes de llamar HttpOpenRequest HttpSendRequest al código HEAD HttpSendRequestEx siguiente.

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

Más información