Categories
Uncategorized

Python Web Flask source Interpretation (a) – Start Process

about me
    A thoughtful program ape, lifelong learning practitioners, is currently in a start-up team any team lead, technology stack involves Android, Python, Java, and Go, this is the main technology stack our team.
    Github: https: //github.com/hylinux1024
    Micro-channel public number: Lifetime developer (angrycode)

What is WSGI 0x00

Web Server Gateway Interface
    It is defined by a set of standard Python Web Server and Web Application Interface Specification interaction.

WSGI not an application, framework, or library module, but the specification.

What is the Web Server (Web Server) and what is Web Application (Web application) it?
    For example the sub-described readily appreciated, for example, common Web application framework Django, Flask the like, and the Web server have uWSGI, Gunicorn like. WSGI is the definition of a standardized interface to interact with it at both ends.

What is 0x01 Werkzeug

Werkzeug is a comprehensive WSGI web application library.

Werkzeug WSGI specification is a set of libraries to achieve. We can use it to create a Web Application (Web applications). For example Flask application framework described in this article is based on Werkzeug to development.

Here we use Werkzeug start a simple server application

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)

After running can be seen in the following information on the console

 * Running on http://localhost:4000/ (Press CTRL+C to quit)

Use browser to open http: // localhost: 4000 / to see the following information about

Hello, World!

What is 0x02 Flask

Flask is a lightweight WSGI web application framework.

Flask is a lightweight web application framework, which is run in a web server application. Werkzeug Flask bottom layer is packaged.

Flask develop a web application using very simple

from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return f'Hello, World!'

if __name__ == '__main__':
    app.run()

It is very simple.

Next we look at the boot process Flask applications.

0x03 startup process

Project from the source address in https://github.com/pallets/flask clone down, and then switch to the 0.1 version tag. Why use the 0.1 version of it? Because this is the beginning of the written version, it should be the least amount of code, and you can easily see the whole of the coding ideas.

Here’s start from the simplest Demo to see how Flask is started. We know that the program is started to perform the following method

if __name__ == '__main__':
    app.run()

and

app = Flask(__name__)

Open source in the __init__ method Flask

Flask.__init__()
        def __init__(self, package_name):
        #: 是否打开debug模式
        self.debug = False

        #: 包名或模块名
        self.package_name = package_name

        #: 获取app所在目录
        self.root_path = _get_package_path(self.package_name)

        #: 存储视图函数的字典,键为函数名称,值为函数对象,使用@route装饰器进行注册
        self.view_functions = {}

        #: 存储错误处理的字典.  键为error code, 值为处理错误的函数,使用errorhandler装饰器进行注册
        self.error_handlers = {}

        #: 处理请求前执行的函数列表,使用before_request装饰器进行注册
        self.before_request_funcs = []

        #: 处理请求前执行的函数列表,使用after_request装饰器进行注册
        self.after_request_funcs = []

        #: 模版上下文
        self.template_context_processors = [_default_template_ctx_processor]
        #: url 映射
        self.url_map = Map()
        
        #: 静态文件
        if self.static_path is not None:
            self.url_map.add(Rule(self.static_path + '/',
                                  build_only=True, endpoint='static'))
            if pkg_resources is not None:
                target = (self.package_name, 'static')
            else:
                target = os.path.join(self.root_path, 'static')
            self.wsgi_app = SharedDataMiddleware(self.wsgi_app, {
                self.static_path: target
            })

        #: 初始化 Jinja2 模版环境. 
        self.jinja_env = Environment(loader=self.create_jinja_loader(),
                                     **self.jinja_options)
        self.jinja_env.globals.update(
            url_for=url_for,
            get_flashed_messages=get_flashed_messages
        )

Various initialization operations carried out in the constructor of Flask.

Then is executed app.run () method

app.run()
def run(self, host='localhost', port=5000, **options):
    """启动本地开发服务器.  如果debug设置为True,那么会自动检查代码是否改动,有改动则会自动执行部署
    :param host: 监听的IP地址. 如果设置为 ``'0.0.0.0'``就可以进行外部访问
    :param port: 端口,默认5000
    :param options: 这个参数主要是对应run_simple中需要的参数
    """
    from werkzeug.serving import run_simple
    if 'debug' in options:
        self.debug = options.pop('debug')
    options.setdefault('use_reloader', self.debug)
    options.setdefault('use_debugger', self.debug)
    return run_simple(host, port, self, **options)

run is very simple, the main method is called run_simple werkzeug.serving in.

Then open source run_simple

rum_simple()

def run_simple(hostname, port, application, use_reloader=False,
               use_debugger=False, use_evalex=True,
               extra_files=None, reloader_interval=1, threaded=False,
               processes=1, request_handler=None, static_files=None,
               passthrough_errors=False, ssl_context=None):
    # 这方法还是比较短的,但是注释写得很详细,由于篇幅问题,就把源码中的注释省略了
    if use_debugger:
        from werkzeug.debug import DebuggedApplication
        application = DebuggedApplication(application, use_evalex)
    if static_files:
        from werkzeug.wsgi import SharedDataMiddleware
        application = SharedDataMiddleware(application, static_files)

    def inner():
        make_server(hostname, port, application, threaded,
                    processes, request_handler,
                    passthrough_errors, ssl_context).serve_forever()

    if os.environ.get('WERKZEUG_RUN_MAIN') != 'true':
        display_hostname = hostname != '*' and hostname or 'localhost'
        if ':' in display_hostname:
            display_hostname = '[%s]' % display_hostname
        _log('info', ' * Running on %s://%s:%d/', ssl_context is None
             and 'http' or 'https', display_hostname, port)
    if use_reloader:
        # Create and destroy a socket so that any exceptions are raised before
        # we spawn a separate Python interpreter and lose this ability.
        test_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        test_socket.bind((hostname, port))
        test_socket.close()
        run_with_reloader(inner, extra_files, reloader_interval)
    else:
        inner()

In rum_simple method also defines a method of nested inner (), that is the core part of the method.

inner()

def inner():
    make_server(hostname, port, application, threaded, processes, request_handler, passthrough_errors, ssl_context).serve_forever()

Inside inner () method, call make_server (…). Serve_forever () to start the service.

make_server()

def make_server(host, port, app=None, threaded=False, processes=1,
                request_handler=None, passthrough_errors=False,
                ssl_context=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)
    elif processes > 1:
        return ForkingWSGIServer(host, port, app, processes, request_handler,
                                 passthrough_errors, ssl_context)
    else:
        return BaseWSGIServer(host, port, app, request_handler,
                              passthrough_errors, ssl_context)

Creates a corresponding WSGI server based on the number of threads or processes in make_server () in. Flask default is to create BaseWSGIServer server.

BaseWSGIServer、ThreadedWSGIServer、ForkingWSGIServer
class BaseWSGIServer(HTTPServer, object):
    ...

class ThreadedWSGIServer(ThreadingMixIn, BaseWSGIServer):
    """A WSGI server that does threading."""
    ...

class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
    """A WSGI server that does forking."""
    ...

Inheritance before they can be seen as

Open start_server BaseWSGIServer’s () method

start_server()
def serve_forever(self):
    try:
        HTTPServer.serve_forever(self)
    except KeyboardInterrupt:
        pass

Can see the end is a method of using HTTPServer start the service. The HTTPServer is the Python standard library interface.

HTTPServer is a subclass of socketserver.TCPServer

socketserver.TCPServer

If you want to start a library in Python http server, the code should be similar to this

import http.server
import socketserver

PORT = 8000

Handler = http.server.SimpleHTTPRequestHandler

with socketserver.TCPServer(("", PORT), Handler) as httpd:
    print("serving at port", PORT)
    httpd.serve_forever()

At this point, to start the entire service would start up here.

This process is call flow

graph TD

A[Flask]-->B[app.run]
B[app.run]-->C[werkzeug.run_simple]
C[werkzeug.run_simple]-->D[BaseWSGIServer]
D[BaseWSGIServer]-->E[HTTPServer.serve_forever]
E[HTTPServer.serve_forever]-->F[TCPServer.serve_forever]

0x04 summarize

WSGI interface specification is the interaction between WEB server and WEB application. werkzeug is to achieve this a standard function library, Flask framework is based on werkzeug to achieve.
    We from Flask.run () method to start the service began tracking the entire service startup process.

0x05 learning materials

  • https://werkzeug.palletsprojects.com/en/0.15.x/
  • https://palletsprojects.com/p/flask/
  • https://docs.python.org/3/library/http.server.html#module-http.server

Leave a Reply