WSGI

全稱為Web Server Gateway Interface,即 Web服務(wù)器網(wǎng)關(guān)接口瓮下。是一種標(biāo)準(zhǔn)接口規(guī)范翰铡,規(guī)定了 web 服務(wù)器 和 Python web 應(yīng)用/框架 之間如何傳遞數(shù)據(jù)钝域,以便 web 應(yīng)用 可以與多種 web 服務(wù)器配合工作。

HTTP 客戶端 --- web 服務(wù)器 --- WSGI --- Flask

作用:

  • 讓 web 服務(wù)器知道如何調(diào)用 web 應(yīng)用锭魔,傳遞用戶的請(qǐng)求給應(yīng)用
  • 讓應(yīng)用知道用戶的請(qǐng)求內(nèi)容例证,以及如何返回消息給 web 服務(wù)器

WSGI 的兩種角色

server/gateway, 通常是 web 服務(wù)器,接受客戶的請(qǐng)求迷捧,調(diào)用 application织咧,將 application 處理的結(jié)果封裝成 HTTP 響應(yīng)返回給客戶。

application/framework, 是 Python 應(yīng)用

application 是一個(gè)需要兩個(gè)參數(shù)的可調(diào)用對(duì)象漠秋,可以是一個(gè)函數(shù)笙蒙、方法,或一個(gè)有__call__方法的實(shí)例膛堤。

角色的實(shí)現(xiàn)

application 端 : 由 Python 框架實(shí)現(xiàn)手趣,會(huì)提供接口讓開發(fā)者能夠獲取到請(qǐng)求內(nèi)容晌该,并幫助進(jìn)行響應(yīng)返回

server 端 : 一般 web 服務(wù)器 不內(nèi)置對(duì) WSGI 的支持肥荔,需要通過擴(kuò)展來完成,比如 Apache 的 mod_wsgi 擴(kuò)展模塊朝群、Nginx 的 uWSGI燕耿。擴(kuò)展可以實(shí)現(xiàn) WSGI 的服務(wù)端、進(jìn)程管理姜胖、對(duì) application 的調(diào)用

application

在 web 框架中定義

這里舉了兩個(gè) application 對(duì)象的例子誉帅,一個(gè)是通過函數(shù)實(shí)現(xiàn),另一個(gè)通過類實(shí)現(xiàn)

HELLO_WORLD = b"Hello world!\n"

def simple_app(environ, start_response):
    """Simplest possible application object"""
    status = '200 OK'
    response_headers = [('Content-type', 'text/plain')]
    start_response(status, response_headers)
    return [HELLO_WORLD]
HELLO_WORLD = b"Hello world!\n"

class AppClass:
    """Produce the same output, but using a class

    (Note: 'AppClass' is the "application" here, so calling it
    returns an instance of 'AppClass', which is then the iterable
    return value of the "application callable" as required by
    the spec.

    If we wanted to use *instances* of 'AppClass' as application
    objects instead, we would have to implement a '__call__'
    method, which would be invoked to execute the application,
    and we would need to create an instance for use by the
    server or gateway.
    """

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

    def __iter__(self):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        self.start(status, response_headers)
        yield HELLO_WORLD

server 調(diào)用 application 對(duì)象

每個(gè) web 應(yīng)用只有一個(gè)入口右莱,就是按照 WSGI 規(guī)范定義的 application蚜锨,這個(gè)可調(diào)用對(duì)象在 Python 應(yīng)用的一個(gè)文件/模塊(入口文件)中定義。

每當(dāng) web 服務(wù)器從一個(gè) HTTP 客戶端收到請(qǐng)求慢蜓,便會(huì)調(diào)用這個(gè) application亚再,將用戶請(qǐng)求傳遞給 web 應(yīng)用。

server 調(diào)用 application 時(shí)傳遞兩個(gè)參數(shù)

application對(duì)象必須接受兩個(gè)位置參數(shù):environstart_response晨抡,對(duì)參數(shù)名稱沒有強(qiáng)制要求氛悬。因此,server/gateway 在調(diào)用application對(duì)象時(shí)耘柱,必須產(chǎn)生并傳遞兩個(gè)位置參數(shù)如捅,而不是關(guān)鍵字參數(shù)。比如调煎,這樣調(diào)用result = application(environ, start_response)镜遣。

environ參數(shù)是一個(gè)字典對(duì)象, 包含 CGI 規(guī)范中定義的environment變量。這個(gè)對(duì)象必須是一個(gè) Python 內(nèi)建的字典, application 可以根據(jù)需要修改內(nèi)容士袄。字典還必須包含 WSGI 規(guī)范要求的變量, 以及 server 指定的擴(kuò)展變量烈涮、任意的操作系統(tǒng)的環(huán)境變量鱼响。

environ中常用的成員,首先是CGI規(guī)范中要求必須包含的變量煤惩,除非值為空字符串:

  • REQUEST_METHOD: HTTP 請(qǐng)求方法笛质,是個(gè)字符串,'GET'讶舰、 'POST'等
  • SCRIPT_NAME: HTTP請(qǐng)求的path中的用于查找到application對(duì)象的部分鞍盗,比如Web服務(wù)器可以根據(jù)path的一部分來決定請(qǐng)求由哪個(gè)virtual host處理
  • PATH_INFO: HTTP請(qǐng)求的path中剩余的部分,也就是application要處理的部分
  • QUERY_STRING: HTTP請(qǐng)求中的查詢字符串跳昼,URL中?后面的內(nèi)容
  • CONTENT_TYPE: HTTP headers中的content-type內(nèi)容
  • CONTENT_LENGTH: HTTP headers中的content-length內(nèi)容
  • SERVER_NAME 和 SERVER_PORT: 服務(wù)器名和端口般甲,這兩個(gè)值和前面的SCRIPT_NAME, PATH_INFO拼起來可以得到完整的URL路徑
  • SERVER_PROTOCOL: HTTP協(xié)議版本,HTTP/1.0或者HTTP/1.1
  • HTTP_: 和HTTP請(qǐng)求中的headers對(duì)應(yīng)鹅颊。
  • WSGI規(guī)范中還要求environ包含下列成員:

WSGI規(guī)范中要求必須有的environ變量:

  • wsgi.version:表示W(wǎng)SGI版本敷存,一個(gè)元組(1, 0),表示版本1.0
  • wsgi.url_scheme:http或者h(yuǎn)ttps
  • wsgi.input:一個(gè)類文件的輸入流堪伍,application可以通過這個(gè)獲取HTTP request body
  • wsgi.errors:一個(gè)輸出流锚烦,當(dāng)應(yīng)用程序出錯(cuò)時(shí),可以將錯(cuò)誤信息寫入這里
  • wsgi.multithread:當(dāng)application對(duì)象可能被多個(gè)線程同時(shí)調(diào)用時(shí)帝雇,這個(gè)值需要為True
  • wsgi.multiprocess:當(dāng)application對(duì)象可能被多個(gè)進(jìn)程同時(shí)調(diào)用時(shí)涮俄,這個(gè)值需要為True
  • wsgi.run_once:當(dāng)server期望application對(duì)象在進(jìn)程的生命周期內(nèi)只被調(diào)用一次時(shí),該值為True

start_response是一個(gè)可調(diào)用的對(duì)象尸闸,接受兩個(gè)必須的位置參數(shù)和一個(gè)可選的參數(shù)彻亲。通常命名為status,response_headersexc_info,但不強(qiáng)制吮廉。start_response的定義方式:start_response(status, response_headers)苞尝。

status參數(shù)是一個(gè)表示 HTTP 響應(yīng)狀態(tài)的字符串, 例如200 okresponse_headers是一個(gè)列表宦芦,由多個(gè)(header_name, header_value)元組組成宙址,描述 HTTP 響應(yīng)頭部∽倏酰可選參數(shù)exc_info曼氛,僅用于 server 向客戶端報(bào)錯(cuò)并在瀏覽器中顯示錯(cuò)誤。

start_response必須返回一個(gè)可調(diào)用的write(body_data)令野,有一個(gè)位置參數(shù): HTTP 響應(yīng)的內(nèi)容舀患。

web 服務(wù)器的例子

import os, sys

enc, esc = sys.getfilesystemencoding(), 'surrogateescape'

def unicode_to_wsgi(u):
    # Convert an environment variable to a WSGI "bytes-as-unicode" string
    return u.encode(enc, esc).decode('iso-8859-1')

def wsgi_to_bytes(s):
    return s.encode('iso-8859-1')

def run_with_cgi(application):
    environ = {k: unicode_to_wsgi(v) for k,v in os.environ.items()}   # 定義 environ
    environ['wsgi.input']        = sys.stdin.buffer
    environ['wsgi.errors']       = sys.stderr
    environ['wsgi.version']      = (1, 0)
    environ['wsgi.multithread']  = False
    environ['wsgi.multiprocess'] = True
    environ['wsgi.run_once']     = True

    if environ.get('HTTPS', 'off') in ('on', '1'):
        environ['wsgi.url_scheme'] = 'https'
    else:
        environ['wsgi.url_scheme'] = 'http'

    headers_set = []
    headers_sent = []

    def write(data):
        out = sys.stdout.buffer

        if not headers_set:
             raise AssertionError("write() before start_response()")

        elif not headers_sent:
             # Before the first output, send the stored headers
             status, response_headers = headers_sent[:] = headers_set
             out.write(wsgi_to_bytes('Status: %s\r\n' % status))
             for header in response_headers:
                 out.write(wsgi_to_bytes('%s: %s\r\n' % header))
             out.write(wsgi_to_bytes('\r\n'))

        out.write(data)
        out.flush()

    def start_response(status, response_headers, exc_info=None):   # 定義 start_response
        if exc_info:
            try:
                if headers_sent:
                    # Re-raise original exception if headers sent
                    raise exc_info[1].with_traceback(exc_info[2])
            finally:
                exc_info = None     # avoid dangling circular ref
        elif headers_set:
            raise AssertionError("Headers already set!")

        headers_set[:] = [status, response_headers]

        # Note: error checking on the headers should happen here,
        # *after* the headers are set.  That way, if an error
        # occurs, start_response can only be re-called with
        # exc_info set.

        return write

    result = application(environ, start_response)     # 調(diào)用 application
    try:
        for data in result:
            if data:    # don't send headers until body appears
                write(data)
        if not headers_sent:
            write('')   # send headers now if body was empty
    finally:
        if hasattr(result, 'close'):
            result.close()

application 返回?cái)?shù)據(jù)

被調(diào)用的 application 對(duì)象根據(jù) environ 的內(nèi)容完成業(yè)務(wù)邏輯,并返回?cái)?shù)據(jù)給 server

  • 先調(diào)用start_response()气破,返回statusresponse_headers給 server 作為 HTTP 響應(yīng)頭部聊浅。這同時(shí)也是一個(gè)信號(hào),告訴 server,要開始返回 HTTP 的 body 了
  • 然后低匙,通過 return 返回一個(gè)可迭代對(duì)象作為 HTTP 響應(yīng)內(nèi)容旷痕,如果響應(yīng)為空,可以返回None

這樣 server 可以按照規(guī)定的 HTTP 報(bào)文格式順序顽冶,先發(fā)送 HTTP 響應(yīng)頭部欺抗,然后再發(fā)送 HTTP 響應(yīng)的內(nèi)容。

WSGI 中間件

需要注意的是强重,有些應(yīng)用可以同時(shí)扮演 WSGI 的兩種角色/具有對(duì)應(yīng)的功能绞呈,比如中間件(middleware)。這是運(yùn)行在 server 與 application 之間的應(yīng)用间景。

對(duì)于 server 佃声,中間件是 application,而對(duì)于 application倘要,中間件是 server圾亏。

可以生成environ, 定義start_response, 調(diào)用application對(duì)象。也可以執(zhí)行業(yè)務(wù)邏輯封拧,調(diào)用start_response志鹃,并通過return返回結(jié)果。server 獲取結(jié)果哮缺,發(fā)送給客戶弄跌。

中間件可以有多層甲喝,能夠處理所有經(jīng)過的requestresponse尝苇,比如檢查、修改埠胖。

中間件的工作過程:


上圖中最上面的三個(gè)彩色框表示角色糠溜,中間的白色框表示操作,操作的發(fā)生順序按照1 ~ 5進(jìn)行了排序直撤,我們直接對(duì)著上圖來說明middleware是如何工作的:

  1. Server 收到客戶端的 HTTP 請(qǐng)求后非竿,生成了environ_s,并且已經(jīng)定義了start_response_s谋竖。
  2. Server 調(diào)用Middlewareapplication對(duì)象红柱,傳遞的參數(shù)是environ_sstart_response_s
  3. Middleware 會(huì)根據(jù)environ執(zhí)行業(yè)務(wù)邏輯蓖乘,生成environ_m锤悄,并且已經(jīng)定義了start_response_m
  4. Middleware 決定調(diào)用 Application 的 application 對(duì)象嘉抒,傳遞參數(shù)是environ_mstart_response_m零聚。Application 的 application 對(duì)象處理完成后,會(huì)調(diào)用start_response_m并且返回結(jié)果給Middleware ,存放在result_m中隶症。
  5. Middleware 處理result_m政模,然后生成result_s,接著調(diào)用start_response_s蚂会,并返回結(jié)果result_s給 Server 端淋样。Server 端獲取到result_s后就可以發(fā)送結(jié)果給客戶端了。

web 框架 WSGI application 端代碼

Pyramid

from pyramid.config import Configurator
from pyramid.response import Response
 
def hello_world(request):
    return Response(
        'Hello world from Pyramid!n',
        content_type='text/plain',
    )
 
config = Configurator()
config.add_route('hello', '/hello')
config.add_view(hello_world, route_name='hello')
app = config.make_wsgi_app()

pyramid.config.__init__.py

from pyramid.router import Router

class Configurator(
    TestingConfiguratorMixin,
    TweensConfiguratorMixin,
    SecurityConfiguratorMixin,
    ViewsConfiguratorMixin,
    RoutesConfiguratorMixin,
    ZCAConfiguratorMixin,
    I18NConfiguratorMixin,
    RenderingConfiguratorMixin,
    AssetsConfiguratorMixin,
    SettingsConfiguratorMixin,
    FactoriesConfiguratorMixin,
    AdaptersConfiguratorMixin,
    ):
    """
    A Configurator is used to configure a :app:`Pyramid`
    :term:`application registry`.

    """
    
    def make_wsgi_app(self):

        self.commit()
        app = Router(self.registry)

        global_registries.add(self.registry)

        self.manager.push({'registry':self.registry, 'request':None})
        try:
            self.registry.notify(ApplicationCreated(app))
        finally:
            self.manager.pop()

        return app

pyramid.Router.py

@implementer(IRouter)
class Router(object):

    debug_notfound = False
    debug_routematch = False

    threadlocal_manager = manager

    def __init__(self, registry):
        q = registry.queryUtility
        self.logger = q(IDebugLogger)
        self.root_factory = q(IRootFactory, default=DefaultRootFactory)
        self.routes_mapper = q(IRoutesMapper)
        self.request_factory = q(IRequestFactory, default=Request)
        self.request_extensions = q(IRequestExtensions)
        tweens = q(ITweens)
        if tweens is None:
            tweens = excview_tween_factory
        self.orig_handle_request = self.handle_request
        self.handle_request = tweens(self.handle_request, registry)
        self.root_policy = self.root_factory # b/w compat
        self.registry = registry
        settings = registry.settings
        if settings is not None:
            self.debug_notfound = settings['debug_notfound']
            self.debug_routematch = settings['debug_routematch']

    def handle_request(self, request):
        attrs = request.__dict__
        registry = attrs['registry']

        request.request_iface = IRequest
        context = None
        routes_mapper = self.routes_mapper
        debug_routematch = self.debug_routematch
        adapters = registry.adapters
        has_listeners = registry.has_listeners
        notify = registry.notify
        logger = self.logger

        has_listeners and notify(NewRequest(request))
        # find the root object
        root_factory = self.root_factory
        if routes_mapper is not None:
            info = routes_mapper(request)
            match, route = info['match'], info['route']
            if route is None:
                if debug_routematch:
                    msg = ('no route matched for url %s' %
                           request.url)
                    logger and logger.debug(msg)
            else:
                attrs['matchdict'] = match
                attrs['matched_route'] = route

                if debug_routematch:
                    msg = (
                        'route matched for url %s; '
                        'route_name: %r, '
                        'path_info: %r, '
                        'pattern: %r, '
                        'matchdict: %r, '
                        'predicates: %r' % (
                            request.url,
                            route.name,
                            request.path_info,
                            route.pattern,
                            match,
                            ', '.join([p.text() for p in route.predicates]))
                        )
                    logger and logger.debug(msg)

                request.request_iface = registry.queryUtility(
                    IRouteRequest,
                    name=route.name,
                    default=IRequest)

                root_factory = route.factory or self.root_factory

        root = root_factory(request)
        attrs['root'] = root

        # find a context
        traverser = adapters.queryAdapter(root, ITraverser)
        if traverser is None:
            traverser = ResourceTreeTraverser(root)
        tdict = traverser(request)

        context, view_name, subpath, traversed, vroot, vroot_path = (
            tdict['context'],
            tdict['view_name'],
            tdict['subpath'],
            tdict['traversed'],
            tdict['virtual_root'],
            tdict['virtual_root_path']
            )

        attrs.update(tdict)
        has_listeners and notify(ContextFound(request))

        # find a view callable
        context_iface = providedBy(context)
        response = _call_view(
            registry,
            request,
            context,
            context_iface,
            view_name
            )

        if response is None:
            if self.debug_notfound:
                msg = (
                    'debug_notfound of url %s; path_info: %r, '
                    'context: %r, view_name: %r, subpath: %r, '
                    'traversed: %r, root: %r, vroot: %r, '
                    'vroot_path: %r' % (
                        request.url, request.path_info, context,
                        view_name, subpath, traversed, root, vroot,
                        vroot_path)
                    )
                logger and logger.debug(msg)
            else:
                msg = request.path_info
            raise HTTPNotFound(msg)

        return response

    def invoke_subrequest(self, request, use_tweens=False):

        registry = self.registry
        has_listeners = self.registry.has_listeners
        notify = self.registry.notify
        threadlocals = {'registry':registry, 'request':request}
        manager = self.threadlocal_manager
        manager.push(threadlocals)
        request.registry = registry
        request.invoke_subrequest = self.invoke_subrequest
        
        if use_tweens:
            handle_request = self.handle_request
        else:
            handle_request = self.orig_handle_request

        try:

            try:
                extensions = self.request_extensions
                if extensions is not None:
                    apply_request_extensions(request, extensions=extensions)
                response = handle_request(request)

                if request.response_callbacks:
                    request._process_response_callbacks(response)

                has_listeners and notify(NewResponse(request, response))
                
                return response

            finally:
                if request.finished_callbacks:
                    request._process_finished_callbacks()

        finally:
            manager.pop()

    def __call__(self, environ, start_response):       # 按照 WSGI 規(guī)范定義的 application
        """
        Accept ``environ`` and ``start_response``; create a
        :term:`request` and route the request to a :app:`Pyramid`
        view based on introspection of :term:`view configuration`
        within the application registry; call ``start_response`` and
        return an iterable.
        """
        request = self.request_factory(environ)
        response = self.invoke_subrequest(request, use_tweens=True)
        return response(request.environ, start_response)

flask

from flask import Flask
from flask import Response
flask_app = Flask('flaskapp')
 
@flask_app.route('/hello')
def hello_world():
    return Response(
        'Hello world from Flask!n',
        mimetype='text/plain'
    )
 
app = flask_app.wsgi_app

flask.app.py

class Flask(_PackageBoundObject):
    """The flask object implements a WSGI application and acts as the central
    object.  It is passed the name of the module or package of the
    application.  Once it is created it will act as a central registry for
    the view functions, the URL rules, template configuration and much more.

    The name of the package is used to resolve resources from inside the
    package or the folder the module is contained in depending on if the
    package parameter resolves to an actual python package (a folder with
    an `__init__.py` file inside) or a standard module (just a `.py` file).

    For more information about resource loading, see :func:`open_resource`.

    Usually you create a :class:`Flask` instance in your main module or
    in the `__init__.py` file of your package like this::

        from flask import Flask
        app = Flask(__name__)

    .. admonition:: About the First Parameter

        The idea of the first parameter is to give Flask an idea what
        belongs to your application.  This name is used to find resources
        on the file system, can be used by extensions to improve debugging
        information and a lot more.

        So it's important what you provide there.  If you are using a single
        module, `__name__` is always the correct value.  If you however are
        using a package, it's usually recommended to hardcode the name of
        your package there.

        For example if your application is defined in `yourapplication/app.py`
        you should create it with one of the two versions below::

            app = Flask('yourapplication')
            app = Flask(__name__.split('.')[0])

        Why is that?  The application will work even with `__name__`, thanks
        to how resources are looked up.  However it will make debugging more
        painful.  Certain extensions can make assumptions based on the
        import name of your application.  For example the Flask-SQLAlchemy
        extension will look for the code in your application that triggered
        an SQL query in debug mode.  If the import name is not properly set
        up, that debugging information is lost.  (For example it would only
        pick up SQL queries in `yourapplication.app` and not
        `yourapplication.views.frontend`)

    """

    #: Default configuration parameters.
    default_config = ImmutableDict({
        'DEBUG':                                False,
        'TESTING':                              False,
        'PROPAGATE_EXCEPTIONS':                 None,
        'PRESERVE_CONTEXT_ON_EXCEPTION':        None,
        'SECRET_KEY':                           None,
        'PERMANENT_SESSION_LIFETIME':           timedelta(days=31),
        'USE_X_SENDFILE':                       False,
        'LOGGER_NAME':                          None,
        'SERVER_NAME':                          None,
        'APPLICATION_ROOT':                     None,
        'SESSION_COOKIE_NAME':                  'session',
        'SESSION_COOKIE_DOMAIN':                None,
        'SESSION_COOKIE_PATH':                  None,
        'SESSION_COOKIE_HTTPONLY':              True,
        'SESSION_COOKIE_SECURE':                False,
        'MAX_CONTENT_LENGTH':                   None,
        'SEND_FILE_MAX_AGE_DEFAULT':            12 * 60 * 60, # 12 hours
        'TRAP_BAD_REQUEST_ERRORS':              False,
        'TRAP_HTTP_EXCEPTIONS':                 False,
        'PREFERRED_URL_SCHEME':                 'http',
        'JSON_AS_ASCII':                        True,
        'JSON_SORT_KEYS':                       True,
        'JSONIFY_PRETTYPRINT_REGULAR':          True,
    })

    def __init__(self, import_name, static_path=None, static_url_path=None,
                 static_folder='static', template_folder='templates',
                 instance_path=None, instance_relative_config=False):
        _PackageBoundObject.__init__(self, import_name,
                                     template_folder=template_folder)
        if static_path is not None:
            from warnings import warn
            warn(DeprecationWarning('static_path is now called '
                                    'static_url_path'), stacklevel=2)
            static_url_path = static_path

        if static_url_path is not None:
            self.static_url_path = static_url_path
        if static_folder is not None:
            self.static_folder = static_folder
        if instance_path is None:
            instance_path = self.auto_find_instance_path()
        elif not os.path.isabs(instance_path):
            raise ValueError('If an instance path is provided it must be '
                             'absolute.  A relative path was given instead.')

        self.instance_path = instance_path

        self.config = self.make_config(instance_relative_config)

        # Prepare the deferred setup of the logger.
        self._logger = None
        self.logger_name = self.import_name

        self.view_functions = {}

        self._error_handlers = {}

        self.error_handler_spec = {None: self._error_handlers}

        self.url_build_error_handlers = []

        self.before_request_funcs = {}

        self.before_first_request_funcs = []

        self.after_request_funcs = {}

        self.teardown_request_funcs = {}

        self.teardown_appcontext_funcs = []

        self.url_value_preprocessors = {}

        self.url_default_functions = {}

        self.template_context_processors = {
            None: [_default_template_ctx_processor]
        }

        self.blueprints = {}

        self.extensions = {}

        self.url_map = Map()

        self._got_first_request = False
        self._before_request_lock = Lock()

        if self.has_static_folder:
            self.add_url_rule(self.static_url_path + '/<path:filename>',
                              endpoint='static',
                              view_func=self.send_static_file)

    def make_response(self, rv):
        """Converts the return value from a view function to a real
        response object that is an instance of :attr:`response_class`.

        The following types are allowed for `rv`:

        .. tabularcolumns:: |p{3.5cm}|p{9.5cm}|

        ======================= ===========================================
        :attr:`response_class`  the object is returned unchanged
        :class:`str`            a response object is created with the
                                string as body
        :class:`unicode`        a response object is created with the
                                string encoded to utf-8 as body
        a WSGI function         the function is called as WSGI application
                                and buffered as response object
        :class:`tuple`          A tuple in the form ``(response, status,
                                headers)`` where `response` is any of the
                                types defined here, `status` is a string
                                or an integer and `headers` is a list of
                                a dictionary with header values.
        ======================= ===========================================

        :param rv: the return value from the view function

        .. versionchanged:: 0.9
           Previously a tuple was interpreted as the arguments for the
           response object.
        """
        status = headers = None
        if isinstance(rv, tuple):
            rv, status, headers = rv + (None,) * (3 - len(rv))

        if rv is None:
            raise ValueError('View function did not return a response')

        if not isinstance(rv, self.response_class):
            # When we create a response object directly, we let the constructor
            # set the headers and status.  We do this because there can be
            # some extra logic involved when creating these objects with
            # specific values (like default content type selection).
            if isinstance(rv, (text_type, bytes, bytearray)):
                rv = self.response_class(rv, headers=headers, status=status)
                headers = status = None
            else:
                rv = self.response_class.force_type(rv, request.environ)

        if status is not None:
            if isinstance(status, string_types):
                rv.status = status
            else:
                rv.status_code = status
        if headers:
            rv.headers.extend(headers)

        return rv

    def wsgi_app(self, environ, start_response):       # 按照 WSGI 規(guī)范定義的 application
        """The actual WSGI application.  This is not implemented in
        `__call__` so that middlewares can be applied without losing a
        reference to the class.  So instead of doing this::

            app = MyMiddleware(app)

        It's a better idea to do this instead::

            app.wsgi_app = MyMiddleware(app.wsgi_app)

        Then you still have the original application object around and
        can continue to call methods on it.

        .. versionchanged:: 0.7
           The behavior of the before and after request callbacks was changed
           under error conditions and a new callback was added that will
           always execute at the end of the request, independent on if an
           error occurred or not.  See :ref:`callbacks-and-errors`.

        :param environ: a WSGI environment
        :param start_response: a callable accepting a status code,
                               a list of headers and an optional
                               exception context to start the response
        """
        ctx = self.request_context(environ)
        ctx.push()
        error = None
        try:
            try:
                response = self.full_dispatch_request()
            except Exception as e:
                error = e
                response = self.make_response(self.handle_exception(e))
            return response(environ, start_response)
        finally:
            if self.should_ignore_error(error):
                error = None
            ctx.auto_pop(error)

    def __call__(self, environ, start_response):
        """Shortcut for :attr:`wsgi_app`."""
        return self.wsgi_app(environ, start_response)

django

# -*- coding:utf-8 -*-
"""
WSGI config for helloworld project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
"""

# 配置 settings 模塊
import os
# os.environ.setdefault("DJANGO_SETTINGS_MODULE", "helloworld.settings")
os.environ["DJANGO_SETTINGS_MODULE"]="helloworld.settings"

'''
如果 "DJANGO_SETTINGS_MODULE"這個(gè)變量沒有設(shè)置胁住,默認(rèn) wsgi.py 設(shè)置為 mysite.settings, mysite 是你的項(xiàng)目的名稱习蓬。這是默認(rèn)情況下, runserver 發(fā)現(xiàn)配置的地方措嵌。
'''

# 應(yīng)用 WSGI middleware
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()

django.core.wsgi

import django
from django.core.handlers.wsgi import WSGIHandler


def get_wsgi_application():
    """
    The public interface to Django's WSGI support. Should return a WSGI
    callable.

    Allows us to avoid making django.core.handlers.WSGIHandler public API, in
    case the internal WSGI implementation changes or moves in the future.
    """
    django.setup()
    return WSGIHandler()

django.core.handlers.wsgi

class WSGIHandler(base.BaseHandler):
    initLock = Lock()
    request_class = WSGIRequest

    def __call__(self, environ, start_response):       # 按照 WSGI 規(guī)范定義的 application
        # Set up middleware if needed. We couldn't do this earlier, because
        # settings weren't available.
        if self._request_middleware is None:
            with self.initLock:
                try:
                    # Check that middleware is still uninitialized.
                    if self._request_middleware is None:
                        self.load_middleware()
                except:
                    # Unload whatever middleware we got
                    self._request_middleware = None
                    raise

        set_script_prefix(get_script_name(environ))
        signals.request_started.send(sender=self.__class__, environ=environ)
        try:
            request = self.request_class(environ)
        except UnicodeDecodeError:
            logger.warning('Bad Request (UnicodeDecodeError)',
                exc_info=sys.exc_info(),
                extra={
                    'status_code': 400,
                }
            )
            response = http.HttpResponseBadRequest()
        else:
            response = self.get_response(request)

        response._handler_class = self.__class__

        status = '%s %s' % (response.status_code, response.reason_phrase)
        response_headers = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values():
            response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
        start_response(force_str(status), response_headers)
        if getattr(response, 'file_to_stream', None) is not None and environ.get('wsgi.file_wrapper'):
            response = environ['wsgi.file_wrapper'](response.file_to_stream)
        return response

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末躲叼,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子企巢,更是在濱河造成了極大的恐慌枫慷,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件浪规,死亡現(xiàn)場(chǎng)離奇詭異或听,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)笋婿,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門誉裆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人缸濒,你說我怎么就攤上這事足丢。” “怎么了庇配?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵斩跌,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我捞慌,道長(zhǎng)耀鸦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任啸澡,我火速辦了婚禮袖订,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘嗅虏。我一直安慰自己洛姑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布旋恼。 她就那樣靜靜地躺著吏口,像睡著了一般奄容。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上产徊,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天昂勒,我揣著相機(jī)與錄音,去河邊找鬼舟铜。 笑死戈盈,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的谆刨。 我是一名探鬼主播塘娶,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼痊夭!你這毒婦竟也來了刁岸?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤她我,失蹤者是張志新(化名)和其女友劉穎虹曙,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體番舆,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡酝碳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了恨狈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疏哗。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖禾怠,靈堂內(nèi)的尸體忽然破棺而出返奉,到底是詐尸還是另有隱情,我是刑警寧澤刃宵,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布衡瓶,位于F島的核電站徘公,受9級(jí)特大地震影響牲证,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜关面,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一坦袍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧等太,春花似錦捂齐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春压真,著一層夾襖步出監(jiān)牢的瞬間娩嚼,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國打工滴肿, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留岳悟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓泼差,卻偏偏與公主長(zhǎng)得像贵少,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子堆缘,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理滔灶,服務(wù)發(fā)現(xiàn),斷路器吼肥,智...
    卡卡羅2017閱讀 134,637評(píng)論 18 139
  • 轉(zhuǎn)載自標(biāo)點(diǎn)符的《網(wǎng)關(guān)協(xié)議學(xué)習(xí):CGI、FastCGI唆鸡、WSGI》 CGI CGI即通用網(wǎng)關(guān)接口(Common Ga...
    李紹俊閱讀 1,657評(píng)論 0 1
  • WSGI協(xié)議 首先弄清下面幾個(gè)概念:WSGI:全稱是Web Server Gateway Interface涝影,WS...
    rainybowe閱讀 68,549評(píng)論 13 140
  • Num01-->瀏覽器動(dòng)態(tài)請(qǐng)求頁面流程圖 以上圖片就是整個(gè)瀏覽器動(dòng)態(tài)請(qǐng)求服務(wù)器的全過程。 Num02-->什么是W...
    曉可加油閱讀 2,953評(píng)論 0 8
  • 想做什么就去做争占,想吃什么就去吃燃逻,不要瞻前顧后,只有試過了臂痕,才能知道結(jié)果伯襟,只有吃過了,才知道味道如何握童,年輕的時(shí)候多犯...
    木木大噠閱讀 158評(píng)論 0 0