|
|
(11個中途的修訂版本沒有顯示) |
第1行: |
第1行: |
| [[category:MasteringVFP/17]] | | [[category:MasteringVFP/17]] |
| + | [[Category:VFPCGI]] |
| ===VFP與CGI=== | | ===VFP與CGI=== |
| ====簡介==== | | ====簡介==== |
第141行: |
第142行: |
| *[http://ind.ntou.edu.tw/~dada/cgi/ CGI&Perl] | | *[http://ind.ntou.edu.tw/~dada/cgi/ CGI&Perl] |
| | | |
- | ====系列文章==== | + | ====實作過程系列文章==== |
- | =====Day 1=====
| + | *[[VFPCGI Day1|VFPCGI的第一天]] |
- | CGI,亦即 Common Gateway Interface,應該說是所有 web server side 技術的前身。
| + | *[[VFPCGI Day2|VFPCGI的第二天]] |
- | | + | *[[VFPCGI Day3|VFPCGI的第三天]] |
- | 基本的原理是利用標準輸出入與環境變數來作為 web server 與應用程式溝通的介面。
| + | *[[VFPCGI Day4|VFPCGI的第四天]] |
- | | + | *[[VFPCGI_Day5|VFPCGI的第五天]] |
- | VFP 本身並不支援標準輸出入,必須透過 Windows API 才可以。
| + | *[[VFPCGI_Day6|VFPCGI的第六天]] |
- | 所以我們利用 GetStdHandle 取得標準輸出檔案的 Handle,再使用 WriteFile 輸出。
| + | *[[VFPCGI_Day7|VFPCGI的第七天]] |
- | | + | *[[VFPCGI_Day8|VFPCGI的第八天]] |
- | 讓我們來看看第一個 CGI 應用程式:
| + | *[[VFPCGI_Day9|VFPCGI的第九天]] |
- | <pre>
| + | *[[VFPCGI_Day10|VFPCGI的第十天]] |
- | * | + | *[[VFPCGI_Day11|VFPCGI的第十一天]] |
- | * cgi01 | + | *[[VFPCGI_Day12|VFPCGI的第十二天]] |
- | * | + | *[[VFPCGI_Day13|VFPCGI的第十三天]] |
- | DECLARE INTEGER GetStdHandle in Win32API integer nHandleType
| + | *[[VFPCGI_Day14|VFPCGI的第十四天]] |
- | 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)
| + | |
- | </pre>
| + | |
- | | + | |
- | 步驟:
| + | |
- | #請先建立一個新專案,命名為 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
| + | |
- | 於是
| + | |
- | <pre>
| + | |
- | <table>
| + | |
- | <tr>
| + | |
- | <td>
| + | |
- | Hello world
| + | |
- | </td>
| + | |
- | </tr>
| + | |
- | </table>
| + | |
- | </pre>
| + | |
- | 就會是一個格子。
| + | |
- | | + | |
- | 把昨天的 cgi01.prg 複製成 cgi02.prg,然後將 cgi02.prg 設為主程式。
| + | |
- | 作一點修改,開啟 VFP 附贈的 Sample 資料庫,並且利用 TRY ... ENDTRY 來幫助我們除錯~
| + | |
- | <pre>
| + | |
- | * | + | |
- | * 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)
| + | |
- | </pre>
| + | |
- | | + | |
- | 編譯之後,還是跟昨天一樣,丟到 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
| + | |
- | | + | |
- | 添加以下內容:(對了,路徑記得自行修改,\ 都要改為 /)
| + | |
- | <pre>
| + | |
- | #
| + | |
- | # 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>
| + | |
- | </pre>
| + | |
- | | + | |
- | 存檔之後,啟動 Apache (用"服務")。
| + | |
- | | + | |
- | 接著我們要修改一下昨天的程式,將
| + | |
- | <pre>
| + | |
- | lcOutput = "HTTP/1.0 200 OK"+chr(13)+chr(10) +
| + | |
- | "Content-type: text/html"+chr(13)+chr(10) + ;
| + | |
- | CHR(13) + CHR(10)
| + | |
- | </pre>
| + | |
- | 改為
| + | |
- | <pre>
| + | |
- | lcOutput = "Content-type: text/html"+chr(13)+chr(10) + ;
| + | |
- | CHR(13) + CHR(10)
| + | |
- | </pre>
| + | |
- | | + | |
- | 同樣的,進行編譯,接著打開瀏覽器,在網址列輸入:http://localhost/vfpcgi/vfpcgi.exe
| + | |
- | | + | |
- | 就會看到昨天的肉了~(放了一天的肉,可能有點酸了~)
| + | |
- | | + | |
- | =====Day 4=====
| + | |
- | Apache 本身提供了 ab 這個命令可以讓你對自己的 web server 作測試。
| + | |
- | 除了可以用在 apache 身上之外,也可以用在 IIS 上。
| + | |
- | | + | |
- | 使用的說明可以參考:[http://blog.longwin.com.tw/archives/000513.html Apache 壓力測試]
| + | |
- | | + | |
- | 我的測試環境:
| + | |
- | *Windows 2000 Professional | + | |
- | *AMD 1.7G | + | |
- | *RAM 512MB | + | |
- | | + | |
- | 這邊我們統一使用下面指令進行測試,也就是模擬有 5000 個 request 連上 web server。<pre>
| + | |
- | ab -n 5000 http://localhost/vfpcgi/vfpcgi.exe
| + | |
- | </pre>
| + | |
- | | + | |
- | 這邊我們只觀察兩個數值:
| + | |
- | *Time taken for tests: 總共執行花了多久的時間.(以上 1000 次共多久)
| + | |
- | *Requests per second: 每秒平均可以處理多少個 connection.
| + | |
- | *Time per request: 每個 connection 所花費時間。
| + | |
- | | + | |
- | Host在IIS上的結果:<pre>
| + | |
- | 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)
| + | |
- | </pre>
| + | |
- | | + | |
- | Host在Apache上的結果:<pre>
| + | |
- | 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)
| + | |
- | </pre>
| + | |
- | Apache 的結果,明顯較 IIS 差,但我這邊使用的是預設的設定,如果你對 Apache 設定有一定瞭解的話,不妨再去作調整,或許可以得到更好的數字。
| + | |
- | | + | |
- | 以上結果,就提供給各位作參考。
| + | |
- | =====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>
| + | |
- | 試試看結果如何~
| + | |
- | =====Day 6=====
| + | |
- | 那麼,要怎麼接收參數呢?
| + | |
- | Web application 如果不能收參數,那就遜掉了~程式也會難寫很多吧~
| + | |
- | | + | |
- | 接收參數有兩種方式,一種是 GET,一種則是 POST
| + | |
- | 差別在哪裡?
| + | |
- | 使用者看得到的,最大的差別就在於網址列。
| + | |
- | 如果網址列有像是 http://localhost/default.aspx?name1=value1&name2=value2 這種的,就表示是以 GET 方式,預設的存取方式其實也是 GET。
| + | |
- | | + | |
- | 對 CGI 應用程式來說,最大的差別在於讀取資料的方式:如果是 GET,那就取得 QUERY_STRING 這個環境變數的內容;如果是 POST,那麼就讀取 STDIN (標準輸入) 的內容。
| + | |
- | | + | |
- | 那麼,CGI 應用程式又要怎麼知道現在是 GET 還是 POST 呢?
| + | |
- | 同樣地,還是透過環境變數,這個環境變數的名字就叫做 REQUEST_METHOD。
| + | |
- | | + | |
- | ok,讓我們根據以上的原則來寫寫程式,請新增一個 cgi04.prg,然後將她設置為 main (set as main)
| + | |
- | <pre>
| + | |
- | *
| + | |
- | * cgi04
| + | |
- | *
| + | |
- | SET PROCEDURE TO cgilib
| + | |
- | | + | |
- | LOCAL oResponse
| + | |
- | oResponse = CREATEOBJECT( "RESPONSE" )
| + | |
- | | + | |
- | LOCAL cRequestMethod
| + | |
- | cRequestMethod = GETENV( "REQUEST_METHOD" )
| + | |
- | | + | |
- | oResponse.Write( "<p>REQUEST_METHOD=" + cRequestMethod + "</p>")
| + | |
- | | + | |
- | DO CASE
| + | |
- | CASE cRequestMethod == "GET"
| + | |
- | oResponse.Write( "<p>QUERY_STRING=" + GETENV( "QUERY_STRING" ) + "</p>" )
| + | |
- | CASE cRequestMethod == "POST"
| + | |
- | * not implemented.
| + | |
- | ENDCASE
| + | |
- | </pre>
| + | |
- | | + | |
- | 同樣,編譯好,丟到正確的位置之後(如果你還不知道,請參考前面),輸入:
| + | |
- | <pre>
| + | |
- | http://localhost/vfpcgi/vfpcgi.exe?name1=value1&name2=value2
| + | |
- | </pre>
| + | |
- | | + | |
- | 就可以看到結果了。
| + | |
- | 這邊只演示了 GET 的部份,明天再來搞 POST 的部份...
| + | |
- | =====Day 7=====
| + | |
- | 上次還沒實做關於 POST 的部份,所以這次延續上次的例子,並且做了一些擴充,這樣可以讓你很清楚怎麼取 GET 與 POST 來的資料。
| + | |
- | | + | |
- | 這裡我添加了一個 form,這是用來放表單資料的,再按下 submit 之後,你會在表單的下方看到讀到的結果。這邊也利用了 text...endtext 來作為一個 template 產生器,你可以看到 text...endtext 可以很方便的同時寫 html,並且在裡面嵌入變數(用<<與>>把變數括起來)。
| + | |
- | | + | |
- | <pre>
| + | |
- | *
| + | |
- | * cgi05
| + | |
- | *
| + | |
- | | + | |
- | SET PROCEDURE TO cgilib
| + | |
- | DECLARE INTEGER GetStdHandle in Win32API integer nHandleType
| + | |
- | declare integer ReadFile in Win32API integer hFile, string @ cBuffer,;
| + | |
- | integer nBytes, integer @ nBytes2, integer @ nBytes3
| + | |
- | | + | |
- | LOCAL oResponse
| + | |
- | oResponse = CREATEOBJECT( "RESPONSE" )
| + | |
- | | + | |
- | LOCAL cRequestMethod, cInput
| + | |
- | cRequestMethod = UPPER( GETENV( "REQUEST_METHOD" ) )
| + | |
- | | + | |
- | cInput = "<p>REQUEST_METHOD=" + cRequestMethod + "</p>"
| + | |
- | | + | |
- | DO CASE
| + | |
- | CASE cRequestMethod == "GET"
| + | |
- | cInput = cInput + GETENV( "QUERY_STRING" )
| + | |
- | CASE cRequestMethod == "POST"
| + | |
- | &&lenght of input string in STDIN is in this environment variable
| + | |
- | LOCAL lcContentLength, lnContentLength
| + | |
- | LOCAL lnHandle
| + | |
- | LOCAL lcInput
| + | |
- | LOCAL lnOverlappedIO
| + | |
- |
| + | |
- | lcContentLength = GETENV("CONTENT_LENGTH")
| + | |
- | IF LEN( lcContentLength ) > 0 THEN
| + | |
- | lnContentLength=VAL( lcContentLength )
| + | |
- | ELSE
| + | |
- | lnContentLength = 0
| + | |
- | ENDIF
| + | |
- | | + | |
- | IF lnContentLength > 0 THEN
| + | |
- | &&get the input from STDIN
| + | |
- | lnInHandle=GetStdHandle(-10)
| + | |
- | lcInPut=REPLICATE(' ', lnContentLength )
| + | |
- | lnOverlappedIO=0
| + | |
- | ReadFile(lnInHandle, @lcInPut, lnContentLength, @lnContentLength, @lnOverlappedIO)
| + | |
- | ELSE
| + | |
- | lcInput=''
| + | |
- | ENDIF
| + | |
- | cInput = cInput + lcInput
| + | |
- | OTHERWISE
| + | |
- | cInput = ""
| + | |
- | ENDCASE
| + | |
- | | + | |
- | lcHtml = ""
| + | |
- | TEXT TO lcHtml NOSHOW ADDITIVE TEXTMERGE
| + | |
- | <script language="javascript">
| + | |
- | function method_change( form )
| + | |
- | {
| + | |
- | switch ( form.cboMethod.value ) {
| + | |
- | case "GET": form.method = "GET"; break;
| + | |
- | case "POST": form.method = "POST"; break;
| + | |
- | }
| + | |
- | }
| + | |
- | </script>
| + | |
- | <form method="get" action="vfpcgi.exe">
| + | |
- | RequestMethod: <select name="cboMethod" onChange="return method_change(this.form);">
| + | |
- | <option value="GET" <<IIF(cRequestMethod=="GET", "selected", "")>> >GET</option>
| + | |
- | <option value="POST" <<IIF(cRequestMethod=="POST", "selected", "")>> >POST</option>
| + | |
- | </select><br/>
| + | |
- | <input type="text" name="txt" value=""/>
| + | |
- | <select name="cbo">
| + | |
- | <option value="0" selected>0</option>
| + | |
- | <option value="1">1</option>
| + | |
- | <option value="2">2</option>
| + | |
- | </select><br/>
| + | |
- | <input type="submit" value="Submit"/>
| + | |
- | <input type="reset" value="Reset"/>
| + | |
- | </form>
| + | |
- | <pre>
| + | |
- | <<cInput>>
| + | |
- | </pre>
| + | |
- | ENDTEXT
| + | |
- | | + | |
- | oResponse.Write( lcHtml )
| + | |
- | </pre>
| + | |
- | | + | |
- | 同時,也對上次的 Response 類別作一點修正,在上次 Response 類別裡面,用來作為 cache 的 cursor,其資料欄位是 varchar(254),換言之,每次 Write 時,只能塞入 254 個字元,如果超過的話,就完蛋了。這次我把她修正為 M,也就是 Memo 型態。
| + | |
- | | + | |
- | <pre>
| + | |
- | DEFINE CLASS RESPONSE as Custom
| + | |
- | bDirty = .F.
| + | |
- | bHeaderOut = .F.
| + | |
- | ADD OBJECT Headers AS collection
| + | |
- |
| + | |
- | PROCEDURE Init
| + | |
- | CREATE CURSOR outputCache ( outLine M )
| + | |
- | 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>
| + | |
- | | + | |
- | 同樣的,新增 cgi05 以後,將她設置為主程式,重新編譯之後,再丟到對應路徑即可。
| + | |
- | | + | |
- | =====Day 8=====
| + | |
- | | + | |
- | 當你知道規則之後,很快就能寫出一個類似 Request 的物件了~
| + | |
- | 所以我們的 cgilib.prg 又多了一個 Request 類別:
| + | |
- | <pre>
| + | |
- | DEFINE CLASS REQUEST as Custom
| + | |
- | TotalBytes = 0
| + | |
- | RequestMethod = ""
| + | |
- | ADD OBJECT QueryString AS collection
| + | |
- | ADD OBJECT FormField AS collection
| + | |
- | ADD OBJECT Cookies as collection
| + | |
- |
| + | |
- | PROCEDURE INIT
| + | |
- | DECLARE INTEGER GetStdHandle in Win32API integer nHandleType
| + | |
- | declare integer ReadFile in Win32API integer hFile, string @ cBuffer,;
| + | |
- | integer nBytes, integer @ nBytes2, integer @ nBytes3
| + | |
- |
| + | |
- | LOCAL lcMethod
| + | |
- | LOCAL lcQueryString
| + | |
- | LOCAL lcContentLength, lnContentLength
| + | |
- | LOCAL lcInput
| + | |
- | LOCAL lnOverlappedIO
| + | |
- | LOCAL lnInHandle
| + | |
- |
| + | |
- | lcMethod = UPPER( GETENV( "REQUEST_METHOD" ) )
| + | |
- | this.RequestMethod = lcMethod
| + | |
- | lcQueryString = ""
| + | |
- |
| + | |
- | DO CASE
| + | |
- | CASE INLIST( lcMethod , "GET" )
| + | |
- | * get environment variable: QUERY_STRING
| + | |
- | lcQueryString = GETENV( "QUERY_STRING" )
| + | |
- | this.ParseData( lcQueryString, this.QueryString )
| + | |
- | CASE INLIST( lcMethod, "POST" )
| + | |
- | &&lenght of input string in STDIN is in this environment variable
| + | |
- | lcContentLength = GETENV("CONTENT_LENGTH")
| + | |
- | IF LEN( lcContentLength ) > 0 THEN
| + | |
- | lnContentLength=VAL( lcContentLength )
| + | |
- | ELSE
| + | |
- | lnContentLength = 0
| + | |
- | ENDIF
| + | |
- | | + | |
- | IF lnContentLength > 0 THEN
| + | |
- | &&get the input from STDIN
| + | |
- | lnInHandle=GetStdHandle(-10)
| + | |
- | lcInPut=REPLICATE(' ', lnContentLength )
| + | |
- | lnOverlappedIO=0
| + | |
- | ReadFile(lnInHandle, @lcInPut, lnContentLength, @lnContentLength, @lnOverlappedIO)
| + | |
- | ELSE
| + | |
- | lcInput=''
| + | |
- | ENDIF
| + | |
- | | + | |
- | IF LEN( lcInput ) > 0 THEN
| + | |
- | this.ParseData( lcInput, this.FormField )
| + | |
- | ENDIF
| + | |
- | OTHERWISE
| + | |
- | ENDCASE
| + | |
- | ENDPROC
| + | |
- |
| + | |
- | HIDDEN PROCEDURE ParseData
| + | |
- | LPARAMETERS cInput as String , oCollection as Collection
| + | |
- | LOCAL i, nStart, nPos, nEqualPos
| + | |
- | LOCAL lcStr, lcKey, lcValue
| + | |
- |
| + | |
- | * parse it.
| + | |
- | i = 1
| + | |
- | nStart = 1
| + | |
- | nPos=AT( "&", cInput, i )
| + | |
- | IF nPos == 0 THEN
| + | |
- | nPos = LEN( cInput )
| + | |
- | ENDIF
| + | |
- | DO WHILE nPos!=0
| + | |
- | lcStr = SUBSTR( cInput, nStart, nPos-nStart )
| + | |
- | nEqualPos = AT( "=", lcStr )
| + | |
- | IF nEqualPos!=0 THEN
| + | |
- | lcKey = SUBSTR( lcStr, 1, nEqualPos-1 )
| + | |
- | lcValue = SUBSTR( lcStr, nEqualPos+1 )
| + | |
- | oCollection.Add( lcValue, lcKey )
| + | |
- | ENDIF
| + | |
- | i=i+1
| + | |
- | nStart = nPos + 1
| + | |
- | nPos=AT( "&", cInput, i )
| + | |
- | ENDDO
| + | |
- | IF nStart < LEN(cInput) THEN
| + | |
- | nPos = LEN( cInput )
| + | |
- | lcStr = SUBSTR( cInput, nStart )
| + | |
- | nEqualPos = AT( "=", lcStr )
| + | |
- | IF nEqualPos!=0 THEN
| + | |
- | lcKey = SUBSTR( lcStr, 1, nEqualPos-1 )
| + | |
- | lcValue = SUBSTR( lcStr, nEqualPos+1 )
| + | |
- | oCollection.Add( lcValue, lcKey )
| + | |
- | ENDIF
| + | |
- | ENDIF
| + | |
- | ENDPROC
| + | |
- | ENDDEFINE
| + | |
- | </pre>
| + | |
- | | + | |
- | Request 這部份是最容易有問題的地方了~因為安全的漏洞往往來自於此。
| + | |
- | 也正式因為如此,才會有人說,永遠不要相信使用者輸入的資料,無論如何都要作詳細的檢查。
| + | |
- | 早期多半用 C 語言來寫 CGI 的時候,這邊常會犯的錯誤是,沒有預留足夠的空間來放這些輸入的字串,這也導致了 hacker 可以用緩衝區溢位的方式來進行攻擊。
| + | |
- | | + | |
- | 我們這邊做的比較簡單,都自己寫程式去 parse 字串,然後放到 collection 裡面去。
| + | |
- | 依照上個範例的輸出結果,我想你應該知道可以利用 aline() 之類的函數來解,這會更方便些~
| + | |
- | | + | |
- | 這邊我們還沒處理 utf-8 與 % 的一些問題....同時也還沒補上 Request 的使用範例...
| + | |
- | ====Day 9====
| + | |
- | | + | |
- | 有了 Request class,我們把 cgi05 改寫成 cgi06,不用自己再處理,改由 request class 去處理。
| + | |
- | | + | |
- | 少掉前面的一堆處理,看起來是比較好一點了~
| + | |
- | 下次來改寫 Request 來讓她可以處理中文。
| + | |
- | | + | |
- | <pre>
| + | |
- | *
| + | |
- | * cgi06
| + | |
- | *
| + | |
- | | + | |
- | SET PROCEDURE TO cgilib
| + | |
- | | + | |
- | TRY
| + | |
- | LOCAL oResponse, oRequest
| + | |
- | oResponse = CREATEOBJECT( "RESPONSE" )
| + | |
- | oRequest = CREATEOBJECT( "REQUEST" )
| + | |
- | | + | |
- | * 取得目前的 Request Method
| + | |
- | LOCAL cInput
| + | |
- | cInput = "<p>REQUEST_METHOD=" + oRequest.RequestMethod + "</p>"
| + | |
- | | + | |
- | * 因為要去處理的 collection 不一樣
| + | |
- | DO CASE
| + | |
- | CASE oRequest.RequestMethod == "POST"
| + | |
- | oCollection = oRequest.FormField
| + | |
- | OTHERWISE
| + | |
- | oCollection = oRequest.QueryString
| + | |
- | ENDCASE
| + | |
- | cInput = cInput + "<p><ul>"
| + | |
- | FOR i=1 TO oCollection.Count
| + | |
- | cInput = cInput + "<li>" + oCollection.GetKey(i) + "=" + oCollection.Item(i) + "</li>"
| + | |
- | ENDFOR
| + | |
- | cInput = cInput + "</ul></p>"
| + | |
- | | + | |
- | * 輸出成 HTML 囉~
| + | |
- | lcHtml = ""
| + | |
- | TEXT TO lcHtml NOSHOW ADDITIVE TEXTMERGE
| + | |
- | <script language="javascript">
| + | |
- | function method_change( form )
| + | |
- | {
| + | |
- | switch ( form.cboMethod.value ) {
| + | |
- | case "GET": form.method = "GET"; break;
| + | |
- | case "POST": form.method = "POST"; break;
| + | |
- | }
| + | |
- | }
| + | |
- | </script>
| + | |
- | <form method="get" action="vfpcgi.exe">
| + | |
- | RequestMethod: <select name="cboMethod" onChange="return method_change(this.form);">
| + | |
- | <option value="GET" <<IIF(oRequest.RequestMethod=="GET", "selected", "")>> >GET</option>
| + | |
- | <option value="POST" <<IIF(oRequest.RequestMethod=="POST", "selected", "")>> >POST</option>
| + | |
- | </select><br/>
| + | |
- | <input type="text" name="txt" value=""/>
| + | |
- | <select name="cbo">
| + | |
- | <option value="0" selected>0</option>
| + | |
- | <option value="1">1</option>
| + | |
- | <option value="2">2</option>
| + | |
- | </select><br/>
| + | |
- | <input type="submit" value="Submit"/>
| + | |
- | <input type="reset" value="Reset"/>
| + | |
- | </form>
| + | |
- | <<cInput>>
| + | |
- | ENDTEXT
| + | |
- | | + | |
- | oResponse.Write( lcHtml )
| + | |
- | CATCH TO oError
| + | |
- | oResponse.Write( oError.Message )
| + | |
- | FINALLY
| + | |
- | ENDTRY
| + | |
- | </pre>
| + | |