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

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

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

症状

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

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

 备注

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

原因

出现此问题的原因是,使用 HttpWebRequest 类时,默认情况下,.NET Framework会缓冲传出数据。

解决方法

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

解决方法导致的错误

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

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

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

匿名身份验证

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

基本身份验证

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

C#
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 服务器配置为使用协商身份验证,并且必须将 属性设置为 HttpWebRequest.AllowWriteStreamBuffering false,则必须发送HEAD请求以在发送 POST 或 PUT 请求之前对连接进行预身份验证。 还可以将 HttpWebRequest.PreAuthenticate 属性设置为 true。 此外,可能必须将 属性设置为 HttpWebRequest.UnsafeAuthenticatedConnectionSharing true。 然后,发送 POST 或 PUT 请求,然后接收响应。 为此,可以使用类似于以下代码示例的代码。

 备注

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

C#
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-Integrated 身份验证,并且必须将 属性设置为 HttpWebRequest.AllowWriteStreamBuffering false,则可以在客户端代码中将身份验证类型设置为 NTLM。 将 IIS 配置为同时使用 Negotiate 和 NTLM 身份验证并在客户端代码中将身份验证类型设置为 NTLM 后,可以通过将 IIS 元数据库中的 AuthPersistSingleRequest 属性设置为 false 来配置 IIS 处理身份验证请求的方式。

 备注

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

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

C#
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 请求来预验证每个请求。

References

posted on 2024-04-23 17:28  大西瓜3721  阅读(252)  评论(0编辑  收藏  举报

导航