В этой статье кратко анализируется исходный код Flask с акцентом на WSGI, структуру данных объекта Flask, процесс запуска приложения Flask, процесс обработки запроса, функцию просмотра, сопоставление URL-адресов, контекст приложения и контекст запроса. Эти темы не будут освещены во всех подробностях, поэтому, пожалуйста, изучите их самостоятельно, так как вам нужно прочитать исходный код. Чтобы прочитать эту статью, вам нужно быть знакомым с Flask, например, вы написали небольшой проект на Flask, и иметь определенные навыки чтения кода, и иметь базовое представление о функциях веб-фреймворка.
Эта статья будет время от времени обновляться, дата последнего обновления: 10 сентября 2017 г.
Это официальный демо-код Flask:
from flask import Flask
app = Flask(__name__)
@app.route(‘/‘)
def index():
return ‘Hello, world!’
if __name__ == ‘__main__’:
app.run()
Эта статья начинается с этого простого кода и кратко знакомит с WSGI, структурой данных объекта Flask, процессом запуска приложения Flask, процессом обработки запросов, функциями просмотра, сопоставлением URL-адресов, классами запросов и ответов (контекст приложения и контекст запроса). веб-фреймворк.
WSGI
После того, как запрос, инициированный пользователем, достигает сервера, он будет получен HTTP-сервером, а затем передан в веб-приложение для обработки бизнеса, поэтому между сервером HTTP и веб-приложения требуется интерфейс между сервером и веб-приложением. В мире Python Web Development, официальный официальный PythonНазначенсоздал этот интерфейс и назвал его WSGI, как указано в PEP333. Любая комбинация серверов и фреймворков может быть реализована, если и серверы, и фреймворки придерживаются этого соглашения. Согласно этому регламенту, фреймворк, ориентированный на WSGI, должен реализовать этот метод:
def application(environ, start_response)
В процессе работы HTTP-сервер вызовет указанный выше метод и передаст информацию запроса, которая называетсяenviron
словарь иstart_response
функция, применяемая изenviron
Получите информацию о запросах в и назовете ее после обработки бизнесаstart_response
Установите заголовок ответа и верните тело ответа (должно быть проходимым объектом, таким как список, словарь) на HTTP-сервер, а HTTP-сервер вернет ответ пользователю.
Итак, Flask, как веб-фреймворк для разработки веб-приложений, отвечает за решение следующих задач:
- Как приложение, которое может быть вызвано сервером HTTP, оно должно иметь
__call__
метод - Через информацию о входящем запросе (URL, метод HTTP и т. д.) найдите правильную логику бизнес-процессинга, то есть правильную функцию просмотра
- Обработка бизнес-логики, которая может включать проверки форм, CRUD базы данных и т. д. (это не будет рассматриваться в этой статье).
- вернуть правильный ответ
- При одновременной обработке нескольких запросов также необходимо защищать эти запросы и знать, какой ответ следует использовать для соответствия какому запросу, то есть защиту потока.
Давайте посмотрим, как Flask решает эти проблемы.
Справочное чтение:Напишем веб-сервер вместеЭта серия статей даст вам базовое понимание того, как веб-серверы и рамки работают вместе через WSGI.
Создание приложений
Чтение исходного кода:
app.py
серединаFlask
код класса.
Вторая строка демонстрационного кода создает экземпляр класса Flask, передавая имя текущего модуля. Давайте сначала посмотрим, что такое приложение Flask и какова его структура данных.
Flask
такой класс:
The flask object implements a WSGI application and acts as the central object. It is passed the name of the module or package of the application. Once it is created it will act as a central registry for the view functions, the URL rules, template configuration and much more.
The name of the package is used to resolve resources from inside the package or the folder the module is contained in depending on if the package parameter resolves to an actual python package (a folder with an
__init__.py
file inside) or a standard module (just a.py
file).
Объект колбы на самом деле является приложением WSGI. Он получает имя модуля или пакета в качестве параметра. После того, как он создан, все функции просмотра, правила URL, настройки шаблона и т. Д. Зарегистрированы выше. Название модуля или пакета состоит в том, чтобы найти некоторые ресурсы.
Класс Flask имеет некоторые свойства:
-
request_class = Request
Установите тип запроса -
response_class = Response
Установите тип ответа
Оба типа являются производными от его зависимой библиотекиwerkzeug
И сделал простое расширение.
Объект фляги__init__
Методы, как показано ниже:
def __init__(self, package_name):
#: Flask 对象有这样一个字典来保存所有的视图函数
self.view_functions = {}
#: 这个字典用来保存所有的错误处理视图函数
#: 字典的 key 是错误类型码
self.error_handlers = {}
#: 这个列表用来保存在请求被分派之前应当执行的函数
self.before_request_funcs = []
#: 在接收到第一个请求的时候应当执行的函数
self.before_first_request_funcs = []
#: 这个列表中的函数在请求完成之后被调用,响应对象会被传给这些函数
self.after_request_funcs = []
#: 这里设置了一个 url_map 属性,并把它设置为一个 Map 对象
self.url_map = Map()
Колба здесь, чтобы создать полные и переменные объектыapp
Он указывает на то, что на самом деле это объект сопоставления URL-адресов, который сохраняет некоторую информацию о конфигурации, связывает некоторые функции представления и имеет объект сопоставления URL-адресов (url_map
)Объект. Но мы до сих пор не знаем, что это за объект Map и что он делает.Из названия кажется, что его роль заключается в сопоставлении URL-адресов с функциями просмотра. Строка 21 исходного кода имеетfrom werkzeug.routing import Map, Rule
, тогда посмотримwerkzeug
Определение карты в этой библиотеке:
The map class stores all the URL rules and some configuration parameters. Some of the configuration values are only stored on the
Map
instance since those affect all rules, others are just defaults and can be overridden for each rule. Note that you have to specify all arguments besides therules
as keyword arguments!
Вы можете видеть, что объекты этого класса хранят все правила URL и некоторую информацию о конфигурации. так какwerkzeug
Механизм маппинга более сложный.Подробнее о механизме маппинга мы узнаем позже.Теперь просто вспомним приложение Flask (то есть приложение Flask).Flask
экземпляр класса) сохранить функцию просмотра и передатьurl_map
В этой переменной хранится механизм сопоставления URL-адресов.
Процесс запуска приложения
Чтение исходного кода:
app.py
серединаFlask
код класса иwerkzeug.serving
код, обратите особое внимание наrun_simple
BaseWSGIServer
WSGIRequestHandler
.
Строка 6 демонстрационного кода представляет собой ограничение, указывающее, что если интерпретатор Python напрямую запускает файл или пакет, запускается программа Flask: в Python, если модуль или пакет выполняется напрямую, интерпретатор будет использовать текущий модуль или пакет. , пакет__name__
установлен в__main_
.
в строке 7run
метод запускает приложение Flask:
def run(self, host=None, port=None, debug=None, **options):
from werkzeug.serving import run_simple
if host is None:
host = '127.0.0.1'
if port is None:
server_name = self.config['SERVER_NAME']
if server_name and ':' in server_name:
port = int(server_name.rsplit(':', 1)[1])
else:
port = 5000
if debug is not None:
self.debug = bool(debug)
options.setdefault('use_reloader', self.debug)
options.setdefault('use_debugger', self.debug)
try:
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
Такой подход можно увидеть по сути в параметрах конфигурации, собственно запуск сервераwerkzeug
изrun_simple
метод, который запускает сервер по умолчаниюBaseWSGIServer
, унаследованная от стандартной библиотеки PythonHTTPServer.TCPServer
.注意在调用run_simple
self
Он передается как параметр, и это правильно, потому что, когда сервер получает запрос, он должен знать, кому звонить.__call__
метод.
Согласно стандартной библиотекеHTTPServer.TCPServer
режиме сервер должен иметь класс, который действует как обработчик запросов для обработки входящих запросов, а неHTTPServer.TCPServer
сам экземпляр для обработки,werkzeug
при условииWSGIRequestHandler
класс в качестве обработчика запроса, этот класс используетсяBaseWSGIServer
Эта функция выполняется при вызове:
def execute(app):
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
Первая строка функции соответствует требованиям WSGI, и для вызова приложенияenviron
иstart_response
входящий. Давайте посмотрим, как flask отвечает на вызовы сервера в соответствии с требованиями WSGI.
def __call__(self, environ, start_response):
return self.wsgi_app(environ, start_response)
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
ctx.push()
error = None
try:
try:
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
Вы можете видеть, что Flask реализован в соответствии с требованиями WSGI.__call__
Метод, следовательно, становится объектом вызова. Но это не напрямую__call__
Логика логична, но называетсяwsgi_app
Метод, это для рассмотрения промежуточного программного обеспечения, поэтому я не буду говорить об этом. возвращено этим методомresponse(environ, start_response)
середина,response
даwerkzueg.response
Экземпляр класса, который также является вызываемым объектом, который отвечает за генерацию конечного проходимого тела ответа и вызовstart_response
Сформируйте заголовок ответа.
обработка запроса
Чтение исходного кода:
app.Flask
код.
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
ctx.push()
error = None
try:
try:
response = self.full_dispatch_request()
except Exception as e:
error = e
response = self.handle_exception(e)
return response(environ, start_response)
finally:
if self.should_ignore_error(error):
error = None
ctx.auto_pop(error)
wsgi_app
Содержимое метода представляет собой высокую абстракцию процесса обработки запросов.
Во-первых, при получении запроса от сервера Flask вызываетrequest_context
функция создаетRequestContext
запросить объект контекста и вставить его в_request_ctx_stack
куча. Контекст и стек будут обсуждаться позже, но сейчас вам нужно знать, что эти операции нужны для того, чтобы flask не запутался при обработке нескольких запросов. После этого Flask позвонитfull_dispatch_request
Метод распространяется на этот запрос, запуская фактический процесс обработки запроса, который генерирует объект ответа и в конечном итоге вызываетresponse
объект, чтобы вернуться на сервер. Если есть ошибка, утверждайте соответствующее сообщение об ошибке. Независимо от ошибок, в конечном итоге колба будет толкать контекст запроса от стека.
full_dispatch_request
является точкой входа распределения запросов, давайте посмотрим на ее реализацию:
def full_dispatch_request(self):
self.try_trigger_before_first_request_functions()
try:
rv = self.preprocess_request()
if rv is None:
rv = self.dispatch_request()
except Exception as e:
rv = self.handle_user_exception(e)
return self.finalize_request(rv)
первый звонокtry_trigger_before_first_request_functions
метод, чтобы попытаться позвонитьbefore_first_request
функция в списке, еслиFlask
из_got_first_request
собственностьFalse
,before_first_request
Функция будет выполнена один раз после выполнения,_got_first_request
будет установлен наTrue
Таким образом, эти функции больше не выполняются.
тогда позвониpreprocess_request
метод, этот метод вызываетbefore_request_funcs
все методы в списке, если ониbefore_request_funcs
Метод что-то возвращает, то запрос на самом деле не отправляется. Например,before_request_funcs
Метод используется для определения того, вошел ли пользователь в систему, если пользователь не вошел в систему, этот метод вызоветabort
Таким образом, метод возвращает ошибку, и Flask не будет отправлять запрос, а напрямую сообщит об ошибке 401.
еслиbefore_request_funcs
Функция in не возвращается, затем снова вызовитеdispatch_request
способ отправки запроса. Этот метод сначала проверит, есть ли соответствующий URL в правилахendpoint
иvalue
значение, если есть, то звонитеview_functions
Соответствующая функция представления в (endpoint
в качестве значения ключа) и передать значение параметра (**req.view_args
), если нет, тоraise_routing_exception
для обработки. Возвращаемое значение функции представления или возвращаемое значение функции представления обработки ошибок будет возвращено вwsgi_app
в методеrv
Переменная.
def dispatch_request(self):
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
if getattr(rule, 'provide_automatic_options', False) \
and req.method == 'OPTIONS':
return self.make_default_options_response()
return self.view_functions[rule.endpoint](**req.view_args)
def finalize_request(self, rv, from_error_handler=False):
response = self.make_response(rv)
try:
response = self.process_response(response)
request_finished.send(self, response=response)
except Exception:
if not from_error_handler:
raise
self.logger.exception('Request finalizing failed with an '
'error while handling an error')
return response
def make_response(self, rv):
if isinstance(rv, self.response_class):
return rv
if isinstance(rv, basestring):
return self.response_class(rv)
if isinstance(rv, tuple):
return self.response_class(*rv)
return self.response_class.force_type(rv, request.environ)
Тогда колба будетrv
чтобы получить ответ, этоmake_response
Метод проверяет, является ли rv требуемым типом возвращаемого значения, в противном случае генерирует правильный тип возвращаемого значения. Например, если возвращаемое значение в Demo является строкой, оно удовлетворитisinstance(rv, basestring)
Оцените и сгенерируйте ответ из строки. После завершения этого шага Flask проверяет, есть ли функции просмотра постобработки для выполнения (вprocess_response
метод) и в конечном итоге возвращает полностью обработанныйresponse
объект.
Просмотр регистрации функций
В разделе об обработке запросов мы видели, как Flask вызывает функцию попытки, В этом разделе мы сосредоточимся на том, как Flask создает структуры данных, связанные с диспетчеризацией запросов. Мы сосредоточимся в основном наview_functions
, потому что другие структуры данных, такие какbefore_request_funcs
Процесс сборки почти такой же или даже проще. Также подробнее рассмотрим вопросы, оставшиеся в разделе о создании приложения, а именноurl_map
что именно это такое.
Строка 4 демонстрационного кода использует декораторroute
Зарегистрируйте функцию просмотра, которая широко известна колбой в дизайне. В классе колбаroute
метод, вы можете видеть, что он вызываетadd_url_rule
метод.
def route(self, rule, **options):
def decorator(f):
endpoint = options.pop('endpoint', None)
self.add_url_rule(rule, endpoint, f, **options)
return f
return decorator
def add_url_rule(self, rule, endpoint, **options):
if endpoint is None:
endpoint = _endpoint_from_view_func(view_func)
options['endpoint'] = endpoint
methods = options.pop('methods', None)
if methods is None:
methods = getattr(view_func, 'methods', None) or ('GET',)
if isinstance(methods, string_types):
raise TypeError('Allowed methods have to be iterables of strings, '
'for example: @app.route(..., methods=["POST"])')
methods = set(item.upper() for item in methods)
required_methods = set(getattr(view_func, 'required_methods', ()))
provide_automatic_options = getattr(view_func,
'provide_automatic_options', None)
if provide_automatic_options is None:
if 'OPTIONS' not in methods:
provide_automatic_options = True
required_methods.add('OPTIONS')
else:
provide_automatic_options = False
methods |= required_methods
rule = self.url_rule_class(rule, methods=methods, **options)
rule.provide_automatic_options = provide_automatic_options
self.url_map.add(rule)
if view_func is not None:
old_func = self.view_functions.get(endpoint)
if old_func is not None and old_func != view_func:
raise AssertionError('View function mapping is overwriting an '
'existing endpoint function: %s' % endpoint)
self.view_functions[endpoint] = view_func
Этот метод отвечает за регистрацию функций просмотра и реализацию сопоставления URL-адресов с функциями просмотра. Во-первых, ему нужно подготовить метод HTTP, поддерживаемый функцией просмотра (в основном это делает более половины кода), а затем передатьurl_rule_class
Создаватьrule
объект и добавить этот объект к своемуurl_map
внутри. Ответ на наш оставшийся вопрос здесь:rule
Цель состоит в том, чтобы сохранить законный (Flask, поддерживаемый приложением) URL-адрес, метод,endpoint
(существует**options
) и соответствующие им структуры данных, аurl_map
это коллекция, которая содержит эти объекты. Затем этот метод добавляет функцию просмотра вview_functions
среди,endpoint
В качестве ключа его значением по умолчанию является имя функции.
Давайте посмотрим поближеrule
Это определено вwerkzeug.routing.Rule
середина:
A Rule represents one URL pattern. There are some options for
Rule
that change the way it behaves and are passed to theRule
Конструктор. Объект Rule представляет собой режим URL-адреса, который может изменять многие из своих свойств с помощью входящих параметров.
Правила__init__
Метод:
def __init__(self, string, defaults=None, subdomain=None, methods=None,
build_only=False, endpoint=None, strict_slashes=None,
redirect_to=None, alias=False, host=None):
if not string.startswith('/'):
raise ValueError('urls must start with a leading slash')
self.rule = string
self.is_leaf = not string.endswith('/')
self.map = None
self.strict_slashes = strict_slashes
self.subdomain = subdomain
self.host = host
self.defaults = defaults
self.build_only = build_only
self.alias = alias
if methods is None:
self.methods = None
else:
if isinstance(methods, str):
raise TypeError('param `methods` should be `Iterable[str]`, not `str`')
self.methods = set([x.upper() for x in methods])
if 'HEAD' not in self.methods and 'GET' in self.methods:
self.methods.add('HEAD')
self.endpoint = endpoint
self.redirect_to = redirect_to
if defaults:
self.arguments = set(map(str, defaults))
else:
self.arguments = set()
self._trace = self._converters = self._regex = self._weights = None
После создания правила перейдитеMap
изadd
метод привязан кMap
объект, мы сказали ранееflask.url_map
только одинMap
объект.
def add(self, rulefactory):
for rule in rulefactory.get_rules(self):
rule.bind(self)
self._rules.append(rule)
self._rules_by_endpoint.setdefault(rule.endpoint, []).append(rule)
self._remap = True
иRule
изbind
Содержание метода состоит в том, чтобы добавитьRule
соответствующийMap
, а затем позвонитеcompile
Способ генерации регулярного выражения,compile
Метод более сложный и не будет расширяться.
def bind(self, map, rebind=False):
"""Bind the url to a map and create a regular expression based on
the information from the rule itself and the defaults from the map.
:internal:
"""
if self.map is not None and not rebind:
raise RuntimeError('url rule %r already bound to map %r' %
(self, self.map))
self.map = map
if self.strict_slashes is None:
self.strict_slashes = map.strict_slashes
if self.subdomain is None:
self.subdomain = map.default_subdomain
self.compile()
Когда приложение Flask получает запрос, они привязываются кurl_map
ВверхRule
просматриваются, чтобы найти соответствующие им функции просмотра. Это реализовано в контексте запроса, который мы ранее использовали вdispatch_request
метод - мы из_request_ctx_stack.top.request
получитьrule
и от этогоrule
оказатьсяendpoint
и, наконец, найти правильную функцию представления для обработки запроса. Итак, далее нам нужно посмотреть на конкретную реализацию запроса вверх и вниз и посмотреть, как Flask получает отurl_map
нашел это вrule
из.
def dispatch_request(self):
req = _request_ctx_stack.top.request
if req.routing_exception is not None:
self.raise_routing_exception(req)
rule = req.url_rule
if getattr(rule, 'provide_automatic_options', False) \
and req.method == 'OPTIONS':
return self.make_default_options_response()
return self.view_functions[rule.endpoint](**req.view_args)
контекст запроса
Чтение исходного кода:
ctx.RequestContext
код.
Как и когда создается контекст запроса? Мы также видели, что когда сервер вызывает приложение, Flask'swsgi_app
Есть такой оператор, который должен создать контекст запроса и поместить его в стек.
def wsgi_app(self, environ, start_response):
ctx = self.request_context(environ)
ctx.push()
request_context
Метод очень простой, просто создайтеRequestContext
Экземпляр класса, определенного вflask.ctx
файл, он содержит ряд информации о запросе,самое главное этосвой собственныйrequest
атрибут указывает наRequest
Экземпляр класса, этот класс наследует отwerkzeug.Request
,существуетRequestContext
процесс создания, он будетenviron
Создаватьwerkzeug.Request
пример.
тогдаRequestContext
изpush
Метод вызывается, этот метод подтолкнет себя к_request_ctx_stack
Верх стека.
_request_ctx_stack
определяется вflask.global
файл, этоLocalStack
Экземпляр классаwerkzeug.local
Что достигается, если вы знакомы с потоковой передачей Python, вы обнаружите, что здесь реализована изоляция потоков, то есть когда интерпретатор Python запускается в_request_ctx_stack
Когда дело доходит до связанного кода, интерпретатор выберет правильный экземпляр на основе текущего процесса.
Однако за весь процесс анализа исходного кода Flask мы не обнаружили, что Flask создавал поток после вызова, так зачем же делать изоляцию потоков? посмотрите, что мы упоминали в началеrun
Функция, на самом деле, это может пройтиthreaded
параметр. Когда эта функция не передается, мы начинаемBasicWSGIServer
, этот сервер однопоточный и однопроцессный, и потокобезопасность Flask, естественно, бессмысленна, но когда мы передаем этот параметр, мы запускаем следующее:ThreadedWSGIServer
, то потокобезопасность Flask имеет смысл, как и на других многопоточных серверах.
Суммировать
запрошенное путешествие
Здесь мы проходим по этой статье, отслеживая путь запроса к серверу и обратно (конечно, «становясь» ответом).
- Прежде чем выдавать запрос, Flask зарегистрировал хороший вид на все функции и сопоставления URL, сервер на зарегистрированном приложении Flask.
- Запрос поступает на сервер, сервер готов
environ
иmake_response
функцию, а затем вызовите приложение Flask, зарегистрированное на себя. - Приложение реализует требования WSGI
application(environ, make_response)
метод. Во Flask этот метод называется__call__
Транзит называетсяwsgi_app
Методы.它首先通过environ
Контекст запроса создается и помещается в стек, чтобы flask мог получить доступ к контексту запроса при обработке текущего запроса. - Затем Flask начинает обрабатывать запрос, вызывая по очереди
before_first_request_funcs
before_request_funcs
view_functions
функция в и, наконец, передатьfinalize_request
генерироватьresponse
Объект, пока есть возвращаемое значение функции, следующая группа функций не будет выполняться снова,after_request_funcs
провестиresponse
После обработки после поколения. - Фласк называет это
response
объект, который в итоге вызываетmake_response
функция и возвращает проходимое содержимое ответа. - Сервер отправляет ответ.
Фляга и Werkzeug
В ходе анализа стало очевидно, что Flask иwerkzeug
На самом деле это сильно связано,werkzeug
Это единственная необходимая зависимость от Flask.werkzeug
Библиотека, в случае с этой статьей, делает как минимум следующие вещи:
- упаковка
Response
иRequest
Тип используется Flask.В реальной разработке наши операции над объектами запроса и ответа фактически называютсяwerkzeug
Методы. - Реализуйте сопоставление URL-адреса с функцией просмотра и можете передавать параметры в URL-адресе функции просмотра. Мы видели атрибуты Flask URL_MAP и видели, как они связывают функции просмотра и функции обработки ошибок, но практика конкретных правил сопоставления и анализ URL-адресов во время процесса ответа выполняются Werkzeug.
- Генерируется классом LocalProxy
_request_ctx_stack
Реализовать защиту потока для Flask.
Для анализа исходного кода Flask давайте временно остановимся здесь. Когда у меня будет время, я проанализирую отрисовку шаблона во Flask,import request
, чертежи и некоторые полезные переменные и функции, или углубленный анализwerkzeug
библиотека.
эталонное чтение
- Серия статей об анализе исходного кода Flask, вы можете прочитать эту серию статей для получения более подробной информации после прочтения этой статьи, чтобы понять основную линию.
- Напишем веб-сервер вместе.
Запись обновления статьи
- 10 сентября 2017 г .: Проанализировано с версией 0.12.0, реструктурировано и упорядочено, добавлено много контента.