你想使用一個簡單的REST接口經過網絡遠程控制或訪問你的應用程序,可是你又不想本身去安裝一個完整的web框架。html
構建一個REST風格的接口最簡單的方法是建立一個基於WSGI標準(PEP 3333)的很小的庫,下面是一個例子:python
# resty.py
import cgi def notfound_404(environ, start_response): start_response('404 Not Found', [ ('Content-type', 'text/plain') ]) return [b'Not Found'] class PathDispatcher: def __init__(self): self.pathmap = { } def __call__(self, environ, start_response): path = environ['PATH_INFO'] params = cgi.FieldStorage(environ['wsgi.input'], environ=environ) method = environ['REQUEST_METHOD'].lower() environ['params'] = { key: params.getvalue(key) for key in params } handler = self.pathmap.get((method,path), notfound_404) return handler(environ, start_response) def register(self, method, path, function): self.pathmap[method.lower(), path] = function return function
爲了使用這個調度器,你只須要編寫不一樣的處理器,就像下面這樣:web
import time _hello_resp = '''\ <html> <head> <title>Hello {name}</title> </head> <body> <h1>Hello {name}!</h1> </body> </html>''' def hello_world(environ, start_response): start_response('200 OK', [ ('Content-type','text/html')]) params = environ['params'] resp = _hello_resp.format(name=params.get('name')) yield resp.encode('utf-8') _localtime_resp = '''\ <?xml version="1.0"?> <time> <year>{t.tm_year}</year> <month>{t.tm_mon}</month> <day>{t.tm_mday}</day> <hour>{t.tm_hour}</hour> <minute>{t.tm_min}</minute> <second>{t.tm_sec}</second> </time>''' def localtime(environ, start_response): start_response('200 OK', [ ('Content-type', 'application/xml') ]) resp = _localtime_resp.format(t=time.localtime()) yield resp.encode('utf-8') if __name__ == '__main__': from resty import PathDispatcher from wsgiref.simple_server import make_server # Create the dispatcher and register functions dispatcher = PathDispatcher() dispatcher.register('GET', '/hello', hello_world) dispatcher.register('GET', '/localtime', localtime) # Launch a basic server httpd = make_server('', 8080, dispatcher) print('Serving on port 8080...') httpd.serve_forever()
要測試下這個服務器,你可使用一個瀏覽器或 urllib
和它交互。例如:編程
>>> u = urlopen('http://localhost:8080/hello?name=Guido') >>> print(u.read().decode('utf-8')) <html> <head> <title>Hello Guido</title> </head> <body> <h1>Hello Guido!</h1> </body> </html> >>> u = urlopen('http://localhost:8080/localtime') >>> print(u.read().decode('utf-8')) <?xml version="1.0"?> <time> <year>2012</year> <month>11</month> <day>24</day> <hour>14</hour> <minute>49</minute> <second>17</second> </time> >>>
在編寫REST接口時,一般都是服務於普通的HTTP請求。可是跟那些功能完整的網站相比,你一般只須要處理數據。 這些數據以各類標準格式編碼,好比XML、JSON或CSV。 儘管程序看上去很簡單,可是以這種方式提供的API對於不少應用程序來說是很是有用的。瀏覽器
例如,長期運行的程序可能會使用一個REST API來實現監控或診斷。 大數據應用程序可使用REST來構建一個數據查詢或提取系統。 REST還能用來控制硬件設備好比機器人、傳感器、工廠或燈泡。 更重要的是,REST API已經被大量客戶端編程環境所支持,好比Javascript, Android, iOS等。 所以,利用這種接口可讓你開發出更加複雜的應用程序。服務器
爲了實現一個簡單的REST接口,你只需讓你的程序代碼知足Python的WSGI標準便可。 WSGI被標準庫支持,同時也被絕大部分第三方web框架支持。 所以,若是你的代碼遵循這個標準,在後面的使用過程當中就會更加的靈活!cookie
在WSGI中,你能夠像下面這樣約定的方式以一個可調用對象形式來實現你的程序。網絡
import cgi def wsgi_app(environ, start_response): pass
environ
屬性是一個字典,包含了從web服務器如Apache[參考Internet RFC 3875]提供的CGI接口中獲取的值。 要將這些不一樣的值提取出來,你能夠像這麼這樣寫:app
def wsgi_app(environ, start_response): method = environ['REQUEST_METHOD'] path = environ['PATH_INFO'] # Parse the query parameters params = cgi.FieldStorage(environ['wsgi.input'], environ=environ)
咱們展現了一些常見的值。environ['REQUEST_METHOD']
表明請求類型如GET、POST、HEAD等。environ['PATH_INFO']
表示被請求資源的路徑。 調用 cgi.FieldStorage()
能夠從請求中提取查詢參數並將它們放入一個類字典對象中以便後面使用。框架
start_response
參數是一個爲了初始化一個請求對象而必須被調用的函數。 第一個參數是返回的HTTP狀態值,第二個參數是一個(名,值)元組列表,用來構建返回的HTTP頭。例如:
def wsgi_app(environ, start_response): pass start_response('200 OK', [('Content-type', 'text/plain')])
爲了返回數據,一個WSGI程序必須返回一個字節字符串序列。能夠像下面這樣使用一個列表來完成:
def wsgi_app(environ, start_response): pass start_response('200 OK', [('Content-type', 'text/plain')]) resp = [] resp.append(b'Hello World\n') resp.append(b'Goodbye!\n') return resp
或者,你還可使用 yield
:
def wsgi_app(environ, start_response): pass start_response('200 OK', [('Content-type', 'text/plain')]) yield b'Hello World\n' yield b'Goodbye!\n'
這裏要強調的一點是最後返回的必須是字節字符串。若是返回結果包含文本字符串,必須先將其編碼成字節。 固然,並無要求你返回的必定是文本,你能夠很輕鬆的編寫一個生成圖片的程序。
儘管WSGI程序一般被定義成一個函數,不過你也可使用類實例來實現,只要它實現了合適的 __call__()
方法。例如:
class WSGIApplication: def __init__(self): ... def __call__(self, environ, start_response) ...
咱們已經在上面使用這種技術建立 PathDispatcher
類。 這個分發器僅僅只是管理一個字典,將(方法,路徑)對映射處處理器函數上面。 當一個請求到來時,它的方法和路徑被提取出來,而後被分發到對應的處理器上面去。 另外,任何查詢變量會被解析後放到一個字典中,以 environ['params']
形式存儲。 後面這個步驟太常見,因此建議你在分發器裏面完成,這樣能夠省掉不少重複代碼。 使用分發器的時候,你只需簡單的建立一個實例,而後經過它註冊各類WSGI形式的函數。 編寫這些函數應該超級簡單了,只要你遵循 start_response()
函數的編寫規則,而且最後返回字節字符串便可。
當編寫這種函數的時候還需注意的一點就是對於字符串模板的使用。 沒人願意寫那種處處混合着 print()
函數 、XML和大量格式化操做的代碼。 咱們上面使用了三引號包含的預先定義好的字符串模板。 這種方式的可讓咱們很容易的在之後修改輸出格式(只須要修改模板自己,而不用動任何使用它的地方)。
最後,使用WSGI還有一個很重要的部分就是沒有什麼地方是針對特定web服務器的。 由於標準對於服務器和框架是中立的,你能夠將你的程序放入任何類型服務器中。 咱們使用下面的代碼測試測試本節代碼:
if __name__ == '__main__': from wsgiref.simple_server import make_server # Create the dispatcher and register functions dispatcher = PathDispatcher() pass # Launch a basic server httpd = make_server('', 8080, dispatcher) print('Serving on port 8080...') httpd.serve_forever()
上面代碼建立了一個簡單的服務器,而後你就能夠來測試下你的實現是否能正常工做。 最後,當你準備進一步擴展你的程序的時候,你能夠修改這個代碼,讓它能夠爲特定服務器工做。
WSGI自己是一個很小的標準。所以它並無提供一些高級的特性好比認證、cookies、重定向等。 這些你本身實現起來也不難。不過若是你想要更多的支持,能夠考慮第三方庫,好比 WebOb
或者 Paste