瞭解了HTTP協議和HTML文檔,咱們其實就明白了一個Web應用的本質就是:css
-
瀏覽器發送一個HTTP請求;html
-
服務器收到請求,生成一個HTML文檔;java
-
服務器把HTML文檔做爲HTTP響應的Body發送給瀏覽器;python
-
瀏覽器收到HTTP響應,從HTTP Body取出HTML文檔並顯示。linux
因此,最簡單的Web應用就是先把HTML用文件保存好,用一個現成的HTTP服務器軟件,接收用戶請求,從文件中讀取HTML,返回。Apache、Nginx、Lighttpd等這些常見的靜態服務器就是幹這件事情的。nginx
若是要動態生成HTML,就須要把上述步驟本身來實現。不過,接受HTTP請求、解析HTTP請求、發送HTTP響應都是苦力活,若是咱們本身來寫這些底層代碼,還沒開始寫動態HTML呢,就得花個把月去讀HTTP規範。web
正確的作法是底層代碼由專門的服務器軟件實現,咱們用Python專一於生成HTML文檔。由於咱們不但願接觸到TCP鏈接、HTTP原始請求和響應格式,因此,須要一個統一的接口,讓咱們專心用Python編寫Web業務。apache
這個接口就是WSGI:Web Server Gateway Interface。django
WSGI接口定義很是簡單,它只要求Web開發者實現一個函數,就能夠響應HTTP請求。咱們來看一個最簡單的Web版本的「Hello, web!」:json
def application(environ, start_response): start_response('200 OK', [('Content-Type', 'text/html')]) return '<h1>Hello, web!</h1>'
上面的application()
函數就是符合WSGI標準的一個HTTP處理函數,它接收兩個參數:
在application()
函數中,調用:
start_response('200 OK', [('Content-Type', 'text/html')])
就發送了HTTP響應的Header,注意Header只能發送一次,也就是隻能調用一次start_response()
函數。start_response()
函數接收兩個參數,一個是HTTP響應碼,一個是一組list
表示的HTTP Header,每一個Header用一個包含兩個str
的tuple
表示。
一般狀況下,都應該把Content-Type
頭髮送給瀏覽器。其餘不少經常使用的HTTP Header也應該發送。
而後,函數的返回值'<h1>Hello, web!</h1>'
將做爲HTTP響應的Body發送給瀏覽器。
有了WSGI,咱們關心的就是如何從environ
這個dict
對象拿到HTTP請求信息,而後構造HTML,經過start_response()
發送Header,最後返回Body。
整個application()
函數自己沒有涉及到任何解析HTTP的部分,也就是說,底層代碼不須要咱們本身編寫,咱們只負責在更高層次上考慮如何響應請求就能夠了。
不過,等等,這個application()
函數怎麼調用?若是咱們本身調用,兩個參數environ
和start_response
咱們無法提供,返回的str
也無法發給瀏覽器。
因此application()
函數必須由WSGI服務器來調用。有不少符合WSGI規範的服務器,咱們能夠挑選一個來用。可是如今,咱們只想儘快測試一下咱們編寫的application()
函數真的能夠把HTML輸出到瀏覽器,因此,要趕忙找一個最簡單的WSGI服務器,把咱們的Web應用程序跑起來。
好消息是Python內置了一個WSGI服務器,這個模塊叫wsgiref,它是用純Python編寫的WSGI服務器的參考實現。所謂「參考實現」是指該實現徹底符合WSGI標準,可是不考慮任何運行效率,僅供開發和測試使用。
運行WSGI服務
咱們先編寫hello.py
,實現Web應用程序的WSGI處理函數:
而後,再編寫一個server.py
,負責啓動WSGI服務器,加載application()
函數:
確保以上兩個文件在同一個目錄下,而後在命令行輸入python server.py
來啓動WSGI服務器:

注意:若是8000
端口已被其餘程序佔用,啓動將失敗,請修改爲其餘端口。
啓動成功後,打開瀏覽器,輸入http://localhost:8000/
,就能夠看到結果了:

在命令行能夠看到wsgiref打印的log信息:

按Ctrl+C
終止服務器。
若是你以爲這個Web應用太簡單了,能夠稍微改造一下,從environ
裏讀取PATH_INFO
,這樣能夠顯示更加動態的內容:
你能夠在地址欄輸入用戶名做爲URL的一部分,將返回Hello, xxx!
:

是否是有點Web App的感受了?
小結
不管多麼複雜的Web應用程序,入口都是一個WSGI處理函數。HTTP請求的全部輸入信息均可以經過environ
得到,HTTP響應的輸出均可以經過start_response()
加上函數返回值做爲Body。
複雜的Web應用程序,光靠一個WSGI函數來處理仍是太底層了,咱們須要在WSGI之上再抽象出Web框架,進一步簡化Web開發。
WSGI是做爲Web服務器與Web應用程序或應用框架之間的一種低級別的接口 。WSGI is the Web Server Gateway Interface. It is a specification for web servers and application servers to communicate with web applications (though it can also be used for more than that
WSGI有兩方:「服務器 」或「網關」一方,以及「應用程序」或「應用框架」一方。WSIG架構:

WSGI 中間件 同時實現了API的兩方,所以能夠在WSGI服務和WSGI應用之間起調解做用,「中間件」組件能夠執行如下功能:
- 重寫環境變量 後,根據目標URL ,將請求消息路由到不一樣的應用對象。
- 容許在一個進程 中同時運行多個應用程序或應用框架。
- 負載均衡 和遠程處理,經過在網絡 上轉發請求和響應消息。
- 進行內容後處理,例如應用XSLT 樣式表。
用Python 語言寫的一個符合WSGI的「Hello World 」應用程序以下所示:
- def hello_world_app(environ, start_response):
- start_response('200 OK', [('Content-Type', 'text/plain')])
- return "Hello world!\n"
- 第一行定義了一個名爲app的callable, 接受兩個參數,environ和start_response,environ是一個字典包含了CGI中的環境變量,start_response也是一 個callable,接受兩個必須的參數,status(HTTP狀態)和response_headers(響應消息的頭)。
- 第二行調用了start_response,狀態指定爲「200 OK」,消息頭指定爲內容類型是「text/plain」
- 第三行將響應消息的消息體返回。
調用這個程序
- from wsgiref.simple_server import make_server
- httpd = make_server('', 8080, hello_world_app)
- print "Serving on port 8080..."
-
- httpd.serve_forever()
完整的代碼:
-
- from wsgiref.simple_server import make_server
-
- def hello_world_app(environ, start_response):
- status = '200 OK'
- headers = [('Content-type', 'text/plain')]
- start_response(status, headers)
-
-
- return ["Hello World"]
-
- httpd = make_server('', 8080, hello_world_app)
- print "Serving on port 8080..."
-
- httpd.serve_forever()
訪問地址: http://localhost:8080/
參考
http://smartzxy.iteye.com/blog/734050
http://zh.wikipedia.org/wiki/Web%E6%9C%8D%E5%8A%A1%E5%99%A8%E7%BD%91%E5%85%B3%E6%8E%A5%E5%8F%A3
更多關於BaseServer的解釋:
BaseServer: 定義基礎服務器接口,這些功能接口提供給子類繼承。同時提供服務處理的骨架
serve_forever() 循環調用 handle_request()
handle_request() 調用子類的get_request() ,在tcpServer時實則進行accept()應答; 驗證處理 verify_request();
最終處理請求 process_request(),
verify_request() 虛函數
process_request() 虛函數,這個函數並無直接在BaseServer的子類TcpServer中被重載,而是在TcpServer的派生類中經過另外一個父類來實
現,好比 ThreadingTCPServer的基類ThreadingMixIn.process_request()實現了此功能函數
finish_request(request, client_address) 執行一次完整的socket數據讀入處理,若是是ThreadMixInTcpServer產生的request,這個方法內必須實行循環讀取 socket數據,直到socket關閉。(此處 request 就是 socket對象)
def finish_request(self, request, client_address):
「」"Finish one request by instantiating RequestHandlerClass.」"」
self.RequestHandlerClass(request, client_address, self)在finish_request裏面便將讀取socket數據的任務扔給了RequestHandler去處理了,代碼能夠跳過去看了
##———————————————
TcpServer: tcp服務器
__init__(self, server_address, RequestHandlerClass) 須要提供服務偵聽地址和請求處理類對象
server_bind() 綁定服務器地址
server_activate() 激活服務器
server_close() 關閉服務器
fileno() 返回服務器socket的句柄fd編號
get_request() 接收應答accept()
close_request(request) 關閉socket,request即爲socket對象
三種輸出處理方式: 阻塞方式、線程處理(ThreadingMixIn)、進程處理(ForkingMixIn)
ThreadingMixIn: 線程模型
process_request( request, client_address) 爲請求的連接建立新的線程,在建立線程時直接指定線程入口和參數:
import threading
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
if self.daemon_threads:
t.setDaemon (1)process_request_thread() 線程處理socket入口,負責接收數據,代碼實現有點繞,看看代碼
def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
self.close_request(request)
except:
self.handle_error(request, client_address)
self.close_request(request)ThreadingMixIn其實就是線程代理, 仍是調用finish_request()進入處理tcp數據的循環,處理完成便close_request()。可是finish_request和 close_request並未在ThreadingMinxIn內定義,在哪裏呢? 經過研讀ThreadingTcpServer,原來經過ThreadingTcpServer這個finish_request又跑回了 BaseServer.finish_request()
ThreadingTCPServer(ThreadingMixIn, TCPServer) 裝配成線程池處理的tcp服務器
BaseRequestHandler: 請求處理基礎對象,提供統一的行爲接口實現處理socket數據。 BaseRequestHandler比較好玩,在構造函數內完成了全部的操做,見代碼: def __init__(self, request, client_address, server):
self.request = request
self.client_address = client_address
self.server = server
try:
self.setup()
self.handle()
self.finish()
finally:
sys.exc_traceback = None # Help garbage collectionsetup()對應的子類會進行初始化處理
self.handle() 直接調用子類的處理函數,能夠參考 BaseHTTPRequestHandler(SocketServer.StreamRequestHandler)::handle()
StreamRequestHandler(BaseRequestHandler) 流式socket處理類
setup() 設置好socket對象和讀寫文件句柄 rfile/wfile
HTTPServer(SocketServer.TCPServer) http服務器
BaseHTTPRequestHandler(SocketServer.StreamRequestHandler) 流式的請求處理類
handle() 處理入口,在基類BaseRequestHandle()的構造函數中直接調用
handle_one_request() 若是不是處理一次則返回false。接收一次socket數據,解析parse_request(),調用對應的do_xxx事件
python 的daemon線程:
若是一個進程的主線程運行完畢而子線程還在執行的話,那麼進程就不會退出,直到全部子線程結束爲止,如何讓主線程結束的時候其餘子線程也乖乖的跟老 大撤退呢?那就要把那些不聽話的人設置爲聽話的小弟,使用線程對象的setDaemon()方法,參數爲bool型。True的話就表明你要聽話,我老大 (主線程)扯呼,你也要跟着撤,不能拖後腿。若是是False的話就不用那麼聽話了,老大容許大家將在外軍命有所不受的。須要注意的是 setDaemon()方法必須在線程對象沒有調用start()方法以前調用,不然沒效果。
Python-WSGI詳解彙總
(2013-07-22 14:41:02)
WSGI是什麼
WSGI(Web Server Gateway Interface)是一種規範
參見PEP 333 http://www.python.org/dev/peps/pep-0333/
WSGI Server有哪些
好比 Django、CherryPy 都自帶 WSGI server 主要是測試用途, 發佈時則使用生產環境的 WSGI server
而有些 WSGI 下的框架好比 pylons、bfg 等, 本身不實現 WSGI server。使用 paste 做爲 WSGI server
CherryPy's WSGI server.
wsgi有兩方,服務器方 和 應用程序
①服務器方:其調用應用程序,給應用程序提供(環境信息)和(回調函數), 這個回調函數是用來將應用程序設置的http header和status等信息傳遞給服務器方.
②應用程序:用來生成返回的header,body和status,以便返回給服務器方。
用Python語言寫的一個符合WSGI的「Hello World」應用程序以下所示:
def app(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
yield "Hello world!\n"
其中
- 第一行定義了一個名爲app的應用程序,接受兩個參數,environ和 start_response,environ是一個字典包含了CGI中的環境變量,start_response也是一個callable,接受兩個必 須的參數,status(HTTP狀態)和response_headers(響應消息的頭)。
- 第二行調用了start_response,狀態指定爲「200 OK」,消息頭指定爲內容類型是「text/plain」
- 第三行將響應消息的消息體返回。
那通俗點來將的話:
wsgi中的服務器方:咱們能夠理解成是webserver,固然這個webserver能夠是外置的,好比lighttpd,也能夠是python本身寫的。
而應用程序說白了就是:請求的統一入口!全部的請求都進入到這個app中來處理! 這個app說白了就是一個函數!!(類中的__call__是同樣的道理)
Python自帶的 wsgiref
WSGI application
一個接口與兩個參數
application(environ, start_response)
Demo
[python] view plaincopy
#! /usr/bin/env python
# Our tutorial's WSGI server
from wsgiref.simple_server import make_server
def application(environ, start_response):
# Sorting and stringifying the environment key, value pairs
response_body = ['%s: %s' % (key, value)
for key, value in sorted(environ.items())]
response_body = '\n'.join(response_body)
status = '200 OK'
response_headers = [('Content-Type', 'text/plain'),
('Content-Length', str(len(response_body)))]
start_response(status, response_headers)
return [response_body]
# Instantiate the WSGI server.
# It will receive the request, pass it to the application
# and send the application's response to the client
httpd = make_server(
'localhost', # The host name.
8051, # A port number where to wait for the request.
application # Our application object name, in this case a function.
)
httpd.serve_forever()
wsgiref的simple_server說明
server的主要做用是接受client的請求,並把的收到的請求交給RequestHandlerClass處理,
RequestHandlerClass處理完成後回傳結果給client
WSGIServer繼承關係
WSGIServer-->HTTPServer-->SocketServer.TCPServer-->BaseServer
主要處理流程
serve_forever
_handle_request_noblock()
process_request
finish_request--> RequestHandlerClass()
close_request
serve_forever循環接受client請求, 若是有請求來,
經finish_request方法把請求交給RequestHandlerClass處理,
RequestHandlerClass調用handle()方法處理request,
WSGIRequestHandler的handle()方法把request又交給ServerHandler處理,
ServerHandler調用run執行application方法, 回傳網頁的結果(含http header及網頁內容)給client
WSGIRequestHandler繼承關係
WSGIRequestHandler-->BaseHTTPRequestHandler-->StreamRequestHandler-->BaseRequestHandler
BaseRequestHandler主要方法及處理流程
一、setup()
二、handle()
三、finish()
WSGIRequestHandler主要方法及處理流程
一、get_environ 增長env
二、handle (override)
handler = ServerHandler
handler.run(self.server.get_app())
ServerHandler繼承關係
ServerHandler-->SimpleHandler-->BaseHandler
run方法
setup_environ
self.result = application(self.environ, self.start_response)
self.finish_response
過去的這個月,接觸的最多的就是Python的WSGI了,WSGI不是框架不是模塊,僅僅是一個規範協議,定義了一些接口,卻影響着Python網絡開發的方方面面。對於WSGI有這麼一段定義:WSGI is the Web Server Gateway Interface. It is a specification for web servers and application servers to communicate with web applications (though it can also be used for more than that).我想我這篇文章不是詳細介紹WSGI內容的,只是想扯扯我對WSGI相關的學習。
誠如那個WSGI的定義所說的,協議定義了一套接口來實現服務器端與應用端通訊的規範化(或者說是統一化)。這是怎樣的一套接口呢?很簡單,尤爲是對於應用端。
應用端只須要實現一個接受兩個參數的,含有__call__方法的,返回一個可遍歷的含有零個或多個string結果的Python對象(我強調說 Python對象,只是想和Java的對象區別開,在Python裏一個方法、一個類型……都是對象,Python是真「一切皆對象」,詳見 《Python源碼分析》)便可。碼農都知道,傳入參數的名字能夠任意取,這裏也不例外,但習慣把第一個參數命名爲「environ」,第二個爲 「start_response」。至於這個對象的內容怎樣,應用自由發揮去吧……
服務器端要作的也不復雜,就是對於每個來訪的請求,調用一次應用端「註冊」的那個協議規定應用端必需要實現的對象,而後返回相應的響應消息。這樣一次 服務器端與應用端的通訊也就完成了,一次對用戶請求的處理也隨之完成了!固然了,既然協議規定了服務器端在調用的時候要傳遞兩個參數,天然也規定了這兩個 參數的一些細節。好比第一個參數其實就是一個字典對象,裏面是全部從用戶請求和服務器環境變量中獲取的信息內容,協議固然會定義一些必須有的值,及這些值 對應的變量名;第二個參數其實就是一個回調函數,它嚮應用端傳遞一個用來生成響應內容體的write對象,這個對象也是有__call__方法的。
協議也提到了,還能夠設計中間件來鏈接服務器端與應用端,來實現一些通用的功能,好比session、routing等。
具體怎麼應用這個協議呢?Python自帶的wsgiref模塊有個簡單的例子:
- from wsgiref.simple_server import make_server
-
- def hello_world_app(environ, start_response):
- status = '200 OK' # HTTP Status
- headers = [('Content-type', 'text/plain')] # HTTP Headers
- start_response(status, headers)
-
- # The returned object is going to be printed
- return ["Hello World"]
-
- httpd = make_server('', 8000, hello_world_app)
- print "Serving on port 8000..."
-
- # Serve until process is killed
- httpd.serve_forever()
這個例子更多體現的是應用端的開發方法,很簡單的按照協議實現一個了知足規範的方法,這樣當瀏覽器向本機8000端口發起一個請求時,就會獲得一個 「Hello World」的字符串文本響應。這個例子雖然簡單,但很是清楚的說明了應用端與服務器端的接口應用方式。
你可能會想到:如今對該端口的不一樣地址的請求都是由這個「hello_world_app」函數處理的,你能夠實現一個功能,解析一下請求的PATH信 息,針對不一樣的地址轉發給不一樣的函數或是類來處理;你可能會以爲使用environ和start_response這兩個參數不直觀,你能夠像Java的 servlet那樣本身封裝成兩個request和response對象來用;你以爲有些經常使用功能能夠提取出來,在具體應用邏輯以外來作……哈哈,那你就 已經在思考怎麼作中間件或是Web框架了!其實這些也都有人作過了,好比Routes、WebOb、Beaker……固然你大能夠本身造本身獨有的輪子, 有時候本身作過一遍了纔會對現有的成熟的東西有更好的理解,最重要的是在Python的世界裏這些都不難作到!
不知你是否是和我同樣,在寫應用的時候或多或少的會想一下服務器端是怎麼運做的呢?可能最模糊的流程你們都能想獲得:服務器開一個socket等待客戶端 鏈接;請求來了,服務器會讀出傳來的數據,而後根據HTTP協議作一些初步的封裝,接着就能夠調用事先註冊的應用程序了,並將請求的數據塞進去;等響應處 理完畢了再把數據經過socket發出去,over。好在Python的代碼簡潔,而自帶的wsgiref中的simple server也很簡單,就讓咱們探究一下更具體的實現吧!
首先看一下類的繼承關係,這個simple server真正的類是WSGIServer,繼承自HTTPServer,HTTPServer類又繼承自TCPServer,TCPServer又繼 承自BaseServer;與server類直接打交道的還有RequestHandler類,從最上層的WSGIRequestHandler —> BaseHTTPRequestHandler —> StreamRequestHandler —> BaseRequestHandler。相對Java而言不是很複雜吧,它們是怎麼工做的呢?容我稍微解釋一下。
讓咱們從Server的最基類BaseServer看起。它有一段註釋很是清楚的介紹了它定義的方法的用處:
- Methods for the caller:
-
- - __init__(server_address, RequestHandlerClass)
- - serve_forever()
- - handle_request() # if you do not use serve_forever()
- - fileno() -> int # for select()
-
- Methods that may be overridden:
-
- - server_bind()
- - server_activate()
- - get_request() -> request, client_address
- - verify_request(request, client_address)
- - server_close()
- - process_request(request, client_address)
- - close_request(request)
- - handle_error()
-
- Methods for derived classes:
-
- - finish_request(request, client_address)
可見,一個server類其實就這麼幾個方法。
在能夠被外部調用的四個方法中,構造方法顯然就是用來建立實例的;第四個多是和構建異步服務器有關的,這裏就略過了;從具體的代碼能夠看到,剩下兩個 方法的用處是相同的,就是處理收到的請求,只是serve_forever()方法會在server進程存在期間循環處理,而 handle_request()處理一次就退出了(其實server_forever()就是循環調用了handle_request())。在 handle_request()中說明了具體的從接受到返回一個請求的所有流程,代碼也很簡單:
- def handle_request(self):
- """Handle one request, possibly blocking."""
- try:
- request, client_address = self.get_request()
- except socket.error:
- return
- if self.verify_request(request, client_address):
- try:
- self.process_request(request, client_address)
- except:
- self.handle_error(request, client_address)
- self.close_request(request)
BaseServer雖然定義了這些內部調用的方法,但內容基本都是空的,留給了具體的Server類去實現。從BaseServer的代碼中就能夠看到 RequestHandler類的用處了,它是具體的解析了request的內容,它由finish_request()調用,而這個 finsh_request()方法顯然應該是在process_request()方法中被調用的。
TCPServer繼承BaseServer類,它真正具體化了咱們猜想的socket鏈接的初始化過程。
在與上面兩個類相同的源文件中,還有兩個主要的類:ThreadingMixIn和ForkingMixIn,這兩個類分別重載了 process_request()方法,而且相應使用了新建一個線程或是進程的方式來調用finish_request()方法。這也從應用的角度解釋 了爲何要在finish_request()外套一層process_request(),而不是直接在handle_request()的第二個 try塊中調用。
HTTPServer其實作的工做很簡單,就是記錄了socket server的名字。
接下來就該看看WSGIServer了。它作了兩件新的工做:設置了一些基本的環境變量值,而且接受應用程序的註冊。從這個Server的代碼能夠看出, 應用端實現的那個接口就是從這裏註冊到服務器端的,並且只能註冊一個哦!因此要有多個應用只能經過routing的方式來轉發調用了。並且這個 WSGIServer不是多線程或是多進程的~
至於具體封裝請求內容的RequestHandler類就不打算分析了,感興趣的話,看官們自個看一下源碼吧,也很簡單哦!下一篇博客打算分享一下我對pylons框架的運行過程的學習。
原文地址 :http://www.javaeye.com/topic/734050
當你在Python的世界中冒險,忽然遭遇一隻Web怪獸,你會選擇什麼武器對付它?在兵器譜上,下列兵器可謂名列前茅:
- Zope,厚重的長槍。較早出現的武器形態。該武器很是之強悍,無堅不摧,無物不破。並且適合在軍隊中使用,排兵佈陣集團做戰效果更佳。然而要用好Zope要花上很長的時間,俗話說「月棍年刀一生槍」,可見其難度。
- TurboGears,威武的刀。快意江湖必備之物。其打造者熟知江湖規矩,很有武林盟主之風,遇事一般拉幫結夥,分派任務,決計不會把全部事情都攬在本身身上。
- Django,飄逸的劍。很是內斂,聽說使用該武器的高手一般是獨行俠,他們的格言是:一劍在手,夫復何求?
- Web.py,小巧的匕首,刺客的最愛。常被用來執行特殊任務。
- pylons,詭異的鞭, 傳言是Ruby世界的rails披上了Python的外衣,使用起來必定要當心,由於你不知道它會纏住敵人的脖子仍是本身的脖子。
然而,咱們今天要說的並非這些武器,而是一種心法。畢竟武器的使用只是「招法」,而心法是招法的靈魂,心法一通,招法百通。這就是由馬里奧創造的「管道心法」,西方大陸稱其爲WSGI(Python web服務網關接口)。
馬里奧是一位水管工,常年鑽在水管中苦心研究武術。馬里奧發現,其實武器無所謂高下,最重要的是看使用武器的人和你要對付的對象。所謂一寸長,一份 強,如Zope威力強大,用來對付大型怪獸很合適,卻不免滯重;而一寸短,一份險,如web.py在應付小型靈敏怪獸時有其獨特優點。因此單單評論武器的 優劣根本是空泛之談。因而乎,馬里奧在水管中左思右想十餘載,終於發現了適用於全部武器的心法。掌握此心法,使用任何武器都能遊刃有餘。因爲馬里奧是在水 管中受到了啓發,故命名爲「管道心法」。本文做者在遊歷時有幸發現此心法,並在「心內求法」上流傳出來。傳說上古時期的大神道格拉斯·麥克羅伊在參與創世時,曾經構築了稱爲Pipeline的時空奇點,用以鏈接stdout和stdin。馬里奧是否受此啓發咱們不得而知,但「管道心法」確實與此有相似之處:
WSGI是馬里奧在探索管道的時候發現的一種鏈接件,它很是簡單,入口處提供一個start_response,用於迴流(回 調,callback),入口會鏈接到出口的一個函數,並傳遞environ字典和start_response做爲參數;而出口處的函數先是調用 start_response並傳遞status和header,而後再返回content。因爲這段心法有些拗口,馬里奧演示了惟一的招式,並聲明其實 全部的招式均可以從這招中演化出來:
def application(environ, start_response):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
start_response(status, response_headers)
return ['Hello']
馬里奧還發現,WSGI其實能夠串聯起來,爲了區分,馬里奧將下水管的入口叫作web server,只能接受外界的請求並調用下一段管件的函數;中間的管件叫作middleware,既能夠接收上一段管件的請求,又能夠調用下一段管件的函數;管道的終點叫作web app,只能被上一段管件調用。
爲了不後人誤解,馬里奧最後強調:武器是死的,好的武器只有在合適的人手裏才能發揮最大的威力,爭執於武器的好壞毫無心義。
馬里奧最後將這段心法奉獻給了PEP,PEP將其編號爲333。
Linux環境Apache+Django+wsgi配置
在 Django的開發環境,咱們簡單的經過「python manage.py runserver 0.0.0.0:8000」就能夠啓動一個簡單的HTTP服務器進行Django的開發。當項目開發完成進行發佈的時候,這個簡單的應用服務器就不能知足 需求了。這時候一個比較好的方案是把Django應用集成到Apache。
Django已經爲咱們作了許多工做,因此將Django集成到Apache是很是簡單的事情。Django集成到Apache有兩種方式:python_mod和wsgi,後者相對於前者更加穩定,因此這裏咱們經過wsgi的方式來進行集成。
推薦閱讀:
Ubuntu Server 12.04 安裝Nginx+uWSGI+Django環境 http://www.linuxidc.com/Linux/2012-05/60639.htm
Django實戰教程 http://www.linuxidc.com/Linux/2013-09/90277.htm
Django Python MySQL Linux 開發環境搭建 http://www.linuxidc.com/Linux/2013-09/90638.htm
--------------------------------分割線--------------------------------
先介紹一下個人環境:
0.CentOS X64
1.Apache 2.2.3
2.Django 1.6.1
集成第一步:安裝mod_wsgi
yum insall python26-mod_wsgi.x86_64
安裝完成以後檢查Apache目錄/etc/httpd/conf.d/會出現python26-mod_wsgi.conf,裏邊已經自動爲咱們好了加載mod_wsgi.so的配置:
#################################################################################
# Do not enable mod_python and mod_wsgi in the same apache process.
#################################################################################
#
# NOTE: By default python26-mod_python with not load if mod_wsgi is installed
# and enabled. Only load if mod_python and mod_wsgi are not already loaded.
<IfModule !python_module>
<IfModule !wsgi_module>
LoadModule wsgi_module modules/python26-mod_wsgi.so
</IfModule>
</IfModule>
集成第二步:編輯python26-mod_wsgi.conf
WSGIScriptAlias / "/search/lizhigang/mysite/mysite/wsgi.py"
WSGIPythonPath /search/lizhigang/mysite
<Directory "/search/lizhigang/mysite/mysite">
<Files wsgi.py>
Order deny,allow
Allow from all
</Files>
</Directory>
這裏須要說明一下,個人Django工程位於「/search/lizhigang/mysite/」,請根據本身工程的位置進行替換。
集成第三步:重啓Apache
service apache restart
在瀏覽器訪問你的Django應用(不是8000端口,而是Apache的端口),正常狀況下這時候會提示「500,服務器內部錯誤」。
檢查/etc/httpd/logs/error_log,看是否有以下錯誤:
[Errno 13] Permission denied: '/var/www/.python-eggs'
這時候須要編輯「/search/lizhigang/mysite/mysite/wsgi.py」,對「PYTHON_EGG_CACHE」進行設置:
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "mysite.settings")
os.environ.setdefault("PYTHON_EGG_CACHE", "/tmp/.python-eggs")
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
刷新瀏覽器,這時候Django就成功集成到Apache了。
可是,若是你的應用使用了靜態資源,好比圖片、css、js,這些,就須要配置static了。咱們假設這些資源均位於mysite/static目錄。
集成第四步:配置static
打開python26-mod_wsgi.conf,加入對static/訪問的支持:
Alias /static/ /search/lizhigang/mysite/static/
<Directory "/static/">
Order allow,deny
Options Indexes
Allow from all
IndexOptions FancyIndexing
</Directory>
刷新網站試試,是否是全部功能都與8000端口開發相同?
至此,咱們就完成了Django到Apache的集成。
WSGI是什麼?
WSGI,全稱 Web Server Gateway Interface,或者 Python Web Server Gateway Interface ,是爲 Python 語言定義的 Web 服務器和 Web 應用程序或框架之間的一種簡單而通用的接口。自從 WSGI 被開發出來之後,許多其它語言中也出現了相似接口。
WSGI 的官方定義是,the Python Web Server Gateway Interface。從名字就能夠看出來,這東西是一個Gateway,也就是網關。網關的做用就是在協議之間進行轉換。
WSGI 是做爲 Web 服務器與 Web 應用程序或應用框架之間的一種低級別的接口,以提高可移植 Web 應用開發的共同點。WSGI 是基於現存的 CGI 標準而設計的。
不少框架都自帶了 WSGI server ,好比 Flask,webpy,Django、CherryPy等等。固然性能都很差,自帶的 web server 更多的是測試用途,發佈時則使用生產環境的 WSGI server或者是聯合 nginx 作 uwsgi 。
WSGI的做用
WSGI有兩方:「服務器」或「網關」一方,以及「應用程序」或「應用框架」一方。服務方調用應用方,提供環境信息,以及一個回調函數(提供給應用程序用來將消息頭傳遞給服務器方),並接收Web內容做爲返回值。
所謂的 WSGI中間件同時實現了API的兩方,所以能夠在WSGI服務和WSGI應用之間起調解做用:從WSGI服務器的角度來講,中間件扮演應用程序,而從應用程序的角度來講,中間件扮演服務器。「中間件」組件能夠執行如下功能:
- 重寫環境變量後,根據目標URL,將請求消息路由到不一樣的應用對象。
- 容許在一個進程中同時運行多個應用程序或應用框架。
- 負載均衡和遠程處理,經過在網絡上轉發請求和響應消息。
- 進行內容後處理,例如應用XSLT樣式表。
WSGI 的設計確實參考了 Java 的 servlet。http://www.python.org/dev/peps/pep-0333/ 有這麼一段話:
By contrast, although Java has just as many web application frameworks available, Java's "servlet" API makes it possible for applications written with any Java web application framework to run in any web server that supports the servlet API.
前面小節《來了解一下WSGI這個概念》已經詳細介紹過 WSGI 了。
WSGI is the Web Server Gateway Interface. It is a specification for web servers and application servers to communicate with web applications (though it can also be used for more than that)
WSGI是一種Web服務器網關接口。它是一個Web服務器(如nginx)與應用服務器(如uWSGI服務器)通訊的一種規範。
接下來,咱們要介紹的是 uWSGI。
uWSGI
uWSGI是一個Web服務器,它實現了WSGI協議、uwsgi、http等協議。Nginx中HttpUwsgiModule的做用是與uWSGI服務器進行交換。
要注意 WSGI / uwsgi / uWSGI 這三個概念的區分。
- WSGI看過前面小節的同窗很清楚了,是一種通訊協議。
- uwsgi同WSGI同樣是一種通訊協議。
- 而uWSGI是實現了uwsgi和WSGI兩種協議的Web服務器。
uwsgi協議是一個uWSGI服務器自有的協議,它用於定義傳輸信息的類型(type of information),每個uwsgi packet前4byte爲傳輸信息類型描述,它與WSGI相比是兩樣東西。
關於uwsgi協議看這裏:The uwsgi protocol。
uWSGI 的安裝很簡單:
如今咱們試下將 Django 跑起來。咱們先在 virtualenv 建立一個 Django Project:
2 |
[root@nowamagic nowamagic_venv] |
3 |
(nowamagic_venv)[root@nowamagic nowamagic_venv] |
virtualenv 的路徑與目錄文件以下:
Django Project 的路徑與目錄文件以下:
測試uwsgi
在你的服務器上寫一個test.py:
2 |
def application(env, start_response): |
3 |
start_response( '200 OK' , [( 'Content-Type' , 'text/html' )]) |
個人 test.py 的路徑是 /root/nowamagic_venv/nowamagic_pj/test.py,執行如下命令:
2 |
[root@nowamagic nowamagic_venv] |
3 |
(nowamagic_venv)[root@nowamagic nowamagic_venv] |
訪問網頁 http://115.28.0.89:8001/,OK,顯示 Hello World,說明 uwsgi 安裝成功。
測試你的 Django 項目
前面咱們用 django-admin.py startproject nowamagic_pj 建立了一個項目,如今咱們用 Django 自帶的 Web 服務器看看咱們的項目有沒出問題。仍是進入咱們虛擬環境:
2 |
[root@nowamagic nowamagic_venv] |
3 |
(nowamagic_venv)[root@nowamagic nowamagic_venv] |
執行這個命令報錯:No module named django.core.management,緣由應該是裝了多個版本的Python致使的。命令指定文件路徑就行,醜是醜些了:
1 |
(nowamagic_venv)[root@nowamagic nowamagic_venv] |
OK,啓動 Django 自帶的服務器了,咱們再訪問 http://115.28.0.89:8002/,成功顯示:
說明 Djanggo 項目也沒問題。
鏈接Django和uwsgi
最後一步了,咱們要把uwsgi與Django鏈接起來。
編寫django_wsgi.py文件,將其放在與文件manage.py同一個目錄下。個人放在 /root/nowamagic_venv/nowamagic_pj/ 下:
09 |
sys.setdefaultencoding( 'utf8' ) |
11 |
os.environ.setdefault( "DJANGO_SETTINGS_MODULE" , "nowamagic_pj.settings" ) |
13 |
from django.core.handlers.wsgi import WSGIHandler |
14 |
application = WSGIHandler() |
OK,進入虛擬環境執行指令:
2 |
[root@nowamagic nowamagic_venv] |
3 |
(nowamagic_venv)[root@nowamagic nowamagic_venv] |
成功顯示 Django It Works 頁面。
這樣,你就能夠在瀏覽器中訪問你的Django程序了。全部的請求都是通過uwsgi傳遞給Django程序的。
這裏咱們介紹瞭如何把uwsgi與Django鏈接起來,在下一篇將繼續介紹如何將uwsgi與Nginx鏈接。
上一篇介紹了 uWSGI 來部署 Django 程序,但在在生產環境中單單隻有 uWSGI 是不夠的,Nginx是必不可少的工具。
先安裝 Nginx,能夠參照前面的小節:使用RPM安裝Nginx。
Nginx 配置
在 nginx.conf 上加入/修改,個人 server 配置以下(一切從簡……):
03 |
server_name 115.28 . 0.89 ; |
06 |
access_log / home / nowamagic / logs / access.log; |
07 |
error_log / home / nowamagic / logs / error.log; |
11 |
uwsgi_pass 127.0 . 0.1 : 8077 ; |
13 |
include / etc / nginx / uwsgi_params; |
注意保證配置裏寫的目錄 /home/nowamagic/logs/ 和 /home/nowamagic/logs/ 存在,接下來就沒啥問題了,Nginx 配置很簡單。
uWSGI 配置
前面咱們是直接使用命令行來啓動 uWSGI,在實際部署環境中,咱們經常使用的是配置文件的方式,而非命令行的方式。
個人 Django 程序目錄:/root/nowamagic_venv/nowamagic_pj/
這裏讓 Nginx 採用 8077 端口與 uWSGI 通信,請確保此端口沒有被其它程序採用。
uWSGI 支持多種配置文件格式,好比 xml,ini,json 等等均可以。
1. xml 配置
請肯定你在上一節中的django_wsgi.py文件已經存在了。新建一個XML文件:nowamagic_pj.xml,將它放在 /root/nowamagic_venv/nowamagic_pj 目錄下
02 |
<socket> 127.0 . 0.1 : 8077 < / socket> |
05 |
<pythonpath> / root / nowamagic_venv / nowamagic_pj< / pythonpath> |
06 |
<processes> 1 < / processes> |
07 |
<logdate>true< / logdate> |
08 |
<daemonize> / var / log / uwsgi.log< / daemonize> |
09 |
<plugins>python< / plugins> |
而後執行命令:
1 |
uwsgi -x /root/nowamagic_venv/nowamagic_pj/nowamagic_pj.xml |
3 |
/usr/ local /bin/uwsgi -x /root/nowamagic_venv/nowamagic_pj/nowamagic_pj.xml |
加載指定的xml配置文件。當使用命令行參數時,可使用簡化命令「-x」。固然也能夠不簡寫:
1 |
uwsgi --xml /etc/nowamagic.xml |
甚至若是在命令行的最後一個參數以「.xml」結尾,那麼就隱含將加載該xml文件做爲配置。
1 |
uwsgi /etc/nowamagic.xml |
有時候因各類環境問題,-x --xml 命令識別不了,可使用下面的 ini 配置方式:
2. ini 配置
04 |
socket = 127.0 . 0.1 : 8077 |
08 |
wsgi - file = / root / nowamagic_venv / nowamagic_pj / nowamagic_pj / wsgi.py |
09 |
virtualenv = / root / nowamagic_venv |
10 |
chdir = / root / nowamagic_venv / nowamagic_pj |
而後執行命令:
1 |
uwsgi --ini /root/nowamagic_venv/nowamagic_pj.ini& |
uwsgi 這樣就啓動起來了。若是無心外的話,就能在網上訪問你的 Python 項目了。
小插曲
我在配置完 Nginx 和 uWSGI 以後,訪問時顯示 502 錯誤。查看 uWSGI 啓動信息,發現這麼一條:ImportError: No module named django.core.wsgi。
而後推斷,個人 CentOS 上的 Python 版本是 2.4.3,而後進入 virtualenv,執行:
3 |
<<< from django.core.wsgi import get_wsgi_application |
則沒報錯,由於個人虛擬環境裏的 Python 版本是 2.7.5。推斷成立,可是虛擬環境裏的 Django 會默認調用外部環境的 Python。解決方法:在虛擬環境裏 pip install django。
OK,問題解決,一切正常。
附
一些我在配置時用到的命令,免得你去搜索:
1. 關閉 uWSGI:
2 |
killall -s HUP /var/www/uwsgi |
3 |
killall -s HUP /usr/ local /bin/uwsgi |
2. 列出端口占用狀況:
Gunicorn(gunicorn.org)是一個 Python WSGI UNIX 的 HTTP 服務器。這是一個預先叉工人模式,從 Ruby 的獨角獸(Unicorn)項目移植。該 Gunicorn 服務器與各類 Web 框架兼容,只需很是簡單的執行,輕量級的資源消耗,以及至關迅速。它的特色是與 Django 結合緊密,部署特別方便。 缺點也不少,不支持 HTTP 1.1,併發訪問性能不高,與 uWSGI,Gevent 等有必定的性能差距。具體比較能夠參看列舉一些常見的Python HTTP服務器。
但其實 Gunicorn 從設計上就不是充當直接從外界接受請求的服務器,做者在 FAQ 裏明確提到,Gunicorn 應該是在 Nginx 等服務器後面支持應用請求的。
1. Gunicorn設計
Gunicorn 是一個 master 進程,spawn 出數個工做進程的 web 服務器。master 進程控制工做進程的產生與消亡,工做進程只須要接受請求而且處理。這樣分離的方式使得 reload 代碼很是方便,也很容易增長或減小工做進程。 工做進程這塊做者給了很大的擴展餘地,它能夠支持不一樣的IO方式,如 Gevent,Sync 同步進程,Asyc 異步進程,Eventlet 等等。master 跟 worker 進程徹底分離,使得 Gunicorn 實質上就是一個控制進程的服務。
2. Gunicorn源碼結構
從 Application.run() 開始,首先初始化配置,從文件讀取,終端讀取等等方式完成 configurate。而後啓動 Arbiter,Arbiter 是實質上的 master 進程的核心,它首先從配置類中讀取並設置,而後初始化信號處理函數,創建 socket。而後就是開始 spawn 工做進程,根據配置的工做進程數進行 spawn。而後就進入了輪詢狀態,收到信號,處理信號而後繼續。這裏喚醒進程的方式是創建一個 PIPE,經過信號處理函數往 pipe 裏 write,而後 master 從 select.select() 中喚醒。
工做進程在 spawn 後,開始初始化,而後一樣對信號進行處理,而且開始輪詢,處理 HTTP 請求,調用 WSGI 的應用端,獲得 resopnse 返回。而後繼續。
Sync 同步進程的好處在於每一個 request 都是分離的,每一個 request 失敗都不會影響其餘 request,但這樣致使了性能上的瓶頸。
Gunicorn 框架圖