(因为我还没完成我的组件,不保证文章中的代码的正确性.)
上传多文件
(sander duivestein)
介绍
这是我写的一个上传文件的activex的组件.这个组件是使用winsocket 控件调用ftp提交文件.它仅仅工作在ie3.02或更高的版本,但有些客户端使用netscape,因此不得不找另外的解决方案.但我搜索 internet 和若干新闻组时,发现不止我一个有这样的要求.
在1999.3.11, 看到啦doug dean的一篇15seconds的文章(关于用vb asp 组件上传文件)给我啦一个很好的提示.
doug dean的组件简单并且易用.但他说,然而,多元件的上传问题没解决?因而仍然有一些工作要完成.
在我开始做自己的组件以前,我想知道其他类似控件提供什么样的功能.因此我看啦另外3个著名的组件:
the upload component of software artisans, the upload component of aspupload,
and the microsoft posting acceptor.
通过比较这些组件我觉的我的组件应该满足下列要求:
提交文件的html表单对asp 组件应该是一黑盒子.也就是说组件能接受各种表单元件并能得到表单元件的名字和值.
它应能提供一个上传路径,并且限制大小.
组件应该能处理多个的文件.
组件应该有一错误处理程序.
组件应该性能很好.
组件应该能在nc中象ie一样工作.
保存文件进入数据库.
仅允许某组用能上载文件.
这些对我来说是有相当的挑战.
解决问题
首先我要创建一html文件,它包含两个元件:一简单的文本框,一文件框.这里给出下列代码:
1:upload.htm
<html>
<head><title>upload</title></head>
<body>
<form name="frmupload" method="post" enctype="multipart/form-data" action="upload.asp"> <table>
<tr><td>author</td><td><input type="text" name="txtauthor"></td></tr>
<tr><td>file</td><td><input type="file" name="txtfilename"></td></tr>
<tr><td colspan="2" align="right"><input type="submit" value="upload"></td></tr>
</table>
</form>
</body>
</html>
使用enctype="multipart/form-data" 使表单能够提交一文件.我们也需要一文件接收文件.
2:upload.asp
<%@ language=vbscript %>
<%
option explicit
response.buffer = true
on error resume next
if request.servervariables("request_method") = "post" then
dim objupload
dim lngmaxfilebytes
dim struploadpath
dim varresult
lngmaxfilebytes = 10000
struploadpath = "c:\inetpub\wwwroot\upload\"
set objupload = server.createobject("pjuploadfile.clsupload")
if err.number <> 0 then
response.write "the component wasnt registered"
else
varresult = objupload.doupload (lngmaxfilebytes, struploadpath)
set objupload = nothing
dim i
for i = 0 to ubound(varresult,1)
response.write varresult(i,0) & " : " & varresult(i,1) & "<br>"
next
end if
end if
%>
在这里设置下面两个变量:
lngmaxfilebytes – 文件最大字节数, 和 struploadpath -文件上传位置.我也增加了错误处理程序检查是否装入组件在网服务器上适当注册.这是我做的处理唯一的一个错误.如果任何另外的错误发生,可以再加入处理它.最后,再声明varreturn.这变量用来接受组件的返回值.这返回值应该包含所有的表单元件名字和他们的值.你能看见for next loop中的程序,这返回值必须是一数组.
这是比较容易的部分.现在我们必须创造一activex 组件,用来处理提交的表单.
打开vb6,选择一activex 项目 (参阅步骤1:)
步骤1:
创造一activex dll 项目
首先,先添加一个引用,在菜单条上选定添加引用项,选中
active server pages object library.(参阅步骤2).
步骤2:
工程引用
通过这个库我们能使用asp的request的请求对象.为保证能使用,要用如下代码:
option explicit
private myscriptingcontext as scriptingcontext
private myrequest as request
private myresponse as request
public sub onstartpage(passedscriptingcontext as scriptingcontext)
set myscriptingcontext = passedscriptingcontext
set myrequest = myscriptingcontext.request
set myresponse = mysriptingcontext.response
end sub
为什么我们需要asp库?通过request对象我们能得到由upload.htm传来的http数据流.在那里为什么有一个 "但是"?当我们尝试读 表单字段名字和相对的值,例如,request.form("txttitle"), 但我们就不能读出余下的发送给我们的原始数据.因此我们使用request.totalbytes 和request.binaryread 读取发送的数据.
下面是我从doug dean得到的代码:
~~~~~ variables ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
dim varbytecount
dim binarray() as byte
~~~~~ byte count of raw form data ~~~~~~~~~~~~
varbytecount = myrequest.totalbytes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~ place raw data into byte array ~~~~~~~~~
redim binarray(varbytecount)
binarray = myrequest.binaryread(varbytecount)
doug dean将数据放入一个二进制的数组,它当然工作的很好.但是当我到想得到表单的名字和值时,我遇见一个问题,例如,我们想要提交包含5个文本框和一个文件框的html表单.提交的文件有100k 的大小 (=102,400字节)的限制.现在我们收到102,400字节.所有的这些字节将进一个二进制的数组.当我们想要分开时,表单元件名,他们的值,我们通过全部数组的循环.我们必须循环6次,因为我们提交了6个表单元件.它工作的很好,但是性能下降.
因此我们需要找到另一种方法从http header中提取数据.当浏览msdn 库,我发现答案.我们必须使用unicode!!!与unicode 我们能把数据放进一个vb的variant,它包含字符串.
因此我的代码看起来像这:
option explicit
private myscriptingcontext as scriptingcontext
private myrequest as request
private myresponse as request
public sub onstartpage(passedscriptingcontext as scriptingcontext)
set myscriptingcontext = passedscriptingcontext
set myrequest = myscriptingcontext.request
set myresponse = mysriptingcontext.response
end sub
public function doupload (byval lngmaxfilebytes as long, _
byval struploadpath as string) as variant
dim varbytecount as variant
dim varhttpheader as variant
count total bytes in http-header
varbytecount = myrequest.totalbytes
conversion of bytes to unicode
varhttpheader = strconv(myrequest.binaryread(varbytecount), vbunicode)
write httpheader
myresponse.write varhttpheader
function end
现在让我们测试这些代码的.为这测试我使用用了一个.txt 文件 "warp11.txt" 那包含的下面一句话:
"warp11 builds state-of-the-art applications at the speed of light."
我们提交的表单看起来像这:
这是上传组件写入我们的浏览器的内容:
—————————–7cf28c330254 content-disposition: form-data;
name="txtauthor" sander duivestein —————————–7cf28c330254 content-
disposition: form-data; name="txtfilename"; filename="c:\download\warp11.txt"
content-type: text/plain warp11 builds state-of-the-art applications at the speed of light.
—————————–7cf28c330254
正如你所见的,这仅仅是一字符串,我们能识别这个字符串.当一html form通过multipart/form-data mme提交,表单数据被分成几部分.表单数据的每部分是由分界符分开:"—————————–7cf28c330254".
通过这个分界符我们能有一串不同的字符串.在上传应用程序中,我们使用这个分界符划分表单数据,因此我们把这界线字符串放在变量vardelimeter.
我们增加下列代码执行这个功能:
dim vardelimeter as variant
vardelimeter = leftb(varhttpheader, 76)
首先,我们想要知道的上传了多少个表单元件.因此我们必须做一计数器:
dim intformfieldcounter as integer
count formfields
intformfieldcounter = len(varhttpheader) – len(replace(varhttpheader, "; name=", mid("; name=", 2)))
当我们知道表单元件的数目时,我们能通过对varhttpheader 进行循环查找,然后我们能分别得到列表单元件的名字和他们的值.当我们查找某一表单域名称和值时,我们想要这些保存这些.存储这些值,我们简单地创造一个两维的数组:
dim intformfieldcounter as integer
count formfields
intformfieldcounter = len(varhttpheader) – len(replace(varhttpheader, "; name=", mid("; name=", 2)))
其次,我们想要返回这些值到upload.asp 文件.这就是为什么我们使用数组变体型的类型.variant是asp能理解的唯一的数组类型.现在列出表单元件和他们的名字.
首先,我们创造一循环:
begin parsing formfield names and values
for i = 0 to intformfieldcounter – 1
然后我们决定第一表单元件将开始的位置:
determine where formfieldname starts
lngformfieldnamestart = instrb(lngformfieldnamestart + 1, varhttpheader, "; name=" & chr(34))
我们现在找到表单元件结束的地方:
determine where formfieldname ends
lngformfieldnameend = instrb(lngformfieldnamestart + _
len(strconv("; name=" & chr(34), vbunicode)), varhttpheader, chr(34)) _
+ len(strconv(chr(34), vbunicode))
如果我们有表单元件开始和结束的位置,我们能过滤出表单元件的名称:
filter formfieldname
strformfieldname = midb(varhttpheader, lngformfieldnamestart, lngformfieldnameend – lngformfieldnamestart)
remove "; name=" from string
strformfieldname = replace(strformfieldname, "; name=", vbnullstring)
remove quotes from string
strformfieldname = replace(strformfieldname, chr(34), vbnullstring)
在表单元件名字后面有一个";",通过它,我们就能找到它的类型.如果是这样,我们就能处理文件,不同的是我们正在处理一文本字段.因此建立一个if then判断.
check for file
if midb(varhttpheader, lngformfieldnameend, 2) = ";" then
当看我们的header, 我们能发现正准备进行的循环的条件是false.这意味着我们正在处理文本字段.现在就能找到我们的表单元件对应的值了.首先我们必须找到表单元件的值开始的地方:
else
determine where formfieldvalue starts
lngformfieldvaluestart = lngformfieldnameend
第二,和以前一样,我们必须发现我们的表单域值结束的地方:
determine where formfieldvalue ends
lngformfieldvalueend = instrb(lngformfieldvaluestart, varhttpheader, vardelimeter)
