面向Java开拓人员的Scala指南 – 用Scitter更新Twitter
副标题#e#
在撰写本文时,夏季即将竣事,新的学年就要开始,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 库分为 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
}
}
}
好吧,我讲错了。也不是那么容易。
#p#副标题#e#
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()
#p#分页标题#e#
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()。到今朝为止,还没有这样的问题,可是此后要记 住这一点。
测试
#p#分页标题#e#
在实施这样的重构之前,先运行 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
}
}
}
}
#p#分页标题#e#
也许这个要领中最 “差异” 的部门就是个中界说的嵌套函数 — 与利用 GET 的其他 Twitter API 挪用差异,Twitter 期望传给 POST 的参数呈此刻执 行 POST 的主体中,这意味着在挪用 Scitter.execute() 之前需要将它们转换 成 Map 条目。可是,默认的 Map(来自 scala.collections.immutable)是不 可变的,这意味着可以组合 Map,可是不能将条目添加到已有的 Map 中。
可变荟萃
在 Scala 中可以利用可变荟萃,只需导入 scala.collections.mutable 包 ,而不是 scala.collections.immutable。可是,这样做要冒常见的 利用可变 数据 的风险,所以通例的 Scala 编程气势气魄发起从其他不行变荟萃建设不行变集 合。假如确实需要或想要利用可变荟萃,那么可以导入 scala.collections,然 后用部门包名前缀按 mutable.Map 或 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 的 “ 函数” 部门越多,就认为这个版本越吸引人。)
#p#分页标题#e#
可是,相对付 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 客户机。(凭据老例,这个任务就留给您来完 成,作为一项 “读者操练”。)
竣事语
编写 Scitter 客户机库是一项有趣的事情。固然不能说 Scitter 已经可以 完全用于出产,可是它绝对足以用于实现简朴的、基于文本的 Twitter 客户机 ,这意味着它已经可以投入利用了。要发明什么人可以利用它,哪些特性是需要 的,从而使之变得更有用,最好的要领就是将它向公家宣布。
我已经将本文和之前关于 Scitter 的文章中的代码作为第一个修订版提交到 Google Code 上的 Scitter 项目主页。接待下载和试用这个库,并汇报我您的 想法。同时也接待提供 bug 陈诉、修复和发起。
您也无需受我的代码库的束缚。见证了之前三篇文章中举办的 Scitter 开拓 ,您应该对 Twitter API 的利用有很好的领略。假如对付利用该 API 有差异的 想法,那么尽量去做:抛开 Scitter,构建本身的 Scala 客户机库。究竟,做 做这些内部项目也是挺有兴趣的。
此刻,我们要向 Scitter 挥手辞别,开始寻找新的用 Scala 办理的项目。 愿您从中找到兴趣,假如发明白用 Scala 编程的事情,别忘了汇报我!
文章来历:
http://www.ibm.com/developerworks/cn/java/j-scala10209.html