【apache+cgi+python】cgi接口淺析

【apache+cgi+python】cgi接口淺析

雖然現在開發web用的都是一些成熟的框架, 使我們可以不用關心底層的接口邏輯, 但是多瞭解一些底層的知識對我們也是有幫助的。

雖然在效率上cgi接口遠不如其它幾個接口, 但是它足夠簡單, 非常適合新手入門。

本文使用python,curl, 在ubuntu + apache環境下爲大家展示cgi接口的基本原理。

本人小菜一枚, 文中錯誤在所難免, 希望大家能夠不吝賜教。

首先、配置apache、htaccess

網站根目錄爲/var/www, 我們放試驗腳本的目錄爲/var/www/python-cgi。

apache的配置

$ gedit /etc/apache2/sites-available/default

<VirtualHost *:80>
    DocumentRoot /var/www
    <Directory "/var/www/python-cgi/">
        Options +ExecCGI +FollowSymLinks
        AllowOverride All
        Order allow,deny
        Allow from all
    </Directory>
</VirtualHost> 

在/var/www/python-cgi目錄下放個.htaccess文件,內容爲

AddHandler cgi-script .py
DirectoryIndex py-cgi-index.py
AddType text/html .py
<IfModule mod_rewrite.c>
    RewriteEngine on
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^(.*)$ py-cgi-index.py/$1 [L]
</IfModule> 

在/var/www/python-cgi目錄下放個py-cgi-index.py文件, 然後添加可執行權限。

$ chmod a+x py-cgi-index.py 

環境配置就完成了。

一、cgi的hello world

cgi的通信依靠stdout與瀏覽器通信。 所以簡單地在py-cgi-index.py裏面寫:

#!/usr/bin/env python
print 'hello world' 

這樣寫是不對的。 cgi接口規定,cgi腳本輸出的開頭應該是http header。 而hello world這種字符無法被識別爲任何有效的http header, 所以如果訪問http://localhost/python-cgi,會返回500錯誤。

解決辦法有兩個:

1、寫上http header。 header與body之間必須有一個空行,以識別前面的是header,後面的是body。 代碼改成:

#!/usr/bin/env python
print 'Content-Type: text/html\n\nhello world' 

2、空白http header。 不寫http header的情況下,apache會自動補上header。 代碼改成:

#!/usr/bin/env python
print '\nhello world' 

關於header,我還要再說一個問題。 cgi腳本的stdout首先要交給apache, apache會對stdout進行一些處理。 如果使用curl -i查看返回的http header, 會發現,header部分被補全了:

HTTP/1.1 200 OK
Date: Sun, 06 Jan 2013 02:49:21 GMT
Server: Apache/2.2.22 (Ubuntu)
Vary: Accept-Encoding
Content-Length: 20
Content-Type: text/html 

二、服務器參數

在php中,有一個重要的全局變量叫$_SERVER,它包含了服務器的一些參數。 那麼在我們的cgi腳本中,如何獲得這些參數呢? 答案是環境變量。 代碼改成:

#!/usr/bin/env python
import os
print 'Content-Type: text/html\n\n'
for i in os.environ:
    print '%s => %s'%(i,os.environ[i]) 

就可以看到,os.environ中有我們需要的很多參數, 包括:

REDIRECT_QUERY_STRING
REDIRECT_STATUS
SERVER_SOFTWARE
SCRIPT_NAME
SERVER_SIGNATURE
REQUEST_METHOD
PATH_INFO
REDIRECT_URL
SERVER_PROTOCOL
QUERY_STRING
PATH
HTTP_USER_AGENT
SERVER_NAME
REMOTE_ADDR
PATH_TRANSLATED
SERVER_PORT
SERVER_ADDR
DOCUMENT_ROOT
SCRIPT_FILENAME
SERVER_ADMIN
HTTP_HOST
REQUEST_URI
HTTP_ACCEPT
GATEWAY_INTERFACE
REMOTE_PORT 

三、get參數

最常用的向服務器提交參數的方法就是get。 我們這裏用curl來模擬:

$ curl -i http://localhost/python-cgi/xxx?aaa=bbb\&ccc=ddd 

然後可以看到:

os.environ['QUERY_STRING']變成了aaa=bbb&ccc=ddd 

這個就是get參數。 不過, 在這裏, 我們需要手工地按照'&'來切分各個query段。

四、post參數

除了get以外, 用戶名、密碼、文件上傳等通常都是使用post來提交。 那麼cgi腳本中如何獲得post的數據呢? 答案是stdin。 代碼改成:

#!/usr/bin/env python
print 'Content-Type: text/html\n\n'
while True:
    i = raw_input()
    if i is None:
        break
    print i 

1、簡單的post參數:

$ curl -i --data "ggg=hhh" --data "iii=jjj" http://localhost/python-cgi/xxx?aaa=bbb\&ccc=ddd\&eee=fff 

它會輸出:

ggg=hhh&iii=jjj 

之後我們需要手動地按照&分隔各個段。

2、文件上傳:

$ curl -i --form upload=@filepath --form name=elephant http://localhost/python-cgi/xxx?aaa=bbb\&ccc=ddd\&eee=fff 

它會輸出:

------------------------------11c41e187464
Content-Disposition: form-data; name="upload"; filename="filepath"
Content-Type: application/octet-stream

中間的是文件內容

------------------------------11c41e187464
Content-Disposition: form-data; name="name"

elephant
------------------------------11c41e187464--

並且此時,os.environ中有一個重要的值:

CONTENT_TYPE => multipart/form-data; boundary=----------------------------11c41e187464 

boundary後面的是分隔線。 之後需要人爲地按照這個分隔線來區分各個段的內容, 並且還要解析Content-Disposition的內容。

五、日誌輸出

apache有日誌功能,我們的cgi腳本能輸出到apache的日誌中? 答案是肯定的,方法是stderr。 代碼改成:

#!/usr/bin/env python
import os
print 'Content-Type: text/html\n\nHello world'
os.stderr.write('this is log') 

在訪問之後, 就可以去apache的日誌中找輸出的內容了。

六、總結

cgi的接口如此的簡單, 使用的僅僅是stdin、stdout、stderr、環境變量四個最常用的進程間交換數據的方式。 而且幾乎所有語言都能夠處理這四項內容。 剩下的事情,

  • http協議規定的東西, 例如在http header添加cookie段可以在瀏覽器端生成cookie。
  • 編程語言自己來處理的事情, 包括數據庫、session、文件讀寫等。

有空也用c語言寫一個吧。

全文完。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章