asp上传文件代码详解1

一、无组件上传的原理
我还是一点一点用一个实例来说明的吧,客户端HTML如下。要浏览上传附件, 

我们通过<input type="file">元素,但是一定要注意必须设置form的enctype

属性为"multipart/form-data": 


<form method="post" action="upload.asp" 

enctype="multipart/form-data">
<label>
<input type="file" name="file1" />
</label>
<br />
<input type="text" name="filename" value="default filename"/>
<br />
<input type="submit" value="Submit"/>
<input type="reset" value="Reset"/>
</form> 


在后台asp程序中,以前获取表单提交的ASCII 数据,非常的容易。但是如果

需要获取上传的文件,就必须使用Request对象的BinaryRead方法来读取。Bin

aryRead方法是对当前输入流进行指定字节数的二进制读取,有点需要注意的

是,一旦使用BinaryRead 方法后,再也不能使用Request.Form 或 

Request.QueryString 集合了。结合Request对象的TotalBytes属性,可以将

所有表单提交的数据全部变成二进制,不过这些数据都是经过编码的。首先让

我们来看看这些数据是如何编码的,有无什么规律可循,编段代码,在代码中

我们将BinaryRead读取的二进制转化为文本,输出出来,在后台的upload.asp

中(注意该示例不要上传大文件,否则可能会造成浏览器死掉): 
<%
Dim biData, PostData
Size = Request.TotalBytes
biData = Request.BinaryRead(Size)
PostData = BinaryToString(biData,Size)
Response.Write "<pre>" & PostData & "</pre>" '使用pre,原样输出格式
' 借助RecordSet将二进制流转化成文本
Function BinaryToString(biData,Size) 
Const adLongVarChar = 201
Set RS = createObject("ADODB.Recordset")
RS.Fields.Append "mBinary", adLongVarChar, Size
RS.Open
RS.AddNew
RS("mBinary").AppendChunk(biData)
RS.update
BinaryToString = RS("mBinary").Value
RS.Close
End Function 
%> 

 

简单起见,上传一个最简单的文本文件(G:/homepage.txt,内容为"uploadcontent:htt

p:// www.XXXXX.net")来试验一下,文本框filename中保留默认值"default 

filename",提交看看输出结果: 

-----------------------------7d429871607fe
Content-Disposition: form-data; name="file1"; 

filename="G:/homepage.txt"
Content-Type: text/plain
uploadcontent: http://www.XXXXX.net
-----------------------------7d429871607fe
Content-Disposition: form-data; name="filename"
default filename
-----------------------------7d429871607fe-- 
可以看出来对于表单中的项目,是用过"-----------------------------7d42

9871607fe"这样的边界来分隔成一块一块的,每一块的开始都有一些描述信息

,例如:Content-Disposition: form-data; name="filename",在描述信息

中,通过name="filename"可以知道表单项的name。如果有filename="G:/home

page.txt"这样的内容,说明是一个上传的文件,如果是一个上传的文件,那

么描述信息会多一行Content-Type: text/plain来描述文件的Content-Type。

描述信息和主体信息之间是通过换行来分隔的。 

嗯,基本上清晰了,根据这个规律我们就知道该怎么来分离数据,再对分离的

数据进行处理了,不过差点忽略一个问题,就是边界值(上例中的"----------

-------------------7d429871607fe")是怎么知道的?每次上传这个边界值是

不一样的,还好还好asp中可以通过Request.ServerVariables( 

"HTTP_CONTENT_TYPE")来获之,例如上例中HTTP_CONTENT_TYPE内容为:"mult

ipart/form-data; boundary=---------------------------7d429871607fe"

,有了这个,我们不仅可以判断客户端的form中有无使用enctype="multipart  /form-data"(如果没有使用,那么下面就没必要执行啦),还可以获取边界值b

oundary=---------------------------7d429871607fe。(注意:这里获取的

边界值比上面的边界值开头要少"--",最好补充上。) 

至于如何分析数据的过程我就不多赘述了,无非就是借助InStr,Mid等这样的

函数来分离出来我们想要的数据。

二、分块上传,记录进度
要实时反映进度条,实质就是要实时知道当前服务器获取了多少数据?再回想

一下我们实现上传的过程,我们是通过Request.BinaryRead(Request.TotalBy

tes)来实现的,在Request的过程中我们无法得知当前服务器获取了多少数据

。所以只能通过变通的方法了,如果我们可以将获取的数据分成一块一块的,

然后根据已经上传的块数我们就可以算出来当前上传了多大了!也就是说,如

果我1K为1块,那么上传1MB的输入流就分成1024块来获取,例如我当前已经获

取了100块,那么就表明当前上传了100K。当我提出分块的时候很多人觉得不

可思议,因为他们都忽略BinaryRead方法不仅是可以读取指定大小,而且可以

连续读取的。

写个例子来验证一下分块读取的完整性,在刚才的例子基础上(注意该示例不

要上传大文件,否则可能会造成浏览器死掉): 

<%
Dim biData, PostData, TotalBytes, ChunkBytes
ChunkBytes = 1 * 1024 ' 分块大小为1K
TotalBytes = Request.TotalBytes ' 总大小
PostData = "" ' 转化为文本类型后的数据
ReadedBytes = 0 ' 初始化为0
' 分块读取
Do While ReadedBytes < TotalBytes
biData = Request.BinaryRead(ChunkBytes) ' 当前块
PostData = PostData & BinaryToString(biData,ChunkBytes) ' 将当前块转

化为文本并拼接
ReadedBytes = ReadedBytes + ChunkBytes ' 记录已读大小
If ReadedBytes > TotalBytes Then ReadedBytes = TotalBytes
Loop
Response.Write "<pre>" & PostData & "</pre>" ' 使用pre,原样输出格式
' 将二进制流转化成文本
Function BinaryToString(biData,Size) 
Const adLongVarChar = 201
Set RS = createObject("ADODB.Recordset")
RS.Fields.Append "mBinary", adLongVarChar, Size
RS.Open
RS.AddNew
RS("mBinary").AppendChunk(biData)
RS.update
BinaryToString = RS("mBinary").Value
RS.Close
End Function 
%> 


试验一下上传刚才的文本文件,输出结果证明这样分块读取的内容是完整的,

并且在While循环中,我们可以在每次循环时将当前状态记录到Application中

,然后我们就可以通过访问该Application动态获取上传进度条。 

另:上例中是通过字符串拼接的,如果是要拼接二进制数据,可以通过ADODB.

Stream对象的Write方法,示例代码如下: 

Set bSourceData = createobject("ADODB.Stream")
bSourceData.Open
bSourceData.Type = 1 'Binary
Do While ReadedBytes < TotalBytes
biData = Request.BinaryRead(ChunkBytes)
bSourceData.Write biData ' 

直接使用write方法将当前文件流写入bSourceData中
ReadedBytes = ReadedBytes + ChunkBytes
If ReadedBytes > TotalBytes Then ReadedBytes = TotalBytes
Application("ReadedBytes") = ReadedBytes
Loop 

 

三、保存上传的文件
通过Request.BinaryRead获取提交数据,分离出上传文件后,根据数据类型的

不同,保存方式也不同:

对于二进制数据,可以直接通过ADODB.Stream对象的SaveToFile方法,将二进

制流保存成为文件。 
对于文本数据,可以通过TextStream对象的Write方法,将文本数据保存到文

件中。 
对于文本数据和二进制数据,是可以方便的相互转换的,对于上传小文件来说

,两者基本上没什么差别。但是两种方式保存时还是有一些差别的,对于ADOD

B.Stream对象,必须将所有数据全部装载完才可以保存成文件,所以使用这种

方式如果上传大文件将很占用内存,而对于TextStream对象,可以在文件创建

好后,一次Write一部分,分多次Write,这样的好处是不会占用服务器内存空

间,结合上面分析的分块获取数据原理,我们可以每获取一块上传数据就将之

Write到文件中。我曾做过试验,同样本机上传一个200多MB的文件,使用第一

种方式内存一直在涨,到最后直接提示计算机虚拟内存不足,最可恨是即使进

度条表示文件已经上传完,但是最终文件还是没有保存上。而使用后一种方法

,上传过程中内存基本上无什么变化。

四、未解决的难题
我在博客园上看到Bestcomy描述他的Asp.Net上传组件是可以和Sever.SetTime

Out无关的,而在Asp中我是没能做到,对于上传大文件,就只有将Server.Set

TimeOut设置为一个很大的值才可以。不知道有没有比较好的解决方法。

如果我们在保存文件时,使用TextStream对象的Write方法,那么如果用户上

传时中断了文件传输,已经上传的那部分文件还是在的,如果可以断点续传就

好了。关键问题是Request.BinaryRead方法虽然可以分块读取,但是却不能跳

过某一段读取!

五、结束语
原理基本上是说清楚了,但是实际代码要比这复杂的多,要考虑很多问题,最

麻烦在分析数据那部分,对于每一块获取的数据,要分析是不是属于描述信息

,是表单项目还是上传的文件,文件是否已经上传结束…… 

相信根据上面的描述,您也可以开发出您自己功能强大的无组件上传组件。我

想更多的人关心的只是代码,而不会自己动手去写的,也许没有时间,也许水

平还不够,更多的只是已经成为了一种习惯……我在CSDN上见过太多技术八股

文——一段说明,然后全是代码。授人以鱼不若授人以渔,给你一个代码,也

许你并不会去思考为什么,直接拿去用,当下次碰到类似的问题的时候,还是

不知道为什么,希望此文能让更多人学到点什么,最重要是"悟"到点什么!
无组件ASP文件上传源代码 记得在建立一个文件夹"updata" saveannounce_upload.asp 上传页 ------------------------------------ body {font-size:9pt;} input {font-size:9pt;} 文件上传 文件 ------------------------------------ saveannouce_upfile.asp 保存文件到服务器 ------------------------------------ 文件上传 <% dim upload,file,formName,formPath set upload=new upload_5xSoft ''建立上传对象 formPath=upload.form("filepath") ''在目录后加(/) if right(formPath,1)"/" then formPath=formPath&"/" for each formName in upload.file ''列出所有上传了的文件 set file=upload.file(formName) ''生成一个文件对象 if file.filesize<100 then response.write "请先选择你要上传的文件 [ 重新上传 ]" response.end end if if file.filesize>500*1000 then '设置上传文件大小为500K response.write "文件大小超过了限制 500K [ 重新上传 ]" response.end end if if file.FileSize>0 then ''如果 FileSize > 0 说明有文件数据 file.SaveAs Server.mappath("updata\"&file.FileName) ''保存文件 end if set file=nothing next set upload=nothing response.write "文件上传成功 [ 继续上传 ]" %> ------------------------------------ upload.inc 建立upload对象 ------------------------------------ dim upfile_5xSoft_Stream Class upload_5xSoft dim Form,File,Version Private Sub Class_Initialize dim iStart,iFileNameStart,iFileNameEnd,iEnd,vbEnter,iFormStart,iFormEnd,theFile dim strDiv,mFormName,mFormValue,mFileName,mFileSize,mFilePath,iDivLen,mStr Version="" if Request.TotalBytes<1 then Exit Sub set Form=CreateObject("Scripting.Dictionary") set File=CreateObject("Scripting.Dictionary") set upfile_5xSoft_Stream=CreateObject("Adodb.Stream") upfile_5xSoft_Stream.mode=3 upfile_5xSoft_Stream.type=1 upfile_5xSoft_Stream.open upfile_5xSoft_Stream.write Request.BinaryRead(Request.TotalBytes) vbEnter=Chr(13)&Chr(10) iDivLen=inString(1,vbEnter)+1 strDiv=subString(1,iDivLen) iFormStart=iDivLen iFormEnd=inString(iformStart,strDiv)-1 while iFormStart 0 and iFileNameStartiStart then mFileSize=iEnd-iStart-4 else mFileSize=0 end if set theFile=new FileInfo theFile.FileName=getFileName(mFileName) theFile.FilePath=getFilePath(mFileName) theFile.FileSize=mFileSize theFile.FileStart=iStart+4 theFile.FormName=FormName file.add mFormName,theFile else iStart=inString(iEnd+1,vbEnter&vbEnter) iEnd=inString(iStart+4,vbEnter&strDiv) if iEnd>iStart then mFormValue=subString(iStart+4,iEnd-iStart-4) else mFormValue="" end if form.Add mFormName,mFormValue end if iFormStart=iformEnd+iDivLen iFormEnd=inString(iformStart,strDiv)-1 wend End Sub Private Function subString(theStart,theLen) dim i,c,stemp upfile_5xSoft_Stream.Position=theStart-1 stemp="" for i=1 to theLen if upfile_5xSoft_Stream.EOS then Exit for c=ascB(upfile_5xSoft_Stream.Read(1)) If c > 127 Then if upfile_5xSoft_Stream.EOS then Exit for stemp=stemp&Chr(AscW(ChrB(AscB(upfile_5xSoft_Stream.Read(1)))&ChrB(c))) i=i+1 else stemp=stemp&Chr(c) End If Next subString=stemp End function Private Function inString(theStart,varStr) dim i,j,bt,theLen,str InString=0 Str=toByte(varStr) theLen=LenB(Str) for i=theStart to upfile_5xSoft_Stream.Size-theLen if i>upfile_5xSoft_Stream.size then exit Function upfile_5xSoft_Stream.Position=i-1 if AscB(upfile_5xSoft_Stream.Read(1))=AscB(midB(Str,1)) then InString=i for j=2 to theLen if upfile_5xSoft_Stream.EOS then inString=0 Exit for end if if AscB(upfile_5xSoft_Stream.Read(1))AscB(MidB(Str,j,1)) then InString=0 Exit For end if next if InString0 then Exit Function end if next End Function Private Sub Class_Terminate form.RemoveAll file.RemoveAll set form=nothing set file=nothing upfile_5xSoft_Stream.close set upfile_5xSoft_Stream=nothing End Sub Private function GetFilePath(FullPath) If FullPath "" Then GetFilePath = left(FullPath,InStrRev(FullPath, "\")) Else GetFilePath = "" End If End function Private function GetFileName(FullPath) If FullPath "" Then GetFileName = mid(FullPath,InStrRev(FullPath, "\")+1) Else GetFileName = "" End If End function Private function toByte(Str) dim i,iCode,c,iLow,iHigh toByte="" For i=1 To Len(Str) c=mid(Str,i,1) iCode =Asc(c) If iCode255 Then iLow = Left(Hex(Asc(c)),2) iHigh =Right(Hex(Asc(c)),2) toByte = toByte & chrB("&H"&iLow) & chrB("&H"&iHigh) Else toByte = toByte & chrB(AscB(c)) End If Next End function End Class Class FileInfo dim FormName,FileName,FilePath,FileSize,FileStart Private Sub Class_Initialize FileName = "" FilePath = "" FileSize = 0 FileStart= 0 FormName = "" End Sub Public function SaveAs(FullPath) dim dr,ErrorChar,i SaveAs=1 if trim(fullpath)="" or FileSize=0 or FileStart=0 or FileName="" then exit function if FileStart=0 or right(fullpath,1)="/" then exit function set dr=CreateObject("Adodb.Stream") dr.Mode=3 dr.Type=1 dr.Open upfile_5xSoft_Stream.position=FileStart-1 upfile_5xSoft_Stream.copyto dr,FileSize dr.SaveToFile FullPath,2 dr.Close set dr=nothing SaveAs=0 end function End Class
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值