From the PERSPECTIVE of the WSGI protocol, a WSGI application would look like this

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['Hello World!']
Copy the code
We can use Werkzeug’s wrapped class Response to help simplify this code

from werkzeug.wrappers import Response def application(environ, start_response): response = Response('Hello World! ', mimetype='text/plain') return response(environ, start_response)Copy the code
2.1 Start our application

We implement an application as a class, executing start_response under Python’s magic function __call__ so that we can handle the request as an object app().

Class MyApp(object): def dispatch_request(self): def dispatch_request(self, request): return Response("hello world") def wsgi_app(self, environ, start_response): request = Request(environ) response = self.dispatch_request(request) return response(environ, start_response) def __call__(self, environ, start_response): return self.wsgi_app(environ, start_response)Copy the code
The wsGI_app function is at the heart of our entire application, so writing this way makes it easy to extend WSGI middleware, which creates a Request object, passes it to Dispatch_Request and returns a WSGI application Response.

Now we can build a unified generator to generate a MyApp object for easy management.

def create_app():
    app = MyApp()
    return app
Copy the code
2.1.1 How is flask started

App. The run (' 0.0.0.0 ', 8001, the debug = app. Config/" debug ", implementing = True)Copy the code
We go to app.run() and find this sentence inside

from werkzeug.serving import run_simple
try:
    run_simple(host, port, self, **options)
finally:
    self._got_first_request = False
Copy the code
2.1.2 So what does run_simple do

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, ): """Start a WSGI application. Optional features include a reloader, multithreading and fork support...Copy the code
Start a WSGI application with optional features such as auto-loading, multi-threading, and multi-processing. Note two of the arguments, threaded and processes. At the heart of run_simple is the function’s built-in inner function

    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()
Copy the code
The ‘fd’ is a file descriptor from which the socket can create objects.

Let’s go to make_server and look at the code

def make_server(
    host=None,
    port=None,
    app=None,
    threaded=False,
    processes=1,
    request_handler=None,
    passthrough_errors=False,
    ssl_context=None,
    fd=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, fd=fd
        )
    elif processes > 1:
        return ForkingWSGIServer(
            host,
            port,
            app,
            processes,
            request_handler,
            passthrough_errors,
            ssl_context,
            fd=fd,
        )
    else:
        return BaseWSGIServer(
            host, port, app, request_handler, passthrough_errors, ssl_context, fd=fd
        )
Copy the code
If the WSGIServer is threaded and processed, the WSGIServer will return the threaded and processes. If the WSGIServer is threaded, the WSGIServer will return the threaded and processes. Create a service that supports multi-threading of requests, or multi-processing, or a single thread of request after request, but does not support multi-threading. Flask’s native support for concurrency relies on WerkZeug, otherwise it’s a single-threaded application, so we can dig a little deeper. 2.1.3 How does WSGIServer work

We find BaseWSGIServer, which inherits from HTTPServer

class BaseWSGIServer(HTTPServer, object):
​
    """Simple single-threaded, single-process WSGI server."""
Copy the code
It tells us that it is a single-threaded WSGI server, and the inner function under run_simple finally executes srv.serve_forever(), which seems to start a permanent service.

    def serve_forever(self):
        self.shutdown_signal = False
        try:
            HTTPServer.serve_forever(self)
        except KeyboardInterrupt:
            pass
        finally:
            self.server_close()
Copy the code
As you can see, the httpServer. serve_forever function is extended and server_close is executed

if hasattr(selectors, 'PollSelector'): _ServerSelector = selectors.PollSelector else: _ServerSelector = selectors. SelectSelector... 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: 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: self._handle_request_noblock() self.service_actions() finally: self.__shutdown_request = False self.__is_shut_down.set()Copy the code
Py, which implements the IO models of select, poll, epoll, and kqueue (found on most Unix systems, including OS X). The poll used here monitors a set of file handles and returns _handle_request_noblock when an active file descriptor is active

    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)
Copy the code
Both the comment and the function name indicate that execution cannot be blocked as long as the selector returned is ready.

request, client_address = self.get_request()

This get_request function is defined in the HTTPServer parent class TCPServer

 def get_request(self):
 """Get the request and client address from the socket.
​
        May be overridden.
​
        """
 return self.socket.accept()
Copy the code
It returns the new socket and the address of the requester. In socket programming, we execute accept and block until a message arrives from the client. In fact, there is no blocking at all. This is the characteristic of the Pollio model, which iterates through all connections until it finds a connection with a new message, returns true, and tells the server socket to accept, which is guaranteed to receive the value.

2.2 Adding a Route

Now that we have a basic WSGi application, we need to refine the routing rules for the application.

Werkzeug provides a flexible integrated routing. You need to create a Map instance and add a series of Rule objects. For each Rule we can pass in two parameters, one for the URL and one for the endpoint

We maintain a Map in MyApp and register our routes in create_app

# add routing app. Url_map = Map ([Rule ('/', the endpoint = "new_url)])Copy the code
Endpoint here is actually the name of the function corresponding to the route, and then we implement the endpoint to point to a function

def dispatch_request(self, request): Adapter = self.url_map.bind_to_environ(request.environ) try Endpoint, values = adapter.match() # return getattr(self, endpoint + '_handler')(request, **values) except HTTPException as e: print(repr(e)) return Response("hello world")Copy the code

2.2.1 How does Flask add routing

Decorator @route (” /home “, methods=[“GET”]) add route

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
Copy the code
Add_url_rule specifies a route. This endpoint is the same as the add_url_rule endpoint

def add_url_rule( self, rule, endpoint=None, view_func=None, provide_automatic_options=None, **options, ): If endpoint is None: view_func; Options ["endpoint"] = endpoint_from_view_func options.pop("methods", None) methods = {item.upper() for item in methods} required_methods = set(getattr(view_func, "required_methods", ())) the methods | = # required_methods generated werkzeug Rule object binding endpoint and routing address Rule = self. Url_rule_class (Rule, the methods = the methods, Add (Rule) 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" f" endpoint function: Self. view_functions[endpoint] = view_funcCopy the code

2.2 Adding WSGI middleware

We know from the WSGI protocol that middleware can be added via app= MW1 (app)

Let’s create a new middleware class that keeps track of the overall response time. The structure is similar to our MyApp class in that it implements a __call__, which is also a WSGI application, except that we first save an application and then execute middleware instances to process the saved application

Class MyMiddleWare(object): """ "wsGi middleware """ def __init__(self, application): Self. Application = application print(" create middleware") def __call__(self, environ, start_response): b = time.time() result = self.application(environ, start_response) duration = (time.time() - b)/1000 print("duration: %f" % duration) return resultCopy the code
Now that we add this middleware to create_app, we can directly rewrite wsGI_app’s function as the middleware object (implementing __call__ to make it callable as a function).

app.wsgi_app = MyMiddleWare(app.wsgi_app)
Copy the code

How do I add middleware in 2.2.1 Flask

In the APP_runner file of the DSP project

def create_app():
    flask_app = Flask('csp-controller')
    with flask_app.app_context():
        i18n(flask_app)
        create_db(flask_app)
        configure_models()
        configure_blueprints(flask_app)
        init_monitor(flask_app)
        setup_default_data()
        add_app_hook(flask_app)
    return flask_app

def run_worker(app=None):
    from app.scheduling.celery_app import make_celery
    if not app:
        app = create_app()
    celery_app = make_celery(app)
Copy the code

2.3 Final Code

From werkzeug. Wrappers import Response class MyMiddleWare(object): """ "wsGI middleware """ def __init__(self, application): Self. Application = application print(" create middleware") def __call__(self, environ, start_response): b = time.time() result = self.application(environ, start_response) duration = (time.time() - b)/1000 print("duration: %f" % duration) return result class MyApp(object): def __init__(self): Self. url_map = None print(" create app") def url_adapter(self): Pass # handler() def new_url_handler(self, request): return Response('{"code": 0}', status=404) def dispatch_request(self, request): adapter = self.url_map.bind_to_environ(request.environ) try: Select the endpoint that matches the request route. Value is a dictionary. Value = adapter.match() # return getattr(self, endpoint + '_handler')(request, **values) except HTTPException as e: print(repr(e)) return Response("hello world") def wsgi_app(self, environ, start_response): request = Request(environ) response = self.dispatch_request(request) return response(environ, start_response) def __call__(self, environ, start_response): return self.wsgi_app(environ, start_response) def create_app(): Wsgi_app = MyMiddleWare(app.wsgi_app) app = MyMiddleWare(app.wsgi_app) App. url_map = Map([Rule('/', endpoint="new_url")]) return app if __name__ == '__main__' : App = create_app() run_simple('127.0.0.1', 5000, app)Copy the code