WSGI框架及Paste+Pastedeploy+route+webob開發

一。前言html

WSGI服務器

一個Web服務器網關接口 (WSGI)服務器實現了WSGI接口的Web服務器端運行的Python的Web應用程序。python

爲何須要WSGI?

傳統的Web服務器不理解或有任何方式來運行Python應用程序。在20世紀90年代後期,名爲Grisha Trubetskoy的開發人員 提出了一個名爲mod_python的Apache模塊 來執行任意的Python代碼。在20世紀90年代後期和21世紀初,Apache配置的mod_python運行了大多數Python Web應用程序。nginx

可是,mod_python不是標準規範。這只是一個容許Python代碼在服務器上運行的實現。因爲mod_python的開發陷入僵局,安全漏洞被發現,社區認識到,須要一致的方式來執行Web應用程序的Python代碼。web

所以,Python社區提出了WSGI做爲模塊和容器能夠實現的標準接口。WSGI如今是運行Python Web應用程序的被接受的方法。sql

WSGI官方規範

WSGI標準v1.0在PEP 0333中指定 截至2010年9月,WSGI v1.0被PEP 3333取代 ,它定義了v1.0.1 WSGI標準。若是您使用Python 2.x,而且您符合PEP 0333,那麼您也符合3333.較新版本僅僅是Python 3的更新,而且有關於如何處理unicode的說明。apache

在的wsgiref的Python 2.x中和 的wsgiref在Python 3.x的 是內置到Python的標準庫WSGI規範的參考實現,所以它能夠被用來建造WSGI服務器和應用程序安全

 

 

PEP 0333 – Python Web Server Gateway Interface 是一種 web server or gateway 和 python web application or framework 之間簡單通用的接口,符合這種接口的 application 可運行在全部符合該接口的 server 上。通俗的講,WSGI 規範了一種簡單的接口,解耦了 server 和 application,使得雙邊的開發者更加專一自身特性的開發。ruby

  • Web server/gateway: 即 HTTP Server,處理 HTTP 協議,接受用戶 HTTP 請求和提供併發,調用 web application 處理業務邏輯。一般採用 C/C++ 編寫,表明:apache, nginx 和 IIS。
  • Python Web application/framework: 專一業務邏輯的 python 應用或者框架

Python Web application/framework

Application/framework 端必須定義一個 callable object,callable object 能夠是如下三者之一:服務器

    • function, method
    • class
    • instance with a __call__ method

Callable object 必須知足如下兩個條件:網絡

    • 接受兩個參數:字典(environ),回調函數(start_response,返回 HTTP status,headers 給 web server)
    • 返回一個可迭代的值

示例:

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['HELLO WORLD!']
class ApplicationClass(object):
    def __init__(self, environ, start_response):
        self.environ = environ
        self.start_response = start_response
 
    def __iter__(self):
        self.start_response('200 OK', [('Content-type', 'text/plain')])
        yield "Hello world!n"
  1. environ 和 start_response 由 http server 提供並實現
  2. environ 變量是包含了環境信息的字典
  3. Application 內部在返回前調用 start_response
  4. start_response也是一個 callable,接受兩個必須的參數,status(HTTP狀態)和 response_headers(響應消息的頭)
  5. 可調用對象要返回一個值,這個值是可迭代的

服務器接口端

服務器端主要專一 HTTP 層面的業務,重點是接收 HTTP 請求和提供併發。

每當收到 HTTP 請求,服務器接口端必須調用 callable object:

  • 接收 HTTP 請求,可是不關心 HTTP url, HTTP method 等
  • 爲 environ 提供必要的參數,實現一個回調函數 start_response,並傳給 callable object
  • 調用 callable object

Middleware,中間件

  Middleware 處於 服務層和 應用接口層 之間,每一個 middleware 實現不一樣的功能,咱們一般根據需求選擇相應的 middleware 並組合起來,實現所需的功能。其做用有如下幾點:

  • 根據 url 把用戶請求調度到不一樣的 application 中。
  • 負載均衡,轉發用戶請求
  • 預處理 XSL 等相關數據
  • 限制請求速率,設置白名單
class IPBlacklistMiddleware(object):
    def __init__(self, app):
        self.app = app

    def __call__(self, environ, start_response):
        ip_addr = environ.get('HTTP_HOST').split(':')[0]
        if ip_addr not in ('127.0.0.1'):
            return forbidden(start_response)

        return self.app(environ, start_response)

def forbidden(start_response):
    start_response('403 Forbidden', [('Content-Type', 'text/plain')])
    return ['Forbidden']

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['Hello World!']

if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    application = IPBlacklistMiddleware(application)
    server = make_server('0.0.0.0', 8080, application)
    server.serve_forever()

 大體瞭解WSGI框架後咱們來看下Paste+Pastedeploy+route+webob開發,openstack開發使用的就是此開發框架,主要使用到的一些模塊是:

  • eventlet: python 的高併發網絡庫
  • paste.deploy: 用於發現和配置 WSGI application 和 server 的庫
  • routes: 處理 http url mapping 的庫
  • webob: 處理HTTP請求並提供了一個對象來方便的處理返回response消息

Eventlet 

Eventlet 是一個基於協程的 Python 高併發網絡庫,額如下特色:

  • 使用 epoll、kqueue 或 libevent 等 I/O 複用機制,對於非阻塞 I/O 具備良好的性能
  • 基於協程(Coroutines),和進程、線程相比,其切換開銷更小,具備更高的性能
  • 簡單易用,特別是支持採用同步的方式編寫異步的代碼

paste.deploy

Paste Deployment是用於查找和配置WSGI應用程序和服務器的系統。對於WSGI應用程序消費者,它提供了從配置文件或Python Egg加載WSGI應用程序的單一簡單函數(loadapp)。對於WSGI應用程序提供商,它只須要一個簡單的入口點到您的應用程序,以便應用程序用戶不須要暴露於應用程序的實現細節。

Paste.deploy 一般要求 application 實現一個 factory 的類方法,以下:

class TestApplication(object):
    def __init__(self):
        pass

    @webob.dec.wsgify
    def __call__(self, req):
        return self.router

    @classmethod
    def factory(cls, global_conf, **local_conf):
        return cls()

if '__main__' == __name__: application = loadapp('config:%s/config.ini' % (CONF)) server = eventlet.spawn(wsgi.server, eventlet.listen(('0.0.0.0', 8080)), application) server.wait()

 Paste.deploy 的.ini配置文件配置 講解

配置文件說明,一個配置文件有不一樣的分段,但pastedeploy 關心的是前綴,好比app:main or filter:errors,:分隔部分以後是是這個分段的name,前一部分是這個分段的type類型,前段給的類型,後段名字將被忽略。

一個簡單的INI配置文件格式是 name = value.能夠經過縮進後續行來擴展配置,#是對前面配置的評論標註。

一般,您有一個或兩個部分,稱爲「main」:一個應用程序部分(app:main)和一個服務器部分(server:main)。(複合:…表示向多個應用程序發送的內容(如如下示例)。

[composite:main]
use = egg:Paste#urlmap
/ = home
/blog = blog
/wiki = wiki
/cms = config:cms.ini

[app:home]
use = egg:Paste#static
document_root = %(here)s/htdocs

[filter-app:blog]
use = egg:Authentication#auth
next = blogapp
roles = admin
htpasswd = /home/me/users.htpasswd

[app:blogapp]
use = egg:BlogApp
database = sqlite:/home/me/blog.db

[app:wiki]
use = call:mywiki.main:application
database = sqlite:/home/me/wiki.db

urlmap 至關於作了多路由的分發,請求http://xxxx/ 時會請求分段名稱爲home的app, 走到相對的處理函數或者controller,其餘類同

第一分段解釋:

[composite:main]
use = egg:Paste#urlmap
/ = home
/blog = blog
/cms = config:cms.ini

composite 分段將請求分發給其餘applications程序,use = egg:Paste#urlmap 意思是這個composite程序將使用paste名稱爲urlmap的包, urlmap是一個特別常見的複合應用程序,它使用路徑前綴將請求映射到另外一個應用程序。其餘的如/ /blog 是應用程序,最後一個/cms,代表指的是另外一個ini配置文件。

[app:home]
use = egg:Paste#static
document_root = %(here)s/htdocs

egg:Paste#static 靜態文件配置,使用paste包static只提供靜態文件,須要指定document_root目錄

使用方法

from paste.deploy import loadapp
wsgi_app = loadapp('config:/path/to/config.ini')

section的type能夠有:app、composite、filter、pipeline、filter-app等。學習具體更多配置詳參官網 

Route 

Routes 是基於 ruby on rails 的 routes system 開發的 python 庫,它根據 http url 把請求映射到具體的方法,routes 簡單易用,可方便的構建 Restful 風格的 url。

示例:

import eventlet
from eventlet import wsgi
from paste.deploy import loadapp
import routes
import routes.middleware as middleware
import webob.dec
import webob.exc

from manager.settings import CONF


class TestController(object):

    def index(self, req):
        return 'GET'

    def create(self, req):
        return 'POST'

    def delete(self, req):
        return 'DELETE'

    def update(self, req):
        return 'PUT'


class Resource(object):
    def __init__(self, controller):
        self.controller = controller()

    @webob.dec.wsgify
    def __call__(self, req):
        match = req.environ['wsgiorg.routing_args'][1]
        action = match['action']
        if hasattr(self.controller, action):
            method = getattr(self.controller, action)
            return method(req)
        return webob.exc.HTTPNotFound()


class TestApplication(object):
    def __init__(self):
        self.mapper = routes.Mapper()
        self.mapper.resource('test', 'tests', controller=Resource(TestController))
        self.router = middleware.RoutesMiddleware(self.dispatch, self.mapper)

    @webob.dec.wsgify
    def __call__(self, req):
        return self.router

    @classmethod
    def factory(cls, global_conf, **local_conf):
        return cls()

    @staticmethod
    @webob.dec.wsgify
    def dispatch(req):
        match = req.environ['wsgiorg.routing_args'][1]
        return match['controller'] if match else webob.exc.HTTPNotFound()


if '__main__' == __name__:
    application = loadapp('config:%s/config.ini' % (CONF))
    server = eventlet.spawn(wsgi.server,
                            eventlet.listen(('0.0.0.0', 8080)), application)
    server.wait()
相關文章
相關標籤/搜索