用Delphi 6開發ASP上傳元件詳解 (轉)

worldblog發表於2007-12-14
用Delphi 6開發ASP上傳元件詳解 (轉)[@more@]

  是開發中經常要用到的功能,但本身和內建的都不支援檔案上傳功能。網上流傳的一些第三方元件雖然能夠解決這個問題,但大多是要收費的,更別說了。本文將詳細剖析WEB檔案上傳的原理,以及一步步指導讀者如何用6開發一個ASP上傳元件。

1 Html檔案分析
  首先我們來看一個html檔案原始碼,檔名是test.htm,功能是提供上傳的介面:




   action="test.asp" method=post>
    







  
  


  這個檔案裡包含了一個名為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 SettingsaaaMy DocumentsMy Pictureszzjh.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 SettingsaaaMy DocumentsMy Pictureszzjh.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 Library,這樣就建立了一個ActiveX庫。將此Library改名為myobj,存檔。選擇File-New-Other-ActiveX-Active Server ,在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;
FFonfo: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,lastp: 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自動生成的,在裝載頁面時發生。在這個方法中,我們完成一些初始化的任務:讀取表單的原始資料,解析表單中的域,並存入相應的屬性中,以備。
  由於Delphi已經對ASP中的物件進行了很好的封裝,所以即使在Delphi環境下,也可以方便地呼叫它們,就象在ASP中一樣,例如Request.TotalBytes。首先將原始表單資料讀入到一個OleViarians型別的tmpvar中,然後透過一個迴圈,將它轉換為Delphi中的string格式,並存放在FContentData中。
  接下來,透過查詢換行符,解析出分隔符的內容和長度。然後在一個迴圈中,用AnalyFormData成員一一解析出每個域。初始化工作就這樣完成了。

  再看AnalyFormData函式的實現:

procedure Tupfile.AnalyFormData(content: string);
var
pos1,pos2:integer;
FormName,FormValue:string;
iile: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[i] := Byte(FFileData[i+1]);
end;
end;

  這三個函式分別返回檔案的名稱、大小、資料。要注意的是,在返回檔案資料時,必須進行相應的轉換,將Delphi中的string型別轉換為OleVariant型別。
  
procedure Tupfile.FileSaveAs(Filename: OleVariant);
var
ut:TFileStream;
begin
fsout := TFileStream.Create(Filename,fmcreate);
try
fsout.Write(Byte(FFileData[1]),Length(FFileData))
finally
fsout.Free;
end;

end;

  這個方法將檔案儲存到上的。

  編譯myobj這個project,得到一個myobj.dll檔案。開發工作就此完成。

3 使用ASP上傳元件
  
  在命令列下,輸入“regsvr32 myobj.dll”。彈出一個對話方塊,告訴你元件已經註冊。如果找不到regsvr32.exe這個檔案,它在system32或winntsystem32目錄下。
  將本文開頭提到的test.asp檔案修改為如下內容:

Set upfile = Server.CreateObject("myobj.upfile")

'獲得表單物件
response.write upfile.form("a1")&"
"
response.write upfile.form("a2")&"
"
response.write upfile.form("a3")&"
"
response.write upfile.form("a4")&"
"
response.write upfile.form("a5")&"
"
response.write upfile.form("a6")&"
"

'獲得檔案大小
response.write "檔案位元組數:"&upfile.filesize&"
"
'獲得檔案型別
response.write "檔案型別:"&upfile.filetype&"
"

'獲得檔名,儲存檔案
upfile.filesaveas(Server.MapPath("")+upfile.filename)

set upfile = nothing
%>

  再次訪問test.htm,提交表單。現在你可以看到相關的返回資訊,並且在伺服器上test.asp所處的目錄下找到上傳的檔案。
  這個元件只能上傳單個檔案,但根據同樣的原理,一次上傳多個檔案的功能也是不難實現的。有興趣的讀者可以自行嘗試。

左輕侯
2002.6.20


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-993216/,如需轉載,請註明出處,否則將追究法律責任。

相關文章