当写入请求流时发生传输错误时,即使服务器发送,也无法访问响应.
完整版
我有一个.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
解决方法
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安装程序来尝试;).以为这可能是你的起点.