`
lw9956164
  • 浏览: 27215 次
  • 性别: Icon_minigender_1
  • 来自: 长沙
最近访客 更多访客>>
社区版块
存档分类
最新评论

用 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 编程的最大缺点是没有任何合适的测试或模拟工具。)

Twitter API如果您不熟悉 Twitter API,那么有必要花几分钟看看 Twitter API Wiki 页面 http://apiwiki. twitter.com/REST+API+Documentation,了解更详细的信息。其基本原理很简单 — 在 URL 查询中传递参数,响应可采用 4 种格式之一(JSON、XML、ATOM 或 RSS),等等 — 但是,和所有 API 一样,细节往往非常重要,这里假设读者阅读本文时,已经在浏览器中打开了 Twitter API,以便将注意力集中到 Scala 上来。
.目前的进展

在开始着手这个库的新的 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 中。(实际上,还是可以做到,但是我们不愿意那样做。请参阅侧边栏 “可变集合”,了解更多这方面的信息。)

可变集合在 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 的 “函数” 部分越多,就认为这个版本越吸引人。)

但是,相对于 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 客户机库。毕竟,做做这些内部项目也是挺有乐趣的。
分享到:
评论

相关推荐

    scala学习源代码

    这个"scala学习源代码"的压缩包文件很可能包含了用于教学或自我学习Scala编程的基础示例。让我们深入了解一下Scala语言的关键概念和特性。 首先,Scala运行在Java虚拟机(JVM)上,这意味着它可以无缝地与Java库...

    scala教程-twitter

    Scala 是 Twitter 使用的主要应用编程语言之一。很多我们的基础架构都是用 Scala 写的,我们也有一些大的库支持我们使用。虽然非常有效,Scala 也是一门大的语言,经验教会我们在实践中要非常小心。 它有什么陷阱?...

    Scala程序设计 例子 源代码

    描述中的重复部分强调了"Scala程序设计 例子 源代码"这一核心内容,这暗示了这个压缩包的目的是为了提供实践性的学习材料,帮助编程者通过实际操作来掌握Scala。 标签"Scala 例子 源代码"进一步明确了主题,说明了...

    twitter scala课堂

    Twitter通过Scala课堂的系列讲座帮助有经验的工程师快速成为高效的Scala程序员,这些课程强调使用Scala的“对象-函数式”风格以及编写可维护、清晰的代码,充分发挥类型系统的强大功能。 首先,课程强调不要将Scala...

    学习scala好的项目

    在这个名为"学习scala好的项目"的压缩包中,我们可以期待找到一系列有助于初学者掌握Scala编程的知识资源。 首先,让我们深入探讨Scala的基础知识。Scala的语法简洁而富有表现力,它的类型系统支持静态类型检查,有...

    wartremover, 灵活的Scala 代码linting工具.zip

    wartremover, 灵活的Scala 代码linting工具 WartRemover WartRemover是一个灵活的Scala 代码linting工具。文档这里有关于Wartremover的文档,请参考这里。报告问题通过 Scala 编译器扩展树是非常有用的,而不是原始...

    最好的scala学习 课件

    Scala是一种强大的多范式编程语言,它融合了面向对象和函数式编程的特性,被广泛应用于大数据处理领域,特别是与Apache Spark相结合时。本课件是针对Scala学习者精心准备的资源,旨在帮助你深入理解和掌握Scala的...

    scala深入学习

    在学习Scala时,还需要注意的是,由于语言的灵活性,可能会存在一些错误或遗漏,开发者在使用Scala的示例代码或学习材料时应保持谨慎。同时,由于Scala构建在Java平台上,了解Java对于Scala学习者来说是非常有帮助的...

    Scala-学习资料-mht.rar

    6. Scala与Java互操作:由于Scala是运行在JVM上的,所以可以直接使用Java库,与Java代码无缝集成。 7. Play框架:Play是一个基于Scala和Java的现代Web应用框架,它采用 Reactive Model(响应式模型)设计,非常适合...

    用500行scala代码实现sql解析器(英文版)

    8. 用Scala代码实现高效SQL解析器的实际案例:文章展示了一个用Scala代码实现的SQL到C的编译器。这个编译器使用了不到500行的Scala代码,突出了Scala语言在编写高效代码方面的优势。 9. 性能与功能编程的结合:这个...

    flinkDemo完整代码scala版 - flinkDemo.zip

    【标题】"flinkDemo完整代码scala版"指的是一个基于Scala实现的Apache Flink示例项目,该项目包含了对Flink核心功能的全面演示。它提供了TableAPI、Flink SQL以及DataStream API的实例,同时也涵盖了从socket、Kafka...

    scala学习资料(带书签)

    这个压缩包“scala学习资料(带书签)”提供了一个全面的学习路径,从基础到高级,帮助你深入理解和掌握Scala语言。 **入门篇** 1. **基础语法**:Scala的基础包括变量声明、类型系统、控制结构(如if/else、循环)...

    Scala代码格式化插件scalariform.zip

    scalariform 是一个用来格式化 Scala 代码的 Eclipse 插件。 同时也提供了各种编辑器插件、命令行工具和开发包 标签:scalariform

    简单的 Rx 平台游戏_Scala_代码_下载

    - **游戏循环**: 通过定义一个 Observable 来触发每一帧的更新,使用 `interval` 或 `timer` 操作符创建定期的事件,执行游戏逻辑。 - **碰撞检测**: 利用 Rx 的 `zip` 或 `merge` 操作符可以合并多个 Observable,...

    scala:scala代码

    在"scala: scala代码"的描述中,我们可以推测这是一个关于Scala编程语言的学习资源或者项目源代码。 1. **面向对象编程**:Scala是基于Java虚拟机(JVM)的语言,因此它兼容Java的所有类库。它支持类、对象和继承等...

    scala 学习

    此外,由Twitter公司推出的在线学习资源***也是一个非常好的Scala学习平台。这个在线学院的课程涵盖了从基础到进阶的Scala知识点,包括了类、特质、模式匹配、集合操作、并发控制等多个方面。课程内容通常会结合实际...

    Scala学习之路(一)

    ### Scala学习之路(一)—— 开发环境搭建与首个程序 #### 一、Scala简介 Scala是一种多范式编程语言,旨在实现可扩展性,并融合了面向对象编程和函数式编程的最佳特性。作为一种与Java非常相似的语言,Scala能够...

    《Scala实用指南》代码清单

    通过学习这些代码清单,读者不仅可以深入理解Scala语言本身,还能掌握如何利用Scala构建实际的、高效的应用程序,特别是当涉及到响应式编程和构建工具的使用时,能更好地适应现代软件开发的需求。

    scala3 scala3 scala3 scala3 scala3

    Scala3,也被称为Scala 3或Dotty,是Scala编程语言的一个重大更新,旨在提高其简洁性、可读性和类型安全性。Scala3的发布标志着该语言的进一步成熟,它引入了一系列改进,旨在解决早期版本中的一些痛点,同时保持对...

Global site tag (gtag.js) - Google Analytics