如何理解 CGI, WSGI?
正好最近在學習CGI。
CGI是比較原始的開發動態網站的方式。你可以想像一下,一個網站的動態內容肯定是程序生成的,光是靜態的html頁面無法達到這個效果。那麼,這個程序就需要接受客戶端的請求,然後進行相應處理,再返回給客戶端,客戶端和服務端的通信當然是通過HTTP協議。
然後我們會發現,這個程序在處理客戶端請求的時候,大部分時候會進行很多重複的工作,比如說HTTP請求的解析。也就是說,你的程序需要解析HTTP請求,我的程序也需要解析。
於是為了DRY原則,Web伺服器誕生了。(以下所說的都是CGI的工作模式)
於是Web伺服器可以解析這個HTTP請求,然後把這個請求的各種參數寫進進程的環境變數,比如
REQUEST_METHOD,PATH_INFO之類的。之後呢,伺服器會調用相應的程序來處理這個請求,這個程序也就是我們所要寫的CGI程序了。它會負責生成動態內容,然後返回給伺服器,再由伺服器轉交給客戶端。伺服器和CGI程序之間通信,一般是通過進程的環境變數和管道。這樣做雖然很清晰,但缺點就是每次有請求,伺服器都會fork and exec,每次都會有一個新的進程產生,開銷還是比較大的。
原因在與CGI程序是一個獨立的程序,它是可以獨立運行的(在提供HTTP請求的情況下),它可以用幾乎所有語言來寫,包括perl,c,lua,python等等。所以對於一個程序,伺服器只能以fork and exec的方式來調用它了。
我所理解的CGI差不多就是這樣。WSGI, Web Server Gateway Interface如全稱代表的那樣,WSGI不是伺服器,不是API,不是Python模塊,更不是什麼框架,而是一種伺服器和客戶端交互的介面規範!更具體的規範說明請搜索「PEP 3333」。在WSGI規範下,web組件被分成三類:client, server, and middleware.WSGI apps(服從該規範的應用)能夠被連接起來(be stacked)處理一個request,這也就引發了中間件這個概念,中間件同時實現c端和s端的介面,c看它是上游s,s看它是下游的c。
WSGI的s端所做的工作僅僅是接收請求,傳給application(做處理),然後將結果response給middleware或client.除此以外的工作都交給中間件或者application來做。
一個HTTP請求過來,一直到最後返回一個頁面,經過哪些環節。把整個流程梳理清楚了,才能真正的理解這幾個概念的位置,以及他們之間的關係。
有一篇很好的文章(WSGI、flup、fastcgi、web.py的關係),直接轉貼(感謝原作者)
Apache/lighttpd: 相當於一個request proxy,根據配置,把不同的請求轉發給不同的server處理,例如靜態的文件請求自己處理,這個時候它就像一個web server,對於fastcgi/python這樣的請求轉發給flup這樣的Server/Gateway進行處理
flup: 一個用python寫的web server,也就是cgi中所謂的Server/Gateway,它負責接受apache/lighttpd轉發的請求,並調用你寫的程序 (application),並將application處理的結果返回到apache/lighttpdfastcgi: apache/lighttpd的一個模塊,雖然flup可以作為一個獨立的web server使用,但是對於瀏覽器請求處理一般都交給 apache/lighttpd處理,然後由apache/lighttpd轉發給flup處理,這樣就需要一個東西來把apache/lighttpd跟flup聯繫起來,這個東西就是fastcgi,它通過環境變數以及socket將客戶端請求的信息傳送給flup並接收flup返回的結果web.py: 應該說有了上面的東西你就可以開始編寫你的web程序了,但是問題是你就要自己處理瀏覽器的輸入輸出,還有cookie、session、模板等各種各樣的問題了,web.py的作用就是幫你把這些工作都做好了,它就是所謂的web framework,另外一個出名的是django,不過感覺太複雜了,web.py差不多就夠用了WSGI : 除了flup Server/Gateway外還有很多其他人的寫的Server/Gateway, 這個時候就會出問題了,如果你在flup上寫了一個程序,現在由於各種原因你要使用xdly了,這個時候你的程序也許就要做很多痛苦的修改才能使用 xdly server了,WSGI就是一個規範,他規範了flup這個服務應該怎麼寫,應該使用什麼方式什麼參數調用你寫的程序(application)等,當然同時也規範你的程序應該怎麼寫了,這樣的話,只要flup跟xdly都遵守WSGI的話,你的程序在兩個上面都可以使用了,flup就是一個WSGI server
CGI,一言以蔽之——協議。為了和普通的網路協議做區分,我們可以稱之為介面協議。所謂協議就是各方約定,達成共識的一種規則。比如中國公路靠右行駛,這就是公路的協議。網路協議約定的是逐位元組的含義,大家遵守,就能方便解析,理解。CGI約定的不是逐位元組的語義,而是一個個kv(不完全是kv,但可以大概這麼理解)。用一個不太恰當的比喻來形容,網路協議描述的單位是數組,而CGI介面協議描述的是map(或者說字典,或者說關聯數組)。
CGI是古老的web技術,在php這類動態網頁語言出現之前承擔過一個時期的歷史任務。當時多是用perl或c來編寫CGI程序。前端通過表單或其他東西可以向伺服器(比如Apache)發送一個URL,以及額外的參數(get或post等請求類型及其參數,伺服器的信息等等),還有cookie等信息。那麼Apache在開啟 了cgi模塊以後可以將其發送給一個CGI程序,這個程序可以是各種語言,比如c++(或c語言)就是從環境變數中解析出這樣請求的具體參數。然後向標準輸出輸出內容(比如cout了一段HTML代碼),這些內容沒有被列印到控制台上,而是最終響應給了你的瀏覽器,渲染出了網頁。重要一點是CGI程序記得要自己先輸出http報頭哦。缺點是每一次向CGI發送請求,都會生成一個CGI進程,,這種就是所謂的fork-and-exec模式,也通常是導致並發瓶頸的癥結。反向代理加上大型的的分散式系統可以一定程度上減輕這些壓力。大浪淘沙,如今CGI幾乎已經消失在了人們的視野中,然而它確實讓你了解很多web底層知識的好工具。近年來由於fcgi的出現,一直以來被人們所詬病的fork-and-exec問題得到解決,使得CGI多少又浮出了一點水面。順帶一提,,騰訊幾乎是是國內碩果僅存的使用c++和CGI做web後端(大部分業務,不過應該不是全部業務採用cgi)的公司了,這主要是由於歷史原因吧。 大學生朋友們,如果應聘騰訊看到後台工程師的技能要求是c++,千萬不要驚訝。。看到php是前端工程師的要求也別奇怪,畢竟c++直接cout出來HTML還是略蛋疼,php做這層view應該是一種高效的解決方案吧。當然不加php,前端直接對接後台c++也是不奇怪的,畢竟restful的風格走俏嘛!手機碼字不易。簡單看了一下PEP-0333,談談個人見解:
WSGI里的組件分為『Server』,『Middleware』和『Application』三種,其中的『Middleware』是『設計模式』里的Decorator(裝飾器)。
WSGI規範在PEP-333里講得很詳細:PEP 0333 -- Python Web Server Gateway Interface v1.0 ,但我覺得比理解規範更重要的,是理解其設計目的和工作原理。
WSGI規範寫得有點繞,之所以繞, 主要原因可能是沒有用『類型提示(Type Hints)』,如果用強類型OOP語言的那種『Interface』和『UML』來解釋會清晰很多。下面我試試用這種帶有『類型提示』的風格簡單講講WSGI的原理。
首先是『Application』
如果把它算作介面的話,它 要能被直接調用(Callable),換句話說,它應該是一個函數或者定義了『__call__』方法的對象,『__call__』方法的簽名是這樣的:def __call__(environ: Dict[String, Any],
start_response: (String, List) -&> Any)): Iterable[String]
為了便於敘述,這裡用的『類型提示』沒有嚴格遵從Python語法。這裡大致的意思是 __call__ 方法需要兩個參數,『environ』是一個鍵(Key)為 String 類型,值(Value)為 任意(Any)類型的字典,『start_response』則是一個函數(實際上為了兼容已有框架,它也可以是一個函數工廠,詳見PEP-0333)。 __call__ 方法返回一個可迭代的對象。
可以這樣理解,如果想正確調用『Application』,就必須知道兩樣東西,『數據』和『上層處理方法』,『數據』是那個environ變數(CGI里就叫這個名字),『上層處理方法』是那個start_response 回調函數,這兩個參數,須在Server調用Application的時候,傳給Application。或者說Application依賴於『數據』和『上層處理方法』,這兩個依賴由Server『注入』給Application(稱作『依賴注入 DI』)。
容易困惑的一點是為什麼『上層處理方法』要通過參數傳給Application,直接在Server里處理好了,把處理好的結果傳給Application不是更簡單嗎?的確這樣做似乎也可以,不過把『上層處理方法』作為回調函數傳給Application,可以由Application本身控制該方法的調用時機,是更加靈活的一種方案(稱作『控制反轉 IoC』)。
接下來是『Middleware』
為了簡化代碼,我們先把『Application』單獨定義為一個類型:type Application = (Dict[String, Any], (String, List) -&> Any)) -&>
Iterable[String]
其實就是前面那個 __call__ 方法的簽名,至於Application究竟是什麼,叫它『函數』,或者『實現了__call__方法的 對象』,『實現了__init__方法的類』都可以,總之都可以通過『Application()』去使用,我們暫且統稱為『Callable對象』。
『Middleware』本質上是一個『裝飾器 Decorator』,和Application類似它也是一個『Callable對象』,如果它有『__call__』方法,其簽名應該是這樣的:
def __call__(app: Application): transformedApp: Application
這個Middleware返回的類型是一個被『裝飾』過的Application(transformedApp變數),這個transformedApp所依賴的『environ』和『start_response』可以被當前Middleware所在上層的Server/Middleware所注入。
再看 這個Middleware的參數,它也是一個Application(app變數),所以Middleware本身的一些附加的邏輯和數據也可以通過app的參數注入到下層的Application里。
Middleware是可以嵌套使用的,比如有『mw1』和『mw2』兩個Middleware和『app』一個Application,就可以通過
mw1(mw2(app))
返回一個新的Application,如果mw1和mw2是相互獨立的,嵌套順序理論上也可以互換。
也可以使用Python的『Decorator』語法,直接裝飾app:
@mw1
@mw2
def app(environ, start_response):
...
cgi通過環境變數,輸入輸出流完成web server與處理邏輯的http協議的交互,由於是基於流方式,所以各種語言都可以寫cgi程序。wsgi是將web server參數python化,封裝為request對象傳遞給apllication命名的func對象並接受其傳出的response參數,由於其處理了參數封裝和結果解析,才有python世界web框架的泛濫,在python下,寫web框架就像喝水一樣簡單:)
apache,nginx這些伺服器,是靜態伺服器,也就是說,只能把本地的現成的html,css之類的東西傳給瀏覽器。而如果我們想要做動態網站呢?(就是說我們希望返回的頁面是通過用戶請求的數據動態生成的,之前沒有的)假設我們打算用python寫,那麼這個時候就有人提出了一個叫做WSGI的協議,告訴我們,大家要寫動態網站的話,肯定會寫一些輸入用戶請求,返回頁面的函數,那麼這些函數都按照WSGI規定的樣子來寫,這樣的話,寫函數的就專心寫函數,不用管獲取用戶輸入,輸出生成頁面的事,而寫獲取和輸出的,不用管函數,這樣大家就可以互相用代碼了。比如一個動態網站的例子,apache+mod_wsgi+django,django是一個web框架,但也可以看成就是一個複雜一點的輸入用戶請求,返回頁面的函數,並且滿足WSGI裡面的規定,而mod_wsgi就是apache的一個模塊,這個模塊讓apache這個靜態伺服器可以幫滿足WSGI規範的函數實現獲取用戶請求和輸出頁面的功能。
所以,總結的話,WSGI就是一個規範,
反向代理的情況下 web 伺服器需要與 app 伺服器通訊,這兩者都是為這個通訊過程所定義的協議,有了這個協議不同端的不同實現之間就可以開箱即溝通不用再修改代碼。能用 nginx、apache 代理 php 應用、python 應用等等,就歸功於此。
wsgi 是 python 社區定義的,主要應用於 python 生態,沒有 cgi 使用的廣泛。
了解細節可以去看 specification,進一步去實現一個簡單的 cgi 或者 wsgi 應用,這個比聽人解釋要清晰更多。cgi是通用網關介面,是連接web伺服器和應用程序的介面。
web伺服器負責接收http請求,但是http請求從request到response的過程需要有應用程序的邏輯處理,web伺服器一般是使用C寫的,比如nginx,apache。而應用程序則是由各種語言編寫,比如php,java,python等。這兩種語言要進行交互就需要有個協議進行規定,而cgi就是這麼個網關協議。
拿nginx+fastcgi+php為例子,nginx裡面的fastcgi模塊實現cgi的客戶端,php的cgi-sapi實現cgi的服務端。
WSGI就是Python的CGI包裝,相對於Fastcgi是PHP的CGI包裝CGI,就是你上網,瀏覽器發送一個請求到要上的網站,而那個網站所在的伺服器,需要經過一些程序處理之後才能給你結果,而不是簡單的返回html文本給你。伺服器上這個過程就叫CGI,程序就是CGI程序,由請求激發的腳本,或者類似的小程序。
關於CGI,機械工業出版社第二版的CSAPP的11章給出了源代碼的實現。(636頁以下)
假設
http://www.test.com/cgi-bin/add?15000213
這個網址,cgi程序全部放在cgi-bin目錄下,集中方便提供cgi服務。CGI用於服務動態內容。
瀏覽器發送http請求,一個請求行的形式是&http://www.test.com
,瀏覽器會自動在最後加上/,即
http://www.test.com/
以下是分析uri的源代碼:
int parse_uri(char *uri, char *filename, char *cgiargs)
{
char *ptr;
if (!strstr(uri, "cgi-bin")) { /* 靜態內容 */
strcpy(cgiargs, "");
strcpy(filename, ".");
strcat(filename, uri);
if (uri[strlen(uri)-1] == "/") /* uri如果以/結尾,加上index.html */
strcat(filename, "index.html");
return 1;
}
else { /* Dynamic content */
ptr = index(uri, "?"); /* 返回?所在字元串的索引指針 */
if (ptr) {
strcpy(cgiargs, ptr+1); /* 將?後的字元串複製到cgiargs */
*ptr = " "; /* uri變為 /cgi-bin/adder */
}
else
strcpy(cgiargs, "");
strcpy(filename, ".");
strcat(filename, uri);
return 0;
}
}
以下是提供動態內容的源代碼:
void serve_dynamic(int fd, char *filename, char *cgiargs)
{
char buf[MAXLINE], *emptylist[] = { NULL };
/* Return first part of HTTP response */
sprintf(buf, "HTTP/1.0 200 OK
");
Rio_writen(fd, buf, strlen(buf));
sprintf(buf, "Server: Tiny Web Server
");
Rio_writen(fd, buf, strlen(buf));
if (Fork() == 0) { /* Child process */
/* Real server would set all CGI vars here */
setenv("QUERY_STRING", cgiargs, 1);
Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client */
Execve(filename, emptylist, environ); /* Run CGI program */
}
Wait(NULL); /* Parent process waits for and reaps child */
}
Child process的execve執行cgi程序,將程序的標準輸出(STDOUT_FILENO)重定向到connfd描述符並將輸出寫進socket通道,client端(即瀏覽器)通過socket通道另一端的clientfd描述符讀取信息到並顯示html內容。參考CSAPP的630頁一下的echo伺服器實例。
以下是add程序的源代碼:
int main(void) {
char *buf, *p;
char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
int n1=0, n2=0;
/* Extract the two arguments */
if ((buf = getenv("QUERY_STRING")) != NULL) {
p = strchr(buf, "");
*p = " ";
strcpy(arg1, buf);
strcpy(arg2, p+1);
n1 = atoi(arg1);
n2 = atoi(arg2);
}
/* Make the response body */
sprintf(content, "Welcome to add.com: ");
sprintf(content, "%sTHE Internet addition portal.
&
", content);
sprintf(content, "%sThe answer is: %d + %d = %d
&
",
content, n1, n2, n1 + n2);
sprintf(content, "%sThanks for visiting!
", content);
/* Generate the HTTP response */
printf("Content-length: %d
", strlen(content));
printf("Content-type: text/html
");
printf("%s", content);
fflush(stdout);
exit(0);
}
這是我學習CSAPP學到的關於cgi的知識,歡迎一起交流。其實我不知道wsgi。哈哈.....
CGI(Common Gateway Interface)可以說是一種替代用戶直接訪問伺服器上文件而誕生的一種「代理」,用戶通過CGI來獲取動態數據或文件等。
從最原始的意義上來說,CGI是一種設計思想,其最早的實現是每次請求都直接調用操作系統來創建進程、銷毀進程,這種程序雖然效率不高但是給WEB數據動態訪問提供了很好的思路。
現在經過改進的CGI效率已經大大提高,尤其是fastCGI等實現。
而WSGI是Web Server Gateway Interface的簡稱,從名字上看和CGI一定有淵源。事實上,由於之前的CGI程序和編寫WEB服務所用的語言往往是不同的(CGI用C,WEB用PHP等),WSGI的其中一個目的就是讓用戶可以用統一的語言編寫前後端,WSGI參考了CGI的設計,對CGI的設計思想進行了進一步包裝。
參考:http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side這麼做執行效率當然不高,不過根據《黑客與畫家》最後的預言,這些效率是值的犧牲了,未來誰知道呢。
總結來說:
1、CGI是一種為用戶動態提供所需數據的設計思想,它有很多各種不同語言的實現。2、WSGI是Python對CGI進行的一種包裝,核心使用Python實現,具體實現通常來說也需要使用Python,目前Django、Google webapp框架都實現了WSGI。 HTH.CGI(Common Gateway Interface)
CGI是外部應用程序(CGI程序)與WEB伺服器之間的介面標準,是在CGI程序和Web伺服器之間傳遞信息的過程。CGI規範允許Web伺服器執行外部程序,並將它們的輸出發送給Web瀏覽器,CGI將Web的一組簡單的靜態超媒體文檔變成一個完整的新的互動式媒體。
CGI是動態執行程序然後將結果返回,而WEB伺服器只能讀取靜態文件然後返回給客戶端。
e.g.
# application.py
from http.server import CGIHTTPRequestHandler, test
test(CGIHTTPRequestHandler, port=8080)
# cgi-bin/blog.py
html = """Content-type:text/html
&
Hello, cgi&
"""print(html)
CGI腳本工作流程:
- 瀏覽器發送一個指向CGI應用程序的URL,即:127.0.0.1:8080/cgi-bin/blog.py
- 伺服器收到請求後首先創建CGI的子進程,然後執行指定的CGI應用程序,處理完之後結束該子進程
# application.py控制台輸出
127.0.0.1 - - [18/Oct/2017 15:47:57] "GET /cgi-bin/blog.py HTTP/1.1" 200 -
127.0.0.1 - - [18/Oct/2017 15:47:57] command: "D:Program Filespython3python.exe" -u D:appcgi-binlog.py ""
127.0.0.1 - - [18/Oct/2017 15:47:57] CGI script exited OK
- CGI應用程序把結果通過網路返回給客戶端
WSGI(Web Server Gateway Interface)
WSGI即Web伺服器網關介面。WSGI是一個規範,只是一個規範,定義了Web伺服器如何與Python應用程序進行交互,使得使用Python寫的Web應用程序可以和Web伺服器(nginx/apache)對接起來。
WSGI存在的目的有兩個:
- 讓Web伺服器知道如何調用Python應用程序,並且把用戶的請求告訴應用程序。
- 讓Python應用程序知道用戶的具體請求是什麼,以及如何返回結果給Web伺服器。
e.g.
from wsgiref.simple_server import make_server
def application(environ, start_response):
"""應用程序
Args:
environ: 字典類型,存放了所有和客戶端相關的信息
start_response: 一個可調用對象,接收兩個必選參數和一個可選參數:
status: 一個字元串,表示HTTP響應狀態字元串
response_headers: 一個列表,包含有如下形式的元組:(header_name, header_value),用來表示HTTP響應的headers
exc_info(可選): 用於出錯時,server需要返回給瀏覽器的信息
:return: 可迭代對象, server端通過遍歷這個可迭代對象可以獲得body的全部內容
"""
start_response("200 OK", [("Content-Type", "text/html")])
return [b"&
Hello, wsgi&
"]# 負責啟動WSGI伺服器,載入application()函數
httpd = make_server("", 8000, application)
httpd.serve_forever()
所以無論請求多複雜,入口都是一個WSGI處理函數,即:application。因為在environ變數中包含了所有請求信息,開發者可以根據這些信息返回不同數據。
WSGI、CGI等標準無非是讓WEB伺服器知道找誰處理請求;讓應用程序知道請求是什麼,響應返回給誰。
不求東西,回答到很有深度,所以畫了一下他描述的
CGI是一種設計思想,其最早的實現是每次請求都直接調用操作系統來創建進程、銷毀進程;但是cgi的實現也可以基於event模型,這樣不必新建進程,甚至不必創建新的線程,從而使效率獲得提升。具體過程:1、在創建socket後,將http_receiver作為通道event的處理函數,收取每個字元;catch {fileevent $channel readable [list http_receiver $channel $chost $cport]}2、在http_receiver中捕捉eof,完成http報文頭的提取;在這裡要同時處理認證,認證通過的給出cgi函數,並要將參數、ip地址、埠等都傳到cgi函數:www_give_cgi $channel $channel $args $request $chost $cport 3、在cgi函數中根據ip地址、埠判斷用戶,並給出適合該用戶的響應。
挖個坑,下次再編輯完整。我粗略回答一下先。
CGI:
全稱:common gateway interface 通用網關介面。
在Web發展的最初,全球的互聯網的網頁是靜態的,客戶端向伺服器請求網頁,獲得了一個靜態的網頁。所謂靜態的網頁的就是所有人得到的網頁都是相同的。
但是隨著網際網路的發展,Web服務商希望可以實現動態的網頁。打個比方,購物網站希望給用戶發送個性化的菜單,比如我的購車裡有一桶泡麵,你的購物車有一瓶礦泉水。當我們各自登錄該購物網站時(實質是瀏覽器提交表單的過程),最後,我們獲得的網頁一樣嗎??顯然是不一樣的。因為我的購物車有泡麵,你有礦泉水。這就是動態的頁面。
如果去實現這樣的功能呢?這就是迫切的需求,然後就 產生了技術--CGI
Web伺服器找到解決方案,通過在CGI應用,當用戶請求時提交了他的Form表單,伺服器接收後調用CGI 應用處理,CGI返回相應的數據,最終交付給用戶。
如下圖:(手機拍的,懶得搜圖了,先將就著看吧)
CGI是一個在高層次的程序。
而WSGI與CGI完全就是不同的東西。CGI是在客戶端,伺服器的「三角戀」之中。。。。而WSGI是伺服器提供給Web框架開發者的標準。必須強調:WSGI是提供給框架開發者的標準! 如果你是應用開發者,那麼這個事跟你沒關係。比如Python的Django框架,如果你是Django 框架的開發者,那麼你要遵守這個標準。
有空再細答
先看一下這裡的關係圖
nginx[+spawn-fcgi]+flup+webpy服務搭建我覺的flup在這裡的角色應該是中間件:
向前能夠滿足FastCGI規範要求的Socket長連接、Shell環境變數傳遞請求參數向後能夠滿足WSGI規範要求的「非同步回調」。(非同步回調的思想在Node.js這個WebServer解決方案中被強調的非常重,可以簡單搜索一下做一個了解)另一篇博客也總結的很好,居然很難搜到。比較詳細,放在後面看。網關協議學習:CGI、FastCGI、WSGIWSGI是一套符合Python標準的規範。然而這個規範里主要由服務和程序組成,每一個請求都會讓服務去觸發一個函數(程序),服務負責返回函數的結果給讓它觸發的web客戶端(請求的發起者)。為何是符合Python規範,因為這裡的函數是Python用實現的
推薦閱讀:
※WSGI 為什麼很重要?
※python搭建網站和cms搭建網站哪個更快,各有何優勢?
※Django寫的博客工具?
※為什麼 Python 裡面的 range 不包含上界?
※CPython是什麼?PyPy是什麼?Python和這兩個東西有什麼關係呢?Python的底層使用什麼語言實現?學習Python需要學習底層實現嗎?