明輝手游網(wǎng)中心:是一個免費提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺!

用Delphi 6開發(fā)ASP上傳組件詳細(xì)說明

[摘要]左輕侯  文件上傳是WEB開發(fā)中經(jīng)常要用到的功能,但ASP本身和內(nèi)置的組件都不支持文件上傳功能。網(wǎng)上流傳的一些第三方組件雖然能夠解決這個問題,但大多是要收費的,更別說Open Source了。本文將...
左輕侯

  文件上傳是WEB開發(fā)中經(jīng)常要用到的功能,但ASP本身和內(nèi)置的組件都不支持文件上傳功能。網(wǎng)上流
傳的一些第三方組件雖然能夠解決這個問題,但大多是要收費的,更別說Open Source了。本文將詳細(xì)
剖析WEB文件上傳的原理,以及一步步指導(dǎo)讀者如何用Delphi6開發(fā)一個ASP上傳組件。

源碼和demo我已經(jīng)發(fā)布在個人主頁上
http://www.wushuang.net


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。用瀏覽器打開這個文件時,
這個域會表現(xiàn)為一個右側(cè)有“瀏覽”字樣的文件輸入框,用戶可以通過它來選擇本地硬盤上的文件。二是
form有一個特殊的屬性:enctype="multipart/form-data"。這個屬性告訴瀏覽器要上傳二進(jìn)制文
件,并進(jìn)行相應(yīng)編碼。
  這種編碼會產(chǎn)生什么樣的表單信息呢?讓我們來看看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 文件類型。
后面是文件本身的數(shù)據(jù)。

  其它各個域的信息也可以以此類推。
  眾所周知,在ASP中,使用request對象,可以訪問用戶提交表單的各個域。因為request對象
會對原始的表單信息進(jìn)行解析,提取出表單中每個域的值。但是,request并不能解析這
"multipart/form-data"格式的表單信息。這就是ASP不能直接支持文件上傳的原因所在。讀者可以
試試,在test.asp中,用request("mefile")這樣的格式,是不能讀取到正確的信息的。
  問題的癥結(jié)已經(jīng)找到,解決的思路也很簡單:用Delphi開發(fā)一個COM組件,接受這種原始表單信息,
將各個域一一提取出來,返回給asp文件。也就是完成request對象沒有完成的功能。

2 用Delphi開發(fā)組件

  Delphi6對開發(fā)ASP組件提供了極好的支持,大大簡化了我們的開發(fā)過程。
  啟動Delphi 6,選擇File-New-Other-ActiveX-ActiveX Library,這樣就建立了一個
ActiveX庫。將此Library改名為myobj,存盤。選擇File-New-Other-ActiveX-Active Server
Object,在CoClassname中填入upfile,確定。這時會跳出一個標(biāo)題為myobj.tlb的對話框,這是
Delphi特有的以可視化方式編輯COM接口的功能,用Delphi開發(fā)過COM的讀者應(yīng)該比較熟悉。
  在myobj下的名為Iupfile的Interface下,添加5個屬性和一個方法。如果不懂得如何操作,
請參見Delphi參考書的相關(guān)部分。按F12可以看到生成的相應(yīng)的myobj_tlb.pas文件,其中的
Iupfile接口應(yīng)該是這個樣子:

  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默認(rèn)生成的,其它的是手動加入的。
  切換到unit1.pas(也是Delphi自動生成的),改名為upfile.pas存盤?梢钥吹酱嬖谝粋
Tupfile類的聲明,它是繼承自TASPObject類和Iupfile接口的。Delphi 6已經(jīng)自動生成了相應(yīng)
的代碼。接下來的任務(wù)就是實現(xiàn)這個接口。
  除了完成Iupfile接口中的屬性和方法之后,還需要補(bǔ)充一些東西,以便完成我們的任務(wù)。最終的
Tupfile類的聲明如下:

  Tupfile = class(TASPObject, Iupfile)
  public
  protected
    procedure OnEndPage; safecall;  //頁面開始
    procedure OnStartPage(const AScriptingContext: IUnknown); safecall;  //頁面
結(jié)束
    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;

  下面我們來一一分析這些成員的具體實現(xiàn)。

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[i]));
  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自動生成的,在裝載頁面時發(fā)生。在這個方法中,我們完
成一些初始化的任務(wù):讀取表單的原始數(shù)據(jù),解析表單中的域,并存入相應(yīng)的屬性中,以備調(diào)用。
  由于Delphi已經(jīng)對ASP中的對象進(jìn)行了很好的封裝,所以即使在Delphi環(huán)境下,也可以方便地調(diào)
用它們,就象在ASP中一樣,例如Request.TotalBytes。首先將原始表單數(shù)據(jù)讀入到一個
OleViarians類型的tmpvar中,然后通過一個循環(huán),將它轉(zhuǎn)換為Delphi中的string格式,并存放
在FContentData中。
  接下來,通過查找換行符,解析出分隔符的內(nèi)容和長度。然后在一個循環(huán)中,用AnalyFormData成
員函數(shù)一一解析出每個域。初始化工作就這樣完成了。

  再看AnalyFormData函數(shù)的實現(xiàn):

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;

  如注釋中所表達(dá)的,AnalyFormData提取原始數(shù)據(jù)中的域。如果是域是文件類型,則將文件類型和
文件數(shù)據(jù)分別放入FFileType和FFileData中。如果是其它類型,則將名稱和值放入一個TStringlist
類型的FFormInfo中。FFormInfo中維護(hù)著除文件類型外的所有域的信息,以“名稱=值”的格式存放。
  
function Tupfile.Get_Form(Formname: OleVariant): OleVariant;
begin
    Result := FFormInfo.Values[Formname];
end;

  這個函數(shù)返回域的值。只需要簡單地調(diào)用FFormInfo的values方法,就可以得到相應(yīng)的值。這是
在Tstringlist類內(nèi)部實現(xiàn)的。

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[i] := Byte(FFileData[i+1]);
  end;
end;

  這三個函數(shù)分別返回文件的名稱、大小、數(shù)據(jù)。要注意的是,在返回文件數(shù)據(jù)時,必須進(jìn)行相應(yīng)的轉(zhuǎn)
換,將Delphi中的string類型轉(zhuǎn)換為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[i]),1)
  end;
  fsout.Free;
end;

  這個方法將文件保存到服務(wù)器上的磁盤。

  編譯myobj這個project,得到一個myobj.dll文件。開發(fā)工作就此完成。

3 使用ASP上傳組件
  
  在命令行下,輸入“regsvr32 myobj.dll”。彈出一個對話框,告訴你組件已經(jīng)注冊。如果找不到
regsvr32.exe這個文件,它在windows\system32或winnt\system32目錄下。
  將本文開頭提到的test.asp文件修改為如下內(nèi)容:

<%'建立對象
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 "文件字節(jié)數(shù):"&upfile.filesize&"<br>"
'獲得文件類型
response.write "文件類型:"&upfile.filetype&"<br>"

'獲得文件名,保存文件
upfile.filesaveas(Server.MapPath("")+upfile.filename)

set upfile = nothing
%>

  再次訪問test.htm,提交表單,F(xiàn)在你可以看到相關(guān)的返回信息,并且在服務(wù)器上test.asp所處
的目錄下找到上傳的文件。
  這個組件只能上傳單個文件,但根據(jù)同樣的原理,一次上傳多個文件的功能也是不難實現(xiàn)的。有興趣
的讀者可以自行嘗試。