Simple Scala: Asynchronous HTTP requests with Scala and Dispatch

简介

现在我们在很多地方使用REST API。很多时候,这需要软件开发工具包的特定语言的实现。如果你打算写一个SDK,或者,你需要调用没有SDK的可用性REST后台,你需要一个框架来发送Http请求。而在Scala中,它原生的使用Future来支持异步处理。
通过使用Future,可以简化你的工作:

  • 应用不在阻塞
  • 应用可以处理更多的并行请求
  • 不再需要一个复杂的线程模型

在Scala中,Dispatch是一个异步http包,让我们使用Future完成一些例子。

普通的http请求

import dispatch._, Defaults._
import scala.util.{Success, Failure}

object DispatchTest {

  def main (args: Array[String]) {
    val svc = url("http://www.wikipedia.org/");
    val response : Future[String] = Http(svc OK as.String)

    response onComplete {
      case Success(content) => {
        println("Successful response" + content)
      }
      case Failure(t) => {
        println("An error has occurred: " + t.getMessage)
      }
    }
  }
}

重定向http请求

这是常见的http端点使用重定向。默认情况下Dispatch不遵循这些重定向,你需要配置HTTP,例如通过使用 Http.configure(_ setFollowRedirects true)(svc OK as.String)。
下面是一个包含重定向的例子:

import dispatch._, Defaults._
import scala.util.{Success, Failure}

object DispatchTest {

  def main (args: Array[String]) {

    val svc = url("http://www.wikipedia.com");
    val response : Future[String] = Http.configure(_ setFollowRedirects true)(svc OK as.String)

    response onComplete {
      case Success(content) => {
        println("Successful response" + content)
      }
      case Failure(t) => {
        println("An error has occured: " + t.getMessage)
      }
    }
  }
}

用户验证的http请求

如果你的http请求中需要基本的用户验证,使用 .as_!()来处理:

val svc = url("http://www.wikipedia.com").as_!("user", "password")

解析Json格式的Response

如今几乎所有的REST端点使用JSON响应。Argonaut 是一个处理http的工具包,它使用许多scala的特性,并与scala结合的非常好。

为了快速解析或者预定义的数据结构不可用时,可以只解析具体的字段:

import scalaz._, Scalaz._
import argonaut._, Argonaut._

val json = """
  { "name" : "Toddler", "age" : 2, "greeting": "gurgle!" }
"""

// extract a simple field "greeting"
val greeting1: String =
    Parse.parseWith(jsonString, _.field("greeting").flatMap(_.string).getOrElse(null), msg => msg)

如果你要应对不断变化的数据结构时,这种处理方式很不错。通常REST API都会定义一个固定的数据结构,既可以用于解析。假设你会得到如下用户数据:

{
  "dn"          :"uid=chris,ou=Users,dc=lollyrock,dc=com",
  "controls"    :[],
  "cn"          :"Chris Rock",
  "givenName"   :"Chris",
  "l"           :"Berlin",
  "mail"        :"chris@lollyrock.com",
  "uid"         : "chris" ,
  "displayName" :"ch.hartmann",
  "o"           :"Rock Inc."
}

首先我们需要一个结构来存储解析后的结果,在scala中使用case class非常合适:

case class User(
   dn           : String,
   cn           : String,
   givenName    : String,
   l            : String,
   mail         : String,
   displayName  : String,
   o            : String
)

下面的例子演示了JSON数据解析到一个预定义的样例类:

import scalaz._, Scalaz._
import argonaut._, Argonaut._

object UserParser {

  // use the implicit json conversion of Argonaut
  // more information at http://argonaut.io/doc/parsing/
  implicit def UserCodecJson: CodecJson[User] =
    // the 9 represents the amount of arguments
    casecodec9(User.apply, User.unapply)("dn", "cn", "givenName","l", "mail", "uid", "displayName", "o", "plan")

  // method to use argonaut parse
  def parse(data: String) : Option[User] = {
    Parse.decodeOption[User](data)
  }

  // simple app to test JSON parsing
  def main(args: Array[String]) {

    // json input data
    var jsonString =
      """
        | {"dn":"uid=chris,ou=Users,dc=lollyrock,dc=com","controls":[],"cn":"Chris Rock","givenName":"Chris","l":"Berlin","mail":"chris@lollyrock.com","uid": "chris" ,"displayName":"ch.hartmann","o":"Rock Inc."}
      """.stripMargin

    // parse json content
    val userdata: Option[User] = parse(jsonString)

    // print specific values
    val usr = userdata.get
    println (usr.dn)
    println (usr.displayName)
  }
}

Dispatch与Argonout的组合提供了一种有效的方法来处理HTTP调用和Response。由于我们使用的是Future,相对一个复杂的线程系统,可以在同一时间处理更多的请求,同时代码更易于阅读。

参考

Asynchronous HTTP requests with Scala and Dispatch - Christoph Hartmann