使用 HttpWebRequest 类发送大量数据时,POST 或 PUT 请求失败

本文可帮助你解决在使用类在运行 Microsoft .NET Framework 的计算机上发送大量数据时 HttpWebRequest 可能会引发错误的问题。

原始产品版本: .NET Framework
原始 KB 数: 908573

症状

使用HttpWebRequest类发送大量数据POSTPUT时,请求可能会在运行 .NET Framework 的计算机上失败。 此外,可能会收到内存不足或超时错误消息。

如果未收到内存不足或超时错误消息,你可能会注意到使用该 HttpWebRequest 类的应用程序使用大量内存。 使用性能监视器监视使用该HttpWebRequest类的应用程序时,发送数据时,专用字节计数将继续增加。 因此,由于内存和资源利用率增加,计算机和其他应用程序中的性能也可能会降低。

注释

默认情况下可以上传的数据量将因计算机上的可用内存和资源而异。

原因

之所以出现此问题,是因为使用类时 HttpWebRequest ,.NET Framework 会默认缓冲传出数据。

解决方法

若要解决此问题,请将 HttpWebRequest.AllowWriteStreamBuffering 属性设置为 false。

解决方法导致的错误

将属性设置为 false 时,可能会收到类似于以下示例的 HttpWebRequest.AllowWriteStreamBuffering 错误消息:

此请求需要缓冲数据,以便对重定向进行身份验证才能成功。

若要在属性设置为 false 时POST使用PUTHttpWebRequest.AllowWriteStreamBuffering请求成功发送大量数据,请使用以下方法之一,具体取决于要使用的身份验证方法。

匿名身份验证

如果 Web 服务器配置为使用匿名身份验证,请将 HttpWebRequest.AllowWriteStreamBuffering 属性设置为 false。 无需进行其他更改。

基本身份验证

如果 Internet Information Services (IIS) Web 服务器配置为使用基本身份验证,并且可以将属性设置为 HttpWebRequest.AllowWriteStreamBuffering false,则必须在发送HEADPOST请求之前发送请求PUT以预先对连接进行身份验证。 还应将 HttpWebRequest.PreAuthenticate 属性设置为 true。 然后发送 POSTPUT 请求,然后接收响应。 若要执行此操作,请使用类似于以下代码示例的代码。

public void test(Uri URL)
{
    HttpWebRequest WRequest;
    HttpWebResponse WResponse;
    //preAuth the request
    // You can add logic so that you only pre-authenticate the very first request.
    // You should not have to pre-authenticate each request.
    WRequest = (HttpWebRequest)HttpWebRequest.Create(URL);
    // Set the username and the password.
    WRequest.Credentials = new NetworkCredential(user, password);
    WRequest.PreAuthenticate = true;
    WRequest.UserAgent = "Upload Test";
    WRequest.Method = "HEAD";
    WRequest.Timeout = 10000;
    WResponse = (HttpWebResponse)WRequest.GetResponse();
    WResponse.Close();
    // Make the real request.
    WRequest = (HttpWebRequest)HttpWebRequest.Create(URL);
    // Set the username and the password.
    WRequest.Credentials = new NetworkCredential(user, password);
    WRequest.PreAuthenticate = true;
    WRequest.UserAgent = "Upload Test";
    WRequest.Method = "POST";
    WRequest.AllowWriteStreamBuffering = false;
    WRequest.Timeout = 10000;
    FileStream ReadIn = new FileStream("c:\\testuploadfile.txt", FileMode.Open, FileAccess.Read);
    ReadIn.Seek(0, SeekOrigin.Begin); // Move to the start of the file.
    WRequest.ContentLength = ReadIn.Length; // Set the content length header to the size of the file.
    Byte[] FileData = new Byte[ReadIn.Length]; // Read the file in 2 KB segments.
    int DataRead = 0;
    Stream tempStream = WRequest.GetRequestStream();
    do
    {
        DataRead = ReadIn.Read(FileData,0,2048);
        if (DataRead > 0) //we have data
        {
            tempStream.Write(FileData,0,DataRead);
            Array.Clear(FileData,0, 2048); // Clear the array.
        }
    } while (DataRead > 0);

    WResponse = (HttpWebResponse)WRequest.GetResponse();
    // Read your response data here.
    // Close all streams.
    ReadIn.Close();
    tempStream.Close();
    WResponse.Close();
}

注释

根据应用程序的设计方式,可能不必通过发送 HEAD 请求来预验证每个请求。

Windows 集成身份验证

可以使用 Negotiate 或 Windows 质询/响应(NTLM)Windows 身份验证配置安装 IIS 以响应的计算机。 如果 IIS 配置为使用 Negotiate for Windows 身份验证,则客户端可以使用 Kerberos 或 NTLM 进行身份验证。 如果 IIS 配置为使用 NTLM 身份验证,则只能使用 NTLM 身份验证,并且不支持 Kerberos 身份验证。

如果使用 Kerberos 身份验证协商,请使用以下解决方法。 如果使用 NTLM,解决方法将失败。

与 Kerberos 身份验证协商

如果 IIS Web 服务器配置为使用 Negotiate 身份验证,并且必须将属性设置为 HttpWebRequest.AllowWriteStreamBuffering false,则必须发送 HEAD 请求,以便在发送 POST 或 PUT 请求之前预先对连接进行身份验证。 还可以将 HttpWebRequest.PreAuthenticate 属性设置为 true。 此外,可能需要将 HttpWebRequest.UnsafeAuthenticatedConnectionSharing 属性设置为 true。 然后,发送 POST 或 PUT 请求,然后接收响应。 为此,可以使用类似于以下代码示例的代码。

注释

如果客户端无法使用 Kerberos 进行协商身份验证,则此解决方法将失败。 还必须确保属性 HttpWebRequest.KeepAlive 设置为 true。 默认情况下,属性 HttpWebRequest.KeepAlive 的设置为 true。 Kerberos 和基本身份验证的逻辑几乎相同。

public void test(Uri URL)
{
    HttpWebRequest WRequest;
    HttpWebResponse WResponse;
    CredentialCache myCredCache = new CredentialCache();
    myCredCache.Add(URL,"Negotiate",(NetworkCredential) CredentialCache.DefaultCredentials);
    // Pre-authenticate the request.
    WRequest = (HttpWebRequest)HttpWebRequest.Create(URL);
    // Set the username and the password.
    WRequest.Credentials = myCredCache;
    // This property must be set to true for Kerberos authentication.
    WRequest.PreAuthenticate = true;
    // Keep the connection alive.
    WRequest.UnsafeAuthenticatedConnectionSharing = true;
    WRequest.UserAgent = "Upload Test";
    WRequest.Method = "HEAD";
    WRequest.Timeout = 10000;
    WResponse = (HttpWebResponse)WRequest.GetResponse(); 
    WResponse.Close();
    // Make the real request.
    WRequest = (HttpWebRequest)HttpWebRequest.Create(URL);
    // Set the username and the password.
    WRequest.Credentials = myCredCache;
    // This property must be set to true for Kerberos authentication.
    WRequest.PreAuthenticate = true;
    // Keep the connection alive.
    WRequest.UnsafeAuthenticatedConnectionSharing = true;
    WRequest.UserAgent = "Upload Test";
    WRequest.Method = "POST";
    WRequest.AllowWriteStreamBuffering = false;
    WRequest.Timeout = 10000;
    FileStream ReadIn = new FileStream("c:\\testuploadfile.txt ", FileMode.Open, FileAccess.Read);
    ReadIn.Seek(0, SeekOrigin.Begin); // Move to the start of the file.
    WRequest.ContentLength = ReadIn.Length; // Set the content length header to the size of the file.
    Byte[] FileData = new Byte[ReadIn.Length]; // Read the file in 2 KB segments.
    int DataRead = 0;
    Stream tempStream = WRequest.GetRequestStream();
    do
    {
        DataRead = ReadIn.Read(FileData,0,2048);
        if (DataRead > 0) // We have data.
        {
            tempStream.Write(FileData,0,DataRead);
            Array.Clear(FileData,0, 2048); // Clear the array.
        }
    }while(DataRead > 0);

    WResponse = (HttpWebResponse)WRequest.GetResponse(); 
    // Read your response data here.
    // Close all streams
    ReadIn.Close();
    tempStream.Close();
    WResponse.Close();
}

注释

根据应用程序的设计方式,可能不必通过发送 HEAD 请求来预验证每个请求。

NTLM 身份验证

如果 IIS Web 服务器还配置为将 NTLM 身份验证与 Windows 集成身份验证配合使用,并且必须将属性设置为 false,则可以在客户端代码中将 HttpWebRequest.AllowWriteStreamBuffering 身份验证类型设置为 NTLM。 将 IIS 配置为同时使用 Negotiate 和 NTLM 身份验证,并将身份验证类型设置为客户端代码中的 NTLM 后,可以通过将 IIS 元数据库中的属性设置为 AuthPersistSingleRequest false 来配置 IIS 如何处理身份验证请求。

注释

有关如何配置 IIS 以支持 Negotiate 和 NTLM 身份验证的详细信息,请参阅“ 参考” 部分。

在发送HEAD请求并将属性设置为 POST true 之前,还必须发送HttpWebrequest.UnsafeAuthenticatedConnectionSharing请求以预先对连接进行身份验证。 然后,将 HttpWebRequest.PreAuthenticate 属性设置为 false。 最后,发送 POSTPUT 请求,然后接收响应。 为此,请使用类似于以下代码示例的代码。

public void test(Uri URL)
{
    HttpWebRequest WRequest;
    HttpWebResponse WResponse;
    CredentialCache myCredCache = new CredentialCache();
    myCredCache.Add(URL,"NTLM",(NetworkCredential) CredentialCache.DefaultCredentials);
    // Pre-authenticate the request.
    WRequest = (HttpWebRequest)HttpWebRequest.Create(URL);
    // Set the username and the password.
    WRequest.Credentials = myCredCache;
    // For NTLM authentication, you must set the following property to true
    // so the connection does not close.
    WRequest.UnsafeAuthenticatedConnectionSharing = true;
    WRequest.UserAgent = "Upload Test";
    WRequest.Method = "HEAD";
    WRequest.Timeout = 10000;
    WResponse = (HttpWebResponse)WRequest.GetResponse(); 
    WResponse.Close();
    // Make the real request.
    WRequest = (HttpWebRequest)HttpWebRequest.Create(URL);
    // Set the username and the password.
    WRequest.Credentials = myCredCache;
    // For NTLM authentication, you must set the following property to true
    // so the connection does not close.
    WRequest.UnsafeAuthenticatedConnectionSharing = true;
    WRequest.UserAgent = "Upload Test";
    WRequest.Method = "POST";
    WRequest.AllowWriteStreamBuffering = false;
    WRequest.Timeout = 10000;
    FileStream ReadIn = new FileStream("c:\\ testuploadfile.txt", FileMode.Open, FileAccess.Read);
    ReadIn.Seek(0, SeekOrigin.Begin); // Move to the start of the file.
    WRequest.ContentLength = ReadIn.Length; // Set the content length header to the size of the file.
    Byte[] FileData = new Byte[ReadIn.Length]; // Read the file in 2 KB segments.
    int DataRead = 0;
    Stream tempStream = WRequest.GetRequestStream();
    do
    {
        DataRead = ReadIn.Read(FileData,0,2048);
        if (DataRead > 0) // We have data.
        {
            tempStream.Write(FileData,0,DataRead);
            Array.Clear(FileData,0, 2048); // Clear the array.
        }
    }while(DataRead > 0);

    WResponse = (HttpWebResponse)WRequest.GetResponse();
    // Read your response data here.
    // Close all streams.
    ReadIn.Close();
    tempStream.Close();
    WResponse.Close();
}

注释

根据应用程序的设计方式,可能不必通过发送 HEAD 请求来预验证每个请求。

参考文献