HttpWorkerRequest实现大文件上传asp.net

HttpWorkerRequest可以实现大文件上传
以前也做过文件上传,但都是些小文件,不超过2M。这次要求上传100M以上的东西。没办 法找来资料研究了一下。基于WEB的文件上传可以使用FTP和HTTP两种协议,用FTP的话虽然传输稳定,但安全性是个严重的问题,而且FTP服务器读 用户库获取权限,这样对于用户使用来说还是不太方便。剩下只有HTTP。在HTTP中有3种方式,PUT、 WEBDAV、RFC1867,前2种方法不适合大文件上传,目前我们使用的web上传都是基于 RFC1867标准的HTML中基于表单的文件上传。
一、先简要介绍一下RFC1867(Form-based File Upload in HTML)标准:
1.带有文件提交功能的HTML表单
现 有的HTML规范为INPUT元素的TYPE属性定义了八种可能的值,分别是:CHECKBOX, HIDDEN, IMAGE, PASSWORD, RADIO, RESET, SUBMIT, TEXT. 另外,当表单采用POST方式的时候,表单默认的具有"application/x-www-form-urlencoded" 的ENCTYPE属性。
RFC1867标准对HTML做出了两处修改:.....
.....
ASP.NET大文件上传解决方案
解 决的方法是利用隐含的HttpWorkerRequest,用它的 GetPreloadedEntityBody 和 ReadEntityBody方法从IIS为ASP.NET建立的pipe里分块读取数据。Chris Hynes为我们提供了这样的一个方案(用HttpModule),该方案除了允许你上传大文件外,还能实时显示上传进度。
Lion.Web.UpLoadModule和AspnetUpload 两个.NET组件都是利用的这个方案。
方案原理:
利用HttpHandler实现了类似于ISAPI Extention的功能,处理请求(Request)的信息和发送响应(Response)。
方案要点:
1. httpHandler or HttpModule
a.在asp.net进程处理request请求之前截获request对象
b.分块读取和写入数据
c.实时跟踪上传进度更新meta信息
2. 利用隐含的HttpWorkerRequest用它的GetPreloadedEntityBody 和 ReadEntityBody方法处理文件流
IServiceProvider provider = (IServiceProvider) HttpContext.Current;
HttpWorkerRequest wr = (HttpWorkerRequest) provider.GetService(typeof(HttpWorkerRequest));
byte[] bs = wr.GetPreloadedEntityBody();
....
if (!wr.IsEntireEntityBodyIsPreloaded())
{
int n = 1024;
byte[] bs2 = new byte;
while (wr.ReadEntityBody(bs2,n) >0)
{
.....
}
}
3. 自定义Multipart MIME 解析器
自动截获MIME分割符
将文件分块写如临时文件
实时更新Appliaction 状态(ReceivingData, Error, Complete)
/例子
HttpApplication application1 = sender as HttpApplication;
HttpWorkerRequest request1 = (HttpWorkerRequest) ((IServiceProvider) HttpContext.Current).GetService(typeof(HttpWorkerRequest));
try
{
if (application1.Context.Request.ContentType.IndexOf("multipart/form-data") <= -1)
{
return;
}
//Check The HasEntityBody
if (!request1.HasEntityBody())
{
return;
}
int num1 = 0;
TimeSpan span1 = DateTime.Now.Subtract(this.beginTime);
string text1 = application1.Context.Request.ContentType.ToLower();
byte[] buffer1 = Encoding.ASCII.GetBytes(("\r\n--" + text1.Substring(text1.IndexOf("boundary=") + 9)).ToCharArray());
int num2 = Convert.ToInt32(request1.GetKnownRequestHeader(11));
Progress progress1 = new Progress();
application1.Context.Items.Add("FileList", new Hashtable());
byte[] buffer2 = request1.GetPreloadedEntityBody();
num1 += buffer2.Length;
string text2 = this.AnalysePreloadedEntityBody(buffer2, "UploadGUID");
if (text2 != string.Empty)
{
application1.Context.Items.Add("LionSky_UpLoadModule_UploadGUID", text2);
}
bool flag1 = true;
if ((num2 > this.UpLoadFileLength()) && ((0 > span1.TotalHours) || (span1.TotalHours > 3)))
{
flag1 = false;
}
if ((0 > span1.TotalHours) || (span1.TotalHours > 3))
{
flag1 = false;
}
string text3 = this.AnalysePreloadedEntityBody(buffer2, "UploadFolder");
ArrayList list1 = new ArrayList();
RequestStream stream1 = new RequestStream(buffer2, buffer1, null, RequestStream.FileStatus.Close, RequestStream.ReadStatus.NoRead, text3, flag1, application1.Context, string.Empty);
list1.AddRange(stream1.ReadBody);
if (text2 != string.Empty)
{
progress1.FileLength = num2;
progress1.ReceivedLength = num1;
progress1.FileName = stream1.OriginalFileName;
progress1.FileCount = ((Hashtable) application1.Context.Items["FileList"]).Count;
application1.Application["_UploadGUID_" + text2] = progress1;
}

if (!request1.IsEntireEntityBodyIsPreloaded())
{
byte[] buffer4;
ArrayList list2;
int num3 = 204800;
byte[] buffer3 = new byte[num3];
while ((num2 - num1) >= num3)
{
if (!application1.Context.Response.IsClientConnected)
{
this.ClearApplication(application1);
}
num3 = request1.ReadEntityBody(buffer3, buffer3.Length);
num1 += num3;
list2 = stream1.ContentBody;
if (list2.Count > 0)
{
buffer4 = new byte[list2.Count + buffer3.Length];
list2.CopyTo(buffer4, 0);
buffer3.CopyTo(buffer4, list2.Count);
stream1 = new RequestStream(buffer4, buffer1, stream1.FileStream, stream1.FStatus, stream1.RStatus, text3, flag1, application1.Context, stream1.OriginalFileName);
}
else
{
stream1 = new RequestStream(buffer3, buffer1, stream1.FileStream, stream1.FStatus, stream1.RStatus, text3, flag1, application1.Context, stream1.OriginalFileName);
}
list1.AddRange(stream1.ReadBody);
if (text2 != string.Empty)
{
progress1.ReceivedLength = num1;
progress1.FileName = stream1.OriginalFileName;
progress1.FileCount = ((Hashtable) application1.Context.Items["FileList"]).Count;
application1.Application["_UploadGUID_" + text2] = progress1;
}
}
buffer3 = new byte[num2 - num1];
if (!application1.Context.Response.IsClientConnected && (stream1.FStatus == RequestStream.FileStatus.Open))
{
this.ClearApplication(application1);
}
num3 = request1.ReadEntityBody(buffer3, buffer3.Length);
list2 = stream1.ContentBody;
if (list2.Count > 0)
{
buffer4 = new byte[list2.Count + buffer3.Length];
list2.CopyTo(buffer4, 0);
buffer3.CopyTo(buffer4, list2.Count);
stream1 = new RequestStream(buffer4, buffer1, stream1.FileStream, stream1.FStatus, stream1.RStatus, text3, flag1, application1.Context, stream1.OriginalFileName);
}
else
{
stream1 = new RequestStream(buffer3, buffer1, stream1.FileStream, stream1.FStatus, stream1.RStatus, text3, flag1, application1.Context, stream1.OriginalFileName);
}
list1.AddRange(stream1.ReadBody);
if (text2 != string.Empty)
{
progress1.ReceivedLength = num1 + buffer3.Length;
progress1.FileName = stream1.OriginalFileName;
progress1.FileCount = ((Hashtable) application1.Context.Items["FileList"]).Count;
if (flag1)
{
progress1.UploadStatus = Progress.UploadStatusEnum.Uploaded;
}
else
{
application1.Application.Remove("_UploadGUID_" + text2);
}
}
}
byte[] buffer5 = new byte[list1.Count];
list1.CopyTo(buffer5);
this.PopulateRequestData(request1, buffer5);
}
catch (Exception exception1)
{
this.ClearApplication(application1);
throw exception1;

}

传统方式上传缺陷客户端差异    客户端只提交数据及文件流, 看似应该没有差别;可是IE和firefox有一个最大的不同点,就是IE上传文件不会增加客户端内存占用, 而firefox则要将文件内容不断地读入内存中再发送,比如你使用firefox上传100MB的文件客户端内存就会增加100MB的消耗, 很诧异firefox怎会有如此缺陷!
IIS服务器处理文件上传  很显然IIS处理文件上传时还是很谨慎的,在上传过程中不会因为提交数据是大文件而引起服务器的内存消耗;所以,猜测IIS是在建立的Pipe读取数据后 直接存到临时文件中。但是,很致命的一个问题那就是我们程序员要对上传的文件进行处理,及需要asp.net把真个文件流交给您来处理,最终结果就是 ASP.Net进程还是会把整个文件内容加载内存交给你处理,例如Bitaec框架中就是此种情况。
  另外值得一提的就是ASP.NET中的FileUpload控件,正如我上面分析的情况FileUpload上传文件时IIS不断的从Pipe中读取数据 并存入临时文件中,从而不会造成服务器内容增加;当上传完毕时,你直接调用FileUpload里的SaveAs方法将文件直接存入你需要的位置,实际上 所做的操作是将收到的临时文件移动你需要的位置,所以是不会增加消耗服务器内存资源。但是,如果你需要对上传的文件流操作的话则会增加内存消耗,因为它需 要把临时文件重新加载到内存中。
大文件上传解决方案利用RFC1867标准处理文件上传的两种方式:
1.一次性得到上传的数据,然后分析处理。
看了N多代码之后发现,目前无组件程序和一些COM组件都是使用Request.BinaryRead方法。一次性得到上传的数据,然后分析处理。这就是为什么上传大文件很慢的原因了,IIS超时不说,就算几百M文件上去了,分析处理也得一阵子。
2.一边接收文件,一边写硬盘。

了 解了一下国外的商业组件,比较流行的有Power-Web,AspUpload,ActiveFile,ABCUpload, aspSmartUpload,SA-FileUp。其中比较优秀的是ASPUPLOAD和SA-FILE,他们号称可以处理2G的文件(SA-FILE EE版甚至没有文件大小的限制),而且效率也是非常棒,难道编程语言的效率差这么多?查了一些资料,觉得他们都是直接操作文件流。这样就不受文件大小的制 约。但老外的东西也不是绝对完美,ASPUPLOAD处理大文件后,内存占用情况惊人。1G左右都是稀松平常。至于SA-FILE虽然是好东西但是破解难 寻。然后发现2款.NET上传组件,Lion.Web.UpLoadModule和AspnetUpload也是操作文件流。但是上传速度和CPU占用率 都不如老外的商业组件。
做了个测试,LAN内传1G的文件。ASPUPLOAD上传速度平均是4.4M/s,CPU占用10-15,内存占用 700M。SA-FILE也差不多这样。而AspnetUpload最快也只有1.5M/s,平均是700K/s,CPU占用15-39,测试环境: PIII800,256M内存,100M LAN。我想AspnetUpload速度慢是可能因为一边接收文件,一边写硬盘。资源占用低的代价就是降低传输速度。但也不得不佩服老外的程序,CPU 占用如此之低.....

三、ASP.NET上传文件遇到的问题
我们在用ASP.NET上传大文件时都遇到过这样或那样的问题。 设置很大的maxRequestLength值并不能完全解决问题,因为ASP.NET会block直到把整个文件载入内存后,再加以处理。实际上,如果 文件很大的话,我们经常会见到Internet Explorer显示 "The page cannot be displayed - Cannot find server or DNS Error",好像是怎么也catch不了这个错误。为什么?因为这是个client side错误,server side端的Application_Error是处理不到的。
四、ASP.NET大文件上传解决方案
解决 的方法是利用隐含的HttpWorkerRequest,用它的GetPreloadedEntityBody 和 ReadEntityBody方法从IIS为ASP.NET建立的pipe里分块读取数据。Chris Hynes为我们提供了这样的一个方案(用HttpModule),该方案除了允许你上传大文件外,还能实时显示上传进度。
Lion.Web.UpLoadModule和AspnetUpload 两个.NET组件都是利用的这个方案。

解决的方法是利用隐含的HttpWorkerRequest,用它的GetPreloadedEntityBody 和 ReadEntityBody方法从IIS为ASP.NET建立的pipe里分块读取数据
  IServiceProvider provider = (IServiceProvider) HttpContext.Current;
  HttpWorkerRequest wr = (HttpWorkerRequest) provider.GetService(typeof(HttpWorkerRequest));
  byte[] bs = wr.GetPreloadedEntityBody();
  ....
  if (!wr.IsEntireEntityBodyIsPreloaded())
  {
        int n = 1024;
        byte[] bs2 = new byte[n];
        while (wr.ReadEntityBody(bs2,n) >0)
      {
            .....
        }
  }
  上传大文件,有好几种方法:
  1、HttpWorkerRequest方法
  2、利用第三方的控件 AspNetUpload 要钱!!算了,咱还是喜欢免费的。
  3、修改web.config文件,但是不能捕获错误。
  4、通过ftp的方式上传。服务器需要提供ftp服务。
 

第三种方: 
  修改Webcong文件:
  <system.web>
  <httpRuntime maxRequestLength="40690"
  useFullyQualifiedRedirectUrl="true"
  executi
  useFullyQualifiedRedirectUrl="false"
  minFreeThreads="8"
  minLocalRequestFreeThreads="4"
  appRequestQueueLimit="100"
  enableVersi
  />
  </system.web>
 
  其中与上传有密切关系的是:
  maxRequestLength
  指示 ASP.NET 支持的最大文件上载大小。
  该限制可用于防止因用户将大量文件传递到该服务器而导致的拒绝服务攻击。
  指定的大小以 KB 为单位。
  默认值为 4096 KB (4 MB)。
 
  executionTimeout
  指示在被 ASP.NET 自动关闭前,允许执行请求的最大秒数。
  单位为秒,在上传大的文件时把这个设的大一些。
 
 
  如果服务器内存512M,已可上传大小160M的文件。(没试过,csdn上众帖子的一致意见。)
  'www.knowsky.com
  到这里web.config的设置就已经结束。
  可是一旦上传文件的大小超过了这个设置的文件大小范围就会发生如下错误:
  该页无法显示
  您要查看的页当前不可用。网站可能遇到技术问题,或者您需要调整浏览器设置。
 
  虽然解决不了,那也要捕获这个错误啊!怎么办呢?
  最近吃了几条鱼,想了想,由于这个错误是由file控件引发的前台错误,所以在后台想利用try...catch来捕获是行不通的。
  于是想到了利用.NET的错误捕获页面的机制来处理。可行哦。
 
  1、先设置web.config
  <customErrors mode="On"/>
  2、新建一个error.aspx 文件,专门用来捕获错误的。
  3、在上传文件的aspx页面的前台页面里添加page指令。ErrorPage="UploadError.aspx"
  4、在error.aspx中添加一些代码来判断错误信息是否是file引起的前台错误。
  public class UploadError : System.Web.UI.Page
  {
  private void Page_Load(object sender, System.EventArgs e)
  {
  Exception ex = Server.GetLastError();
  if (ex != null)
  {
  Response.Redirect("../error.aspx");
  }
  else //前台错误ex为空值
  {
  Response.Redirect("uploadexcel.aspx?err=1"); //重新跳转到上传页面,加上err参数是为了显示错误信息
  }
  }
 
  5、显示错误提示。
  public class uploadexcel : System.Web.UI.Page
  {
  private void Page_Load(object sender, System.EventArgs e)
  {
  if (Request["err"] == "1")
  {
  Page.RegisterStartupScript("budget","<script language = javascript>alert('Upload file has failed ! File size is too large !')</script>");
  }
  }
  }

原文地址:https://www.cnblogs.com/Byrd/p/2040959.html