此篇就對 Cage & Ping wedding 中實作專案中最為重要的 backend API (endpoint API) 部份進行簡單的說明,每一個 Google App Engine Service 實作的細節會在後序的篇幅中介紹
weddingcnp 系例傳送門
- weddingcnp via GCP 簡介
- weddingcnp via GCP - 1. 專案架構切分
- weddingcnp via GCP - 2. 前端頁面設計實作
- weddingcnp via GCP - 3. endpointAPI 設計實作
- weddingcnp 前端 vue.js 設計實作
- weddingcnp edm 寄送 sendgrid
endpointAPI 設計實作
Cage & Ping wedding 的網站大至上可以區分為靜態頁面(frontend Service, Golang) 及負責資料收集的 API (endpoints Service, Python). Google Endpoints API 是架構在 GAE 上的一個 RPC (remote procedure call) 服務。由於 endpoints API 是基於 ProtoRPC remote.Service 實作出來的,所以在實作我們的方法時也是依照 protorpc 的方式來定義,小弟在寫 Cage & Ping wedding 這一個網站的時候 Endpoints API 還是 1.0,所以這篇文章還是以 Endpoints API 1.0 來說明
endpoint API demo project
小弟我建立一個比較簡單的範例來說明
https://github.com/cage1016/endpoint-api-demo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
| # git clone repo
$ git clone [email protected]:cage1016/endpoint-api-demo.git
# install GAE python requirement packages
$ pip install -r requirements.txt -t lib
Collecting arrow==0.8.0 (from -r requirements.txt (line 1))
Collecting python-dateutil (from arrow==0.8.0->-r requirements.txt (line 1))
Downloading python_dateutil-2.6.1-py2.py3-none-any.whl (194kB)
100% |████████████████████████████████| 194kB 1.2MB/s
Collecting six>=1.5 (from python-dateutil->arrow==0.8.0->-r requirements.txt (line 1))
Using cached six-1.10.0-py2.py3-none-any.whl
Installing collected packages: six, python-dateutil, arrow
Successfully installed arrow python-dateutil-2.2 six-1.10.0
...
# run GAE python locally
$ dev_appserver.py app.yaml
INFO 2017-07-12 13:30:19,063 devappserver2.py:116] Skipping SDK update check.
INFO 2017-07-12 13:30:19,706 api_server.py:312] Starting API server at: http://localhost:51100
INFO 2017-07-12 13:30:19,710 dispatcher.py:226] Starting module "default" running at: http://localhost:8080
INFO 2017-07-12 13:30:19,711 admin_server.py:116] Starting admin server at: http://localhost:8000
# visit http://localhost:8080
|
完全上述步驟後透過瀏覽器訪問 http://localhost:8080/_ah/api/explorer
就可以得到圖一中類似 Google API explorer 風格的本地測試頁面, 這是一個非常方便的工具,可以讓自己在本地透過瀏覽器直接操作自己定義的 endpointAPI
專案架構
1
2
3
4
5
6
7
8
9
10
11
12
| .
├── apis // endpoint api packages
│ ├── __init__.py
│ └── books.py
├── lib
├── app.py // 主程式進入點
├── app.yaml // GAE 配置檔
├── appengine_config.py // lib 載入配置
├── models.py // GAE Datastore 檔
├── requirements.txt // GAE python 引用資源庫
├── settings.py // 專案設定檔
└── utils.py // helpers function
|
一個 GAE endpoint API Service 大至上進行簡單的配置可以執行了
app.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| module: default
runtime: python27
api_version: 1
threadsafe: yes
handlers:
- url: /_ah/spi/.*
script: app.API
secure: always
libraries:
- name: endpoints
version: latest
|
app.yaml 配置上的重點為在 handlers 區塊加入 /_ah/spi/.*
這一個 route 並將 route 導至 endpoints API server
app.py
1
2
3
4
5
6
7
| import endpoints
from apis import books
API = endpoints.api_server([
books.BooksAPI
])
|
endpoints api server 由 endpoints.api_server([])
建立, 其中的參數是實作 protorpc.remote.Service
類別的 API, 目前我們只有實作一個 books.BooksAPI
所以只有填入一個
settings.py
1
2
3
4
5
6
7
8
| ...
books_api = endpoints.api(name='dummy',
version='v1',
description='dummy API',
allowed_client_ids=[CLIENT_ID,
endpoints.API_EXPLORER_CLIENT_ID],
scopes=[endpoints.EMAIL_SCOPE])
|
在我們定義的 protorpc.remote.Service
API 上加一個 Decorate 來描述我們的 API(API 名稱、版號、說明、允許呼叫的 GCP CLIENT_ID, scopes 等等)
@util.positional(2)
def api(name, version, description=None, hostname=None, audiences=None,
scopes=None, allowed_client_ids=None, canonical_name=None,
auth=None, owner_domain=None, owner_name=None, package_path=None,
frontend_limits=None, title=None, documentation=None, auth_level=None):
api/books.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
| @books_api.api_class(resource_name="books")
class BooksAPI(remote.Service):
"""
Books API
"""
@endpoints.method(BOOKS_LIST_RESOURCE, BookForms, path="books",
http_method="GET", name="list")
def listBooks(self, request):
"""List books"""
...
@endpoints.method(BOOKS_CREATE_RESOURCE, BookForm, path="books",
http_method="POST", name="post")
def addBook(self, request):
"""Add books"""
...
@endpoints.method(BOOKS_DELETE_RESOURCE, BooleanMessage, path="books/{websafeKey}",
http_method="DELETE", name="delete")
def deleteBook(self, request):
"""Delete book"""
...
@endpoints.method(BOOKS_UPDATE_RESOURCE, BookForm, path="books/{websafeKey}",
http_method="PUT", name="put")
def updateBook(self, request):
"""Update book"""
...
|
在定義 resource 下的方法時依然使用 Decorate 的方式來對方法進行設置 (method name, path, http method,
cache control, scopes, audiences, client ids and auth_level 等)
@util.positional(2)
def method(request_message=message_types.VoidMessage,
response_message=message_types.VoidMessage,
name=None,
path=None,
http_method=‘POST’,
cache_control=None,
scopes=None,
audiences=None,
allowed_client_ids=None,
auth_level=None):
設置完 endpointAPI 的基本架構,剩下的部份就是 GAE Python 的基本操作,定義 Datastore module、對 Datastore 進行 CRUD 的操作。有興趣的朋友可以把專案 clone 下來玩看看,並試著自己俢改修改會更有感覺。
endpointAPI 其他的功能還有很多,可以搭配 gRPC一起使用、建立 Android/iOS 的 frameworks 等,詳細的說明可以見官方的文件 How-to Guides
curl
透過瀏覽器操作過後,透過 curl 操作的結果也是一樣的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
| $ curl http://localhost:8080/_ah/api/dummy/v1/books
{
"items": [
{
"name": "Shel Silverstein",
"title": "The Giving Tree",
"websafeKey": "aghkZXZ-Tm9uZXIZCxIEQm9vayIPVGhlIEdpdmluZyBUcmVlDA"
},
{
"name": "Harper Lee",
"title": "To Kill a Mockingbird",
"websafeKey": "aghkZXZ-Tm9uZXIfCxIEQm9vayIVVG8gS2lsbCBhIE1vY2tpbmdiaXJkDA"
},
{
"name": "dfad",
"title": "fdafd",
"websafeKey": "aghkZXZ-Tm9uZXIPCxIEQm9vayIFZmRhZmQM"
}
]
}
|