http://cifer.me/2014-07-07-common-gateway-interface-evolution-2/
http://chriswu.me/blog/writing-hello-world-in-fcgi-with-c-plus-plus/
CGI 由於前面提到的性能問題, 越來越無法滿足大多數網站的要求. 於是, FastCGI 和 Simple CGI 出現了.
Simple CGI (SCGI)
Simple CGI 簡稱 SCGI, 和 FastCGI 一起, 是為了解決原始 CGI 的性能問題而出現的. 它們的解決方式和 “將腳本程序解釋器嵌入 webserver (如 mod_php, mod_python” 不同, 它們的解決方式是創建一個 long-running 的后台進程, 以處理 webserver 的 forward 過來的請求.
當然, webserver 上仍然需要實現 FastCGI 或者 SCGI 協議的, apache 有 mod_fastcgi/mod_fcgid, lighttpd 也有 mod_fastcgi 和 mod_scgi.
SCGI 和 FastCGI 基本是一樣的, 除了 SCGI 比 FastCGI 更容易實現 — 正如其名字所暗示的那樣.
下面看一下在各個 webserver 對 SCGI 的支持情況與配置方式.
Apache
最初, Apache 的模塊 mod_scgi 負責實現 scgi 協議, 這個模塊不是 Apache 自己開發的, 似乎是 python 用戶組開發的, 因為官網就是 python 站上 (參考1). 這個模塊在 Apache 2.0+ 上可用, 是非常穩定的模塊. 可是現在其開發似乎停滯了, 可能是因為 Apache 上 SCGI 用的少, 而且 mod_proxy_scgi 模塊出來的原因吧.
mod_proxy_scgi 模塊是相對較新的模塊, 是被包含在 Apache 源代碼里的, 內建的模塊.
mod_scgi 模塊的配置大概如下 (詳見 參考5):
# (This actually better set up permanently with the command line # "a2enmod scgi" but shown here for completeness) LoadModule scgi_module /usr/lib/apache2/modules/mod_scgi.so # Set up a location to be served by an SCGI server process SCGIMount /dynamic/ 127.0.0.1:4000 The deprecated way of delegating requests to an SCGI server is as follows: <Location "/dynamic"> # Enable SCGI delegation SCGIHandler On # Delegate requests in the "/dynamic" path to daemon on local # server, port 4000 SCGIServer 127.0.0.1:4000 </Location>
mod_proxy_scgi 模塊的配置大概如下 (詳見 參考6):
ProxyPass /scgi-bin/ scgi://localhost:4000/ <Proxy balancer://somecluster> BalancerMember scgi://localhost:4000 BalancerMember scgi://localhost:4001 </Proxy>
前面說了, SCGI 的解決方式是創建一個 long-running 的進程, 或者監聽某個 TCP/IP 端口, 或者監聽一個 unix 套接字, 以便於和 webserver 通信. 那么現在 webserver 相當於 scgi 客戶端, 我們現在還缺少一個 scgi 服務器端.
從上面的 mod_scgi 和 mod_proxy_scgi 的配置也可以看出, SCGI 協議里是還需要一個 scgi 服務器端的.
SCGI 的服務器端是和語言相關的 (與 FastCGI 一樣), 不同的語言有不同的實現, 參考8 介紹了這一點.
注: 下面的內容對 FastCGI 也是適用的
如最古老的 CGI 協議一樣, SCGI 服務器會將請求遞交給它的子進程, 子進程會去執行實際的任務. 不同的地方是, 子進程完成任務后不會退出, 而是 sleep, 等待下一個請求的到來.
SCGI 的另一個好處是, 它不必非得和 webserver 處在同一個機器上.
Lighttpd
Lighttpd 自帶着 mod_scgi 模塊, 其配置方式與 Lighttpd 的 FastCGI 配置方式一樣, 可參考 Lighttpd 的 FastCGI 部分.
Nginx
Nginx 也有自己的 ngx_http_scgi_module 模塊以支持 scgi.
參考
- SCGI 的官方網站, 不明白為何是放在 python 官網上的: http://python.ca/scgi/
- 維基頁, 講得很少: http://en.wikipedia.org/wiki/Simple\_Common\_Gateway\_Interface
- 強烈推薦: https://docs.python.org/2/howto/webservers.html
- 講了 Apache 中的兩種 scgi 的模塊, 推薦: http://alesteska.blogspot.com/2012/07/scgi-in-apache-http-server-and-in.html
- 講了 Apache 中的 mod_scgi 模塊的配置: http://quixote.python.ca/scgi.dev/doc/guide.html
- Apache 官網上關於 mod_proxy_scgi 的介紹: http://httpd.apache.org/docs/trunk/mod/mod\_proxy\_scgi.html
- http://woof.sourceforge.net/woof-ug/\_woof/docs/ug/apache\_scgi
- 吐血推薦, 不過這文章有四頁, 只看前兩頁即可: http://www.linuxjournal.com/article/9310
==============
FastCGI
FastCGI 和 SCGI 是比較類似的, 但 FastCGI 明顯要比 SCGI 流行很多, 可以看一下參考1 中維基頁面, 實現 FastCGI 的 webserver, 以及語言 binding 明顯要比 SCGI 的維基頁中介紹的多.
FastCGI 也有自己的官方網站, 其官方網站可以看出 FastCGI 的一系列優點 (這些優點對 SCGI 也適用):
- FastCGI 夠簡單, 它實際只是 CGI 以及一些擴展.
- 如 CGI 一樣, FastCGI 也是和語言無關的.
- 如 CGI 一樣, FastCGI 進程與 webserver 的進程是隔離的, 這相比模塊化的方案來說, 提高了安全性. (mod_perl, mod_php 這樣的方案, 如果模塊中有 bug, 會導致 webserver 受影響)
- 如 CGI 一樣, FastCGI 並不是與 webserver 的架構結合在一起的, 而模塊化的方式, 是與 webserver 的架構結合在一起的.
當然, 除了兼具 CGI 的好處, FastCGI 還具備以下兩點主要好處:
- 分布式計算: FastCGI 進程不必和 webserver 運行在同一台機器上.
- 多角色: CGI 腳本能夠控制某一個 HTTP 請求的響應值, 而 FastCGI 能做的事情更多, 比如 HTTP 的驗證機制.
按照慣例, 我們看一下在各個 webserver 里配置 FastCGI 的方式.
Apache
(這一節的內容可以參見參考2)
Apache 最早通過 mod_fcgid 實現的 FastCGI, 這是一個第三方的后來也被 ASF 組織承認的模塊. 不過, 它只支持 unix socket 模式, 不支持 tcp socket.
另一個第三方的模塊是 mod_fastcgi, 曾經不能很好的被編譯為 apache 2.4.x 的模塊, 后來被修復了.
apache 2.4.x 推出了另一個 mod_proxy_fcgi 模塊, 這是最新的支持 FastCGI 的模塊, 類似於 mod_proxy_scgi.
至於這三個模塊的配置文件怎么寫, 就不在這里多費口舌了, 自行 google 之.
Lighttpd
自己帶着 mod_fastcgi 模塊
Nginx
也有自己的 ngx_http_fastcgi_module 模塊
FastCGI Server
這才是最重要的部分吧, 和 SCGI 一樣, FastCGI server 的是實現也是和語言相關的. 因為我對 PHP 最為熟悉, 所以從 PHP 開始說起.
PHP
PHP 可以作為其它 webserver (如 apache) 的一個模塊工作, 也可以工作在 CLI 模式下. 當然我們這里要說的是 CGI 模式, PHP 有可以以兩種方式工作在 CGI 模式下, 一是開箱即用的 php-cgi, 另一是 php-fpm.
php-cgi
php-cgi 是開箱即用的, 當你編譯完了 php 源代碼, php-cgi 也就編譯完了.
php-fpm
php-fpm 是更強大的 FastCGI 實現, 它實際上是包裝了 php-cgi, php-cgi 啟動后就一個進程, 而 php-fpm 則是能夠根據負載量動態的創建/銷毀進程 (叫做 worker 進程), 而且在創建這些 workder 進程的時候, 是可以指定不同的用戶, 用戶組的, 這都是單純的 php-cgi 不能實現的.
php-fpm 最開始作為一個第三方的實現, 目前已經被包含到 php 核心里了, 現在編譯 php 源碼, 默認就會編譯 php-fpm.
spawn-fcgi + run_php
實際上, 還有第三種方式 — spawn-fcgi, 之所以上面只說 php 有兩種運行在 FastCGI 的方式沒有包括它, 是因為 spawn-fcgi 並不是專用於 php 的, 它還可以和別的 fastcgi 客戶端程序配合使用, 比如可以和 rails 搭配使用.
spawn-fcgi 先前是 lighttpd 的項目, 現在獨立了出來, 它的作用就是將別的 fastcgi 程序進程化, 通過它的調用方式你就能猜測到它實際上所干的事:
這是 spawn-fcgi 的 Lighttpd 官網文檔里, 啟動 php-cgi 的腳本, 腳本的名字叫 run_php:
#!/bin/sh
# Use this as a ./run script with daemontools or runit # You should replace xxx with the user you want php to run as (and www-data with the user lighty runs as) exec 2>&1 PHP_FCGI_CHILDREN=2 \ PHP_FCGI_MAX_REQUESTS=1000 \ exec /usr/bin/spawn-fcgi -n -s /var/run/lighttpd/php-xxx.sock -n -u xxx -U www-data -- /usr/bin/php5-cgi
spawn-fcgi 還有一個腳本的名字叫 run_rails, 還有一個叫 run_generic, 你能夠猜出它們的作用是什么. 具體可以見參考3.
最后, FastCGI 官網上有關於 php-fpm, php-cgi, spawn-fcgi 的比較, 寫的非常好. 可以看一看.
Python
我找了很久, 在 python 里似乎沒有直接使用 fastcgi 的案例, 因為 python 有一套自己的與 webserver 通信的協議: WSGI. 當然, 沒找到相關的案例不代表 python 不能支持 fastcgi, 其實想要支持 fastcgi 是非常簡單的, 幾乎任何語言都可以寫出一個 fastcgi server 來. 想想說白了, fastcgi 中, fastcgi server 與 webserver 基本也就通過兩種方式通信, 一種是 unix socket (win32 中的 named pipe), 一種是 tcp sockets. webserver 以前將客戶端的請求信息通過環境變量傳給 CGI 腳本, 現在是通過 socket 傳給 fastcgi server. 所以說, 任何語言, 只要其編譯器/解釋器能夠支持調用系統的 socket 偵聽機制, 還能夠較好的解析出 webserver 發給 socket 上的消息, 那么它也就實現了 fastcgi server 的功能.
python 也不例外, 而在 python 中不使用 FastCGI, 是因為 python 有着更適合這們語言的協議, 也就是上面說的 WSGI, 它能夠使得在開發 wsgi 應用程序時, 開發者不必處理 HTTP 請求流信息, wsgi 暴露給開發者的直接就是 python 對象, 比如上傳文件時, 開發者能夠直接取得文件的對象.
python 里確實是有一些框架有實現 fastcgi/scgi 的功能的, 比如 flup, django, 但是他們一般會把 fastcgi 的細節隱藏了, 暴露給開發者的仍然是 wsgi 的接口. 比如下面這個使用 flup 的例子:
#!/usr/bin/env python # -*- coding: UTF-8 -*- from cgi import escape import sys, os from flup.server.fcgi import WSGIServer def app(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) yield '<h1>FastCGI Environment</h1>' yield '<table>' for k, v in sorted(environ.items()): yield '<tr><th>%s</th><td>%s</td></tr>' % (escape(k), escape(v)) yield '</table>' WSGIServer(app).run()
WSGIServer 這個類的 run() 方法調用后, 就啟動了一個 fastcgi server, 但是, 這個類仍然是需要接受一個 callable (上面的 app(environ, start_response) 方法), 這是 wsgi 里的標准接口.
關於 wsgi 的更多內容, 可見本系列的番外篇.
補充一點
python 有個庫叫做 fcgi-python, 是方便別人用 python 寫 fastcgi server 用的.
[未完待續…]
參考
- http://en.wikipedia.org/wiki/FastCGI
- FastCGI 官網: http://www.fastcgi.com/drupal/node/2
- spawn-fcgi 項目官網: http://redmine.lighttpd.net/projects/spawn-fcgi/wiki
- FastCGI 官網上, 關於 php-fpm, php-cgi, spawn-fcgi 的比較, 強烈推薦: http://php-fpm.org/about/
- 一篇中文文章, 介紹了 php-cgi, php-fpm, spawn-fcgi, 還不錯: http://www.joyphper.net/article/201310/237.html
- mod_wsgi 官方的文檔, 強烈推薦: http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide
- django 配置 fastcgi 的教程: https://docs.djangoproject.com/en/dev/howto/deployment/fastcgi/
- flup 配置 fastcgi 的教程: http://wiki.dreamhost.com/Python\_FastCGI
- 有關 python 使用 fastcgi 的主題: http://stackoverflow.com/questions/7048057/running-python-through-fastcgi-for-nginx?lq=1
==========================
WSGI
在 python 大紅大紫的今天, 除了 FastCGI, 我們聽到的最多的可能就要數 WSGI 了, 那么 WSGI 又是啥呢? 它和 CGI, SCGI, FastCGI 又有什么關系呢?
你應該能猜得到, 既然 WSGI 也歸到了 “通用網關協議 (CGI) 進化史” 這一系列里面, 那么 WSGI 和 CGI 肯定也是有點關系的.
WSGI 是專為 python 設計的協議, 其包裝了 FastCGI 協議, WSGI server 一般也都能夠作為 SCGI server, 或者 FastCGI server 運行.
如果說 FastCGI 傳遞信息時用的是一種底層字節流的形式, 那么 WSGI 就是將這字節流結構化為 python 對象, 這使得在 python 中進行 web 開發時, 你只要寫一個文件上傳的表單, 通過 WSGI 你就能直接獲得這個文件的對象, 而不必自己去讀取 HTTP 請求體來接收上傳的文件.
Python 養了很多的框架, 如 Zope, Quixote, Webware, SkunkWeb, PSO 以及 Twisted Web. 如此之多的框架對於 python 用戶來說可能反倒是個問題, 因為一般情況下這些框架並不是適配於所有的 webserver, 於是選擇某個框架也就意味着你相應的只能使用某幾個 webserver, 反之亦然.
然而, 瞧瞧 java, 盡管 java 擁有很多的網絡應用程序開發框架, 然而所有的框架以及所有的 java 容器都遵循同一個標准: servlet API. 遵循 servlet api 的框架如 struts, spring, hibernate, 而容器則有 tomcat, jboss 等.
如果在 python 中也存在這樣的 api, 那么在 python 開發中, 框架的選擇與 webserver 的選擇就也可以互不干擾了.
因此, 一種簡單通用的接口被定義出來, 旨在統一 webserver 與 web application (或者是框架) 直接的通信接口: the Python Web Server Gateway Interface (WSGI).
webserver 對 WSGI 的支持
Apache
apache 里有一個模塊叫做 mod_wsgi, 是一個第三方模塊, apache 通過這個模塊提供對 wsgi 的支持. 這個模塊既能夠運行在 embeded 模式下, 也能夠運行在 daemon 模式下. 這里我們討論他運行在 embeded 模式下的情況.
在 embeded 模式下, mod_wsgi 的配置與 mod_cgi 的配置十分相似, 首先, 我們創建一個 wsgi 腳本, 叫做 myapp.wsgi:
def application(environ, start_response): status = '200 OK' output = 'Hello World!' response_headers = [('Content-type', 'text/plain'), ('Content-Length', str(len(output)))] start_response(status, response_headers) return [output]
可以將其放在 /usr/local/apache/wsgi-bin
目錄下 (當然你可以隨便放, 但要保證相關的權限, 還記的 cgi 腳本放在 cgi-bin 目錄下嗎?), 這里定義的 callable 的名字是 “application”, 這是 WSGI 協議里規定的, 必須就叫這個名字.
然后, 還記得在使用 mod_cgi 模塊時, 指定 cgi 腳本所使用的 ScriptAlias 指令吧? 現在要制定 WSGI 腳本, 我們需要使用 WSGIScriptAlias 指令, 在 apache 的配置文件添加如下這句:
WSGIScriptAlias /myapp /usr/local/apache/wsgi-bin/myapp.wsgi
打開瀏覽器, 訪問 http://localhost/myapp
, 就能看到你的 wsgi 腳本的運行結果了.
當然, WSGIScriptAlias
指令也是可以這么用的:
WSGIScriptAlias /myapp /usr/local/apache/wsgi-bin/
這樣一來, wsgi-bin/ 整個目錄下的文件都能夠被當作 wsgi 腳本運行, 這點和 mod_cgi 模塊的 ScriptAlias 指令是一致的.
關於 mod_wsgi 模塊還有很多強大的功能, 詳情請看參考2.
mod_wsgi embeded 與 mod_cgi 的區別
相比 FastCGI, SCGI, WSGI 與 CGI 應該是最像的, 不過當然也是有區別的.
一方面 WSGI 提供了更加豐富的指令, 第二, wsgi 程序並不是另起一個進程執行的, 而是和 apache 處在同一個進程里 (和 apche 的 worker process 處於同一進程). 這一點通過上面的 wsgi 腳本其實也能看出來, 因為上面的 wsgi 腳本里第一行沒有 shebang, 這就意味着不需要調用一個外部程序去執行這個腳本.
mod_wsgi 的另一種模式
剛才說了 mod_wsgi 還可以運行在 daemon 模式下, 在 embeded 模式下, wsgi 腳本改變了就需要重啟 apache 服務器, 而在這種模式下, daemon 進程能夠監控 wsgi 腳本的改變, 一旦腳本改變 daemon 腳本就回重啟, 而不需要重啟 apache.
在這種模式下, mod_wsgi 與 fastcgi 的方式更像了一些, 但是 mod_wsgi 的 daemon 進程與 apache worker 進程是父子進程的關系 (我沒有查資料, 我是靠 mod_wsgi daemon 模式下的一個配置指令: SetENV 猜測出來的), 而不是通過什么 socket 通信的, 這點與 fastcgi 不一樣.
關於這種模式的詳情可以參見 參考1, 參考2.
mod_wsgi 的新一代
mod_wsgi 是個很不錯的項目, 這個項目持續了很久, 感謝它的維護者們, 這個項目從 Google Code 已經遷移到了 Github 上, 開啟了新的篇章.
上面所介紹的關於 wsgi 的內容, 全部都是 mod_wsgi 的開發者們在 Google Code 上就已經做好的了事情, 當項目遷移到 Github 之后, 開發者們又開發了新一代的 mod_wsgi.
最新的一代 mod_wsgi 已經不必安裝為 apache 的模塊了, 它可以直接安裝到你的 python 中, 並且能夠作為獨立的 http server 啟動 (好像還是調用了 apache 的可執行程序, 我沒有深究, 不重要了), 它有一個新的名字: mod_wsgi-express.
參考
- mod_wsgi 官方的快速配置文檔, 強烈推薦: http://code.google.com/p/modwsgi/wiki/QuickConfigurationGuide
- mod_wsgi 官方的詳細配置文檔, 強烈推薦: http://code.google.com/p/modwsgi/wiki/ConfigurationGuidelines
- mod_wsgi 項目新生代, Github 主頁: https://github.com/GrahamDumpleton/mod\_wsgi
- PEP 333, WSGI 的標准文檔: http://legacy.python.org/dev/peps/pep-0333/#specification-overview
- 這篇主題很好的介紹了 FastCGI 和 WSGI 的區別: http://stackoverflow.com/questions/1747266/is-there-a-speed-difference-between-wsgi-and-fcgi
- 這篇主題的作者經歷很不錯, 提問的很廣泛, 回答者們的答案雖然都得票不多, 膽答得都很值得一看: http://stackoverflow.com/questions/2532477/mod-cgi-mod-fastcgi-mod-scgi-mod-wsgi-mod-python-flup-i-dont-know-how-m
- 這個主題, 回答者很精彩的介紹了 WSGI, CGI 等的關系: http://stackoverflow.com/questions/219110/how-python-web-frameworks-wsgi-and-cgi-fit-together
===============
http://www.mike.org.cn/articles/what-is-cgi-fastcgi-php-fpm-spawn-fcgi/
FastCGI的工作原理
1、Web Server啟動時載入FastCGI進程管理器(IIS ISAPI或Apache Module)
2、FastCGI進程管理器自身初始化,啟動多個CGI解釋器進程(可見多個php-cgi)並等待來自Web Server的連接。
3、當客戶端請求到達Web Server時,FastCGI進程管理器選擇並連接到一個CGI解釋器。Web server將CGI環境變量和標准輸入發送到FastCGI子進程php-cgi。
4、FastCGI子進程完成處理后將標准輸出和錯誤信息從同一連接返回Web Server。當FastCGI子進程關閉連接時,請求便告處理完成。FastCGI子進程接着等待並處理來自FastCGI進程管理器(運行在Web Server中)的下一個連接。 在CGI模式中,php-cgi在此便退出了。
在上述情況中,你可以想象CGI通常有多慢。每一個Web請求PHP都必須重新解析php.ini、重新載入全部擴展並重初始化全部數據結構。使用FastCGI,所有這些都只在進程啟動時發生一次。一個額外的好處是,持續數據庫連接(Persistent database connection)可以工作。
===================
================================
http://chriswu.me/blog/writing-hello-world-in-fcgi-with-c-plus-plus/
FCGI (FastCGI) is a protocol in which web applications can talk to a web server to serve web requests.
We will need to install the libfcgi++ library, a web server (nignx in our case) and spawn-fcgi to run the fcgi app.(需要三者配合)