在 Google 資料上運作

Eric Dataelman,Google Data API 團隊
2009 年 2 月

簡介

「Ruby 清單上的 Ruby 在哪裡?」

受開發人員的渴望和 Ruby onrail (RoR) 長久的潮流所啟發,我的同事 Jeff Fisher 從 Mount Doom 的火熱深處得以建構出 Ruby 公用程式庫。提醒您,這不是完整的程式碼用戶端程式庫,但可處理驗證和基本 XML 操控等基礎知識。此外,您也需要使用 REXML 模組和 XPath 直接與 Atom 資訊提供搭配使用。

目標對象

本文的適用對象,是有意使用 Ruby (特別是 Ruby on Ram) 存取 Google Data API 的開發人員。 本文假設讀者對於 Ruby 程式設計語言和 Railation 網站開發架構有一定程度的瞭解。針對大多數範例,我會專注於 Documents List API,但相同的概念也可以套用至任何 Data API

開始使用

需求條件

  • 下載 Ruby 1.8.6 修補程式等級 114 以上版本
  • 下載 RubyGems 1.3.1 以上版本
  • Railst 2.2.2 以上版本下載

安裝 Google Data Ruby 公用程式庫

如要取得程式庫,您可以直接從專案託管下載程式庫來源,或安裝 gem:

sudo gem install gdata

提示:為確保正確評估,請在執行 gem list --local 後確認寶石已正確安裝。

驗證

ClientLogin

ClientLogin 可讓應用程式以程式輔助的方式將使用者登入 Google 或 G Suite 帳戶。驗證使用者憑證之後,Google 會發出驗證權杖,供後續 API 要求參考。這組憑證會在指定時間內有效 (視您使用的 Google 服務而定)。基於安全考量,以及為使用者提供最佳體驗,您只應在開發已安裝的桌面應用程式時使用 ClientLogin。針對網路應用程式,建議使用 AuthSubOAuth

Ruby 程式庫為每個 API 都有一個用戶端類別。例如,使用以下程式碼片段登入 user@gmail.com 到 Documents List Data API:

client = GData::Client::DocList.new
client.clientlogin('user@gmail.com', 'pa$$word')

The YouTube Data API would be:

client = GData::Client::YouTube.new
client.clientlogin('user@gmail.com', 'pa$$word')

請參閱已實作服務類別的完整清單。 如果服務沒有用戶端類別,請使用 GData::Client::Base 類別。舉例來說,以下程式碼會強制使用者使用 G Suite 帳戶登入。

client_login_handler = GData::Auth::ClientLogin.new('writely', :account_type => 'HOSTED')
token = client_login_handler.get_token('user@example.com', 'pa$$word', 'google-RailsArticleSample-v1')
client = GData::Client::Base.new(:auth_handler => client_login_handler)

注意:根據預設,程式庫會將 HOSTED_OR_GOOGLE 用於 accountType。可能的值為 HOSTED_OR_GOOGLEHOSTEDGOOGLE

使用 ClientLogin 的缺點之一,就是應用程式在登入失敗時,可以傳送人機驗證 (Captcha) 驗證問題。如果發生此情況,您可以呼叫 clientlogin() 方法及其額外參數來處理錯誤:client.clientlogin(username, password, captcha_token, captcha_answer)。如要進一步瞭解如何處理人機驗證 (Captcha),請參閱完整的安裝版應用程式驗證說明文件。

AuthSub

產生 AuthSubRequest 網址

scope = 'http://www.google.com/calendar/feeds/'
next_url = 'http://example.com/change/to/your/app'
secure = false  # set secure = true for signed AuthSub requests
sess = true
authsub_link = GData::Auth::AuthSub.get_url(next_url, scope, secure, sess)

上一個程式碼區塊會在 authsub_link 中建立下列網址:

https://www.google.com/accounts/AuthSubRequest?next=http%3A%2F%2Fexample.com%2Fchange%2Fto%2Fyour%2Fapp&scope=http%3A%2F%2Fwww.google.com%2Fcalendar%2Ffeeds%2F&session=1&secure=0

您也可以使用用戶端物件的 authsub_url 方法。每個服務類別皆設定了預設的 authsub_scope 屬性,因此您不需要自行指定屬性。

client = GData::Client::DocList.new
next_url = 'http://example.com/change/to/your/app'
secure = false  # set secure = true for signed AuthSub requests
sess = true
domain = 'example.com'  # force users to login to a G Suite hosted domain
authsub_link = client.authsub_url(next_url, secure, sess, domain)

上一個程式碼區塊會建立下列網址:

https://www.google.com/accounts/AuthSubRequest?next=http%3A%2F%2Fexample.com%2Fchange%2Fto%2Fyour%2Fapp&scope=http%3A%2F%2Fdocs.google.com%2Ffeeds%2F&session=1&secure=0&hd=example.com

將一次性使用憑證升級至工作階段符記

在使用者授予資料存取權後,AuthSub 會將使用者重新導向回 http://example.com/change/to/your/app?token=SINGLE_USE_TOKEN。請注意,這個網址只是 next_url,當中包含單次使用憑證,做為查詢參數附加。

接著,將一次性使用權杖交換成長期工作階段符記:

client.authsub_token = params[:token] # extract the single-use token from the URL query params
session[:token] = client.auth_handler.upgrade()
client.authsub_token = session[:token] if session[:token]

安全 AuthSub 非常類似,唯一的做法是在升級權杖之前設定私密金鑰:

PRIVATE_KEY = '/path/to/private_key.pem'

client.authsub_token = params[:token]
client.authsub_private_key = PRIVATE_KEY
session[:token] = client.auth_handler.upgrade()
client.authsub_token = session[:token] if session[:token]

注意:如要使用安全憑證,請務必在要求單次使用憑證時設定 secure=true。請參閱上方的產生 AuthSubRequest 網址一節。

管理權杖

AuthSub 提供兩個其他處理常式:AuthSubTokenInfoAuthSub 撤銷 Token 來管理憑證。AuthSubTokenInfo 可用於檢查憑證的有效性。AuthSubRevokeToken 可讓使用者決定其資料存取權。您的應用程式應使用 AuthSubRevokeToken 做為最佳做法。Ruby 程式庫支援這兩種方法。

如何查詢憑證的中繼資料:

client.auth_handler.info

如何撤銷工作階段符記:

client.auth_handler.revoke

如需 AuthSub 的完整資訊,請參閱網路應用程式的 AuthSub 驗證說明文件。

OAuth

撰寫本文時,系統尚未將 OAuth 新增至 GData::Auth 模組。

使用 Azure oauth-plugin 或 Ruby oauth Gem 時,在公用程式庫中使用 OAuth 相對簡單。無論是哪一種情況,您都必須建立 GData::HTTP::Request 物件並傳遞至每個程式庫產生的 Authorization 標頭。

存取資訊提供

GET (擷取資料)

設定用戶端物件後,請使用其 get() 方法查詢 Google 資料動態饋給。XPath 可用於擷取特定的 Atom 元素。擷取使用者 Google 文件的範例如下:

feed = client.get('http://docs.google.com/feeds/documents/private/full').to_xml

feed.elements.each('entry') do |entry|
  puts 'title: ' + entry.elements['title'].text
  puts 'type: ' + entry.elements['category'].attribute('label').value
  puts 'updated: ' + entry.elements['updated'].text
  puts 'id: ' + entry.elements['id'].text
  
  # Extract the href value from each <atom:link>
  links = {}
  entry.elements.each('link') do |link|
    links[link.attribute('rel').value] = link.attribute('href').value
  end
  puts links.to_s
end

POST (建立新資料)

使用用戶端的 post() 方法在伺服器上建立新資料。以下範例會將 new_writer@example.com 新增為 ID 為 doc_id 的文件的協作者。

# Return documents the authenticated user owns
feed = client.get('http://docs.google.com/feeds/documents/private/full/-/mine').to_xml
entry = feed.elements['entry']  # first <atom:entry>

acl_entry = <<-EOF
<entry xmlns="http://www.w3.org/2005/Atom" xmlns:gAcl='http://schemas.google.com/acl/2007'>
  <category scheme='http://schemas.google.com/g/2005#kind'
    term='http://schemas.google.com/acl/2007#accessRule'/>
  <gAcl:role value='writer'/>
  <gAcl:scope type='user' value='new_writer@example.com'/>
</entry>
EOF

# Regex the document id out from the full <atom:id>.
# http://docs.google.com/feeds/documents/private/full/document%3Adfrk14g25fdsdwf -> document%3Adfrk14g25fdsdwf
doc_id = entry.elements['id'].text[/full\/(.*%3[aA].*)$/, 1]
response = client.post("http://docs.google.com/feeds/acl/private/full/#{doc_id}", acl_entry)

PUT (更新資料)

如要更新伺服器上的資料,請使用用戶端的 put() 方法。以下範例將會更新文件的標題。 並假設您有上一個查詢的資訊提供。

entry = feed.elements['entry'] # first <atom:entry>

# Update the document's title
entry.elements['title'].text = 'Updated title'
entry.add_namespace('http://www.w3.org/2005/Atom')
entry.add_namespace('gd','http://schemas.google.com/g/2005')

edit_uri = entry.elements["link[@rel='edit']"].attributes['href']
response = client.put(edit_uri, entry.to_s)

刪除

如要從伺服器中刪除 <atom:entry> 或其他資料,請使用 delete() 方法。 以下範例會刪除文件。程式碼假設您有先前查詢的文件。

entry = feed.elements['entry'] # first <atom:entry>
edit_uri = entry.elements["link[@rel='edit']"].attributes['href']
client.headers['If-Match'] = entry.attribute('etag').value  # make sure we don't nuke another client's updates
client.delete(edit_uri)

建立新的鐵路應用程式

通常在建立新的 Map 應用程式時,需要先執行 Scaffold 產生器來建立 MVC 檔案。之後,資料庫就會執行 rake db:migrate 來設定資料庫資料表。不過,由於我們的應用程式會向 Google Documents List API 查詢資料,因此幾乎不需要一般鷹架或資料庫。請改為建立新的應用程式和簡易控制器:

rails doclist
cd doclist
ruby script/generate controller doclist

並對 config/environment.rb 進行下列變更:

config.frameworks -= [ :active_record, :active_resource, :action_mailer ]
config.gem 'gdata', :lib => 'gdata'

第一行會從應用程式取消掛載 ActiveRecord。第二行會在啟動時載入 gdata 寶石。

最後,我選擇將預設路徑 (/) 連結至 DoclistController 中的 documents 動作。在 config/routes.rb 中加入這一行:

map.root :controller => 'doclist', :action => 'all'

啟動控制器

由於我們並未產生鷹架,因此請在 app/controllers/doclist_controller.rb 中的 DoclistController 中手動新增名為 all 的動作。

class DoclistController < ApplicationController
  def all
    @foo = 'I pity the foo!'
  end
end

並在 app/views/doclist/ 下建立 all.html.erb

<%= @foo %>

啟動網路伺服器並開始開發

您現在應該可以叫用 ruby script/server 來啟動預設網路伺服器。如果一切順利,將瀏覽器指向 http://localhost:3000/ 時,應該會顯示「I pity the foo!」。

提示:別忘了移除或重新命名 public/index.html

完成工作後,請查看我最後的 DoclistControllerApplicationController 做為 DocList Manager 專案的肉類。建議您也查看 ContactsController,其會處理對 Google Contacts API 的呼叫。

結語

建立 Google Datarail 應用程式的最困難部分,就是設定 Rail!不過,即將完成應用程式的部署。因此,我們強烈建議 Apache 的 mod_rails。設定、安裝和執行程序非常簡單。您很快就能開始使用。

資源

附錄

範例

DocList Manager 是完整的 Ruby onrail 範例,用於示範本文所討論的主題。完整原始碼可從專案託管取得。