緣起
關於web開發的服務器端的編程的技術,個人瞭解的有ASP,JSP,PHP等系列的腳本語言。ASP使用IIS,JSP使用Tomcat或Apache,PHP之類的腳本語言使用Apache或CGI。這裏關於CGI的認識有點模糊,尤其是決定學習Ruby和Rails時,頻頻能看到CGI這個詞。十分好奇,CGI到底爲何物,搜索了一番。
1. CGI
1.1. 歷史
最初,CGI是在1993年由美國國家超級電腦應用中心(NCSA)爲 NCSA HTTPd Web 服務器(http和sqlite一樣,都是公公域軟件,針對httpd補丁的彙總構成了最初的Apache)開發的。Httpd Web服務器使用了UNIX shell 環境變量來保存從 Web 服務器傳遞給CGI的參數,然後生成一個運行 CGI 的獨立的進程。
1.2. 簡介
公共網關接口CGI(Common Gateway Interface)是WWW技術中最重要的技術之一,其規範的地址爲
http://www.ietf.org/rfc/rfc3875。CGI是外部應用程序(CGI程序)與Web服務器之間的接口標準,是在CGI程序和Web服務器之間傳遞信息的規程。CGI規範允許Web服務器執行外部程序,並將它們的輸出發送給Web瀏覽器。CGI運行在網絡服務器上。CGI程序可以用任何一種語言編寫,只要這種語言具有標準輸入、輸出和環境變量,如php,perl,tcl等。
大多數CGI程序用來處理和解釋來自表單的輸入信息,並在服務器進行相應的處理,或將相應的信息反饋給瀏覽器。CGI程序使網頁具有交互功能。
1.3. CGI處理流程
簡單來說:CGI可以理解爲一個很簡單的遠程腳本調用。在服務器端有許多的腳本,這些腳本可以使用任何可執行語言編寫(shell、ruby、perl、PHP),然後用戶通過url訪問web服務器,服務器會根據已有的url mapping找到對應的一個腳本,然後執行這個腳本。最後把腳本執行的結果按照既定的格式(比如Html,xml,json之類的)返回給用戶。
CGI的簡單處理流程:1.用戶點擊URL發送請求給web服務器 2.web服務器接受請求轉交CGI程序 3.CGI程序把處理結果傳送給web服務器 4.web服務器把結果發給到用戶。這個處理流程就是著名的請求/響應模型,web開發都要遵守這個模型。
上述步驟中web服務器接受請求轉交CGI程序,需要配置web服務器,將對因的URL請求映射爲CGI程序(大多爲腳本程序)。
每次CGI請求都要生成一個進程來運行,大量請求就會壓垮服務器。對腳本語言而言,每次請求都重新載入和初始化解釋器,開銷太大。此時,可以將腳本語言直接集成到web服務器中(例如,Apache中的mod_perl,mod_php,mod_python,mod_ruby)。CGI相關的Apache模塊:mod_actions(CGI腳本類型),mod_cgi(啓動CGI並維護相關事件出錯日誌),mod_cgid,mod_env(設置CGI編譯器的環境變量,比如軟件庫)
1.4. CGI環境變量
CGI實際通過Shell環境變量來獲取相應的參數,一些常用的環境變量如下:
服務器類變量:
SERVER_NAME:運行CGI序爲機器名或IP地址。
GETWAY_INTERFACE:CGI程序的版本,在UNIX下爲 CGI/1.1
SERVER_INTERFACE:WWW服務器的類型,如:CERN型或NCSA型。
請求類變量:
SERVER_PROTOCOL:通信協議,應當是HTTP/1.0。
SERVER_PORT:TCP端口,一般說來web端口是80
REQUEST_METHOD:HTTP方法名,常用的有GET,PUT,DELETE,POST
PATH_INFO:瀏覽器用GET方式發送數據時的附加路徑前綴
PATH_TRANSLATED:根據PATH_INFO指定的服務器上實際路徑名。
SCRIPT_NAME:CGI程序的相對路徑名,比如/cgi-bin/script.cgi
QUERY_STRING:URL中問號後的內容,通過GET方法提交的表單輸入的數據,格式爲name=value,以&分隔
REMOTE_HOST:客戶端的主機名,不能確定該值。
REMOTE_ADDR:客戶端的IP地址。
REMOTE_USER:客戶端的人名,用作某些權限類型
CONTENT_TYPE:使用PUT或POST發送的因特網媒體數據,通過HTTP包頭提供
CONTENT_LENGTH:POST或PUT提交的數據的大小,由HTTP包頭提供
HTTP_ACCEPT, HTTP_ACCEPT_LANGUAGE, HTTP_USER_AGENT, HTTP_COOKIE以及其他相關的變量包含HTTP包頭中對應的數據。
HTTP_ACCEPT:HTTP定義的瀏覽器能夠接受的數據類型。
HTTP_REFERER:發送表單的文件URL。(並非所有的瀏覽器都傳送這一變量)
HTTP_USER_AGENT:發送表單的瀏覽器的有關信息。
CGI部署的約定:
1.cgi-bin目錄作爲所有CGI程序的根目錄
2.擴展名約定:.cgi的文件爲CGI腳本。
1.5. CGI編程語言
CGI本身獨立於任何編程語言,只要相應的語言可以運行在服務器上,CGI 程序可以用任何腳本語言或者是完全獨立編程語言實現。除 Perl 外,Unix shell script, Python, Ruby, PHP, Tcl, C/C++, 和 Visual Basic 都可以用來編寫 CGI 程序,但Perl被廣泛用來編寫CGI程序。
腳本語言靈活性和高效開發效率,可以快速開發CGI程序,同時可以完成非常多的任務。CGI中腳本語言介紹如下:
Perl
1987年,Larry Wall吸取C,sed,awk,內部集成正則表達式,瑞士軍刀般的腳本語言。
PHP
1994年,Rasmus Lerdorf,個人網站開發,Perl程序,C重寫,Zend Engine,php5。
特點:
1.混合C,java,Perl,加自創語法
2.在執行動態網頁上,比其他動態語言(Perl)速度快
3.擁有強大的功能,可替代CGI
4.支持主流數據庫和操作系統
5.可以用C/C++擴展
優勢:開源,免費,快捷,誇平臺,高效,面向對象,圖像處理
Ruby
1995年,吸收了Smalltalk,Perl,Lisp等語言,是一個讓程序員感到快樂的語言。
其他的就不介紹了,諸如Python這類的語言。
1.6. 進一步學習
CGI的執行程序的模式爲fork & execute,即對每次請求都創建一個新的進程(fork系統調用),然後執行,這種方法容易實現,但效率差,難以擴展,面對大量請求,進程的大量建立和消亡使操作系統性能大大下降。此外,由於地址空間無法共享,也限制了資源重用。因而存在替代性的解決方案:
1.web服務器自己實現允許第三方軟件在其中運行的擴展機制,比如Apache的擴展模塊,NSAPI插件和ISAPI插件
2.FastCGI
3.Simple Common Gateway Interface 或SCGI
4.使用其他的動態網站架構,比如Java EE,在Java servlet容器中運行Java代碼提供動態或靜態內容,通過多線程來取代多進程。
CGI標準:http://www.w3.org/CGI/
一些免費的CGI書籍:
2.http://oreilly.com/openbook/cgi/
2. CGI變體
由於CGI的fork&execute模型存在性能問題,因而存在一些CGI變體。羅列如下:
2.1. FastCGI
FastCGI官方地址:http://www.fastcgi.com/drupal/ FastCGI是CGI的增強版,致力於減少web服務器和CGI程序之間互動開銷,從而使服務器可以同時處理更多的網頁請求。
CGI
與CGI中爲每個請求創建新的進程不同,FastCGI使用持續的進程來處理一連串的請求。這些進程由FastCGI服務器管理,而不是web服務器。當請求到來時,web服務器把環境變量和頁面請求通過一個socket(FastCGI進程在服務器上)或者TCP connection(FastCGI進程在遠程服務器上)傳遞給FastCGI進程。
FastCGI像是一個常駐(long-live)型的CGI,可以一直運行,一旦激活後,不必每次都要花費時間去fork進程(這是CGI最爲人詬病的fork-and-execute 模式)。支持分佈式的運算,即FastCGI程序可以在網站服務器以外的主機上執行並且接受來自其它網站服務器來的請求。
在Apache中,FastCGI通過mod_fcgid模塊或mod_fastcgi模塊實現。
2.1.1. FastCGI處理流程
FastCGI處理的流程如下:
1、Web服務器啓動時載入FastCGI進程管理器(IIS ISAPI或Apache Module)
2、FastCGI進程管理器自身初始化,啓動多個CGI解釋器進程(比如多個php-cgi)並等待來自Web服務器的連接
3、當客戶端請求到達Web服務器時,FastCGI進程管理器選擇並連接到一個CGI解釋器。Web服務器將CGI環境變量和標準輸入發送到FastCGI子進程(比如php-cgi)
4、FastCGI子進程完成處理後將標準輸出和錯誤信息從同一連接返回Web服務器。當FastCGI子進程關閉連接時,請求便告處理完成。FastCGI子進程接着等待並處理來自FastCGI進程管理器(運行在Web服務器中)的下一個連接。在CGI模式中,php-cgi在此便退出了
在上述情況中,使用CGI,每一個PHP頁面請求都必須重新解析php.ini、重新載入全部擴展並重初始化全部數據結構。使用FastCGI,所有這些都只在進程啓動時發生一次。額外的好處,可以使用持續數據庫連接(Persistent database connection)。
2.1.2. 特點
優點:語言無關,獨立安全的執行環境,支持諸多語言和服務器,獨立web服務器內部架構
缺點:多進程,耗內存
2.1.3. 進一步學習
FastCGI官方網:http://www.fastcgi.com/drupal/ 中的文檔。
2.2. SCGI-Simple Common Gateway Interface
Simple Common Gateway Interface (SCGI)是一個和HTTP Server交互的應用程序接口,作爲CGI的替代選項。和FastCGI類似,但更易實現,且部分運行CGI操作連接到外部數據庫。
2.2.1. 規範Specification
客戶端通過允許傳輸8位的可靠流控制協議來連接到SCGI服務器。客戶端通過發送請求開始,但SCGI服務器看到請求結束後,返回一個相應並關閉連接。請求格式如下,相應的格式在規範中沒有定義。
請求格式
請求由一組頭部和一個主體(body)組成,頭部的格式如下:
headers ::= header* header ::= name NUL value NUL name ::= notnull+ value ::= notnull* notnull ::= <01> | <02> | <03> | … | <ff> NUL = <00> |
頭部不允許出現重複字符串,第一個頭部的name必須爲CONTENT_LENGTH,value爲body的長度。即使value爲0,也必須有要CONTENT_LENGTH和SCGI這兩個頭部,爲了操作CGI轉換,標準CGI的環境變量也必須在頭部提供。
頭部編碼爲網絡字符串(netstring即[len]":"[string]","格式)併發送給服務器應用程序,body隨後發送,其長度由CONTENT_LENGTH頭指定。
樣例
web 服務器(SCGI客戶端)打開一個連接併發送如下字符串連接:
"70:" "CONTENT_LENGTH" <00> "56" <00> "SCGI" <00> "1" <00> "REQUEST_METHOD" <00> "POST" <00> "REQUEST_URI" <00> "/deepthought" <00> "," "What is the answer to life, the Universe and everything?" |
Web應用程序(SCGI 服務器)發送如下相應結束關閉鏈接:
"Status: 200 OK" <0d 0a> "Content-Type: text/plain" <0d 0a> "" <0d 0a> "42" |
2.2.2. 實現
服務器實現:Apache Http Server的SCGI模塊、Lighttpd、Nginx、IIS的ISAPI SCGI擴展
語言接口:Haskell, Java, Lisp, Perl,PHP, Python, Ruby, Tcl
2.2.3. 進一步學習
SCGI規範:http://www.python.ca/scgi/protocol.txt
Apache SCGI modules and Python SCGI interface
3. PSGI
Perl web server Gateway Interface,簡稱PSGI,是web服務器和基於Perl的web應用程序和框架的接口,用來編寫可移植的應用程序(單獨運行或使用CGI,FastCGI,mod_perl等)。項目靈感來自Python的WSGI(Web Server Gateway Interface),Ruby的Rack,JavaScript的JSGI。
PSGI的應用程序是Perl子程序,它接受單個哈希表參數,返回包含三個元素的數組(HTTP狀態碼,HTTP頭數組的引用,HTTP body行數組的引用)。其中HTTP body用來生成HTML文檔。
Plack是PSGI的一個實現,其支持的Perl框架很多(參見參考資料8)。
3.1. 樣例
PSGI的hello world程序hello.psgi:
my $app = sub {
return [200, ['Content-Type' => 'text/plain'], ["hello, world\n"]];
}
運行命令:plackup hello.psgi
3.2. 進一步學習
PSGI規範:Perl Web Server Gateway Interface Specification
Catalyzed上關於Plack和Nginx的文章:http://www.catalyzed.org/2009/11/mtplack-on-nginx-love.html
4. WSGI
Web Server Gateway Interface,中文名Web服務器網管接口,簡稱WSGI,是爲Python定義的Web服務器和Web應用程序或框架之間的一種簡單而通用的接口。自從WSGI被開發出來以後,許多其它語言中也出現了類似接口(比如Perl中的PSGI,Ruby中的Rack)。
4.1. 緣由
以前,如何選擇Python框架是一個問題。Web框架的選擇會限制web服務器的選擇,反之亦然。WSGI出現之前,Python應用程序通常爲CGI,FastCGI,mod_python甚至特定web服務器設計的。
WSGI是服務器(web服務器)與應用程序之間的一個低級別接口,提供開發可移植web應用。WSGI是基於CGI標準的,所以可以套接到CGI上。
簡單來看,WSGI就是服務器與應用程序之間的中間件,他對於服務器來說,就是應用程序,對於應用程序來說就是服務器,在中間做中轉處理,比如,在request和response上做修改。
4.2. 規範概覽
WSGI有兩方:“服務器”或“網關”一方,以及“應用程序”或“應用框架”一方。服務方調用應用方,提供環境信息,以及一個回調函數(提供給應用程序用來將消息頭傳遞給服務器方),並接收Web內容作爲返回值。
WSGI中間件同時實現了API的兩方,因此可以在WSGI服務和WSGI應用之間起調解作用:從WSGI服務器的角度來說,中間件扮演應用程序,而從應用程序的角度來說,中間件扮演服務器。“中間件”組件可以執行以下功能:
- l 根據目標URL,將請求消息路由到不同的應用對象。
- l 允許在一個進程中同時運行多個應用程序或應用框架。
- l 負載均衡和遠程處理,通過在網絡上轉發請求和響應消息。
- l 進行內容後處理,例如應用XSLT樣式表。
4.3. 實例
Python語言寫的符合WSGI的“Hello World”應用程序如下所示:
def app(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
yield "Hello world!\n"
代碼解釋:
第一行定義了一個名爲app的callable,接受兩個參數,environ和start_response,environ是包含了CGI中的環境變量的字典,start_response也是一個callable,接受兩個必須的參數,status(HTTP狀態)和response_headers(響應消息的頭)。
第二行調用了start_response,狀態指定爲“200 OK”,消息頭指定爲內容類型是“text/plain”
第三行將響應消息的消息體返回
4.4. 進一步學習
WSGI維基百科:.http://en.wikipedia.org/wiki/Web_Server_Gateway_Interface
PEP 3333 -- Python Web Server Gateway Interface v1.0.1
WSGI metaframework啄木鳥關於wsgi的介紹
5. Rack
Rack提供最小的,模塊化的,可配性接口來開發Ruby的web應用程序。通過以最簡方法包裝HTTP請求和響應,it unifies and distills the API for web servers, web frameworks, and software in between (the so-called middleware) into a single method call.
Rack已經被用到所有的Ruby web框架和庫,比如Ruby on Rails和Sinatra。可以通過Ruby Gem進行安裝。
5.1. 簡介
Rack就是Ruby的WSGI,是在服務器與應用程序提供一箇中間件。通過截獲用戶發來的請求和應用程序以既定形式返回的數據,然後作出相應的處理。例如,一個用戶的請求到來後,驗證用戶的訪問url是否有效,或者驗證用戶的身份等。
作爲一箇中間件,rack的主要作用體現在通用邏輯和實際業務邏輯的分離,在rack中加入通用的邏輯,而無需在實際業務程序中添加類似的功能。由於rack標準的普遍性,幾乎所有的主流Ruby web框架(Ruby on Rails和Sinatra)都按照rack標準開發的模塊,rack也爲開發者提供了大量的api來簡化開發。
5.2. 樣例
Rack兼容的Hello world程序:
app = lambda do |env|
body = "Hello, World!"
[200, {"Content-Type" => "text/plain", "Content-Length" => body.length.to_s}, [body]]
end
run app
5.3. 進一步學習
Rack編程中文pdf文檔:http://www.iteye.com/topic/605707
後記
寫博客最重要的是確定主題,有了主題,可以收集資料,分析處理資料,整理成文。主題(topic)的來源有很多,看到的,聽說的,想到的都可以衍生出主題。
寫博客要明確讀者,自己或是別人,要站在讀者角度,多問自己幾個問題,想說什麼,怎麼說,有用嗎,有趣嗎。
CGI程序沒有開發過,但是,自己曾經安裝過一個使用CGI的Perl程序-Monitorix。寫這篇博客不容易啊,前後花了兩天的時候。不過,也瞭解了很多有趣的東西,CGI,Libwww,Rack,其實我最感興趣的是
參考文獻
1.CGI,WSGI和Rack:http://blog.csdn.net/cherry_sun/article/details/7751452
2.CGI 百度百科:http://baike.baidu.com/subview/32614/12037322.htm
3.Wikipedia:公共網關接口
4.Common Gateway Interface:http://en.wikipedia.org/wiki/Common_Gateway_Interface
5.Fast CGI百度百科:http://baike.baidu.com/view/641394.htm
6.Fast CGI 維基百科: http://zh.wikipedia.org/wiki/FastCGI
7.Simple Common Gateway Interface:http://en.wikipedia.org/wiki/Simple_Common_Gateway_Interface
8.PSGI:http://en.wikipedia.org/wiki/PSGI
9.WSGI中文維基