創建基於REST風格的簡單介面

我們希望通過一個基於RESET風格的簡單介面來對程序實現遠程控制和交互。但是我們又不想為此去安裝一個成熟的Web編程框架

構建基於RESET風格的介面最簡單的方式之一就是根據WSGI規範創建一個小型的庫:

# resty.py

import cgi

def notfound_404(environ, start_response):
start_response(404 Not Found, [ (Content-type, text/plain) ])
return [bNot 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

為了使用這個調度器,你只需要編寫不同的處理器,就像下面這樣:

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框架支持。 因此,如果你的代碼遵循這個標準,在後面的使用過程中就會更加的靈活!

在WSGI中,你可以像下面這樣約定的方式以一個可調用對象形式來實現你的程序。

import cgi

def wsgi_app(environ, start_response):
pass

environ屬性是一個字典,包含了從web伺服器如Apache提供的CGI介面中獲取的值。 要將這些不同的值提取出來,你可以像這麼這樣寫:

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程序通常被定義成一個函數,不過你也可以使用類實例來實現,只要它實現了合適的__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

參考書目

《Python CookBook》作者:【美】 David Beazley, Brian K. Jones

Github地址:

yidao620c/python3-cookbook?

github.com
圖標

推薦閱讀:
相关文章