使用 WinINet API 将文件上传到 IIS WebDav 目录
本文介绍如何使用 WinINet API 以编程方式将文件上传到 Microsoft Internet Information Services (IIS) 上托管的 WebDav 目录。
原始产品版本: Internet 信息服务
原始 KB 编号: 2001156
症状
应用程序使用 WinINet API 以编程方式将文件上传到 IIS 上托管的 WebDav 目录, (HTTP) PUT
谓词发送超文本传输协议。 你注意到,应用程序在 IIS 和 Windows Server 上正常工作,但可能无法将文件上传到 IIS WebDav 目录,即使没有对 WinINet 应用程序进行代码更改也是如此。
原因
此问题的原因是 IIS 上运行的 WebDav 的设计更改。 在 IIS 上运行的 WebDav 现在需要身份验证,如果仅使用匿名身份验证,则不起作用。 因此,使用 的 WinINet API 序列 的应用程序将遇到 对 HttpEndRequest
的HttpSendRequestEx
HttpEndRequest
InternetWriteFile
调用返回 FALSE,并将GetLastError()
指示错误代码 12032 - 。 ERROR_INTERNET_FORCE_RETRY
解决方案
此问题的解决方法是重试相同的操作序列,即:
HttpSendRequestEx
InternetWriteFile
HttpEndRequest
直到 HttpEndRequest
不返回 FALSE 且 GetLastError()
不报告回 12032 (或) 出现其他错误。 如果 IIS 显示错误的身份验证信息,则 IIS 将在每次重试时继续返回 HTTP 错误 401。 因此,需要跟踪函数返回错误 12032 的次数 HttpEndRequest
,并防止进入无限循环。
如果有 Windows NTLM 身份验证, HttpEndRequest
将返回错误 12032 最多两次,以满足三向 NTLM 握手。 第一个错误 12032 将指示来自服务器的 HTTP 错误 401 响应,第二个错误 12032 将指示来自服务器的 Type-2 NTLM 握手消息,如果向 IIS 传递了有效的身份验证信息,则将对用户进行身份验证并成功上传。
使用重试逻辑在循环中调用上述函数时,请注意,对 的调用 InternetWriteFile
是多次进行的。 这意味着,对 InternetWriteFile
的调用最终将通过网络写入数据,这将导致带宽浪费。 若要防止这种情况发生,可以将虚拟 HTTP HEAD
请求发送到服务器,该请求将对请求进行预身份验证,并导致 稍后调用 HttpSendRequest
在调用 时 InternetWriteFile
不发送 HTTP 有效负载。 如果熟悉网络监视器或 WinINet 日志记录,你将看到发送到服务器的第 PUT
一个请求将具有零的内容长度,这将阻止有效负载传输,在 NTLM 握手完成之前不会传输有效负载。
WebDav 功能不要求你使用 Windows 身份验证;可以将 WebDav 服务器配置为使用基于 SSL 的基本身份验证,这将确保数据上传的安全性。 配置基本身份验证后,可以直接在传出请求中注入有效的 base-64 编码用户名密码字符串,这将防止 IIS 返回 HTTP 错误 401,这就是不会返回错误 12032 的原因 HttpEndRequest
。 可以通过调用 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
,HttpOpenRequest
HttpSendRequest
使用 HTTP HEAD
谓词调用 目标服务器。
代码示例
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;
}
}
更多信息
反馈
https://aka.ms/ContentUserFeedback。
即将发布:在整个 2024 年,我们将逐步淘汰作为内容反馈机制的“GitHub 问题”,并将其取代为新的反馈系统。 有关详细信息,请参阅:提交和查看相关反馈