做爲python web開發,咱們首先要弄清楚,到底一個請求過來,發生了什麼事,請求的傳遞是怎麼樣完成的,由nginx是怎麼轉發到uwsgi, uwsgi又是怎樣把請求傳給到咱們的框架(django or falsk)由咱們本身寫的代碼處理,返回數據給客戶端的。所以我做了如下一個粗略的流程圖:html
如下我會逐個步驟從下往上詳細講解,並附上代碼和配置,python
從上面的圖看得出 wsgi server (好比uwsgi) 要和 wsgi application(好比django )交互,uwsgi須要將過來的請求轉給django 處理,那麼uwsgi 和 django的交互和調用就須要一個統一的規範,這個規範就是WSGI WSGI(Web Server Gateway Interface) ,WSGI是 Python PEP333中提出的一個 Web 開發統一規範。nginx
Web 應用的開發一般都會涉及到 Web 框架(django, flask)的使用,各個 Web 框架內部因爲實現不一樣相互不兼容,給用戶的學習,使用和部署形成了不少麻煩。
正是有了WSGI這個規範,它約定了wsgi server 怎麼調用web應用程序的代碼,web 應用程序須要符合什麼樣的規範,只要 web 應用程序和 wsgi server 都遵照 WSGI 協議,那麼,web 應用程序和 wsgi server就能夠隨意的組合。 好比uwsgi+django , uwsgi+flask, gunicor+django, gunicor+flask 這些的組合均可以任意組合,由於他們遵循了WSGI規範。程序員
WSGI 標準中主要定義了兩種角色:web
爲了方便理解,咱們能夠把server具體成 uwsgi, application具體成djangoshell
這裏能夠看到,WSGI 服務器須要調用應用程序的一個可調用對象,這個可調用對象(callable object)能夠是一個函數,方法,類或者可調用的實例,總之是可調用的。django
下面是一個 callable object 的示例,這裏的可調用對象是一個函數:flask
def simple_app(environ, start_response): """Simplest possible application object""" status = '200 OK' response_headers = [('Content-type', 'text/html')] start_response(status, response_headers) return ['Hello World']
這裏,咱們首先要注意,這個對象接收兩個參數:服務器
environ
:請求的環境變量,它是一個字典,包含了客戶端請求的信息,如 HTTP 請求的首部,方法等信息,能夠認爲是請求上下文,
start_response
:一個用於發送HTTP響應狀態(HTTP status )、響應頭(HTTP headers)的回調函數。在返回內容以前必須先調用這個回掉函數併發
上面的 start_response 這個回調函數的做用是用於讓 WSGI Server 返回響應的 HTTP 首部和 HTTP 狀態碼。這個函數有兩個必須的參數,返回的狀態碼和返回的響應首部組成的元祖列表。返回狀態碼和首部的這個操做始終都應該在響應 HTTP body 以前執行。
還須要注意的是,最後的返回結果,應該是一個可迭代對象,這裏是將返回的字符串放到列表裏。若是直接返回字符串可能致使 WSGI 服務器對字符串進行迭代而影響響應速度。
固然,這個函數是一個最簡單的可調用對象,它也能夠是一個類或者可調用的類實例。
def application(env, start_response): start_response('200 OK', [('Content-Type', 'text/html'), ('X-Coder', 'Cooffeeli')]) return ['<h1>你好!!世界</h1>']
from wsgiref.simple_server import make_server from app import application # 啓動 WSGI 服務器 httpd = make_server ( 'localhost', 9000, application # 這裏指定咱們的 application object) ) # 開始處理請求 httpd.handle_request()
python wsgiref_server.py
運行上面的程序,並訪問 http://localhost:9000 , 將返回這次請求全部的首部信息。
這裏,咱們利用 environ 字典,獲取了請求中全部的變量信息,構形成相應的內容返回給客戶端。
environ 這個參數中包含了請求的首部,URL,請求的地址,請求的方法等信息。能夠參考 PEP3333來查看 environ 字典中必須包含哪些 CGI 變量。
既然咱們知道了WSGI的規範,咱們徹底能夠本身實現一個WSGI Server
根據這個規範,咱們能夠總結WSGI Server須要實現如下功能:
env
參數,該參數包括HTTP,wsgi信息,能夠看做是請求上下文start_response
函數,做爲調用application的參數,用做application回調函數,負責http相應頭實現代碼: WSGIServer.py
#!/usr/bin/env python # -*- coding: utf-8 -*- import socket import sys import StringIO from app import application from datetime import datetime class WSGIServer(object): def __init__(self, server_address): """初始構造函數, 建立監聽socket""" self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.listen_sock.bind(server_address) self.listen_sock.listen(5) (host, port) = self.listen_sock.getsockname() self.server_port = port self.server_name = socket.getfqdn(host) def set_application(self, application): """設置wsgi application, 供server 調用""" self.application = application def get_environ(self): """構造WSGI環境變量,傳給application的env參數""" self.env = { 'wsgi.version': (1, 0), 'wsgi.url_scheme': 'http', 'wsgi.errors': sys.stderr, 'wsgi.multithread': False, 'wsgi.run_once': False, 'REQUEST_METHOD': self.request_method, 'PATH_INFO': self.request_path, 'SERVER_NAME': self.server_name, 'SERVER_PORT': str(self.server_port), 'wsgi.input': StringIO.StringIO(self.request_data), } return self.env def start_response(self, http_status, http_headers): """構造WSGI響應, 傳給application的start_response""" self.http_status = http_status self.http_headers = dict(http_headers) headers = { 'Date': datetime.utcnow().strftime('%a, %d %b %Y %H:%M:%S GMT'), 'Server': 'WSGIServer 1.0' } self.http_headers.update(headers) def parse_request(self, text): """獲取http頭信息,用於構造env參數""" request_line = text.splitlines()[0] request_info = request_line.split(' ') (self.request_method, self.request_path, self.request_version) = request_info def get_http_response(self, response_data): """完成response 內容""" res = 'HTTP/1.1 {status} \r\n'.format(status=self.http_status) for header in self.http_headers.items(): res += '{0}: {1} \r\n'.format(*header) res += '\r\n' res_body = '' for val in response_data: res_body += val res += res_body return res def handle_request(self): """處理請求""" # 初始版本,只接受一個請求 conn, addr = self.listen_sock.accept() # 獲取http 請求的request內容 self.request_data = conn.recv(1024) self.parse_request(self.request_data) # 構造調用application須要的兩個參數 env, start_response env = self.get_environ() start_response = self.start_response # 調用application, 並獲取須要返回的http response內容 response_data = self.application(env, start_response) # 獲取完整http response header 和 body, 經過socket的sendall返回到客戶端 res = self.get_http_response(response_data) conn.sendall(res) # 腳本運行完畢也會結束 conn.close() def make_server(server_address, application): """建立WSGI Server 負責監聽端口,接受請求""" wsgi_server = WSGIServer(server_address) wsgi_server.set_application(application) return wsgi_server SERVER_ADDRESS = (HOST, PORT) = '', 8124 wsgi_server = make_server(SERVER_ADDRESS, application) wsgi_server.handle_request()
上面的 WSGI 服務器運行過程爲:
至此, wsgi server -> wsgi application 的交互講解完畢, 下面咱們繼續看nginx->uwsgi交互過程
上面說了咱們本身實現WSGI Server的過程,如今咱們用uwsgi 來做爲Server
運行監聽請求uwsgi
uwsgi --http :9090 --wsgi-file foobar.py --master --processes 4 --threads 2
執行這個命令會產生4個uwsgi進程(每一個進程2個線程),1個master進程,當有子進程死掉時再產生子進程,1個 the HTTP router進程,一個6個進程。
這個Http route進程的地位有點相似nginx,(能夠認爲與nginx同一層)負責路由http請求給worker, Http route進程和worker之間使用的是uwsgi協議
在構建 Web 應用時,一般會有 Web Server (nginx)和 Application Server(wsgi server eg:uwsgi) 兩種角色。其中 Web Server 主要負責接受來自用戶的請求,解析 HTTP 協議,並將請求轉發給 Application Server,Application Server 主要負責處理用戶的請求,並將處理的結果返回給 Web Server,最終 Web Server 將結果返回給用戶。
因爲有不少動態語言和不少種 Web Server,他們彼此之間互不兼容,給程序員形成了很大的麻煩。所以就有了 CGI/FastCGI ,uwsgi 協議,定義了 Web Server 如何經過輸入輸出與 Application Server 進行交互,將 Web 應用程序的接口統一了起來。
總而言之, 這些協議就是進程交互的一種溝通方式。
舉個例子:美國人和中國人溝通必需要有一個公共的語言:英語, 這時候英語就是兩我的溝通的協議, 否則,一個說英語(uwsgi協議), 一個說中文(fastcgi協議)是確定會亂碼的,處理不成功的。用同一個協議,你們都知道該如何解析過來的內容。
因此,nginx 和 uwsgi交互就必須使用同一個協議,而上面說了uwsgi支持fastcgi,uwsgi,http協議,這些都是nginx支持的協議,只要你們溝通好使用哪一個協議,就能夠正常運行了。
將uwsgi 放在nginx後面,讓nginx反向代理請求到uwsgi
uwsgi 原生支持HTTP, FastCGI, SCGI,以及特定的uwsgi協議, 性能最好的明顯時uwsgi, 這個協議已經被nginx支持。
因此uwsgi 配置使用哪一個協議,nginx 要使用對應協議
# 使用http協議 uwsgi --http-socket 127.0.0.1:9000 --wsgi-file app.py
# nginx配置 lcation / { proxy_pass 127.0.0.1:9000; }
更多協議
[uwsgi] # 使用uwsgi協議 socket, uwsgi-socket 都是uwsgi協議 # bind to the specified UNIX/TCP socket using default protocol # UNIX/TCP 意思時能夠UNIX: xx.sock, 或者 TCP: 127.0.0.1:9000 他們是均可以的 # UNIX 沒有走TCP協議,不是面向鏈接, 而是直接走文件IO # nginx 使用uwsgi_pass socket = 127.0.0.1:9000 socket = /dev/shm/owan_web_uwsgi.sock uwsgi-socket = /dev/shm/owan_web_uwsgi.sock # nginx 使用 uwsgi_pass /dev/shm/owan_web_uwsgi.sock; # 使用fastcgi協議 fastcgi-socket # bind to the specified UNIX/TCP socket using FastCGI protocol # nginx 就能夠好象PHP那樣配置 使用fastcgi_pass fastcgi-socket = /dev/shm/owan_web_uwsgi.sock # nginx 使用fastcgi_pass /dev/shm/owan_web_uwsgi.sock; # 使用http協議 http-socket # bind to the specified UNIX/TCP socket using HTTP protocol # nginx 使用proxy_pass # 原來proxy_pass 是http協議,但不必定要用TCP # proxy_pass http://unix:/dev/shm/owan_web_uwsgi.sock; http-socket = /dev/shm/owan_web_uwsgi.sock # nginx 使用 proxy_pass /dev/shm/owan_web_uwsgi.sock; chdir = /data/web/advance_python/uwsgi/ wsgi-file = app.py processes = 4 threads = 2 master = true ...
至此,nginx ->uwsgi ->web 框架 以及 WSGI的相關知識已經講解完了。 須要補充的是,咱們本身實現的WSGI Server只能支持一個請求,在以後的日子,我會再寫一些教程,關於socket IO 複用 和線程池 讓咱們本身寫server支持多請求,多併發的功能