您好,登錄后才能下訂單哦!
本篇內容主要講解“Scala:Twitter API和Scala交互的方法”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Scala:Twitter API和Scala交互的方法”吧!
在撰寫本文時,夏季即將結束,新的學年就要開始,Twitter 的服務器上不斷涌現出世界各地的網蟲和非網蟲們發布的更新。對于我們很多身在北美的人來說,從海灘聚會到足球,從室外娛樂到室內項目,各種各樣的想法紛至沓來。為了跟上這種形勢,是時候重訪 Scitter 這個用于訪問 Twitter 的 Scala 客戶機庫了。
如果 到目前為止 您一直緊隨 Scitter 的開發,就會知道,這個庫現在能夠利用各種不同的 Twitter API 查看用戶的好友、追隨者和時間線,以及其他內容。但是,這個庫還不具備發布狀態更新的能力。在這最后一篇關于 Scitter 的文章中,我們將豐富這個庫的功能,增加一些有趣的內容(終止和評價)功能和重要方法 update()、show() 和 destroy()。在此過程中,您將了解更多關于 Twitter API 的知識,它與 Scala 之間的交互如何,您還將了解如何克服兩者之間不可避免的編程挑戰。
注意,當您看到本文的時候,Scitter 庫將位于一個 公共源代碼控制庫 中。當然,我還將在本文中包括 源代碼,但是要知道,源代碼庫可能發生改變。換句話說,項目庫中的代碼與您在這里看到的代碼可能略有不同,或者有較大的不同。
POST 到 Twitter
到目前為止,我們的 Scitter 開發主要集中于一些基于 HTTP GET
的操作,這主要是因為這些調用非常容易,而我想輕松切入 Twitter API。將 POST
和 DELETE
操作添加到庫中對于可見性來說邁出了重要一步。到目前為止,可以在個人 Twitter 帳戶上運行單元測試,而其他人并不知道您要干什么。但是,一旦開始發送更新消息,那么全世界都將知道您要運行 Scitter 單元測試。
如果繼續測試 Scitter,那么需要在 Twitter 上創建自己的 “測試” 帳戶。(也許用 Twitter API 編程的最大缺點是沒有任何合適的測試或模擬工具。)
目前的進展
在開始著手這個庫的新的 UPDATE
功能之前,我們來回顧一下到目前為止我們已經創建的東西。(我不會提供完整的源代碼清單,因為 Scitter 已經開始變得過長,不便于全部顯示。但是,可以在閱讀本文時,從另一個窗口查看 代碼。)
大致來說,Scitter 庫分為 4 個部分:
來回發送的請求和響應類型(User
、Status
等),包含在 API 中;它們被建模為 case 類。
OptionalParam
類型,同樣在 API 中的某些地方;也被建模為 case 類,這些 case 類繼承基本的 OptionalParam
類型。
Scitter
對象,用于通信基礎和對 Twitter 的匿名(無身份驗證)訪問。
Scitter
類,存放一個用戶名和密碼,用于訪問給定 Twitter 帳戶時進行驗證。
注意,在這最后一篇文章中,為了使文件大小保持在相對合理的范圍內,我將請求/響應類型分開放到不同的文件中。
終止和評價
那么,現在我們清楚了目標。我們將通過實現兩個 “只讀” Twitter API 來達到目標:end_session
API(結束用戶會話)和 rate_limit_status
API(描述在某一特定時段內用戶帳戶還剩下多少可用的 post)。
end_session
API 與它的同胞 verify_credentials
相似,也是一個非常簡單的 API:只需用一個經過驗證的請求調用它,它將 “結束” 當前正在運行的會話。在 Scitter 類上實現它非常容易,如清單 1 所示:
清單 1. 在 Scitter 上實現 end_session
package com.tedneward.scitter { import org.apache.commons.httpclient._, auth._, methods._, params._ import scala.xml._ // ... class Scitter { /** * */ def endSession : Boolean = { val (statusCode, statusBody) = Scitter.execute("http://twitter.com/account/end_session.xml", username, password) statusCode == 200 } } } |
好吧,我失言了。也不是那么容易。
POST
和我們到目前為止用過的 Twitter API 中的其他 API 不一樣,end_session
要求傳入的消息是用 HTTP POST
語義發送的。現在,Scitter.execute
方法做任何事情都是通過 GET
,這意味著需要將那些期望 GET
的 API 與那些期望 POST
的 API 區分開來。
現在暫不考慮這一點,另外還有一個明顯的變化:POST
的 API 調用還需將名稱/值對傳遞到 execute()
方法中。(記住,在其他 API 調用中,若使用 GET
,則所有參數可以作為查詢參數出現在 URL 行;若使用 POST
,則參數出現在 HTTP 請求的主體中。)在 Scala 中,每當提到名稱/值對,自然會想到 Scala Map
類型,所以在考慮建模作為 POST
一部分發送的數據元素時,最容易的方法是將它們放入到一個 Map[String,String]
中并傳遞。
例如,如果將一個新的狀態消息傳遞給 Twitter,需要將這個不超過 140 個字符的消息放在一個名稱/值對 status
中,那么應該如清單 2 所示:
清單 2. 基本 map 語法
val map = Map("status" -> message) |
在此情況下,我們可以重構 Scitter.execute()
方法,使之用 一個 Map
作為參數。如果 Map
為空,那么可以認為應該使用 GET
而不是 POST
,如清單 3 所示:
清單 3. 重構 execute()
private[scitter] def execute(url : String) : (Int, String) = execute(url, Map(), "", "") private[scitter] def execute(url : String, username : String, password : String) : (Int, String) = execute(url, Map(), username, password) private[scitter] def execute(url : String, dataMap : Map[String,String]) : (Int, String) = execute(url, dataMap, "", "") private[scitter] def execute(url : String, dataMap : Map[String,String], username : String, password : String) = { val client = new HttpClient() val method = if (dataMap.size == 0) { new GetMethod(url) } else { var m = new PostMethod(url) val array = new Array[NameValuePair](dataMap.size) var pos = 0 dataMap.elements.foreach { (pr) => pr match { case (k, v) => array(pos) = new NameValuePair(k, v) } pos += 1 } m.setRequestBody(array) m } method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, new DefaultHttpMethodRetryHandler(3, false)) if ((username != "") && (password != "")) { client.getParams().setAuthenticationPreemptive(true) client.getState().setCredentials( new AuthScope("twitter.com", 80, AuthScope.ANY_REALM), new UsernamePasswordCredentials(username, password)) } client.executeMethod(method) (method.getStatusLine().getStatusCode(), method.getResponseBodyAsString()) } |
execute()
方法最大的變化是引入了 Map[String,String]
參數,以及與它的大小有關的 “if” 測試。該測試決定是處理 GET
請求還是 POST
請求。由于 Apache Commons HttpClient
要求 POST
請求的主體放在 NameValuePairs
中,因此我們使用 foreach()
調用遍歷 map 的元素。我們以二元組 pr 的形式傳入 map 的鍵和值,并將它們分別提取到本地綁定變量 k 和 v,然后使用這些值作為 NameValuePair
構造函數的構造函數參數。
我們還可以使用 PostMethod
上的 setParameter(name, value)
API 更輕松地做這些事情。出于教學的目的,我選擇了清單 3 中的方法:以表明 Scala 數組和 Java 數組一樣,仍然是可變的,即使數組引用被標記為 val 仍是如此。記住,在實際代碼中,對于每個 (k,v) 元組,使用 PostMethod
上的 setParameter(name, value)
方法要好得多。
還需注意,對于 if/else 返回的 “method” 對象的類型,Scala 編譯器會進行 does the right thing 類型推斷。由于 Scala 可以看到 if/else 返回的是 GetMethod
還是 PostMethod
對象,它會選擇最接近的基本類型 HttpMethodBase
作為 “method” 的返回類型。這也意味著,在 execute()
方法的其余部分中,HttpMethodBase
中的任何不可用方法都是不可訪問的。幸運的是,我們不需要它們,所以至少現在沒有問題。
清單 3 中的實現的背后還潛藏著最后一個問題,這個問題是由這樣一個事實引起的:我選擇了使用 Map
來區分 execute()
方法是處理 GET
操作,還是處理 POST
操作。如果還需要使用其他 HTTP 動作(例如 PUT
或 DELETE
),那么將不得不再次重構 execute()
。到目前為止,還沒有這樣的問題,但是今后要記住這一點。
測試
在實施這樣的重構之前,先運行 ant test
,以確保原有的所有基于 GET
的請求 API 仍可使用 — 事實確實如此。(這里假設生產 Twitter API 或 Twitter 服務器的可用性沒有變化)。一切正常(至少在我的計算機上是這樣),所以實現新的 execute()
方法就非常容易:
清單 4. Scitter v0.3: endSession
def endSession : Boolean = { val (statusCode, statusBody) = Scitter.execute("http://twitter.com/account/end_session.xml", Map("" -> ""), username, password) statusCode == 200 } |
這實在是再簡單不過了。
接下來要做的是實現 rate_limit_status
API,它有兩個版本,一個是經過驗證的版本,另一個是沒有經過驗證的版本。我們將該方法實現為 Scitter
對象和 Scitter
類上的 rateLimitStatus
,如清單 5 所示:
清單 5. Scitter v0.3: rateLimitStatus
package com.tedneward.scitter { object Scitter { // ... def rateLimitStatus : Option[RateLimits] = { val url = "http://twitter.com/account/rate_limit_status.xml" val (statusCode, statusBody) = Scitter.execute(url) if (statusCode == 200) { Some(RateLimits.fromXml(XML.loadString(statusBody))) } else { None } } } class Scitter { // ... def rateLimitStatus : Option[RateLimits] = { val url = "http://twitter.com/account/rate_limit_status.xml" val (statusCode, statusBody) = Scitter.execute(url, username, password) if (statusCode == 200) { Some(RateLimits.fromXml(XML.loadString(statusBody))) } else { None } } } } |
我覺得還是很簡單。
更新
現在,有了新的 POST
版本的 HTTP 通信層,我們可以來處理 Twitter API 的中心:update
調用。毫不奇怪,需要一個 POST
,并且至少有一個參數,即 status
。
status
參數包含要發布到認證用戶的 Twitter 提要的不超過 140 個字符的消息。另外還有一個可選參數:in_reply_to_status_id
,該參數提供另一個更新的 id,執行了 POST
的更新將回復該更新。
update
調用差不多就是這樣了,如清單 6 所示:
清單 6. Scitter v0.3: update
package com.tedneward.scitter { class Scitter { // ... def update(message : String, options : OptionalParam*) : Option[Status] = { def optionsToMap(options : List[OptionalParam]) : Map[String, String]= { options match { case hd :: tl => hd match { case InReplyToStatusId(id) => Map("in_reply_to_status_id" -> id.toString) ++ optionsToMap(tl) case _ => optionsToMap(tl) } case List() => Map() } } val paramsMap = Map("status" -> message) ++ optionsToMap(options.toList) val (statusCode, body) = Scitter.execute("http://twitter.com/statuses/update.xml", paramsMap, username, password) if (statusCode == 200) { Some(Status.fromXml(XML.loadString(body))) } else { None } } } } |
也許這個方法中最 “不同” 的部分就是其中定義的嵌套函數 — 與使用 GET
的其他 Twitter API 調用不同,Twitter 期望傳給 POST
的參數出現在執行 POST
的主體中,這意味著在調用 Scitter.execute()
之前需要將它們轉換成 Map
條目。但是,默認的 Map
(來自 scala.collections.immutable
)是不可變的,這意味著可以組合 Map
,但是不能將條目添加到已有的 Map
中。
解決這個小難題的最容易的方法是遞歸地處理傳入的 OptionalParam
元素的列表(實際上是一個 Array[]
)。我們將每個元素拆開,將它轉換成各自的 Map
條目。然后,將一個新的 Map
(由新創建的 Map
和從遞歸調用返回的 Map
組成)返回到 optionsToMap
。
然后,將 OptionalParam
的 Array[]
傳遞到 optionsToMap
嵌套函數。然后,將返回的 Map
與我們構建的包含 status
消息的 Map
連接起來。最后,將新的 Map
和用戶名、密碼一起傳遞給 Scitter.execute()
方法,以傳送到 Twitter 服務器。
隨便說一句,所有這些任務需要的代碼并不多,但是需要更多的解釋,這是比較優雅的編程方式。
潛在的重構
理論上,傳給 update
的可選參數與傳給其他基于 GET
的 API 調用的可選參數將受到同等對待;只是結果的格式有所不同(結果是用于 POST
的名稱/值對,而不是用于 URL 的名稱/值對)。
如果 Twitter API 需要其他 HTTP 動作支持(PUT
和/或 DELETE
就是可能需要的動作),那么總是可以將 HTTP 參數作為特定參數 — 也許又是一組 case 類 — 并讓 execute()
以一個 HTTP 動作、URL、名稱/值對的 map 以及(可選)用戶名/密碼作為 5 個參數。然后,必要時可以將可選參數轉換成一個字符串或一組 POST
參數。這些內容只需記在腦中就行了。
顯示
show
調用接受要檢索的 Twitter 狀態的 id,并顯示 Twitter 狀態。和 update
一樣,這個方法非常簡單,無需再作說明,如清單 7 所示:
清單 7. Scitter v0.3: show
package com.tedneward.scitter { class Scitter { // ... def show(id : Long) : Option[Status] = { val (statusCode, body) = Scitter.execute("http://twitter.com/statuses/show/" + id + ".xml", username, password) if (statusCode == 200) { Some(Status.fromXml(XML.loadString(body))) } else { None } } } } |
還有問題嗎?
另一種顯示方法
如果想再試一下模式匹配,那么可以看看清單 8 中是如何以另一種方式編寫 show()
方法的:
清單 8. Scitter v0.3: show redux
package com.tedneward.scitter { class Scitter { // ... def show(id : Long) : Option[Status] = { Scitter.execute("http://twitter.com/statuses/show/" + id + ".xml", username, password) match { case (200, body) => Some(Status.fromXml(XML.loadString(body))) case (_, _) => None } } } } |
這個版本比起 if/else 版本是否更加清晰,這很大程度上屬于審美的問題,但公平而論,這個版本也許更加簡潔。(很可能查看代碼的人看到 Scala 的 “函數” 部分越多,就認為這個版本越吸引人。)
但是,相對于 if/else 版本,模式匹配版本有一個優勢:如果 Twitter 返回新的條件(例如不同的錯誤條件或來自 HTTP 的響應代碼),那么模式匹配版本在區分這些條件時可能更清晰。例如,如果某天 Twitter 決定返回 400 響應代碼和一條錯誤消息(在主體中),以表明某種格式錯誤(也許是沒有正確地重新 Tweet),那么與 if/else 方法相比,模式匹配版本可以更輕松(清晰)地同時測試響應代碼和主體的內容。
還應注意,我們還可以使用清單 8 中的方式創建一些局部應用的函數,這些函數只需要 URL 和參數。但是,坦白說,這是一種自找麻煩的解放方案,所以我不會采用。
撤銷
我們還想讓 Scitter 用戶可以撤銷剛才執行的動作。為此,需要一個 destroy
調用,它將刪除已發布的 Twitter 狀態,如清單 9 所示:
清單 9. Scitter v0.3: destroy
package com.tedneward.scitter { class Scitter { // ... def destroy(id : Long) : Option[Status] = { val paramsMap = Map("id" -> id.toString()) val (statusCode, body) = Scitter.execute("http://twitter.com/statuses/destroy/" + id.toString() + ".xml", paramsMap, username, password) if (statusCode == 200) { Some(Status.fromXml(XML.loadString(body))) } else { None } } def destroy(id : Id) : Option[Status] = destroy(id.id.toLong) } } |
有了這些東西,我們可以考慮將這個 Scitter 客戶機庫作為 “alpha” 版,至少實現一個簡單的 Scitter 客戶機。(按照慣例,這個任務就留給您來完成,作為一項 “讀者練習”。)
到此,相信大家對“Scala:Twitter API和Scala交互的方法”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。