【摘要】資源傳輸是基于Web的E-learning平臺的一項必備功能。在對現有資源傳輸解決方案對比分析的基礎上,提出了一套基于CGI方式實現資源傳輸的解決方案,其設計思路與實現過程對網絡學習平臺、資源庫管理系統等的設計與開發,具有很好的參考價值。
【關鍵詞】E-learning;資源傳輸;Perl;CGI;進度條
【中圖分類號】G434 【文獻標識碼】B 【論文編號】1009—8097 (2008) 04—0090—05
引言
清華大學網絡學堂是清華大學開發的一套基于Web的E-learning網絡教育支持系統。作為一個先進的網絡教育基礎服務支撐平臺,為整個系統的集成提供公共的平臺與工具,包括用戶檔案管理、目錄服務、數據交換服務、資源共享等功能。該系統自1999年開始在清華大學運行以來,為清華大學課堂教育的提供了有力的支持。并且于2004年7月通過國家CELTS標準認證。同時該系統適用于其它各類遠程教育系統的應用,目前已經在全國多所各類高校得到實際運用。
在網絡學堂開發過程中,遇到了文件資料或圖片資料客戶端上載到服務器以便資源共享的問題,而這個問題也是基本上所以的E-learning平臺都會遇到的問題。通常的做法是利用FTP工具將資料上傳到服務器,然后由管理員整理發布。這就要求有專人維護,不符合一般的需求,最好的做法是讓想發布信息的用戶自己上傳文件資料,然后作為一條相關的記錄存放到數據庫中,這樣其他用戶和系統可以通過簡單的查詢即可獲得所需信息。同時,網絡學堂是一個基于Web方式的E-learning平臺系統,用戶量非常大,用戶群也種類繁多,這也決定了不可能要求用戶都安裝相應的FTP軟件來上傳需要共享的資源,而且,這樣上傳到服務器的文件也比較混亂,不便于管理員的管理和維護,以上這幾點就要求在Web方式下實現資源的上載。
同樣的原因,資源的下載也需要在Web下實現,雖然當前資源下載很方便實現,但大部分卻有一個普遍存在的問題:當用戶點擊下載的時候,對于很多例如doc、ppt等后綴名的文件會在用戶的系統中直接打開,而這時候用戶往往并不希望打開而是希望先下載到硬盤的某個地方再做其他操作。這種直接打開的方式會讓用戶從下載頁面突然跳轉到應用程序界面,同時由于直接打開的方式中,系統是先將文件下載到臨時文件夾,再由相應程序打開,這對用戶來講是一個很長的時間,也會迫使用戶從當前頁面離開,這些都大大的降低了用戶操作的舒適度。所以新版的網絡學堂就要求實現讓所有后綴名的文件在客戶端都不直接打開,而是提示用戶自己選擇下載或打開。
一 WEB下實現技術
1 Perl
Perl 語言融合了許多語言的特性。它主要由C 語言、UNIX shell 等至少十數種其他的工具和語言演化而來。加上Perl 對文字極強的處理、變換能力,以及良好的移植性,所以,Perl 是CGI 編程語言的首選,適合于各種層次的編程人員進行軟件開發。[1]
2 ASP
由于ASP具有靈活的啟動自定義服務器控制的處理機制,因此其功能非常強大,使得目前不少基于Web的應用程序開發均采用ASP方式。此外,由于ASP 可以調用標準的OLE/ COM 組件,所以也可以用Delphi 等高級編程工具根據自己的要求來定制自己的ASP 文件上載組件,滿足自己的應用系統要求。[2]
3 Java
在基于Java 的Web開發中實現文件上載, 主要使用HTTP 協議的RFC1876 方式, 它的實現主要通過運用Java 的相應組件。如果僅僅在JSP 頁面中實現文件上傳, 可以使用JspSmartUpload組件。如果是在流行的Struts 框架下, 可以使用Struts 提供的FormFile類。文件可以直接上傳到服務器上, 也可以上傳到服務器的數據庫中。
二 設計思路
首先是實現語言的選擇。使用Perl 語言實現WEB 模式下文件上傳,不需要專用組件,就可以輕松實現文件上傳,程序是純腳本寫成,簡單易懂,調試容易,并且Perl 語言的運行效率也較高。而采用ASP+第三方組件的方式進行文件上傳,則存在組件編寫、調試困難的問題。用JAVA語言實現對服務器的壓力比較大,特別是當并發的用戶量很大的時候,往往會使服務器不堪重負而當掉。所以本文采用Perl通過CGI來實現資源上傳和下載功能。
公共網關接口CGI 是溝通Web 服務器與“外部程序”的標準方法,所有的Web 服務器都支持這種方法。在這種方法中,Web 服務器將用戶端提交的數據以標準輸入的方式輸入給CGI 程序。如圖1所示:

1資源上傳
RFC1867規范中建議了一個通過瀏覽器上傳文件的方法,目前的瀏覽器均支持這個方法,在客戶端通過Form對象的設置,在用戶選擇好文件并提交后,服務器端調用一個外部進程來完成處理提交的Multipart/form-data型數據[3]。而在這里,服務器是通過調用一個CGI程序來完成相關操作。用CGI實現的上傳文件的程序流程如圖2所示:

考慮到程序的可移植性,在服務端的設計中加入了一個配置文件(服務器端文件存放目錄、臨時目錄等),服務器文件接收進程在啟動時會自動讀取這個配置文件,這樣,在移植這個組件到其他項目的時候只需要修改這個配置文件的內容就可以了。
在上面流程中的寫數據庫環節的設計中,寫數據庫這個操作并沒有放到CGI程序中去做,而是由CGI程序在接收成功文件后,在把相關文件信息POST給相應的程序,然后在那個程序中進行數據庫的讀寫。例如POST給一個Jsp頁面或servlet。這樣設計的好處是給了程序更大的靈活性,并保證了是在接收文件成功后才進行數據庫操作,避免了如下這個在其他應用程序中很常見的問題:由于接收和寫數據庫不同步的原因,造成文件接收失敗,但數據庫卻寫成功,從而數據庫中出現一個不存在的錯誤項,造成數據不一致錯誤。
同時,考慮到用戶的使用體驗,以及在上傳大文件的時候造成的頁面的“假死”,在上傳時,需要在客戶端顯示一個實時的進度條,而這個進度條需要客戶端喚起服務器端的另外一個實時檢查已上傳文件大小和進度的進程。進度條設計流程如圖3所示:

在用戶點擊上傳文件時,客戶端會與服務器建立連接,服務器此時也會記錄下需要上傳的文件的文件名,大小等信息,并寫入一個臨時的上傳進度描述文件,而客戶端會在當前頁面顯示出上傳進度條。在服務器接收的過程中,會實時的更新進度描述文件的內容,這個文件又會實時的將更新通知客戶端的進度條,從而真正意義上得實現了客戶端的進度條功能。同時,由于服務器數據是實時更新的,所以這個進度條反映的進度信息也是實時和準確的。
2資源下載
資源下載程序流程如下:

下載的流程比較簡單,和其他的下載實現基本一致,由一個download.cgi完成下載的工作。但由于特殊的一個要求,即要求實現讓所有后綴名的文件在客戶端都不直接打開,而是彈出對話框提示用戶選擇下載或打開。所以在實現過程中,需要對文件傳輸中的響應頭Content-type和Content-Disposition做一些特殊設置,從而讓客戶端在接收從服務端傳過來的文件是彈出對話框,而不是對某些后綴名文件直接打開。
三 技術實現
服務器端的配置文件UploadConfig.pm,其中包括對臨時文件存放目錄以及文件目標目錄等的設置。例如:
temp_dir => '/home/services/thnsv2/data/temp', #臨時文件存放目錄
target_dir => '/home/services/thnsv2/data', #上傳文件存放目錄
max_upload_size => 70000000000000000,#最大可上傳文件量Kbyte
1資源上傳
(1)服務端
Upload.cgi負責在服務端接收上傳的文件。它先通過讀入前面所描述的配置文件UploadConfig.pm來獲得服務器端文件存放目錄、臨時目錄等,再根據獲得的配置信息建立例如臨時子目錄等上傳需要的環境目錄。由于Perl 語言是面向對象編程語言,在讀取上傳文件內容前,只需建立CGI 類的對象就可以取得上傳文件的相關信息,如文件名。接下來就是接收文件,在接收文件的同時將已經接收的文件的相關信息(以上傳量、速度等)寫入另外一個描述文件,而此文件用于提供客戶端顯示進度條時需要的數據。此外還需要子過程用于將接收的文件從臨時文件夾移動到存儲文件夾。在接收完后再根據客戶端傳過來的一個標志(url_post)判斷是否將文件信息POST到一個url,在那個url中將進行數據庫的操作。在執行完以上操作后還需要執行清除臨時子目錄等清理工作。Upload.cg具體源代碼如下(由于源碼較長,這里只列出較為重要的部分):
……
use UploadConfig;
mkdir $temp_dir;
chmod $mode,$temp_dir;
if(-e $target_dir){
}else{
mkdir $target_dir;
chmod $mode,$target_dir;
}
…….
binmode $file;
binmode OUTPUT;
while ( read( $file, $buffer, BUFFER_SIZE ) ) {
print OUTPUT $buffer;
}
……
sub SaveFile2{
my ($temp,$dir,$fname) = @_;
rename($temp,\"$dir/$fname\");
}
if($url_post)
{
my @har;
map { push @har, { name=>$_, value=>param($_) } } grep { $_=~/^post/ } param();
print\"<HTML><BODY onLoad='document.F1.submit();'><Form name='F1' action='$url_post' method='POST'>\";
print\"<textarea name='$_->{name}' style='display: none;'>$_->{value}</textarea>\" for @har;
print\"</Form></BODY></HTML>\";
exit;
}
DelData($temp_dir);
DeleteOldTempFiles;
……
Upload_status.cgi用于實時記錄上傳的進度,其中包括已經上傳量、文件大小、速度等,并根據速度和文件大小計算出剩余時間等關鍵信息,然后根據計算結果實時地更新客戶端顯示進度條。部分重要代碼如下:
……
while( -e $flength_file $init_flenght_size == -s $flength_file )
{
$curr_time = time;
$size = UpSize;
$percent = int(100*$size/$total);
if ($old_percent != $percent || $old_time != $curr_time)
{
$old_percent = $percent;
$old_time=$curr_time;
$time = $curr_time-$ftime;
$size = int($size/1024);
$speed = $time ? int($size/$time) : 0;
$time_left = $speed ? int( ($totalKB-$size)/$speed ) : 0;
……
#以上是對一些進度值的計算
print\"<Script>SP($size,$time,$speed,$files_uploaded,$time_left);</Script>\";
#更新客戶端的進度條顯示
}
}
……
(2) 客戶端
客戶端的上傳部分代碼如下:
<form name=\"F1\" enctype=\"multipart/form-data\" action=\" /cgi-bin /upload.cgi?sid=\" method=\"post\" onSubmit=\"return StartUpload();\" target=\"upload\">
<input name=\"file1x\" type=\"file\" onChange=\"checkExt(this.value)\">
</form>
通過HTTP上傳文件,在<Form>標記中的enctype 屬性必須采用“multipart/form-data”格式,這樣才能保證文件原理的內容傳輸時不作任何編碼處理,以二進制流的方式上傳到服務器。并且該屬性一旦設置為\"multipart/form-data\"就創建了一個與傳統結構不同的post緩沖區(復合結構),該緩沖區中的數據存放不同于普通表單元素數據上傳的數據存放格式[4]。
如果在上傳后需要寫數據庫,則在客戶端的form里面將這些項的name的設置為post_*, 并通過<input type=\"hidden\" name=\"url_post\" value=\"/xx.jsp\">來設置post的url,則在xx.jsp里面就可以通過getParameter來獲得這些參數值,如果沒有設置url_post,則服務器端的upload.cgi不post。
此外,客戶端還要在上傳的同時顯示進度條,在這里,通過在這個頁面里面嵌套一個iframe來實現:
<div id=\"div_inline\" style=\"BORDER: ;display: none >
<iframe src=\"about:blank?auth_key=1748501812-978238190-0-179511b5c80702ff3fac58bdc659cbc6\" name=\"transfer\" border=0 xSCROLLING=NO topmargin=0 leftmargin=0 frameborder=0 style=\"width: 320px; border: 1px;\">
</iframe>
</div>
在顯示進度條時,還需要將頁面其他部分置為灰色的不可選狀態,可通過javascript設置一個圖層來實現:
<DIV ID=\"divModal\" STYLE=\"BACKGROUND-COLOR: white; LEFT: 0px; POSITION: absolute; TOP: 0px; Z-INDEX: 3\"onclick=\"window.event.cancelBubble=true; window.event.returnValue=1;\">
</DIV>
在界面配置完后,需要通過javascript設置這個內嵌的iframe的地址,讓它指向upload_status.cgi:
self.transfer.document.location = '/cgi-bin/ooao/upload_status.cgi'
這樣就可以實現有實時進度條的資源上載了。
2 資源下載
(1) 服務端
download.cgi實現:
……
use UploadConfig;
my $target_file = \"$c->{target_dir}/$sid/$u_type/$fname\";
#通過讀取上面的UploadConfig.pm獲得目標文件的基本目錄
print \"Content-type: application/octet-stream\\r\\";
print \"Content-Disposition: attachment; filename=$fname\\r\\\r\\";
……
上面代碼是實現瀏覽器出現提示對話框,而不是直接打開的關鍵。文件下載時,可以把文件以二進制的方式,輸出到客戶端(瀏覽器),把content-type設為application/octet-stream就會自動出下載窗口。
(2) 客戶端
客戶端不需要做其他的編碼,只用將下載鏈接的地址指向download.cgi即可。
四 結論
采用上述方案可以將資源傳輸功能完全溶入具體的應用程序中,資源上傳、下載與管理操作方便簡單,可以免去維護環節。方案通過CGI實現,不僅效率較高,而且與應用程序使用語言無關,在其他項目中只要將上述程序重新組織或修改一下配置文件即可使用。另外,進度條的加入也使得傳輸界面更為友好。
目前,該解決方案已經實際應用到了清華大學網絡學堂中。經實踐檢驗,程序的執行速度和執行效率較好的滿足了實際要求。
參考文獻
[1]杜經農,等. Perl5 編程核心技術[M]. 北京:希望電子出版社,2000.
[2]金松.WEB的文件上載技術[J].計算機工程與應用,1999.
[3]王新房,鄧亞玲等. 基于瀏覽器的文件上載技術研究[J].計算機應用,2000.
[4]E.Nebel L.Masinter.Form-based File Upload in HTML[A].RFC 1867[C].Xerox Corporation,1995.