通过 Scala 可以实现更好的社交网络(3)

来源:developerWorks 中国 作者:Ted Neward
  

从 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 服务器上有许多用户,但通常应与清单 9 的 JUnit 文本文件转储类似:


清单 9. 我们的 Tweets 结果
				
<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. Breaker,您的状态是什么?
				
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 数据;反之亦然,Status 也可以嵌入在 User 中,这样可以明确避免该问题。(至少,在没发现问题之前,这种方法是有效的)。

现在,创建了表示 Twitter 消息的对象类型之后,我们可以遵循 XML 反序列化的公共 Scala 模式:创建相应的对象定义,其中包含一个 fromXml 方法,用于将 XML 节点分离到对象实例中,如清单 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))
      }
    }
  }

时间:2009-08-20 16:49 来源:developerWorks 中国 作者:Ted Neward 原文链接

好文,顶一下
(1)
100%
文章真差,踩一下
(0)
0%
------分隔线----------------------------


把开源带在你的身边-精美linux小纪念品
无觅相关文章插件,快速提升流量