scalaj-http
Java HttpURLConnectionのシンプルなScalaラッパー。軽量で依存関係最小、OAuth認証サポート付き。基本的なHTTP操作に特化したシンプルなAPI設計により、複雑な設定不要で素早くHTTP通信を実現。学習コストの低いHTTPクライアント。
GitHub概要
scalaj/scalaj-http
Simple scala wrapper for HttpURLConnection. OAuth included.
トピックス
スター履歴
ライブラリ
scalaj-http
概要
scalaj-httpは「Scala向けのシンプルなHttpURLConnectionラッパー」として開発された、Scalaエコシステムで軽量HTTPクライアントライブラリとして使用されていた高レベルHTTPライブラリです。「ゼロ依存性とシンプルさ」を重視して設計され、JavaのHttpURLConnectionをベースとした薄いラッパーとして動作し、OAuth 1.0サポート、不変オブジェクト設計、流暢なAPI、自動圧縮処理を提供。しかし2022年4月にアーカイブ化され、開発終了しており、現在はsttpやhttp4s等の現代的な代替ライブラリへの移行が推奨されています。
詳細
scalaj-http 2.4.2(最終版)は、Java HttpURLConnectionの薄いラッパーとして、最小限の機能でHTTP通信を提供していました。Http、HttpRequest、HttpResponseの3つの主要コンポーネントにより、不変かつスレッドセーフなHTTPクライアント機能を実現。ゼロ依存関係(Base64エンコーディングやOAuth署名も内部実装)、不変ビルダーパターン、流暢なAPI、自動gzip/deflate圧縮、マルチパートアップロード、OAuth v1署名機能を特徴としていました。ただしプロジェクト終了により、現在は保守・更新されておらず、新規プロジェクトでの使用は非推奨です。
主な特徴(歴史的参考)
- ゼロ依存性: 外部ライブラリ依存なしの完全スタンドアロン実装
- 不変オブジェクト設計: スレッドセーフで再利用可能なリクエスト構築
- 流暢なAPI: メソッドチェーンによる直感的なリクエスト構築
- OAuth 1.0サポート: 組み込みOAuth署名機能
- 自動圧縮: gzip/deflate応答の自動展開
- 軽量実装: 最小限の機能で高いパフォーマンス
メリット・デメリット
メリット(歴史的評価)
- 外部依存関係ゼロによる軽量性と導入の容易さ
- 不変オブジェクト設計によるスレッドセーフティと予測可能性
- シンプルで理解しやすいAPI設計による学習コスト低減
- Google App EngineやAndroid環境での動作実績
- OAuth 1.0の組み込みサポートによる認証統合の簡素化
- Java HttpURLConnectionベースの安定性と互換性
デメリット(移行理由)
- 開発終了により保守・サポートなし(2022年4月アーカイブ化)
- 非同期処理や接続プールなどの現代的機能欠如
- HTTP/2対応なし、現代的プロトコル未対応
- 限定的なエラーハンドリングと詳細制御オプション
- 大規模アプリケーションには機能不足
- セキュリティアップデートや新機能追加なし
参考ページ
移行について
重要: scalaj-httpは2022年4月にアーカイブ化され、開発が終了しています。新規プロジェクトでは以下の現代的な代替ライブラリの使用を推奨します:
推奨代替ライブラリ
- sttp - 多様なプログラミングモデル対応の高機能HTTPクライアント
- http4s - 関数型プログラミング向けHTTPライブラリ
- Akka HTTP Client - リアクティブストリーミング対応
- Java HttpClient (JDK 11+) - 標準ライブラリの現代的HTTPクライアント
書き方の例(歴史的参考)
インストール(最終版)
// build.sbt
libraryDependencies += "org.scalaj" %% "scalaj-http" % "2.4.2"
<!-- Maven pom.xml -->
<dependency>
<groupId>org.scalaj</groupId>
<artifactId>scalaj-http_2.13</artifactId>
<version>2.4.2</version>
</dependency>
基本的なHTTPリクエスト
import scalaj.http._
// 基本的なGETリクエスト
val response: HttpResponse[String] = Http("https://api.example.com/users")
.param("page", "1")
.param("limit", "10")
.asString
println(response.body) // レスポンスボディ
println(response.code) // HTTPステータスコード
println(response.headers) // レスポンスヘッダー
println(response.cookies) // クッキー
// 再利用可能なHttpRequest
val request: HttpRequest = Http("https://api.example.com/")
val responseOne = request.asString
val responseTwo = request.asString
// JSONデータのPOSTリクエスト
val jsonData = """{"name": "田中太郎", "email": "[email protected]"}"""
val postResponse = Http("https://api.example.com/users")
.postData(jsonData)
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.asString
if (postResponse.code == 201) {
println("ユーザー作成成功: " + postResponse.body)
} else {
println("エラー: " + postResponse.code + " - " + postResponse.body)
}
// フォームデータのPOST
val formResponse = Http("https://api.example.com/login")
.postForm(Seq(
"username" -> "testuser",
"password" -> "secret123"
))
.asString
// クエリパラメータ付きGET
val searchResponse = Http("https://api.example.com/search")
.param("q", "scala")
.param("type", "repository")
.param("sort", "stars")
.asString
// レスポンス詳細処理
val detailedResponse = Http("https://api.example.com/data").asString
detailedResponse match {
case HttpResponse(body, 200, headers) =>
println("成功: " + body)
case HttpResponse(body, 404, _) =>
println("見つかりません: " + body)
case HttpResponse(body, code, _) =>
println(s"エラー $code: $body")
}
高度な設定とカスタマイズ
import scalaj.http._
import scala.concurrent.duration._
// タイムアウト設定
val timeoutResponse = Http("https://api.example.com/slow-endpoint")
.timeout(connTimeoutMs = 5000, readTimeoutMs = 10000)
.asString
// カスタムヘッダー設定
val customHeaderResponse = Http("https://api.example.com/secure")
.header("Authorization", "Bearer your-jwt-token")
.header("X-API-Version", "v2")
.header("User-Agent", "MyScalaApp/1.0")
.asString
// SSL設定(開発環境用)
val unsafeSslResponse = Http("https://localhost:8443/api/data")
.option(HttpOptions.allowUnsafeSSL)
.asString
// プロキシ設定
val proxyResponse = Http("https://api.example.com/data")
.proxy("proxy.example.com", 8080)
.asString
// Basic認証
val basicAuthResponse = Http("https://api.example.com/protected")
.auth("username", "password")
.asString
// 複数オプション組み合わせ
val complexRequest = Http("https://api.example.com/complex")
.timeout(connTimeoutMs = 3000, readTimeoutMs = 15000)
.header("Authorization", "Bearer token123")
.option(HttpOptions.connTimeout(3000))
.option(HttpOptions.readTimeout(15000))
.option(HttpOptions.followRedirects(true))
.asString
// カスタムパーサーによるレスポンス処理
case class User(id: Int, name: String, email: String)
val parsedResponse: HttpResponse[User] = Http("https://api.example.com/user/1")
.execute(parser = { inputStream =>
// カスタムJSONパーサー使用
val jsonString = scala.io.Source.fromInputStream(inputStream).mkString
// JSON解析ロジック(play-json、circe等を使用)
parseUser(jsonString)
})
def parseUser(json: String): User = {
// JSON解析の実装
User(1, "テストユーザー", "[email protected]")
}
OAuth 1.0認証
import scalaj.http._
// OAuth 1.0認証設定
val consumer = Token("consumer-key", "consumer-secret")
val accessToken = Token("access-token", "access-token-secret")
// OAuth認証リクエスト
val oauthResponse = Http("https://api.twitter.com/1.1/statuses/user_timeline.json")
.param("screen_name", "scala_lang")
.oauth(consumer, Some(accessToken))
.asString
if (oauthResponse.code == 200) {
println("Twitter API成功: " + oauthResponse.body)
} else {
println("Twitter API エラー: " + oauthResponse.code)
}
// OAuth リクエストトークン取得
val requestTokenResponse = Http("https://api.twitter.com/oauth/request_token")
.postForm(Seq("oauth_callback" -> "oob"))
.oauth(consumer)
.asToken
requestTokenResponse match {
case Right(token) =>
println("リクエストトークン取得成功: " + token.key)
case Left(errorResponse) =>
println("リクエストトークン取得失敗: " + errorResponse.body)
}
ファイルアップロード
import scalaj.http._
import java.io.File
// ファイルアップロード
val file = new File("/path/to/document.pdf")
val uploadResponse = Http("https://api.example.com/upload")
.postMulti(MultiPart("file", "document.pdf", "application/pdf", file))
.asString
// 複数ファイルとフォームデータ
val multipartResponse = Http("https://api.example.com/upload/multiple")
.postMulti(
MultiPart("title", "ドキュメントタイトル"),
MultiPart("description", "ファイルの説明"),
MultiPart("file1", "doc1.pdf", "application/pdf", new File("/path/to/doc1.pdf")),
MultiPart("file2", "image.jpg", "image/jpeg", new File("/path/to/image.jpg"))
)
.header("Authorization", "Bearer your-token")
.asString
// バイナリデータのアップロード
val binaryData: Array[Byte] = loadBinaryData()
val binaryResponse = Http("https://api.example.com/binary")
.postData(binaryData)
.header("Content-Type", "application/octet-stream")
.asString
def loadBinaryData(): Array[Byte] = {
// バイナリデータ読み込み
Array.emptyByteArray
}
エラーハンドリング
import scalaj.http._
import scala.util.{Try, Success, Failure}
// 基本的なエラーハンドリング
def safeHttpRequest(url: String): Either[String, String] = {
Try {
val response = Http(url).asString
if (response.code >= 200 && response.code < 300) {
Right(response.body)
} else {
Left(s"HTTP Error ${response.code}: ${response.body}")
}
} match {
case Success(result) => result
case Failure(exception) => Left(s"Network Error: ${exception.getMessage}")
}
}
// 使用例
safeHttpRequest("https://api.example.com/data") match {
case Right(data) => println("成功: " + data)
case Left(error) => println("エラー: " + error)
}
// リトライ機能付きリクエスト
def httpWithRetry(url: String, maxRetries: Int = 3): HttpResponse[String] = {
def attempt(retriesLeft: Int): HttpResponse[String] = {
val response = Http(url).asString
if (response.code >= 500 && retriesLeft > 0) {
Thread.sleep(1000) // 1秒待機
attempt(retriesLeft - 1)
} else {
response
}
}
attempt(maxRetries)
}
// 例外を投げるエラーハンドリング
val response = Http("https://api.example.com/data").asString
if (response.is2xx) {
println("成功: " + response.body)
} else if (response.is4xx) {
throw new RuntimeException(s"クライアントエラー: ${response.code}")
} else if (response.is5xx) {
throw new RuntimeException(s"サーバーエラー: ${response.code}")
}
// カスタムエラー処理
case class ApiError(code: Int, message: String, details: String)
def handleApiResponse(response: HttpResponse[String]): Either[ApiError, String] = {
if (response.is2xx) {
Right(response.body)
} else {
val errorMessage = response.code match {
case 400 => "リクエストが不正です"
case 401 => "認証が必要です"
case 403 => "アクセス権限がありません"
case 404 => "リソースが見つかりません"
case 429 => "レート制限に達しました"
case 500 => "サーバー内部エラー"
case _ => "予期しないエラー"
}
Left(ApiError(response.code, errorMessage, response.body))
}
}
移行ガイド(sttpへの移行例)
// scalaj-http (非推奨)
import scalaj.http._
val response = Http("https://api.example.com/users").param("page", "1").asString
// sttp (推奨代替)
import sttp.client3._
val response = basicRequest
.get(uri"https://api.example.com/users?page=1")
.send(backend)
注意: 新規プロジェクトでは、アクティブに開発されているsttp、http4s、またはJava HttpClientの使用を強く推奨します。