上傳|詳解
檔案上傳是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圖片的編碼。(實際的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; safecal