來杯咖啡看Pecan

Pecan的介紹及安裝

文章內容來自官方文檔:http://pecan.readthedocs.io/en/latest/quick_start.htmlhtml

Pecan的介紹:node

   Pecan是一個路由對象分發的oython web框架。本質上能夠將url經過分割爲每一部分,而後對每一部分查找對應處理該URL部分的處理類,處理後,繼續交給後面部分的URL處理,直到全部URL部分都被處理後,調用最後分割的URL對應的處理函數處理。python

本文以Xshall爲主在其進行操做web

Pecan的安裝json

建立項目segmentfault

項目建立好以後目錄結構以下api

app.py:通常包含了Pecan應用的入口,包含初始化代碼。app

Config.py : 包含Pecan的應用配置,會被 app.py使用。框架

Controllersl : 這個目錄包含全部的控制器,也就是API具體邏輯的地方 。curl

Cotrollers/root.py : 這個包含根路徑對應的控制器。

Controllers/v1/ 這個目錄放的是版本的API的。

Public : 文件夾存放一些Web應用所需的Image,Css或者JavaScript。

setup.py和setup.cfg用於Web應用的安裝部署。

templates:存儲Html或者Json的末班文件。

tests:存放測試用例。

代碼變少了:application的配置

Pecan的配置很容易,經過 一個python的源碼式的配置文件就能夠完成基本的配置,這個配置的主要目的是指定應用程序的root,而後用於生成WSGI application。咱們來看Magnum項目的列子,Magnum項目有個API服務是 用Pecan實現的,在magnum/api/config.py文件中能夠找到這個文件,主要內容以下:

 1 app = {
 2     'root': 'magnum.api.controllers.root.RootController',
 3     'modules': ['magnum.api'],
 4     'debug': False,
 5     'hooks': [
 6         hooks.ContextHook(),
 7         hooks.RPCHook(),
 8         hooks.NoExceptionTracebackHook(),
 9     ],
10     'acl_public_routes': [
11         '/'
12     ],
13 }

上面這個app對象就是Pecan的配置,每一個Pecan應用都須要有這麼一 個名爲app的配置。app配置中最重要的就是root的值,這個值表示了應用程序的入口,也就是從哪一個地方開始解析HTTP的根path:/。 hooks對應的配置是一些pecan的hook,做用相似於WSGI Middleware。有了app配置後,就可讓Pecan生成一個WSGI application。在Magnum項目中,magnum/api/app.py文件就是生成WSGI application的地方,咱們來看一下這個主要的內容:

 1 def get_pecan_config():
 2     # Set up the pecan configuration
 3     filename = api_config.__file__.replace('.pyc', '.py')
 4     return pecan.configuration.conf_from_file(filename)
 5 
 6 
 7 def setup_app(config=None):
 8     if not config:
 9         config = get_pecan_config()
10 
11     app_conf = dict(config.app)
12 
13     app = pecan.make_app(
14         app_conf.pop('root'),
15         logging=getattr(config, 'logging', {}),
16         wrap_app=middleware.ParsableErrorMiddleware,
17         **app_conf
18     )
19 
20     return auth.install(app, CONF, config.app.acl_public_routes)

get_pecan_config()方法讀取咱們上面提到的config.py文件,而後返回一個pecan.configuration.Config對象,setup_app()函數首先調用get_pecan_config()函數獲取application的配置,而後調用pecan.make_app()函數建立了一個WSGI application,調用了auth.install()函數(也就是magnum.api.auth.install()函數)爲剛剛生成的WSGI application加上keystone的認證中間件(確保全部的請求都會經過keystone認證)。

到這邊爲止,一個pecan的WSGI application就已經準備好了,只要調用這個setup_app()函數就得到,至於如何部署這個WSGI application請參考WSGI簡介這篇文章(https://segmentfault.com/a/1190000003069785)從Magnum這個實際的列子能夠看出,使用了pecan以後 ,咱們再也不須要本身寫那些WSGI application代碼了,直接調用pecan的make_app()函數就能完成 這些工做,另外,對於以前使用pasteDeploy時用到的不少WSGI中間件,能夠選擇使用pecan的hooks機制來實現,也選擇使用WSGI中間件的方式來實現,在Magnum的API服務就同時使用了這兩種方式,其實pecan還能夠和pastedeploy一塊兒使用,ceilometer項目就是這麼作的,你們能夠看看。

肯定路由變得容易了:對象分發式的路由

 Pecan不只縮減了生成WSGI application的代碼,並且也讓開發人員更容易的指定一個application的路由,Pecan採用了一種對象分發風格(object-dispatch style)的路由模式,咱們直接經過列子來解釋這種路由模式,仍是以Magnum項目爲例。

上面提到了,Magnum的API服務的root是magnum.api.controllers.root.RootController。這裏的RootController的是一個類,咱們來看代碼:

 1 class RootController(rest.RestController):
 2 
 3     _versions = ['v1']
 4     """All supported API versions"""
 5 
 6     _default_version = 'v1'
 7     """The default API version"""
 8 
 9     v1 = v1.Controller()
10 
11     @expose.expose(Root)
12     def get(self):
13         # NOTE: The reason why convert() it's being called for every
14         #       request is because we need to get the host url from
15         #       the request object to make the links.
16         return Root.convert()
17 
18     @pecan.expose()
19     def _route(self, args):
20         """Overrides the default routing behavior.
21 
22         It redirects the request to the default version of the magnum API
23         if the version number is not specified in the url.
24         """
25 
26         if args[0] and args[0] not in self._versions:
27             args = [self._default_version] + args
28         return super(RootController, self)._route(args)

別看這個類這麼長,我來解釋下你就懂了,首先你能夠忽略掉_route()函數,這個函數使用來覆蓋Pecan的默認路由實現的,在這裏去掉它不妨礙咱們理解Pecan(這裏的_route()函數的做用把全部請求重定向到默認的API版本去),去掉_route()和其餘的東西后,整個類就是變成這麼短:

1 class RootController(rest.RestController):
2     v1 = v1.Controller()
3 
4     @expose.expose(Root)
5     def get(self):
6         return Root.convert()

首先,你要記住,這個RootController對應的是URL中根路徑,也就是path中最左邊的/。

RootController繼承自rest.RestController,是Pecan實現的RESTful控制器,這裏get()函數表示,當訪問的是GET/時,由該函數處理,get()函數會返回一個WSME對象,表示已個形式的HTTP Response,這個下面再講。get()函數上面的expose裝飾器是Pecan實現路由控制的一個方式,被expose的函數纔會被路由處理。

這裏的v1 = v1.Controller()表示,當訪問的是GET/v1或者GET/v1/....時,請求由一個v1.Controller實例來處理。

爲了加深你們的理解,咱們再來看下v1.Controller的實現:

 1 class Controller(rest.RestController):
 2     """Version 1 API controller root."""
 3 
 4     bays = bay.BaysController()
 5     baymodels = baymodel.BayModelsController()
 6     containers = container.ContainersController()
 7     nodes = node.NodesController()
 8     pods = pod.PodsController()
 9     rcs = rc.ReplicationControllersController()
10     services = service.ServicesController()
11     x509keypairs = x509keypair.X509KeyPairController()
12     certificates = certificate.CertificateController()
13 
14     @expose.expose(V1)
15     def get(self):
16         return V1.convert()
17 
18     ...

上面這個Controoler也是繼承自restRestController。因此它的get函數表示,當訪問的是GET/v1的時候,要作的處理。而後它還有不少類屬性,這些屬性分別表示不一樣的URL路徑的控制器:

一、/vq/bays   由bays處理

二、/v1baymodels  由baymodels處理

三、/v1/containers  由containers處理

其餘的都是相似的。咱們在繼續看bay.B安陽市Controller的代碼:

class BaysController(rest.RestController):
    """REST controller for Bays."""
    def __init__(self):
        super(BaysController, self).__init__()

    _custom_actions = {
        'detail': ['GET'],
    }

    def get_all(...):
    
    def detail(...):
    
    def get_one(...):
    
    def post(...):
    
    def patch(...):

    def delete(...):

這個Controller中只有函數,沒有任何類屬性,並且沒有實現任何特殊方法,因此/v1/bays開頭的URL處理都在這個controller中終結,這個類會處理以下請求:

一、GET /v1/bays

二、GET /v1/bays/{UUID}

三、POST /v1/bays

四、PATCH /v1/bays/{UUID}

五、DELETE /v1/bays/{UUID}

六、GET / v1/bays/detail/{UUID}

看到上面的3個controller以後,你應該能大概明白Pecan是如何對URL進行路由的,這種路由方式就是對象分發:(根據類屬性)、(包括數據屬性)和方法屬性來決定如何路由一個HTTP請求,Pecan的文檔中請求額路由有專門的描述,要想掌握Pecan的路由仍是要完整的看一下官方文檔。

內置RESTful支持

咱們上面舉例的controller都是繼承自pecan.rest.RestController,這種controller稱爲RESTful controller,專門用於實現RESTful API的,所以在Openstack中使用特別多,Pecan還支持普通的controller,稱爲Generic controller。Generic controller繼承自object對象,默認沒有實現對RESTful請求的方法。簡單的說,RESTful controller幫咱們規定好了get_one(),get_all(),get(),post()等方法對應的HTTP請求,而Generic controller則沒有,關於這兩種controller的區別 ,能夠看官方文檔,有很清楚的示例。

對於RestController中沒有預先定義好的方法,咱們能夠經過控制器的_custom_actions屬性來指定其能處理的方法。

1 class RootController(rest.RestController):
2     _custom_actions = {
3         'test': ['GET'],
4     }
5 
6     @expose()
7     def test(self):
8         return 'hello'

上面這個控制器是一個根控制器,指定了/test路徑支持GET方法,效果以下:

$ curl http://localhost:8080/test

hello%

那麼HTTP請求和HTTP響應呢?

wsme

Pecan對請求和響應的處理

在開始提到WSME以前,咱們吸納來看下Pecan本身對HTTP請求和響應的處理。這樣你能更好的理解爲何會引入WSME庫。

Pecan框架爲每一個線程維護了單獨的請求和響應的對象,你能夠直接在處理函數中訪問。

pecan.requesr和pecan.response分別表明當前須要處理的請求和響應對象,你能夠直接操做這兩個對象,好比指定響應的狀態碼,就像下面這個列子同樣:

1 @pecan.expose()
2 def login(self):
3     assert pecan.request.path == '/login'
4     username = pecan.request.POST.get('username')
5     password = pecan.request.POST.get('password')
6 
7     pecan.response.status = 403
8     pecan.response.text = 'Bad Login!'

這個列子演示了訪問POST請求的參數以及返回403,你也能夠從新構造一個pecan.Response對象做爲返回值:

1 from pecan import expose, Response
2 
3 class RootController(object):
4 
5     @expose()
6     def hello(self):
7         return Response('Hello, World!', 202)

另外,HTTP請求參數的參數也會能夠做爲控制器方法的參數,仍是來看幾個官方文檔的列子:

1 class RootController(object):
2     @expose()
3     def index(self, arg):
4         return arg
5 
6     @expose()
7     def kwargs(self, **kwargs):
8         return str(kwargs)

這個控制器中的方法直接返回了參數,演示了對GET請求參數的處理,效果是這樣的:

1 $ curl http://localhost:8080/?arg=foo
2 foo
3 $ curl http://localhost:8080/kwargs?a=1&b=2&c=3
4 {u'a': u'1', u'c': u'3', u'b': u'2'}

有時候,參數也多是URL的一部分,好比最後一段path做爲參數,就像下面這樣:

1 class RootController(object):
2     @expose()
3     def args(self, *args):
4         return ','.join(args)

效果是這樣的:

1 $ curl http://localhost:8080/args/one/two/three
2 one,two,three

另外,咱們還要看一下POST方法的參數 如何處理:

1 class RootController(object):
2     @expose()
3     def index(self, arg):
4         return arg

效果以下,就是把HTTP body解析成了控制器方法的參數:

1 $ curl -X POST "http://localhost:8080/" -H "Content-Type: 
2 application/x-www-form-urlencoded" -d "arg=foo" foo

返回JSON仍是HTML?

若是你不是明確的返回一個Response對象,那麼Pecan中方法的返回內容類型就是由expose()裝飾器決定的,默認狀況下,控制器的方法返回的content-type是HTML。

1 class RootController(rest.RestController):
2     _custom_actions = {
3         'test': ['GET'],
4     }
5 
6     @expose()
7     def test(self):
8         return 'hello'

效果以下:

 1 $ curl -v http://localhost:8080/test
 2 * Hostname was NOT found in DNS cache
 3 *   Trying 127.0.0.1...
 4 * Connected to localhost (127.0.0.1) port 8080 (#0)
 5 > GET /test HTTP/1.1
 6 > User-Agent: curl/7.38.0
 7 > Host: localhost:8080
 8 > Accept: */*
 9 >
10 * HTTP 1.0, assume close after body
11 < HTTP/1.0 200 OK
12 < Date: Tue, 15 Sep 2015 14:31:28 GMT
13 < Server: WSGIServer/0.1 Python/2.7.9
14 < Content-Length: 5
15 < Content-Type: text/html; charset=UTF-8
16 <
17 * Closing connection 0
18 hello% 

也可讓他返回JSON:

1 class RootController(rest.RestController):
2     _custom_actions = {
3         'test': ['GET'],
4     }
5 
6     @expose('json')
7     def test(self):
8         return 'hello'

效果以下:

 1 curl -v http://localhost:8080/test
 2 * Hostname was NOT found in DNS cache
 3 *   Trying 127.0.0.1...
 4 * Connected to localhost (127.0.0.1) port 8080 (#0)
 5 > GET /test HTTP/1.1
 6 > User-Agent: curl/7.38.0
 7 > Host: localhost:8080
 8 > Accept: */*
 9 >
10 * HTTP 1.0, assume close after body
11 < HTTP/1.0 200 OK
12 < Date: Tue, 15 Sep 2015 14:33:27 GMT
13 < Server: WSGIServer/0.1 Python/2.7.9
14 < Content-Length: 18
15 < Content-Type: application/json; charset=UTF-8
16 <
17 * Closing connection 0
18 {"hello": "world"}% 

甚至,你可讓一個控制器方法根據URL path的來決定是返回HTML仍是JSON:

1 class RootController(rest.RestController):
2     _custom_actions = {
3         'test': ['GET'],
4     }
5 
6     @expose()
7     @expose('json')
8     def test(self):
9         return json.dumps({'hello': 'world'})

返回JSON:

 1 $ curl -v http://localhost:8080/test.json
 2 * Hostname was NOT found in DNS cache
 3 *   Trying 127.0.0.1...
 4 * Connected to localhost (127.0.0.1) port 8080 (#0)
 5 > GET /test.json HTTP/1.1
 6 > User-Agent: curl/7.38.0
 7 > Host: localhost:8080
 8 > Accept: */*
 9 >
10 * HTTP 1.0, assume close after body
11 < HTTP/1.0 200 OK
12 < Date: Wed, 16 Sep 2015 14:26:27 GMT
13 < Server: WSGIServer/0.1 Python/2.7.9
14 < Content-Length: 24
15 < Content-Type: application/json; charset=UTF-8
16 <
17 * Closing connection 0
18 "{\"hello\": \"world\"}"% 

返回HTML:

 1 $ curl -v http://localhost:8080/test.html
 2 * Hostname was NOT found in DNS cache
 3 *   Trying 127.0.0.1...
 4 * Connected to localhost (127.0.0.1) port 8080 (#0)
 5 > GET /test.html HTTP/1.1
 6 > User-Agent: curl/7.38.0
 7 > Host: localhost:8080
 8 > Accept: */*
 9 >
10 * HTTP 1.0, assume close after body
11 < HTTP/1.0 200 OK
12 < Date: Wed, 16 Sep 2015 14:26:24 GMT
13 < Server: WSGIServer/0.1 Python/2.7.9
14 < Content-Length: 18
15 < Content-Type: text/html; charset=UTF-8
16 <
17 * Closing connection 0
18 {"hello": "world"}% 

這裏要注意一下;

一、同一個字符串做爲JSON返回和做爲HTML返回是不同的,仔細看一下HTTP響應的內容。

二、咱們的列子中在URL的最後加上了.html後綴或者.json後綴,請嘗試一下不加後綴的變化是返回什麼?而後,調換一下兩個expose()的順序再試一下。

從上面的列子能夠看出,決定響應類型的主要是傳遞給expose()函數的參數,咱們看下expose()函數的完整聲明:

1 pecan.decorators.expose(template=None,
2                         content_type='text/html',
3                         generic=False)

template參數用來指定返回值得末班,若是是json就會返回json內容,這裏能夠指定一個

 HTML文件,或者指定一個mako模板。

content_type指定響應的content-type,默認值是"text/html"

generic參數代表該方法是一個"泛型"方法,能夠指定多個不一樣的函數對應同一個路徑的不一樣的HTTP方法。

看過參數的解釋後,你應該能大概瞭解expose()函數是如何控制HTTP響應的內容和類型的。

相關文章
相關標籤/搜索