大文件上传(WebException:连接被意外关闭)

Posted

技术标签:

【中文标题】大文件上传(WebException:连接被意外关闭)【英文标题】:Big files uploading (WebException: The connection was closed unexpectedly) 【发布时间】:2010-11-06 20:28:54 【问题描述】:

更新

请参阅下面的#3 帖子。

需要自动将文件上传到网络(无需浏览器)。主持人 - Mini File Host v1.2(如果这很重要)。在文档中没有找到具体的 api,所以一开始我在 Firebug 中嗅探浏览器请求如下:

Params : do
Value : verify
POST /upload.php?do=verify HTTP/1.1
Host: webfile.ukrwest.net
User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.2; ru; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.8 (.NET CLR 4.0.20506)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru,en-us;q=0.7,en;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Referer: http://filehoster.awardspace.com/index.php
Content-Type: multipart/form-data; boundary=---------------------------27368237179714
Content-Length: 445

-----------------------------27368237179714
Content-Disposition: form-data; name="upfile"; filename="Test.file"
Content-Type: application/octet-stream

12345678901011121314151617sample text
-----------------------------27368237179714
Content-Disposition: form-data; name="descr"


-----------------------------27368237179714
Content-Disposition: form-data; name="pprotect"


-----------------------------27368237179714--

在这里我们可以看到参数、标题、内容类型和信息块(1 - 文件名和类型,2 - 文件内容,3 - 附加参数 - 描述和密码,不一定适用)。 所以我创建了一个逐步模拟这种行为的类:在 url 上使用 HttpWebRequest,将所需的参数应用于请求,使用 StringBuilder 形成请求字符串并将它们转换为字节数组,使用 FileStream 读取文件,将所有这些东西放到MemoryStream,然后将其写入请求(从 CodeProject 的一篇文章中获取大部分代码,它将文件上传到 Rapidshare 主机)。 整洁,但是......它似乎不起作用:(。结果它返回初始上传页面,而不是带有我可以解析并呈现给用户的链接的结果页面...... 以下是 Uploader 类的主要方法:

// Step 1 - request creation 
 private HttpWebRequest GetWebrequest(string boundary)
 
            Uri uri = new Uri("http://filehoster.awardspace.com/index.php?do=verify");
            System.Net.HttpWebRequest httpWebRequest = (System.Net.HttpWebRequest)System.Net.WebRequest.Create(uri);
            httpWebRequest.CookieContainer = _cookies;
            httpWebRequest.ContentType = "multipart/form-data; boundary=" + boundary;
            httpWebRequest.UserAgent = "Mozilla/5.0 (Windows; U; Windows NT 5.2; ru; rv:1.9.0.8) Gecko/2009032609 Firefox/3.0.8 (.NET CLR 4.0.20506)";
            httpWebRequest.Referer = "http://filehoster.awardspace.com/index.php";
            httpWebRequest.Method = "POST";
            httpWebRequest.KeepAlive = true;
            httpWebRequest.Timeout = -1;
            //httpWebRequest.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            httpWebRequest.Headers.Add("Accept-Charset", "windows-1251,utf-8;q=0.7,*;q=0.7");
            httpWebRequest.Headers.Add("Accept-Encoding", "gzip,deflate");
            httpWebRequest.Headers.Add("Accept-Language", "ru,en-us;q=0.7,en;q=0.3");
            //httpWebRequest.AllowAutoRedirect = true;
            //httpWebRequest.ProtocolVersion = new Version(1,1);
            //httpWebRequest.SendChunked = true;
            //httpWebRequest.Headers.Add("Cache-Control", "no-cache");
            //httpWebRequest.ServicePoint.Expect100Continue = false;
            return httpWebRequest;

// Step 2 - first message part (before file contents)
private string GetRequestMessage(string boundary, string FName, string description, string password)
    
            System.Text.StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append("--");
            stringBuilder.Append(boundary);
            stringBuilder.Append("\r\n");
            stringBuilder.Append("Content-Disposition: form-data; name=\"");
            stringBuilder.Append("upfile");
            stringBuilder.Append("\"; filename=\"");
            stringBuilder.Append(FName);
            stringBuilder.Append("\"");
            stringBuilder.Append("\r\n");
            stringBuilder.Append("Content-Type: application/octet-stream");
            stringBuilder.Append("\r\n");
            return stringBuilder.ToString();

// Step 4 - additional request parameters. Step 3 - reading file is in method below
private string GetRequestMessageEnd(string boundary)
    
            System.Text.StringBuilder stringBuilder = new StringBuilder();
            stringBuilder.Append(boundary);
            stringBuilder.Append("\r\n");
            stringBuilder.Append("Content-Disposition: form-data; name=\"descr\"");
            stringBuilder.Append("\r\n");
            stringBuilder.Append("\r\n");
            stringBuilder.Append("Default description");
            stringBuilder.Append("\r\n");
            stringBuilder.Append(boundary);
            stringBuilder.Append("\r\n");
            stringBuilder.Append("Content-Disposition: form-data; name=\"pprotect\"");
            stringBuilder.Append("\r\n");
            stringBuilder.Append("\r\n");
            stringBuilder.Append("");
            stringBuilder.Append("\r\n");
            stringBuilder.Append(boundary);
            stringBuilder.Append("--");
            //stringBuilder.Append("\r\n");
            //stringBuilder.Append(boundary);
            //stringBuilder.Append("\r\n");
            return stringBuilder.ToString();

// Main method
public string ProcessUpload(string FilePath, string description, string password)

            // Chosen file information
            FileSystemInfo _file = new FileInfo(FilePath);
            // Random boundary generation
            DateTime dateTime2 = DateTime.Now;
            long l2 = dateTime2.Ticks;
            string _generatedBoundary = "----------" + l2.ToString("x");
            // Web request creation
            System.Net.HttpWebRequest httpWebRequest = GetWebrequest(_generatedBoundary);
            // Main app block - form and send request
            using (System.IO.FileStream fileStream = new FileStream(_file.FullName, FileMode.Open, FileAccess.Read, FileShare.Read))
            
                byte[] bArr1 = Encoding.ASCII.GetBytes("\r\n--" + _generatedBoundary + "\r\n");
                // Generating pre-content post message
                string firstPostMessagePart = GetRequestMessage(_generatedBoundary, _file.Name, description, password);
                // Writing first part of request
                byte[] bArr2 = Encoding.UTF8.GetBytes(firstPostMessagePart);
                Stream memStream = new MemoryStream();
                memStream.Write(bArr1, 0, bArr1.Length);
                memStream.Write(bArr2, 0, bArr2.Length);
                // Writing file
                byte[] buffer = new byte[1024];
                int bytesRead = 0;
                while ((bytesRead = fileStream.Read(buffer, 0, buffer.Length)) != 0)
                
                    memStream.Write(buffer, 0, bytesRead);
                
                // Generating end of a post message
                string secondPostMessagePart = GetRequestMessageEnd(_generatedBoundary);
                byte[] bArr3 = Encoding.UTF8.GetBytes(secondPostMessagePart);
                memStream.Write(bArr3, 0, bArr3.Length);
                // Preparing to send
                httpWebRequest.ContentLength = memStream.Length;
                fileStream.Close();

                Stream requestStream = httpWebRequest.GetRequestStream();

                memStream.Position = 0;
                byte[] tempBuffer = new byte[memStream.Length];
                memStream.Read(tempBuffer, 0, tempBuffer.Length);
                memStream.Close();
                // Sending request
                requestStream.Write(tempBuffer, 0, tempBuffer.Length);
                requestStream.Close();
            
            // Delay (?)
            System.Threading.Thread.Sleep(5000);
            // Getting response
            string strResponse = "";
            using (Stream stream = httpWebRequest.GetResponse().GetResponseStream())
            using (StreamReader streamReader = new StreamReader(stream/*, Encoding.GetEncoding(1251)*/))
            
                strResponse = streamReader.ReadToEnd();
            
            return strResponse;

使用 ProtocolVersion (1.0, 1.1)、AllowAutoRedirect (true/false),甚至已知的 ServicePoint.Expect100Continue (false) 都无法解决问题。即使在得到响应之前有 5 秒的超时(考虑到大文件的上传速度不会那么快)也无济于事。 内容类型“octet-stream”是根据上传任何文件的目的选择的(可以对最流行的 jpg/zip/rar/doc 等使用一些开关,但那个似乎是通用的)。边界是从计时器滴答声中随机生成的,没什么大不了的。还有什么? :/ 我可以放弃并忘记这一点,但我觉得我很接近解决问题,然后忘记它:P。 如果您需要整个应用程序来运行和调试 - 这里是(70kb,压缩 C# 2.0 VS2k8 解决方案,无广告,无病毒):

@Mediafire @FileQube @FileDropper

【问题讨论】:

“显示主机的主页”是什么意思?你能详细说明你得到的实际错误吗?究竟是什么不起作用? 带有“选择文件”框和上传按钮的页面:i.piccy.info/i3/74/b5/3089a82aa091a0d975b987f1bedd.png 不是带有文件链接的页面:i.piccy.info/i3/30/48/b66db6dee2e353949bd5d5269557.png 我认为在发送数据的步骤中有些东西不起作用,我们得到结果。即使使用 AllowAutoRedirect 参数 = true 并初始化 CookieContainer,也不会发生重定向。 【参考方案1】:

更新:不,没有重定向。

screenshot

读了RFC2388 几次,重写了代码,终于成功了(我猜问题出在 utf-read 尾随边界而不是正确的 7 位 ascii)。万岁?不:(。只传输小文件,大文件抛出“连接意外关闭”。

System.Net.WebException was unhandled by user code
  Message="The underlying connection was closed: The connection was closed unexpectedly."
  Source="Uploader"
  StackTrace:
   at Uploader.Upload.ProcessUpload(String FilePath, String description, String password) in F:\MyDocuments\Visual Studio 2008\Projects\Uploader\Uploader.cs:line 96
   at Uploader.Form1.backgroundWorker1_DoWork(Object sender, DoWorkEventArgs e) in F:\MyDocuments\Visual Studio 2008\Projects\Uploader\Form1.cs:line 45
   at System.ComponentModel.BackgroundWorker.WorkerThreadStart(Object argument) 

我知道这是 .net 堆栈的一个错误,并且几乎没有解决方案:

1) 增加请求的 Timeout 和 ReadWriteTimeout

2) 分配 request.KeepAlive = false 和 System.Net.ServicePointManager.Expect100Continue = false

3) 将 ProtocolVersion 设置为 1.0 但对我而言,其中任何一个或全部都没有帮助。有什么想法吗?

编辑 - 源代码:

// .. request created, required params applied
httpWebRequest.ProtocolVersion = HttpVersion.Version10; // fix 1
httpWebRequest.KeepAlive = false; // fix 2
httpWebRequest.Timeout = 1000000000; // fix 3
httpWebRequest.ReadWriteTimeout = 1000000000; // fix 4
// .. request processed, data written to request stream
string strResponse = "";            
try

    using (WebResponse httpResponse = httpWebRequest.GetResponse()) // error here
        
            using (Stream responseStream = httpResponse.GetResponseStream())
            
                using (StreamReader streamReader = new StreamReader(responseStream))
                    
                        strResponse = streamReader.ReadToEnd();
                    
                
            
        
catch (WebException exception)

    throw exception;

【讨论】:

在某些主机上,代码可以正常工作,而在某些主机上却不行……我想我可以保留上面的内容。我唯一有趣的一点——如何实现上传进度。 request.KeepAlive = false and System.Net.ServicePointManager.Expect100Continue = false 为我工作【参考方案2】:

“结果它返回初始上传页面,而不是带有我可以解析并呈现给用户的链接的结果页面......”

也许这只是上传功能的行为:上传完成后,您可以上传另一个文件吗? 我认为您必须为“浏览文件”页面调用另一个 url(我想这就是您需要的)。


编辑:实际上,如果服务器发送“重定向”(http 3xx),这是浏览器必须处理的事情,所以如果您使用自己的客户端应用程序而不是浏览器,您必须自己实现重定向。这里rfc了解更多信息。

【讨论】:

真相就在某处......一个服务的常见使用场景逐步如下: 1)加载服务首页somehost.com(实际上是somehost.com/index.php) ; 2)通过浏览和文件打开对话框选择要上传的文件; 3)选中“同意服务条款”复选框以启用上传按钮(在非浏览器情况下它不会影响我猜的任何内容?) 4)点击“上传!” 5) 等待文件在同一位置上传(只是一些 js 进度条显示在表单上,​​/ignore) 6) 查看和复制文件链接和“删除文件”操作。地址是 somehost.com/upload.php?do=verify. 重点是获取带有链接的页面内容,如果上传后找不到,单个或多个文件下载是没有用的。调用“正确”页面有逻辑,但“upload.php?do=verify”页面仅在请求完成后生成,手动导航到它会给我们一个错误“未选择文件”。【参考方案3】:

尝试在 Web.config 中设置 httpRuntime 元素的 maxRequestLength 属性。

【讨论】:

【参考方案4】:

在我的情况下,重复的文件名也会导致问题。我将文件的设置保存在 xml 文件中,但名称设置相互重复。

      <field name="StillImage">
        <prefix>isp_main_</prefix>
        <suffix>308</suffix>
        <width>1080</width>
        <height>1080</height>
      </field>
      <field name="ThumbnailImage">
        <prefix>isp_thumb_</prefix> // pay attention to this
        <suffix>308</suffix>
        <width>506</width>
        <height>506</height>
      </field>
      <field name="Logo">
        <prefix>isp_thumb_</prefix> // and this
        <suffix>306</suffix>
        <width>506</width>
        <height>506</height>
      </field>

而且,在另一种情况下,问题在于文件长度。请检查您的服务器上允许的文件大小。在你的脚本中检查这部分:

dataStream.Write(filesBytesArray, 0, filesBytesArray.Length);
dataStream.Close();

如果您不知道,只需在前端部分限制上传的文件大小,即。 HTML &lt;input type="file"&gt; 上传元素,这是good reference for limiting file size and other filter。

【讨论】:

以上是关于大文件上传(WebException:连接被意外关闭)的主要内容,如果未能解决你的问题,请参考以下文章

Try/Catch 没有捕捉到 WebException

System.Net.WebException:'错误:ConnectFailure(连接被拒绝)'

使用 HttpWebRequest 通过 PUT 上传文件时出错

WebException:底层连接已关闭

System.IO.IOException:由于意外 > 数据包格式,握手失败?

(413) 使用 WCF 请求实体太大错误