flask源码之app.run方法的背后
引子:
掐指一算从去年年底的公司分享开始接触到flask到现在已经有半年多了,3月份在网上查询资料,偶然发现了很多flask源码解析的博客,注意到一句话flask执行run方法后实际上调用的是flask实例的__call__方法,但是从网上找了很多的资料,发现大多数的博客内容雷同,且很模糊,并且好多都是断的,自己的瘾又犯了,搞了半个月,决定记录一下。
过程图示:
源码推导过程:
1、程序入口 app.run()
from flask import Flask app = Flask(__name__) # app.route 通过 route内部定义的 url_map 匹配到视图函数 # 这时候已经拿到了对象 获取到请求 则执行对象的__call__方法 @app.route('/') def hello_world(): return 'Hello World!' if __name__ == '__main__': """ 监听用户请求 如果有用户请求到来 则执行app__call__方法 也是flask 的请求入口 """ #app.__call__ app.run()
run方法内部
def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
# Change this into a no-op if the server is invoked from the
# command line. Have a look at cli.py for more information.
if os.environ.get("FLASK_RUN_FROM_CLI") == "true":
from .debughelpers import explain_ignored_app_run
explain_ignored_app_run()
return
if get_load_dotenv(load_dotenv):
cli.load_dotenv()
# if set, let env vars override previous values
if "FLASK_ENV" in os.environ:
self.env = get_env()
self.debug = get_debug_flag()
elif "FLASK_DEBUG" in os.environ:
self.debug = get_debug_flag()
# debug passed to method overrides all other sources
if debug is not None:
self.debug = bool(debug)
_host = "127.0.0.1"
_port = 5000
server_name = self.config.get("SERVER_NAME")
sn_host, sn_port = None, None
if server_name:
sn_host, _, sn_port = server_name.partition(":")
host = host or sn_host or _host
# pick the first value that's not None (0 is allowed)
port = int(next((p for p in (port, sn_port) if p is not None), _port))
options.setdefault("use_reloader", self.debug)
options.setdefault("use_debugger", self.debug)
options.setdefault("threaded", True)
cli.show_server_banner(self.env, self.debug, self.name, False)
以上代码明显是对flask实例做了一些默认配置的设置
真正执行的是下边 try代码块,看到其实调用了werkzug的run_simple方法
这里其实就和多实例调用的是一个方法
from werkzeug.serving import run_simple
try:
run_simple中的第三个参数self 就是 flask实例
run_simple(host, port, self, **options)
finally:
# reset the first request information if the development server
# reset normally. This makes it possible to restart the server
# without reloader and that stuff from an interactive shell.
self._got_first_request = False
run_simple代码:
run_simple接受的第三个参数 application就是 run方法中传进来的self 也就是falsk实例
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, ): if not isinstance(port, int): raise TypeError("port must be an integer") if use_debugger: from .debug import DebuggedApplication application = DebuggedApplication(application, use_evalex) if static_files: from .middleware.shared_data import SharedDataMiddleware application = SharedDataMiddleware(application, static_files) def log_startup(sock): display_hostname = hostname if hostname not in ("", "*") else "localhost" quit_msg = "(Press CTRL+C to quit)" if sock.family == af_unix: _log("info", " * Running on %s %s", display_hostname, quit_msg) else: if ":" in display_hostname: display_hostname = "[%s]" % display_hostname port = sock.getsockname()[1] _log( "info", " * Running on %s://%s:%d/ %s", "http" if ssl_context is None else "https", display_hostname, port, quit_msg, ) def inner(): try: fd = int(os.environ["WERKZEUG_SERVER_FD"]) except (LookupError, ValueError): fd = None
3、走这里 有调用了一个make_server的方法或者类 同时将 app传了进去 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() 1、根据传入的条件 最总代码走了 内嵌函数 inner if use_reloader: # If we're not running already in the subprocess that is the # reloader we want to open up a socket early to make sure the # port is actually available. if not is_running_from_reloader(): if port == 0 and not can_open_by_fd: raise ValueError( "Cannot bind to a random port with enabled " "reloader if the Python interpreter does " "not support socket opening by fd." ) # Create and destroy a socket so that any exceptions are # raised before we spawn a separate Python interpreter and # lose this ability. address_family = select_address_family(hostname, port) server_address = get_sockaddr(hostname, port, address_family) s = socket.socket(address_family, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(server_address) if hasattr(s, "set_inheritable"): s.set_inheritable(True) # If we can open the socket by file descriptor, then we can just # reuse this one and our socket will survive the restarts. if can_open_by_fd: os.environ["WERKZEUG_SERVER_FD"] = str(s.fileno()) s.listen(LISTEN_QUEUE) log_startup(s) else: s.close() if address_family == af_unix: _log("info", "Unlinking %s" % server_address) os.unlink(server_address) from ._reloader import run_with_reloader run_with_reloader(inner, extra_files, reloader_interval, reloader_type) else:
2、执行inner inner()
maker_server代码:
最终返回的的是BaseWSGIServer的实例
def make_server( host=None, port=None, app=None, threaded=False, processes=1, request_handler=None, passthrough_errors=False, ssl_context=None, fd=None, ): 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 此时的返回就是一个BaseWSGIServer的实例,同时在实例话的时候 传入了 app else: return BaseWSGIServer( host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd )
BaseWSGIServer类的init方法:
class BaseWSGIServer(HTTPServer, object): BaseWSGIServer类又继承了HTTPServer类 multithread = False multiprocess = False request_queue_size = LISTEN_QUEUE def __init__( self, host, port, app, handler=None, passthrough_errors=False, ssl_context=None, fd=None, ): if handler is None:
handler = WSGIRequestHandler # 指定baseserver中的requestHandlerclass作为处理请求的handler 这个类后续将会用到 self.address_family = select_address_family(host, port) if fd is not None: real_sock = socket.fromfd(fd, self.address_family, socket.SOCK_STREAM) port = 0 server_address = get_sockaddr(host, int(port), self.address_family) # remove socket file if it already exists if self.address_family == af_unix and os.path.exists(server_address): os.unlink(server_address)
在BaseWSGIServer的__init__方法中 手动调用父类的 __init__ 根据继承.......
这里需要特别注意 在调用HttpServer.__init__传入的参数 handler=WSGIRequstHandler
HTTPServer.__init__(self, server_address, handler) self.app = app self.passthrough_errors = passthrough_errors self.shutdown_signal = False self.host = host self.port = self.socket.getsockname()[1] # Patch in the original socket. if fd is not None: self.socket.close() self.socket = real_sock self.server_address = self.socket.getsockname() if ssl_context is not None: if isinstance(ssl_context, tuple): ssl_context = load_ssl_context(*ssl_context) if ssl_context == "adhoc": ssl_context = generate_adhoc_ssl_context() # If we are on Python 2 the return value from socket.fromfd # is an internal socket object but what we need for ssl wrap # is the wrapper around it :( sock = self.socket if PY2 and not isinstance(sock, socket.socket): sock = socket.socket(sock.family, sock.type, sock.proto, sock) self.socket = ssl_context.wrap_socket(sock, server_side=True) self.ssl_context = ssl_context else: self.ssl_context = None
HTTPServer代码:
此类没有实现__init__方法,但是继承了 TCPServer,子类手动调用父类的_init__方法,父类没有的情况下会向上调用
这个类主要实现的是socket服务器绑定BaseWSGIServer实例,而这个实例属性又有flask实例,所以。。。。。。
class HTTPServer(socketserver.TCPServer): allow_reuse_address = 1 # Seems to make sense in testing environment def server_bind(self): """Override server_bind to store the server name.""" socketserver.TCPServer.server_bind(self) host, port = self.server_address[:2] self.server_name = socket.getfqdn(host) self.server_port = port
TCPServer.__init__代码:
class TCPServer(BaseServer): address_family = socket.AF_INET socket_type = socket.SOCK_STREAM request_queue_size = 5 allow_reuse_address = False def __init__(self, server_address, RequestHandlerClass, bind_and_activate=True): """Constructor. May be extended, do not override."""
这里 是关键的一句 它又调用了 父类的 __init__方法
同时注意传入的参数。RequestHandlerClass 就是BaseWSGISever 传入的 WSGIRequestHandler BaseServer.__init__(self, server_address, RequestHandlerClass)
这里明显调用了 python的内置socket模块去启动socket服务去了 self.socket = socket.socket(self.address_family, self.socket_type) if bind_and_activate: try: self.server_bind() self.server_activate() except: self.server_close() raise
BaseServer.__init__代码:
这里其实并没什么特别的,就是 初始化了几个属性,那么到这里 BaseWSGIServer类才完成实例化,才返回给make_server方法 返回值给了 run_simple中inner的srv变量,所以还要回到 run_simple的inner方法
class BaseServer: timeout = None def __init__(self, server_address, RequestHandlerClass): """Constructor. May be extended, do not override.""" self.server_address = server_address 这里 给 属性赋值去了 self.RequestHandlerClass = RequestHandlerClass 这里调用python内置模块threading。 线程事件方法 具体干什么的后续再研究 估计和服务器多线程有关系 self.__is_shut_down = threading.Event() self.__shutdown_request = False
run_simple.inner
BaseWSGIServer完成实例化后,赋值给了srv变量,紧接着srv调用serve_forever方法,即调用了 BaseWSGIServer中的serve_forever方法
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()
BaseWSGIServer.serve_forever代码:
没什么特别的,又调用了父类HttpServer的serve_forever方法 但是在上述中明显发现 HttpServer没有该方法,根据继承明显调用了其长辈类的方法 在其祖父类 BaseServer找到了该方法
def serve_forever(self): self.shutdown_signal = False try: HTTPServer.serve_forever(self) except KeyboardInterrupt: pass finally: self.server_close()
BaseServer.serve_forever
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.
# 选择模式 多路io复用
with _ServerSelector() as selector:
selector.register(self, selectors.EVENT_READ)
while not self.__shutdown_request:
ready = selector.select(poll_interval)
# bpo-35017: shutdown() called during select(), exit immediately.
if self.__shutdown_request:
break
if ready:
# 调用 _handle_request_noblock 获取请求
self._handle_request_noblock()
self.service_actions()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
BaseServer._handle_request_noblock代码:
其中 self.get_request方法查看源码,调用socket实例中的accept方法 等待接收请求,获取到请求后在self.process_request
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)
BaseServer.process_request代码:
这里也没什么,就是到了2个内部的方法 并且没有返回值
def process_request(self, request, client_address): """Call finish_request. Overridden by ForkingMixIn and ThreadingMixIn. """ # 处理请求 finish_request 实际是实例化 RequestHandlerClass # 这里的 RequestHandlerClass 就是basewsgiserver 指定的 handler self.finish_request(request, client_address) self.shutdown_request(request)
BaseServer.finish_request代码:
根据上述 BaseWSGIServer实例化过程传入的参数 ,RequestHandlerClass = BaseRequestHandler,并且在该方法中实例化了该类,到这里没有看到一丝对请求进行解析的地方 request还是accept获取的原始请求
def finish_request(self, request, client_address): """Finish one request by instantiating RequestHandlerClass.""" 这里是一个 比较 精妙(坑爹)的 地方 # 根据继承关系 在祖父类 BaseRequestHandler 找到的__init__方法中 实际调用了WSGIRequestHandler的 handle方法 self.RequestHandlerClass(request, client_address, self)
紧接着 self.RequestHandlerClass(request, client_address, self) 其实就是 实例的 BaseRequestHandler 同时注意实例传入的第三个参数 self 就是BaseWSGIServer实例,返回BaseWSGIServer中看看 BaseRequestHandler 到底是什么玩意
在 BaseRequestHandler类中没有实现 __init__方法,但是又没报错,所以在其长辈类中一定有 __init__方法进行接收,其父类是 BaseHTTPRequestHandler,点进去一看还是没有,继续向上找 StreamRequestHandler,还是没有,但似乎看到了曙光。
BaseRequestHandler中含有 __init__方法
class WSGIRequestHandler(BaseHTTPRequestHandler, object):
class BaseHTTPRequestHandler(socketserver.StreamRequestHandler):
class StreamRequestHandler(BaseRequestHandler):
BaseRequestHandler.__init__代码:
到这里 要注意几点
1 、self.server就是finish_request传进来的第三个参数 self 也就是 BaseWSGIServer
2、这里的self指向了 WSGIRequestHandler,__init__方式只是类的初始化方法,在此之前执行了__new__方法,这个方法返回值就是self 也就是类的实例,若看到这忘记了,自己到python总结中看看自己是怎么写的
3、那么根据 2 ,可以推算出 self.handle 调的是 WSGIRequestHandler.handle() 方法,BaseRequestHandler中实现的handle方法均为空方法,这里为什么会这么写呢,希望一年后的你能给出答案---这里实际上子类在实例化的时候执行的,所以这里的self是子类实例子,父类无法感知子类有那些方法,不写会报错,估计作者就是在这里顶一下,别报错
泥马兜兜转转这么大一圈一夜回到解放前,还要回到 WSGIRequestHandler去看 handle()方法
class BaseRequestHandler: def __init__(self, request, client_address, server): #print("WSGIRequestHandler.self====>",self)
谁调的__init__方法 self 就是谁 self.request = request self.client_address = client_address self.server = server self.setup() try: self.handle() finally: self.finish()
WSGIRequestHandler.handle代码:
def handle(self): """Handles a request ignoring dropped connections.""" rv = None try: # 1 在 WSGIRequestHandler 的祖父类中 的构造方法中调用了此 handle方法 # 2 本方法中 又调用了父类的 hanle方法 # 3 极其坑爹 rv = BaseHTTPRequestHandler.handle(self) 这里的self 是 当前 WSGIRequestHandler 实例 # 4 所以在父类中 BaseHTTPRequestHandler.handle 执行的 handle_one_request 就是 WSGIRequestHandler 的 handle_one_request 同样的套路
这里也是比较 精妙(kd)的一部分 rv = BaseHTTPRequestHandler.handle(self) except (_ConnectionError, socket.timeout) as e: self.connection_dropped(e) except Exception as e: if self.server.ssl_context is None or not is_ssl_error(e): raise if self.server.shutdown_signal: self.initiate_shutdown() return rv
BaseHTTPRequestHandler.handle中的代码
但是不知道为什么 在请求过来之后 这个方法会被调用2次,不知道是不是可debug模式有关,在回到WSGIRequestHandler.handle_one_request
def handle(self): """Handle multiple requests if necessary.""" # 本方法在 子类WSGIRequestHandler 中的handle 被调用 # 并且这里的self 并不是指 BaseHTTPRequestHandler的实例 而是子类 WSGIRequestHandler 的实例 因为子类在调用的时候 将self传入 self.close_connection = True # print("in BaseHTTPRequestHandler", self) # 调用 WSGIRequestHandler 的 handle_one_request 方法 # 因为 WSGIRequestHandler 重写了 该方法 #print("in BaseHTTPRequestHandler handle start ===>") self.handle_one_request() #print("in BaseHTTPRequestHandler handle end===>") while not self.close_connection: #print(55555) self.handle_one_request()
WSGIRequestHandler.handle_one_request代码:
def handle_one_request(self): """Handle a single HTTP request.""" # print("WSGIRequestHandler IN handle_one_request") # import traceback # s=traceback.extract_stack() # print("WSGIRequestHandler==>%s"%s) # print(222222) self.raw_requestline = self.rfile.readline() if not self.raw_requestline: self.close_connection = 1 # 解析request elif self.parse_request(): # 最终执行 run_wsgi 方法 return self.run_wsgi()
这里貌似看到了一丝曙光
WSGIRequestHandler.run_wsgi代码:
这里 实现了几个 嵌套函数 其中 execute(self.server.app) 中self.server 属性 就是 上述 BaseRequestHandler.__init__代码中的 self.server 也就是 BaseWSGIServer的实例 然而在实例该类的时候 将app当做也传了进去 所以 self.server.app正是傻逼似的 flask实例
并且在 execute 方法中 application_iter = app(environ, start_response) 这样调用实例,那么类加括号是实例化,方法加括号是调用,那么实例括号调用实例的__call__ 最后一步看看 flask的 __call__长得是不是像佩奇,nnd的。
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") # 生成被解析过的request对象 self.environ = environ = self.make_environ() headers_set = [] headers_sent = [] # 解析 environ 处理request 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): # 在这里实际调用了 flask实例也就是app的__call__方法 application_iter = app(environ, start_response) 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 执行flask实例 __call__ # 此 self.server.app 来源于 BaseWSGIServer 的祖父类 # BaseServer 的 self.finish()=======>self.RequestHandlerClass(request, client_address, self) execute(self.server.app) except (_ConnectionError, socket.timeout) as e: self.connection_dropped(e, environ) except Exception: if self.server.passthrough_errors: raise from .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)
flask实例的__call__代码:
就一行代码,凌乱
def __call__(self, environ, start_response): """The WSGI server calls the Flask application object as the WSGI application. This calls :meth:`wsgi_app` which can be wrapped to applying middleware.""" # import traceback # s=traceback.extract_stack() # print("s===>%s"%s) # print("调用链 ======>") return self.wsgi_app(environ, start_response)
总结:
1、需要熟悉 python中__new__ 和 __init__方法
2、组合类
3、其实看到这,仔细想想是不是符合wsgi的概念呢