asp无组件上传
2009年10月14日
文件上传组件:upload.asp
上传,true 为普通字段,path为上传文件存放路径
if foro then
getvalue=""
istart=instring(istart,fstr) istart=istart+len(fstr)+5
iend=instring(istart,vbenter+"-----------------------------")
if istart>5+len(fstr) then
getvalue=substring(istart,iend-istart)
else
getvalue=""
end if
else
istart=instring(istart,fstr)
istart=istart+len(fstr)+13
iend=instring(istart,vbenter)-1
filename=substring(istart,iend-istart)
filename=getfilename(filename)
'CheckFileExt(fstr)'''''''''''''''''''''''''''''''''''''''''''''''''''''''
istart=instring(iend,vbenter+vbenter)+3
iend=instring(istart,vbenter+"-----------------------------")
filestart=istart
filesize=iend-istart-1
objstream.position=filestart
Set sf = Server.CreateObject("ADODB.Stream")
sf.Mode=3
sf.Type=1
sf.Open
objstream.copyto sf,FileSize
if filename"" then
Set rf = Server.CreateObject("Scripting.FileSystemObject")
i=0
fn=filename
while rf.FileExists(server.mappath(paths+fn)) fn=cstr(i)+filename
i=i+1
wend
filename=fn
sf.SaveToFile server.mappath(paths+filename),2
end if
getvalue=filename
end if
end functionPrivate function GetFileName(FullPath)
If FullPath "" Then
GetFileName = mid(FullPath,InStrRev(FullPath, "\")+1)
Else
GetFileName = ""
End If
End function Function inString(theStart,varStr)
dim i,j,bt,theLen,str
InString=0
Str=toByte(varStr)
theLen=LenB(Str)
for i=theStart to objStream.Size-theLen
if i>objstream.size then exit Function
objstream.Position=i-1
if AscB(objstream.Read(1))=AscB(midB(Str,1)) then
InString=i
for j=2 to theLen
if objstream.EOS then
inString=0
Exit for
end if
if AscB(objstream.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
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 Function subString(theStart,theLen)
dim i,c,stemp
objStream.Position=theStart-1
stemp=""
for i=1 to theLen
if objStream.EOS then Exit for
c=ascB(objStream.Read(1))
If c > 127 Then
if objStream.EOS then Exit for
stemp=stemp&Chr(AscW(ChrB(AscB(objStream.Read(1)))&ChrB(c)))
i=i+1
else
stemp=stemp&Chr(c)
End If
Next
subString=stemp
End function
%>
1.html
图片和文本一同上传
body {font-size:12px;}
文件路径
标题
uploadfile.asp
0 then
set objstream=server.CreateObject("adodb.stream")
objstream.Mode=3
objstream.Type=1
objstream.Open
objstream.Write Request.BinaryRead(Request.TotalBytes) path=getvalue("filepath",false,"pic/") 'pic为当前目录下一个文件夹名,也可以改成../pic,即上层目录中的pic文件夹
name=getvalue("filename",true,"")
response.write "文件名:"&path&" 标题:"&name
' response.EndEnd if
%> 出处:宝玉BLOG
一、无组件[b]上传[/b]的原理
我还是一点一点用一个实例来说明的吧,客户端HTML如下。要浏览[b]上传[/b]附件,我们通过元素,但是一定要注意必须设置form的enctype属性为"multipart/form-data":
在后台[b]asp[/b]程序中,以前获取表单提交的ASCII 数据,非常的容易。但是如果需要获取[b]上传[/b]的 文件,就必须使用Request对象的BinaryRead方法来读取。BinaryRead方法是对当前输入流进行指定字节数的二进制读取,有点需要注 意的是,一旦使用BinaryRead 方法后,再也不能使用Request.Form 或 Request.QueryString 集合了。结合Request对象的TotalBytes属性,可以将所有表单提交的数据全部变成二进制,不过这些数据都是经过编码的。首先让我们来看看这 些数据是如何编码的,有无什么规律可循,编段代码,在代码中我们将BinaryRead读取的二进制转化为文本,输出出来,在后台的upload.[b]asp[/b]中(注意该示例不要[b]上传[/b]大文件,否则可能会造成浏览器死掉):
" & PostData & "" '使用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
%>
简单起见,[b]上传[/b]一个最简单的文本文件(G:\homepage.txt,内容为"宝玉:http://www.webuc.net")来试验一下,文本框filename中保留默认值"default filename",提交看看输出结果: -----------------------------7d429871607fe
Content-Disposition: form-data; name="file1"; filename="G:\homepage.txt"
Content-Type: text/plain
宝玉:http://www.webuc.net
-----------------------------7d429871607fe
Content-Disposition: form-data; name="filename"
default filename
-----------------------------7d429871607fe--可 以看出来对于表单中的项目,是用过"-----------------------------7d429871607fe"这样的边界来分隔成一块一 块的,每一块的开始都有一些描述信息,例如:Content-Disposition: form-data; name="filename",在描述信息中,通过name="filename"可以知道表单项的name。如果有filename="G:\ homepage.txt"这样的内容,说明是一个[b]上传[/b]的文件,如果是一个[b]上传[/b]的文件,那么描述信息会多一行Content-Type: text/plain来描述文件的Content-Type。描述信息和主体信息之间是通过换行来分隔的。
嗯,基本上清晰了,根据这个规律我们就知道该怎么来分离数据,再对分离的数据进行处理了,不过差点忽略一个问题,就是边界值(上例中的"-----------------------------7d429871607fe")是怎么知道的?每次[b]上传[/b]这个边界值是不一样的,还好还好[b]asp[/b]中 可以通过Request.ServerVariables( "HTTP_CONTENT_TYPE")来获之,例如上例中HTTP_CONTENT_TYPE内容为:"multipart/form-data; boundary=---------------------------7d429871607fe",有了这个,我们不仅可以判断客户端的form 中有无使用enctype="multipart/form-data"(如果没有使用,那么下面就没必要执行啦),还可以获取边界值boundary= ---------------------------7d429871607fe。(注意:这里获取的边界值比上面的边界值开头要少"--",最好补 充上。)
至于如何分析数据的过程我就不多赘述了,无非就是借助InStr,Mid等这样的函数来分离出来我们想要的数据。
二、分块[b]上传[/b],记录进度
要实时反映进度条,实质就是要实时知道当前服务器获取了多少数据?再回想一下我们实现[b]上传[/b]的过程,我们是通过Request.BinaryRead(Request.TotalBytes)来实现的,在Request的过程中我们无法得知当前服务器获取了多少数据。所以只能通过变通的方法了,如果我们可以将获取的数据分成一块一块的,然后根据已经[b]上传[/b]的块数我们就可以算出来当前[b]上传[/b]了多大了!也就是说,如果我1K为1块,那么[b]上传[/b]1MB的输入流就分成1024块来获取,例如我当前已经获取了100块,那么就表明当前[b]上传[/b]了100K。当我提出分块的时候很多人觉得不可思议,因为他们都忽略BinaryRead方法不仅是可以读取指定大小,而且可以连续读取的。写个例子来验证一下分块读取的完整性,在刚才的例子基础上(注意该示例不要[b]上传[/b]大文件,否则可能会造成浏览器死掉):
TotalBytes Then ReadedBytes = TotalBytes
Loop
Response.Write "" & PostData & "" ' 使用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
%>试验一下[b]上传[/b]刚才的文本文件,输出结果证明这样分块读取的内容是完整的,并且在While循环中,我们可以在每次循环时将当前状态记录到Application中,然后我们就可以通过访问该Application动态获取[b]上传[/b]进度条。
另:上例中是通过字符串拼接的,如果是要拼接二进制数据,可以通过ADODB.Stream对象的Write方法,示例代码如下:
Set bSourceData = createobject("ADODB.Stream")
bSourceData.Open
bSourceData.Type = 1 'Binary
Do While ReadedBytes TotalBytes Then ReadedBytes = TotalBytes
Application("ReadedBytes") = ReadedBytes
Loop
三、保存[b]上传[/b]的文件
通过Request.BinaryRead获取提交数据,分离出[b]上传[/b]文件后,根据数据类型的不同,保存方式也不同:对于二进制数据,可以直接通过ADODB.Stream对象的SaveToFile方法,将二进制流保存成为文件。
对于文本数据,可以通过TextStream对象的Write方法,将文本数据保存到文件中。
对于文本数据和二进制数据,是可以方便的相互转换的,对于[b]上传[/b]小文件来说,两者基本上没什么差别。但是两种方式保存时还是有一些差别的,对于ADODB.Stream对象,必须将所有数据全部装载完才可以保存成文件,所以使用这种方式如果[b]上传[/b]大文件将很占用内存,而对于TextStream对象,可以在文件创建好后,一次Write一部分,分多次Write,这样的好处是不会占用服务器内存空间,结合上面分析的分块获取数据原理,我们可以每获取一块[b]上传[/b]数据就将之Write到文件中。我曾做过试验,同样本机[b]上传[/b]一个200多MB的文件,使用第一种方式内存一直在涨,到最后直接提示计算机虚拟内存不足,最可恨是即使进度条表示文件已经[b]上传[/b]完,但是最终文件还是没有保存上。而使用后一种方法,[b]上传[/b]过程中内存基本上无什么变化。四、未解决的难题
我在博客园上看到Bestcomy描述他的[b]Asp[/b].Net[b]上传[/b]组件是可以和Sever.SetTimeOut无关的,而在[b]Asp[/b]中我是没能做到,对于[b]上传[/b]大文件,就只有将Server.SetTimeOut设置为一个很大的值才可以。不知道有没有比较好的解决方法。如果我们在保存文件时,使用TextStream对象的Write方法,那么如果用户[b]上传[/b]时中断了文件传输,已经[b]上传[/b]的那部分文件还是在的,如果可以断点续传就好了。关键问题是Request.BinaryRead方法虽然可以分块读取,但是却不能跳过某一段读取!
五、结束语
原理基本上是说清楚了,但是实际代码要比这复杂的多,要考虑很多问题,最麻烦在分析数据那部分,对于每一块获取的数据,要分析是不是属于描述信息,是表单项目还是[b]上传[/b]的文件,文件是否已经[b]上传[/b]结束…… 相信根据上面的描述,您也可以开发出您自己功能强大的无组件[b]上传[/b]组件。 我想更多的人关心的只是代码,而不会自己动手去写的,也许没有时间,也许水平还不够,更多的只是已经成为了一种习惯……我在CSDN上见过太多技术八股文 ――一段说明,然后全是代码。授人以鱼不若授人以渔,给你一个代码,也许你并不会去思考为什么,直接拿去用,当下次碰到类似的问题的时候,还是不知道为什 么,希望此文能让更多人学到点什么,最重要是“悟”到点什么!
2009年10月14日
文件上传组件:upload.asp
上传,true 为普通字段,path为上传文件存放路径
if foro then
getvalue=""
istart=instring(istart,fstr) istart=istart+len(fstr)+5
iend=instring(istart,vbenter+"-----------------------------")
if istart>5+len(fstr) then
getvalue=substring(istart,iend-istart)
else
getvalue=""
end if
else
istart=instring(istart,fstr)
istart=istart+len(fstr)+13
iend=instring(istart,vbenter)-1
filename=substring(istart,iend-istart)
filename=getfilename(filename)
'CheckFileExt(fstr)'''''''''''''''''''''''''''''''''''''''''''''''''''''''
istart=instring(iend,vbenter+vbenter)+3
iend=instring(istart,vbenter+"-----------------------------")
filestart=istart
filesize=iend-istart-1
objstream.position=filestart
Set sf = Server.CreateObject("ADODB.Stream")
sf.Mode=3
sf.Type=1
sf.Open
objstream.copyto sf,FileSize
if filename"" then
Set rf = Server.CreateObject("Scripting.FileSystemObject")
i=0
fn=filename
while rf.FileExists(server.mappath(paths+fn)) fn=cstr(i)+filename
i=i+1
wend
filename=fn
sf.SaveToFile server.mappath(paths+filename),2
end if
getvalue=filename
end if
end functionPrivate function GetFileName(FullPath)
If FullPath "" Then
GetFileName = mid(FullPath,InStrRev(FullPath, "\")+1)
Else
GetFileName = ""
End If
End function Function inString(theStart,varStr)
dim i,j,bt,theLen,str
InString=0
Str=toByte(varStr)
theLen=LenB(Str)
for i=theStart to objStream.Size-theLen
if i>objstream.size then exit Function
objstream.Position=i-1
if AscB(objstream.Read(1))=AscB(midB(Str,1)) then
InString=i
for j=2 to theLen
if objstream.EOS then
inString=0
Exit for
end if
if AscB(objstream.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
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 Function subString(theStart,theLen)
dim i,c,stemp
objStream.Position=theStart-1
stemp=""
for i=1 to theLen
if objStream.EOS then Exit for
c=ascB(objStream.Read(1))
If c > 127 Then
if objStream.EOS then Exit for
stemp=stemp&Chr(AscW(ChrB(AscB(objStream.Read(1)))&ChrB(c)))
i=i+1
else
stemp=stemp&Chr(c)
End If
Next
subString=stemp
End function
%>
1.html
图片和文本一同上传
body {font-size:12px;}
文件路径
标题
uploadfile.asp
0 then
set objstream=server.CreateObject("adodb.stream")
objstream.Mode=3
objstream.Type=1
objstream.Open
objstream.Write Request.BinaryRead(Request.TotalBytes) path=getvalue("filepath",false,"pic/") 'pic为当前目录下一个文件夹名,也可以改成../pic,即上层目录中的pic文件夹
name=getvalue("filename",true,"")
response.write "文件名:"&path&" 标题:"&name
' response.EndEnd if
%> 出处:宝玉BLOG
一、无组件[b]上传[/b]的原理
我还是一点一点用一个实例来说明的吧,客户端HTML如下。要浏览[b]上传[/b]附件,我们通过元素,但是一定要注意必须设置form的enctype属性为"multipart/form-data":
在后台[b]asp[/b]程序中,以前获取表单提交的ASCII 数据,非常的容易。但是如果需要获取[b]上传[/b]的 文件,就必须使用Request对象的BinaryRead方法来读取。BinaryRead方法是对当前输入流进行指定字节数的二进制读取,有点需要注 意的是,一旦使用BinaryRead 方法后,再也不能使用Request.Form 或 Request.QueryString 集合了。结合Request对象的TotalBytes属性,可以将所有表单提交的数据全部变成二进制,不过这些数据都是经过编码的。首先让我们来看看这 些数据是如何编码的,有无什么规律可循,编段代码,在代码中我们将BinaryRead读取的二进制转化为文本,输出出来,在后台的upload.[b]asp[/b]中(注意该示例不要[b]上传[/b]大文件,否则可能会造成浏览器死掉):
" & PostData & "" '使用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
%>
简单起见,[b]上传[/b]一个最简单的文本文件(G:\homepage.txt,内容为"宝玉:http://www.webuc.net")来试验一下,文本框filename中保留默认值"default filename",提交看看输出结果: -----------------------------7d429871607fe
Content-Disposition: form-data; name="file1"; filename="G:\homepage.txt"
Content-Type: text/plain
宝玉:http://www.webuc.net
-----------------------------7d429871607fe
Content-Disposition: form-data; name="filename"
default filename
-----------------------------7d429871607fe--可 以看出来对于表单中的项目,是用过"-----------------------------7d429871607fe"这样的边界来分隔成一块一 块的,每一块的开始都有一些描述信息,例如:Content-Disposition: form-data; name="filename",在描述信息中,通过name="filename"可以知道表单项的name。如果有filename="G:\ homepage.txt"这样的内容,说明是一个[b]上传[/b]的文件,如果是一个[b]上传[/b]的文件,那么描述信息会多一行Content-Type: text/plain来描述文件的Content-Type。描述信息和主体信息之间是通过换行来分隔的。
嗯,基本上清晰了,根据这个规律我们就知道该怎么来分离数据,再对分离的数据进行处理了,不过差点忽略一个问题,就是边界值(上例中的"-----------------------------7d429871607fe")是怎么知道的?每次[b]上传[/b]这个边界值是不一样的,还好还好[b]asp[/b]中 可以通过Request.ServerVariables( "HTTP_CONTENT_TYPE")来获之,例如上例中HTTP_CONTENT_TYPE内容为:"multipart/form-data; boundary=---------------------------7d429871607fe",有了这个,我们不仅可以判断客户端的form 中有无使用enctype="multipart/form-data"(如果没有使用,那么下面就没必要执行啦),还可以获取边界值boundary= ---------------------------7d429871607fe。(注意:这里获取的边界值比上面的边界值开头要少"--",最好补 充上。)
至于如何分析数据的过程我就不多赘述了,无非就是借助InStr,Mid等这样的函数来分离出来我们想要的数据。
二、分块[b]上传[/b],记录进度
要实时反映进度条,实质就是要实时知道当前服务器获取了多少数据?再回想一下我们实现[b]上传[/b]的过程,我们是通过Request.BinaryRead(Request.TotalBytes)来实现的,在Request的过程中我们无法得知当前服务器获取了多少数据。所以只能通过变通的方法了,如果我们可以将获取的数据分成一块一块的,然后根据已经[b]上传[/b]的块数我们就可以算出来当前[b]上传[/b]了多大了!也就是说,如果我1K为1块,那么[b]上传[/b]1MB的输入流就分成1024块来获取,例如我当前已经获取了100块,那么就表明当前[b]上传[/b]了100K。当我提出分块的时候很多人觉得不可思议,因为他们都忽略BinaryRead方法不仅是可以读取指定大小,而且可以连续读取的。写个例子来验证一下分块读取的完整性,在刚才的例子基础上(注意该示例不要[b]上传[/b]大文件,否则可能会造成浏览器死掉):
TotalBytes Then ReadedBytes = TotalBytes
Loop
Response.Write "" & PostData & "" ' 使用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
%>试验一下[b]上传[/b]刚才的文本文件,输出结果证明这样分块读取的内容是完整的,并且在While循环中,我们可以在每次循环时将当前状态记录到Application中,然后我们就可以通过访问该Application动态获取[b]上传[/b]进度条。
另:上例中是通过字符串拼接的,如果是要拼接二进制数据,可以通过ADODB.Stream对象的Write方法,示例代码如下:
Set bSourceData = createobject("ADODB.Stream")
bSourceData.Open
bSourceData.Type = 1 'Binary
Do While ReadedBytes TotalBytes Then ReadedBytes = TotalBytes
Application("ReadedBytes") = ReadedBytes
Loop
三、保存[b]上传[/b]的文件
通过Request.BinaryRead获取提交数据,分离出[b]上传[/b]文件后,根据数据类型的不同,保存方式也不同:对于二进制数据,可以直接通过ADODB.Stream对象的SaveToFile方法,将二进制流保存成为文件。
对于文本数据,可以通过TextStream对象的Write方法,将文本数据保存到文件中。
对于文本数据和二进制数据,是可以方便的相互转换的,对于[b]上传[/b]小文件来说,两者基本上没什么差别。但是两种方式保存时还是有一些差别的,对于ADODB.Stream对象,必须将所有数据全部装载完才可以保存成文件,所以使用这种方式如果[b]上传[/b]大文件将很占用内存,而对于TextStream对象,可以在文件创建好后,一次Write一部分,分多次Write,这样的好处是不会占用服务器内存空间,结合上面分析的分块获取数据原理,我们可以每获取一块[b]上传[/b]数据就将之Write到文件中。我曾做过试验,同样本机[b]上传[/b]一个200多MB的文件,使用第一种方式内存一直在涨,到最后直接提示计算机虚拟内存不足,最可恨是即使进度条表示文件已经[b]上传[/b]完,但是最终文件还是没有保存上。而使用后一种方法,[b]上传[/b]过程中内存基本上无什么变化。四、未解决的难题
我在博客园上看到Bestcomy描述他的[b]Asp[/b].Net[b]上传[/b]组件是可以和Sever.SetTimeOut无关的,而在[b]Asp[/b]中我是没能做到,对于[b]上传[/b]大文件,就只有将Server.SetTimeOut设置为一个很大的值才可以。不知道有没有比较好的解决方法。如果我们在保存文件时,使用TextStream对象的Write方法,那么如果用户[b]上传[/b]时中断了文件传输,已经[b]上传[/b]的那部分文件还是在的,如果可以断点续传就好了。关键问题是Request.BinaryRead方法虽然可以分块读取,但是却不能跳过某一段读取!
五、结束语
原理基本上是说清楚了,但是实际代码要比这复杂的多,要考虑很多问题,最麻烦在分析数据那部分,对于每一块获取的数据,要分析是不是属于描述信息,是表单项目还是[b]上传[/b]的文件,文件是否已经[b]上传[/b]结束…… 相信根据上面的描述,您也可以开发出您自己功能强大的无组件[b]上传[/b]组件。 我想更多的人关心的只是代码,而不会自己动手去写的,也许没有时间,也许水平还不够,更多的只是已经成为了一种习惯……我在CSDN上见过太多技术八股文 ――一段说明,然后全是代码。授人以鱼不若授人以渔,给你一个代码,也许你并不会去思考为什么,直接拿去用,当下次碰到类似的问题的时候,还是不知道为什 么,希望此文能让更多人学到点什么,最重要是“悟”到点什么!