随笔 - 62  文章 - 73  评论 - 591 

      实在很抱歉隔了这么久才继续补上这篇,因为后期调试时发现上传很不稳定,所以调试了几天,目前测试基本没什么问题。

      回顾上一篇,我们可以了解到以下内容:

      HTTP请求流到达服务器后,由IIS进程或http.sys接收并调用ASP.NET ISAPI 扩展,接着生成HttpWorkerRequest并将HttpWorkerRequest传递给ProcessRequestInternal方法,这之后才创建了HttpContext请求上下文和HttpApplication 类的实例,然后又经过一系列处理并最终将消息返回(也就是送回客户端浏览器)。

      本篇概述:
      在本篇中,主要是从HTTP请求流中将数据部分进行截取,同时将数据相关信息进行保存。通过本篇你可以实现多个大文件的上传功能(实验平台 XP SP2,IIS 5.1, VS 2005)。
      注:因为当初只是为了实现这个功能,所以并没有太多的考虑过性能和断点续传的功能。不过针对这些内容,大概已经有了个构思,可能会在写完这个专题后找时间在目前所实现的功能之上再考虑进这些元素,然后对这个专题进行相应的补充。

      正文部分:

      本篇中我们要做的就是在尽可能早的事件中对这个请求进行修改,截取其中上传的数据部分,然后重新将请求进行封装。这样就不会因为上传文件太大等原因引发异常。Asp.NET提供了HttpModule(HTTP模块),在模块的 Init 方法内,可以订阅各种应用程序事件(如 BeginRequestEndRequest),而HttpApplication.BeginRequest 事件始终是请求处理期间发生的第一个事件。为了让大家更多的理解模块,我引用了一段MSDN上关于HTTP模块的说明:

      HTTP 模块是一个在每次针对应用程序发出请求时调用的程序集。HTTP 模块作为 ASP.NET 请求管线的一部分调用,它们能够在整个请求过程中访问寿命周期事件。因此,HTTP 模块使您有机会检查传入的请求并根据该请求采取操作。它们还使您有机会检查出站响应并修改它。

      在应用程序的 Web.config 文件中注册自定义的 HTTP 模块。当 ASP.NET 创建表示您的应用程序的 HttpApplication 类的实例时,将创建已注册的任何模块的实例。在创建模块时,将调用它的 Init 方法,并且模块会自行初始化。有关更多信息,请参见 ASP.NET 应用程序生命周期概述。

      在模块的 Init 方法内,可以订阅各种应用程序事件(如 BeginRequest 或 EndRequest),这可以通过将事件绑定到模块中已创建的方法来完成。当这些事件被引发时,会调用模块中适当的方法,并且模块可以执行所需的任何逻辑,如身份验证检查或记录请求信息。在事件处理过程中,模块能够访问当前请求的 Context 属性。这使您可以将请求重定向到其他页、修改请求或者执行任何其他请求操作。例如,如果您的模块中包括身份验证检查,则模块可能会检查凭据,如果凭据不正确的话,会重定向到登录页或错误页。否则,当模块的事件处理程序完成运行时,ASP.NET 会调用管线中的下一个进程,这可能是另一个模块,也可能是用于该请求的 HTTP 处理程序(如 .aspx 文件)。

      好了,相信大家对模块有了一个基本的认识。我们的目的就是要在模块的Init方法内,订阅BeginRequest事件,来拦截HTTP请求,并对请求进行修改。那么接下来我们就要正式开始了,我会对步骤进行编号,以便大家理解。在开始之前大家可以先参考下MSDN的相关文章:创建自定义 HTTP 模块
      
      1、在Web.config文件中设置maxRequestLength,并在<system.web>节中注册一个模块,格式如下:
      
httpModules

      根据上面的格式,我写的如下:

MyHttpModule

      由于后期测试8M左右基本够用,因些设置maxRequestLength="8192"。

      2、创建一个与type的值相同的类MyHttpModule,注意这个类必须实现 IHttpModule 接口,这样才能实现HttpModule的基本功能。
     
当我们实现IHttpModule接口后,在"I"的下方会出现短横,我们只需要将鼠标移上去便会出现可用的选项,你只需要傻瓜式的点击,系统便自动替我们往类中添加了模块的初始化事件和处置事件,当然有的朋友喜欢完全自己写也可以。代码如下:
     
IHttpModule 成员

      3、去掉原Init中的事件,添加我们自己的事件
      
总算到了关键的步骤了,我们就是要在这里实现我们截取HTTP请求,获取文件信息,获取上传进度等事件。   
 
      3.1 判断是否是上传文件
      因为需要上传文件的FORM表单都必须设置FORM表彰中的enctype属性为multipart/form-data(如果我们使用FileUpload组件,则会在编译时自动替我们加上这个属性)。所以我们只需要判断HTTP请求头的ContentType值是否为multipart/form-data即可,如果不是,我们就没必要对该请求进行处理了。
      enctype="multipart/form-data实际上是一种编码规范(默认的话是application/x-www-form-urlencoded,该方式不适合大块二进制数据传输),该编码方式的基本思想就是用分隔符来分隔数据项,分隔符如“-----------------------------7d87d1cc0a88”。

      3.2 获取HTTP请求总长度和HttpWorkerRequest对象
      通过获取HttpApplication.Request.ContentLength属性便可获取HTTP请求内容的总长度,以便我们后来的使用。
      HttpWorkerRequest对象在第二篇中有所提到,如果朋友们有看过,那应该可以认识到它其实内含了HTTP请求的全部内容,除此之外,还包括一些对请求进行处理的方法。
      接下来我们就要使用这个对象对请求进行处理。
      以下代码用来获取该对象(摘自网上):

GetWorkerRequest(HttpContext context)

      3.3 判断是否已经预先加载了HTTP请求的部分数据
      因为包含上传文件的HTTP请求含有大量的数据,所以客户端并非一次性将整个请求全部发送到网上,而是分多次发送,大家可以通过协议分析软件进行观察。因此服务器的侦听端可能会预先侦听到已经发送到服务器端的部分数据。我们可以使用HttpWorkerRequest.GetPreloadedEntityBody()方法获取HTTP请求上下文已经被读取的部分,[这里有一个非常头痛的问题,我几乎就快因为这个问题放弃继续实现这个功能了。问题就是在.net 调试情况下,GetPreloadedEntityBody()时常取不到值,后在网上发现很多类似问题,有朋友说明是因为在IIS中使用ISAPIWorkerRequest , 而.net自带的web服务器则使用SimpleWorkerRequest。因此换成IIS调试,几乎100%可以收到数据。但是这样断点什么的便无法使用,郁闷。现在根据第二篇提到的信息分析下,可能原因是ISAPIRuntime的ProcessRequest方法未能每次都将SimpleWorkerRequest转换成合适的HttpWorkRequest,于是造成数据获取失败]
      如果没有预先加载,可以使用ReadEntityBody方法直接从异名管道中提取HTTP请求数据,[注:如果在.net 调试情况下无法获取预先加载的数据,那99%使用这个办法同样无法获取] ,由于我在使用IIS进行调试N次也没有发生过读取不到预先加载的数据的现象,所以基本不需要另外使用此方法来获取第一次数据。
      如果以上方法还是无法读取到HTTP请求的第一次数据,那么基本确定此次上传请求失败。

      实在是困死了,剩下的明天补上。

      下一篇
posted on 2008-08-03 00:58 stg609 阅读(6051) 评论(13) 编辑 收藏

 回复 引用 查看   
#1楼 2008-08-03 04:32 | 李彬      
我认为,. Net 提供的上传组件不具备 上传大容量数据的功能,
.Net 默认的 FileUpload 虽然可以在 webconfig 配置大小,
但是没有用
.Net 上传组件的大小的极限是 8~12M 超过这个范围,IIS服务器就会当掉,
本机可以稍大点,博客园, php ,asp 都是这个情况,上传不了大的文件。
我在开源社区见过几个无限制大容量上传的,它们可以上传 200+ M的文件,
而且很好的一点,就是 如果中途失败,或者进程失败或者异常,都不会牵连IIS和服务器无关, 它们好像用的是 FTP 那种的协议 或者是 另一中协议,而不是http 协议。

 回复 引用 查看   
#2楼 2008-08-03 04:53 | 李彬      
我个人愚鲁的认为, 如果想在上传组件做更大的空间,就要多看国外的优秀源代码 比如 NeatUpload 或着比它高级的很多, 我暂时是用 NeatUpload 虽然不是最好的, 我也想向你一样开发自己的大容量上传组件, 可是当我找到国外优秀的开源组件时候,我放弃了,因为它们的代码,成千, 类库十分庞大,或取人家用几年的时间才做出,那不是个人一朝一夕能做出来的。

就算我勤奋努力几年,也做不出他那样的稳定和全面为什么,因为我只是一个使用者,而人家是团队吃饭的。

如果楼主愿意上传组件有更大的发展空间,就不要在 http 钻死胡同,本身就限制了,而且对 IIS影响非常大,何不另需协议空间,好处就是,无论你这怎样上传,失败,异常,超载,都和IIS无关, 那又是一片广阔的天空。

 回复 引用   
#3楼 2008-08-03 12:13 | asddd[未注册用户]
 回复 引用 查看   
#4楼 2008-08-03 16:13 | 王景      
不错.持续关注.
你的http的基础挺扎实的.

 回复 引用 查看   
#5楼 2008-08-07 13:53 | 游侠_1      
因此换成IIS调试,几乎100%可以收到数据。但是这样断点什么的便无法使用,郁闷


--------设置断点--->打开一个页面如localhost/default.aspx------vs2005 -->工具-->附加到进程--->选择IIS的进程

 回复 引用 查看   
#6楼[楼主] 2008-08-08 23:01 | stg609      
@游侠_1
谢谢,可是为什么我这样不行呢,我设置了断点,然后选择附加到inetinfo.exe后,变成运行状态,但是并没有跳出网页。我在浏览器中输入网址访问我的aspx网页,可是这样并没有响应断点呀。

 回复 引用 查看   
#7楼 2008-09-03 09:42 | 天启      
为什么不用FtpWebResponse?
我也想写这么一个东西,思路是FtpWebResponse上传,同时也可以把相关信息添加到数据库,通过http下载,容易控制权限。可行否?

 回复 引用 查看   
#8楼[楼主] 2008-09-04 12:44 | stg609      
这个没接触过,利用ftp上传的话,能获取上传进度吗?如果你做出来了的话,欢迎指教
 回复 引用   
#9楼 2008-10-15 10:12 | 你开心我开心[未注册用户]
请问 GetPreloadedEntityBody()和ReadEntityBody()有什么区别。。
 回复 引用 查看   
#10楼[楼主] 2008-10-17 09:18 | stg609      
@你开心我开心
一个HTTP请求数据包很大的话,那么这个数据包会根据一次HTTP请求的最大长度被划分成N块发送到服务器。
GetPreloadedEntityBody()获取初次到达服务器的HTTP请求中已经被读取的部分。一般用这个来判断服务器是否获取到请求,如果这个获取不到,那基本可以认定此次请求失败。
ReadEntityBody(),是将HTTP请求读取到字节数组中,并且返回读取到的字节数。可以用于多次的请求。

 回复 引用 查看   
#11楼 2009-11-24 00:16 | 水无声      
请问在.net 调试情况下,GetPreloadedEntityBody()取不到值,该如何解决??不用IIS调试
 回复 引用 查看   
#12楼 2011-08-07 12:01 | 夷延卫      
楼主,能不能把源码发到我的邮箱里啊(yiyanwei@live.com)
 回复 引用 查看   
#13楼 2011-11-14 11:15 | liuhh      
呵呵,不错正好遇到这个问题