Adobe AIR for FTP communication

摘要:Adobe AIR for FTP communication

 

根據 flexonrails.net 這個網站中的 Simple FTP app using Flex and Rails 這一篇文章,我相信一些撰寫Flex或是AIR的人一定會讀過,
因為這篇文章demo了一個簡單的ftp client的設計,讓我們可以了解到透過 ActionScript 3.0中的 flash.net.Socket 如何來連接 ftp並且上傳
或下載檔案。FTP的運作流程與指令碼,回想起來已經有好一陣子沒有自己動手寫過了,最後一次操作它,好像是在我使用Unix的時候,
現在都直接使用CuteFTP來操作了。因此,我忘記自己也有架設FTP,一些FTP Server軟體的管理介面都有指令碼的log或是追蹤檔,
因此如果有意思了解一下的人,自己可以找一下,如果你本身沒有架設FTP的話,在http://maliboo.pl/projects/FlexFTP/這個souce code裡
Commands.asResponses.as這二個Class,清楚地說明了Command要下些什麼,與他們相對應的Responses,可以幫助大家快速了解。
 
我在這邊就簡單介紹一下,如何透過http://maliboo.pl/projects/FlexFTP/這個souce code 來下載檔案,如果你從http://maliboo.pl/projects/FlexFTP/
直接下載檔案,沒有辦法直接透過Flex Builder編譯的話,記得先把FlexFTP-app.xml修改成支援AIR SDK 1.5的內容,並且把FlexFTP.mxml中
mx:ApolloApplication修改成mx:WindowedApplication,這樣就可以編譯了。
 
在介紹下載檔案之前先跟大家說執行該功能可能會遇到沒有辦法成功下載的錯誤,主要是因為在DownloadInv.as中的responseHandler
這個function中,在處理case Responses.FILE_STATUS_OK時會發生錯誤,它主要是為了取得你下載檔案的SIZE,不過程式裡沒有下SIZE指
令,而是直接下了RETR,而RETR的回傳值是150 File status okay; about to open data connection.因此就沒有變法編譯,大家修改一下就ok了。
透過FTP下載檔案,通常我們會經過幾個重要的步驟如下解釋:

	
1: - (not logged in) (192.192.192.1) ! New user connected from 192.192.192.1, sending welcome message...
2: - (not logged in) (192.192.192.1) > USER GUEST 3: - (not logged in) (192.192.192.1) < 331 User name received, need password. 4: - (not logged in) (192.192.192.1) > PASS ***** 5: - guest (192.192.192.1) ! Successfully logged in. 6: - guest (192.192.192.1) < 230 User logged in, proceed. 7: - guest (192.192.192.1) > PASV 8: - guest (192.192.192.1) < 227 Entering Passive Mode (192,192,192,103,8,14). 9: - guest (192.192.192.1) > LIST 10: - guest (192.192.192.1) < 150 Opening connection for /bin/ls. 11: - guest (192.192.192.1) < 226 Closing data connection. Efficiency: 0.00 KiB/s (Bytes out: 70, Time: 00:00:00). 12: - guest (192.192.192.1) > CWD /temp 13: - guest (192.192.192.1) < 250 CWD Command successful. 14: - guest (192.192.192.1) > PWD 15: - guest (192.192.192.1) < 257 "/temp/" is working directory. 16: - guest (192.192.192.1) > PASV 17: - guest (192.192.192.1) < 227 Entering Passive Mode (192,192,192,103,8,15). 18: - guest (192.192.192.1) > LIST 19: - guest (192.192.192.1) < 150 Opening connection for /bin/ls. 20: - guest (192.192.192.1) < 226 Closing data connection. Efficiency: 0.00 KiB/s (Bytes out: 80, Time: 00:00:00). 21: - guest (192.192.192.1) > TYPE I 22: - guest (192.192.192.1) < 200 Ok 23: - guest (192.192.192.1) > PASV 24: - guest (192.192.192.1) < 227 Entering Passive Mode (192,192,192,103,8,16). 25: - guest (192.192.192.1) > RETR /temp//Document.rar 26: - guest (192.192.192.1) < 150 File status okay; about to open data connection. 27: - guest (192.192.192.1) < 226 Closing data connection. Efficiency: 1.66 MiB/s (Bytes out: 271167, Time: 00:00:00). 28: - guest (192.192.192.1) ! Disconnecte
從上表我們可以規納出幾個重要的指令:USER、PASS、PASV、LIST、CWD、PWD、TYPE I、RETR、SIZE。這些指令,大致如下之解釋:
 
USER:登入FTP的帳號,例如: USER guest\n,因為一個指令需要一個換行當結束字元。
PASS:登入FTP帳號的密碼:例如:PASS 123\n。
PASV:被動模式 ( Passive mode )。主要是FTP Client來告訴FTP Server採用被動的溝通方式。這一段的回傳值:
              227 Entering Passive Mode (59,37,124,43,158,251),其中前四個數字是 FTP Server 的 IP 位址:59.37.124.43,
     後兩個數字是FTP Server 接受連線的 Port 埠號,埠號的計算方式是 (第五個數字 * 256 + 第六個數字),以此範
     例來說,FTP Server 可接受的連線埠號是 158 * 256 + 251 = 40,699。(來源:解說 FTP Protocol的運作原理)。
LIST:列出該目錄下所有檔案與子目錄。
CWD:進入目錄或退出目錄。
PWD:列出目前目錄。
 
上面幾個重點的指定將會在下面我所舉的例子中使用到。接下來我要介紹的部分是如何簡單做出一個連接上FTP下載檔案的程式。
我的DEMO CODE只是測試指定的檔案,如果覺得不夠彈性的話,各位也可以自行編修。OK,以下我就解釋一下我範例程式的假設:
 
1. 我架設了一個127.0.0.1的ftp,提port: 21來當作FTP Client與FTP Server溝通控制指令的管道。
2. ftp中提供一個帳號與密碼均為:guest的使用者。
3. 指定該guest可以下載的路徑為:~temp/,指定下載檔案為:Document.rar。
4. FTP Client下載之後存放的路徑為桌面。
 
以下我將放上程式碼,解釋主要處理FTP的部分,大家只要記得以下幾個FTP運作的重點,將可以快速的了解我的程式範例:
 
A. FTP的處理本來就是提供二個PORT,一個處理COMMAND,一個負責接收DATA,因此,在AIR你就必需開啟二個Socket
  去負責處理這二個Port。
B. 現在很多免費的FTP Server軟體,它們採用FTP溝通的方式大部分以被動的方式為主,造成必需透過一個Socket固定接收
  程式所下的Command,另一個Socket的連接,將會依照FTP Server的Response加以解譯。 要特別注意以下幾個重要的訊息:
  ‧227 Entering Passive Mode (h1,h2,h3,h4,p1,p2).
  ‧250 Requested file action okay, completed.
private function onControlHandler(event:ProgressEvent):void
{
    var evt:ftpEvents=null;
    if (controlSocket.bytesAvailable!=-1)
    {
        var result:String=controlSocket.readUTFBytes(controlSocket.bytesAvailable);
        result=result.replace(String.fromCharCode(13,10),"");
        var response:String=result.substring(0,3);
        switch (response)
        {
            case "220": //Service ready for new user.
                SendFTPCmd("USER "+this.userid+"\n");
                break;
            case "331": //User name okay, need password.
                SendFTPCmd("PASS "+this.password+"\n");
                break;
            case "230": //User logged in, proceed. Logged out if appropriate.
                SendFTPCmd("CWD "+this.filePath+"\n");
                break; 
            case "250": //Requested file action okay, completed.
                SendFTPCmd("SIZE "+this.fileName+"\n");
                break;
            case "213": //File status. get file size
                bytesTotal=int(result.substring(4,result.length));
                SendFTPCmd("TYPE I\n");
                break;
            case "200": //Command okay.
                SendFTPCmd("PASV\n");
                break;
            case "227": //Entering Passive Mode (h1,h2,h3,h4,p1,p2).
                descomposeDataConnection(result.substring(4,result.length));
                tempFile=File.documentsDirectory.resolvePath(this.tempPath+this.fileName);
                fileStream=new FileStream();
                fileStream.open(this.tempFile, FileMode.WRITE);
                SendFTPCmd("RETR "+this.fileName+"\n");
                break;
            case "226": //Closing data connection. Requested file action successful (for example, file transfer or file abort).
                fileStream.close();
                closeSockets();
                evt=new ftpEvents(ftpEvents.DOWNLOAD);
                dispatchEvent(evt);
                break;
            case "150": //File status okay; about to open data connection.
                break;    
            case "426": //Connection closed; transfer aborted.
                rollback();
                evt=new ftpEvents(ftpEvents.ROLLBACK);
                dispatchEvent(evt);
                break;
            case "500": //Syntax error, command unrecognized. This may include errors such as command line too long.
                rollback();
                evt=new ftpEvents(ftpEvents.ROLLBACK);
                dispatchEvent(evt);
                break;
        }
    }
}        
在使用Socket的過程中,如果遇到如下之錯誤訊息:「Error #2044: Unhandled securityError:. text=Error #2048: Security sandbox violation: app:/ftpSocket.swf cannot load data from 127.0.0.1:2076.」
可以參考此網址之討論內容 http://www.nabble.com/SecurityError--2048-20-seconds-after-Socket-closure.-td19777703.html,其實並不是程式
上的錯誤,看此網頁的討論再加上http://www.51ajax.net/?p=121這一篇的討論,感覺是Flex SDK 3.2的問題,不過不管怎麼樣,檔案確定
是可以正常下載,這個問題也已經有人回報給Adobe Bug Report,所以不用太擔心。
以上是本篇的說明,希望對大家有幫助。
 
程式範例下載:
原始程式範例下載:
 
References:
flexonrails.net:http://flexonrails.net/
Simple FTP app using Flex and Rails:http://flexonrails.net/?p=9.