博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
python之WSGI与Guincorn
阅读量:6832 次
发布时间:2019-06-26

本文共 7559 字,大约阅读时间需要 25 分钟。

WSGI与Guincorn

 

WSGI

WSGI (Web Server Gateway Interface),WSGI是为Python语言定义的Web服务器和Web应用程序之间的一种通用接口。

如下图,WSGI就像一座桥梁,一边连着web服务器,另一边连着应用程序。

 

 

 

 

 

 

wsgi server可以理解为一个符合wsgi规范的web server,它接收client发来的request,解析请求后封装到环境变量environ中,按照wsgi规范调用注册的wsgi app,最后将response返回给客户端。
 
一个WSGI接口包括如下三个组件:
  • Server,处理请求,提供环境信息、以及一个callback 给appication,并接收web响应作为返回值;
  • Middleware,连接server和application两方,可以重写环境信息,根据目标URL,将请求路由到不同的应用对象;
  • Application,一个callable对象,

 

 下面用一个例子来说明WSGI的工作模式:

from wsgiref.simple_server import make_serverdef simple_app(environ, start_response):    status = '200 OK'    response_headers = [('Content-type', 'text/plain')]    start_response(status, response_headers)    return [u"This is hello wsgi app".encode('utf8')]httpd = make_server('', 8000, simple_app)print "Serving on port 8000..."httpd.serve_forever()

例子中, simple_app函数是符合WSGI标准的一个HTTP处理函数,它有两个参数:

  1. environ,一个dict对象,包含请求的所有信息;
  2. start_response:callback函数,,包括HTTP响应码,HTTP响应头;

此外,simple_app函数的return语句返回的iterator对象作为HTTP响应body

 

实际上,wsgi app只要是一个callable对象就可以了,因此不一定要是函数,一个实现了__call__方法的实例也可以,示例代码如下:

from wsgiref.simple_server import make_serverclass AppClass:    def __call__(self, environ, start_response):        status = '200 OK'        response_headers = [('Content-type', 'text/plain')]        start_response(status, response_headers)        return ["hello world!"]app = AppClass()httpd = make_server('', 8000, app)print "Serving on port 8000..."httpd.serve_forever()

 

middleware

Middleware的概念没有appllication和server那么容易理解。

假设一个符合application标准的可调用对象,它接受可调用对象作为参数,返回一个可调用对象的对象。
那么对于server来说,它是一个符合标准的可调用对象,因此是application。
而对于application来说,它可以调用application,因此是server。
这样的可调用对象称为middleware,middleware的概念非常接近decorator。

 middleware的例子:

def exampleApplication(environ, start_response):      if environ['superSession'].has_key('visited'):          text = "You have already visited!"      else:          environ['superSession']['visited'] = 1          text = "This is your first visit."      start_response('200 OK', [('Content-type','text/plain')])      return [text]        def session(application):      def app(environ, start_response):          if "superSession" not in environ:              import superSession              environ["superSession"] = superSession.session()          return application(environ, start_response)      return app        application = session(exampleApplication)

如上面,session函数用于判断用户访问行为。session函数将判断结果至于环境变量environ字典中。

exampleApplication通过environ字典获得用户访问行为。
我们称session函数为middleware,它处于server与application之间,对server传来的请求做相应的处理;它对于Server和application是透明的。
middleware的好处在于,通过middleware(本例中session函数)可以很简单的给WSGI程序添加新功能。

 

我们也可见将middleware包装成类,这样,我们可以通过继承,复用现有的中间件。类中要重载__call__。

class Session:      def __init__(self, application):          self.application = application        def __call__(self, environ, start_response):          if "superSession" not in environ:              import superSession              environ["superSession"] = superSession.session() # Options would obviously need specifying          return self.application(environ,start_response)            application = Session(exampleApplication)

 

 

 

 


 

Gunicorn

Gunicorn(绿色独角兽)是一个被广泛使用的高性能的Python WSGI UNIX HTTP服务器,移植自Ruby的独角兽(Unicorn )项目,使用pre-fork worker模式,具有使用非常简单,轻量级的资源消耗,以及高性能等特点。

Gunicorn 服务器作为wsgi app的容器,能够与各种Web框架兼容(flask,django等),得益于gevent等技术,使用Gunicorn能够在基本不改变wsgi app代码的前提下,大幅度提高wsgi app的性能。

Gunicorn 使用例子:

$ cat myapp.py    def app(environ, start_response):        data = b"Hello, World!\n"        start_response("200 OK", [            ("Content-Type", "text/plain"),            ("Content-Length", str(len(data)))        ])        return iter([data])  $ gunicorn -w 4 myapp:app  [2014-09-10 10:22:28 +0000] [30869] [INFO] Listening at: http://127.0.0.1:8000 (30869)  [2014-09-10 10:22:28 +0000] [30869] [INFO] Using worker: sync  [2014-09-10 10:22:28 +0000] [30874] [INFO] Booting worker with pid: 30874  [2014-09-10 10:22:28 +0000] [30875] [INFO] Booting worker with pid: 30875  [2014-09-10 10:22:28 +0000] [30876] [INFO] Booting worker with pid: 30876  [2014-09-10 10:22:28 +0000] [30877] [INFO] Booting worker with pid: 30877

 

 

多进程模型

Gunicorn 有一个master进程,以及几个的worker进程,master通过pre-fork的方式创建多个worker,跟Nginx的有点像。

如下是master进程fork出worker进程的代码:

def spawn_worker(self):    self.worker_age += 1    #创建worker。请注意这里的app 对象并不是真正的wsgi app对象,而是gunicorn的app对象;    #gunicorn的app对象负责import我们自己写的wsgi app对象。    worker = self.worker_class(self.worker_age, self.pid, self.LISTENERS,                                self.app, self.timeout / 2.0,                                self.cfg, self.log)     pid = os.fork()    if pid != 0:  #父进程,返回后继续创建其他worker,没worker后进入到自己的消息循环        self.WORKERS[pid] = worker        return pid    # Process Child    worker_pid = os.getpid()    try:        ..........        worker.init_process() #子进程,初始化woker,进入worker的消息循环,        sys.exit(0)    except SystemExit:        raise    ............

在worker.init_process()函数中,worker中gunicorn的app对象会去import 我们的wsgi app。也就是说,每个woker子进程都会单独去实例化我们的wsgi app对象。每个worker中的swgi app对象是相互独立、互不干扰的。

 

manager维护数量固定的worker:

def manage_workers(self):        if len(self.WORKERS.keys()) < self.num_workers:            self.spawn_workers()        while len(workers) > self.num_workers:            (pid, _) = workers.pop(0)            self.kill_worker(pid, signal.SIGQUIT)

创建完所有的worker后,worker和master各自进入自己的消息循环。 

master的事件循环就是收收信号,管理管理worker进程,而worker进程的事件循环就是监听网络事件并处理(如新建连接,断开连接,处理请求发送响应等等),所以真正的连接最终是连到了worker进程上的。

 

 

worker

woker有很多种,包括:ggevent、geventlet、gtornado等等。这里主要分析ggevent。

每个ggevent worker启动的时候会启动多个server对象:worker首先为每个listener创建一个server对象(注:为什么是一组listener,因为gunicorn可以绑定一组地址,每个地址对于一个listener),每个server对象都有运行在一个单独的gevent pool对象中。真正等待链接和处理链接的操作是在server对象中进行的。

#为每个listener创建server对象。    for s in self.sockets:        pool = Pool(self.worker_connections) #创建gevent pool        if self.server_class is not None:           #创建server对象            server = self.server_class(                  s, application=self.wsgi, spawn=pool, log=self.log,                handler_class=self.wsgi_handler, **ssl_args)        .............        server.start() #启动server,开始等待链接,服务链接        servers.append(server)        .........

 上面代码中的server_class实际上是一个gevent的WSGI SERVER的子类:

class PyWSGIServer(pywsgi.WSGIServer):    base_env = BASE_WSGI_ENV

注意,server_class的参数中s是server用来监听链接的套接字。spawn是gevent的协程池。application即是我们的wsgi app(通俗点讲就是你用 flask 或者 django写成的app),我们的app就是通过这种方式交给gunicorn的woker去跑的。 handler_class是gevent的pywsgi.WSGIHandler子类。

 

 

WSGI Server

真正等待链接和处理链接的操作是在gevent的WSGIServer 和 WSGIHandler中进行的。 

最后再来看一下gevent的WSGIServer 和 WSGIHandler的主要实现:

WSGIServer 的start函数里面调用start_accepting来处理到来的链接。在start_accepting里面得到接收到的套接字后调用do_handle来处理套接字:

def do_handle(self, *args):    spawn = self._spawn    spawn(self._handle, *args)

 

可以看出,WSGIServer 实际上是创建一个协程去处理该套接字,也就是说在WSGIServer 中,一个协程单独负责一个HTTP链接。协程中运行的self._handle函数实际上是调用了WSGIHandler的handle函数来不断处理http 请求:

def handle(self):    try:        while self.socket is not None:            result = self.handle_one_request()#处理HTTP请求            if result is None:                break            if result is True:                continue            self.status, response_body = result            self.socket.sendall(response_body)#发送回应报文          ..............

 

在handle函数的循环内部,handle_one_request函数首先读取HTTP 请求,初始化WSGI环境,然后最终调用run_application函数来处理请求:

def run_application(self):    self.result = self.application(self.environ, self.start_response)    self.process_result()

在这个地方才真正的调用了我们的 app。

 

总结:gunicorn 会启动一组 worker进程,所有worker进程公用一组listener,在每个worker中为每个listener建立一个wsgi server。每当有HTTP链接到来时,wsgi server创建一个协程来处理该链接,协程处理该链接的时候,先初始化WSGI环境,然后调用用户提供的app对象去处理HTTP请求。

 

转载地址:http://wmjkl.baihongyu.com/

你可能感兴趣的文章
买低配车,更保值?!坊间有一句戏言:买车都要买低配
查看>>
数据迁移
查看>>
ubuntu14中创建python虚拟环境
查看>>
简单两步使用node发送qq邮件
查看>>
CSS
查看>>
区块链架构
查看>>
PHP Primary script unknown 终极解决方法
查看>>
3D文本悬停改变效果
查看>>
递归算法的时间复杂度
查看>>
有点不安全却又一亮的 Go unsafe.Pointer
查看>>
Linux安装mysql 8.0
查看>>
Webpack vs Rollup
查看>>
Springboot 前后端参数交互方式
查看>>
px、em、rem、%、vw、vh、vm等单位有什么区别?
查看>>
滴滴出行基于RocketMQ构建企业级消息队列服务的实践
查看>>
如何理解git rebase?
查看>>
程序部署到服务器服务无法启动问题
查看>>
以太坊源码分析—p2p节点发现与协议运行
查看>>
在MaxCompute上分析IP来源的方法
查看>>
JavaScript对象内部属性及其特性总结
查看>>