wsgi_wsgi 原理 - CSDN
精华内容
参与话题
  • Python之Wsgi

    2018-06-09 10:56:25
    全称为Web Server Gateway Interface,即 Web服务器网关接口。是一种标准接口规范,规定了 ...HTTP 客户端 --- web 服务器 --- WSGI --- Flask作用:让 web 服务器知道如何调用 web 应用,传递用户的请求给应用让应...

    全称为Web Server Gateway Interface,即 Web服务器网关接口。是一种标准接口规范,规定了 web 服务器 和 Python web 应用/框架 之间如何传递数据,以便 web 应用 可以与多种 web 服务器配合工作。

    HTTP 客户端 --- web 服务器 --- WSGI --- Flask

    作用:

    • 让 web 服务器知道如何调用 web 应用,传递用户的请求给应用
    • 让应用知道用户的请求内容,以及如何返回消息给 web 服务器

    WSGI 的两种角色

    server/gateway, 通常是 web 服务器,接受客户的请求,调用 application,将 application 处理的结果封装成 HTTP 响应返回给客户。

    application/framework, 是 Python 应用

    application 是一个需要两个参数的可调用对象,可以是一个函数、方法,或一个有__call__方法的实例。

    角色的实现

    application 端 : 由 Python 框架实现,会提供接口让开发者能够获取到请求内容,并帮助进行响应返回

    server 端 : 一般 web 服务器 不内置对 WSGI 的支持,需要通过扩展来完成,比如 Apache 的 mod_wsgi 扩展模块、Nginx 的 uWSGI。扩展可以实现 WSGI 的服务端、进程管理、对 application 的调用

    application

    在 web 框架中定义

    这里举了两个 application 对象的例子,一个是通过函数实现,另一个通过类实现

    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 调用 application 对象

    每个 web 应用只有一个入口,就是按照 WSGI 规范定义的 application,这个可调用对象在 Python 应用的一个文件/模块(入口文件)中定义。

    每当 web 服务器从一个 HTTP 客户端收到请求,便会调用这个 application,将用户请求传递给 web 应用。

    server 调用 application 时传递两个参数

    application对象必须接受两个位置参数:environstart_response,对参数名称没有强制要求。因此,server/gateway 在调用application对象时,必须产生并传递两个位置参数,而不是关键字参数。比如,这样调用result = application(environ, start_response)

    environ参数是一个字典对象, 包含 CGI 规范中定义的environment变量。这个对象必须是一个 Python 内建的字典, application 可以根据需要修改内容。字典还必须包含 WSGI 规范要求的变量, 以及 server 指定的扩展变量、任意的操作系统的环境变量。

    environ中常用的成员,首先是CGI规范中要求必须包含的变量,除非值为空字符串:

    • REQUEST_METHOD: HTTP 请求方法,是个字符串,'GET'、 'POST'等
    • SCRIPT_NAME: HTTP请求的path中的用于查找到application对象的部分,比如Web服务器可以根据path的一部分来决定请求由哪个virtual host处理
    • PATH_INFO: HTTP请求的path中剩余的部分,也就是application要处理的部分
    • QUERY_STRING: HTTP请求中的查询字符串,URL中?后面的内容
    • CONTENT_TYPE: HTTP headers中的content-type内容
    • CONTENT_LENGTH: HTTP headers中的content-length内容
    • SERVER_NAME 和 SERVER_PORT: 服务器名和端口,这两个值和前面的SCRIPT_NAME, PATH_INFO拼起来可以得到完整的URL路径
    • SERVER_PROTOCOL: HTTP协议版本,HTTP/1.0或者HTTP/1.1
    • HTTP_: 和HTTP请求中的headers对应。
    • WSGI规范中还要求environ包含下列成员:

    WSGI规范中要求必须有的environ变量:

    • wsgi.version:表示WSGI版本,一个元组(1, 0),表示版本1.0
    • wsgi.url_scheme:http或者https
    • wsgi.input:一个类文件的输入流,application可以通过这个获取HTTP request body
    • wsgi.errors:一个输出流,当应用程序出错时,可以将错误信息写入这里
    • wsgi.multithread:当application对象可能被多个线程同时调用时,这个值需要为True
    • wsgi.multiprocess:当application对象可能被多个进程同时调用时,这个值需要为True
    • wsgi.run_once:当server期望application对象在进程的生命周期内只被调用一次时,该值为True

    start_response是一个可调用的对象,接受两个必须的位置参数和一个可选的参数。通常命名为status,response_headersexc_info,但不强制。start_response的定义方式:start_response(status, response_headers)

    status参数是一个表示 HTTP 响应状态的字符串, 例如200 okresponse_headers是一个列表,由多个(header_name, header_value)元组组成,描述 HTTP 响应头部。可选参数exc_info,仅用于 server 向客户端报错并在浏览器中显示错误。

    start_response必须返回一个可调用的write(body_data),有一个位置参数: HTTP 响应的内容。

    web 服务器的例子

    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)     # 调用 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 返回数据

    被调用的 application 对象根据 environ 的内容完成业务逻辑,并返回数据给 server

    • 先调用start_response(),返回statusresponse_headers给 server 作为 HTTP 响应头部。这同时也是一个信号,告诉 server,要开始返回 HTTP 的 body 了
    • 然后,通过 return 返回一个可迭代对象作为 HTTP 响应内容,如果响应为空,可以返回None

    这样 server 可以按照规定的 HTTP 报文格式顺序,先发送 HTTP 响应头部,然后再发送 HTTP 响应的内容。

    WSGI 中间件

    需要注意的是,有些应用可以同时扮演 WSGI 的两种角色/具有对应的功能,比如中间件(middleware)。这是运行在 server 与 application 之间的应用。

    对于 server ,中间件是 application,而对于 application,中间件是 server。

    可以生成environ, 定义start_response, 调用application对象。也可以执行业务逻辑,调用start_response,并通过return返回结果。server 获取结果,发送给客户。

    中间件可以有多层,能够处理所有经过的requestresponse,比如检查、修改。

    中间件的工作过程:


    上图中最上面的三个彩色框表示角色,中间的白色框表示操作,操作的发生顺序按照1 ~ 5进行了排序,我们直接对着上图来说明middleware是如何工作的:

    1. Server 收到客户端的 HTTP 请求后,生成了environ_s,并且已经定义了start_response_s
    2. Server 调用Middlewareapplication对象,传递的参数是environ_sstart_response_s
    3. Middleware 会根据environ执行业务逻辑,生成environ_m,并且已经定义了start_response_m
    4. Middleware 决定调用 Application 的 application 对象,传递参数是environ_mstart_response_m。Application 的 application 对象处理完成后,会调用start_response_m并且返回结果给Middleware ,存放在result_m中。
    5. Middleware 处理result_m,然后生成result_s,接着调用start_response_s,并返回结果result_s给 Server 端。Server 端获取到result_s后就可以发送结果给客户端了。

    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 规范定义的 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 规范定义的 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"这个变量没有设置,默认 wsgi.py 设置为 mysite.settings, mysite 是你的项目的名称。这是默认情况下, runserver 发现配置的地方。
    '''
    
    # 应用 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 规范定义的 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
    

    参考资料



    作者:超net
    链接:https://www.jianshu.com/p/34ee01d85b0a
    來源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    学习:
    展开全文
  • 在 “三百六十行,行行转 IT” 的现状下,很多来自各行各业的同学,都选择 Python 这门胶水语言作为踏入互联网大门的第一块敲门砖,在这些人里,又有相当大比例的同学选...
        

    在 “三百六十行,转 IT” 的现状下,很多来自各行各业的同学,都选择 Python 这门胶水语言作为踏入互联网大门的第一块敲门砖,在这些人里,又有相当大比例的同学选择了 Web 开发这个方向(包括我,曾经也想选择)。而做 web 开发,绕不过一个知识点,就是 WSGI。

    转载来源

    公众号:Python 编程时光

    阅读本文大概需要 15 分钟。


    不管你是否是这些如上这些同学中的一员,都应该好好地学习一下这个知识点。

    由于我本人不从事专业的 Python Web 开发,所以在写这篇文章的时候,借鉴了许多优秀的网络博客,并花了很多的精力阅读了大量的 OpenStack 代码。

    为了写这篇文章,我零零散散地花了大概两个星期的时间。本来可以拆成多篇文章,写成一个系列的,经过一番思虑,还是准备一篇讲完,这就是本篇文章这么长的原因,微信后台显示将近15000字(有一定水分)。

    另外,一篇文章是不能吃透一个知识点的,本篇涉及的背景知识也比较多的,若我有讲得不到位的,还请你多多查阅其他人的网络博客进一步学习。

    在你往下看之前,我先问你几个问题,你带着这些问题往下看,可能更有目的性,学习起来效果会更好。

    问1:一个 HTTP 请求到达对应的 application处理函数要经过怎样的过程?

    问2:如何不通过流行的 web 框架来写一个简单的web服务?

    一个HTTP请求的过程可以分为两个阶段,第一阶段是从客户端到WSGI Server,第二阶段是从 WSGI Server 到 WSGI Application

    640?wx_fmt=png

    今天主要是讲第二阶段,主要内容有以下几点:

    1、WSGI 是什么,因何而生?
    2、HTTP请求是如何到应用程序的?
    3、实现一个简单的 WSGI Server
    4、实现“高并发”的WSGI Server
    5、第一次路由:PasteDeploy
    6、PasteDeploy 使用说明
    7、webob.dec.wsgify 装饰器
    8、第二次路由:中间件 routes 路由

    1. WSGI 是什么,因何而生?

    WSGI是 Web Server Gateway Interface 的缩写。

    它是 Python应用程序(application)或框架(如 Django)和 Web服务器之间的一种接口,已经被广泛接受。

    它是一种协议,一种规范,其是在 PEP 3333 提出的。这个协议旨在解决众多 web 框架和web server软件的兼容问题。有了WSGI,你不用再因为你使用的web 框架而去选择特定的 web server软件。

    常见的web应用框架有:Django,Flask等

    常用的web服务器软件有:uWSGI,Gunicorn等

    那这个 WSGI 协议内容是什么呢?知乎上有人将 PEP 3333 翻译成中文,写得非常好,我将这段协议的内容搬运过来。

    WSGI 接口有服务端和应用端两部分,服务端也可以叫网关端,应用端也叫框架端。服务端调用一个由应用端提供的可调用对象。如何提供这个对象,由服务端决定。例如某些服务器或者网关需要应用的部署者写一段脚本,以创建服务器或者网关的实例,并且为这个实例提供一个应用实例。另一些服务器或者网关则可能使用配置文件或其他方法以指定应用实例应该从哪里导入或获取。

    WSGI 对于 application 对象有如下三点要求:

    1. 必须是一个可调用的对象

    2. 接收两个必选参数environ、start_response。

    3. 返回值是可迭代对象,用来表示http body。

    2. HTTP请求是如何到应用程序的?

    当客户端发出一个 HTTP 请求后,是如何转到我们的应用程序处理并返回的呢?

    关于这个过程,细节的点这里没法细讲,只能讲个大概。

    我根据其架构组成的不同将这个过程的实现分为两种:

    640?wx_fmt=png

    1、两级结构
    在这种结构里,uWSGI作为服务器,它用到了HTTP协议以及wsgi协议,flask应用作为application,实现了wsgi协议。当有客户端发来请求,uWSGI接受请求,调用flask app得到相应,之后相应给客户端。 
    这里说一点,通常来说,Flask等web框架会自己附带一个wsgi服务器(这就是flask应用可以直接启动的原因),但是这只是在开发阶段用到的,在生产环境是不够用的,所以用到了uwsgi这个性能高的wsgi服务器。

    2、三级结构
    这种结构里,uWSGI作为中间件,它用到了uwsgi协议(与nginx通信),wsgi协议(调用Flask app)。当有客户端发来请求,nginx先做处理(静态资源是nginx的强项),无法处理的请求(uWSGI),最后的相应也是nginx回复给客户端的。 
    多了一层反向代理有什么好处?

    提高web server性能(uWSGI处理静态资源不如nginx;nginx会在收到一个完整的http请求后再转发给wWSGI)

    nginx可以做负载均衡(前提是有多个服务器),保护了实际的web服务器(客户端是和nginx交互而不是uWSGI)

    3. 实现一个简单的 WSGI Server

    在上面的架构图里,不知道你发现没有,有个库叫做 wsgiref ,它是 Python 自带的一个 wsgi 服务器模块。

    从其名字上就看出,它是用纯Python编写的WSGI服务器的参考实现。所谓“参考实现”是指该实现完全符合WSGI标准,但是不考虑任何运行效率,仅供开发和测试使用。

    有了 wsgiref 这个模块,你就可以很快速的启动一个wsgi server。

    from wsgiref.simple_server import make_server

    # 这里的 appclass 暂且不说,后面会讲到
    app = appclass()
    server = make_server(''64570, app)
    server.serve_forever()

    当你运行这段代码后,就会开启一个 wsgi server,监听 0.0.0.0:64570 ,并接收请求。

    使用 lsof 命令可以查到确实开启了这个端口

    640?wx_fmt=png

    以上使用 wsgiref 写了一个demo,让你对wsgi有个初步的了解。其由于只适合在学习测试使用,在生产环境中应该另寻他道。

    4. 实现“高并发”的 WSGI Server

    上面我们说不能在生产中使用 wsgiref ,那在生产中应该使用什么呢?选择有挺多的,比如优秀的 uWSGI,Gunicore等。但是今天我并不准备讲这些,一是因为我不怎么熟悉,二是因为我本人从事 OpenStack 的二次开发,对它比较熟悉。

    所以下面,是我花了几天时间阅读 OpenStack 中的 Nova 组件代码的实现,刚好可以拿过来学习记录一下,若有理解偏差,还望你批评指出。

    在 nova 组件里有不少服务,比如 nova-api,nova-compute,nova-conductor,nova-scheduler 等等。

    其中,只有 nova-api 有对外开启 http 接口。

    要了解这个http 接口是如何实现的,从服务启动入口开始看代码,肯定能找到一些线索。

    从 Service 文件可以得知 nova-api 的入口是 nova.cmd.api:main()

    640?wx_fmt=png
    640?wx_fmt=png

    打开nova.cmd.api:main() ,一起看看是 OpenStack Nova 的代码。

    在如下的黄框里,可以看到在这里使用了service.WSGIService 启动了一个 server,就是我们所说的的 wsgi server

    640?wx_fmt=png

    那这里的 WSGI Server 是依靠什么实现的呢?让我们继续深入源代码。

    640?wx_fmt=png

    wsgi.py 可以看到这里使用了 eventlet 这个网络并发框架,它先开启了一个绿色线程池,从配置里可以看到这个服务器可以接收的请求并发量是 1000 。

    640?wx_fmt=png

    可是我们还没有看到 WSGI Server 的身影,上面使用eventlet 开启了线程池,那线程池里的每个线程应该都是一个服务器吧?它是如何接收请求的?

    再继续往下,可以发现,每个线程都是使用 eventlet.wsgi.server 开启的 WSGI Server,还是使用的 eventlet。

    由于源代码比较多,我提取了主要的代码,精简如下

    # 创建绿色线程池
    self._pool = eventlet.GreenPool(self.pool_size)

    # 创建 socket:监听的ip,端口
    bind_addr = (host, port)
    self._socket = eventlet.listen(bind_addr, family, backlog=backlog)
    dup_socket = self._socket.dup()

    # 整理孵化协程所需的各项参数
    wsgi_kwargs = {
        'func': eventlet.wsgi.server,
        'sock': dup_socket,
        'site': self.app, # 这个就是 wsgi 的 application 函数
        'protocol': self._protocol,
        'custom_pool': self._pool,
        'log': self._logger,
        'log_format': CONF.wsgi.wsgi_log_format,
        'debug'False,
        'keepalive': CONF.wsgi.keep_alive,
        'socket_timeout': self.client_socket_timeout
    }

    # 孵化协程
    self._server = utils.spawn(**wsgi_kwargs)

    640?wx_fmt=png

    就这样,Nova 开启了一个可以接受1000个请求并发(理论值,应该有瓶颈)的 WSGI Server。

    5. 第一次路由:PasteDeploy

    上面我们提到 WSGI Server 的创建要传入一个 Application,用来处理接收到的请求,对于一个有多个 app 的项目。

    比如,你有一个个人网站提供了如下几个模块

    /blog  # 博客 app
    /wiki  # wiki app

    如何根据 请求的url 地址,将请求转发到对应的application上呢?

    答案是,使用 PasteDeploy 这个库(在 OpenStack 中各组件被广泛使用)。

    PasteDeploy 到底是做什么的呢?

    根据 官方文档 的说明,翻译如下

    PasteDeploy 是用来寻找和配置WSGI应用和服务的系统。PasteDeploy给开发者提供了一个简单的函数loadapp。通过这个函数,可以从一个配置文件或者Python egg中加载一个WSGI应用。

    使用PasteDeploy的其中一个重要意义在于,系统管理员可以安装和管理WSGI应用,而无需掌握与Python和WSGI相关知识。

    由于 PasteDeploy 原来是属于 Paste 的,现在独立出来了,但是安装的时候还是会安装到paste目录(site-packages\paste\deploy)下。

    我会先讲下在 Nova 中,是如何借助 PasteDeploy 实现对url的路由转发。

    还记得在上面创建WSGI Server的时候,传入了一个 self.app 参数,这个app并不是一个固定的app,而是使用 PasteDeploy 中提供的 loadapp 函数从 paste.ini 配置文件中加载application。

    具体可以,看下Nova的实现。

    640?wx_fmt=png

    通过打印的 DEBUG 内容得知 config_url 和 app name 的值

    app: osapi_compute
    config_url: /etc/nova/api-paste.inia

    通过查看 /etc/nova/api-paste.ini  ,在 composite 段里找到了 osapi_compute 这个app(这里的app和wsgi app 是两个概念,需要注意区分) ,可以看出 nova 目前有两个版本的api,一个是 v2,一个是v2.1,目前我们在用的是 v2.1,从配置文件中,可以得到其指定的 application 的路径是nova.api.openstack.compute 这个模块下的 APIRouterV21 类 的factory方法,这是一个工厂函数,返回 APIRouterV21 实例。

    [composite:osapi_compute]
    use = call:nova.api.openstack.urlmap:urlmap_factory
    /: oscomputeversions
    /v2: openstack_compute_api_v21_legacy_v2_compatible
    /v2.1: openstack_compute_api_v21

    [app:osapi_compute_app_v21]

    paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory

    这是 OpenStack 使用 PasteDeploy 实现的第一层的路由,如果你不感兴趣,可以直接略过本节,进入下一节,下一节是 介绍 PasteDeploy 的使用,教你实现一个简易的Web Server demo。推荐一定要看。

    6. PasteDeploy 使用说明

    到上一步,我已经得到了 application 的有用的线索。考虑到很多人是第一次接触 PasteDeploy,所以这里结合网上博客做了下总结。对你入门会有帮助。

    掌握 PasteDeploy ,你只要按照以下三个步骤逐个完成即可。

    1、配置 PasteDeploy使用的ini文件;

    2、定义WSGI应用;

    3、通过loadapp函数加载WSGI应用;

    第一步:写 paste.ini 文件

    在写之前,咱得知道 ini 文件的格式吧。

    首先,像下面这样一个段叫做 section

    [type:name]
    key = value
    ...

    其上的type,主要有如下几种

    1. composite (组合):多个app的路由分发;

      [composite:main]
      use = egg:Paste#urlmap
      / = home
      /blog = blog
      /wiki = wiki
    2. app(应用):指明 WSGI 应用的路径;

      [app:home]
      paste.app_factory = example:Home.factory
    3. pipeline(管道):给一个 app 绑定多个过滤器。将多个filter和最后一个WSGI应用串联起来。

      [pipeline:main]
      pipeline = filter1 filter2 filter3 myapp

      [filter:filter1]

      ...

      [filter:filter2]

      ...

      [app:myapp]

      ...
    4. filter(过滤器):以 app 做为唯一参数的函数,并返回一个“过滤”后的app。通过键值next可以指定需要将请求传递给谁。next指定的可以是一个普通的WSGI应用,也可以是另一个过滤器。虽然名称上是过滤器,但是功能上不局限于过滤功能,可以是其它功能,例如日志功能,即将认为重要的请求数据记录下来。

      [app-filter:filter_name]
      use = egg:...
      next = next_app

      [app:next_app]

      ...

    对 ini 文件有了一定的了解后,就可以看懂下面这个 ini 配置文件了

    [composite:main]
    use = egg:Paste#urlmap
    /blog = blog
    /wiki = wiki

    [app:blog]

    paste.app_factory = example:Blog.factory

    [app:wiki]

    paste.app_factory = example:Wiki.factory

    第二步是定义一个符合 WSGI 规范的 applicaiton 对象。

    符合 WSGI 规范的 application 对象,可以有多种形式,函数,方法,类,实例对象。这里仅以实例对象为例(需要实现 __call__ 方法),做一个演示。

    import osfrom paste import deployfrom wsgiref.simple_server import make_serverclass Blog(object):    def __init__(self):        print("Init Blog.")    def __call__(self, environ, start_response):        status_code = "200 OK"        response_headers = [("Content-Type", "text/plain")]        response_body = "This is Blog's response body.".encode('utf-8')        start_response(status_code, response_headers)        return [response_body]    @classmethod    def factory(cls, global_conf, **kwargs):        print("Blog factory.")        return Blog()
    from paste import deploy
    from wsgiref.simple_server import make_server

    class Blog(object):
        def __init__(self):
            print("Init Blog.")

        def __call__(self, environ, start_response):
            status_code = "200 OK"
            response_headers = [("Content-Type""text/plain")]
            response_body = "This is Blog's response body.".encode('utf-8')

            start_response(status_code, response_headers)
            return [response_body]

        @classmethod
        def factory(cls, global_conf, **kwargs):
            print("Blog factory.")
            return Blog()

    最后,第三步是使用 loadapp 函数加载 WSGI 应用。

    loadapp 是 PasteDeploy 提供的一个函数,使用它可以很方便地从第一步的ini配置文件里加载 app

    loadapp 函数可以接收两个实参:

    1. WSGI 对于 application 对象有如下三点要求

    2. URI:"config:<配置文件的全路径>"

    conf_path = os.path.abspath('paste.ini')

    # 加载 app
    applications = deploy.loadapp("config:{}".format(conf_path) , "main")

    # 启动 server, 监听 localhost:22800 
    server = make_server("localhost""22800", applications)
    server.serve_forever()

    applications 是URLMap 对象。

    640?wx_fmt=png

    完善并整合第二步和第三步的内容,写成一个 Python 文件(wsgi_server.py)。内容如下

    import os
    from paste import deploy
    from wsgiref.simple_server import make_server

    class Blog(object):
        def __init__(self):
            print("Init Blog.")

        def __call__(self, environ, start_response):
            status_code = "200 OK"
            response_headers = [("Content-Type""text/plain")]
            response_body = "This is Blog's response body.".encode('utf-8')

            start_response(status_code, response_headers)
            return [response_body]

        @classmethod
        def factory(cls, global_conf, **kwargs):
            print("Blog factory.")
            return Blog()


    class Wiki(object):
        def __init__(self):
            print("Init Wiki.")

        def __call__(self, environ, start_response):
            status_code = "200 OK"
            response_headers = [("Content-Type""text/plain")]
            response_body = "This is Wiki's response body.".encode('utf-8')

            start_response(status_code, response_headers)
            return [response_body]

        @classmethod
        def factory(cls, global_conf, **kwargs):
            print("Wiki factory.")
            return Wiki()


    if __name__ == "__main__":
        app = "main"
        port = 22800
        conf_path = os.path.abspath('paste.ini')

        # 加载 app
        applications = deploy.loadapp("config:{}".format(conf_path) , app)
        server = make_server("localhost", port, applications)

        print('Started web server at port {}'.format(port))
        server.serve_forever()

    一切都准备好后,在终端执行 python wsgi_server.py来启动 web server

    640?wx_fmt=png

    如果像上图一样一切正常,那么打开浏览器

    • 访问http://127.0.0.1:8000/blog,应该显示:This is Blog's response body.

    • 访问http://127.0.0.1:8000/wiki,应该显示:This is Wiki's response body.。

    注意:urlmap对url的大小写是敏感的,例如如果访问http://127.0.0.1:8000/BLOG,在url映射中未能找到大写的BLOG。

    到此,你学会了使用 PasteDeploy 的简单使用。

    7. webob.dec.wsgify 装饰器

    经过了 PasteDeploy 的路由调度,我们找到了 nova.api.openstack.compute:APIRouterV21.factory 这个 application 的入口,看代码知道它其实返回了 APIRouterV21 类的一个实例。

    640?wx_fmt=png

    WSGI规定 application 必须是一个 callable 的对象,函数、方法、类、实例,若是一个类实例,就要求这个实例所属的类实现 __call__ 的方法。

    APIRouterV21 本身没有实现 __call__ ,但它的父类 Router实现了 __call__ 

    640?wx_fmt=png

    我们知道,application 必须遵丛 WSGI 的规范

    1. 必须接收environ, start_response两个参数;

    2. 必须返回 「可迭代的对象」。

    但从 Router 的 __call__ 代码来看,它并没有遵从这个规范,它不接收这两个参数,也不返回 response,而只是返回另一个 callable 的对象,就这样我们的视线被一次又一次的转移,但没有关系,这些__call__都是外衣,只要扒掉这些外衣,我们就能看到核心app。

    而负责扒掉这层外衣的,就是其头上的装饰器 @webob.dec.wsgify ,wsgify 是一个类,其 __call__ 源码实现如下:

    640?wx_fmt=png

    可以看出,wsgify 在这里,会将 req 这个原始请求(dict对象)封装成 Request 对象(就是规范1里提到的 environ)。然后会一层一层地往里地执行被wsgify装饰的函数(self._route), 得到最内部的核心application。

    上面提到了规范1里的第一个参数,补充下第二个参数start_response,它是在哪定义并传入的呢?

    其实这个无需我们操心,它是由 wsgi server 提供的,如果我们使用的是 wsgiref 库做为 server 的话。那这时的 start_response 就由 wsgiref 提供。

    再回到 wsgify,它的作用主要是对 WSGI app 进行封装,简化wsgi app的定义与编写,它可以很方便的将一个 callable 的函数或对象,封装成一个 WSGI app。

    上面,其实留下了一个问题,self._route(routes 中间件 RoutesMiddleware对象)是如何找到真正的 application呢?

    带着这个问题,我们了解下 routes 是如何为我们实现第二次路由。

    8. 第二次路由:中间件 routes 路由

    在文章最开始处,我们给大家画了一张图。

    640?wx_fmt=png

    这张图把一个 HTTP 请求粗略简单地划分为两个过程。但事实上,整个过程远比这个过程要复杂得多。

    实际上在 WSGI Server 到 WSGI Application 这个过程中,我们加很多的功能(比如鉴权、URL路由),而这些功能的实现方式,我们称之为中间件。

    今天以URL路由为例,来讲讲中间件在实际生产中是如何起作用的。

    当服务器拿到了客户端请求的URL,不同的URL需要交由不同的函数处理,这个功能叫做 URL Routing。

    在 Nova 中是用 routes 这个库来实现对URL的的路由调度。接下来,我将从源代码处分析一下这个过程。

    在routes模块里有个中间件,叫 routes.middleware.RoutesMiddleware ,它将接受到的 url,自动调用 map.match()方法,对 url 进行路由匹配,并将匹配的结果存入request请求的环境变量['wsgiorg.routing_args'],最后会调用self._dispatch(dispatch返回真正的application)返回response,最后会将这个response返回给 WSGI Server。

    640?wx_fmt=png

    这个中间件的原理,看起来是挺简单的。并没有很复杂的逻辑。

    但是,我在阅读 routes 代码的时候,却发现了另一个令我困惑的点。

    self._dispatch (也就上图中的self.app)函数里,我们看到了 app,controller 这几个很重要的字眼,其是否是我苦苦追寻的 application 对象呢?

    640?wx_fmt=png

    要搞明白这个问题,只要看清 match 到是什么东西?

    这个 match 对象 是在 RoutesMiddleware.__call__() 里塞进 req.environ的,它是什么东西呢,我将其打印出来。

    {'action'u'detail''controller': <nova.api.openstack.wsgi.ResourceV21 object at 0x667bad0>, 'project_id'u'2ac17c7c792d45eaa764c30bac37fad9'}

    {'action'u'index''controller': <nova.api.openstack.wsgi.ResourceV21 object at 0x6ec8910>, 'project_id'u'2ac17c7c792d45eaa764c30bac37fad9'}

    {'action'u'show''controller': <nova.api.openstack.wsgi.ResourceV21 object at 0x6ed9710>, 'project_id'u'2ac17c7c792d45eaa764c30bac37fad9''id'u'68323d9c-ebe5-499a-92e9-32fea900a892'}

    结果令人在失所望呀,这个 app 并不是我们要寻找的 Controller 对象。而是 nova.api.openstack.wsgi.ResourceV21 类的实例对象,说白了就是Resource对象。

    看到这里,我有心态有点要崩了,怎么还没到 Controller?OpenStack 框架的代码绕来绕去的,没有点耐心还真的很难读下去。

    既然已经开了头,没办法还得硬着头皮继续读了下去。

    终于我发现,在APIRouter初始化的时候,它会去注册所有的 Resource,同时将这些 Resource 交由 routes.Mapper 来管理、创建路由映射,所以上面提到的 routes.middleware.RoutesMiddleware 才能根据url通过 mapper.match 获取到相应的Resource。

    从 Nova 代码中看出每个Resource 对应一个 Controller 对象,因为 Controller 对象本身就是对一种资源的操作集合。

    640?wx_fmt=png

    通过日志的打印,可以发现 nova 管理的 Resource 对象有多么的多而杂

    os-server-groups
    os-keypairs
    os-availability-zone
    remote-consoles
    os-simple-tenant-usage
    os-instance-actions
    os-migrations
    os-hypervisors
    diagnostics
    os-agents
    images
    os-fixed-ips
    os-networks
    os-security-groups
    os-security-groups
    os-security-group-rules
    flavors
    os-floating-ips-bulk
    os-console-auth-tokens
    os-baremetal-nodes
    os-cloudpipe
    os-server-external-events
    os-instance_usage_audit_log
    os-floating-ips
    os-security-group-default-rules
    os-tenant-networks
    os-certificates
    os-quota-class-sets
    os-floating-ip-pools
    os-floating-ip-dns
    entries
    os-aggregates
    os-fping
    os-server-password
    os-flavor-access
    consoles
    os-extra_specs
    os-interface
    os-services
    servers
    extensions
    metadata
    metadata
    limits
    ips
    os-cells
    versions
    tags
    migrations
    os-hosts
    os-virtual-interfaces
    os-assisted-volume-snapshots
    os-quota-sets
    os-volumes
    os-volumes_boot
    os-volume_attachments
    os-snapshots
    os-server-groups
    os-keypairs
    os-availability-zone
    remote-consoles
    os-simple-tenant-usage
    os-instance-actions
    os-migrations
    os-hypervisors
    diagnostics
    os-agents
    images
    os-fixed-ips
    os-networks
    os-security-groups
    os-security-groups
    os-security-group-rules
    flavors
    os-floating-ips-bulk
    os-console-auth-tokens
    os-baremetal-nodes
    os-cloudpipe
    os-server-external-events
    os-instance_usage_audit_log
    os-floating-ips
    os-security-group-default-rules
    os-tenant-networks
    os-certificates
    os-quota-class-sets
    os-floating-ip-pools
    os-floating-ip-dns
    entries
    os-aggregates
    os-fping
    os-server-password
    os-flavor-access
    consoles
    os-extra_specs
    os-interface
    os-services
    servers
    extensions
    metadata
    metadata
    limits
    ips
    os-cells
    versions
    tags
    migrations
    os-hosts
    os-virtual-interfaces
    os-assisted-volume-snapshots
    os-quota-sets
    os-volumes
    os-volumes_boot
    os-volume_attachments
    os-snapshots

    你一定很好奇,这路由是如何创建的吧,关键代码就是如下一行。如果你想要了解更多路由的创建过程,可以看一下这篇文章(Python Route总结:https://blog.csdn.net/bellwhl/article/details/8956088),写得不错。

    routes.mapper.connect("server",
                   "/{project_id}/servers/list_vm_state",
                   controller=self.resources['servers'],
                   action='list_vm_state',
                   conditions={'list_vm_state''GET'})

    历尽了千辛万苦,我终于找到了 Controller 对象,知道了请求发出后,wsgi server是如何根据url找到对应的Controller(根据routes.Mapper路由映射)。

    但是很快,你又会问。对于一个资源的操作(action),有很多,比如新增,删除,更新等

    不同的操作要执行Controller 里不同的函数。

    • 如果是新增资源,就调用 create()

    • 如果是删除资源,就调用 delete()

    • 如果是更新资源,就调用 update()

    那代码如何怎样知道要执行哪个函数呢?

    以/servers/xxx/action请求为例,请求调用的函数实际包含在请求的body中。

    经过routes.middleware.RoutesMiddleware的__call__函数解析后,此时即将调用的Resource已经确定为哪个模块中的Controller所构建的Resource,而 action 参数为"action",接下来在Resource的__all__ 函数里面会因为action=="action"从而开始解析body的内容,找出Controller中所对应的方法。

    Controller在构建的过程中会由于MetaClass的影响将其所有action类型的方法填入一个字典中,key由每个_action_xxx方法前的 @wsgi.action('xxx')装饰函数给出,value为每个action_xxx方法的名字(从中可以看出规律,在body里面请求的方法名前加上_aciton即为Controller中对应调用的方法)。

    之后在使用Controller构建Resource对象的过程中会向Resource注册该Controller的这个字典中的内容。这样,只需在请求的body中给出调用方法的key,然后就可以找到这个key所映射的方法,最后在Resource的__call__函数中会调用Controller类的这个函数!

    其实我在上面我们打印 match 对象时,就已经将对应的函数打印出来了。

    这边以 nova show(展示资源为例),来理解一下。

    当你调用 nova show [uuid] 命令,novaclient 就会给 nova-api 发送一个http的请求

    nova show 1c250b15-a346-43c5-9b41-20767ec7c94b

    通过打印得到的 match 对象如下

    {'action'u'show''controller': <nova.api.openstack.wsgi.ResourceV21 object at 0x667bad0>, 'project_id'u'2ac17c7c792d45eaa764c30bac37fad9'}

    其中 action 就是对应的处理函数,而controller 就对应的 Resource 对象,project_id 是租户id(你可以不理会)。

    继续看 ResourceV21 类里的 __call__ 函数的代码。

    图示地方,会从 environ 里获取中看到获取 action 的具体代码

    640?wx_fmt=png

    我将这边的 action_args打印出来

    {'action''show''project_id''2ac17c7c792d45eaa764c30bac37fad9''id''1c250b15-a346-43c5-9b41-20767ec7c94b'}

    其中 action 还是是函数名,id 是要操作的资源的唯一id标识。

    在 __call__ 的最后,会 调用 _process_stack 方法

    640?wx_fmt=png

    在图标处,get_method 会根据 action(函数名) 取得处理函数对象。

    meth :<bound method ServersController.show of <nova.api.openstack.compute.servers.ServersController object at 0x7be3750>>

    最后,再执行这个函数,取得 action_result,在 _process_stack 会对 response 进行初步封装。

    640?wx_fmt=png

    然后将 response 再返回到 wsgify ,由这个专业的工具函数,进行 response 的最后封装和返回给客户端。

    640?wx_fmt=png

    至此,一个请求从发出到响应就结束了。


    你能看到这里,真的很难得,本篇文章干货还是不少的。因为我自己不太喜欢讲理论,所以此次我结合了项目,对源码进行实战分析。

    原本我就只是给自己提出了个小问题,没想到给自己挖了这么大一个坑,这篇文章前前后后一共花了两个星期的时间,几乎所有的下班时间都花在这里了,这就是为什么近两周更新如此少的缘故。

    在这个过程中,确实也学到了不少东西。很多内容都是站在巨人的肩膀上,感谢如此多优秀的网络博客。同时这期间自行阅读了大量的OpenStack 源码,验证了不少自己疑惑已久的知识点,对自己的提升也很有帮助。

    最后,还是那句老话,如果你觉得此文对你有帮助,不防点个在看,转发一下。smiley_13.png

    参考文章


    https://zhuanlan.zhihu.com/p/27600327

    https://www.cnblogs.com/Security-Darren/p/4087587.html

    http://www.fmttr.com/python/thirdpartylibrary/pastedeploy/

    https://blog.csdn.net/baidu_35085676/article/details/80184874


    推荐阅读

    1

    跟繁琐的命令行说拜拜!Gerapy分布式爬虫管理框架来袭!

    2

    跟繁琐的模型说拜拜!深度学习脚手架 ModelZoo 来袭!

    3

    只会用Selenium爬网页?Appium爬App了解一下

    4

    妈妈再也不用担心爬虫被封号了!手把手教你搭建Cookies池


    崔庆才

    静觅博客博主,《Python3网络爬虫开发实战》作者

    隐形字

    个人公众号:进击的Coder

    640?wx_fmt=gif640?wx_fmt=jpeg640?wx_fmt=gif

    长按识别二维码关注


    好文和朋友一起看~
    展开全文
  • 进一步详解WSGI

    千次阅读 2016-10-21 22:08:15
    还是老样子,需要知道什么是WSGI? 一、什么是WSGI (1)、WSGI(Web 服务器网关接口)是python中所定义的Web服务器和Web应用程序之间或框架之间的接口标准规范 (2)、WSGI接口规范的目的就是规范Web服务器与Web应用之间...

    还是老样子,需要知道什么是WSGI?

    一、什么是WSGI

    (1)、WSGI(Web 服务器网关接口)是python中所定义的Web服务器和Web应用程序之间或框架之间的接口标准规范

    (2)、WSGI接口规范的目的就是规范Web服务器与Web应用之间的交互,在协议之间进行转换

    (3)、WSGI将Web组件分成三类:Web服务器(Server)、Web中间件与Web应用程序。        

       应用:指的是可以被调用的一个对象,一般指的是包含一个__call__方法(实例可以当作函数一般调用)的对象。

       服务器:指的是实现了调用应用的部分。

       中间件:处于服务器和应用两侧,起粘合作用,具体包括:请求处理、environ处理。

    举例说明:

    from wsgiref.simple_server import make_server
    from webob import Request, Response    #后面介绍这个模块
    
    class APPTest(object):
        def __call__(self, environ, start_response):
            urll =  ['%s : %s' % (key, value) for key,value in environ.items()]  #传递进来的environ环境变量
    
            print '\n'.join(urll)
            print '\n\n\n'
    
            req = Request(environ) #处理环境变量,生成Request对象,代表客户端HTTP请求传递而来的环境变量
            print req
    
            print '\n\n\n'
            return self.test(environ, start_response)
    
        def test(self, environ, start_response):
    
            urll =  ['%s : %s' % (key, value) for key,value in environ.items()]
    
            print '\n'.join(urll)
            print '\n\n\n'
    
            res = Response()  //Response类类型的实例对象res,实现__call__函数可以直接作为函数调用,对于HTTP响应 header和body的封装
            print res
            print '\n\n\n'
            print type(res)
    
            res.status = 200
            res.body = 'hello world'
            return res(environ, start_response)
    
    application = APPTest()
    
    httpd = make_server('127.0.0.1', 8000, application)
    print 'Listen on port 8000....'
    httpd.serve_forever()
    


    这个例子中,httpd服务器调用APPTest应用,中间件部分对Response部分做了处理。中间件主要处理Request和Response(HTTP请求信息和响应信息的封装)。

    print req;


    print res;


    print type(res);


    程序执行结果:

     

     

    二、environ变量都有哪些

      需要注意的是environ 变量,environ 字典中包含了在 CGI 规范中定义了的 CGI 环境变量。
     
    REQUEST_METHOD:HTTP 请求的类型,比如「GET」或者「POST」。这个不可能是空字符串,所以是必须给出的。

      SCRIPT_NAME:URL 请求中路径的开始部分,对应应用程序对象(?),这样应用程序就知道它的虚拟位置。如果该应用程序对应服务器的根目录的话,它可能是空字符串。
      PATH_INFO:URL 请求中路径的剩余部分,指定请求的目标在应用程序内部的虚拟位置(?)。如果请求的目标是应用程序根目录并且没有末尾的斜杠的话,可能为空字符串。


      QUERY_STRING:URL 请求中跟在「?」后面的那部分,可能为空或不存在。
      CONTENT_TYPE:HTTP 请求中任何 Content-Type 域的内容,可能为空或不存在
      CONTENT_LENGTH:HTTP 请求中任何 Content-Length 域的内容,可能为空或不存在。
      SERVER_NAME,SERVER_PORT:这些变量可以和 SCRIPT_NAME、PATH_INFO 一起组成完整的URL。然而要注意的是,重建请求 URL 的时候应该优先使用 HTTP_HOST 而非 SERVER_NAME 。。SERVER_NAME 和 SERVER_PORT 永远不能为空字符串,也总是必须存在的。
      SERVER_PROTOCOL:客户端发送请求所使用协议的版本。通常是类似「HTTP/1.0」或「HTTP/1.1」的东西,可以被用来判断如何处理请求包头。(既然这个变量表示的是请求中使用的协议,而且和服务器响应时使用的协议无关,也许它应该被叫做REQUEST_PROTOCOL。不过为了保持和 CGI 的兼容性,我们还是使用这个名字。)

      其他的看看了解下即可,等用到的时候再返回来查看。
      wsgi.version:(1, 0) 元组,代表 WSGI 1.0 版
      wsgi.url_scheme:字符串,表示应用请求的 URL 所属的协议,通常为「http」或「https」。
      wsgi.input: 类文件对象的输入流,用于读取 HTTP 请求包体的内容。(服务端在应用端请求时开始读取,或者预读客户端请求包体内容缓存在内存或磁盘中,或者视情况而定采用任何其他技术提供此输入流。)
      wsgi.errors: 类文件对象的输出流,用于写入错误信息,以集中规范地记录程序产生的或其他相关错误信息。这是一个文本流,即应用应该使用「n」来表示行尾,并假定其会被服务端正确地转换。(在 str 类型是 Unicode 编码的平台上,错误流应该正常接收并记录任意 Unicode 编码而不报错,并且允许自行替代在该平台编码中无法渲染的字符。)很多 Web 服务器中         wsgi.errors 是主要的错误日志,也有一些使用 sys.stderr 或其他形式的文件来记录。Web 服务器的自述文档中应该包含如何配置错误日志以及如何找到记录的位置。服务端可以在被要求的情况下,向不同的应用提供不同的错误日志。
      wsgi.multithread:如果应用对象可能会被同一进程的另一个线程同步调用,此变量值为真,否则为假。
      wsgi.multiprocess:如果同一个应用对象可能会被另一个进程同步调用,此变量值为真,否则为假。
      wsgi.run_once:如果服务端期望(但是不保证能得到满足)应用对象在生命周期中只被调用一次,此变量值为真,否则为假。一般只有在基于类似 CGI 的网关服务器中此变量才会为真。

    举例说明: 

    #! /usr/bin/python
    #coding=utf-8
    
    from webob import Request
    
    req = Request.blank('/article?id=1')
    
    print req
    print '\n\n'
    urll = ['%s : %s' % (key,value) for key,value in sorted(req.environ.items())]
    print '\n'.join(urll)

    程序执行结果:



    三、相关模块

    (1)、wsgiref :wsgiref是一个实现wsgi规范的模块,它提供了操纵WSGI环境变量和response头的工具,并且还实现了一个WSGI服务器。  具体可以参考(https://docs.python.org/2/library/wsgiref.html )。


    (2)、webob(WebOb):webob提供了包装后的WSGI请求(Request)环境,并辅助创建WSGI响应(Response)。具体可以参考(http://webob.org/ )。

       安装:apt-get install python-webob


    1)、Webob通过对WSGI的请求与响应进行封装来简化WSGI应用的编写

    Webob中两个最重要的对象,一是webob.Request,对WSGI请求的environ参数进行封装,一是webob.Response,包含了标准WSGI响应的所有要素  

    2)、原始的WSGI格式 
    app_iter = myfunc(environ, start_response)

    3)、使用webob封装之后
    def myfunc(req):  
            return webob.Response('hey there')  
    resp = myfunc(req)


    (3)、routes:是一个管理URL(路由)的模块。具体参考(https://routes.readthedocs.org/en/latest/ )。

       安装:apt-get install routes  (后面会详细介绍此模块)

    四、Request和Response

      Request对象是对HTTP请求的environ环境变量的封装操作,那么Response是什么作用呢?

    举例说明:

    from webob import Response
    
    res = Response(content_type = 'text/plain', charset = 'utf-8')
    print res
    
    print res.status     #响应状态
    print res.headerlist #响应的头部信息
    print res.body       #此时响应的body为空
    
    f = res.body_file    #响应的body,文件对象
    print f, type(f)   
    f.write('hello world')
    
    print res.body
    运行结果:



    五、加上装饰器,简化应用程序的书写

    一中:将APPTest这个APP实现一个__call__方法供服务器调用,在APP中将请求部分放入__call__处理,处理完之后调用test函数生成相应的响应。通过装饰器,可以进行改进:

    程序如下:

    from wsgiref.simple_server import make_server
    from webob import Request,Response
    from webob.dec import *
    
    class App(object):
    
        @wsgify
        def __call__(self, req):  #通过装饰器的修饰,通过req就是Request对象,可以得到HTTP请求的变量信息
            print req
            print req.environ
    
            return self.test(req)
    
        @wsgify
        def test(self, req):
            res = Response()
            res.status = 200
            res.body_file.write('hello world!')
    
            return res                      #res(environ, start_response)
    
    application = App()
    
    httpd = make_server('localhost', 8000, application)
    httpd.serve_forever()
    程序运行结果:



    这个例子是对最上面第一个例子的改进,使用@wsgify装饰器(可以将一个普通函数转变成WSGI应用程序),就不需要写那么多参数,这个语法糖为我们做了封装。(装饰器不熟悉的话请复习python基本的语法:python装饰器)。  用装饰器进行修饰之后,代表的req = Request(environ)实例。参数req是一个Request实例,可以通过req读取响应的环境变量。


    五、路由处理

    上小节基础上加入路由(URL)处理,使用routes模块来管理URL。

    from wsgiref.simple_server import make_server
    from webob import Request, Response
    from webob.dec import *
    from routes import Mapper, middleware
    
    class Test(object):
        def index(self):
            return "do index()\n"
        def add(self):
            return "do show()\n"
    
    class Router(object):
        def __init__(self):
            print '__init__'
            self.controller = Test()
            mapper = Mapper()
            mapper.connect('blog', '/blog/{action}/{id}', controller=Test,conditions={'method': ['GET']})
            mapper.redirect('/', '/blog/index/1')
            self.router = middleware.RoutesMiddleware(self.dispatch, mapper)
            
        @wsgify
        def dispatch(self, req):
            match = req.environ['wsgiorg.routing_args'][1]
            print req.environ['wsgiorg.routing_args']
            if not match:
                return 'error url: %s' % req.environ['PATH_INFO']
            action = match['action']
            if hasattr(self.controller, action):
    <span style="white-space:pre">	</span>    func = getattr(self.controller, action)
                ret = func()
                return ret
            else:
                return "has no action:%s" % action
    
        @wsgify
        def __call__(self,req):
            print '__call__'
            return self.router
    
    if __name__ == '__main__':
        httpd = make_server('127.0.0.1', 8000, Router())
        print "listening on 8000..."
        httpd.serve_forever()     

    routes模块是WSGI中典型的中间件模块。在Router的构造方法中,将路由对象mapper和dispatch方法放入中间件self.router,在实现__call__方法时

    返回中间件self.router。


    “dispatch”中的“getattr(self.controller, action)”使用了python的内省机制(类似设计模式中的反射),通过匹配map中的url规则自动匹配对

    应的类和方法。

    mapper.redirect('/', '/blog/index/1')路由重定向。。。

    代码执行结果:



    301,302 都是HTTP状态的编码,都代表着某个URL发生了转移,不同之处在于: 

    301 redirect: 301 代表永久性转移(Permanently Moved)。

    302 redirect: 302 代表暂时性转移(Temporarily Moved )。 

    from wsgiref.simple_server import make_server
    from webob import Request, Response
    from webob.dec import *
    from routes import Mapper, middleware
    
    class images(object):
        def index(self):
            return "images do index()\n"
        def create(self):
            return "images do create()\n"
        def detail(self):
            return "images do detail()\n"
    
    class servers(object):
        def index(self):
            return "servers do index()\n"
        def create(self):
            return "servers do create()\n"
        def detail(self):
            return "servers do detail()\n"
    
    class Router(object):
        def __init__(self):
            print '__init__'
            self.controller = None
            mapper = Mapper()
            mapper.connect('blog', '/:class_name/{action}/{id}',conditions={'method': ['GET']})                              
            mapper.redirect('/', '/servers/index/1')
            self.router = middleware.RoutesMiddleware(self.dispatch, mapper)
        
        @wsgify
        def dispatch(self, req):
            match = req.environ['wsgiorg.routing_args'][1]
            print req.environ['wsgiorg.routing_args']
            if not match:
                return 'error url: %s' % req.environ['PATH_INFO']
            class_name = match['class_name']
            self.controller = globals()[class_name]()
    
            action = match['action']
            if hasattr(self.controller, action):
                func = getattr(self.controller, action)
                ret = func()
                return ret
            else:
                return "has no action:%s" % action
    
        @wsgify
        def __call__(self,req):
            print '__call__'
            return self.router
    
    if __name__ == '__main__':
        httpd = make_server('127.0.0.1', 8000, Router())
        print "listening on 8000..."
        httpd.serve_forever()

    程序执行结果:




    自行理解程序!!!


    后面会介绍@wsgify装饰器和python装饰器。  全部是我国庆期间总结看了好多博客的干货啊,基础不好伤不起啊!!!


    参考博客:

    https://my.oschina.net/crooner/blog/609030


    展开全文
  • WSGI,uwsgi, uWSGI详解

    千次阅读 2019-06-03 09:24:38
    WSGI 全称:Web Server Gateway Interface 翻译过来就是Web服务器网关接口;WSGI是一个规范协议,定义了Web服务器如何与Python应用程序进行交互,使得使用Python写的Web应用程序可以和Web服务器对接起来。 在WSGI中...

    WSGI

    全称:Web Server Gateway Interface 翻译过来就是Web服务器网关接口;WSGI是一个规范协议,定义了Web服务器如何与Python应用程序进行交互,使得使用Python写的Web应用程序可以和Web服务器对接起来。

    在WSGI中定义了两个角色,Web服务器端称为server或者gateway,应用程序端称为application或者framework(因为WSGI的应用程序端的规范一般都是由具体的框架来实现的)。我们下面统一使用server和application这两个术语。

    server端会先收到用户的请求,然后会根据规范的要求调用application端,如下图所示:
    在这里插入图片描述
    调用的结果会被封装成HTTP响应后再发送给客户端。

    要使用WSGI,需要分别实现server角色和application角色。

    Application端的实现一般是由Python的各种框架来实现的,比如Django, web.py等,一般开发者不需要关心WSGI的实现,框架会会提供接口让开发者获取HTTP请求的内容以及发送HTTP响应;

    Server端的实现会比较复杂一点,这个主要是因为软件架构的原因。一般常用的Web服务器,如Apache和nginx,都不会内置WSGI的支持,而是通过扩展来完成。比如Apache服务器,会通过扩展模块mod_wsgi来支持WSGI。Apache和mod_wsgi之间通过程序内部接口传递信息,mod_wsgi会实现WSGI的server端、进程管理以及对application的调用。Nginx上一般是用proxy的方式,用nginx的协议将请求封装好,发送给应用服务器,比如uWSGI,应用服务器会实现WSGI的服务端、进程管理以及对application的调用。

    uwsgi

    它是一个二进制协议,可以携带任何类型的数据。一个uwsgi分组的头4个字节描述了这个分组包含的数据类型。
    uwsgi是一种线路协议而不是通信协议,在此常用于在uWSGI服务器与其他网络服务器的数据通信;

    uWSGI

    uWSGI是一种web服务器,是实现了uwsgi和WSGI两种协议的Web服务器


    WSGI,uwsgi, uWSGI: 实现过程图解

    在这里插入图片描述

    展开全文
  • 浅析WSGI

    千次阅读 2018-05-04 13:58:49
    关于Python的web ...Python Web Server Gateway Interface,翻译过来时Python web服务器网关接口,实际上就是一种协议,我们的应用(Django,Flask)实现了WSGI,就可以配合实现了WSGI(uWSGI,gunicorn)的服务...
  • wsgi的实质

    2018-12-16 20:29:53
    wsgi的实质 wsgi其实就是一个接口,作用就是连接服务器和框架的文件,必须按照一定的协议来写 为了理解其原理,先不管协议有什么规定,先来看看内在的处理问题实质是什么 不定导包 这个具体叫啥专业名字我也没有查,简单...
  • WSGI的理解(转载)

    万次阅读 2012-12-31 14:42:48
    wsgi是一个搞web开发的pythoner必须了解的内容,之前也零散的看过一些文章,但总感觉好多概念很模糊。这几天抽空又把相关内容好好整理了一下,把笔记贴出来,一些只言片语也许对某些正在研究这个的人有所帮助。 ...
  • WSGI协议介绍(萌新版)

    千次阅读 2019-09-19 10:31:22
    在探讨WSGI具体是什么之前,我们先考虑一个更加生活化的问题: 在邮局出现之前,人们为了联系出门在外的游子,往往依靠熟人捎信的方式来传递信息和物品。在一个偏远的山村里,老刘的儿子在上海卖药材,他要等到碰巧...
  • WSGI

    2019-07-13 16:14:02
    the Python Web Server Gateway Interface: 它规定了服务器怎么把请求信息告诉给应用,应用怎么把执行情况回传给服务器,这样的话,服务器与应用都按一个标准办事,只要实现...WSGI 是作为 Web 服务器与 Web 应用程...
  • WSGI 简介

    万次阅读 多人点赞 2014-02-19 11:09:27
    介绍了Python Web 开发中,服务器及框架实现中需要了解的标准:WSGI
  • 简单的WSGI例子

    千次阅读 2016-08-23 16:48:53
    原文转自廖雪峰官网:WSGI接口 了解了HTTP协议和HTML文档,我们其实就明白了一个Web应用的本质就是: 浏览器发送一个HTTP请求; 服务器收到请求,生成一个HTML文档; 服务器把HTML文档作为HTTP响应的Body发送给...
  • Apache mod_wsgi模块简介

    千次阅读 2017-02-27 22:48:51
    Apache HTTP服务器的mod_wsgi扩展模块,实现了Python WSGI标准,可以支持任何兼容Python WSGI标准的Python应用。 出于安全的考虑,建议使用mod_wsgi 3.5及以后的版本,最新版本是2017年1月发布的4.5.14。 1. WSGI...
  • django.core.exceptions.ImproperlyConfigured: WSGI application 'MySite.wsgi.application' could not be loaded; Error importing module: 'No module named 'app''    
  • WSGI服务与django的关系

    万次阅读 2018-04-10 23:30:54
    WSGI接口wsgi是将python服务器程序连接到web服务器的通用协议。uwsgi是独立的实现了wsgi协议的服务器。web服务器服务端程序简化版的WSGI架构服务端程序(类似django的角色)新建webapp.py# coding=utf-8 # 简化版的...
  • whl文件中找不到mod_wsgi.so文件解决办法

    万次阅读 热门讨论 2017-07-05 20:35:52
    mod_wsgi.so whl文件中找不到mod_wsgi.so mod_wsgi.so下载 mod_wsgi.so解决办法 python3.6 Django部署到apche中需要使用到mod_wsgi.so文件 Windows下python3.6 django 部署到Apache下 python3.6 mod_wsgi.so问题 ...
  • 最近在腾讯云上部署一个django Web应用,在使用uwsgi时候遇到一些问题 系统centos 7 64位 python 2.7 ...通过在wsgi.py里面打印sys.path发现是因为path里面并没有包含该安装的site-packages的路径,因
  • uwsgi、wsgi和nginx的区别和关系

    万次阅读 多人点赞 2018-10-15 11:00:50
    uWSGI、WSGI和Nginx 区分uWSGI和WSGI 在python web开发中,我们经常使用uwsgi配合nginx部署一个web框架,如Django或flask。同时我们又会说,框架和web服务器之间要符合WSGI协议。那就来厘清一下这几个概念。 web...
  • mod_wsgi的安装之路

    万次阅读 2016-12-10 15:28:23
    1、网上有一篇用visual ...另有直接下载mod_wsgi.so文件的办法,我选择这条路; 2、登录python三方库,http://www.lfd.uci.edu/~gohlke/pythonlibs/#mod_wsgi 看页面相关文字: Mod_wsgi, a WSGI adapter module for
  • File "/data1/wwwdocs/799/mdqp/1/index.wsgi", line 2, in from project0 import wsgi File "/data1/wwwdocs/799/mdqp/1/project0gi.py", line 27, in from django.core.wsgi import get_wsgi_application ...
  • 如何使用WSGI 部署Django 首要的部署平台是WSGI,它是Python Web 服务器和应用的标准。Django 的startproject 管理命名为你设置一个简单的默认WSGI 配置,你可以根据你项目的需要做调整并指定任何与WSGI 兼容的应用...
1 2 3 4 5 ... 20
收藏数 32,861
精华内容 13,144
关键字:

wsgi