如何在传输期间关闭请求流时获取HTTP响应

时间:2014-11-12 15:51:34

标签: c# .net http tomcat httpwebrequest

TL; DR版

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


完整版

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

问题是如果上传的文件很大,请求流在之前关闭我写完请求正文后,我得到IOException

  

消息:无法将数据写入传输连接:远程主机强行关闭现有连接。
   InnerException SocketException:远程主机强行关闭现有连接

我可以捕获此异常,但是当我致电GetResponse时,我得到一个WebException,前一个IOException作为其内部异常,并且为Response属性。 所以我永远无法获得响应,即使服务器发送它(使用WireShark检查)。

由于我无法得到回复,我不知道实际问题是什么。从我的应用程序的角度来看,看起来连接被中断,所以我将其视为与网络相关的错误并重试上传...当然,这会再次失败。

如何解决此问题并从服务器检索实际响应?它甚至可能吗?对我来说,当前行为看起来像HttpWebRequest中的错误,或者至少是一个严重的设计问题......


以下是我用来重现问题的代码:

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, 0, 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}", (int)response.StatusCode, response.StatusDescription);
        }
        else
        {
            Console.WriteLine("No response");
        }
    }
}

附加说明:

如果我正确理解100 Continue状态的作用,如果服务器拒绝该文件,则不应将其发送给我。但是,这个状态似乎是由Tomcat直接控制的,并且不能由应用程序控制。理想情况下,我希望服务器在这种情况下不要发送给我100 Continue,但根据负责后端的同事的说法,没有简单的方法可以做到这一点。所以我现在正在寻找一个客户端解决方案;但如果你碰巧知道如何解决服务器端的问题,也会受到赞赏。

我遇到问题的应用程序的目标是.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

6 个答案:

答案 0 :(得分:5)

我不知道什么是客户端解决问题的方法。但我仍然认为使用自定义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设置来尝试它;)。 认为这可能是你的起点。

答案 1 :(得分:2)

我遇到了同样的问题。当我尝试执行异步请求时,服务器在客户端传输请求体之前发送响应。经过一系列实验,我找到了一个解决方法。 收到请求流后,我使用反射来检查_CoreResponse的私有字段HttpWebRequest。如果它是类CoreResponseData的对象,我会使用他的私有字段(使用反射):m_StatusCodem_StatusDescriptionm_ResponseHeadersm_ContentLength。它们包含有关服务器响应的信息! 在大多数情况下,这个黑客都有效!

答案 2 :(得分:1)

你在第二个异常的状态代码和响应中得到了什么?而不是内部异常?

如果抛出WebException,请使用异常的Response和Status属性来确定服务器的响应。

http://msdn.microsoft.com/en-us/library/system.net.httpwebrequest.getresponse(v=vs.110).aspx

答案 3 :(得分:1)

您并未说明您使用的Tomcat 7的确切版本...

  

使用WireShark检查

您对WireShark实际看到了什么?

您是否看到了响应的状态行?

您是否看到完整的状态行,最后是CR-LF字符?

Tomcat是否要求提供身份验证凭据(401),或者由于某些其他原因拒绝文件上传(先用100确认它然后在飞行途中中止)?

  

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

如果您不希望关闭连接,但是所有数据都通过网络传输并在服务器端吞下,那么在Tomcat 7.0.55及更高版本上,可以在HTTP连接器上配置maxSwallowSize属性,例如: maxSwallowSize =&#34; -1&#34;

http://tomcat.apache.org/tomcat-7.0-doc/config/http.html

如果你想讨论连接处理的Tomcat方面,你最好问一下Tomcat用户&#39;邮件列表,

http://tomcat.apache.org/lists.html#tomcat-users

在.Net方面:

  1. 是否可以同时从不同的线程执行stream.Write()和request.GetResponse()?

  2. 在实际上传文件之前,是否可以在客户端执行某些检查?

答案 4 :(得分:0)

嗯...我不明白 - 这就是为什么在许多现实场景中大型文件以块的形式上传(而不是作为单个大文件)

顺便提一下:许多互联网服务器都有大小限制。例如在tomcat中由maxPostSize表示(如此链接中所示:http://tomcat.apache.org/tomcat-5.5-doc/config/http.html

所以调整服务器配置似乎很容易,但我认为正确的方法是将文件拆分为多个请求

编辑:用HttpServerUtility.UrlEncode替换Uri.EscapeDataString

 Uri.EscapeDataString(filename) // a problematic .net implementation
 HttpServerUtility.UrlEncode(filename) // the proper way to do it

答案 5 :(得分:0)

我目前在Tomcat和Java客户端上遇到了类似的问题。在读取整个请求主体之前,Tomcat REST服务发送带有响应主体的HTTP返回码。然而,客户端失败并出现IOException。我在客户端上插入了一个HTTP代理来嗅探协议,实际上HTTP响应最终被发送到客户端。最有可能的是Tomcat在发送响应之前关闭了请求输入流。

一种解决方案是使用不像Jetty这样的不同HTTP服务器。另一个解决方案是在Tomcat前面添加一个带有AJP的Apache HTTP服务器。 Apache HTTP服务器对流的处理方式不同,问题就此消失了。