网站推广,网站提供网站推广、免费网络推广、网络推广研究、网络推广、免费网站推广、网络推广培训、网络推广软件、网络推广工具、交换连接、行业网站推广、地域网站推广,提供功能强大的免费推广服务.

如何用delphi 6开发asp上传组件详解


文件上传是web开发中经常要用到的功能,但asp本身和内置的组件都不支持文件上传功能。网上流传的一些第三方组件虽然能够解决这个问题,但大多是要收费的,更别说open source了。本文将详细剖析web文件上传的原理,以及一步步指导读者如何用delphi6开发一个asp上传组件。
1 html文件分析
首先我们来看一个html文件源码,文件名是test.htm,功能是提供用户上传的界面:
<html> <body> <center>    <form name="mainform" enctype="multipart/form-data" action="test.asp" method=post>     <input type=file name=mefile><br> <input type=hidden name=a1 value="fdsaf"> <input type=hidden name=a2 value="fdsaf"> <input type=hidden name=a3 value="fdsaf"> <input type=hidden name=a4 value="fsdfsdsaf"> <input type=hidden name=a5 value="这个是这个"> <input type=text name=a6 value="fdsaf">    <input type=submit name=ok value="ok">    </form> </center> </body> </html>


这个文件里包含了一个名为mainform的form,以及随手写的一些input域。注意这个form和一般的form有两个不同的地方:一是它有一个type=file的域,没有value。用浏览器打开这个文件时,这个域会表现为一个右侧有“浏览”字样的文件输入框,用户可以通过它来选择本地硬盘上的文件。二是form有一个特殊的属性:enctype="multipart/form-data"。这个属性告诉浏览器要上传二进制文件,并进行相应编码。
这种编码会产生什么样的表单信息呢?让我们来看看test.asp,也就是接受表单的asp文件的源码,它非常简单:
<% formsize=request.totalbytes   '获得表单原始信息的长度 formdata=request.binaryread(formsize)   '读取表单原始信息 response.binarywrite formdata  '返回表单原始信息 %> 如读者在注释中了解的,这段代码的功能是将表单的原始信息返回。让我们来看看它的运行效果。将 这两个文件置于web目录下,访问test.htm。在文件输入框中,选择一个文件(我选了一个jpg图片, 不过最大不要太大)。提交,然后可以看到这样一堆乱七八糟的信息: -----------------------------7d2227629012e content-disposition: form-data; name="mefile"; filename="c:\documents and settings\aaa\my documents\my pictures\zzjh.jpg" content-type: image/pjpeg (作者注:以下为乱码) -----------------------------7d2227629012e content-disposition: form-data; name="a1" fdsaf -----------------------------7d2227629012e content-disposition: form-data; name="a2" fdsaf -----------------------------7d2227629012e content-disposition: form-data; name="a3" fdsaf -----------------------------7d2227629012e content-disposition: form-data; name="a4" fsdfsdsaf -----------------------------7d2227629012e content-disposition: form-data; name="a5" 这个是这个 -----------------------------7d2227629012e content-disposition: form-data; name="a6" fdsaf -----------------------------7d2227629012e content-disposition: form-data; name="ok" ok -----------------------------7d2227629012e-- 这就是用"multipart/form-data"方式编码的表单原始信息。其中那一段看起来是乱码的部分,就是jpg图片的编码。 分析一下这段信息的格式:   -----------------------------7d2227629012e 这是各个域之间的分隔符。 content-disposition: form-data; 说明这是表单中的域。 name="mefile"; 域的名称。 filename="c:\documents and settings\aaa\my documents\my pictures\zzjh.jpg" 上 传文件在本地硬盘上的名称。 content-type: image/pjpeg 文件类型。 后面是文件本身的数据。


其它各个域的信息也可以以此类推。
众所周知,在asp中,使用request对象,可以访问用户提交表单的各个域。因为request对象
会对原始的表单信息进行解析,提取出表单中每个域的值。但是,request并不能解析这
"multipart/form-data"格式的表单信息。这就是asp不能直接支持文件上传的原因所在。读者可以
试试,在test.asp中,用request("mefile")这样的格式,是不能读取到正确的信息的。
问题的症结已经找到,解决的思路也很简单:用delphi开发一个com组件,接受这种原始表单信息,
将各个域一一提取出来,返回给asp文件。也就是完成request对象没有完成的功能。
2 用delphi开发组件
delphi6对开发asp组件提供了极好的支持,大大简化了我们的开发过程。
启动delphi 6,选择file-new-other-activex-activex library,这样就建立了一个activex库。将此library改名为myobj,存盘。选择file-new-other-activex-active server object,在coclassname中填入upfile,确定。这时会跳出一个标题为myobj.tlb的对话框,这是delphi特有的以可视化方式编辑com接口的功能,用delphi开发过com的读者应该比较熟悉。
在myobj下的名为iupfile的interface下,添加5个属性和一个方法。如果不懂得如何操作,
请参见delphi参考书的相关部分。按f12可以看到生成的相应的myobj_tlb.pas文件,其中的
iupfile接口应该是这个样子:
iupfile = interface(idispatch) ['{5c40d0eb-5a22-4a1e-8808-62207ae04b51}'] procedure onstartpage(const ascriptingcontext: iunknown); safecall; procedure onendpage; safecall; function get_form(formname: olevariant): olevariant; safecall; function get_filename: olevariant; safecall; function get_filesize: integer; safecall; procedure filesaveas(filename: olevariant); safecall; function get_filedata: olevariant; safecall; function get_filetype: olevariant; safecall; property form[formname: olevariant]: olevariant read get_form; property filename: olevariant read get_filename; property filesize: integer read get_filesize; property filedata: olevariant read get_filedata; property filetype: olevariant read get_filetype; end;


其中的onstartpage方法和onendpage方法是delphi默认生成的,其它的是手动加入的。
切换到unit1.pas(也是delphi自动生成的),改名为upfile.pas存盘。可以看到存在一个tupfile类的声明,它是继承自taspobject类和iupfile接口的。delphi 6已经自动生成了相应的代码。接下来的任务就是实现这个接口。
除了完成iupfile接口中的属性和方法之后,还需要补充一些东西,以便完成我们的任务。最终的
tupfile类的声明如下:
tupfile = class(taspobject, iupfile) public protected procedure onendpage; safecall; //页面开始 procedure onstartpage(const ascriptingcontext: iunknown); safecall; //页面 结束 procedure filesaveas(filename: olevariant); safecall; //保存文件 function get_form(formname: olevariant): olevariant; safecall; // function get_filename: olevariant; safecall; function get_filesize: integer; safecall; function get_filedata: olevariant; safecall; function get_filetype: olevariant; safecall; private fcontentdata:string; ffiledata,ffilename,ffiletype:string; fforminfo:tstringlist; function instr(str1,str2:string;startpos:integer):integer; procedure analyformdata(content:string); end;


下面我们来一一分析这些成员的具体实现。
procedure tupfile.onstartpage(const ascriptingcontext: iunknown); var aolevariant : olevariant; tmpvar : olevariant; contentlength : integer; i,delicount,pos1,pos2,lastpos : integer; fdelimeter : string; begin inherited onstartpage(ascriptingcontext); fforminfo := tstringlist.create; contentlength := request.totalbytes; aolevariant := contentlength; tmpvar := request.binaryread(aolevariant); for i := 1 to contentlength -1 do begin fcontentdata := fcontentdata + chr(byte(tmpvar)); end; pos1 := pos(#13#10,fcontentdata); fdelimeter := copy(fcontentdata,1,pos1+1); delicount := length(fdelimeter); lastpos := 1; pos1:=0; while pos2>=pos1 do begin pos1 := instr(fdelimeter,fcontentdata,lastpos); if pos1 = 0 then break; pos1 := pos1 + delicount; pos2 := instr(fdelimeter,fcontentdata,pos1)-1; analyformdata(copy(fcontentdata,pos1,pos2-pos1-1)); lastpos := pos2; end; end;


前面说过,onstartpage方法是delphi自动生成的,在装载页面时发生。在这个方法中,我们完成一些初始化的任务:读取表单的原始数据,解析表单中的域,并存入相应的属性中,以备调用。
由于delphi已经对asp中的对象进行了很好的封装,所以即使在delphi环境下,也可以方便地调用它,就象在asp中一样,例如request.totalbytes。首先将原始表单数据读入到一个oleviarians类型的pvar中,然后通过一个循环,将它转换为delphi中的string格式,并存放在fcontentdata中。
接下来,通过查找换行符,解析出分隔符的内容和长度。然后在一个循环中,用analyformdata成员函数一一解析出每个域。初始化工作就这样完成了。

再看analyformdata函数的实现:
procedure tupfile.analyformdata(content: string); var pos1,pos2:integer; formname,formvalue:string; isfile:boolean; begin isfile := false; pos1 := instr('name="',content,1)+6; pos2 := instr('"',content,pos1); formname := copy(content,pos1,pos2-pos1); //检查是否文件 pos1 := instr('filename="',content,pos2+1); if pos1 <> 0 then begin isfile := true; pos1 := pos1 + 10; pos2 := instr('"',content,pos1); ffilename := copy(content,pos1,pos2-pos1); end; pos1 := instr(#13#10#13#10,content,pos2+1)+4; formvalue := copy(content,pos1,length(content)-pos1); if isfile then begin ffiledata := formvalue; //查找文件类型信息 pos2 := instr('content-type: ',content,pos2+1); if pos2 <> 0 then begin pos2 := pos2 + 14; ffiletype := copy(content,pos2,pos1-4-pos2); end; end else begin fforminfo.add(formname+'='+formvalue); end; end;


如注释中所表达的,analyformdata提取原始数据中的域。如果是域是文件类型,则将文件类型和文件数据分别放入ffiletype和ffiledata中。如果是其它类型,则将名称和值放入一个tstringlist类型的fforminfo中。fforminfo中维护着除文件类型外的所有域的信息,以“名称=值”的格式存放。
   function tupfile.get_form(formname: olevariant): olevariant; begin result := fforminfo.values[formname]; end;


这个函数返回域的值。只需要简单地调用fforminfo的values方法,就可以得到相应的值。这是在tstringlist类内部实现的。
function tupfile.get_filename: olevariant; begin result := extractfilename(ffilename); end; function tupfile.get_filesize: integer; begin result := length(ffiledata); end; function tupfile.get_filedata: olevariant; var i:integer; begin result := vararraycreate( [0,length(ffiledata)], varbyte ); for i := 0 to length(ffiledata)-1 do begin result := byte(ffiledata[i+1]); end; end;


这三个函数分别返回文件的名称、大小、数据。要注意的是,在返回文件数据时,必须进行相应的转换,将delphi中的string类型转换为olevariant类型。
   procedure tupfile.filesaveas(filename: olevariant); var fsout:tfilestream; i:integer; afile:file of byte; begin fsout := tfilestream.create(filename,fmcreate); for i := 1 to length(ffiledata) do begin fsout.write(byte(ffiledata),1) end; fsout.free; end;


这个方法将文件保存到服务器上的磁盘。
编译myobj这个project,得到一个myobj.dll文件。开发工作就此完成。
3 使用asp上传组件
在命令行下,输入“regsvr32 myobj.dll”。弹出一个对话框,告诉你组件已经注册。如果找不到regsvr32.exe这个文件,它在windows\system32或winnt\system32目录下。
将本文开头提到的test.asp文件修改为如下内容:
<%'建立对象 set upfile = server.createobject("myobj.upfile") '获得表单对象 response.write upfile.form("a1")&"<br>" response.write upfile.form("a2")&"<br>" response.write upfile.form("a3")&"<br>" response.write upfile.form("a4")&"<br>" response.write upfile.form("a5")&"<br>" response.write upfile.form("a6")&"<br>" '获得文件大小 response.write "文件字节数:"&upfile.filesize&"<br>" '获得文件类型 response.write "文件类型:"&upfile.filetype&"<br>" '获得文件名,保存文件 upfile.filesaveas(server.mappath("")+upfile.filename) set upfile = nothing %>


再次访问test.htm,提交表单。现在你可以看到相关的返回信息,并且在服务器上test.asp所处
的目录下找到上传的文件。
这个组件只能上传单个文件,但根据同样的原理,一次上传多个文件的功能也是不难实现的。有兴趣的读者可以自行尝试。