zoukankan      html  css  js  c++  java
  • WSGI——python-Web框架基础

    1. 简介

    WSGI

    WSGI:web服务器网关接口,这是python中定义的一个网关协议,规定了Web Server如何跟应用程序交互。可以理解为一个web应用的容器,通过它可以启动应用,进而提供HTTP服务。

    ​ 它最主要的目的是保证在Python中所有的Web Server程序或者说Gateway程序,能够通过统一的协议跟Web框架或者说Web应用进行交互。

    uWSGI

    uWGSI:是一个web服务器,或者wsgi server服务器,他的任务就是接受用户请求,由于用户请求是通过网络发过来的,其中用户到服务器端之间用的是http协议,所以我们uWSGI要想接受并且正确解出相关信息,我们就需要uWSGI实现http协议,没错,uWSGI里面就实现了http协议。所以现在我们uWSGI能准确接受到用户请求,并且读出信息。现在我们的uWSGI服务器需要把信息发给Django,我们就需要用到WSGI协议,刚好uWSGI实现了WSGI协议,所以。uWSGI把接收到的信息作一次简单封装传递给Django,Django接收到信息后,再经过一层层的中间件,于是,对信息作进一步处理,最后匹配url,传递给相应的视图函数,视图函数做逻辑处理......后面的就不叙述了,然后将处理后的数据通过中间件一层层返回,到达Djagno最外层,然后,通过WSGI协议将返回数据返回给uWSGI服务器,uWSGI服务器通过http协议将数据传递给用户。这就是整个流程。

    ​ 这个过程中我们似乎没有用到uwsgi协议,但是他也是uWSGI实现的一种协议,鲁迅说过,存在即合理,所以说,他肯定在某个地方用到了。我们过一会再来讨论

    ​ 我们可以用这条命令:python manage.py runserver,启动Django自带的服务器。DJango自带的服务器(runserver 起来的 HTTPServer 就是 Python 自带的 simple_server)。是默认是单进程单多线程的,对于同一个http请求,总是先执行一个,其他等待,一个一个串行执行。无法并行。而且django自带的web服务器性能也不好,只能在开发过程中使用。于是我们就用uWSGI代替了。

    为什么有了WSGI为什么还需要nginx?

    ​ 因为nginx具备优秀的静态内容处理能力,然后将动态内容转发给uWSGI服务器,这样可以达到很好的客户端响应。支持的并发量更高,方便管理多进程,发挥多核的优势,提升性能。这时候nginx和uWSGI之间的沟通就要用到uwsgi协议。

    2. 简单的Web Server

    在了解WSGI协议之前,首先看一个通过socker编程实现的Web服务的代码。

    import socket
    EOL1 = b'
    
    '
    EOL2 = b'
    
    '
    body = """hello world <h1> from test</h1>"""
    response_params = [
        'HTTP/1.0 200 OK',
        'DATE: Sun, 27 may 2019 01:01:01 GMT',
        'Content-Type:text/plain; charset=utf-8',
        'Content-Length: {}
    '.format(len(body.encode())),
        body,
    ]
    response = '
    '.join(response_params)
    
    
    def handle_connection(conn, addr):
        request = b""
        print('new conn', conn, addr)
        import time
        time.sleep(100)
        while EOL1 not in request and EOL2 not in request:
            request += conn.recv(1024)
        print(request)
        conn.send(response.encode())
        conn.close()
    
    
    def main():
        serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        serversocket.bind(('127.0.0.1', 8000))
        serversocket.listen(5)
        print('http://127.0.0.1:8000')
        try:
            while True:
                conn, address = serversocket.accept()
                handle_connection(conn, address)
        finally:
            serversocket.close()
    
    
    if __name__ == '__main__':
        main()
    

    多线程

    import errno
    import socket
    import threading
    import time
    EOL1 = b'
    
    '
    EOL2 = b'
    
    '
    body = """hello world <h1> from test</h1>"""
    response_params = [
        'HTTP/1.0 200 OK',
        'DATE: Sun, 27 may 2019 01:01:01 GMT',
        'Content-Type:text/plain; charset=utf-8',
        'Content-Length: {}
    '.format(len(body.encode())),
        body,
    ]
    response = '
    '.join(response_params)
    
    def handle_connection(conn, addr):
        print(conn, addr)
        time.sleep(60)
        request = b""
        while EOL1 not in request and EOL2 not in request:
            request += conn.recv(1024)
        print(request)
        current_thread = threading.currentThread()
        content_length = len(body.format(thread_name=current_thread.name).encode())
        print(current_thread.name)
        conn.send(response.format(thread_name=current_thread.name, length=content_length).encode())
        conn.close()
    
    def main():
        serversocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        serversocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        serversocket.bind(('127.0.0.1', 8000))
        serversocket.listen(10)
        print('http://127.0.0.1:8000')
        serversocket.setblocking(True) # 设置socket为阻塞模式
        try:
            i = 0
            while True:
                try:
                    conn, address = serversocket.accept()
                except socket.error as e:
                    if e.args[0] != errno.EAGAIN:
                        raise
                    continue
                i += 1
                print(i)
                t = threading.Thread(target=handle_connection, args=(conn, address), name='thread-%s'%i)
                t.start()
        finally:
            serversocket.close()
    
    if __name__ == '__main__':
    
        main()
    

    3. 简单的WSGI Application

    该协议分为两个部分:

    • Web Server 或者Gateway

      监听在某个端口上接收外部的请求

    • Web Application

    ​ Web Server接收请求之后,会通过WSGI协议规定的方式把数据传递给Web Application,在Web Application中处理完之后,设置对应的状态和header,之后返回body部分。Web Server拿到返回的数据之后,再进行HTTP协议的封装,最终返回完整的HTTPResponse数据。

    下面我们来实现一个简单的应用:

    app.py
    
    def simple_app(environ, start_response):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        start_response(status, response_headers)
        return [b'Hello world! -by test 
    ']
    

    我们需要一个脚本运行上面这个应用:

    import os
    import sys
    
    from app import simple_app
    
    
    def wsgi_to_bytes(s):
        return s.encode()
    
    
    def run_with_cgi(application):
        environ = dict(os.environ.items())
        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:
                # 在输出第一行数据之前,先发送响应头
                status, response_headers = headers_sent[:] = headers_set
                out.write(wsgi_to_bytes('Status: %s
    ' % status))
                for header in response_headers:
                    out.write(wsgi_to_bytes('%s: %s
    ' % header))
                out.write(wsgi_to_bytes('
    '))
            out.write(data)
            out.flush()
    
        def start_response(status, response_headers, exc_info=None):
            if exc_info:
                try:
                    if headers_sent:
                        # 如果已经发送了header,则重新抛出原始异常信息
                        raise (exc_info[0], exc_info[1], exc_info[2])
                finally:
                    exc_info = None
            elif headers_set:
                raise AssertionError('*Headers already set!')
            headers_set[:] = [status, response_headers]
            return write
    
        result = application(environ, start_response)
        try:
            for data in result:
                if data:
                    write(data)
            if not headers_sent:
                write('')
        finally:
            if hasattr(result, 'close'):
                result.close()
    
    
    if __name__ == '__main__':
        run_with_cgi(simple_app)
    

    运行结果:

    Status: 200 OK
    Content-type: text/plain
    
    Hello world! -by test 
    

    如果不是windows系统,还可以采用另一种方式运行:

    • pip install gunicorn
    • gunicorn app:simle_app

    4. 理解

    ​ 对于上述代码我们只需要关注一点,result = application(environ, start_response),我们要实现的Application,只需要能够接收一个环境变量以及一个回调函数即可。但处理完请求之后,通过回调函数(start_response)来设置response的状态和header,最终返回结果,也就是body。

    WSGI协议规定,application必须是一个可调用对象,这意味这个对象既可以是Python中的一个函数,也可以是一个实现了__call__方法的类的实例,比如:

    样例一

    class AppClass(object):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
    
        def __call__(self, environ, start_response):
            print(environ, start_response)
            start_response(self.status, self.response_headers)
            return [b'Hello AppClass.__call__
    ']
    
    
    application = AppClass()
    

    gunicorn app: application运行上述文件

    样例二

    ​ 除此之外,我们还可以通过另一种方式实现WSGI协议,从上面的simple_app和这里的AppClass.__call__的返回值来看,WSGI Server只需要返回一个可迭代的对象就行

    class AppClassIter(object):
        status = '200 OK'
        response_headers = [('Content-type', 'text/plain')]
        
        def __init__(self, environ, start_response):
            self.environ = environ
            self.start_response = start_response
            
        def __iter__(self):
            self.start_response(self.status, self.response_headers)
            yield b'Hello AppClassIter
    '
    

    gunicorn app: AppClassIter运行上述文件

    ​ 这里的启动命令并不是一个类的实例,而是类本身。通过上面两个代码,我们可以看到能够被调用的方法会传environ和start_response过来,而现在这个实现没有可调用的方式,所以就需要在实例化的时候通过参数传递进来,这样在返回body之前,可以先调用start_response方法。

    ​ 因此,可以推测出WSGI Server是如何调用WSGI Application的,大概代码如下:

    def start_response(status, headers):
        # 伪代码
        set_status(status)
        for k, v in headers:
            set_header(k, v)
    def handle_conn(conn):
        # 调用我们定义的application(也就是上面的simple_app, 或者是AppClass的实例,或者是AppClassIter本身)
        app = application(environ, start_response)
        # 遍历返回的结果,生成response
        for data in app:
            response += data
            
        conn.sendall(response)
    

    5. WSGI中间件和Werkzeug

    ​ WSGI中间件可以理解为Python中的一个装饰器,可以在不改变原方法的情况下对方法的输入和输出部分进行处理。

    类似这样:

    def simple_app(enbiron, start_response):
    	response = Response('Hello World', start_response=start_response)
    	response.set_header('Content-Type', 'text/plain') # 这个函数里面调用start_response
    	return response
    

    这样就看起来更加自然一点。

    ​ 因此,就存在Werkzeug这样的WSGI工具集,让你能够跟WSGI协议更加友好的交互。从理论上来看,我们可以直接通过WSGI协议的简单实现写一个Web服务。但是有了Werkzeug之后,我们可以写的更加容易。

    6. 杂谈

    django 的并发能力真的是令人担忧,这里就使用 nginx + uwsgi 提供高并发

    nginx 的并发能力超高,单台并发能力过万(这个也不是绝对),在纯静态的 web 服务中更是突出其优越的地方,由于其底层使用 epoll 异步IO模型进行处理,使其深受欢迎

    做过运维的应该都知道,

    Python需要使用nginx + uWSGI 提供静态页面访问,和高并发

    php 需要使用 nginx + fastcgi 提供高并发,

    java 需要使用 nginx + tomcat 提供 web 服务

    django 原生为单线程序,当第一个请求没有完成时,第二个请求辉阻塞,知道第一个请求完成,第二个请求才会执行。
    Django就没有用异步,通过线程来实现并发,这也是WSGI普遍的做法,跟tornado不是一个概念
    
    官方文档解释django自带的server默认是多线程
    django开两个接口,第一个接口sleep(20),另一个接口不做延时处理(大概耗时几毫秒)
    先请求第一个接口,紧接着请求第二个接口,第二个接口返回数据,第一个接口20秒之后返回数据
    证明django的server是默认多线程
    
    启动uWSGI服务器
    # 在django项目目录下 Demo工程名
    uwsgi --http 0.0.0.0:8000 --file Demo/wsgi.py
    经过上述的步骤测试,发现在这种情况下启动django项目,uWSGI也是单线程,访问接口需要"排队"
    不给uWSGI加进程,uWSGI默认是单进程单线程
    
    uwsgi --http 0.0.0.0:8000 --file Demo/wsgi.py --processes 4 --threads 2
    # processes: 进程数 # processes 和 workers 一样的效果 # threads : 每个进程开的线程数
    经过测试,接口可以"同时"访问,uWSGI提供多线程
    
    • Python因为GIL的存在,在一个进程中,只允许一个线程工作,导致单进程多线程无法利用多核
    • 多进程的线程之间不存在抢GIL的情况,每个进程有一个自己的线程锁,多进程多GIL
  • 相关阅读:
    SilkTest Q&A 5
    SilkTest Q&A 6
    产生n不同随机数的算法
    根据日期计算星期小算法
    Linux $( )与${ }的区别
    UVA 10313(完全背包变形)
    HDU 4277 USACO ORZ
    Android Studio上手,基于VideoView的本地文件及流媒体播放器
    Mina框架项目运用
    【iOS】怎样推断文本文件的字符编码格式
  • 原文地址:https://www.cnblogs.com/ZSMblog/p/11677225.html
Copyright © 2011-2022 走看看