Typhoeus
Ruby向けの並列HTTPリクエストライブラリ。libcurl基盤により高性能な非同期HTTP通信を実現。複数リクエストの並列実行、キューイング、コールバック処理に特化。大量のHTTPリクエストを効率的に処理する用途に最適化。
GitHub概要
typhoeus/typhoeus
Typhoeus wraps libcurl in order to make fast and reliable requests.
スター4,110
ウォッチ54
フォーク441
作成日:2009年2月18日
言語:Ruby
ライセンス:MIT License
トピックス
なし
スター履歴
データ取得日時: 2025/10/22 09:54
ライブラリ
Typhoeus
概要
Typhoeus は「libcurl をラップして高速で信頼性の高いリクエストを作成する」Ruby用の高性能HTTPクライアントライブラリです。C言語で実装されたlibcurlの性能を活用し、同期・非同期両方のHTTPリクエストに対応。独自のHydraシステムによる並列リクエスト処理が最大の特徴で、複数のHTTPリクエストを効率的に同時実行可能。Ruby エコシステムにおいて高性能なHTTP通信が求められる場面で重宝される、企業レベルのアプリケーション開発に適したライブラリです。
詳細
Typhoeus 2025年版は libcurl の安定した基盤の上に構築された実証済みの高性能HTTPクライアントとして確固たる地位を維持しています。C言語のlibcurlが提供する高速性と信頼性をRubyから簡単に利用でき、特に大量のHTTPリクエストを効率的に処理することに長けています。Hydraシステムによる並列処理機能は他のRuby HTTPライブラリにはない独自の強みで、数十〜数百のHTTPリクエストを同時に実行して処理時間を大幅に短縮可能。Faradayアダプターとしても利用でき、既存のアプリケーションへの統合も容易です。
主な特徴
- Hydra並列処理: 複数HTTPリクエストの効率的な同時実行
- libcurl基盤: C言語libcurlによる高速で安定したHTTP通信
- Faraday統合: Faradayアダプターとしての利用が可能
- 豊富な認証サポート: Basic認証、プロキシ認証、カスタム認証対応
- ストリーミング処理: 大容量ファイルの効率的なダウンロード対応
- 高度な設定: SSL/TLS、Cookie、圧縮、タイムアウト等の詳細設定
メリット・デメリット
メリット
- libcurlベースによる圧倒的な性能と安定性
- Hydraによる並列処理で大量リクエストの効率的な実行が可能
- 豊富なHTTP機能(認証、プロキシ、SSL、圧縮等)の包括的サポート
- Faradayエコシステムとの優れた統合性
- エンタープライズレベルの信頼性と実績
- RubyコミュニティでのScrapingやAPI統合での実績
デメリット
- libcurlへのネイティブ依存によるセットアップの複雑さ
- 学習コストが高く簡単なHTTPリクエストには過剰
- Windows環境でのインストールに課題がある場合
- メモリ使用量が純Ruby実装より多い傾向
- デバッグ時にRubyレベルでの詳細制御が困難
- 軽量なHTTPクライアントが求められる場面には不向き
参考ページ
書き方の例
インストールと基本セットアップ
# Gemfileに追加
gem 'typhoeus'
# Bundlerでインストール
bundle add typhoeus
# 直接インストール
gem install typhoeus
# libcurlバージョン確認
curl --version
# Rubyでの確認
ruby -e "require 'typhoeus'; puts Typhoeus::VERSION"
基本的なリクエスト(GET/POST/PUT/DELETE)
require 'typhoeus'
# 基本的なGETリクエスト
response = Typhoeus.get('https://api.example.com/users')
puts response.code # 200
puts response.body # レスポンスボディ
puts response.headers # レスポンスヘッダー
puts response.total_time # 実行時間(秒)
# パラメータ付きGETリクエスト
response = Typhoeus.get(
'https://api.example.com/users',
params: { page: 1, limit: 10, sort: 'created_at' }
)
puts response.effective_url # 実際のリクエストURL
# POSTリクエスト(JSON送信)
user_data = {
name: '田中太郎',
email: '[email protected]',
age: 30
}
response = Typhoeus.post(
'https://api.example.com/users',
body: user_data.to_json,
headers: {
'Content-Type' => 'application/json',
'Authorization' => 'Bearer your-token',
'Accept' => 'application/json'
}
)
if response.success?
created_user = JSON.parse(response.body)
puts "ユーザー作成完了: ID=#{created_user['id']}"
else
puts "エラー: #{response.code} - #{response.body}"
end
# POSTリクエスト(フォームデータ送信)
response = Typhoeus.post(
'https://api.example.com/login',
body: { username: 'testuser', password: 'secret123' }
)
# PUTリクエスト(データ更新)
updated_data = { name: '田中次郎', email: '[email protected]' }
response = Typhoeus.put(
'https://api.example.com/users/123',
body: updated_data.to_json,
headers: {
'Content-Type' => 'application/json',
'Authorization' => 'Bearer your-token'
}
)
# DELETEリクエスト
response = Typhoeus.delete(
'https://api.example.com/users/123',
headers: { 'Authorization' => 'Bearer your-token' }
)
puts "削除完了" if response.code == 204
# HEADリクエスト(ヘッダー情報のみ取得)
response = Typhoeus.head('https://api.example.com/users/123')
puts "Content-Length: #{response.headers['Content-Length']}"
puts "Last-Modified: #{response.headers['Last-Modified']}"
# OPTIONSリクエスト(対応メソッド確認)
response = Typhoeus.options('https://api.example.com/users')
puts "Allow: #{response.headers['Allow']}"
高度な設定とカスタマイズ(認証、プロキシ、SSL等)
require 'typhoeus'
# カスタムヘッダー設定
headers = {
'User-Agent' => 'MyApp/1.0 (Ruby Typhoeus)',
'Accept' => 'application/json',
'Accept-Language' => 'ja-JP,en-US',
'X-API-Version' => 'v2',
'X-Request-ID' => SecureRandom.uuid
}
response = Typhoeus.get('https://api.example.com/data', headers: headers)
# Basic認証
response = Typhoeus.get(
'https://api.example.com/private',
userpwd: 'username:password'
)
# Bearer Token認証
response = Typhoeus.get(
'https://api.example.com/protected',
headers: { 'Authorization' => 'Bearer your-jwt-token' }
)
# タイムアウト設定
response = Typhoeus.get(
'https://api.example.com/slow',
timeout: 30, # 全体タイムアウト(秒)
connecttimeout: 10 # 接続タイムアウト(秒)
)
# リダイレクト設定
response = Typhoeus.get(
'https://api.example.com/redirect',
followlocation: true, # リダイレクト自動追従
maxredirs: 5 # 最大リダイレクト回数
)
# プロキシ設定
response = Typhoeus.get(
'https://api.example.com/data',
proxy: 'http://proxy.example.com:8080'
)
# 認証付きプロキシ
response = Typhoeus.get(
'https://api.example.com/data',
proxy: 'http://proxy.example.com:8080',
proxyuserpwd: 'proxy_user:proxy_password'
)
# SSL/TLS設定
response = Typhoeus.get(
'https://secure-api.example.com/data',
ssl_verifypeer: true, # SSL証明書検証
ssl_verifyhost: 2, # ホスト名検証
cainfo: '/path/to/ca-bundle.crt' # CA証明書バンドル
)
# SSL検証無効化(開発環境のみ)
response = Typhoeus.get(
'https://self-signed.example.com/',
ssl_verifypeer: false,
ssl_verifyhost: 0
)
# Cookie設定
response = Typhoeus.get(
'https://api.example.com/user-data',
cookiefile: '/tmp/cookies.txt',
cookiejar: '/tmp/cookies.txt'
)
# 圧縮有効化
response = Typhoeus.get(
'https://api.example.com/large-data',
accept_encoding: 'gzip'
)
# カスタムUser-Agent設定(グローバル)
Typhoeus::Config.user_agent = 'MyApp/1.0 (Custom Agent)'
# Verbose出力有効化(デバッグ用)
Typhoeus::Config.verbose = true
エラーハンドリングとリトライ機能
require 'typhoeus'
# 包括的なエラーハンドリング
def safe_request(url, options = {})
request = Typhoeus::Request.new(url, options)
request.on_complete do |response|
if response.success?
puts "成功: #{response.code}"
return response
elsif response.timed_out?
puts "タイムアウトエラー: リクエストがタイムアウトしました"
elsif response.code == 0
puts "接続エラー: #{response.return_message}"
else
puts "HTTPエラー: #{response.code} - #{response.body}"
end
end
response = request.run
response
end
# 使用例
response = safe_request('https://api.example.com/data', timeout: 10)
# リトライ機能付きリクエスト
def request_with_retry(url, options = {}, max_retries = 3)
retries = 0
begin
response = Typhoeus.get(url, options)
if response.success?
return response
elsif response.timed_out? || response.code >= 500
raise StandardError, "Retryable error: #{response.code}"
else
raise StandardError, "Non-retryable error: #{response.code}"
end
rescue StandardError => e
retries += 1
if retries <= max_retries
wait_time = 2 ** retries # 指数バックオフ
puts "リトライ #{retries}/#{max_retries}: #{wait_time}秒後に再試行"
sleep(wait_time)
retry
else
puts "最大リトライ回数に達しました: #{e.message}"
raise
end
end
end
# 使用例
begin
response = request_with_retry(
'https://api.example.com/unstable',
{ timeout: 10 },
3
)
puts "成功: #{response.body}"
rescue StandardError => e
puts "最終的に失敗: #{e.message}"
end
# ステータスコード別処理
response = Typhoeus.get('https://api.example.com/status-check')
case response.code
when 200
puts "正常: #{JSON.parse(response.body)}"
when 401
puts "認証エラー: トークンを確認してください"
when 403
puts "権限エラー: アクセス権限がありません"
when 404
puts "見つかりません: リソースが存在しません"
when 429
puts "レート制限: しばらく待ってから再試行してください"
when 500..599
puts "サーバーエラー: #{response.code} - #{response.body}"
else
puts "予期しないステータス: #{response.code}"
end
# タイムアウトチェック
response = Typhoeus.get('https://api.example.com/slow', timeout: 5)
if response.timed_out?
puts "リクエストがタイムアウトしました"
end
Hydra並列処理とバッチリクエスト
require 'typhoeus'
# 基本的な並列処理
hydra = Typhoeus::Hydra.new
urls = [
'https://api.example.com/users',
'https://api.example.com/posts',
'https://api.example.com/comments',
'https://api.example.com/categories'
]
# リクエストをキューに追加
requests = urls.map do |url|
request = Typhoeus::Request.new(url, followlocation: true)
hydra.queue(request)
request
end
# 並列実行
hydra.run
# 結果を収集
responses = requests.map do |request|
{
url: request.url,
status: request.response.code,
body: request.response.body,
time: request.response.total_time
}
end
responses.each do |response|
puts "#{response[:url]}: #{response[:status]} (#{response[:time]}s)"
end
# コールバック付き並列処理
hydra = Typhoeus::Hydra.new
10.times do |i|
request = Typhoeus::Request.new("https://api.example.com/items/#{i}")
request.on_complete do |response|
if response.success?
data = JSON.parse(response.body)
puts "アイテム #{i}: #{data['name']}"
else
puts "エラー アイテム #{i}: #{response.code}"
end
end
hydra.queue(request)
end
hydra.run
puts "全てのリクエスト完了"
# 並行数制限付き並列処理
hydra = Typhoeus::Hydra.new(max_concurrency: 5)
50.times do |i|
request = Typhoeus::Request.new("https://api.example.com/data/#{i}")
request.on_complete do |response|
puts "処理完了: #{i} - ステータス: #{response.code}"
end
hydra.queue(request)
end
hydra.run
# 動的リクエスト追加
hydra = Typhoeus::Hydra.new
first_request = Typhoeus::Request.new('https://api.example.com/posts/1')
first_request.on_complete do |response|
if response.success?
data = JSON.parse(response.body)
# レスポンスに基づいて追加リクエストを生成
data['related_urls'].each do |url|
related_request = Typhoeus::Request.new(url)
hydra.queue(related_request)
end
end
end
hydra.queue(first_request)
hydra.run
# メモ化設定(キャッシュ)
Typhoeus::Config.memoize = true
hydra = Typhoeus::Hydra.new
2.times do
hydra.queue(Typhoeus::Request.new('https://api.example.com/cache-test'))
end
hydra.run # 2回目は1回目の結果を再利用
# メモ化無効化
Typhoeus::Config.memoize = false
ストリーミング処理とファイル操作
require 'typhoeus'
# 大容量ファイルのストリーミングダウンロード
downloaded_file = File.open('large_file.zip', 'wb')
request = Typhoeus::Request.new('https://api.example.com/files/large.zip')
request.on_headers do |response|
if response.code != 200
downloaded_file.close
File.delete('large_file.zip')
raise "ダウンロード失敗: #{response.code}"
end
content_length = response.headers['Content-Length']
puts "ファイルサイズ: #{content_length}バイト" if content_length
end
total_downloaded = 0
request.on_body do |chunk|
downloaded_file.write(chunk)
total_downloaded += chunk.bytesize
# 進捗表示
print "\rダウンロード中: #{total_downloaded}バイト"
# 条件によるダウンロード中止
if total_downloaded > 100 * 1024 * 1024 # 100MB制限
puts "\nファイルサイズ制限によりダウンロード中止"
:abort
end
end
request.on_complete do |response|
downloaded_file.close
puts "\nダウンロード完了" if response.success?
end
request.run
# ファイルアップロード
file_path = '/path/to/upload.pdf'
response = Typhoeus.post(
'https://api.example.com/upload',
body: {
title: 'アップロードファイル',
description: 'テストファイルです',
file: File.open(file_path, 'rb')
},
headers: {
'Authorization' => 'Bearer your-token'
}
)
if response.success?
upload_result = JSON.parse(response.body)
puts "アップロード完了: #{upload_result['file_id']}"
else
puts "アップロード失敗: #{response.code}"
end
# マルチパート形式での複数ファイルアップロード
response = Typhoeus.post(
'https://api.example.com/upload-multiple',
body: {
document1: File.open('file1.pdf', 'rb'),
document2: File.open('file2.docx', 'rb'),
metadata: { category: 'documents', public: false }.to_json
},
headers: {
'Authorization' => 'Bearer your-token'
}
)
# レスポンスストリーミング処理
buffer = StringIO.new
request = Typhoeus::Request.new('https://api.example.com/stream-data')
request.on_body do |chunk|
buffer.write(chunk)
# チャンクごとの処理
lines = buffer.string.split("\n")
# 完全な行のみ処理
complete_lines = lines[0..-2]
buffer.string = lines.last || ""
complete_lines.each do |line|
begin
data = JSON.parse(line)
puts "受信データ: #{data['timestamp']} - #{data['message']}"
rescue JSON::ParserError
# JSON以外の行は無視
end
end
end
request.run
Faraday統合と実用的な活用例
require 'faraday'
require 'typhoeus'
require 'typhoeus/adapters/faraday'
# Faraday with Typhoeus adapter
conn = Faraday.new(url: 'https://api.example.com') do |builder|
builder.request :url_encoded
builder.response :json
builder.adapter :typhoeus
end
# 単一リクエスト
response = conn.get('/users/123')
puts response.body
# Faradayでの並列処理
conn.in_parallel do
@user_response = conn.get('/users/123')
@posts_response = conn.get('/users/123/posts')
@comments_response = conn.get('/users/123/comments')
end
# 並列処理完了後にレスポンス利用可能
puts "ユーザー: #{@user_response.body['name']}"
puts "投稿数: #{@posts_response.body.size}"
puts "コメント数: #{@comments_response.body.size}"
# API クライアントクラス
class APIClient
def initialize(base_url, token = nil)
@conn = Faraday.new(url: base_url) do |builder|
builder.request :json
builder.response :json
builder.adapter :typhoeus
end
@conn.headers['Authorization'] = "Bearer #{token}" if token
@conn.headers['User-Agent'] = 'APIClient/1.0'
end
def get(path, params = {})
@conn.get(path, params)
end
def post(path, data = {})
@conn.post(path, data)
end
def batch_requests(&block)
@conn.in_parallel(&block)
end
end
# 使用例
client = APIClient.new('https://api.example.com', 'your-token')
# バッチリクエスト
client.batch_requests do
@users = client.get('/users')
@categories = client.get('/categories')
@settings = client.get('/settings')
end
# WebスクレイピングでのTyphoeus活用
class WebScraper
def initialize
@hydra = Typhoeus::Hydra.new(max_concurrency: 10)
end
def scrape_urls(urls)
results = []
urls.each do |url|
request = Typhoeus::Request.new(url,
followlocation: true,
timeout: 30,
headers: {
'User-Agent' => 'Mozilla/5.0 (compatible; WebScraper/1.0)'
}
)
request.on_complete do |response|
if response.success?
results << {
url: url,
title: extract_title(response.body),
content: extract_content(response.body),
status: response.code
}
else
puts "スクレイピング失敗: #{url} - #{response.code}"
end
end
@hydra.queue(request)
end
@hydra.run
results
end
private
def extract_title(html)
html.match(/<title>(.*?)<\/title>/i)&.captures&.first&.strip
end
def extract_content(html)
# 簡単なコンテンツ抽出例
html.gsub(/<[^>]*>/, '').strip[0..200]
end
end
# 使用例
scraper = WebScraper.new
urls = [
'https://example.com/page1',
'https://example.com/page2',
'https://example.com/page3'
]
results = scraper.scrape_urls(urls)
results.each do |result|
puts "#{result[:title]} - #{result[:url]}"
end