c# – 如何在请求流在传输过程中关闭时获取HTTP响应

TL; DR版本

当写入请求流时发生传输错误时,即使服务器发送,也无法访问响应.

完整版

我有一个.NET应用程序,使用HttpWebRequest将文件上传到Tomcat服务器.在某些情况下,服务器提前关闭请求流(因为它由于某种原因拒绝文件,例如文件名无效),并发送具有自定义标头的400响应以指示错误的原因.

问题是,如果上传文件很大,请求流在我完成编写请求正文之前关闭,我得到一个IOException:

Message: Unable to write data to the transport connection: An existing connection was forcibly closed by the remote host.
InnerException: SocketException: An existing connection was forcibly closed by the remote host

我可以捕获这个异常,但是当我调用GetResponse时,我得到一个WebException,其中以前的IOException作为它的内部异常,并且返回一个空的Response属性.所以我无法得到响应,即使服务器发送它(使用WireShark检查).

由于我无法得到答复,我不知道实际的问题是什么.从我的应用程序的角度来看,它看起来像是连接被中断,所以我把它当作一个网络相关的错误,并重试上传…当然,再次失败.

如何解决这个问题并从服务器检索实际响应?甚至有可能吗对我来说,目前的行为看起来像HttpWebRequest中的一个bug,或至少是一个严重的设计问题…

这是我用来重现问题的代码

var request = HttpWebRequest.CreateHttp(uri);
request.Method = "POST";
string filename = "foo\u00A0bar.dat"; // Invalid characters in filename,the server will refuse it
request.Headers["Content-disposition"] = string.Format("attachment; filename*=utf-8''{0}",Uri.EscapeDataString(filename));
request.AllowWriteStreamBuffering = false;
request.ContentType = "application/octet-stream";
request.ContentLength = 100 * 1024 * 1024;

// Upload the "file" (just random data in this case)
try
{
    using (var stream = request.GetRequestStream())
    {
        byte[] buffer = new byte[1024 * 1024];
        new Random().NextBytes(buffer);
        for (int i = 0; i < 100; i++)
        {
            stream.Write(buffer,buffer.Length);
        }
    }
}
catch(Exception ex)
{
    // here I get an IOException; InnerException is a SocketException
    Console.WriteLine("Error writing to stream: {0}",ex);
}

// Now try to read the response
try
{
    using (var response = (HttpWebResponse)request.GetResponse())
    {
        Console.WriteLine("{0} - {1}",(int)response.StatusCode,response.StatusDescription);
    }
}
catch(Exception ex)
{
    // here I get a WebException; InnerException is the IOException from the prevIoUs catch
    Console.WriteLine("Error getting the response: {0}",ex);
    var webEx = ex as WebException;
    if (webEx != null)
    {
        Console.WriteLine(webEx.Status); // SendFailure
        var response = (HttpWebResponse)webEx.Response;
        if (response != null)
        {
            Console.WriteLine("{0} - {1}",response.StatusDescription);
        }
        else
        {
            Console.WriteLine("No response");
        }
    }
}

补充笔记:

如果我正确地了解100继续状态的作用,服务器不应该将其发送给我,如果它将拒绝该文件.但是,似乎这个状态是由Tomcat直接控制的,不能被应用程序所控制.在这种情况下,理想情况下,我希望服务器不要送我100继续,但是根据我的后台负责人的介绍,没有简单的方法.所以我现在正在寻找一个客户端解决方案;但是如果您碰巧知道如何解决服务器端的问题,那么也不胜感激.

我遇到问题的应用程序针对.NET 4.0,但是我也用4.5转载.

我不会超时异常在超时之前抛出很久.

我尝试了异步请求.它没有改变任何东西.

我尝试将请求协议版本设置为HTTP 1.0,结果相同.

其他人已经在Connect问题上提交了一个错误https://connect.microsoft.com/VisualStudio/feedback/details/779622/unable-to-get-servers-error-response-when-uploading-file-with-httpwebrequest

解决方法

关于什么可以是客户端解决您的问题,我是不明智的.但是我仍然认为使用自定义tomcat阀门的服务器端解决方案可以在此帮助.我目前没有一个tomcat安装程序,我可以测试这个,但我认为这里的服务器端解决方案将如下:

RFC第8.2.3节明确规定:
HTTP / 1.1原始服务器的要求:

- Upon receiving a request which includes an Expect request-header
    field with the "100-continue" expectation,an origin server MUST
    either respond with 100 (Continue) status and continue to read
    from the input stream,or respond with a final status code. The
    origin server MUST NOT wait for the request body before sending
    the 100 (Continue) response. If it responds with a final status
    code,it MAY close the transport connection or it MAY continue
    to read and discard the rest of the request.  It MUST NOT
    perform the requested method if it returns a final status code.

所以假设tomcat确认到RFC,而在自定义阀门中,您将收到HTTP请求头,但是请求主体不会被发送,因为控件尚未在读取主体的servlet中.

所以你可以实现一个自定义阀门,类似于:

import org.apache.catalina.connector.Request;
import org.apache.catalina.connector.Response;
import org.apache.catalina.valves.ErrorReportValve;

public class CustomUploadHandlerValve extends ValveBase {

    @Override
    public void invoke(Request request,Response response) throws IOException,servletexception {
         HttpServletRequest httpRequest = (HttpServletRequest) request;
         String fileName = httpRequest.getHeader("Filename");  // get the filename or whatever other parameters required as per your code
         bool validationSuccess = Validate(); // perform filename check or anyother validation here
         if(!validationSuccess)
         {
             response = CreateResponse(); //create your custom 400 response here
             request.SetResponse(response);
             // return the response here
         }
         else
         {
             getNext().invoke(request,response); // to pass to the next valve/ servlet in the chain
         }
    }
    ...
}

免责声明:再次,我没有尝试这个成功,需要一些时间和一个tomcat安装程序来尝试;).以为这可能是你的起点.

相关文章

在要实现单例模式的类当中添加如下代码:实例化的时候:frmC...
1、如果制作圆角窗体,窗体先继承DOTNETBAR的:public parti...
根据网上资料,自己很粗略的实现了一个winform搜索提示,但是...
近期在做DSOFramer这个控件,打算自己弄一个自定义控件来封装...
今天玩了一把WMI,查询了一下电脑的硬件信息,感觉很多代码都...
最近在研究WinWordControl这个控件,因为上级要求在系统里,...