Read webpy source code - request response 2

Continue to "read webpy source code - request response 1", this article will talk about respond.


def communicate(self):
       request_seen = False
       try:
           while True:
              req = None
              req = self.RequestHandlerClass(self.server, self)
              req.parse_request()
              if self.server.stats['Enabled']:
                  self.requests_seen += 1
              if not req.ready:
                  return
              request_seen = True
              #响应入口
              req.respond()
              if req.close_connection:
                   return


------------------------>

def respond(self):
        mrbs = self.server.max_request_body_size
        if self.chunked_read:
            self.rfile = ChunkedRFile(self.conn.rfile, mrbs)
        else:
            cl = int(self.inheaders.get("Content-Length", 0))
            ***省略号
            self.rfile = KnownLengthRFile(self.conn.rfile, cl)
        #

In response to the entry, getway(self) gets the gateway adaptation, mainly WSGIGateway_10, WSGIGateway_u0, both of which inherit WSGIGateway, and respond is the method of WSGIGateway; # WSGIGateway_10 has the get_environ method: used to obtain environmental information about the request from the server ( Submission method method, path request path, etc.)

        self.server.gateway(self).respond()
        if (self.ready and not self.sent_headers):
            self.sent_headers = True
            self.send_headers()
        if self.chunked_write:
            self.conn.wfile.sendall("0\r\n\r\n")


------------------------>

def respond(self):
        #这里是回调了wsgi_app函数,后面接着看下req.server.wsgi_app什么时候加载的。
        response = self.req.server.wsgi_app(self.env, self.start_response)
        try:
            for chunk in response:
                if chunk:
                    if isinstance(chunk, unicode):
                        chunk = chunk.encode('ISO-8859-1')
                    self.write(chunk)
        finally:
            if hasattr(response, "close"):
                response.close()


------------------------>
The run method of the application:
def run(self, *middleware):
    #The parameter is self.wsgifunc(*middleware), which is the wsgi_app method that needs to be called back later. Here are two points:
  1. How to fill the callback function into the WorkThread through the context. (See the comments in "Reading webpy source code - request response 1", which has been explained.)
  2. What does the callback function do.
def respond(self):
    #self.req.server.wsgi_app就是runsimple方法的参数func。
    response = self.req.server.wsgi_app(self.env, self.start_response)
        try:
            for chunk in response:
                if chunk:
                    if isinstance(chunk, unicode):
                        chunk = chunk.encode('ISO-8859-1')
                    self.write(chunk)
        finally:
            if hasattr(response, "close"):
                response.close()


-------------------------->

Let's take a look at the definition of wsgi_app (ie func)


def wsgifunc(self, *middleware):
        def peep(iterator):
            try:
                firstchunk = iterator.next()
            except StopIteration:
                firstchunk = ''
            return itertools.chain([firstchunk], iterator)     
        def is_generator(x): return x and hasattr(x, 'next')
        def wsgi(env, start_resp):
            self._cleanup()
            self.load(env)
            try:
                if web.ctx.method.upper() != web.ctx.method:
                    raise web.nomethod()
                result = self.handle_with_processors()
                if is_generator(result):
                    result = peep(result)
                else:
                    result = [result]
            except web.HTTPError, e:
                result = [e.data]

      result = web.safestr(iter(result))
      status, headers = web.ctx.status, web.ctx.headers
      start_resp(status, headers)
      def cleanup():
          self._cleanup()
          yield '' 

      return itertools.chain(result, cleanup())

   for m in middleware: 
        wsgi = m(wsgi)
   #最后的返回,是把wsgi方法返回了。wsgi方法主要是做_cleanup(),load(),handle_with_processors()。
   return wsgi

Description: _cleanup() mainly cleans up the old request environment variable dictionary; load() mainly fills the web.ctx context according to the parsed request information; handle_with_processors() recursively calls the processing method implemented in the application.


def handle_with_processors(self):
  def process(processors):
    try:
      if processors:
        p, processors = processors[0], processors[1:]
        return p(lambda: process(processors))
      else:
        return self.handle()
    except web.HTTPError:
      raise
    except (KeyboardInterrupt, SystemExit):
      raise
    except:
      print >> web.debug, traceback.format_exc()
      raise self.internalerror()

  return process(self.processors)

The following quotes are from other people's blogs, which can be viewed directly in the past

This function is quite complicated, and the core part is implemented recursively (I feel that the same function can be achieved without recursion). For the sake of clarity, examples are used.
As mentioned earlier, when initializing the application instance, two processors are added to self.processors:
self.add_processor(loadhook(self._load))
self.add_processor(unloadhook(self._unload))
So, the current self.processors looks like this:
self.processors = [loadhook(self._load), unloadhook(self._unload)]
 # In order to facilitate the subsequent description, let's abbreviate:
self.processors = [load_processor, unload_processor]
When the framework starts to execute handle_with_processors, these processors are executed one by one. Let's still look at the code decomposition, first simplify the handle_with_processors function:
def handle_with_processors(self):
  def process(processors):
    try:
      if processors: # position 2
        p, processors = processors[0], processors[1:]
        return p(lambda: process(processors)) # 位置3
      else:
        return self.handle() # position 4
    except web.HTTPError:
      raise
    ...
  return process(self.processors) # 位置1
    The starting point of function execution is position 1, calling its internally defined function process(processors).
    If the position 2 judges that the processor list is not empty, enter the if inside.
    Call the processor function that needs to be executed this time at position 3, the parameter is a lambda function, and then return.
    If position 2 determines that the handler list is empty, execute self.handle(), which actually calls our application code (described below).
Taking the example above, there are currently two processors:
self.processors = [load_processor, unload_processor]
After entering the code from position 1, it will judge that there is still a processor to be executed at position 2, and will go to position 3. At this time, the code to be executed is as follows:
return load_processor(lambda: process([unload_processor]))
The load_processor function is a function decorated with loadhook, so its definition is like this when it is executed:
def load_processor(lambda: process([unload_processor])):
  self._load()
  return process([unload_processor]) # is the lambda function of the parameter
It will execute self._load() first, and then continue to execute the process function. It will still go to position 3. The code to be executed at this time is as follows:
return unload_processor(lambda: process([]))
The unload_processor function is a function decorated with unloadhook, so its definition is like this when it is executed:
def unload_processor(lambda: process([])):
  try:
    result = process([]) # parameter passed in lambda function
    is_generator = result and hasattr(result, 'next')
  except:
    self._unload()
    raise

  if is_generator:
    return wrap(result)
  else:
    self._unload()
    return result
Now the process([]) function will be executed first, and it will go to position 4 (where self.handle() is called) to get the processing result of the application, and then call the processing function self._unload() of this processor.
To summarize the order of execution:
self._load()
  self.handle()
self._unload()
If there are more processors, it is also executed according to this method. For loadhook decorated processors, the ones added first are executed first, and for unloadhook decorated processors, the ones added later are executed first.
handle function
After talking so much, we talk about where we really want to call the code we wrote. After all load handlers are executed, the self.handle() function will be executed, which will internally call the application code we wrote. For example, return a hello, world and the like. self.handle is defined as follows:
def handle(self):
  fn, args = self._match(self.mapping, web.ctx.path)
  return self._delegate(fn, self.fvars, args)
This function is easy to understand. The self._match called in the first line is to perform the routing function and find the corresponding class or sub-application. The self._delegate in the second line is to call this class or pass the request to the sub-application.
_match function
The definition of the _match function is as follows:
def _match(self, mapping, value):
  for pat, what in mapping:
    if isinstance(what, application): # 位置1
      if value.startswith(pat):
        f = lambda: self._delegate_sub_application(pat, what)
        return f, None
      else:
        continue
    elif isinstance(what, basestring): # 位置2
      what, result = utils.re_subm('^' + pat + '$', what, value)
    else: # position 3
      result = utils.re_compile('^' + pat + '$').match(value)

    if result: # it's a match
      return what, [x for x in result.groups()]
  return None, None
The mapping in the parameters of this function is self.mapping, which is the URL routing mapping table; the value is web.ctx.path, which is the path of this request. This function traverses self.mapping and processes it according to the type of object processed in the mapping relationship:
    Position 1, the processing object is an application instance, that is, a sub-application, then an anonymous function is returned, and the anonymous function will call self._delegate_sub_application for processing.
    Position 2, if the processing object is a string, call utils.re_subm for processing, and replace the part matching pat in value (that is, web.ctx.path) with what (that is, a URL we specified) The pattern processing object string), and then returns the replaced result and the matched item (which is a re.MatchObject instance).
    Position 3, in other cases, such as directly specifying a class object as the processing object.
If result is not empty, return the processing object and a parameter list (this parameter list is the parameter passed to the GET and other functions we implemented).
_delegate function
The result returned from the _match function is passed as an argument to the _delegate function:
fn, args = self._match(self.mapping, web.ctx.path)
return self._delegate(fn, self.fvars, args)
in:
    fn: is the object to process the current request, usually a class name.
    args: are the arguments to be passed to the request handler object.
    self.fvars: is the global namespace when instantiating the application, which will be used to find processing objects.
The implementation of the _delegate function is as follows:
def _delegate(self, f, fvars, args=[]):
  def handle_class(cls):
    meth = web.ctx.method
    if meth == 'HEAD' and not hasattr(cls, meth):
      meth = 'GET'
    if not hasattr(cls, meth):
      raise web.nomethod(cls)
    tocall = getattr(cls(), meth)
    return tocall(*args)

  def is_class(o): return isinstance(o, (types.ClassType, type))

  if f is None:
    raise web.notfound()
  elif isinstance(f, application):
    return f.handle_with_processors()
  elif is_class(f):
    return handle_class(f)
  elif isinstance(f, basestring):
    if f.startswith('redirect '):
      url = f.split(' ', 1)[1]
      if web.ctx.method == "GET":
        x = web.ctx.env.get('QUERY_STRING', '')
        if x:
          url += '?' +x
      raise web.redirect(url)
    elif '.' in f:
      mod, cls = f.rsplit ('.', 1)
      mod = __import__(mod, None, None, [''])
      cls = getattr(mod, cls)
    else:
      cls = fvars[f]
    return handle_class(cls)
  elif hasattr(f, '__call__'):
    return f()
  else:
    return web.notfound()
This function mainly makes different processing according to the type of parameter f:
    If f is empty, return 302 Not Found.
    f is an application instance, then call handle_with_processors() of the sub-application for processing.
    f is a class object, the internal function handle_class is called.
    If f is a string, perform redirection processing, or after obtaining the class name of the request to be processed, call handle_class for processing (the code we write is generally called under this branch).
    f is a callable object, which is called directly.
    In other cases it returns 302 Not Found.
 

 

Guess you like

Origin http://10.200.1.11:23101/article/api/json?id=326664143&siteId=291194637