查看: 1433|回复: 0

asp无组件上传的原理

[复制链接]
发表于 2009-6-2 02:04:57 | 显示全部楼层 |阅读模式
一、无组件上传的原理 <br />我还是一点一点用一个实例来说明的吧,客户端HTML如下。要浏览上传附件,我们通过&lt;input type=&quot;file&quot;&gt;元素,但是一定要注意必须设置form的enctype属性为&quot;multipart/form-data&quot;: <br /><br />&lt;form method=&quot;post&quot; action=&quot;upload.asp&quot; enctype=&quot;multipart/form-data&quot;&gt; <br /> &lt;label&gt; <br />  &lt;input type=&quot;file&quot; name=&quot;file1&quot; /&gt; <br /> &lt;/label&gt; <br /> &lt;br /&gt; <br /> &lt;input type=&quot;text&quot; name=&quot;filename&quot; value=&quot;default filename&quot;/&gt; <br /> &lt;br /&gt; <br /> &lt;input type=&quot;submit&quot; value=&quot;Submit&quot;/&gt; <br /> &lt;input type=&quot;reset&quot; value=&quot;Reset&quot;/&gt; <br />&lt;/form&gt; <br /><br />在后台asp程序中,以前获取表单提交的ASCII 数据,非常的容易。但是如果需要获取上传的文件,就必须使用Request对象的BinaryRead方法来读取。BinaryRead方法是对当前输入流进行指定字节数的二进制读取,有点需要注意的是,一旦使用BinaryRead 方法后,再也不能使用Request.Form 或 Request.QueryString 集合了。结合Request对象的TotalBytes属性,可以将所有表单提交的数据全部变成二进制,不过这些数据都是经过编码的。首先让我们来看看这些数据是如何编码的,有无什么规律可循,编段代码,在代码中我们将BinaryRead读取的二进制转化为文本,输出出来,在后台的upload.asp中(注意该示例不要上传大文件,否则可能会造成浏览器死掉): <br />&lt;% <br />Dim biData, PostData <br />Size = Request.TotalBytes <br />biData = Request.BinaryRead(Size) <br />ostData = BinaryToString(biData,Size) <br />Response.Write &quot;&lt;pre&gt;&quot; &amp; PostData &amp; &quot;&lt;/pre&gt;&quot;  &#39;使用pre,原样输出格式 <br />&#39; 借助RecordSet将二进制流转化成文本 <br />Function BinaryToString(biData,Size) <br /> Const adLongVarChar = 201 <br /> Set RS = CreateObject(&quot;ADODB.Recordset&quot;) <br /> RS.Fields.Append &quot;mBinary&quot;, adLongVarChar, Size <br /> RS.Open <br /> RS.AddNew <br />  RS(&quot;mBinary&quot;).AppendChunk(biData) <br /> RS.Update <br /> BinaryToString = RS(&quot;mBinary&quot;).Value <br /> RS.Close <br />End Function <br />%&gt; <br /><br />简单起见,上传一个最简单的文本文件(G:\homepage.txt,内容为&quot;宝玉:http://www.webuc.net&quot;)来试验一下,文本框filename中保留默认值&quot;default filename&quot;,提交看看输出结果: <br />-----------------------------7d429871607fe <br />Content-Disposition: form-data; name=&quot;file1&quot;; filename=&quot;G:\homepage.txt&quot; <br />Content-Type: text/plain <br />宝玉:http://www.webuc.net <br />-----------------------------7d429871607fe <br />Content-Disposition: form-data; name=&quot;filename&quot; <br />default filename <br />-----------------------------7d429871607fe-- <br />可以看出来对于表单中的项目,是用过&quot;-----------------------------7d429871607fe&quot;这样的边界来分隔成一块一块的,每一块的开始都有一些描述信息,例如:Content-Disposition: form-data; name=&quot;filename&quot;,在描述信息中,通过name=&quot;filename&quot;可以知道表单项的name。如果有filename=&quot;G:\homepage.txt&quot;这样的内容,说明是一个上传的文件,如果是一个上传的文件,那么描述信息会多一行Content-Type: text/plain来描述文件的Content-Type。描述信息和主体信息之间是通过换行来分隔的。 <br />嗯,基本上清晰了,根据这个规律我们就知道该怎么来分离数据,再对分离的数据进行处理了,不过差点忽略一个问题,就是边界值(上例中的&quot;-----------------------------7d429871607fe&quot;)是怎么知道的?每次上传这个边界值是不一样的,还好还好asp中可以通过Request.ServerVariables( &quot;HTTP_CONTENT_TYPE&quot;)来获之,例如上例中HTTP_CONTENT_TYPE内容为:&quot;multipart/form-data; boundary=---------------------------7d429871607fe&quot;,有了这个,我们不仅可以判断客户端的form中有无使用enctype=&quot;multipart/form-data&quot;(如果没有使用,那么下面就没必要执行啦),还可以获取边界值boundary=---------------------------7d429871607fe。(注意:这里获取的边界值比上面的边界值开头要少&quot;--&quot;,最好补充上。) <br />至于如何分析数据的过程我就不多赘述了,无非就是借助InStr,Mid等这样的函数来分离出来我们想要的数据。 <br />二、分块上传,记录进度 <br />要实时反映进度条,实质就是要实时知道当前服务器获取了多少数据?再回想一下我们实现上传的过程,我们是通过Request.BinaryRead(Request.TotalBytes)来实现的,在Request的过程中我们无法得知当前服务器获取了多少数据。所以只能通过变通的方法了,如果我们可以将获取的数据分成一块一块的,然后根据已经上传的块数我们就可以算出来当前上传了多大了!也就是说,如果我1K为1块,那么上传1MB的输入流就分成1024块来获取,例如我当前已经获取了100块,那么就表明当前上传了100K。当我提出分块的时候很多人觉得不可思议,因为他们都忽略BinaryRead方法不仅是可以读取指定大小,而且可以连续读取的。 <br />写个例子来验证一下分块读取的完整性,在刚才的例子基础上(注意该示例不要上传大文件,否则可能会造成浏览器死掉): <br />&lt;% <br />Dim biData, PostData, TotalBytes, ChunkBytes <br />ChunkBytes = 1 * 1024     &#39; 分块大小为1K <br />TotalBytes = Request.TotalBytes  &#39; 总大小 <br />ostData = &quot;&quot;         &#39; 转化为文本类型后的数据 <br />ReadedBytes = 0        &#39; 初始化为0 <br />&#39; 分块读取 <br />Do While ReadedBytes &lt; TotalBytes <br /> biData = Request.BinaryRead(ChunkBytes)  &#39; 当前块 <br /> PostData = PostData &amp; BinaryToString(biData,ChunkBytes) &#39; 将当前块转化为文本并拼接 <br /> ReadedBytes = ReadedBytes + ChunkBytes &#39; 记录已读大小 <br /> If ReadedBytes &gt; TotalBytes Then ReadedBytes = TotalBytes <br />Loop <br />Response.Write &quot;&lt;pre&gt;&quot; &amp; PostData &amp; &quot;&lt;/pre&gt;&quot;  &#39; 使用pre,原样输出格式 <br />&#39; 将二进制流转化成文本 <br />Function BinaryToString(biData,Size) <br /> Const adLongVarChar = 201 <br /> Set RS = CreateObject(&quot;ADODB.Recordset&quot;) <br /> RS.Fields.Append &quot;mBinary&quot;, adLongVarChar, Size <br /> RS.Open <br /> RS.AddNew <br />  RS(&quot;mBinary&quot;).AppendChunk(biData) <br /> RS.Update <br /> BinaryToString = RS(&quot;mBinary&quot;).Value <br /> RS.Close <br />End Function <br />%&gt; <br />试验一下上传刚才的文本文件,输出结果证明这样分块读取的内容是完整的,并且在While循环中,我们可以在每次循环时将当前状态记录到Application中,然后我们就可以通过访问该Application动态获取上传进度条。 <br />另:上例中是通过字符串拼接的,如果是要拼接二进制数据,可以通过ADODB.Stream对象的Write方法,示例代码如下: <br />Set bSourceData = createobject(&quot;ADODB.Stream&quot;) <br />bSourceData.Open <br />bSourceData.Type = 1 &#39;Binary <br />Do While ReadedBytes &lt; TotalBytes <br /> biData = Request.BinaryRead(ChunkBytes) <br /> bSourceData.Write biData &#39; 直接使用write方法将当前文件流写入bSourceData中 <br /> ReadedBytes = ReadedBytes + ChunkBytes <br /> If ReadedBytes &gt; TotalBytes Then ReadedBytes = TotalBytes <br /> Application(&quot;ReadedBytes&quot;) = ReadedBytes <br />Loop <br /><br />三、保存上传的文件 <br />通过Request.BinaryRead获取提交数据,分离出上传文件后,根据数据类型的不同,保存方式也不同: <br />对于二进制数据,可以直接通过ADODB.Stream对象的SaveToFile方法,将二进制流保存成为文件。 <br />对于文本数据,可以通过TextStream对象的Write方法,将文本数据保存到文件中。 <br />对于文本数据和二进制数据,是可以方便的相互转换的,对于上传小文件来说,两者基本上没什么差别。但是两种方式保存时还是有一些差别的,对于ADODB.Stream对象,必须将所有数据全部装载完才可以保存成文件,所以使用这种方式如果上传大文件将很占用内存,而对于TextStream对象,可以在文件创建好后,一次Write一部分,分多次Write,这样的好处是不会占用服务器内存空间,结合上面分析的分块获取数据原理,我们可以每获取一块上传数据就将之Write到文件中。我曾做过试验,同样本机上传一个200多MB的文件,使用第一种方式内存一直在涨,到最后直接提示计算机虚拟内存不足,最可恨是即使进度条表示文件已经上传完,但是最终文件还是没有保存上。而使用后一种方法,上传过程中内存基本上无什么变化。 <br />四、未解决的难题 <br />我在博客园上看到Bestcomy描述他的Asp.Net上传组件是可以和Sever.SetTimeOut无关的,而在Asp中我是没能做到,对于上传大文件,就只有将Server.SetTimeOut设置为一个很大的值才可以。不知道有没有比较好的解决方法。 <br />如果我们在保存文件时,使用TextStream对象的Write方法,那么如果用户上传时中断了文件传输,已经上传的那部分文件还是在的,如果可以断点续传就好了。关键问题是Request.BinaryRead方法虽然可以分块读取,但是却不能跳过某一段读取! <br />五、结束语 <br />原理基本上是说清楚了,但是实际代码要比这复杂的多,要考虑很多问题,最麻烦在分析数据那部分,对于每一块获取的数据,要分析是不是属于描述信息,是表单项目还是上传的文件,文件是否已经上传结束…… <br />相信根据上面的描述,您也可以开发出您自己功能强大的无组件上传组件。我想更多的人关心的只是代码,而不会自己动手去写的,也许没有时间,也许水平还不够,更多的只是已经成为了一种习惯……我在CSDN上见过太多技术八股文——一段说明,然后全是代码。授人以鱼不若授人以渔,给你一个代码,也许你并不会去思考为什么,直接拿去用,当下次碰到类似的问题的时候,还是不知道为什么,希望此文能让更多人学到点什么,最重要是“悟”到点什么!<br /><br /><blockquote class="blockquote">From: http://www.husc.cn/read.php?tid=218&page=e  Powered by PHPWind.com</blockquote>
回复

使用道具 举报

本版积分规则

关注公众号

相关侵权、举报、投诉及建议等,请发 E-mail:admin@discuz.vip

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖
关注公众号
QQ客服返回顶部