技術をかじる猫

適当に気になった技術や言語、思ったこと考えた事など。

Scala_curl で HTTP クライアントを作ってみる

このエントリは Scala Advent Calendar の 22 日目になります。

事の始まりは Playframework の WS を使いにくいと感じたこと。

multipart/form-data in WS POST

そもそも、WS で multipart を想定していないとのことでした。

そして、その下で動いてる AsyncHttpClient/async-http-client · GitHub でどうにか実現しようとしたのが Play2.2.1のWSがもう少しどうにかならんかと思って解析してみた - 謎言語使いの徒然 です。

それでもしんどいので、サードパーティのライブラリに走りました。

Scala で HTTP クライアントライブラリは、Dispatch が有名です。こちらは 2013 ScalaAdventCalendar で公開された 2013-12-18 - NetPenguinの日記 が参考になります。

で、まぁ DSL だらけになるのですね、これ。

ということで、次に使ってみたのが m3dev/curly · GitHub です。

こちらは Java/Scala 両対応で、非常にシンプルな IF をしているので、中々に使い勝手が良さそうでした。

最大の利点としては、ソケットに自前で書き込みをしているので、他の jar に依存しないことです。

これは複数のライブラリに依存しているプロジェクトでは非常に魅力的ではあります。

『だがいや待たれよ』

ソケットを自前で書いている以上、一部の操作が変更できないと言った内容や、送信で利用される一般的なヘッダが一部未実装であったり、multipart でエンコーディングが入ってないとか、数えていくと結構ありました。

手をくわえて pull request するのが筋かなとは思いましたが、Maven/SBT のハイブリッド構成や、SBT 側が Maven 側のデプロイに依存していたりと、手を加えてテストするには面倒な状態でした。

そっか、ならば

自分で使いたいもの作ればいいんだ

ということで作ってみました。

Sunao-Yoshii/scala_curl · GitHub

自分でソケットを書く気力はなかったので、ApacheCommons の御厄介にはなっていますが、なんとなく curly に似せて作ってます。

インストール

リポジトリは今日時点で作ってないので、当面は publishLocal となります。

  git clone https://github.com/Sunao-Yoshii/scala_curl.git
  sbt clean update publishLocal

あとは何時もの通り build.sbt に下記を書いて、設定終了です。

  libraryDependencies += "net.white-azalea" %% "scala_curl" % "0.1"

因みに Scala 2.10.3 でビルドしてます。

コード的に 2.10.x 系以降でのみビルドできるような状態です。

簡単な使い方

  import bet.azalea.curl._
  import net.azalea.curl.HTTPHelper._

  val response         = HTTP.get("http://localhost:8000")
  val status:Int       = response.status // returns server response code. like 200
  val body:Array[Byte] = response.bodyAsBytes // returns body content as byte array.
  val bodyStr:String   = response.bodyAsString()

PUT / POST も

  val putResponse  = HTTP.put("http://localhost/path/to", "SampleMessage".toEntity())
  val postResponse = HTTP.post("http://localhost/path/to", "SampleMessage".toEntity())

ファイルの送信等

  import org.apache.http.entity.mime.content.ContentBody

  val file = new java.io.File("sample.txt")
  HTTP.post("http://localhost/path/to", file.toEntity())

  // HTTP FORM 送信
  HTTP.post("http://localhost:9100/form", Map(
    "param1" -> "value1",
    "param2" -> "value2"
  ).toEntity)

  // multipart での送信
  HTTP.post("http://localhost:9100/multipart", Map[String, ContentBody](
    "param1" -> "value1".toContentBody(),
    "param2" -> "value2".toContentBody(),
    "file" -> file.toContentBody()
  ).toEntity)

という、今日の為に作りたてのライブラリです。

早速クライアントとして使ってみる

まずは build.sbt からです。

name := "curl_scala_sample"

version := "0.1"

scalaVersion := "2.10.3"

libraryDependencies ++= Seq(
  "net.white-azalea" %% "scala_curl" % "0.1"
)

そしてコード

object ReadMyFeed extends App {
  import net.azalea.curl._

  val rss = HTTP.get("http://white-azalea.hatenablog.jp/rss")
  println(rss.bodyAsString())
}

実行してみると、

> run
[info] Compiling 1 Scala source to D:\sources\sample\target\scala-2.10\classes...
[info] Running ReadMyFeed
<?xml version="1.0"?>
<rss version="2.0">
  <channel>
    <title>謎言語使いの徒然</title>
    <link>http://white-azalea.hatenablog.jp/</link>
    <description>適当に気になった技術や言語を流すブログ。</description>
    <lastBuildDate>Sat, 21 Dec 2013 22:39:45 +0900</lastBuildDate>
    <docs>http://blogs.law.harvard.edu/tech/rss</docs>
(以下略)

実際に運用していくには全く枯れてないので、何とも不安は残りますが、興味本位で使って意見とか出していただけると幸いです。

頑張って github では英語を書いていますが、中身が日本人なので、日本語で質問でもリクエストでも書いていただいてもOKです。