什麼是CGI
CGI 目前由NCSA維護,NCSA定義CGI如下:
CGI(Common Gateway Interface),通用閘道介面,它是一段程式,運行在伺服器上如:HTTP伺服器,提供同用戶端HTML頁面的介面。
網頁瀏覽
為了更好的瞭解CGI是如何工作的,我們可以從在網頁上點擊一個連結或URL的流程:
1、使用你的瀏覽器訪問URL並串連到HTTP 網頁伺服器。
2、Web伺服器接收到請求資訊後會解析URL,並尋找訪問的檔案在伺服器上是否存在,如果存在返迴文件的內容,否則返回錯誤資訊。
3、瀏覽器從伺服器上接收資訊,並顯示接收的檔案或者錯誤資訊。
CGI程式可以是Python指令碼,PERL指令碼,SHELL指令碼,C或者C++程式等。
CGI架構圖
Web伺服器支援及配置
在你進行CGI編程前,確保您的Web伺服器支援CGI及已經配置了CGI的處理常式。
所有的HTTP伺服器執行CGI程式都儲存在一個預先配置的目錄。這個目錄被稱為CGI目錄,並按照慣例,它被命名為/var/www/cgi-bin目錄。
CGI檔案的副檔名為.cgi,python也可以使用.py副檔名。
預設情況下,Linux伺服器配置啟動並執行cgi-bin目錄中為/var/www。
如果你想指定其他運行CGI指令碼的目錄,可以修改httpd.conf設定檔,如下所示:
<Directory "/var/www/cgi-bin">
AllowOverride None
Options ExecCGI
Order allow,deny
Allow from all
</Directory>
<Directory "/var/www/cgi-bin">
Options All
</Directory>
第一個CGI程式
我們使用Python建立第一個CGI程式,檔案名稱為hellp.py,檔案位於/var/www/cgi-bin目錄中,內容如下,修改檔案的許可權為755:
#!/usr/bin/python
print "Content-type:text/html\r\n\r\n"
print '<html>'
print '<head>'
print '<title>Hello Word - First CGI Program</title>'
print '</head>'
print '<body>'
print '<h2>Hello Word! This is my first CGI program</h2>'
print '</body>'
print '</html>'
以上程式在瀏覽器訪問顯示結果如下:
Hello Word! This is my first CGI program
這個的hello.py指令碼是一個簡單的Python指令碼,指令碼第一的輸出內容"Content-type:text/html\r\n\r\n"發送到瀏覽器並告知瀏覽器顯示的內容類型為"text/html"。
HTTP頭部
hello.py檔案內容中的" Content-type:text/html\r\n\r\n"即為HTTP頭部的一部分,它會發送給瀏覽器告訴瀏覽器檔案的內容類型。
HTTP頭部的格式如下:
HTTP 欄位名: 欄位內容
例如
Content-type: text/html\r\n\r\n
以下表格介紹了CGI程式中HTTP頭部經常使用的資訊:
頭
描述
Content-type: 請求的與實體對應的MIME資訊。例如: Content-type:text/html
Expires: Date 響應到期的日期和時間
Location: URL 用來重新導向接收方到非請求URL的位置來完成請求或標識新的資源
Last-modified: Date 請求資源的最後修改時間
Content-length: N 請求的內容長度
Set-Cookie: String 設定Http Cookie
CGI環境變數
所有的CGI程式都接收以下的環境變數,這些變數在CGI程式中發揮了重要的作用:
變數名
描述
CONTENT_TYPE 這個環境變數的值指示所傳遞來的資訊的MIME類型。目前,環境變數CONTENT_TYPE一般都是:application/x-www-form-urlencoded,他表示資料來自於HTML表單。
CONTENT_LENGTH 如果伺服器與CGI程式資訊的傳遞方式是POST,這個環境變數即使從標準輸入STDIN中可以讀到的有效資料的位元組數。這個環境變數在讀取所輸入的資料時必須使用。
HTTP_COOKIE 客戶機內的 COOKIE 內容。
HTTP_USER_AGENT 提供包含了版本數或其他專有資料的客戶瀏覽器資訊。
PATH_INFO 這個環境變數的值表示緊接在CGI程式名之後的其他路徑資訊。它常常作為CGI程式的參數出現。
QUERY_STRING 如果伺服器與CGI程式資訊的傳遞方式是GET,這個環境變數的值即使所傳遞的資訊。這個資訊經跟在CGI程式名的後面,兩者中間用一個問號'?'分隔。
REMOTE_ADDR 這個環境變數的值是發送請求的客戶機的IP地址,例如上面的192.168.1.67。這個值總是存在的。而且它是Web客戶機需要提供給Web伺服器的唯一標識,可以在CGI程式中用它來區分不同的Web客戶機。
REMOTE_HOST 這個環境變數的值包含發送CGI請求的客戶機的主機名稱。如果不支援你想查詢,則無需定義此環境變數。
REQUEST_METHOD 提供指令碼被調用的方法。對於使用 HTTP/1.0 協議的指令碼,僅 GET 和 POST 有意義。
SCRIPT_FILENAME CGI指令碼的完整路徑
SCRIPT_NAME CGI指令碼的的名稱
SERVER_NAME 這是你的 WEB 伺服器的主機名稱、別名或IP地址。
SERVER_SOFTWARE 這個環境變數的值包含了調用CGI程式的HTTP伺服器的名稱和版本號碼。例如,上面的值為Apache/2.2.14(Unix)
以下是一個簡單的CGI指令碼輸出CGI的環境變數:
#!/usr/bin/python
import os
print "Content-type: text/html\r\n\r\n";
print "<font size=+1>Environment</font><\br>";
for param in os.environ.keys():
print "<b>%20s</b>: %s<\br>" % (param, os.environ[param])
GET和POST方法
瀏覽器用戶端通過兩種方法向伺服器傳遞資訊,這兩種方法就是 GET 方法和 POST 方法。
使用GET方法傳輸資料
GET方法發送編碼後的使用者資訊到服務端,資料資訊包含在請求頁面的URL上,以"?"號分割, 如下所示:
http://www.test.com/cgi-bin/hello.py?key1=value1&key2=value2
有關 GET 請求的其他一些注釋:
GET 請求可被緩衝
GET 請求保留在瀏覽器記錄中
GET 請求可被收藏為書籤
GET 請求不應在處理敏感性資料時使用
GET 請求有長度限制
GET 請求只應當用於取回資料
簡單的url執行個體:GET方法
以下是一個簡單的URL,使用GET方法向hello_get.py程式發送兩個參數:
/cgi-bin/hello_get.py?first_name=ZARA&last_name=ALI
以下為hello_get.py檔案的代碼:
#!/usr/bin/python
# CGI處理模組
import cgi, cgitb
# 建立 FieldStorage 的執行個體化
form = cgi.FieldStorage()
# 擷取資料
first_name = form.getvalue('first_name')
last_name = form.getvalue('last_name')
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>"
print "<title>Hello - Second CGI Program</title>"
print "</head>"
print "<body>"
print "<h2>Hello %s %s</h2>" % (first_name, last_name)
print "</body>"
print "</html>"
瀏覽器請求輸出結果:
Hello ZARA ALI
簡單的表單一實例:GET方法
以下是一個通過HTML的表單使用GET方法向伺服器發送兩個資料,提交的伺服器指令碼同樣是hello_get.py檔案,代碼如下:
<form action="/cgi-bin/hello_get.py" method="get">
First Name: <input type="text" name="first_name"> <br />
Last Name: <input type="text" name="last_name" />
<input type="submit" value="Submit" />
</form>
使用POST方法傳遞資料
使用POST方法向伺服器傳遞資料是更安全可靠的,像一些敏感資訊如使用者密碼等需要使用POST傳輸資料。
以下同樣是hello_get.py ,它也可以處理瀏覽器提交的POST表單資料:
#!/usr/bin/python
# 引入 CGI 模組
import cgi, cgitb
# 建立 FieldStorage 執行個體
form = cgi.FieldStorage()
# 擷取表單資料
first_name = form.getvalue('first_name')
last_name = form.getvalue('last_name')
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>"
print "<title>Hello - Second CGI Program</title>"
print "</head>"
print "<body>"
print "<h2>Hello %s %s</h2>" % (first_name, last_name)
print "</body>"
print "</html>"
以下為表單通過POST方法向伺服器指令碼hello_get.py提交資料:
<form action="/cgi-bin/hello_get.py" method="post">
First Name: <input type="text" name="first_name"><br />
Last Name: <input type="text" name="last_name" />
<input type="submit" value="Submit" />
</form>
通過CGI程式傳遞checkbox資料
checkbox用於提交一個或者多個選項資料,HTML代碼如下:
<form action="/cgi-bin/checkbox.cgi" method="POST" target="_blank">
<input type="checkbox" name="maths" value="on" /> Maths
<input type="checkbox" name="physics" value="on" /> Physics
<input type="submit" value="Select Subject" />
</form>
以下為 checkbox.cgi 檔案的代碼:
#!/usr/bin/python
# 引入 CGI 處理模組
import cgi, cgitb
# 建立 FieldStorage的執行個體
form = cgi.FieldStorage()
# 接收欄位資料
if form.getvalue('maths'):
math_flag = "ON"
else:
math_flag = "OFF"
if form.getvalue('physics'):
physics_flag = "ON"
else:
physics_flag = "OFF"
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>"
print "<title>Checkbox - Third CGI Program</title>"
print "</head>"
print "<body>"
print "<h2> CheckBox Maths is : %s</h2>" % math_flag
print "<h2> CheckBox Physics is : %s</h2>" % physics_flag
print "</body>"
print "</html>"
通過CGI程式傳遞Radio資料
Radio只向伺服器傳遞一個資料,HTML代碼如下:
<form action="/cgi-bin/radiobutton.py" method="post" target="_blank">
<input type="radio" name="subject" value="maths" /> Maths
<input type="radio" name="subject" value="physics" /> Physics
<input type="submit" value="Select Subject" />
</form>
radiobutton.py 指令碼代碼如下:
#!/usr/bin/python
# Import modules for CGI handling
import cgi, cgitb
# Create instance of FieldStorage
form = cgi.FieldStorage()
# Get data from fields
if form.getvalue('subject'):
subject = form.getvalue('subject')
else:
subject = "Not set"
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>"
print "<title>Radio - Fourth CGI Program</title>"
print "</head>"
print "<body>"
print "<h2> Selected Subject is %s</h2>" % subject
print "</body>"
print "</html>"
通過CGI程式傳遞 Textarea 資料
Textarea向伺服器傳遞多行資料,HTML代碼如下:
<form action="/cgi-bin/textarea.py" method="post" target="_blank">
<textarea name="textcontent" cols="40" rows="4">
Type your text here...
</textarea>
<input type="submit" value="Submit" />
</form>
textarea.cgi指令碼代碼如下:
#!/usr/bin/python
# Import modules for CGI handling
import cgi, cgitb
# Create instance of FieldStorage
form = cgi.FieldStorage()
# Get data from fields
if form.getvalue('textcontent'):
text_content = form.getvalue('textcontent')
else:
text_content = "Not entered"
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>";
print "<title>Text Area - Fifth CGI Program</title>"
print "</head>"
print "<body>"
print "<h2> Entered Text Content is %s</h2>" % text_content
print "</body>"
通過CGI程式傳遞下拉資料
HTML下拉框代碼如下:
<form action="/cgi-bin/dropdown.py" method="post" target="_blank">
<select name="dropdown">
<option value="Maths" selected>Maths</option>
<option value="Physics">Physics</option>
</select>
<input type="submit" value="Submit"/>
</form>
dropdown.py 指令碼代碼如下所示:
#!/usr/bin/python
# Import modules for CGI handling
import cgi, cgitb
# Create instance of FieldStorage
form = cgi.FieldStorage()
# Get data from fields
if form.getvalue('dropdown'):
subject = form.getvalue('dropdown')
else:
subject = "Not entered"
print "Content-type:text/html\r\n\r\n"
print "<html>"
print "<head>"
print "<title>Dropdown Box - Sixth CGI Program</title>"
print "</head>"
print "<body>"
print "<h2> Selected Subject is %s</h2>" % subject
print "</body>"
print "</html>"
CGI中使用Cookie
在http協議一個很大的缺點就是不作使用者身份的判斷,這樣給編程人員帶來很大的不便,
而cookie功能的出現彌補了這個缺憾。
所有cookie就是在客戶訪問指令碼的同時,通過客戶的瀏覽器,在客戶硬碟上寫入紀錄資料 ,當下次客戶訪問指令碼時取回資料資訊,從而達到身份判別的功能,cookie常用在密碼判斷中 。
cookie的文法
http cookie的發送是通過http頭部來實現的,他早於檔案的傳遞,頭部set-cookie的文法如下:
Set-cookie:name=name;expires=date;path=path;domain=domain;secure
name=name: 需要設定cookie的值(name不能使用";"和","號),有多個name值時用";"分隔例如:name1=name1;name2=name2;name3=name3。
expires=date: cookie的有效期間限,格式: expires="Wdy,DD-Mon-YYYY HH:MM:SS"
path=path: 設定cookie支援的路徑,如果path是一個路徑,則cookie對這個目錄下的所有檔案及子目錄生效,例如: path="/cgi-bin/",如果path是一個檔案,則cookie指對這個檔案生效,例如:path="/cgi-bin /cookie.cgi"。
domain=domain: 對cookie生效的網域名稱,例如:domain="www.chinalb.com"
secure: 如果給出此標誌,表示cookie只能通過SSL協議的https伺服器來傳遞。
cookie的接收是通過設定環境變數HTTP_COOKIE來實現的,CGI程式可以通過檢索該變數擷取cookie資訊。
Cookie設定
Cookie的設定非常簡單,cookie會在http頭部單獨發送。以下執行個體在cookie中設定了UserID 和 Password:
<pre>
#!/usr/bin/python
print "Set-Cookie:UserID=XYZ;\r\n"
print "Set-Cookie:Password=XYZ123;\r\n"
print "Set-Cookie:Expires=Tuesday, 31-Dec-2007 23:12:40 GMT";\r\n"
print "Set-Cookie:Domain=www.ziqiangxuetang.com;\r\n"
print "Set-Cookie:Path=/perl;\n"
print "Content-type:text/html\r\n\r\n"
...........Rest of the HTML Content....
以上執行個體使用了 Set-Cookie 頭資訊來設定Cookie資訊,可選項中設定了Cookie的其他屬性,如到期時間Expires,網域名稱Domain,路徑Path。這些資訊設定在 "Content-type:text/html\r\n\r\n"之前。
檢索Cookie資訊
Cookie資訊檢索頁非常簡單,Cookie資訊儲存在CGI的環境變數HTTP_COOKIE中,儲存格式如下:
key1=value1;key2=value2;key3=value3....
以下是一個簡單的CGI檢索cookie資訊的程式:
#!/usr/bin/python
# Import modules for CGI handling
from os import environ
import cgi, cgitb
if environ.has_key('HTTP_COOKIE'):
for cookie in map(strip, split(environ['HTTP_COOKIE'], ';')):
(key, value ) = split(cookie, '=');
if key == "UserID":
user_id = value
if key == "Password":
password = value
print "User ID = %s" % user_id
print "Password = %s" % password
以上指令碼輸出結果如下:
User ID = XYZ
Password = XYZ123
檔案上傳執行個體:
HTML設定上傳檔案的表單需要設定enctype 屬性為multipart/form-data,代碼如下所示:
<html>
<body>
<form enctype="multipart/form-data"
action="save_file.py" method="post">
<p>File: <input type="file" name="filename" /></p>
<p><input type="submit" value="Upload" /></p>
</form>
</body>
</html>
save_file.py指令檔代碼如下:
#!/usr/bin/python
import cgi, os
import cgitb; cgitb.enable()
form = cgi.FieldStorage()
# 擷取檔案名稱
fileitem = form['filename']
# 檢測檔案是否上傳
if fileitem.filename:
# 設定檔案路徑
fn = os.path.basename(fileitem.filename)
open('/tmp/' + fn, 'wb').write(fileitem.file.read())
message = 'The file "' + fn + '" was uploaded successfully'
else:
message = 'No file was uploaded'
print """\
Content-Type: text/html\n
<html>
<body>
<p>%s</p>
</body>
</html>
""" % (message,)
如果你使用的系統是Unix/Linux,你必須替換檔案分隔字元,在window下只需要使用open()語句即可:
fn = os.path.basename(fileitem.filename.replace("\\", "/" ))
檔案下載對話方塊
如果我們需要為使用者提供檔案下載連結,並在使用者點選連結後彈出檔案下載對話方塊,我們通過設定HTTP頭資訊來實現這些功能,功能代碼如下:
#!/usr/bin/python
# HTTP Header
print "Content-Type:application/octet-stream; name=\"FileName\"\r\n";
print "Content-Disposition: attachment; filename=\"FileName\"\r\n\n";
# Actual File Content will go hear.
fo = open("foo.txt", "rb")
str = fo.read();
print str
# Close opend file
fo.close()