Twitter席卷了互联网。 无疑,您可以使用此漂亮的社交网络工具来订阅者提供有关自己及其当前行为的简短状态更新。 追随者收到的“ Twitter提要”的更新方式与博客将更新生成到博客读者的提要中的方式相同。
就其本身而言,Twitter是关于社交网络和新一代用户的“高度连接”的有趣讨论,并结合了您认为与之相关的所有利弊。
由于Twitter早早发布了其API,因此许多Twitter客户端应用程序已爆炸到Internet上。 因为该API基本上基于非常简单易懂的基础,所以许多开发人员发现以自己的方式来构建自己的Twitter客户端具有教育意义,就像学习Web技术的开发人员以自己的方式来构建自己的博客服务器一样行使。
鉴于Scala的功能性质(这似乎与Twitter的RESTful性质很好地吻合)和非常好的XML处理功能,在我看来,建立一个用于访问Twitter的Scala客户端库是一个很好的尝试。
Twitter什么?
在我们深入探讨这一方面之前,如果您还没有研究Twitter API(或使用过该技术),那么现在就开始做吧。
简而言之,Twitter是一个“微博客”,即关于您自己的个性化小声明,长度不超过140个字符,任何希望“关注”的人都可以通过Web更新,RSS,文本接收消息,等等。 (140个字符的限制是必要的,这仅是因为文本消息(Twitter的主要来源渠道之一)受到类似的限制。)
从编程的角度来看,Twitter是主要使用RESTful的 API,因为您使用某种消息格式(XML,ATOM,RSS或JSON)从Twitter服务器发送和接收消息。 不同的URL,结合不同的消息以及它们的必需和可选消息部分,进行了不同的API调用。 例如,如果要从Twitter上的每个人(也称为“公共时间轴”)接收所有“ Tweets”(Twitter更新)的完整列表,则可以准备XML,ATOM,RSS或JSON消息,然后发送将其保存到适当的URL,并以与Twitter网站apiwiki.twitter.com上记录的格式相同的格式使用结果:
------------------------------------------------------------
public_timeline
返回不受保护的用户的20个最新状态
谁设置了自定义用户图标。 不需要身份验证。
请注意,公共时间轴缓存了60秒,因此
经常要求它是浪费资源。
网址: http://twitter.com/statuses/public_timeline. format
: http://twitter.com/statuses/public_timeline. format
http://twitter.com/statuses/public_timeline. format
格式: xml,json,rss,atom
方法: GET
API限制:不适用
返回:状态元素列表
------------------------------------------------------------
从编程的角度来看,这意味着我们向Twitter服务器发送了一个简单的GET HTTP
请求,并且将获得包装在XML,RSS,ATOM或JSON消息中的“状态”消息列表。 在Twitter站点上,“状态”消息被定义为类似于清单1的内容:
清单1.嗨,世界,您在哪里?
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
<title>Twitter / tedneward</title>
<id>tag:twitter.com,2007:Status</id>
<link type="text/html" rel="alternate" href="http://twitter.com/tedneward"/>
<updated>2009-03-07T13:48:31+00:00</updated>
<subtitle>Twitter updates from Ted Neward / tedneward.</subtitle>
<entry>
<title>tedneward: @kdellison Happens to the best of us...</title>
<content type="html">tedneward: @kdellison Happens to the best of us...</content>
<id>tag:twitter.com,2007:http://twitter.com/tedneward/statuses/1292396349</id>
<published>2009-03-07T11:07:18+00:00</published>
<updated>2009-03-07T11:07:18+00:00</updated>
<link type="text/html" rel="alternate"
href="http://twitter.com/tedneward/statuses/1292396349"/>
<link type="image/png" rel="image"
href="http://s3.amazonaws.com/twitter_production/profile_images/
55857457/javapolis_normal.png"/>
<author>
<name>Ted Neward</name>
<uri>http://www.tedneward.com</uri>
</author>
</entry>
</feed>
状态消息中的大多数元素(如果不是全部)都非常简单明了,因此我们将其想象出来。
因为我们可以使用三种基于XML的格式之一来处理Twitter消息,而且Scala具有一些非常强大的XML功能(包括XML文字和类似XPath的查询语法API),所以编写一个可以发送和接收Twitter消息的Scala库是一项练习。一些基本的Scala编码。 例如,使用Scala使用清单1消息来选择状态更新的标题或内容可以利用Scala的XML类型以及\
和\\
方法,如清单2所示:
清单2.嘿,特德,你在哪里?
<![CDATA[
package com.tedneward.scitter.test
{
class ScitterTest
{
import org.junit._, Assert._
@Test def simpleAtomParse =
{
val atom =
<feed xml:lang="en-US" xmlns="http://www.w3.org/2005/Atom">
<title>Twitter / tedneward</title>
<id>tag:twitter.com,2007:Status</id>
<link type="text/html" rel="alternate" href="http://twitter.com/tedneward"/>
<updated>2009-03-07T13:48:31+00:00</updated>
<subtitle>Twitter updates from Ted Neward / tedneward.</subtitle>
<entry>
<title>tedneward: @kdellison Happens to the best of us...</title>
<content type="html">tedneward: @kdellison
Happens to the best of us...</content>
<id>tag:twitter.com,2007:
http://twitter.com/tedneward/statuses/1292396349</id>
<published>2009-03-07T11:07:18+00:00</published>
<updated>2009-03-07T11:07:18+00:00</updated>
<link type="text/html" rel="alternate"
href="http://twitter.com/tedneward/statuses/1292396349"/>
<link type="image/png" rel="image"
href="http://s3.amazonaws.com/twitter_production/profile_images/
55857457/javapolis_normal.png"/>
<author>
<name>Ted Neward</name>
<uri>http://www.tedneward.com</uri>
</author>
</entry>
</feed>
assertEquals(atom \\ "entry" \ "title",
"tedneward: @kdellison Happens to the best of us...")
}
}
}
]]>
有关Scala的XML支持的更多详细信息,请查看“ Scala和XML”(请参阅参考资料 )。
单独使用原始XML确实不是一个有趣的练习。 如果Scala旨在使我们的生活更轻松,那么让我们通过创建专门设计用于使发送和接收Scala消息更轻松的类或类的集合来使我们的生活更轻松。 为此,该库应该可以从“常规” Java程序中轻松使用(这意味着可以从理解常规Java语义的任何内容(例如Groovy或Clojure)中轻松访问该库)。
API设计
在我们深入了解Scala / Twitter库的API设计(我的我的ThoughtWorker尼尔·福特(Neal Ford)曾建议我将其称为“ Scitter”)之前,需要弄清一些要求。
首先,很明显,Scitter将对网络访问产生某种程度的依赖性,而对Twitter服务器的扩展则取决于它,这将使其很难进行测试。
其次,我们将需要解析(并测试)Twitter发送回的各种格式。
第三,我们要隐藏API背后各种格式之间的差异,以便客户端不必担心记录在案的Twitter消息格式,而可以只使用标准类。
最后,由于Twitter依赖大量API的“经过身份验证的用户”,因此Scitter库将需要适当地适应“经过身份验证的”和“未经身份验证的” API之间的差异,而又不会使事情变得太复杂。
为了访问Twitter服务器,网络访问将需要某种形式的HTTP通信。 尽管我们可以使用Java库本身(特别是URL类及其兄弟),但由于Twitter API需要大量请求和响应主体连接,因此使用不同的HTTP API(特别是Apache)会变得更加容易Commons HttpClient库。 为了使测试客户端API更容易,实际的通信将隐藏在Scitter库内部的某些API的后面,这样不仅可以更轻松地换用另一个HTTP库(我无法想象为什么会这样)是必要的),但这样可以更轻松地模拟实际的网络通信以进行更轻松的测试(这很容易想象出需要)。
结果,第一个测试就是简单地对HttpClient调用进行Scala-fy调用,以确保我们已经建立了基本的通信模式。 请注意,由于HttpClient依赖于其他两个Apache库(Commons Logging和Commons Codec),因此在运行时还需要同时存在这两个库。 对于那些希望开发类似代码的读者,请确保所有三个库都位于类路径中。
因为最容易使用的Twitter API是测试API,该API已记录为可读取
以200 OK HTTP状态代码以请求的格式返回字符串“ ok”。
让我们将其用作Scitter探索测试中的开场白。 它被记录为位于URL http://twitter.com/help/test.format(其中“ format”是“ xml”或“ json”;我们现在选择使用“ xml”)支持HTTP的方法是GET
。 因此,HttpClient代码几乎可以自行编写,如清单3所示:
清单3. Twitter PING!
package com.tedneward.scitter.test
{
class ExplorationTests
{
// ...
import org.apache.commons.httpclient._, methods._, params._, cookie._
@Test def callTwitterTest =
{
val testURL = "http://twitter.com/help/test.xml"
// HttpClient API 101
val client = new HttpClient()
val method = new GetMethod(testURL)
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
client.executeMethod(method)
val statusLine = method.getStatusLine()
assertEquals(statusLine.getStatusCode(), 200)
assertEquals(statusLine.getReasonPhrase(), "OK")
}
}
}
这段代码的绝大部分是HttpClient样板-感兴趣的读者应该查看HttpClient API文档以获取详细信息。 假设运行时可以使用与公共Internet的网络连接(并且Twitter尚未更改其公共API),则此测试应该以通俗易懂的方式通过。
鉴于此,让我们对Scitter客户端的第一部分进行哈希处理。 这意味着我们要面对一个设计要点:如何构造Scitter客户端以处理经过身份验证与未经过身份验证的呼叫。 目前,我将以经典的Scala方式进行操作,假设身份验证是“每个对象”的质量,因此将需要身份验证的调用放入类定义中,而将非身份验证的调用放入对象定义中:
清单4. Scitter.test
package com.tedneward.scitter
{
/**
* Object for consuming "non-specific" Twitter feeds, such as the public timeline.
* Use this to do non-authenticated requests of Twitter feeds.
*/
object Scitter
{
import org.apache.commons.httpclient._, methods._, params._, cookie._
/**
* Ping the server to see if it's up and running.
*
* Twitter docs say:
* test
* Returns the string "ok" in the requested format with a 200 OK HTTP status code.
* URL: http://twitter.com/help/test.format
* Formats: xml, json
* Method(s): GET
*/
def test : Boolean =
{
val client = new HttpClient()
val method = new GetMethod("http://twitter.com/help/test.xml")
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
client.executeMethod(method)
val statusLine = method.getStatusLine()
statusLine.getStatusCode() == 200
}
}
/**
* Class for consuming "authenticated user" Twitter APIs. Each instance is
* thus "tied" to a particular authenticated user on Twitter, and will
* behave accordingly (according to the Twitter API documentation).
*/
class Scitter(username : String, password : String)
{
}
}
现在,让我们不理会网络抽象-这将是稍后脱机测试变得更加重要时增加的一项改进。 当我们更好地了解我们如何使用HttpClient类时,它还将有助于避免“过度抽象”网络通信。
因为我们已经区分了经过身份验证的Twitter客户端和未经身份验证的Twitter客户端,所以让我们也快速创建一个经过身份验证的方法。 事实证明,Twitter具有一个API来验证用户的登录凭据,该API与我们将要获得的身份验证ping差不多。 再次,HttpClient代码将与先前的代码相似,不同之处在于,现在我们还必须将用户名和密码也传递到Twitter API中。
这使我们想到了Twitter如何验证用户身份的概念。 在Twitter API页面上进行了一些快速研究之后,发现Twitter使用了一种常规的HTTP身份验证方法,与HTTP中任何经过身份验证的资源相同。 这意味着HttpClient代码必须提供用户名和密码作为HTTP请求的一部分,而不是像预期的那样提供POST
,如清单5所示:
清单5.嗨,Twitter,是我!
package com.tedneward.scitter.test
{
class ExplorationTests
{
def testUser = "TwitterUser"
def testPassword = "TwitterPassword"
@Test def verifyCreds =
{
val client = new HttpClient()
val verifyCredsURL = "http://twitter.com/account/verify_credentials.xml"
val method = new GetMethod(verifyCredsURL)
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
client.getParams().setAuthenticationPreemptive(true)
val defaultcreds = new UsernamePasswordCredentials(testUser, testPassword)
client.getState().setCredentials(new AuthScope("twitter.com", 80,
AuthScope.ANY_REALM), defaultcreds)
client.executeMethod(method)
val statusLine = method.getStatusLine()
assertEquals(200, statusLine.getStatusCode())
assertEquals("OK", statusLine.getReasonPhrase())
}
}
}
请注意,为了使此测试通过,需要在Twitter用户名和密码字段中填充Twitter可以接受的内容-我在开发过程中使用了自己的Twitter用户名和密码,但是显然您必须使用自己的Twitter用户名和密码。 注册一个新的Twitter帐户非常简单,因此我假设您拥有一个,或者可以弄清楚如何获得一个。 (没关系,我等您一会儿。)
一旦完成,很容易看到如何使用用户名和密码构造函数参数将其映射到Scitter类本身,如清单6所示:
清单6. Scitter.verifyCredentials
package com.tedneward.scitter
{
import org.apache.commons.httpclient._, auth._, methods._, params._
// ...
/**
* Class for consuming "authenticated user" Twitter APIs. Each instance is
* thus "tied" to a particular authenticated user on Twitter, and will
* behave accordingly (according to the Twitter API documentation).
*/
class Scitter(username : String, password : String)
{
/**
* Verify the user credentials against Twitter.
*
* Twitter docs say:
* verify_credentials
* Returns an HTTP 200 OK response code and a representation of the
* requesting user if authentication was successful; returns a 401 status
* code and an error message if not. Use this method to test if supplied
* user credentials are valid.
* URL: http://twitter.com/account/verify_credentials.format
* Formats: xml, json
* Method(s): GET
*/
def verifyCredentials : Boolean =
{
val client = new HttpClient()
val method = new GetMethod("http://twitter.com/help/test.xml")
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
client.getParams().setAuthenticationPreemptive(true)
val creds = new UsernamePasswordCredentials(username, password)
client.getState().setCredentials(
new AuthScope("twitter.com", 80, AuthScope.ANY_REALM), creds)
client.executeMethod(method)
val statusLine = method.getStatusLine()
statusLine.getStatusCode() == 200
}
}
}
清单7中相应的Scitter类测试也非常简单:
清单7.测试Scitter.verifyCredentials
package com.tedneward.scitter.test
{
class ScitterTests
{
import org.junit._, Assert._
import com.tedneward.scitter._
def testUser = "TwitterUsername"
def testPassword = "TwitterPassword"
// ...
@Test def verifyCreds =
{
val scitter = new Scitter(testUser, testPassword)
val result = scitter.verifyCredentials
assertTrue(result)
}
}
}
还不错,还不错。 库的基本结构已初具规模,尽管显然还有很长的路要走,特别是因为到目前为止,Scala所特有的东西还没有真正完成—构建库更多地是面向对象设计的工作比什么都重要。 因此,让我们开始使用一些XML并将其以更可口的形式返回。
从XML到对象
目前最容易添加的API是public_timeline,它收集Twitter在所有用户中收到的最新n个更新,并将其交还给用户使用。 与目前为止看到的其他两个API不同,public_timeline API会传递响应主体(而不是仅依赖状态码),因此我们需要先将生成的XML / RSS / ATOM /其他内容分解开,然后再将其返回给Scitter客户端。
为了保持到目前为止的进展,让我们编写一个探查测试,该测试命中公共提要并将结果转储到stdout
进行检查,如清单8所示:
清单8.每个人都在做什么?
package com.tedneward.scitter.test
{
class ExplorationTests
{
// ...
@Test def callTwitterPublicTimeline =
{
val publicFeedURL = "http://twitter.com/statuses/public_timeline.xml"
// HttpClient API 101
val client = new HttpClient()
val method = new GetMethod(publicFeedURL)
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
client.executeMethod(method)
val statusLine = method.getStatusLine()
assertEquals(statusLine.getStatusCode(), 200)
assertEquals(statusLine.getReasonPhrase(), "OK")
val responseBody = method.getResponseBodyAsString()
System.out.println("callTwitterPublicTimeline got... ")
System.out.println(responseBody)
}
}
}
运行时,由于公共Twitter服务器上的大量用户,结果每次都会有所不同,但在JUnit文本文件转储中,结果通常类似于清单9:
清单9.这些是我们生活中的推文
<statuses type="array">
<status>
<created_at>Tue Mar 10 03:14:54 +0000 2009</created_at>
<id>1303777336</id>
<text>She really is. http://tinyurl.com/d65hmj</text>
<source><a href="http://iconfactory.com/software/twitterrific">twitterrific</a>
</source>
<truncated>false</truncated>
<in_reply_to_status_id></in_reply_to_status_id>
<in_reply_to_user_id></in_reply_to_user_id>
<favorited>false</favorited>
<user>
<id>18729101</id>
<name>Brittanie</name>
<screen_name>brittaniemarie</screen_name>
<description>I'm a bright character. I suppose.</description>
<location>Atlanta or Philly.</location>
<profile_image_url>http://s3.amazonaws.com/twitter_production/profile_images/
81636505/goodish_normal.jpg</profile_image_url>
<url>http://writeitdowntakeapicture.blogspot.com</url>
<protected>false</protected>
<followers_count>61</followers_count>
</user>
</status>
<status>
<created_at>Tue Mar 10 03:14:57 +0000 2009</created_at>
<id>1303777334</id>
<text>Number 2 of my four life principles. "Life is fun and rewarding"</text>
<source>web</source>
<truncated>false</truncated>
<in_reply_to_status_id></in_reply_to_status_id>
<in_reply_to_user_id></in_reply_to_user_id>
<favorited>false</favorited>
<user>
<id>21465465</id>
<name>Dale Greenwood</name>
<screen_name>Greeendale</screen_name>
<description>Vegetarian. Eat and use only organics.
Love helping people become prosperous</description>
<location>Melbourne Australia</location>
<profile_image_url>http://s3.amazonaws.com/twitter_production/profile_images/
90659576/Dock_normal.jpg</profile_image_url>
<url>http://www.4abundance.mionegroup.com</url>
<protected>false</protected>
<followers_count>15</followers_count>
</user>
</status>
(A lot more have been snipped)
</statuses>
从检查结果和Twitter文档来看,很明显的是,调用的结果是具有统一消息结构的大部分“状态”消息的集合。 使用Scala的XML支持将结果分开是一个非常简单的练习,但是一旦基本测试通过,我们将对其进行优化,如清单10所示:
清单10.每个人都在做什么?
package com.tedneward.scitter.test
{
class ExplorationTests
{
// ...
@Test def simplePublicFeedPullAndParse =
{
val publicFeedURL = "http://twitter.com/statuses/public_timeline.xml"
// HttpClient API 101
val client = new HttpClient()
val method = new GetMethod(publicFeedURL)
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
val statusCode = client.executeMethod(method)
val responseBody = new String(method.getResponseBody())
val responseXML = scala.xml.XML.loadString(responseBody)
val statuses = responseXML \\ "status"
for (n <- statuses.elements)
{
n match
{
case <status>{ contents @ _*}</status> =>
{
System.out.println("Status: ")
contents.foreach((c) =>
c match
{
case <text>{ t @ _*}</text> =>
System.out.println("\tText: " + t.text.trim)
case <user>{ contents2 @ _* }</user> =>
{
contents2.foreach((c2) =>
c2 match
{
case <screen_name>{ u }</screen_name> =>
System.out.println("\tUser: " + u.text.trim)
case _ =>
()
}
)
}
case _ =>
()
}
)
}
case _ =>
() // or, if you prefer, System.out.println("Unrecognized element!")
}
}
}
}
}
在示例代码模式中,这很难让人感到鼓舞-感觉很像DOM,一次导航到每个子元素,提取文本,然后导航到另一个节点。 我们可以简单地执行两个XPath样式的查询,如清单11所示:
清单11.替代解析方法
for (n <- statuses.elements)
{
val text = (n \\ "text").text
val screenName = (n \\ "user" \ "screen_name").text
}
这肯定会更短,但是由此引起两个基本问题:
- 我们可能会强制Scala的XML库针对所需的每个元素或子元素遍历一次图形,这可能会随着时间的推移而变慢。
- 我们仍在直接努力处理XML消息的结构。 这是两者中更为重要的。
可以这么说,这不会成比例。假设我们最终对Twitter状态消息中的每个元素都感兴趣,那么我们就必须从每个状态中分别提取每个元素。
这导致了另一个问题,即各个格式本身的问题。 记住,Twitter具有他们喜欢使用的四种不同格式,我们不希望Scitter客户必须了解它们之间的区别,因此Scitter需要一个中间结构,我们可以将其传递给客户端以供进一步使用,以及清单12的行:
清单12.断路器,断路器,您的状态如何?
abstract class Status
{
val createdAt : String
val id : Long
val text : String
val source : String
val truncated : Boolean
val inReplyToStatusId : Option[Long]
val inReplyToUserId : Option[Long]
val favorited : Boolean
val user : User
}
...和User
相似的外观,为简洁起见,在此不再赘述。 请注意, User
子元素对此有一个有趣的警告-尽管有Twitter用户类型,但其内部嵌套有一个可选的“最新状态”。 状态消息中还嵌套了一个用户。 在这种情况下,为了帮助避免某些潜在的递归问题,我选择创建一个嵌套在Status
类型内部的User
类型,以反映出现在其中的User
数据,反之亦然,嵌套在User
内部的Status
,反之亦然以明确避免该问题。 (至少,这是我发现问题之前的计划。)
现在,在创建了表示Twitter消息的对象类型之后,我们可以遵循一种通用的Scala XML反序列化模式:创建一个对应的对象定义,其中包含fromXml
方法来获取XML节点并将其撕成对象实例,如清单1所示。 13:
清单13.破解XML
/**
* Object wrapper for transforming (format) into Status instances.
*/
object Status
{
def fromXml(node : scala.xml.Node) : Status =
{
new Status {
val createdAt = (node \ "created_at").text
val id = (node \ "id").text.toLong
val text = (node \ "text").text
val source = (node \ "source").text
val truncated = (node \ "truncated").text.toBoolean
val inReplyToStatusId =
if ((node \ "in_reply_to_status_id").text != "")
Some((node \"in_reply_to_status_id").text.toLong)
else
None
val inReplyToUserId =
if ((node \ "in_reply_to_user_id").text != "")
Some((node \"in_reply_to_user_id").text.toLong)
else
None
val favorited = (node \ "favorited").text.toBoolean
val user = User.fromXml((node \ "user")(0))
}
}
}
关于这个特定习惯用法的妙处在于,它可以扩展为Twitter支持的任何其他格式fromXml
方法本身可以在将其拆开之前检查该节点是否持有XML,RSS或Atom类型的内容,或Status
可以具有fromXml
, fromRss
, fromAtom
和fromJson
方法。 后一种方法实际上是我的首选,因为它随后将基于XML和JSON(基于文本)的格式均等地对待。
好奇和细心的读者会注意到,在Status
及其嵌套User
的fromXml
方法中,我使用的是XPath样式的分解方法,而不是像我之前建议的那样遍历嵌套的元素。 目前,XPath风格的方法似乎更容易阅读,但是幸运的是,我以后应该改变主意,好的ole'封装仍然是我的朋友-我以后可以更改它,而Scitter之外的任何人都不会知道或不在意。
注意Status
内的两个成员如何使用Option[T]
类型。 这是因为这些元素经常被遗漏在“ Status
消息中,并且尽管它们本身将出现,但它们将显示为空(例如在<in_reply_to_user_id></in_reply_to_user_id>
)。 这正是Option[T]
表示的内容,因此当为空时,它们将获得“无”作为值。 (这意味着对于基于Java的兼容性,访问起来会有些困难,而只能通过在生成的Option
实例上调用get()
,该实例并不繁琐,并且可以很好地处理“ nulls-or -0“问题,否则将会出现。)
因此,现在,使用公共时间表很简单:
清单14.破解公共时间表
@Test def simplePublicFeedPullAndDeserialize =
{
val publicFeedURL = "http://twitter.com/statuses/public_timeline.xml"
// HttpClient API 101
val client = new HttpClient()
val method = new GetMethod(publicFeedURL)
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
val statusCode = client.executeMethod(method)
val responseBody = new String(method.getResponseBody())
val responseXML = scala.xml.XML.loadString(responseBody)
val statuses = responseXML \\ "status"
for (n <- statuses.elements)
{
val s = Status.fromXml(n)
System.out.println("\t'@" + s.user.screenName + "' wrote " + s.text)
}
}
坦率地说,它看起来更干净且更易于使用。
将所有内容放到Scitter单例中是一个简单的事情,只需执行查询,解析出单个Status
元素,然后将它们一起添加到List[Status]
实例中以进行传递,如清单15所示:
清单15. Scitter.publicTimeline
package com.tedneward.scitter
{
import org.apache.commons.httpclient._, auth._, methods._, params._
import scala.xml._
object Scitter
{
// ...
/**
* Query the public timeline for the most recent statuses.
*
* Twitter docs say:
* public_timeline
* Returns the 20 most recent statuses from non-protected users who have set
* a custom user icon. Does not require authentication. Note that the
* public timeline is cached for 60 seconds so requesting it more often than
* that is a waste of resources.
* URL: http://twitter.com/statuses/public_timeline.format
* Formats: xml, json, rss, atom
* Method(s): GET
* API limit: Not applicable
* Returns: list of status elements
*/
def publicTimeline : List[Status] =
{
import scala.collection.mutable.ListBuffer
val client = new HttpClient()
val method =
new GetMethod("http://twitter.com/statuses/public_timeline.xml")
method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,
new DefaultHttpMethodRetryHandler(3, false))
client.executeMethod(method)
val statusLine = method.getStatusLine()
if (statusLine.getStatusCode() == 200)
{
val responseXML =
XML.loadString(method.getResponseBodyAsString())
val statusListBuffer = new ListBuffer[Status]
for (n <- (responseXML \\ "status").elements)
statusListBuffer += (Status.fromXml(n))
statusListBuffer.toList
}
else
{
Nil
}
}
}
}
一个小时的工作时间还不错。 拥有完整功能的Twitter客户端之前,我们显然还有一段距离,但是到目前为止,基本行为看起来不错。
结论
我们正在努力构建Scitter库。 到目前为止,从相对简单的Scitter测试实现即可证明,事情从外部来说很简单而且很干净,尤其是与产生Scitter API本身的探索测试相比时。 外部用户不必担心Twitter API或其各种格式的复杂性,尽管现在Scitter库有点难以测试(取决于网络不是进行单元测试的好方法),我们还是及时修复。
请注意,我故意保持Twitter API的面向对象的感觉,并与Scala的精神保持一致-仅仅因为Scala支持许多功能特性并不意味着我们必须放弃Java的对象设计方法结构拥抱。 我们将在有意义的地方使用功能部件,但将“旧方法”保留在它们似乎适合的地方。
这并不是说我在这里进行的设计是解决问题的最佳方法,而仅仅是我决定设计它的方法。 因为我是撰写本文的人,所以我们按照我的方式去做。 不喜欢它,编写您自己的库和文章(然后将URL发送给我,以便在以后的专栏中向您大喊大叫)。 实际上,在以后的文章中,我们将所有这些打包到Scala的“ sbaz”包中,并将其发布到Web上的某个位置以便于下载。
现在,是时候让我们再次分开。 下个月,我们将在Scitter库中添加一些更有趣的功能,并开始考虑使它更易于测试和使用的方法。
翻译自: https://www.ibm.com/developerworks/java/library/j-scala05059/index.html