三、CGI編程實例
本節將用VB編寫一個處理主頁客戶留言簿的CGI程序。除了要調用本文前面所介紹的Win32API函數外,程序中還調用了Win32API函數GetTempFileName()來獲得一個唯一的臨時文件名。程序中的函數UrlDecode()用來對客戶端的輸入進行URL譯碼。函數GetCgiValue()則用來分解字符串,根據表單元素的NAME屬性獲取其VALUE值,並調用UrlDecode()函數對其進行URL譯碼。
本程序要求在留言簿文件guests.html中使用一個定位串“<! ENDHEAD >”,將文件的開始部分和具體的客戶留言部分分開。CGI程序將在“<! ENDHEAD >”所在的位置插入客戶新的留言。guests.html應具有如下所示的樣式:
<html> <head><title>DHTML Zone </title></head> <body bgcolor="#FFFFFF" text="#00000" vlink="#990000" link="#333399"> <! ENDHEAD > <!---客戶的留言部分從這開始--> <P>………………………. <!---客戶的留言部分結束於此--> </body></html> 這種樣式將保證最後的留言出現在留言簿的最前面。如果要想使最後的留言出現在留言簿的最後面,則只需將留言簿文件中的定位字符串“<! ENDHEAD >”移到留言簿文件中客戶留言部分和HTML文件結尾部分之間的位置就行了。整個程序的完整代碼如下所示: 注釋:guestbook.bas Declare Function GetStdHandle Lib "kernel32" (ByVal nStdHandle As Long) As Long Declare Function ReadFile Lib "kernel32" (ByVal hFile As Long, lpBuffer As Any,ByVal nNumberOfBytesToRead As Long, lpNumberOfBytesRead As Long, lpOverlapped As Any) As Long Declare Function WriteFile Lib "kernel32" (ByVal hFile As Long,ByVal lpBuffer As String, ByVal nNumberOfBytesToWrite As Long,lpNumberOfBytesWritten As Long, lpOverlapped As Any) As Long Declare Function GetTempFileName Lib "kernel32" Alias "GetTempFileNameA"(ByVal lpszPath As String, ByVal lpPrefixString As String, ByVal wUnique As Long, ByVal lpTempFileName As String) As Long Public Const STD_INPUT_HANDLE = -10& Public Const STD_OUTPUT_HANDLE = -11& Public Const FILE_BEGIN = 0& Public hStdIn As Long 注釋: 標准輸入文件句柄 Public hStdOut As Long 注釋: 標准輸出文件句柄 Public sFormData As String 注釋: 用於存儲沒有經過URL譯碼的用戶輸入數據 Public lContentLength As Long Public CGI_RequestMethod As String Sub Main() Dim CGI_ContentLength As String, CGI_QueryString As String, sBuff As String, chinesetail As String Dim lBytesRead As Long, rc As Long,I As Long Dim sEmail As String, sName As String, sURL As String, sfrom As String, tempstring As String Dim sComment As String, tempFileName As String, guestbook As String 注釋:CGI程序的初始化工作 hStdIn = GetStdHandle(STD_INPUT_HANDLE) hStdOut = GetStdHandle(STD_OUTPUT_HANDLE) CGI_RequestMethod = Environ("REQUEST_METHOD") CGI_QueryString = Environ("QUERY_STRING") CGI_ContentLength = Environ("CONTENT_LENGTH") lContentLength = Val(CGI_ContentLength) sBuff = String(lContentLength, Chr$(0)) OutPut "Content-type: text/html" & vbCrLf 注釋: 輸出MIME類型 OutPut "<FONT SIZE=""+2"">" If CGI_RequestMethod = "POST" Then sBuff = String(lContentLength, Chr$(0)) rc = ReadFile(hStdIn, ByVal sBuff, lContentLength, lBytesRead, ByVal 0&) sFormData = Left$(sBuff, lBytesRead) ElseIf CGI_RequestMethod = "GET" Then sFormData = CGI_QueryString Else OutPut "Unknow Form Method !" End If chinesetail = String(400, " ") 注釋:為了在頁面上正確顯示中文,生成一個空格串以獲取客戶端用戶的輸入 sName = GetCgiValue("name") sEmail = GetCgiValue("email") sURL = GetCgiValue("URL") sfrom = GetCgiValue("from") sComment = GetCgiValue("URL_Comment") 注釋:對客戶端用戶的輸入進行檢查 If Len(sName) = 0 Then OutPut "<P>非常抱歉!您還沒有填寫姓名!" & chinesetail Exit Sub End If If Len(sComment) = 0 Then OutPut "<P>非常抱歉!您還沒有提出建議!" & chinesetail Exit Sub End If 注釋:獲取唯一的臨時文件名和留言簿文件並打開它們 tempFileName = TempFile("c:windowstemp", "gbk") guestbook = "e:netscapeserverdocsguests.html" Open tempFileName For Output As #1 Open guestbook For Input As #2 Do 注釋:本循環體用於將留言簿中字符串"<! ENDHEAD >"前面的內容寫入臨時文件 Line Input #2, tempstring Print #1, tempstring Loop While tempstring <> "<! ENDHEAD >" And Not EOF(2) 注釋:向臨時文件中插入客戶端用戶的留言 Print #1, "<hr>" & vbCrLf Print #1, "<ul>" & vbCrLf Print #1, "<li><b>留言時間</b>:" & Date$ & " " & Time$ & vbCrLf Print #1, "<li><b>姓名: </b>" & sName & vbCrLf If Len(sEmail) <> 0 Then Print #1, "<li><b>E-mail: </b><a href=""mailto:" & sEmail & """ >" & sEmail & "</a>" & vbCrLf End If If Len(sURL) <> 0 Then Print #1, "<li><b>我的主頁: </b> <a href=""" & sURL & """ >" & sURL & "</a>" & vbCrLf End If If Len(sfrom) <> 0 Then Print #1, "<li><b>我來自: </b>" & sfrom & vbCrLf End If Print #1, "<li><b>我的建議: </b>" & vbCrLf Print #1, sComment & vbCrLf Print #1, "</ul>" & vbCrLf Do 注釋:本循環體用於將留言簿剩余的東西寫入留言簿 Line Input #2, tempstring Print #1, tempstring Loop While Not EOF(2) Close #1 Close #2 Kill guestbook 注釋:刪除舊的留言簿 Name tempFileName As guestbook 注釋:將臨時文件改成新的留言簿 OutPut "<P>非常感謝您的留言!" & chinesetail OutPut "<P>歡迎您經常光顧本主頁!" & chinesetail OutPut "</FONT>" End Sub Sub OutPut(s As String) 注釋: 本子程序用於向標准輸出寫信息 Dim lBytesWritten As Long s = s & vbCrLf WriteFile hStdOut, s, Len(s), lBytesWritten, ByVal 0& End Sub Public Function GetCgiValue(cgiName As String) As String 注釋: 本子程序可以獲取表單上某一元素的數據 Dim delim2 As Long 注釋: position of "=" Dim delim1 As Long 注釋: position of "&" Dim n As Integer Dim pointer1 As Long,pointer2 As Long,length As Long,length1 As Long Dim tmpstring1 As String,tmpstring2 As String pointer1 = 1 pointer2 = 1 delim2 = InStr(pointer2, sFormData, "=") pointer2 = delim2 + 1 Do length = delim2 - pointer1 tmpstring1 = Mid(sFormData, pointer1, length) delim1 = InStr(pointer1, sFormData, "&") pointer1 = delim1 + 1 length1 = delim1 - pointer2 If delim1 = 0 Then length1 = lContentLength + 1 - pointer2 If tmpstring1 = cgiName Then tmpstring2 = Mid$(sFormData, pointer2, length1) GetCgiValue = UrlDecode(tmpstring2) Exit Do End If If delim1 = 0 Then Exit Do End If delim2 = InStr(pointer2, sFormData, "=") pointer2 = delim2 + 1 Loop End Function Public Function UrlDecode(ByVal sEncoded As String) As String 注釋: 本函數可以對用戶輸入的數據進行URL解碼 Dim pointer As Long 注釋: sEncoded position pointer Dim pos As Long 注釋: position of InStr target Dim temp As String If sEncoded = "" Then Exit Function pointer = 1 Do 注釋:本循環體用於將"+"轉換成空格 pos = InStr(pointer, sEncoded, "+") If pos = 0 Then Exit Do Mid$(sEncoded, pos, 1) = " " pointer = pos + 1 Loop pointer = 1 Do 注釋:本循環體用於將%XX轉換成字符。對於兩個連續的%XX,如果第一個%XX不是某些特指的Web系統保留字符,將把它們轉換成漢字 pos = InStr(pointer, sEncoded, "%") If pos = 0 Then Exit Do temp = Chr$("&H" & (Mid$(sEncoded, pos + 1, 2))) If Mid(sEncoded, pos + 3, 1) = "%" And (temp <> ":") And (temp <> "/") _ And (temp <> "(") And (temp <> ")") And (temp <> ".") And (temp <> ",") _ And (temp <> ";") And (temp <> "%") Then Mid$(sEncoded, pos, 2) = Chr$("&H" & (Mid$(sEncoded, pos + 1, 2)) _ & (Mid$(sEncoded, pos + 4, 2))) sEncoded = Left$(sEncoded, pos) & Mid$(sEncoded, pos + 6) pointer = pos + 1 Else Mid$(sEncoded, pos, 1) = temp sEncoded = Left$(sEncoded, pos) & Mid$(sEncoded, pos + 3) pointer = pos + 1 End If Loop UrlDecode = sEncoded Exit Function End Function Public Function TempFile(sPath As String, sPrefix As String) As String 注釋:本函數可以獲得一個唯一的臨時文件名 Dim x As Long,rc As Long TempFile = String(127, Chr$(0)) rc = GetTempFileName(sPath, sPrefix, ByVal 0&, TempFile) x = InStr(TempFile, Chr$(0)) If x > 0 Then TempFile = Left$(TempFile, x - 1) End Function CGI程序guestbook.bas所要處理的表單如下所示: <html><head><title>貴賓留言簿</title></head> <body> <h3>貴賓留言簿測試</h3> <form action="/cgi-bin/guest.exe" method="post"> 您的姓名: <input type="text" name="name"><br> 您的Email信箱: <input type="text" name="email"><br> 您的主頁的URL: <input type="text" name="URL"><br> 您的建議:<br> <textarea name="URL_Comment" rows=4 cols=30></textarea><br> 您來自: <input type="text" name="from"><br> <input type="submit" value=" 留言 "> </form> </body></html>
雖然目前已經有很多可以取代CGI且其性能較CGI要高的技術(例如ASP、ISAPI、NSAPI等),但使用它們時需要用到專門的知識和工具,並且利用這些技術所編制的程序只適用於特定的Web服務器或系統平台。考慮到CGI編程具有易用易學性、跨服務器平台特性等優點,因此,CGI程序還將在WWW上占有一席之地。