WSGI及gunicorn指北(一)

微信扫一扫,分享到朋友圈

WSGI及gunicorn指北(一)

作为一个Python Web 开发工程师,pyg0每天都喜滋滋的写着基于各种web框架的业务代码。

突然有一天,技术老大过来跟pyg0说,嘿,我们要新上线一个服务,你来帮我部署一下吧。不用太复杂。用gunicorn跑flask, 启8个进程, 用gevent模式跑就可以。这个很好配,给你一个小时吧。

这时候的pyg0心里充满的黑人问号, gunicorn, gevent ???什么鬼?

赶紧请教万能的google,发现网上配置的教程满天飞, pyg0找了一个看着顺眼的,照猫画虎,终于赶在下班前搞定。(说好的一个小时呢?)

但是真的搞定了吗?pyg0其实发现自己更加迷惑了。再配置的过程中,更多的名词出现在他的视野之中, wgsi?uwsgi?master? worker?

为了让自己不再迷糊下去。pyg0决定开始一场学习python web server的旅程

WSGI

WSGI(Web Server Gateway Interface),中文是网络服务器网关接口。它不是web server, 也不是web application,而是架在web server 和web application 之间一道协议和规范。

WSGI规范的目的就是解耦Web Server 和Web Application。 一个完整的WSGI协议包括server 和appliction 两部分。server的作用是接受客户端传来的请求,转发给application, 然后把application返回的response发给客户端。 application用来接受server发来的请求,处理请求,然后把response发回给server。这样一来,我们可以有多个实现了server协议的Web Server和多个实现了application的web framework。

重点来了,上文提到的gunicorn, uwsgi就是实现了WSGI server协议的web server。 而我们常用的Django,Flask等等就是实现了WSGI application协议的 web framework。这样一来,我们就可以像搭积木一样随意组合web server 和 web framework了。

其实python自己就带了一个实现了WSGI协议的server 和 application, 各个web framework也基本上都有自己实现的server, 但这些server基本上只能用来调试,不能用于生产环境。我们先来看python自己实现的WSGI协议: wsgiref。这个名字明确告诉我们,大哥我只是个reference, 你们看看就好,千万不要用啊,性能没保障的啊。

1 #coding:utf-8
2 from wsgiref.simple_server import make_server, demo_app
3 
4 app = demo_app
5 server = make_server("127.0.0.1", 9000, app)
6 server.serve_forever()

demo_app就是个简单的web application, 我们看看他都干了什么

def demo_app(environ,start_response):
    from io import StringIO
    stdout = StringIO()
    print("Hello world!", file=stdout)
    print(file=stdout)
    h = sorted(environ.items())
    for k,v in h:
        print(k,'=',repr(v), file=stdout)
    start_response("200 OK", [('Content-Type','text/plain; charset=utf-8')])
    return [stdout.getvalue().encode("utf-8")]

完美的满足的WSGI的标准,接受两个参数, environ和start_response, environ是一个包括了客户端所有请求信息和相关信息的dict, start_response 是一个可以发送响应状态和响应头的回调函数。除此之外, 我们的web application 把environ里面所有的信息都打印出来。

现在问题来了。我们应该如何调用这个web application呢?答案就是我们不用调,我们依靠web server 来调。让我们看看wsgiref里的webserver长什么样

class WSGIServer(HTTPServer):

    """BaseHTTPServer that implements the Python WSGI protocol"""

    application = None

    def server_bind(self):
        """Override server_bind to store the server name."""
        HTTPServer.server_bind(self)
        self.setup_environ()

    def setup_environ(self):
        # Set up base environment
        env = self.base_environ = {}
        env['SERVER_NAME'] = self.server_name
        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
        env['SERVER_PORT'] = str(self.server_port)
        env['REMOTE_HOST']=''
        env['CONTENT_LENGTH']=''
        env['SCRIPT_NAME'] = ''

    def get_app(self):
        return self.application

    def set_app(self,application):
        self.application = application

这个web server 也同样很简单。继承了HTTPServer, 重写了server_bind方法,在绑定的同时创建了必要的环境变量。同时提供了绑定和获取web application的方法。

我们再来看看WSGIRequestHandler

class WSGIRequestHandler(BaseHTTPRequestHandler):

    server_version = "WSGIServer/" + __version__

    def get_environ(self):
        env = self.server.base_environ.copy()
        env['SERVER_PROTOCOL'] = self.request_version
        env['SERVER_SOFTWARE'] = self.server_version
        env['REQUEST_METHOD'] = self.command
        if '?' in self.path:
            path,query = self.path.split('?',1)
        else:
            path,query = self.path,''

        env['PATH_INFO'] = urllib.parse.unquote(path, 'iso-8859-1')
        env['QUERY_STRING'] = query

        host = self.address_string()
        if host != self.client_address[0]:
            env['REMOTE_HOST'] = host
        env['REMOTE_ADDR'] = self.client_address[0]

        if self.headers.get('content-type') is None:
            env['CONTENT_TYPE'] = self.headers.get_content_type()
        else:
            env['CONTENT_TYPE'] = self.headers['content-type']

        length = self.headers.get('content-length')
        if length:
            env['CONTENT_LENGTH'] = length

        for k, v in self.headers.items():
            k=k.replace('-','_').upper(); v=v.strip()
            if k in env:
                continue                    # skip content length, type,etc.
            if 'HTTP_'+k in env:
                env['HTTP_'+k] += ','+v     # comma-separate multiple headers
            else:
                env['HTTP_'+k] = v
        return env

    def get_stderr(self):
        return sys.stderr

    def handle(self):
        """Handle a single HTTP request"""

        self.raw_requestline = self.rfile.readline(65537)
        if len(self.raw_requestline) > 65536:
            self.requestline = ''
            self.request_version = ''
            self.command = ''
            self.send_error(414)
            return

        if not self.parse_request(): # An error code has been sent, just exit
            return

        handler = ServerHandler(
            self.rfile, self.wfile, self.get_stderr(), self.get_environ()
        )
        handler.request_handler = self      # backpointer for logging
        handler.run(self.server.get_app())

我们的WSGIRequestHandler会添加更多请求相关的信息放进environ里。同时重写了handle方法,在这里我们看到了熟悉的get_app(), 没错,就是在这里, 我们的web_application出现了。它会接受server 的 environ和回调函数start_response, 处理完成之后,再通过start_response 把HTTP Code 和HEADER发送给handler,通过return把http response返回给handler。

通过这个简单的示例,pyg0终于明白了WSGI是怎么回事儿。但是他也认同这个wsgiref实在是太low了。所以赶紧开始钻研老大推荐的gunicorn。

我们下回再见!

微信扫一扫,分享到朋友圈

WSGI及gunicorn指北(一)

一周金融人物权力榜|百度金融终拆分,港交所迎来首位女主席

上一篇

联想智慧零售布局加速 深圳成都两地连开智生活店

下一篇

你也可能喜欢

WSGI及gunicorn指北(一)

长按储存图像,分享给朋友