一、概述
wsgi服務啟動并監聽http請求的流程:
- 1.利用paste.deploy子產品的loadapp函數加載指定服務(如proxy)的配置檔案,擷取到使用者的application,即業務程式
- 2.調用wsgi.server,其中wsgi.server會綁定IP和端口,監聽來自用戶端的消息。并由process_request函數負責處理消息。
下面主要說下處理http請求的過程(其他的在另外的文章中已有講解)
我們都知道wsgi application都需要實作__call__()方法,并且參數必須為environ, start_response;
那麼這兩個參數是從哪裡傳遞進來的呢?下面就來解密下這個。
二、http請求處理
子產品:../site-packages/eventlet/wsgi.py
1、入口函數server
wsgi.py檔案中定義了Server類,用于開啟一個服務端socket,處理socket通信;
檔案中還定義了一個入口函數:server,源碼如下:
def server():
serv = Server(sock, sock.getsockname(),
site, log,
environ=environ,
max_http_version=max_http_version,
protocol=protocol,
minimum_chunk_size=minimum_chunk_size,
log_x_forwarded_for=log_x_forwarded_for,
keepalive=keepalive,
log_output=log_output,
log_format=log_format,
url_length_limit=url_length_limit,
debug=debug,
socket_timeout=socket_timeout,
capitalize_response_headers=capitalize_response_headers,
)
if server_event is not None:
server_event.send(serv)
if max_size is None:
max_size = DEFAULT_MAX_SIMULTANEOUS_REQUESTS
if custom_pool is not None:
pool = custom_pool
else:
pool = greenpool.GreenPool(max_size)
try:
serv.log.info("(%s) wsgi starting up on %s" % (
serv.pid, socket_repr(sock)))
while is_accepting:
try:
client_socket = sock.accept()
client_socket[0].settimeout(serv.socket_timeout)
serv.log.debug("(%s) accepted %r" % (
serv.pid, client_socket[1]))
try:
pool.spawn_n(serv.process_request, client_socket)
except AttributeError:
warnings.warn("wsgi's pool should be an instance of "
"eventlet.greenpool.GreenPool, is %s. Please convert your"
" call site to use GreenPool instead" % type(pool),
DeprecationWarning, stacklevel=2)
pool.execute_async(serv.process_request, client_socket)
except ACCEPT_EXCEPTIONS as e:
if support.get_errno(e) not in ACCEPT_ERRNO:
raise
except (KeyboardInterrupt, SystemExit):
serv.log.info("wsgi exiting")
break
該函數首先建立一個伺服器對象(可以指定協定);
通過外部指定的協程池大小初始化協程池;
最後循環監聽來自用戶端的連接配接:每次收到一個請求,就新開一個協程去處理該請求。
2、請求處理函數process_request
在入口函數server中,調用了process_request方法來處理請求,源碼如下:
def process_request(self, sock_params):
# The actual request handling takes place in __init__, so we need to
# set minimum_chunk_size before __init__ executes and we don't want to modify
# class variable
sock, address = sock_params
proto = new(self.protocol)
if self.minimum_chunk_size is not None:
proto.minimum_chunk_size = self.minimum_chunk_size
proto.capitalize_response_headers = self.capitalize_response_headers
try:
proto.__init__(sock, address, self)
except socket.timeout:
# Expected exceptions are not exceptional
sock.close()
# similar to logging "accepted" in server()
self.log.debug('(%s) timed out %r' % (self.pid, address))
該方法執行個體化了協定類(由外部指定協定),調用了該協定類的__init__方法。
注:HttpProtocol對象自身沒有init函數,是以會調用父類BaseRequestHandler(PythonXX/Lib/SocketServer.py)的init 函數 ,源碼如下:
def __init__(self, request, client_address, server):
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish()
此函數中,執行了setup, handle, finish 三個操作,下面依次進行分析。
I、setup函數
HttpProtocal 類重寫了setup函數,根據client socket生成rfile和wfile。
II、handle函數
BaseHTTPRequestHandler(../site-packages/eventlet/green/http/server.py) 重寫了handle函數,此函數主要調用 handle_one_request (HttpProtocl類重寫)函數。
handle_one_request函數,調用parse_request(BaseHTTPRequestHandler類)函數,通過mimetools.Message類生成headers,以及command,path,version等參數;
調用get_environ (HttpProtocol類)函數生成environ字典;
将self.server.app指派self.application,其中self.server.app就是外部初始化wsgi.Server()時傳遞進來的,具體後面講解;
調用handle_one_response函數。
handle_one_response函數,調用self.application函數,傳遞environ參數和start_response函數,執行使用者業務流程并傳回響應資料;
調用write函數,重新構造響應資料,調用wfile.writelines函數傳回給用戶端。
在self.application(即__call__函數)執行過程中,根據envrion 會生成request對象。
注:這就回答了文章開頭提出的那個問題。
III、finish函數
HttpProtocol類重寫,核心功能由StreamRequestHandler類完成。重新整理緩存,關閉wfile,rfile,釋放協程和socket資源。
start_response函數解析
在多個中間件項目中,start_response函數會被作為參數繼續傳遞,直到最後一個app時,利用app過程中執行的status和headers參數,重寫handle_one_response内部的header_set變量,作為執行結果傳回給用戶端。
3、Application對象
在上一節中我們知道在handle_one_response方法中會執行application()方法,而這個application是怎麼來的呢?
它是通過../site-packages/oslo_service/wsgi.py中load_app來的,源碼如下:
def load_app(self, name):
"""Return the paste URLMap wrapped WSGI application.
:param name: Name of the application to load.
:returns: Paste URLMap object wrapping the requested application.
:raises: PasteAppNotFound
"""
try:
LOG.debug("Loading app %(name)s from %(path)s",
{'name': name, 'path': self.config_path})
return deploy.loadapp("config:%s" % self.config_path, name=name)
except LookupError:
LOG.exception("Couldn't lookup app: %s", name)
raise PasteAppNotFound(name=name, path=self.config_path)
這裡的name就是在api-paste.ini檔案中定義的入口section,例如下面的app_name
[composite:app_name]
use = egg:Paste#urlmap
/: apiversions
/v1: xxxx
def __call__(self, environ, start_response):
host = environ.get('HTTP_HOST', environ.get('SERVER_NAME')).lower()
if ':' in host:
host, port = host.split(':', 1)
else:
if environ['wsgi.url_scheme'] == 'http':
port = '80'
else:
port = '443'
path_info = environ.get('PATH_INFO')
path_info = self.normalize_url(path_info, False)[1]
for (domain, app_url), app in self.applications:
if domain and domain != host and domain != host+':'+port:
continue
if (path_info == app_url
or path_info.startswith(app_url + '/')):
environ['SCRIPT_NAME'] += app_url
environ['PATH_INFO'] = path_info[len(app_url):]
return app(environ, start_response)
environ['paste.urlmap_object'] = self
return self.not_found_application(environ, start_response)
4、業務route
- 一個就是我們業務中定義的route mapper,即每個url對應的具體處理的controller類。
- 一個就是持有controller引用的資源分發處理類,該類必須也是一個可調用對象:在__call__中,解析出url對應的處理方法、參數,以及response封裝等等。