Google Protocol RPC 程式庫總覽

注意:如要進行 Google Protocol RPC 驗證,您可以使用 Google Cloud Platform 主控台中的 App Engine 應用程式目前可用的驗證方法。您還必須要在 app.yaml 檔案中指定使用登入要求。App Engine 內含的 Google Protocol RPC 程式庫目前不支援其他驗證方法。

Google Protocol RPC 程式庫是在導入 HTTP 式的遠端程序呼叫 (RPC) 服務時所使用的架構。RPC 服務是各種訊息類型和遠端方法的集合,可讓外部應用程式以結構化的方式與網路應用程式互動。由於您能夠使用 Python 程式語言定義訊息與服務,因此可以輕鬆地在 App Engine 上開發 Protocol RPC 服務、測試該服務,以及調度該服務使用的資源。

雖然您可以將 Google Protocol RPC 程式庫用於任何類型的 HTTP 式 RPC 服務,但常見的用途包括:

  • 發布網路 API 以供第三方使用
  • 建立結構化的 Ajax 後端
  • 複製長期執行的伺服器通訊

您可以透過包含任一數量之已宣告遠端方法的單一 Python 類別,來定義 Google Protocol RPC 服務。每個遠端方法都會接受一組特定參數作為要求,並傳回特定回應。這些要求與回應參數屬於使用者定義的類別,一般稱為訊息。

Google Protocol RPC 的 Hello World

本節提供的範例是非常簡單的服務定義,而該服務會接收來自遠端用戶端的訊息。這個訊息包含使用者的名稱 (HelloRequest.my_name),還會把問候語傳回給該使用者 (HelloResponse.hello):

from protorpc import messages
from protorpc import remote
from protorpc.wsgi import service

package = 'hello'

# Create the request string containing the user's name
class HelloRequest(messages.Message):
    my_name = messages.StringField(1, required=True)

# Create the response string
class HelloResponse(messages.Message):
    hello = messages.StringField(1, required=True)

# Create the RPC service to exchange messages
class HelloService(remote.Service):

    @remote.method(HelloRequest, HelloResponse)
    def hello(self, request):
        return HelloResponse(hello='Hello there, %s!' % request.my_name)

# Map the RPC service and path (/hello)
app = service.service_mappings([('/hello.*', HelloService)])

開始使用 Google Protocol RPC

本節說明如何利用在 App Engine 入門指南 (Python) 中開發的留言板應用程式,來開始使用 Google Protocol RPC。使用者可以造訪線上留言板 (Python SDK 中也有這項示範)、撰寫留言,以及瀏覽所有使用者的留言。使用者可以直接與介面互動,但網路應用程式無法輕易存取該資訊。

此時就是 Protocol RPC 能派上用場的時候。在本教學課程中,我們會將 Google Protocol RPC 套用到這個基本留言板,讓其他網路應用程式能存取留言板資料。本教學課程僅說明如何使用 Google Protocol RPC 擴充留言板功能,後續要執行的操作需由您自行決定。舉例來說,您可以編寫相關工具,用來讀取使用者張貼的訊息,並且製作每日留言的時間序列圖表。您使用 Protocol RPC 的方式,會因應用程式的不同而異;重點在於,Google Protocol RPC 會大幅擴展您對應用程式資料的運用方式。

一開始,您將建立檔案 postservice.py,而該檔案會實作遠端方法來存取留言板應用程式資料儲存庫中的資料。

建立 PostService 模組

要開始使用 Google Protocol RPC 的第一個步驟,就是在應用程式目錄中建立名為 postservice.py 的檔案。您將使用這個檔案定義新的服務,而這個服務會實作兩個方法:一個是遠端張貼資料,另一個是遠端取得資料。

此時還不需要新增任何內容到這個檔案,但您在後續章節中定義的所有程式碼都會放到這個檔案。在下一節中,您將建立訊息,代表張貼到留言板應用程式資料儲存庫的記事。

處理訊息

訊息是 Google Protocol RPC 中所用的基礎資料類型。訊息的定義方式,就是宣告一個繼承自 Message基本類別的類別。接著,您需要指定會對應到訊息中每個欄位的類別屬性。

舉例來說,留言板服務可讓使用者張貼記事。如果您還沒有在應用程式目錄中建立名為 postservice.py 的檔案,請立刻這麼做,然後在必要時查看留言板教學課程。在該教學課程中,留言板問候語在放入資料儲存庫中時,會採用 guestbook.Greeting 類別。PostService 也會使用 Greeting 類別,將留言儲存在資料儲存庫中。那我們就來定義一個代表這種記事的訊息:

from protorpc import messages

class Note(messages.Message):

    text = messages.StringField(1, required=True)
    when = messages.IntegerField(2)

我們用兩個欄位 (textwhen) 來定義記事訊息,每個欄位都有特定的類型。文字欄位是 Unicode 字串,代表使用者張貼到留言板頁面的內容。when 欄位是個整數,代表留言的時間戳記。我們在定義字串時,也會:

  • 為每個欄位提供一個專屬的數值 (text1,而 when2),讓基本的網路通訊協定能用來識別欄位。
  • text 設定為必要欄位。這些欄位預設是選用欄位,您可以藉由設定 required=True 將它們標示為必要欄位。您必須為必要欄位設定一個值,訊息才會初始化。Google Protocol RPC 服務的方法只接受已正確初始化的訊息。

您可以使用 Note 類別的建構函式,來設定欄位的值:

# Import the standard time Python library to handle the timestamp.
import time

note_instance = Note(text=u'Hello guestbook!', when=int(time.time()))

您也可以讀取和設定訊息中的值,就像讀取和設定一般 Python 屬性值一樣。以下範例說明如何變更訊息:

print note_instance.text
note_instance.text = u'Good-bye guestbook!'
print note_instance.text
輸出內容如下:
Hello guestbook!
Good-bye guestbook!

定義服務

「服務」是繼承自 Service 基本類別的類別定義。您可以使用 remote 修飾符來表示服務的遠端方法。服務的每個方法都會接受單一訊息來做為自己的參數,並傳回單一訊息來做為自己的回應。

以下範例說明如何定義 PostService 的第一個方法。請將以下內容新增到您的 postservice.py 檔案中:

import datetime

from protorpc import message_types
from protorpc import remote

import guestbook

class PostService(remote.Service):

    # Add the remote decorator to indicate the service methods
    @remote.method(Note, message_types.VoidMessage)
    def post_note(self, request):

        # If the Note instance has a timestamp, use that timestamp
        if request.when is not None:
            when = datetime.datetime.utcfromtimestamp(request.when)

        # Else use the current time
        else:
            when = datetime.datetime.now()
        note = guestbook.Greeting(content=request.text, date=when, parent=guestbook.guestbook_key)
        note.put()
        return message_types.VoidMessage()

remote 修飾符有兩個參數:

  • 預期的要求類型:post_note() 方法接受 Note 執行個體做為要求類型。
  • 預期的回應類型:Google Protocol RPC 程式庫隨附名為 VoidMessage (在 protorpc.message_types 模組中) 的內建類型,它的定義是沒有欄位的訊息。這代表 post_note() 訊息不會將任何有用的內容傳回給呼叫者。如果它成功傳回訊息,系統就會將該訊息視為已張貼。

Note.when是選用欄位,因此呼叫者可能沒有設定。在這種情況下,when 的值會設定為 None。當 Note.when 設定為 None 時,post_note() 會以收到訊息的時間來建立時間戳記。

回應訊息是由遠端方法實例化的,並且會成為遠端方法的傳回值。

註冊服務

您可以利用 protorpc.wsgi.service 程式庫,將新的服務發布為 WSGI 應用程式。請在應用程式目錄中建立名為 services.py 的新檔案,並新增下列程式碼以建立服務:

from protorpc.wsgi import service

import postservice

# Map the RPC service and path (/PostService)
app = service.service_mappings([('/PostService', postservice.PostService)])

現在,請將下列處理常式新增至 app.yaml 檔案中現有的全部接收項目上方。

- url: /PostService.*
  script: services.app
- url: .*
  script: guestbook.app

透過指令列測試服務

既然您已經建立服務,現在就能利用 curl 或類似的指令列工具來測試服務。

# After starting the development web server:
# NOTE: ProtoRPC always expect a POST.
% curl -H \
   'content-type:application/json' \
   -d '{"text": "Hello guestbook!"}'\
   http://localhost:8080/PostService.post_note

空的 JSON 回應表示記事已成功張貼,您可以透過瀏覽器前往留言板應用程式,從中查看記事 (http://localhost:8080/)。

新增訊息欄位

既然我們可以將訊息張貼到 PostService,那就來新增能從 PostService 取得訊息的方法吧。首先,我們會在 postservice.py 定義要求訊息,為要求訊息定義某些預設值及新的 enum 欄位,而 enum 欄位將指示伺服器如何將回應中的記事排序。請在您先前定義的 PostService 類別「上方」 進行定義:

class GetNotesRequest(messages.Message):
    limit = messages.IntegerField(1, default=10)
    on_or_before = messages.IntegerField(2)

    class Order(messages.Enum):
        WHEN = 1
        TEXT = 2
    order = messages.EnumField(Order, 3, default=Order.WHEN)

當這個訊息傳送至 PostService 時,會要求在特定日期之前或當日,依特定順序排列的幾個記事。limit 欄位表示要擷取的記事數量上限。如果您沒有明確設定,limit 會預設為 10 個記事 (正如依 default=10 關鍵字引數所示)。

order 欄位會導入 EnumField 類別,而該類別會在某個欄位的值只能為數量有限的已知符號值時,啟用 enum 欄位類型,。在這種情況下,enum 會指示伺服器如何在回應中排序記事。如要定義 enum 值,請建立 Enum 類別的子類別。您必須為每個類型的名稱指派唯一的數值,每個數值都會轉換為 enum 類型的執行個體,且可從該類別中存取。

print 'Enum value Order.%s has number %d' % (GetNotesRequest.Order.WHEN.name,
                                             GetNotesRequest.Order.WHEN.number)

每個 enum 值都有特殊的特性,可輕易地轉換成自己的名稱或數字。因此您不必存取名稱和數字屬性,只要把每個值轉換成字串或整數即可:

print 'Enum value Order.%s has number %d' % (GetNotesRequest.Order.WHEN,
                                             GetNotesRequest.Order.WHEN)

列舉欄位的宣告方式與其他欄位類似,不過第一個參數必須是列舉類型,接著才是欄位數值。列舉欄位也可以包含預設值。

定義回應訊息

現在要來定義 get_notes() 回應訊息。回應必須是 Note 訊息的集合,訊息可以包含其他訊息。以下方定義的 Notes.notes 欄位為例,我們提供 Note 類別來做為 messages.MessageField 建構函式的第一個參數 (在欄位數字之前),代表這是幾個訊息的集合:

class Notes(messages.Message):
    notes = messages.MessageField(Note, 1, repeated=True)

正如 repeated=True 關鍵字引數所示,Notes.notes 欄位也是重複欄位。重複欄位的值必須是其宣告的欄位類型清單。在這種情況下,Notes.notes 必須是 Note 執行個體的清單。清單會自動建立,且無法指派為 None。

以下舉例說明如何建立 Note 物件:

response = Notes(notes=[Note(text='This is note 1'),
                        Note(text='This is note 2')])
print 'The first note is:', response.notes[0].text
print 'The second note is:', response.notes[1].text

實作 get_notes

現在,我們可以將 get_notes() 方法新增到 PostService 類別中:

import datetime
import time
from protorpc import remote

class PostService(remote.Service):
    @remote.method(GetNotesRequest, Notes)
    def get_notes(self, request):
        query = guestbook.Greeting.query().order(-guestbook.Greeting.date)

        if request.on_or_before:
            when = datetime.datetime.utcfromtimestamp(
                request.on_or_before)
            query = query.filter(guestbook.Greeting.date <= when)

        notes = []
        for note_model in query.fetch(request.limit):
            if note_model.date:
                when = int(time.mktime(note_model.date.utctimetuple()))
            else:
                when = None
            note = Note(text=note_model.content, when=when)
            notes.append(note)

        if request.order == GetNotesRequest.Order.TEXT:
            notes.sort(key=lambda note: note.text)

        return Notes(notes=notes)