runsisi's

technical notes

CherryPy 请求处理流程

2019-05-30 runsisibe

后端应用使用 Tree 实例的 mount 方法将用户对象封装成一个 Application 实例挂载到路由处理链上:

# cherrypy/__init__.py

tree = _cptree.Tree()

cherrypy/_cptree.py

class Tree(object):
    apps = {}

    def __init__(self):
        self.apps = {}

    def mount(self, root, script_name="", config=None):
        # Next line both 1) strips trailing slash and 2) maps "/" -> "".
        script_name = script_name.rstrip("/")
        app = Application(root, script_name)
        self.apps[script_name] = app
        return app

    def script_name(self, path=None):
        while True:
            if path in self.apps:
                return path

            if path == "":
                return None

            # Move one node up the tree and try again.
            path = path[:path.rfind("/")]

    def __call__(self, environ, start_response):
        sn = self.script_name(path or "/")
        if sn is None:
            start_response('404 Not Found', [])
            return []
        app = self.apps[sn]
        return app(environ, start_response)

class Application(object):
    wsgiapp = None

    request_class = _cprequest.Request
    response_class = _cprequest.Response

    def __init__(self, root, script_name="", config=None):
        self.wsgiapp = _cpwsgi.CPWSGIApp(self)

    def get_serving(self, local, remote, scheme, sproto):
        """Create and return a Request and Response object."""
        req = self.request_class(local, remote, scheme, sproto)
        req.app = self
        resp = self.response_class()
        cherrypy.serving.load(req, resp)
        return req, resp

    def release_serving(self):
        """Release the current serving (request and response)."""
        req = cherrypy.serving.request
        req.close()
        cherrypy.serving.clear()

    def __call__(self, environ, start_response):
        return self.wsgiapp(environ, start_response)

请求经由各中间件处理,然后返回 AppResponse 实例作为响应:

# cherrypy/_cpwsgi.py

class CPWSGIApp(object):
    """A WSGI application object for a CherryPy Application."""

    pipeline = [('ExceptionTrapper', ExceptionTrapper),
                ('InternalRedirector', InternalRedirector),
                ]
    """A list of (name, wsgiapp) pairs. Each 'wsgiapp' MUST be a
    constructor that takes an initial, positional 'nextapp' argument,
    plus optional keyword arguments, and returns a WSGI application
    (that takes environ and start_response arguments). The 'name' can
    be any you choose, and will correspond to keys in self.config."""

    head = None
    """Rather than nest all apps in the pipeline on each call, it's only
    done the first time, and the result is memoized into self.head. Set
    this to None again if you change self.pipeline after calling self."""

    response_class = AppResponse
    """The class to instantiate and return as the next app in the WSGI chain."""

    def __init__(self, cpapp, pipeline=None):
        self.cpapp = cpapp
        self.pipeline = self.pipeline[:]
        if pipeline:
            self.pipeline.extend(pipeline)

    def tail(self, environ, start_response):
        """WSGI application callable for the actual CherryPy application.

        any WSGI middleware in self.pipeline runs first.
        """
        return self.response_class(environ, start_response, self.cpapp)

    def __call__(self, environ, start_response):
        head = self.head
        if head is None:
            # Create and nest the WSGI apps in our pipeline (in reverse order).
            # Then memoize the result in self.head.
            head = self.tail
            for name, callable in self.pipeline[::-1]:
                conf = self.config.get(name, {})
                head = callable(head, **conf)
            self.head = head
        return head(environ, start_response)

class AppResponse(object):
    """WSGI response iterable for CherryPy applications."""

    def __init__(self, environ, start_response, cpapp):
        self.cpapp = cpapp

        self.run()

        r = _cherrypy.serving.response

        self.write = start_response(outstatus, outheaders)

    def run(self):
        """Create a Request object using environ."""

        request, resp = self.cpapp.get_serving(local, remote, scheme, sproto)

        request.run(meth, path, qs, rproto, headers, rfile)

AppResponse 实例的 run 方法调用 Request 实例的 run 方法并最终调用用户对象 expose 出来的方法得到后端接口的用户数据:

# cherrypy/_cprequest.py

class Request(object):
    handler = None
    """
    The function, method, or other callable which CherryPy will call to
    produce the response. The discovery of the handler and the arguments
    it will receive are determined by the request.dispatch object.
    By default, the handler is discovered by walking a tree of objects
    starting at request.app.root, and is then passed all HTTP params
    (from the query string and POST body) as keyword arguments."""

    def run(self, method, path, query_string, req_protocol, headers, rfile):
        self.handler = None
        self.respond(pi)

    def respond(self, path_info):
        """Generate a response for the resource at self.path_info. (Core)"""
        response = cherrypy.serving.response
        self.get_resource(path_info)
        response.body = self.handler()