基礎概念
首先要了解 WSGI 規範的概念,WSGI(Web Server Gateway Interface)規範描述了web server(Gunicorn,uWSGI等)如何與web application(flask, django等)互動、web application如何處理請求,定義在 pep 3333。正是有了 WSGI 規範,我們才能在任意 web server 上跑各種 web 應用。WSGI API 定義看起來很簡單:
def application(environ, start_response)
複製程式碼
-
application 就是 WSGI app,一個可呼叫物件
-
引數:
- environ: 一個包含 WSGI 環境資訊的字典,由 WSGI 伺服器提供,常見的 key 有 PATH_INFO,QUERY_STRING 等
- start_response: 生成 WSGI 響應的回撥函式,接收兩個引數,status 和 headers
-
函式返回值為響應體的迭代器 ###簡單舉例 下面舉個簡單的例子,比如一個返回 hello world 的應用:
def application(environ, start_response):
status = '200 OK'
headers = [('Content-Type', 'text/html; charset=utf8')]
start_response(status, headers)
return [b"<h1>Hello, World!</h1>"]
複製程式碼
werkzeug相關
werkzeug是Python實現的WSGI規範的使用函式庫。 正如werkzeug官網Werkzeug上所說,werkzeug使用起來非常簡單,但是卻非常強大。關於使用簡單的這個特性,官網給了一段示例程式碼。
from werkzeug.wrappers import Request, Response
@Request.application
def application(request):
return Response('Hello World!')
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, application)
複製程式碼
###簡單小結
關於上面的程式碼我做一下總結:
application
--可呼叫物件,wsig模組中加括號括號執行
application
的返回值--Response物件,wsgi中會對該物件加括號執行其__call__
方法
一次成功的訪問,由以下幾步完成
- 瀏覽器(client)傳送一個請求(request
- 伺服器(server)接收到請求
- 伺服器處理請求
- 返回處理的結果(response
- 瀏覽器處理返回的結果,顯示出來。
Detail
具體來說:
- wigi相關模組通過建立socket拿到客戶端傳送的資料,然後進行解析,然後封裝到environ中
- web框架比如flask,他拿到environ,執行其內部各種呼叫函式,檢視函式,然後返回Response物件
- wigi相關模組拿到相應的Response物件,執行其__call__方法拿到app_iter物件,進行for迴圈進行socket.sendall(data)方法進行資料傳送 ###原始碼 現在我們開始看一下原始碼:
def run_simple(hostname, port, application, use_reloader=False,
use_debugger=False, use_evalex=True,
extra_files=None, reloader_interval=1,
reloader_type='auto', threaded=False,
processes=1, request_handler=None, static_files=None,
passthrough_errors=False, ssl_context=None):
def log_startup(sock):
display_hostname = hostname not in ('', '*') and hostname or 'localhost'
if ':' in display_hostname:
display_hostname = '[%s]' % display_hostname
quit_msg = '(Press CTRL+C to quit)'
port = sock.getsockname()[1]
_log('info', ' * Running on %s://%s:%d/ %s',
ssl_context is None and 'http' or 'https',
display_hostname, port, quit_msg)
def inner():
try:
fd = int(os.environ['WERKZEUG_SERVER_FD'])
except (LookupError, ValueError):
fd = None
srv = make_server(hostname, port, application, threaded,
processes, request_handler,
passthrough_errors, ssl_context,
fd=fd)
if fd is None:
log_startup(srv.socket)
srv.serve_forever()
inner()
複製程式碼
執行inner
方法
然後執行make_server方法拿到其返回值並賦值給srv
def make_server(host=None, port=None, app=None, threaded=False, processes=1,
request_handler=None, passthrough_errors=False,
ssl_context=None, fd=None):
"""Create a new server instance that is either threaded, or forks
or just processes one request after another.
"""
if threaded and processes > 1:
raise ValueError("cannot have a multithreaded and "
"multi process server.")
elif threaded:
return ThreadedWSGIServer(host, port, app, request_handler,
passthrough_errors, ssl_context, fd=fd)
elif processes > 1:
return ForkingWSGIServer(host, port, app, processes, request_handler,
passthrough_errors, ssl_context, fd=fd)
else:
return BaseWSGIServer(host, port, app, request_handler,
passthrough_errors, ssl_context, fd=fd)
複製程式碼
以BaseWSGIServer類為例,將其例項化就是執行其__init__
方法
因為類的各種繼承,我就不一一細說了:
總的來說:
就是建立socket和定義處理request的類RequestHandleClass
其為:WSGIRequestHandler,自己看一下原始碼找一下吧
複製程式碼
然後執行srv.server_forver
srv
為BaseWSGIServer
的例項,根據類的繼承,去查詢各種方法.
記住一點就是查詢方法優先從自己的類定義中找,如果沒有就去父類中找.時刻謹記self是誰
#BaseWSGIServer中定義
def serve_forever(self):
self.shutdown_signal = False
try:
HTTPServer.serve_forever(self)
except KeyboardInterrupt:
pass
finally:
self.server_close()
複製程式碼
###BaseServer
def serve_forever(self, poll_interval=0.5):
"""Handle one request at a time until shutdown.
Polls for shutdown every poll_interval seconds. Ignores
self.timeout. If you need to do periodic tasks, do them in
another thread.
"""
self.__is_shut_down.clear()
try:
# XXX: Consider using another file descriptor or connecting to the
# socket to wake this up instead of polling. Polling reduces our
# responsiveness to a shutdown request and wastes cpu at all other
# times.
with _ServerSelector() as selector:
selector.register(self, selectors.EVENT_READ)
while not self.__shutdown_request:
ready = selector.select(poll_interval)
if ready:
self._handle_request_noblock()
self.service_actions()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
複製程式碼
# BaseServer
def _handle_request_noblock(self):
"""Handle one request, without blocking.
I assume that selector.select() has returned that the socket is
readable before this function was called, so there should be no risk of
blocking in get_request().
"""
try:
request, client_address = self.get_request()
except OSError:
return
if self.verify_request(request, client_address):
try:
self.process_request(request, client_address)
except Exception:
self.handle_error(request, client_address)
self.shutdown_request(request)
except:
self.shutdown_request(request)
raise
else:
self.shutdown_request(request)
複製程式碼
執行process_request方法
def process_request(self, request, client_address):
"""Call finish_request.
Overridden by ForkingMixIn and ThreadingMixIn.
"""
self.finish_request(request, client_address)
self.shutdown_request(request)
複製程式碼
Next
def finish_request(self, request, client_address):
"""Finish one request by instantiating RequestHandlerClass."""
self.RequestHandlerClass(request, client_address, self)
複製程式碼
執行RequestHandlerClass類的例項化
執行BaseHTTPRequestHandler的handle方法 WSGIRequestHandler.handle_one_request
def handle_one_request(self):
"""Handle a single HTTP request."""
self.raw_requestline = self.rfile.readline()
if not self.raw_requestline:
self.close_connection = 1
elif self.parse_request():
return self.run_wsgi()
複製程式碼
def run_wsgi(self):
if self.headers.get('Expect', '').lower().strip() == '100-continue':
self.wfile.write(b'HTTP/1.1 100 Continue\r\n\r\n')
self.environ = environ = self.make_environ()
headers_set = []
headers_sent = []
def write(data):
assert headers_set, 'write() before start_response'
if not headers_sent:
status, response_headers = headers_sent[:] = headers_set
try:
code, msg = status.split(None, 1)
except ValueError:
code, msg = status, ""
code = int(code)
self.send_response(code, msg)
header_keys = set()
for key, value in response_headers:
self.send_header(key, value)
key = key.lower()
header_keys.add(key)
if not ('content-length' in header_keys or
environ['REQUEST_METHOD'] == 'HEAD' or
code < 200 or code in (204, 304)):
self.close_connection = True
self.send_header('Connection', 'close')
if 'server' not in header_keys:
self.send_header('Server', self.version_string())
if 'date' not in header_keys:
self.send_header('Date', self.date_time_string())
self.end_headers()
assert isinstance(data, bytes), 'applications must write bytes'
self.wfile.write(data)
self.wfile.flush()
def start_response(status, response_headers, exc_info=None):
if exc_info:
try:
if headers_sent:
reraise(*exc_info)
finally:
exc_info = None
elif headers_set:
raise AssertionError('Headers already set')
headers_set[:] = [status, response_headers]
return write
def execute(app): # app_iter物件 包含了需要返回的各項資料
application_iter = app(environ, start_response) # Flask例項的call方法返回的的response物件的__call__方法返回的東西
try:
for data in application_iter:
write(data)
if not headers_sent:
write(b'')
finally:
if hasattr(application_iter, 'close'):
application_iter.close()
application_iter = None
try:
execute(self.server.app)
except (socket.error, socket.timeout) as e:
self.connection_dropped(e, environ)
except Exception:
if self.server.passthrough_errors:
raise
from werkzeug.debug.tbtools import get_current_traceback
traceback = get_current_traceback(ignore_system_exceptions=True)
try:
# if we haven't yet sent the headers but they are set
# we roll back to be able to set them again.
if not headers_sent:
del headers_set[:]
execute(InternalServerError())
except Exception:
pass
self.server.log('error', 'Error on request:\n%s',
traceback.plaintext)
複製程式碼
通過這個程式碼,我們拿到了app執行後拿到的可迭代物件 application_iter = app(environ, start_response) # Flask例項的call方法返回的的response物件的__call__方法返回的可迭代物件
END
最終for迴圈這個物件傳送了資料
for data in application_iter:
write(data)
複製程式碼