OpenStack之RESTful API呼叫(一)
前面兩篇文章分析了OpenStack同一組件下不同模組之間的通訊方式——RPC。這篇文章我們來繼續看一下OpenStack中的另一種通訊方式——RESTful API。
在OpenStack中,RESTful API用於不同元件之間的通訊。先來簡單的瞭解一下REST吧。
REST構建於HTTP協議之上,遵循並擴充套件和規範了傳統HTTP協議中的標準方法,以下是RESTful API定義的標準方法:
GET:查詢資源
POST:增加資源
PUT:更新資源
HEAD:驗證,包括使用者身份的驗證和資源的驗證
DELETE:刪除資源
同時,REST要求URL的格式遵守統一的規範,所有資源都具有唯一的ID。具體到OpenStack中,每個資源都有一個UUID,作為全域性唯一的標識。
在OpenStack中,所有的Web服務都是通過WSGI部署的。所謂WSGI是Python Web Server Gateway Interface的縮寫,是Python應用程式或框架和Web伺服器之間的一種介面。簡而言之,在Python Web 開發中,服務端程式可以分為兩個部分,一是伺服器程式,二是應用程式。前者負責把客戶端請求接收,整理,後者負責具體的邏輯處理。WSGI 是伺服器程式與應用程式的一個約定,它規定了雙方各自需要實現什麼介面,提供什麼功能,以便二者能夠配合使用。
本著設計的“開閉原則”,OpenStack通過配置檔案來配置WSGI服務的應用程式,PasteDeploy就是專門定製WSGI服務的開發包。
來看一個簡單的利用PasteDeploy定製的WSGI服務。
1)api-paste.ini配置檔案
OpenStack中,大部分元件的WSGI服務的配置是儲存在api-paste.ini檔案中的。這裡也用這樣的方式,api-paste.ini配置檔案如下
[app:main]
paste.app_factory = wsgi_paste:app_factory
以上配置中,第01行定義了名為main的app。app(應用程式)是PasteDeploy定義的一類部件。除了app外,PasteDeploy還定義了filer(過濾器)、pipeline(管道)和composite(複合體)等部件。第02行指定了main應用程式對應的工廠方法,該工廠方法必須返回一個方法的例項,該方法便是處理HTTP請求的應用程式。這個應用程式定義在wsgi_paste包的app_factory方法。
2)wsgi_paste.py檔案
在api-paste.ini配置檔案中引用的wsgi_paste包就是自定義的wsgi_paste.py檔案。
#應用程式
@wsgify
def application(request):
return Response("Hello!\n")
#應用程式工廠方法
def app_factory(global,**local_config):
return application
#根據api-paste.ini檔案動態載入應用程式
wsgi_app = loadapp('config:' + 配置檔案路徑)
#啟動WSGI服務
httpserver.serve(wsgi_app,host='127.0.0.1',port=8080)
這個程式很簡單,在一個api-paste.ini檔案中,可以定義多個部件,預設情況下,loadapp方法會把名為main的部件作為應用程式的入口。
3)程式碼測試
在終端執行python wsgi_paste.py啟動WSGI服務,然後在另一終端執行curl 127.0.0.1:8080傳送HTTP請求。
顯然,如果把一個WSGI服務的所有功能都放在一個方法中,是不利於擴充套件的,所以PasteDeploy引入了filter(過濾器)的概念,過濾器的作用和用於java開發中的struts框架中的過濾器的作用類似,多個過濾器和應用可以組成一個pipeline(管道),這個概念和struts中的stack(棧)的概念類似。下面來看一個應用了過濾器的WSGI服務的例子。
1)api-paste.ini配置檔案
[pipeline:main]
pipeline = auth hello
[app:hello]
paste.app_factory = wsgi_paste:app_factory
[filter:auth]
paste.filter_factory = wsgi_middleware:filter_factory
管道main部件由過濾器auth和應用程式hello組成。應用程式hello和上面的應用程式一樣,過濾器auth的工廠方法在wsgi_middleware包中定義。這裡的wsgi_middleware包對應於自定義的wsgi_middleware.py。
2)wsgi_middleware.py
#過濾器方法
@wsgify.middleware
def filter(request,app):
if request.headers.get('X-Auth-Token') != 'open-sesame' #驗證HTTP頭
return exc.HTTPForbidden()
return app(request) #驗證成功,執行下一個過濾器或應用程式
#過濾器工廠方法
def filter_factory(global,**local_config):
return filter
這裡的filter方法與之前介紹的application方法的不同點在於多了一個app引數。
通過上面的方式可以方便的新增和刪除功能模組了,但是還存在兩個問題:
1. 過濾器和應用程式是用方法實現的,對於實現比較複雜的功能是不合適的
2. 通過httpserver來啟動WSGI服務,使得一個程序對應一個WSGI服務。對於WSGI服務的啟動、關閉操作都需要直接對程序進行操作
所以為了彌補以上兩個不足,在OpenStack中利用類來實現過濾器和應用程式並利用eventlet來啟動WSGI服務。其中eventlet是python多執行緒操作的類庫。
我們來看一下如何利用類來實現過濾器和應用程式的例項。
還是先來看一下api-paste.ini配置檔案:
[pipeline:main]
pipeline = auth hello
[app:hello]
paste.app_factory = app:Hello.app_factory
[filter:auth]
paste.filter_factory = middleware:Auth.filter.factory
可以看到配置檔案盒上面的差別不大,主要區別在於以下的幾個類。
(1)Auth類
class Auth(object):
def __init__(self, app):
self.app = app
#工廠方法
@classmethod
def filter_factory(cls, global_config, **local_config):
def _factory(app):
return cls(app)
return _factory
#實現業務邏輯
@wsgify(RequestClass=webob.Request)
def __call__(self, request):
resp = self.process_request(req)
if resp:
return resp
return req.get_response(self.app)
def process_request(self, request):
if req.headers.get('X-Auth-Token') != 'open-sesame':
return exc.HTTPForbidden()
上述程式碼定義了一個工廠方法,該工廠返回一個Auth類的例項,該例項是一個callable物件。當過濾器接收到請求後,會呼叫類中的__call__方法,__call__方法內部的邏輯和上面分析的“利用方法實現過濾器”中定義的過濾器方法一樣。
(2)Hello類
class Hello(object):
#工廠方法
@classmethod
def app_factory(cls,global_config,**local_config):
def _factory(app):
return cls(app)
return _factory
@wsgify(RequestClass=Request)
def __call__(request):
return Response('Hello')
(3)Loader類
class Loader(object):
def load_app(self):
ini_path = os.path.normpath(
os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
'api-paste.ini'))
if not os.path.isfile(ini_path):
print("Cannot find api-paste.ini.\n")
exit(1)
return deploy.loadapp('config:' + ini_path)
這個類比較簡單,它實現了一個load_app方法。該方法引用api-paste.ini配置檔案,並通過呼叫PasteDeploy的loadapp方法來載入應用程式。
(4)Server類
class Server(object):
def __init__(self, app, host='0.0.0.0', port=0):
self._pool = eventlet.GreenPool(10)
self.app = app
self._socket = eventlet.listen((host, port), backlog=10)
(self.host, self.port) = self._socket.getsockname()
print("Listening on %(host)s:%(port)s" % self.__dict__)
def start(self):
self._server = eventlet.spawn(eventlet.wsgi.server,
self._socket,
self.app,
protocol=eventlet.wsgi.HttpProtocol,
custom_pool=self._pool)
def stop(self):
if self._server is not None:
self._pool.resize(0)
self._server.kill()
def wait(self):
try:
self._server.wait()
except greenlet.GreenletExit:
print("WSGI server has stopped.")
這個類的功能是實現對執行緒的建立和管理。
(5)WSGIService類
service.py
class WSGIService(object):
def __init__(self):
self.loader = wsgi.Loader()
self.app = self.loader.load_app()
self.server = wsgi.Server(self.app,
'0.0.0.0',
8080)
def start(self):
self.server.start()
def wait(self):
self.server.wait()
def stop(self):
self.server.stop()<pre name="code" class="python"> if __name__ == "__main__":
server = WSGIService()
server.start()
server.wait()
可以看到WSGIService類就是呼叫Server類的start、stop和wait方法。該類定義了主方法,我們可以利用python service.py啟動WSGI服務。
至此,我們分析了應用方法和類的方式通過PasteDeploy來實現的WSGI服務。後面我們將繼續分析OpenStack中WSGI。