MasteringVFP/17/1
出自VFP Wiki
小 (→系列文章) |
小 (→系列文章) |
||
第344行: | 第344行: | ||
以上結果,就提供給各位作參考。 | 以上結果,就提供給各位作參考。 | ||
+ | =====Day 5===== | ||
+ | 第五天~ | ||
+ | |||
+ | 接著,我們就把 WriteFile 包裝一下吧,讓她可以像 ASP 的 Response 一樣這麼用~ | ||
+ | |||
+ | <pre> | ||
+ | * | ||
+ | * CGI03.prg | ||
+ | * | ||
+ | SET PROCEDURE TO cgilib | ||
+ | |||
+ | LOCAL oResponse | ||
+ | oResponse = CREATEOBJECT( "RESPONSE" ) | ||
+ | |||
+ | oResponse.Write( "<p>Hello world!</p>") | ||
+ | </pre> | ||
+ | |||
+ | 所以,Response 類別就如下面,這邊利用了 VFP 的 cursor 來當作 cache,所有的 Write,實際上都是先塞到 cursor 裡面去,等到最後被釋放或是呼叫 Flush 的時候,才利用 scan...endscan 一次把所有資料寫出去。 | ||
+ | |||
+ | <pre> | ||
+ | * | ||
+ | * CGILIB.prg | ||
+ | * | ||
+ | DEFINE CLASS RESPONSE as Custom | ||
+ | bDirty = .F. | ||
+ | bHeaderOut = .F. | ||
+ | ADD OBJECT Headers AS collection | ||
+ | |||
+ | PROCEDURE Init | ||
+ | CREATE CURSOR outputCache ( outLine varchar(254) ) | ||
+ | ENDPROC | ||
+ | |||
+ | PROCEDURE destroy | ||
+ | IF this.bDirty == .T. | ||
+ | this.flush() | ||
+ | ENDIF | ||
+ | USE IN outputCache | ||
+ | ENDPROC | ||
+ | |||
+ | PROCEDURE Write | ||
+ | LPARAMETERS theHtml | ||
+ | LOCAL lcOutput | ||
+ | |||
+ | INSERT INTO outputCache values( theHtml ) | ||
+ | this.bDirty = .t. | ||
+ | RETURN | ||
+ | ENDPROC | ||
+ | |||
+ | HIDDEN PROCEDURE InternalWrite | ||
+ | LPARAMETER lcOutput | ||
+ | |||
+ | DECLARE INTEGER GetStdHandle in Win32API integer nHandleType | ||
+ | declare integer WriteFile in Win32API integer hFile, string @ cBuffer,; | ||
+ | integer nBytes, integer @ nBytes2, integer @ nBytes3 | ||
+ | |||
+ | LOCAL lnOutHandle | ||
+ | LOCAL lnBytesWritten | ||
+ | LOCAL lnOverLappedIO | ||
+ | |||
+ | lnOutHandle=GetStdHandle(-11) | ||
+ | lnBytesWritten=0 | ||
+ | lnOverLappedIO=0 | ||
+ | WriteFile(lnOutHandle, @lcOutput, len(lcOutput), @lnBytesWritten, @lnOverLappedIO) | ||
+ | ENDPROC | ||
+ | |||
+ | PROCEDURE flushHeader | ||
+ | LOCAL lcDefaultOutput | ||
+ | LOCAL lcOutput | ||
+ | LOCAL lcKey | ||
+ | local i | ||
+ | |||
+ | lcDefaultOutput="" | ||
+ | lcOutput = "" | ||
+ | |||
+ | FOR i = 1 TO this.Headers.Count | ||
+ | lcKey = this.Headers.GetKey( i ) | ||
+ | IF !EMPTY( lcKey ) THEN | ||
+ | lcOutput = lcOutput + lcKey + ": " + this.Headers.Item(i) + CHR(13) + CHR(10) | ||
+ | ENDIF | ||
+ | NEXT | ||
+ | |||
+ | IF this.Headers.getKey( "Content-type" ) == 0 THEN | ||
+ | lcOutput = lcOutput + "Content-type: text/html"+chr(13)+chr(10) | ||
+ | ENDIF | ||
+ | |||
+ | lcOutput = lcOutput + chr(13)+chr(10) | ||
+ | this.InternalWrite( lcDefaultOutput + lcOutput ) | ||
+ | this.bHeaderOut = .T. | ||
+ | ENDPROC | ||
+ | |||
+ | PROCEDURE flush | ||
+ | IF this.bDirty == .f. then | ||
+ | RETURN | ||
+ | ENDIF | ||
+ | |||
+ | LOCAL lcAlias | ||
+ | LOCAL lcOutput | ||
+ | |||
+ | IF this.bHeaderOut == .F. THEN | ||
+ | this.flushHeader() | ||
+ | ENDIF | ||
+ | |||
+ | lcAlias = ALIAS() | ||
+ | SELECT outputCache | ||
+ | GO top | ||
+ | SCAN | ||
+ | lcOutput = outputCache.outLine | ||
+ | this.InternalWrite( lcOutput ) | ||
+ | ENDSCAN | ||
+ | SELECT( lcAlias ) | ||
+ | ENDPROC | ||
+ | ENDDEFINE | ||
+ | </pre> | ||
+ | |||
+ | 這邊我也利用 VFP 提供的 collection 來實做了 Response 的 Header。 | ||
+ | VFP 的 collection 跟其他語言的用法不太一樣,前面是所謂的 value,後面才是所謂的 name。 | ||
+ | 於是,你可以試試看,在 oResponse.Write( "<p>Hello world!</p>") 的前面加上 | ||
+ | <pre> | ||
+ | oResponse.Headers.Add( "text/plain", "Content-type" ) | ||
+ | oResponse.Headers.Add( "us-ascii", "charset" ) | ||
+ | </pre> | ||
+ | 試試看結果如何~ |
在2006年10月30日 (一) 14:20所做的修訂版本
目錄 |
VFP與CGI
簡介
在網址上有時候會看到類似 http://www.your_site.com/xxx.exe?xxx=yyy 的網址,那並不是一個常見的 .exe, 而是實做了 CGI 介面的 .exe.
甚麼是 CGI ?? CGI 的全名是 Common Gateway Interface,也就是一種介面。只要你的應用程式實做此一介面,Web server 就可以據此與你的應用程式溝通。
原理
CGI是怎麼與Web Server溝通的呢?HTML 裡面有所謂的 Form,你可以指定 Form 的 method 為 POST 或 GET。POST 與 GET 決定了 CGI 該怎麼去取得資料;如果是 POST,那麼 Web server 會把這些資料放到 STDIN (標準輸入資料流)去,你的 CGI 就應該使用標準輸入函數去讀取這些資料;如果是 GET,Web server 會把這些資料放到名為 QUERYSTRING 的環境變數裡,於是你需要使用取環境變數的函數來取得這些資料。至於輸出的部份,統一都是輸入到 STDOUT (標準輸出資料流)。
之後的所有技術都與 CGI 脫不了關係,都是以 CGI 為基礎發展出來的。所以會了 CGI 之後,後面也會了一半。
事實上,VFP在配合Win32 API 之後,是可以實做出CGI介面的,關鍵的API在於 ReadFile / GetENV / WriteFile 這幾個API。ReadFile 可以讀取 STDIN (標準輸入資料流),GetENV 則可以取得環境變數,於是你已經可以取得 POST 或 GET 的資料。WriteFile 可以輸出到 STDOUT (標準輸出資料流)。
在網路上可以搜索到不少資料:
- 用VB開發標準CGI程序 _ 安全中國http://anqn.com/dev/vb/a/2005-08-30/a52663.shtml
- Google 網上論壇 : microsoft.public.fox.programmer.exchange http://groups.google.com.tw/group/microsoft.public.fox.programmer.exchange/browse_thread/thread/51297d29e4c3e99c/2147429131fd30e5%232147429131fd30e5?sa=X&oi=groupsr&start=0&num=3
第二個連結裡面有 VFP 的 sample.
實做CGI以後,你需要考量的是當 Request 過多時的問題。當 request 過多的時候,CGI 的效能並不好。因為 web server 在遇到 request CGI 的時候,會為這個 CGI create 一個 process 起來。當同時遇到 1000, 10000, 100000 ....個 request 的時候,web server 就會去 create 1000、10000、100000 ....個 process, 這會拖垮整個 server 的效能!!
這也是後來為甚麼 script 會大為盛行的原因,因為他們所需要的資源較少,處理以及除錯上也較為方便,不過基礎原理還是一樣的。
簡易範例
* * Main.prg * SET PROCEDURE TO cgilib LOCAL oResponse oResponse = CREATEOBJECT( "RESPONSE" ) oResponse.Headers.Add( "text/plain", "Content-type" ) oResponse.Headers.Add( "us-ascii", "charset" ) oResponse.Write( "<p>Hello world!</p>")
* * cgilib.prg * DEFINE CLASS RESPONSE as Custom bDirty = .F. bHeaderOut = .F. ADD OBJECT Headers AS collection PROCEDURE Init CREATE CURSOR outputCache ( outLine varchar(254) ) ENDPROC PROCEDURE destroy IF this.bDirty == .T. this.flush() ENDIF USE IN outputCache ENDPROC PROCEDURE Write LPARAMETERS theHtml LOCAL lcOutput INSERT INTO outputCache values( theHtml ) this.bDirty = .t. RETURN ENDPROC HIDDEN PROCEDURE InternalWrite LPARAMETER lcOutput DECLARE INTEGER GetStdHandle in Win32API integer nHandleType declare integer WriteFile in Win32API integer hFile, string @ cBuffer,; integer nBytes, integer @ nBytes2, integer @ nBytes3 LOCAL lnOutHandle LOCAL lnBytesWritten LOCAL lnOverLappedIO lnOutHandle=GetStdHandle(-11) lnBytesWritten=0 lnOverLappedIO=0 WriteFile(lnOutHandle, @lcOutput, len(lcOutput), @lnBytesWritten, @lnOverLappedIO) ENDPROC PROCEDURE flushHeader LOCAL lcDefaultOutput LOCAL lcOutput LOCAL lcKey local i lcDefaultOutput="HTTP/1.0 200 OK"+chr(13)+chr(10) lcOutput = "" FOR i = 1 TO this.Headers.Count lcKey = this.Headers.GetKey( i ) IF !EMPTY( lcKey ) THEN lcOutput = lcOutput + lcKey + ": " + this.Headers.Item(i) + CHR(13) + CHR(10) ENDIF NEXT IF this.Headers.getKey( "Content-type" ) == 0 THEN lcOutput = lcOutput + "Content-type: text/html"+chr(13)+chr(10) ENDIF lcOutput = lcOutput + chr(13)+chr(10) this.InternalWrite( lcDefaultOutput + lcOutput ) this.bHeaderOut = .T. ENDPROC PROCEDURE flush IF this.bDirty == .f. then RETURN ENDIF LOCAL lcAlias LOCAL lcOutput IF this.bHeaderOut == .F. THEN this.flushHeader() ENDIF lcAlias = ALIAS() SELECT outputCache GO top SCAN lcOutput = outputCache.outLine ? lcOutput this.InternalWrite( lcOutput ) ENDSCAN SELECT( lcAlias ) ENDPROC ENDDEFINE
參考資料
- CGI - Common Gateway Interface
- The Common Gateway Interface - RFC Project Page
- The Common Gateway Interface
- RFC2396
- CGI&Perl
系列文章
Day 1
CGI,亦即 Common Gateway Interface,應該說是所有 web server side 技術的前身。
基本的原理是利用標準輸出入與環境變數來作為 web server 與應用程式溝通的介面。
VFP 本身並不支援標準輸出入,必須透過 Windows API 才可以。 所以我們利用 GetStdHandle 取得標準輸出檔案的 Handle,再使用 WriteFile 輸出。
讓我們來看看第一個 CGI 應用程式:
* * cgi01 * DECLARE INTEGER GetStdHandle in Win32API integer nHandleType declare integer WriteFile in Win32API integer hFile, string @ cBuffer,; integer nBytes, integer @ nBytes2, integer @ nBytes3 LOCAL lnOutHandle LOCAL lnBytesWritten LOCAL lnOverLappedIO LOCAL lcOutput lnOutHandle=GetStdHandle(-11) lnBytesWritten=0 lnOverLappedIO=0 lcOutput = "HTTP/1.0 200 OK"+chr(13)+chr(10) + ; "Content-type: text/html"+chr(13)+chr(10) + ; CHR(13) + CHR(10) + ; "<p>Hello world</p>" WriteFile(lnOutHandle, @lcOutput, len(lcOutput), @lnBytesWritten, @lnOverLappedIO)
步驟:
- 請先建立一個新專案,命名為 vfpcgi,再新增一個 cgi01.prg,把上面的程式碼貼進去,再編譯成 Windows executable (.exe) 檔案,所以你得到了 vfpcgi.exe
- 在 c:\inetpub\wwwroot 下新增一個目錄,命名為 vfpcgi,將 vfpcgi.exe 複製到這裡
- [控制台][系統管理工具]執行Internet Information Services
- 在左邊你會看到 vfpcgi,滑鼠右鍵,選取內容
- 點選 "建立" 按鈕,左邊原本黯淡的 TextBox 會亮起來,將使用權限改為"指令及執行檔",應用程式保護選擇為"高",按下確定。
- 確定你的 IIS 已經啟動,然後打開你的瀏覽器,在網址列輸入 http://localhost/vfpcgi/vfpcgi.exe,這個時候你應該會看到顯示著 Hello world 的頁面~
Day 2
客人:來點肉吧~ 夥計:就來啦~
所以,放點肉吧~ 如果你會一點簡單的 html 語法的話,那麼你大概已經想到,可以怎麼顯示資料了。如果不太懂,那麼也沒關係,這裡會慢慢告訴你。
html 裡面要想畫表格的話,只要會三個 html tag:table、tr、td <table>與</table>代表的是表格,夾在中間的元素就是 tr 與 td 於是
<table> <tr> <td> Hello world </td> </tr> </table>
就會是一個格子。
把昨天的 cgi01.prg 複製成 cgi02.prg,然後將 cgi02.prg 設為主程式。 作一點修改,開啟 VFP 附贈的 Sample 資料庫,並且利用 TRY ... ENDTRY 來幫助我們除錯~
* * cgi02 * DECLARE INTEGER GetStdHandle in Win32API integer nHandleType declare integer WriteFile in Win32API integer hFile, string @ cBuffer,; integer nBytes, integer @ nBytes2, integer @ nBytes3 LOCAL lnOutHandle LOCAL lnBytesWritten LOCAL lnOverLappedIO LOCAL lcOutput LOCAL lnCount lnOutHandle=GetStdHandle(-11) lnBytesWritten=0 lnOverLappedIO=0 lcOutput = "HTTP/1.0 200 OK"+chr(13)+chr(10) + ; "Content-type: text/html"+chr(13)+chr(10) + ; CHR(13) + CHR(10) SET EXCLUSIVE OFF SET TALK OFF TRY OPEN DATABASE HOME(1) + "\Samples\Data\testdata" USE customer GO top lnCount = 0 lcOutput = lcOutput + "<table border='1'>" SCAN lcOutput = lcOutput + "<tr>" lcOutput = lcOutput + "<td>" lcOutput = lcOutput + customer.contact lcOutput = lcOutput + "</td>" lcOutput = lcOutput + "<td>" lcOutput = lcOutput + customer.company lcOutput = lcOutput + "</td>" lcOutput = lcOutput + "</tr>" lnCount = lnCount + 1 IF lnCount > 50 THEN && 只顯示 50 筆 EXIT ENDIF ENDSCAN lcOutput = lcOutput + "</table>" CATCH TO oErr lcOutput = lcOutput + "<p>" lcOutput = lcOutput + "Error: " + oErr.Message lcOutput = lcOutput + "</p>" FINALLY CLOSE DATABASES ALL ENDTRY WriteFile(lnOutHandle, @lcOutput, len(lcOutput), @lnBytesWritten, @lnOverLappedIO)
編譯之後,還是跟昨天一樣,丟到 c:\inetpub\wwwroot\vfpcgi 下面 記得,確定 IIS 已經啟動之後,打開瀏覽器,在網址列輸入 http://localhost/vfpcgi/vfpcgi.exe
Yes~你已經看到今天的肉了。
Day 3
因為 CGI 是 Web server 共同 follow 的標準,因此當然也適用於 Apache。
我的環境:Windows 2000 + Apache 2.2.3
在安裝 Apache 之後,修改 httpd.conf (開始 > 程式集 > Apache HTTP Server 2.2.3 > Configure Apache Server > Edit the Apache httpd.conf Configuration File
添加以下內容:(對了,路徑記得自行修改,\ 都要改為 /)
# # VFPCGI # Alias /vfpcgi/ "d:/vfpces/vfpcgi/" <Directory "d:/vfpces/vfpcgi"> AllowOverride None Options ExecCGI Order allow,deny Allow from all AddHandler cgi-script exe </Directory>
存檔之後,啟動 Apache (用"服務")。
接著我們要修改一下昨天的程式,將
lcOutput = "HTTP/1.0 200 OK"+chr(13)+chr(10) + "Content-type: text/html"+chr(13)+chr(10) + ; CHR(13) + CHR(10)
改為
lcOutput = "Content-type: text/html"+chr(13)+chr(10) + ; CHR(13) + CHR(10)
同樣的,進行編譯,接著打開瀏覽器,在網址列輸入:http://localhost/vfpcgi/vfpcgi.exe
就會看到昨天的肉了~(放了一天的肉,可能有點酸了~)
Day 4
Apache 本身提供了 ab 這個命令可以讓你對自己的 web server 作測試。 除了可以用在 apache 身上之外,也可以用在 IIS 上。
使用的說明可以參考:Apache 壓力測試
我的測試環境:
- Windows 2000 Professional
- AMD 1.7G
- RAM 512MB
ab -n 5000 http://localhost/vfpcgi/vfpcgi.exe
這邊我們只觀察兩個數值:
- Time taken for tests: 總共執行花了多久的時間.(以上 1000 次共多久)
- Requests per second: 每秒平均可以處理多少個 connection.
- Time per request: 每個 connection 所花費時間。
Time taken for tests: 600.93750 seconds Requests per second: 8.33 [#/sec] (mean) Time per request: 120.019 [ms] (mean) Time per request: 120.019 [ms] (mean, across all concurrent requests)Host在Apache上的結果:
Time taken for tests: 754.250000 seconds Requests per second: 6.63 [#/sec] (mean) Time per request: 150.850 [ms] (mean) Time per request: 150.850 [ms] (mean, across all concurrent requests)
Apache 的結果,明顯較 IIS 差,但我這邊使用的是預設的設定,如果你對 Apache 設定有一定瞭解的話,不妨再去作調整,或許可以得到更好的數字。
以上結果,就提供給各位作參考。
Day 5
第五天~
接著,我們就把 WriteFile 包裝一下吧,讓她可以像 ASP 的 Response 一樣這麼用~
* * CGI03.prg * SET PROCEDURE TO cgilib LOCAL oResponse oResponse = CREATEOBJECT( "RESPONSE" ) oResponse.Write( "<p>Hello world!</p>")
所以,Response 類別就如下面,這邊利用了 VFP 的 cursor 來當作 cache,所有的 Write,實際上都是先塞到 cursor 裡面去,等到最後被釋放或是呼叫 Flush 的時候,才利用 scan...endscan 一次把所有資料寫出去。
* * CGILIB.prg * DEFINE CLASS RESPONSE as Custom bDirty = .F. bHeaderOut = .F. ADD OBJECT Headers AS collection PROCEDURE Init CREATE CURSOR outputCache ( outLine varchar(254) ) ENDPROC PROCEDURE destroy IF this.bDirty == .T. this.flush() ENDIF USE IN outputCache ENDPROC PROCEDURE Write LPARAMETERS theHtml LOCAL lcOutput INSERT INTO outputCache values( theHtml ) this.bDirty = .t. RETURN ENDPROC HIDDEN PROCEDURE InternalWrite LPARAMETER lcOutput DECLARE INTEGER GetStdHandle in Win32API integer nHandleType declare integer WriteFile in Win32API integer hFile, string @ cBuffer,; integer nBytes, integer @ nBytes2, integer @ nBytes3 LOCAL lnOutHandle LOCAL lnBytesWritten LOCAL lnOverLappedIO lnOutHandle=GetStdHandle(-11) lnBytesWritten=0 lnOverLappedIO=0 WriteFile(lnOutHandle, @lcOutput, len(lcOutput), @lnBytesWritten, @lnOverLappedIO) ENDPROC PROCEDURE flushHeader LOCAL lcDefaultOutput LOCAL lcOutput LOCAL lcKey local i lcDefaultOutput="" lcOutput = "" FOR i = 1 TO this.Headers.Count lcKey = this.Headers.GetKey( i ) IF !EMPTY( lcKey ) THEN lcOutput = lcOutput + lcKey + ": " + this.Headers.Item(i) + CHR(13) + CHR(10) ENDIF NEXT IF this.Headers.getKey( "Content-type" ) == 0 THEN lcOutput = lcOutput + "Content-type: text/html"+chr(13)+chr(10) ENDIF lcOutput = lcOutput + chr(13)+chr(10) this.InternalWrite( lcDefaultOutput + lcOutput ) this.bHeaderOut = .T. ENDPROC PROCEDURE flush IF this.bDirty == .f. then RETURN ENDIF LOCAL lcAlias LOCAL lcOutput IF this.bHeaderOut == .F. THEN this.flushHeader() ENDIF lcAlias = ALIAS() SELECT outputCache GO top SCAN lcOutput = outputCache.outLine this.InternalWrite( lcOutput ) ENDSCAN SELECT( lcAlias ) ENDPROC ENDDEFINE
這邊我也利用 VFP 提供的 collection 來實做了 Response 的 Header。 VFP 的 collection 跟其他語言的用法不太一樣,前面是所謂的 value,後面才是所謂的 name。 於是,你可以試試看,在 oResponse.Write( "<p>Hello world!</p>") 的前面加上
oResponse.Headers.Add( "text/plain", "Content-type" ) oResponse.Headers.Add( "us-ascii", "charset" )
試試看結果如何~