专栏名称: Python开发者
人生苦短,我用 Python。伯乐在线旗下账号「Python开发者」分享 Python 相关的技术文章、工具资源、精选课程、热点资讯等。
目录
相关文章推荐
Python爱好者社区  ·  刚刚!微信 DeepSeek ... ·  昨天  
Python爱好者社区  ·  模仿一下领导说话的样子 ·  15 小时前  
python自留地  ·  Python语法基本单词 ·  昨天  
Python开发者  ·  DeepSeek+软考,杀疯了?! ·  3 天前  
Python爱好者社区  ·  机器学习全书 ·  3 天前  
51好读  ›  专栏  ›  Python开发者

深入理解 nova-api 的 WSGI

Python开发者  · 公众号  · Python  · 2017-03-20 19:51

正文

(点击 上方蓝字 ,快速关注我们)


来源: koala bear

wsfdl.com/openstack/2013/10/18/理解nova-api的WSGI框架.html

如有好文章投稿,请点击 → 这里了解详情


本文是 理解 WSGI 框架 的下篇,重点介绍 WSGI 框架下一些常用的 python module,并使用这些 module 编写一个类似 nova-api 里 WSGI 的简单样例,最后分析 nova 是如何使用这些 module 构建其 WSGI 框架。


  • eventlet: python 的高并发网络库

  • paste.deploy: 用于发现和配置 WSGI application 和 server 的库

  • routes: 处理 http url mapping 的库


Eventlet


Eventlet 是一个基于协程的 Python 高并发网络库,和上篇文章所用的 wsgiref 相比,它具有更强大的功能和更好的性能,OpenStack 大量的使用 eventlet 以提供并发能力。它具有以下特点:


  • 使用 epoll、kqueue 或 libevent 等 I/O 复用机制,对于非阻塞 I/O 具有良好的性能

  • 基于协程(Coroutines),和进程、线程相比,其切换开销更小,具有更高的性能

  • 简单易用,特别是支持采用同步的方式编写异步的代码


Eventlet.wsgi


Eventlet WSGI 简单易用,数行代码即可实现一个基于事件驱动的 WSGI server。本例主要使用了 eventlet.wsgi.server 函数:


eventlet . wsgi . server ( sock , site , log = None , environ = None ,

max_size = None , max_http_version = 'HTTP/1.1' ,

protocol = eventlet . wsgi . HttpProtocol , server_event = None ,

minimum_chunk_size = None , log_x_forwarded_for = True ,

custom_pool = None , keepalive = True ,

log_output = True , log_format = '%(client_ip)s...' ,

url_length_limit = 8192 , debug = True ,

socket_timeout = None , capitalize_response_headers = True )


该函数的参数众多,重点介绍以下 2 个参数:


  • sock: 即 TCP Socket,通常由 eventlet.listen(‘IP’, PORT) 实现

  • site: WSGI 的 application


回顾上篇文章内容,本例采用 callable 的 instance 实现一个 WSGI application,利用 eventlet.server 构建 WSGI server,如下:


import eventlet

from eventlet import wsgi

class AnimalApplication ( object ) :

def __init__ ( self ) :

pass

def __call__ ( self , environ , start_response ) :

start_response ( '200 OK' , [( 'Content-Type' , 'text/plain' )])

return [ 'This is a animal applicaltion!rn' ]

if '__main__' == __name__ :

application = AnimalApplication ()

wsgi . server ( eventlet . listen (( '' , 8080 )), application )


Eventlet.spawn


Eventlet.spawn 基于 greenthread,它通过创建一个协程来执行函数,从而提供并发处理能力。


eventlet . spawn ( func , * args , ** kw )

加入该函数后,样例如下:


import eventlet

from eventlet import wsgi

class AnimalApplication ( object ) :

def __init__ ( self ) :

pass

def __call__ ( self , environ , start_response ) :

start_response ( '200 OK' , [( 'Content-Type' , 'text/plain' )])

return [ 'This is a animal applicaltion!rn' ]

if '__main__' == __name__ :

application = AnimalApplication ()

server = eventlet . spawn ( wsgi . server ,

eventlet . listen (( '' , 8080 )), application )

server . wait ()


Paste.deploy


Paste.deploy 是一个用户发现和配置 WSGI server 和 application 的 python 库,它定义简洁的 loadapp 函数,用于从配置文件或者 python egg 中加载 WSGI 应用,它仅关注 application 的入口,不关心 application 的内部细节。


Paste.deploy 通常要求 application 实现一个 factory 的类方法,如下:


import eventlet

from eventlet import wsgi

from paste . deploy import loadapp

class AnimalApplication ( object ) :

def __init__ ( self ) :

pass

def __call__ ( self , environ , start_response ) :

start_response ( '200 OK' , [( 'Content-Type' , 'text/plain' )])

return [ 'This is a animal applicaltion!rn' ]

@ classmethod

def factory ( cls , global_conf , ** kwargs ) :

return cls ()

if '__main__' == __name__ :

application = loadapp ( 'config:/path/to/animal.ini' )

server = eventlet . spawn ( wsgi . server ,

eventlet . listen (( '' , 8080 )), application )

server . wait ()


配置文件的规则请参考官网介绍,相应的配置文件如下,其中 app:animal 给出了 application 的入口,pipeline:animal_pipeline 用于配置 WSGI middleware。


[ composite : main ]

use = egg : Paste #urlmap

/ = animal _ pipeline

[ pipeline : animal_pipeline ]

pipeline = animal

[ app : animal ]

paste . app_factory = animal : AnimalApplication . factory


现在我们新增一个 IPBlackMiddleware,用于限制某些 IP:


class IPBlacklistMiddleware ( object ) :

def __init__ ( self , application ) :

self . application = application

def __call__ ( self , environ , start_response ) :

ip_addr = environ . get ( 'HTTP_HOST' ). split ( ':' )[ 0 ]

if ip_addr not in ( '127.0.0.1' ) :

start_response ( '403 Forbidden' , [( 'Content-Type' , 'text/plain' )])

return [ 'Forbidden' ]

return self . application ( environ , start_response )

@ classmethod

def factory ( cls , global_conf , ** local_conf ) :

def _factory ( application ) :

return cls ( application )

return _factory


相关配置文件:


[ composite : main ]

use = egg : Paste #urlmap

/ = animal _ pipeline

[ pipeline : animal_pipeline ]

pipeline = ip_blacklist animal

[ filter : ip_blacklist ]

paste . filter_factory = animal : IPBlacklistMiddleware . factory

[ app : animal ]

paste . app_factory = animal : AnimalApplication . factory


Route


Routes 是基于 ruby on rails 的 routes system 开发的 python 库,它根据 http url 把请求映射到具体的方法,routes 简单易用,可方便的构建 Restful 风格的 url。


本例增加 CatController 和 DogController,对于 url_path 为 cats 的 HTTP 请求,映射到 CatController 处理,对于 url_path 为 dogs 的 HTTP 请求,映射到 DogController 处理,最终样例如下:



测试如下:


$ curl 127.0.0.1 : 8080 / test

The resource could not be found .

$ curl 127.0.0.1 : 8080 / cats

List cats .

$ curl - X POST 127.0.0.1 : 8080 / cats

create cat .

$ curl - X PUT 127.0.0.1 : 8080 / cats / kitty

update cat .

$ curl - X DELETE 127.0.0.1 : 8080 / cats / kitty

delete cat .

$ curl 127.0.0.1 : 8080 / dogs

List dogs .

$ curl - X DELETE 127.0.0.1 : 8080 / dogs / wangcai

delete dog .


WSGI In Nova-api


WSGI Server


Nova-api(nova/cmd/api.py) 服务启动时,初始化 nova/wsgi.py 中的类 Server,建立了 socket 监听 IP 和端口,再由 eventlet.spawn 和 eventlet.wsgi.server 创建 WSGI server:


class Server ( object ) :

"""Server class to manage a WSGI server, serving a WSGI application."""

def __init__ ( self , name , app , host = '0.0.0.0' , port = 0 , pool_size = None ,

protocol = eventlet . wsgi . HttpProtocol , backlog = 128 ,

use_ssl = False , max_url_len = None ) :

"""Initialize, but do not start, a WSGI server."""

self . name = name

self . app = app

self . _server = None

self . _protocol = protocol

self . _pool = eventlet . GreenPool ( pool_size or self . default_pool_size )

self . _logger = logging . getLogger ( "nova.%s.wsgi.server" % self . name )

self . _wsgi_logger = logging . WritableLogger ( self . _logger )

if backlog < 1 :

raise exception . InvalidInput (

reason = 'The backlog must be more than 1' )

bind_addr = ( host , port )

# 建立 socket,监听 IP 和端口

self . _socket = eventlet . listen ( bind_addr , family , backlog = backlog )

def start ( self ) :

"""Start serving a WSGI application.

:returns: None

"""

# 构建所需参数

wsgi_kwargs = {

'func' : eventlet . wsgi . server ,

'sock' : self . _socket ,

'site' : self . app ,

'protocol' : self . _protocol ,

'custom_pool' : self . _pool ,

'log' : self . _wsgi_logger ,

'log_format' : CONF . wsgi_log _ format

}

if self . _max_url_len :

wsgi_kwargs [ 'url_length_limit' ] = self . _max_url_len

# 由 eventlet.sawn 启动 server

self . _server = eventlet . spawn ( ** wsgi_kwargs )


Application Side & Middleware


Application 的加载由 nova/wsgi.py 的类 Loader 完成,Loader 的 load_app 方法调用了 paste.deploy.loadapp 加载了 WSGI 的配置文件 /etc/nova/api-paste.ini:


class Loader ( object ) :

"""Used to load WSGI applications from paste configurations."""







请到「今天看啥」查看全文