web.py Source Analysis: Application

Source: Internet
Author: User

The main analysis in this paper is the code of the web.py library application.py this module. In general, this module implements the WSGI compatible interface so that applications can be called by the WSGI application server. WSGI is the abbreviation for Web Server Gateway Interface, and details can be viewed on the WSGI wiki page

Use of web.py with HTTP Server for interface

The following example is from the official document Hello world, which is typically the code for the application Portal:

Import Web urls = ("/.*", "Hello") app = Web.application (URLs, Globals ()) class Hello:    def GET (self):        return ' Hello ' , world! ' if __name__ = = "__main__":    App.run ()

The above example describes the most basic elements of a web.py application:

    • URL routing Table
    • An Web.application instance app
    • Call App.run ()

where App.run () is called to initialize various wcgi interfaces and start a built-in HTTP server and docking these interfaces, the code is as follows:

def run (self, *middleware):    return Wsgi.runwsgi (Self.wsgifunc (*middleware))
Docking with the WSGI application server

If your application is to be connected to a WSGI application server, such as Uwsgi,gunicorn, then the code for the application entry will be written in a different way:

Import WebClass Hello:  def GET (self):    return ' Hello, world! ' URLs = ("/.*", "Hello") app = Web.application (URLs, Globals ()) application = App.wsgifunc ()

In this scenario, the applied code does not need to start the HTTP server, but instead implements an WSGI-compatible interface for the WSGI server to invoke. web.py framework for us to implement such an interface, you only need to call application = App.wsgifunc () on it, the resulting application variable is the Wsgi interface (after analyzing the code later you will know).

Implementation analysis of WSGI interface

The analysis is carried out mainly around the following two lines of code:

App = Web.application (URLs, Globals ()) application = App.wsgifunc ()
Web.application instantiation

The initialization of this instance requires passing two parameters: The result of the URL routing tuple and the Globals ().

In addition, the third variable can be passed: Autoreload, which is used to specify whether the Python module needs to be automatically re-imported, which is useful when debugging, but we can ignore it when we analyze the main process.

The initialization code for the application class is as follows:

Class application:  def __init__ (self, mapping= (), fvars={}, Autoreload=none):    if Autoreload is None:      Autoreload = Web.config.get (' Debug ', False)    self.init_mapping (mapping)    self.fvars = Fvars    Self.processors = []    self.add_processor (Loadhook (self._load))    self.add_processor (Unloadhook (self._unload )    if autoreload: ...      

The code for AUTORELOAD related functions is omitted. The other code mainly does the following things:

    • Self.init_mapping (mapping): Initializes the URL route mapping relationship.
    • Self.add_processor (): two processors added.
Initializing URL route mapping relationships
def init_mapping (self, mapping):    self.mapping = List (Utils.group (mapping, 2))

This function also calls a tool function, and the effect is this:

URLs = ("/", "Index",        "/hello/(. *)", "Hello",        "/world", "World")

If a tuple is passed when the user initializes it, after calling Init_mapping:

self.mapping = [["/", "Index"],                ["/hello/(. *)", "Hello"],                ["/world", "World"]

This list is traversed by the back frame when it is routing URLs.

Adding processors
    Self.add_processor (Loadhook (self._load))    self.add_processor (Unloadhook (self._unload))

These two lines of code add two processors: Self._load and Self._unload, and these two functions are also decorated. The processor is used before and after the HTTP request processing, it is not really used to process an HTTP request, but can be used to do some extra work, such as the official tutorial in the reference to add a session to the sub-application of the practice, is the use of the processor:

Def session_hook ():    web.ctx.session = Sessionapp.add_processor (Web.loadhook (Session_hook))

The definition and use of a processor are more complex, and are later specifically said.

Wsgifunc function

The result of Wsgifunc is to return a WSGI-compatible function, and the function internally implements URL routing.

def wsgifunc (self, *middleware): "" "  Returns a wsgi-compatible function for this application.  " "" ...  For M in middleware:     Wsgi = m (wsgi)  return WSGI

Except for the definition of intrinsic functions, the definition of wsgifunc is so simple that if you do not implement any middleware, you return directly to the WSGI function that is defined internally.

Wsgi function

This function realizes the Wsgi compatible interface, and also implements the function of URL routing.

def WSGI (env, START_RESP):  # Clear threadlocal to avoid inteference of previous requests  Self._cleanup ()  Self.load (env)  try:    # Allow uppercase methods only    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 ' # Force this function to is a generator  return Itertools.chain (result, Cleanup ()) for M in middleware:   Wsgi = m (wsgi) return ws Gi

Here's a closer look at this function:

    Self._cleanup ()    self.load (env)

Self._cleanup () calls Utils internally. Threadeddict.clear_all (), clears all thread local data and avoids memory leaks (because much of the web.py framework's data is stored in the thread local variable).

Self.load (env) uses the parameters in env to initialize the WEB.CTX variable, which covers the information of the current request, which we may use in the application, such as Web.ctx.fullpath.

    Try:  # Allow uppercase methods only  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]

This section is primarily called self.handle_with_processors (), which routes the URL of the request, finds the appropriate class or sub-application to handle the request, and invokes the added processor to do some other work (on the part of the processor, later specifically). There are three possible ways to return results for processing:

    • Returns an iterative object that is secured for iterative processing.
    • Returns other values, a list object is created to hold.
    • If a Httperror exception is thrown (for example, we use the raise web. Ok ("Hello, World") this way to return the result), the data e.data in the exception is encapsulated into a list.

-

  result = WEB.SAFESTR (iter (Result))  status, headers = Web.ctx.status, web.ctx.headers  start_resp (status, Headers)  def cleanup ():    self._cleanup ()    yield ' # force this function to be a generator  return itertools . Chain (result, Cleanup ())

The next piece of code will string the result of the list returned earlier to get the body part of the HTTP response. Then the following two things are done according to the WSGI specification:

    • Call the START_RESP function.
    • Converts result results to an iterator.

Now you can see that the application = App.wsgifunc () We mentioned earlier is assigning the WSGI function to the application variable so that the application server can use the WSGI standard to dock with our application.

Handling HTTP Requests

The previously analyzed code illustrates how the web.py framework implements the WSGI-compatible interface, which means that we already know the process of HTTP requests arriving at the framework and returning from the framework to the application server. So how does the framework internally invoke our application code to implement a request for processing? This requires a detailed analysis of the process of adding and invoking the processor that was just ignored.

Loadhook and Unloadhook Decorators

These two functions are the adorner function of the function of the real processor (although his use is not using the adorner's @ operator), the resulting processor is mapped to the request before processing (Loadhook) and request processing (Unloadhook) respectively.

Loadhook
def loadhook (h):    def processor (handler):        h ()        return handler ()    return processor

This function returns a function processor, which ensures that the processor function h you provide is called first, and then the subsequent operation function handler is called.

Unloadhook
def unloadhook (h):  def processor (handler):    try:      result = Handler ()      Is_generator = result and Hasattr ( result, ' next ')    except:      # Run the hook even when handler raises some exception      H ()      raise    if Is_gen Erator:      return Wrap (Result)    else:      H ()      return result  def wrap (Result):    def next ():      try:        return Result.next () except: # Call the hook at the and of        iterator        H ()        raise    result = ITER (result) while    True:      yield Next ()  return processor

This function also returns a processor, which invokes the handler passed in by the parameter before invoking the processor function that you provide.

handle_with_processors function
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 ()  # Processors must be applied in the Resvere order . (??)  return process (self.processors)

This function is very complex, the core part of the implementation of recursion (I do not feel recursive should also be able to achieve the same function). In order to illustrate clearly, use the example description.

As mentioned earlier, when initializing a application instance, two processors are added to Self.processors:

    Self.add_processor (Loadhook (self._load))    self.add_processor (Unloadhook (self._unload))

So, now the self.processors is like this:

Self.processors = [Loadhook (self._load), Unloadhook (self._unload)]# to facilitate follow-up instructions, we abbreviated: Self.processors = [Load_ Processor, Unload_processor]

These processors are executed one at a time when the framework starts executing handle_with_processors. Let's take a look at code decomposition and 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))  # position 3      else:        return Self.handle ()  # location 4    except Web. Httperror:      raise ...  # processors must is applied in the Resvere order. (??)  return process (self.processors)  # position 1
    1. The starting point for the function execution is position 1, which calls its internal definition function process (processors).
    2. If location 2 determines that the processor list is not empty, enter if inside.
    3. At position 3, call the handler function that needs to be executed, the parameter is a lambda function, and then return.
    4. If location 2 determines that the processor list is empty, execute Self.handle (), which actually calls our application code (as described below).

For the above example, there are currently two processors:

Self.processors = [Load_processor, Unload_processor]

After entering the code from position 1, at position 2 It is determined that the processor is going to execute and will go to position 3, at which point the code is executed:

Return Load_processor (Lambda:process ([unload_processor]))

The Load_processor function is a loadhook decorated function, so its definition is executed as follows:

def load_processor (Lambda:process ([Unload_processor]):    self._load ()    return process ([unload_processor])  # is the lambda function of the parameter

Executes the self._load () before continuing with the process function, still going to position 3, at which point the code to execute is:

Return Unload_processor (Lambda:process ([]))

The Unload_processor function is a unloadhook decorated function, so its definition is executed as follows:

def unload_processor (Lambda:process ([])):  try:    result = Process ([])  # parameter passed in the lambda function    is_generator = result and Hasattr (result, ' next ')  except:    # Run the hook even when handler raises some exception    Self._unlo AD ()    raise  if Is_generator:    return Wrap (Result)  else:    self._unload ()    return result

The process ([]) function is now executed and goes to position 4 (where Self.handle () is called), resulting in the processing of the application and then invoking the handler function Self._unload () of the processor.

Summarize the order of execution:

    • Self._load ()
      • Self.handle ()
    • Self._unload ()

If there are more processors, it is also done in this way, for loadhook decoration of the processor, first added first executed, for the Unloadhook decoration of the processor, after the addition of the first execution.

Handle function

Speaking so much, we are talking about the place where we really want to call the code we write. After all the load processors are executed, the Self.handle () function is executed, and the application code we write is called inside. Like to return a Hello, world, something like that. 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 very good to understand that the first line called Self._match is to do the routing function, find the corresponding class or sub-application, the second line of Self._delegate is to call this class or pass the request to the sub-application.

_match function

The _match function is defined as follows:

def _match (self, mapping, value):  to Pat, what in mapping:    if Isinstance (What, application):  # position 1      if VA Lue.startswith (PAT):        f = lambda:self._delegate_sub_application (Pat, what)        return F, None      else:        Continue    elif isinstance (What, basestring):  # position 2 What      , result = UTILS.RE_SUBM (' ^ ' + Pat + ' $ ', what, value) C11/>else:  # position 3      result = Utils.re_compile (' ^ ' + Pat + ' $ '). Match (value)    if result: # It's a match      ret Urn what, [x for X in Result.groups ()]  return None, none

The parameter of this function is mapping is self.mapping, is the URL route map table; value is Web.ctx.path, which is the request path. This function iterates through the self.mapping, processing the type of the object in the mapping relationship:

    • Position 1, the processing object is an application instance, which is a child application, returns an anonymous function that calls self._delegate_sub_application for processing.
    • Position 2, if the processing object is a string, then call UTILS.RE_SUBM for processing, here will be the value (that is, Web.ctx.path) and Pat matching part of what (that is, we specify a URL pattern processing object string), and then Returns the replaced result and the matching item (is a re. Matchobject instance).
    • Position 3, in other cases, such as directly specifying a class object as the processing object.

If result is non-null, the processing object and a list of arguments are returned (this parameter list is the parameter passed to the GET function we implement).

_delegate function

The result returned from the _match function is passed as a parameter to the _delegate function:

fn, args = Self._match (self.mapping, Web.ctx.path) return Self._delegate (FN, self.fvars, args)

which

    • fn: is the object to process the current request, typically a class name.
    • Args: is the parameter to pass to the request processing object.
    • Self.fvars: Is the global namespace when instantiating application, which is used to find processing objects.

The _delegate function is implemented as follows:

def _delegate (self, F, fvars, args=[]): Def handle_class (CLS): Meth = web.ctx.method if meth = "HEAD" and not Hasa TTR (CLS, meth): Meth = ' GET ' If not hasattr (CLS, meth): Raise Web.nomethod (CLS) Tocall = GetAttr (CLS (), ME TH) 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_process      ORS () 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 is mainly based on the type of the parameter F to make different processing:

    • F is empty, the 302 not Found is returned.
    • F is an application instance, the Handle_with_processors () of the child application is called for processing.
    • F is a class object, the intrinsic function handle_class is called.
    • F is a string that is either redirected or gets the class name to process the request, calling Handle_class for processing (the code we write is typically called under this branch).
    • F is a callable object that is called directly.
    • Other cases return 302 not Found.
    • This article is from: Linux Tutorial Network

web.py Source Analysis: Application

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.