flask
- handler的處理流程.(路由)
- ThreadedWSGIServer.(多線程非阻塞服務器)
- 多線程如何保證請求安全.(ctx)
1.flask handler 執行流程:
# 1.wsgi階段
WSGI -> Flask().__call__
# 2.Flask階段
wsgi_app() -> full_dispatch_request() -> dispatch_request() -> view_handler()
full_dispatch_request: 框架實現.
1.請求封裝, 請求ctx入棧
2.觸發鉤子函數.(try_trigger_before_first_request_functions)
3.發送請求開始信號.(request_started.send(self))
4.觸發dispatch_request. 調用到用戶的handler
view_handler: 用戶編寫, 實現業務
1.1 WSGI: 實現網關協議的接口
- call(environ, start_response))
flask.app.py 實現了wsgi接口.(上線可以託管在uwsgi/gunicorn後面)
class Flask(_PackageBoundObject):
def dispatch_request(self):
# 1.從_request_ctx_stack棧頂獲取請求.
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
# 2.Rule對象包含了url到viewhandler的映射
rule = req.url_rule
# ...
# 3.取出handler並執行
return self.view_functions[rule.endpoint](**req.view_args)
def full_dispatch_request(self):
# 1.觸發first_request_functions
self.try_trigger_before_first_request_functions()
try:
# 2.發送request_started信號, 觸發所有註冊了信號的函數
request_started.send(self)
# 3.預處理請求
rv = self.preprocess_request()
if rv is None:
# 4.處理請求,調用到用戶註冊的對應方法
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
# 5.結束請求
return self.finalize_request(rv)
def wsgi_app(self, environ, start_response):
# 1.請求封裝ctx
ctx = self.request_context(environ)
error = None
try:
try:
# 2.ctx入棧
ctx.push()
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
except: # noqa: B001
error = sys.exc_info()[1]
raise
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
# 3.ctx自動出棧
ctx.auto_pop(error)
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
1.2 View CBV: 實現dispatch接口. 完成http方法到CBV.meth的調用.
- View: 基視圖類.
- 實現as_view
- dispatch_request: 子類實現
- MethodViewType:方法視圖元類.
- cls.methods = methods 綁定方法集到類屬性
- MethodView(with_metaclass(MethodViewType, View)): CBV
flask.views.py
http_method_funcs = frozenset(
["get", "post", "head", "options", "delete", "put", "trace", "patch"]
)
class View(object):
"""
class MyView(View):
methods = ['GET']
def dispatch_request(self, name):
return 'Hello %s!' % name
app.add_url_rule('/hello/<name>', view_func=MyView.as_view('myview'))
"""
methods = None
provide_automatic_options = None
decorators = ()
def dispatch_request(self):
raise NotImplementedError()
@classmethod
def as_view(cls, name, *class_args, **class_kwargs):
def view(*args, **kwargs):
# 1.實例化視圖類
self = view.view_class(*class_args, **class_kwargs)
# 2.觸發dispatch_request
return self.dispatch_request(*args, **kwargs)
# 註冊裝飾器
if cls.decorators:
view.__name__ = name
view.__module__ = cls.__module__
for decorator in cls.decorators:
view = decorator(view)
view.view_class = cls
view.__name__ = name
view.__doc__ = cls.__doc__
view.__module__ = cls.__module__
view.methods = cls.methods
view.provide_automatic_options = cls.provide_automatic_options
# 路由註冊的函數. 與url形成映射. 記錄在Flask().views_functions
return view
class MethodViewType(type):
def __init__(cls, name, bases, d):
super(MethodViewType, cls).__init__(name, bases, d)
# 1.cls的屬性中沒有methods時
if "methods" not in d:
methods = set()
for base in bases:
if getattr(base, "methods", None):
methods.update(base.methods)
for key in http_method_funcs:
if hasattr(cls, key):
methods.add(key.upper())
if methods:
# 2.綁定methods屬性到當前cls
cls.methods = methods
class MethodView(with_metaclass(MethodViewType, View)):
"""A class-based view that dispatches request methods to the corresponding
class methods. For example, if you implement a ``get`` method, it will be
used to handle ``GET`` requests. ::
class CounterAPI(MethodView):
def get(self):
return session.get('counter', 0)
def post(self):
session['counter'] = session.get('counter', 0) + 1
return 'OK'
app.add_url_rule('/counter', view_func=CounterAPI.as_view('counter'))
"""
def dispatch_request(self, *args, **kwargs):
# 1.獲取視圖實例的meth
meth = getattr(self, request.method.lower(), None)
if meth is None and request.method == "HEAD":
meth = getattr(self, "get", None)
assert meth is not None, "Unimplemented method %r" % request.method
# 2.執行methd
return meth(*args, **kwargs)
1.3 Blueprint 藍圖
- 實現路由.
auth_view.py
auth = Blueprint('auth', __name__)
# 綁定 /api/auth/login 到login函數上, 支持GET,POST
@auth.route('/login', methods=['GET', 'POST'])
def login():
pass
# CBV
class Files(views.MethodView):
methods = ['POST', 'GET', 'PUT']
decorators = [auth_decorator]
def get(self, *args, **kwargs):
pass
def post(self, *args, **kwargs):
pass
def put(self, *args, **kwargs):
pass
auth.add_url_rule('/files', view_func=Files.as_view(name='files'))
main.py
from auth_view import auth
app = Flask(__name__)
app.register_blueprint(auth, url_prefix='/api/auth')
2.flask server
開發環境的服務器.(默認: ThreadedWSGIServer)
- 多線程服務器啓動.(每個建立一個連接,創建一個線程處理請求).
- WSGIRequestHandler: 處理請求,封裝(environ, start_response)
- Flask().call(environ, start_response): 被執行
2.1 開發服務器啓動流程.
app.run -> run_simple -> inner() -> ThreadedWSGIServer().serve_forever()
-> BaseWSGIServer().serve_forever()
-> HTTPServer().serve_forever()
-> TCPServer().serve_forever()
-> BaseServer().serve_forever()
flask.server.py
# app.run()
class Flask(_PackageBoundObject):
def run(self, host=None, port=None, debug=None, load_dotenv=True, **options):
options.setdefault("use_reloader", self.debug)
options.setdefault("use_debugger", self.debug)
options.setdefault("threaded", True)
try:
run_simple(host, port, self, **options)
finally:
self._got_first_request = False
SocketServer.py: 標準庫實現
- serve_forever: 服務器啓動,等待請求
- process_request: 處理請求
class BaseServer:
def _handle_request_noblock(self):
"""Handle one request, without blocking.
I assume that select.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 socket.error:
return
if self.verify_request(request, client_address):
try:
# 調用ThreadingMixIn().process_request處理請求.
self.process_request(request, client_address)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)
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:
while not self.__shutdown_request:
r, w, e = _eintr_retry(select.select, [self], [], [],
poll_interval)
if self in r:
self._handle_request_noblock()
finally:
self.__shutdown_request = False
self.__is_shut_down.set()
class TCPServer(BaseServer):
pass
class HTTPServer(SocketServer.TCPServer):
pass
2.2 WSGIRequestHandler 處理請求
werkzeug.serving.py: 實現處理wsgi協議的請求
{
"wsgi.multiprocess": False,
"HTTP_COOKIE": "csrftoken=; gfsessionid=",
"SERVER_SOFTWARE": "Werkzeug/0.16.0",
"SCRIPT_NAME": "",
"REQUEST_METHOD": "GET",
"PATH_INFO": "/api/auth/login",
"SERVER_PROTOCOL": "HTTP/1.1",
"werkzeug.server.shutdown": <function shutdown_server at 0x10d050050>,
"HTTP_CONNECTION": "keep-alive",
"werkzeug.request": <BaseRequest "http://localhost:8065/api/auth/login?username=test&password=test" [GET
]>,
"wsgi.input": <open file "<socket>", mode "rb" at 0x10d04e030>,
"wsgi.multithread": True,
"REQUEST_URI": "/api/auth/login?username=test&password=test",
"wsgi.version": "(1, 0)",
"REMOTE_ADDR": "127.0.0.1",
"HTTP_ACCEPT_ENCODING": "gzip, deflate"
...
}
class WSGIRequestHandler(BaseHTTPRequestHandler, object):
def run_wsgi(self):
# 1.讀取socket的數據,封裝成wsgi的env
self.environ = environ = self.make_environ()
def execute(app):
# 2.調用Flask().__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(self.server.app)
except (_ConnectionError, socket.timeout) as e:
self.connection_dropped(e, environ)
except Exception:
if self.server.passthrough_errors:
raise
pass
class BaseWSGIServer(HTTPServer, object):
"""
單線程,單進程 wsgi server
"""
def init():
handler = WSGIRequestHandler
#給http服務器綁定了wsgi請求的handler
HTTPServer.__init__(self, server_address, handler)
class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
"""A WSGI server that does threading."""
multithread = True
daemon_threads = True
def make_server():
return ThreadedWSGIServer(
host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
)
def inner(...):
srv = make_server()
# 啓動服務器
srv.serve_forever()
app.run流程:
-> srv.serve_forever()
-> ThreadedWSGIServer().serve_forever()
-> BaseServer().serve_forever()
2.3 ThreadingMixIn 多線程處理請求
ThreadingMixIn: 實現了process_request接口. 啓動子線程處理當前請求
class ThreadingMixIn:
"""Mix-in class to handle each request in a new thread."""
daemon_threads = False
def process_request_thread(self, request, client_address):
try:
self.finish_request(request, client_address)
self.shutdown_request(request)
except:
self.handle_error(request, client_address)
self.shutdown_request(request)
def process_request(self, request, client_address):
t = threading.Thread(target = self.process_request_thread,
args = (request, client_address))
t.daemon = self.daemon_threads
t.start()
serve_forever 中使用非阻塞處理請求,一旦請求可處理,執行self.process_request, 調用ThreadingMixIn().process_request()
- 啓動新的線程處理請求.
- 主線程繼續執行serve_forever()
- 新線程的啓動執行流程.(觸發Flask().call())
1.process_request_thread()
2.self.finish_request(request, client_address)
-> BaseServer().finish_request(self, request, client_address):
-> self.RequestHandlerClass(request, client_address, self)
-> WSGIRequestHandler().handle()
-> WSGIRequestHandler().run_wsgi()
-> app(environ, start_response) # Flask().__call__()
3.self.shutdown_request(request)
5.ctx local
werkzeug.local.py
- Local
- LocalProxy
- LocalStack
- LocalManager 當使用線程模型時,get_ident獲取線程標識 當使用greenlet協程時, get_ident獲取到協程標識
try:
from greenlet import getcurrent as get_ident
except ImportError:
try:
from thread import get_ident
except ImportError:
from _thread import get_ident
class Local(object):
__slots__ = ("__storage__", "__ident_func__")
def __init__(self):
object.__setattr__(self, "__storage__", {})
object.__setattr__(self, "__ident_func__", get_ident)
def __iter__(self):
return iter(self.__storage__.items())
def __call__(self, proxy):
"""Create a proxy for a name."""
return LocalProxy(self, proxy)
def __release_local__(self):
self.__storage__.pop(self.__ident_func__(), None)
def __getattr__(self, name):
try:
return self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
def __setattr__(self, name, value):
ident = self.__ident_func__()
storage = self.__storage__
try:
storage[ident][name] = value
except KeyError:
storage[ident] = {name: value}
def __delattr__(self, name):
try:
del self.__storage__[self.__ident_func__()][name]
except KeyError:
raise AttributeError(name)
flask.globals.py
- _request_ctx_stack: 請求上下文棧
- _app_ctx_stack: app上下文棧
- current_app: 當前請求,同時啓動多個Flask()
- request
- session
- g
# context locals
_request_ctx_stack = LocalStack()
_app_ctx_stack = LocalStack()
current_app = LocalProxy(_find_app)
request = LocalProxy(partial(_lookup_req_object, "request"))
session = LocalProxy(partial(_lookup_req_object, "session"))
g = LocalProxy(partial(_lookup_app_object, "g"))
py3 context是否可以替換: context