[Twisted] Part 12: A Poetry Transformation Server
本文由Dave的Part 12: A Poetry Transformation Server翻譯而成,你可以由Part 1開始閱讀這個系列的文章,也可以在這裡找到整個系列的目錄。
在Part 9與Part 10我們介紹了詩歌轉換引擎的概念。我們最後實現的cummingsifier非常簡單,以致我們必須加入隨機例外來模擬失敗的情況。但是如果轉換引擎位於另一個伺服器,提供網路化的「詩歌轉換服務」,那麼這就存在著一個更有可能實際發生的故障原因:轉換伺服器停止。
因此,在Part 12中,我們將實現一個詩歌轉換伺服器(譯註:也就是說將提供詩歌與轉換詩歌分為兩個伺服器),然後在Part 13中,我們會更新我們的詩歌伺服器來使用這個外部轉換服務,並在這個過程中學習幾個關於Deferreds的新內容。
當我們在設計協定時,我們會讓伺服器支援多種轉換類型,並允許用戶端選擇使用哪種轉換方式。所以用戶端會傳送兩份訊息:轉換方法的名稱與詩歌的全文。而伺服器會回傳一份訊息,也就是轉換後的詩歌。所以,我們有了一個非常簡單的Remote Procedure Call(遠端程序呼叫)。
Twisted支援了幾個我們能用來解決這個問題的協定:XML-RPC、Perspective Broker、與AMP。
但是介紹這些功能完整的協定會讓我們太離題,所以我們會使用我們自己簡單的協定來代替。我們會讓用戶端傳送這個格式的字串(沒有括號):
這邊就只有轉換方法的名稱,後面一個句點,再後面是完整的詩歌內容。然後我們會用netstring的格式對所有東西進行編碼,伺服器也會使用netstring格式送回轉換過的詩歌內容。由於netstring使用length-encoding,用戶端可以檢測出伺服器沒有送回完整的結果(也許結果在操作過程中間崩潰了)。如果你還記得,我們之前的詩歌協定無法檢測到詩歌傳輸中斷的情況。
協定設計的內容到此為止。它沒有好到會贏得什麼獎項,但對我們的目標而言已經夠好了。
這個轉換服務目前透過cummingsify方法實現了一個與它同名的轉換。我們可以增加其他的方法來增加演算法的種類。有一件重要的事情要注意:轉換服務與我們之前看到的協定細節完全無關(譯註:轉換服務與協定在這邊是兩個獨立的類別)。將協定邏輯與服務邏輯分開是Twisted程式設計中常見的模式,這樣做得以更容易以不同的協定提供相同的服務,而無須重複的程式碼。
現在讓我們來看下協定的工廠類別(我們之後會看到協定的部分):
這個工廠提供了一個transform方法,使得協定的實例可用於替一個連接的用戶端來請求詩歌轉換。如果沒有這種轉換方式或轉換失敗,這個方法會回傳None。就像TransformService,協定工廠與wire-level protocol(譯註:wire-level protocol依照搜尋到的資料是指一種透過網路以八位元組串流傳送資料的格式,任何可以發送和讀取符合該資料格式的工具都可以與其它兼容的工具進行互動,而無需考慮使用什麼程式語言)無關,wire-level protocol的細節都交給協定類別本身去實現。
有一件事情要注意,我們透過xfrom_這個前綴(prefixed)方法來保護對服務的存取。這種模式你會在Twisted原始碼中找到,雖然前綴詞會有所不同以及它們通常會在從工廠分離出的物件之上。因為用戶端可以傳送任何它們想要的轉換名稱,這是一種防止用戶端程式碼在服務物件上執行惡意方法的方式。這種方式也提供了一個地方讓服務物件所提供的API 執行協定指定適配。
現在我們來看看協定的實現:
在這個協議的實現中,我們利用了Twisted透過NetstringReceiver協定支援netstrings的優點。這個父類別(譯註:NetstringReceiver)負責編碼與解碼netstrings,而我們所需要做的就是實現stringReceived方法。換句話說,stringReceived是被用戶端傳送的netstring內容所呼叫,不需要netstring編碼添加額外的位元組。這個父類別也負責緩衝傳入的位元組,直到我們有足夠的完整字串來解碼。
如果一切正常(如果不是,我們只會關閉連接),我們使用NetstringReceiver提供的sendString方法(並且最後呼叫transport.write())將轉換後的詩歌送回用戶端。該說的就這些了,我們不打算列出main函式,因為它跟我們以前看到的類似。
注意我們如何透過定義xformRequestReceived方法將傳入的位元組串流轉換為更高的抽象層級來延續Twisted模式,xformRequestReceived會將轉換的名稱與詩歌作為兩個單獨的引數來傳遞。
然後你可以像這樣針對伺服器執行測試script:
然後你應該會看到一些像這樣的輸出:
那是netstring編碼轉換過的詩歌(原文是全部大寫)。
雙向通訊的基本機制很簡單,我們在以前的用戶端與伺服器使用相同的技術去讀取與寫入資料;跟現在的唯一區別是我們是一起使用它們。當然,更複雜的協定需要更複雜的程式碼去處理位元組串流與格式化傳出的訊息。這是使用像我們今天所做的現有協定實現的一個很好的原因。
一旦你開始習慣於撰寫基本的協定,看一下Twisted提供的不同協定實現會是個很好的主意。你可以先仔細閱讀twisted.protocols.basic模組並從這裡開始延伸閱讀。撰寫簡單的協定是讓你自己熟悉Twisted程式設計風格的好辦法,但在「現實」的程式中,假設有一種協議可以用於你想要使用的協議,使用現成協議的實現可能比較普遍。
最後一個我們介紹的新想法,使用一個服務物件來分離功能與協定邏輯,這在Twisted程式設計中是非常重要設計模式。雖然我們今天所建立的服務物件很簡單,但你可以想像一個現實的網路服務可能相當複雜。並且透過使服務獨立於協定層級的細節,我們可以不使用重複性的程式碼在新的協定上快速的提供相同的服務。
圖27顯示了一個透過兩種不同的協定來提供詩歌轉換的轉換伺服器(我們上面介紹的伺服器版本只有一個協定):
儘管我們在圖27中需要兩個獨立的協定工廠,它們可能只在peotocol類別屬性上有所不同,而其他地方都是相同的。這些工廠將共用相同的服務物件,只有Protocols本身需要個別實現。這就是程式碼複用。
One More Server
好啦,我們已經寫了一個Twisted伺服器,所以讓我們再寫另外一個吧,然後我們再回頭學習一些關於Deferreds的內容。在Part 9與Part 10我們介紹了詩歌轉換引擎的概念。我們最後實現的cummingsifier非常簡單,以致我們必須加入隨機例外來模擬失敗的情況。但是如果轉換引擎位於另一個伺服器,提供網路化的「詩歌轉換服務」,那麼這就存在著一個更有可能實際發生的故障原因:轉換伺服器停止。
因此,在Part 12中,我們將實現一個詩歌轉換伺服器(譯註:也就是說將提供詩歌與轉換詩歌分為兩個伺服器),然後在Part 13中,我們會更新我們的詩歌伺服器來使用這個外部轉換服務,並在這個過程中學習幾個關於Deferreds的新內容。
Designing the Protocol
到目前為止,用戶端和伺服器之間的互動僅有單向。伺服器會向用戶端傳送一首詩,而用戶端永遠不會向伺服器傳送任何東西。但轉換服務是雙向的-用戶端傳送一首詩給伺服器,然後伺服器傳送一首轉換過的詩回來。所以我們需要使用或者創造一個協議來處理這個互動。當我們在設計協定時,我們會讓伺服器支援多種轉換類型,並允許用戶端選擇使用哪種轉換方式。所以用戶端會傳送兩份訊息:轉換方法的名稱與詩歌的全文。而伺服器會回傳一份訊息,也就是轉換後的詩歌。所以,我們有了一個非常簡單的Remote Procedure Call(遠端程序呼叫)。
Twisted支援了幾個我們能用來解決這個問題的協定:XML-RPC、Perspective Broker、與AMP。
但是介紹這些功能完整的協定會讓我們太離題,所以我們會使用我們自己簡單的協定來代替。我們會讓用戶端傳送這個格式的字串(沒有括號):
<transform-name>.<text of the poem>
這邊就只有轉換方法的名稱,後面一個句點,再後面是完整的詩歌內容。然後我們會用netstring的格式對所有東西進行編碼,伺服器也會使用netstring格式送回轉換過的詩歌內容。由於netstring使用length-encoding,用戶端可以檢測出伺服器沒有送回完整的結果(也許結果在操作過程中間崩潰了)。如果你還記得,我們之前的詩歌協定無法檢測到詩歌傳輸中斷的情況。
協定設計的內容到此為止。它沒有好到會贏得什麼獎項,但對我們的目標而言已經夠好了。
The Code
讓我們看看在twisted-server-1/transformedpoetry.py中轉換伺服器的程式碼。首先,我們定義了一個TransformService類別:class TransformService(object):
def cummingsify(self, poem):
return poem.lower()
這個轉換服務目前透過cummingsify方法實現了一個與它同名的轉換。我們可以增加其他的方法來增加演算法的種類。有一件重要的事情要注意:轉換服務與我們之前看到的協定細節完全無關(譯註:轉換服務與協定在這邊是兩個獨立的類別)。將協定邏輯與服務邏輯分開是Twisted程式設計中常見的模式,這樣做得以更容易以不同的協定提供相同的服務,而無須重複的程式碼。
現在讓我們來看下協定的工廠類別(我們之後會看到協定的部分):
class TransformFactory(ServerFactory):
protocol = TransformProtocol
def __init__(self, service):
self.service = service
def transform(self, xform_name, poem):
thunk = getattr(self, 'xform_%s' % (xform_name,), None)
if thunk is None: # no such transform
return None
try:
return thunk(poem)
except:
return None # transform failed
def xform_cummingsify(self, poem):
return self.service.cummingsify(poem)
這個工廠提供了一個transform方法,使得協定的實例可用於替一個連接的用戶端來請求詩歌轉換。如果沒有這種轉換方式或轉換失敗,這個方法會回傳None。就像TransformService,協定工廠與wire-level protocol(譯註:wire-level protocol依照搜尋到的資料是指一種透過網路以八位元組串流傳送資料的格式,任何可以發送和讀取符合該資料格式的工具都可以與其它兼容的工具進行互動,而無需考慮使用什麼程式語言)無關,wire-level protocol的細節都交給協定類別本身去實現。
有一件事情要注意,我們透過xfrom_這個前綴(prefixed)方法來保護對服務的存取。這種模式你會在Twisted原始碼中找到,雖然前綴詞會有所不同以及它們通常會在從工廠分離出的物件之上。因為用戶端可以傳送任何它們想要的轉換名稱,這是一種防止用戶端程式碼在服務物件上執行惡意方法的方式。這種方式也提供了一個地方讓服務物件所提供的API 執行協定指定適配。
現在我們來看看協定的實現:
class TransformProtocol(NetstringReceiver):
def stringReceived(self, request):
if '.' not in request: # bad request
self.transport.loseConnection()
return
xform_name, poem = request.split('.', 1)
self.xformRequestReceived(xform_name, poem)
def xformRequestReceived(self, xform_name, poem):
new_poem = self.factory.transform(xform_name, poem)
if new_poem is not None:
self.sendString(new_poem)
self.transport.loseConnection()
在這個協議的實現中,我們利用了Twisted透過NetstringReceiver協定支援netstrings的優點。這個父類別(譯註:NetstringReceiver)負責編碼與解碼netstrings,而我們所需要做的就是實現stringReceived方法。換句話說,stringReceived是被用戶端傳送的netstring內容所呼叫,不需要netstring編碼添加額外的位元組。這個父類別也負責緩衝傳入的位元組,直到我們有足夠的完整字串來解碼。
如果一切正常(如果不是,我們只會關閉連接),我們使用NetstringReceiver提供的sendString方法(並且最後呼叫transport.write())將轉換後的詩歌送回用戶端。該說的就這些了,我們不打算列出main函式,因為它跟我們以前看到的類似。
注意我們如何透過定義xformRequestReceived方法將傳入的位元組串流轉換為更高的抽象層級來延續Twisted模式,xformRequestReceived會將轉換的名稱與詩歌作為兩個單獨的引數來傳遞。
A Simple Client
我們會在下個章節實現用於轉換服務的Twisted用戶端,現在我們只需要使用twisted-server-1/transform-test中的簡單script。它使用netcat程式傳送一首詩到伺服器並輸出回應(會被編碼為netstring)。假設你像這樣在11000埠執行轉換伺服器:python twisted-server-1/transformedpoetry.py --port 11000
然後你可以像這樣針對伺服器執行測試script:
./twisted-server-1/transform-test 11000
然後你應該會看到一些像這樣的輸出:
15:here is my poem,
那是netstring編碼轉換過的詩歌(原文是全部大寫)。
Discussion
這個Part介紹了一些新的想法:- 雙向通訊。
- 建立於Twisted提供的現有協定的實現。
- 使用服務物件由協議邏輯中分離出功能邏輯。
雙向通訊的基本機制很簡單,我們在以前的用戶端與伺服器使用相同的技術去讀取與寫入資料;跟現在的唯一區別是我們是一起使用它們。當然,更複雜的協定需要更複雜的程式碼去處理位元組串流與格式化傳出的訊息。這是使用像我們今天所做的現有協定實現的一個很好的原因。
一旦你開始習慣於撰寫基本的協定,看一下Twisted提供的不同協定實現會是個很好的主意。你可以先仔細閱讀twisted.protocols.basic模組並從這裡開始延伸閱讀。撰寫簡單的協定是讓你自己熟悉Twisted程式設計風格的好辦法,但在「現實」的程式中,假設有一種協議可以用於你想要使用的協議,使用現成協議的實現可能比較普遍。
最後一個我們介紹的新想法,使用一個服務物件來分離功能與協定邏輯,這在Twisted程式設計中是非常重要設計模式。雖然我們今天所建立的服務物件很簡單,但你可以想像一個現實的網路服務可能相當複雜。並且透過使服務獨立於協定層級的細節,我們可以不使用重複性的程式碼在新的協定上快速的提供相同的服務。
圖27顯示了一個透過兩種不同的協定來提供詩歌轉換的轉換伺服器(我們上面介紹的伺服器版本只有一個協定):
儘管我們在圖27中需要兩個獨立的協定工廠,它們可能只在peotocol類別屬性上有所不同,而其他地方都是相同的。這些工廠將共用相同的服務物件,只有Protocols本身需要個別實現。這就是程式碼複用。
Looking Ahead
這些改變非常適合我們的轉換伺服器。在Part 13中,我們將更新我們的詩歌用戶端來使用轉換伺服器,代替過去在用戶端中實現轉換。Suggested Exercises
- 閱讀NetstringReceiver類別的原始碼。如果用戶端傳送異常的netstring會發生什麼事情?如果用戶端傳送大型netstring會發生什麼事情?
- 創造另一個轉換演算法並把它家道轉換服務與協定工廠之中。透過修改netcat用戶端來測試它。
- 創造另一個請求詩歌轉換的協定並修改伺服器來處理兩種協定(在兩個不同的埠口)。對於兩個協定使用相同的TransformService實例。
- 如果TransformService上的方法是非同步的,程式碼需要怎樣修改(即它們回傳Deferreds)?
- 為轉換伺服器寫一個同步用戶端。
- 更新原本的用戶端與伺服器,在傳送詩歌時使用netstrings。
留言
張貼留言