簡答描述Web應用程序的本質,就是咱們經過瀏覽器訪問互聯網上指定的網頁文件展現到瀏覽器上。
流程以下圖: html
直接將寫好的html頁面放在服務器上,而後經過瀏覽器訪問指定服務器的文件。python
隨着需求變化靜態開發沒法知足需求,當頁面只有部份內容發生變化時,開發相同的頁面。
一是開發上是一種重複工做
二是數據量變化巨大時,速度慢且數據變化也不是定時更新
爲了應對這種問題,動態網頁技術誕生。早期的動態網頁開發技術是CGI
CGI全程:Common Gateway Interface,通用網關接口,它是一段程序,運行在服務器上如:HTTP服務器,提供同客戶端HTML頁面的接口。
CGI程序能夠是Python腳本,PERL腳本,SHELL腳本,C或C++程序等等。web
WSGI(Web Server Gateway Interface),WSGI既不是服務器,也不是python模塊、框架、API或者任何軟件,他只是一種規範,一種描述web server如何與web application 通訊的規範。要實現WSGI協議,必須同時實現web server和web application,當前運行在WSGI協議之上的web框架有Bottle,Flask,Django。django
uwsgi:與WSGI同樣是一種通訊協議,是uWSGI服務器的獨佔協議,用於定義和傳輸信息的類型(type of information),每個uwsgi packet前4byte爲傳輸信息類型的描述,與WSGI協議是兩種東西。瀏覽器
uWSGI:是一個web服務器,實現了WSGI協議、uwsgi協議、http協議等。服務器
WSGI協議主要包括server和application兩部分:cookie
WSGI協議實際上是定義了一種server與application解耦的規範,便可以有多個實現WSGI server的服務器,也能夠有多個實現WSGI application的框架,那麼就能夠選擇任意的server和application組合實現本身的web應用。例如uWSGI和Gunicorn都是實現了WSGI server協議的服務器,Flask是實現了WSGI application協議的web框架,能夠根據項目實際狀況搭配使用。網絡
由於實現WSGI協議必需要有 WSGI server和application,所以須要實現這兩部份內容。app
應用程序,是一個可重複調用的可調用對象,在Python中能夠是一個函數,也能夠是一個類,若是是類的話要實現__call__方法,要求這個可調用對象接收2個參數,返回一個內容結果。
接收的2個參數分別是environ和start_response.
(1)environ是web服務器解析HTTP協議的一些信息,例如請求方法,請求URL等信息構成一個Dict對象。
(2)start_response是一個函數,接收2個參數,一個是HTTP狀態碼,一個HTTP消息中的響應頭。框架
官網使用WSGI的wsgiref模塊實現的小demo
def simple_app(environ, start_response):
"""Simple possible application object"""
status = '200 OK'
response_headers = [('Content-type', 'text/plain; charset=utf-8')]
start_response(status, response_headers)
return_body = []
for key, value in environ.items():
return_body.append("{}:{}".format(key, value))
return_body.append("\nHello WSGI!")
# 返回結果必須是bytes
return ["\n".join(return_body).encode("utf-8")]
複製代碼
Web服務器,主要是實現相應的信息轉換,將網絡請求中的信息,按照HTTP協議將內容拿出,同時按照WSGI協議組裝成新的數據,同時將提供的start_response傳遞給Application。最後接收Application返回的內容,按照WSGI協議解析出。最終按照HTTP協議組織好內容返回就完成了一次請求。
Server操做的步驟以下:
(1)根據HTTP協議內容構建environ
(2)提供一個start_response函數,接收HTTP STATU和HTTP HEADER
(3)將environ和start_response做爲參數調用Application
(4)接收Application返回的結果
(5)按照HTTP協議,順序寫入HTTP響應頭(start_response接收),HTTP響應體(Application返回結果)
一個實現的server
def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler):
"""Create a new WSGI server listening on host and port for app"""
server = server_class((host, port), handler_class)
server, set_app(app)
return server
複製代碼
WSGI規定每一個Python程序(Application)必須是一個可調用的對象(實現了__call__函數的方法或者類),接受兩個參數environ(WSGI的環境信息)和start_response(開始響應請求的函數),而且返回iterable。幾點說明:
(1)environ和start_response 由 http_server提供並實現
(2)environ變量包含了環境信息的字典
(3)Application內部在返回前調用start_response
(4)start_response也是一個callable,接受兩個必須的參數,status(HTTP狀態)和response_headers(響應消息的頭)
(5)可調用對象要返回一個值,這個值是可迭代的。
# 可調用對象是一個類
class AppClass:
def __init__(self, environ, start_response):
self.environ = environ
self.start = start_response
def __iter__(self):
status = '200 OK'
response_headers = [('Content-type','text/plain')]
self.start(status, response_headers)
yield "Hello World!\n"
複製代碼
標準要可以確切的實行,必需要求程序端和服務器端共同遵照。
(1)準備environ函數
(2)定義start_response函數
(3)調用程序端的可調用對象
import os
import sys
def run_with_cgi(application): # application 是程序端的可調用對象
# 準備 environ 參數,這是一個字典,裏面的內容是一次 HTTP 請求的環境變量
environ = dict(os.environ.items())
environ['wsgi.input'] = sys.stdin
environ['wsgi.errors'] = sys.stderr
environ['wsgi.version'] = (1, 0)
environ['wsgi.multithread'] = False
environ['wsgi.multiprocess'] = True
environ['wsgi.run_once'] = True
environ['wsgi.url_scheme'] = 'http'
headers_set = []
headers_sent = []
# 把應答的結果輸出到終端
def write(data):
sys.stdout.write(data)
sys.stdout.flush()
# 實現 start_response 函數,根據程序端傳過來的 status 和 response_headers 參數,
# 設置狀態和頭部
def start_response(status, response_headers, exc_info=None):
headers_set[:] = [status, response_headers]
return write
# 調用客戶端的可調用對象,把準備好的參數傳遞過去
result = application(environ, start_response)
# 處理獲得的結果,這裏簡單地把結果輸出到標準輸出。
try:
for data in result:
if data: # don't send headers until body appears
write(data)
finally:
if hasattr(result, 'close'):
result.close()
複製代碼
WSGI application應該實現爲一個可調用iter對象,例如函數、方法、類(包含__call__方法)。須要接受兩個參數:
一個字典,該字典能夠包含了客戶端請求的信息以及其餘信息,能夠認爲是請求上下文,通常叫作environment(編碼中多簡寫爲environ、env)。
一個用於發送HTTP響應狀態(HTTP status)、響應頭(HTTP headers)的回調函數,也就是start_response()。經過回調函數將響應狀態和響應頭返回給server,同時返回響應正文(response body),響應正文是可迭代的、而且包含了多個字符串。
下面是Django中application的具體實現部分:
class WSGIHandler(base.BaseHandler):
initLock = Lock()
request_class = WSGIRequest
def __call__(self, environ, start_response):
# 加載中間件
if self._request_middleware is None:
with self.initLock:
try: # Check that middleware is still uninitialized.
if self._request_middleware is None:
self.load_middleware()
except: # Unload whatever middleware we got
self._request_middleware = None
raise
set_script_prefix(get_script_name(environ)) # 請求處理以前發送信號
signals.request_started.send(sender=self.__class__, environ=environ)
try:
request = self.request_class(environ)
except UnicodeDecodeError:
logger.warning('Bad Request (UnicodeDecodeError)', exc_info=sys.exc_info(), extra={'status_code': 400, }
response = http.HttpResponseBadRequest()
else:
response = self.get_response(request)
response._handler_class = self.__class__
status = '%s %s' % (response.status_code, response.reason_phrase)
response_headers = [(str(k), str(v)) for k, v in response.items()]
for c in response.cookies.values():
response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
# server提供的回調方法,將響應的header和status返回給server
start_response(force_str(status), response_headers)
if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
response = environ['wsgi.file_wrapper'](response.file_to_stream)
return response
複製代碼
能夠看出application的流程包括:加載全部的中間件,以及執行框架相關的操做,設置當前線程腳本前綴,發送請求開始信息;處理請求,調用get_response()方法處理當前請求,該方法的主要邏輯是經過urlconf找到對應的view和callback,按順序執行各類middleware和callback。調用由server傳入的start_response()方法將響應header與status返回給server。返回響應正文。
負責獲取http請求,將請求傳遞給WSGI application,由application處理請求後返回response。以Django內建立server爲例看一下具體實現。經過runserver運行Django項目,在啓動時都會調用下面的run方法,建立一個WSGIServer實例,以後再調用其server_forever()方法啓動服務。
def run(addr, port, wsgi_handler, ipv6=False, threading=False):
server_address = (addr, port)
if threading:
httpd_cls = type(str('WSGIServer'), (socketserver.ThreadingMixIn, WSGIServer), {})
else:
httpd_cls = WSGIServer # 這裏的wsgi_handler就是WSGIApplication
httpd = httpd_cls(server_address, WSGIRequestHandler, ipv6=ipv6)
if threading:
httpd.daemon_threads = True
httpd.set_app(wsgi_handler)
httpd.serve_forever()
複製代碼
下面表示WSGI server服務器處理流程中關鍵的類和方法:
run()方法會建立WSGIServer實例,主要做用是接收客戶端請求,將請求傳遞給application,而後將application返回的response返回給客戶端。
django的simple_server.py模塊實現了一個簡單的HTTP服務器,並給出了一個簡單的demo,能夠直接運行,運行結果會將請求中涉及到的環境變量在瀏覽器中展現出來。
其中包括上述描述的整個http請求的全部組件:
ServerHandler, WSGIServer, WSGIRequestHandler,以及demo_app表示的簡易版的WSGIApplication。
if __name__ == '__main__':
# 經過make_server方法建立WSGIServer實例
# 傳入建議application,demo_app
httpd = make_server('', 8000, demo_app)
sa = httpd.socket.getsockname()
print("Serving HTTP on", sa[0], "port", sa[1], "...")
import webbrowser
webbrowser.open('http://localhost:8000/xyz?abc')
# 調用WSGIServer的handle_request方法處理http請求
httpd.handle_request() # serve one request, then exit
httpd.server_close()
def make_server(host, port, app, server_class=WSGIServer, handler_class=WSGIRequestHandler):
"""Create a new WSGI server listening on `host` and `port` for `app`"""
server = server_class((host, port), handler_class)
server.set_app(app)
return server
# demo_app可調用對象,接受請求輸出結果
def demo_app(environ, start_response):
from io import StringIO
stdout = StringIO()
print("Hello world!", file=stdout)
print(file=stdout)
h = sorted(environ.items())
for k, v in h:
print(k, '=', repr(v), file=stdout)
start_response("200 OK", [('Content-Type', 'text/plain; charset=utf-8')])
return [stdout.getvalue().encode("utf-8")]
複製代碼
demo_app()表示一個簡單的WSGI application實現,經過make_server()方法建立一個WSGIServer實例,調用其handle_request()方法,該方法會調用demo_app()處理請求,並最終返回響應。
uWSGI旨在爲部署分佈式集羣的網絡應用開發一套完整的解決方案。主要面向web及其標準服務。因爲其可擴展性,可以被無限制的擴展用來支持更多平臺和語言。uWSGI是一個web服務器,實現了WSGI協議,uwsgi協議,http協議等。
uWSGI的主要特色是: