Typhoeus

Parallel HTTP request library for Ruby. Achieves high-performance async HTTP communication based on libcurl. Specialized for parallel execution of multiple requests, queuing, and callback processing. Optimized for efficiently handling large volumes of HTTP requests.

HTTP ClientRubyParallel ProcessinglibcurlHigh Performance

GitHub Overview

typhoeus/typhoeus

Typhoeus wraps libcurl in order to make fast and reliable requests.

Stars4,110
Watchers54
Forks441
Created:February 18, 2009
Language:Ruby
License:MIT License

Topics

None

Star History

typhoeus/typhoeus Star History
Data as of: 10/22/2025, 09:54 AM

Library

Typhoeus

Overview

Typhoeus is a high-performance HTTP client library for Ruby that "wraps libcurl to make fast and reliable requests." Leveraging the performance of libcurl implemented in C language, it supports both synchronous and asynchronous HTTP requests. Its greatest feature is the unique Hydra system for parallel request processing, enabling efficient concurrent execution of multiple HTTP requests. It's a library valued in the Ruby ecosystem for scenarios requiring high-performance HTTP communication, suitable for enterprise-level application development.

Details

Typhoeus 2025 edition maintains its solid position as a proven high-performance HTTP client built on the stable foundation of libcurl. It provides easy access to the speed and reliability of libcurl from Ruby, excelling particularly in efficiently processing large volumes of HTTP requests. The Hydra system's parallel processing functionality is a unique strength not found in other Ruby HTTP libraries, capable of simultaneously executing dozens to hundreds of HTTP requests to significantly reduce processing time. It can also be used as a Faraday adapter, making integration into existing applications straightforward.

Key Features

  • Hydra Parallel Processing: Efficient concurrent execution of multiple HTTP requests
  • libcurl Foundation: Fast and stable HTTP communication powered by C language libcurl
  • Faraday Integration: Can be used as a Faraday adapter
  • Rich Authentication Support: Basic authentication, proxy authentication, custom authentication support
  • Streaming Processing: Efficient downloading support for large files
  • Advanced Configuration: Detailed settings for SSL/TLS, cookies, compression, timeouts, etc.

Pros and Cons

Pros

  • Outstanding performance and stability based on libcurl
  • Efficient execution of bulk requests through Hydra parallel processing
  • Comprehensive support for rich HTTP features (authentication, proxy, SSL, compression, etc.)
  • Excellent integration with the Faraday ecosystem
  • Enterprise-level reliability and proven track record
  • Proven results in Ruby community for scraping and API integration

Cons

  • Setup complexity due to native dependency on libcurl
  • High learning cost, excessive for simple HTTP requests
  • Potential challenges with installation on Windows environments
  • Tends to use more memory than pure Ruby implementations
  • Difficult detailed control at Ruby level during debugging
  • Unsuitable for scenarios requiring lightweight HTTP clients

Reference Pages

Code Examples

Installation and Basic Setup

# Add to Gemfile
gem 'typhoeus'

# Install with Bundler
bundle add typhoeus

# Direct installation
gem install typhoeus

# Check libcurl version
curl --version

# Verification in Ruby
ruby -e "require 'typhoeus'; puts Typhoeus::VERSION"

Basic Requests (GET/POST/PUT/DELETE)

require 'typhoeus'

# Basic GET request
response = Typhoeus.get('https://api.example.com/users')
puts response.code       # 200
puts response.body       # Response body
puts response.headers    # Response headers
puts response.total_time # Execution time (seconds)

# GET request with parameters
response = Typhoeus.get(
  'https://api.example.com/users',
  params: { page: 1, limit: 10, sort: 'created_at' }
)
puts response.effective_url  # Actual request URL

# POST request (sending JSON)
user_data = {
  name: 'John Doe',
  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 "User created: ID=#{created_user['id']}"
else
  puts "Error: #{response.code} - #{response.body}"
end

# POST request (sending form data)
response = Typhoeus.post(
  'https://api.example.com/login',
  body: { username: 'testuser', password: 'secret123' }
)

# PUT request (data update)
updated_data = { name: 'Jane Doe', 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 request
response = Typhoeus.delete(
  'https://api.example.com/users/123',
  headers: { 'Authorization' => 'Bearer your-token' }
)

puts "Delete completed" if response.code == 204

# HEAD request (header information only)
response = Typhoeus.head('https://api.example.com/users/123')
puts "Content-Length: #{response.headers['Content-Length']}"
puts "Last-Modified: #{response.headers['Last-Modified']}"

# OPTIONS request (check supported methods)
response = Typhoeus.options('https://api.example.com/users')
puts "Allow: #{response.headers['Allow']}"

Advanced Configuration and Customization (Authentication, Proxy, SSL, etc.)

require 'typhoeus'

# Custom header configuration
headers = {
  'User-Agent' => 'MyApp/1.0 (Ruby Typhoeus)',
  'Accept' => 'application/json',
  'Accept-Language' => 'en-US,ja-JP',
  'X-API-Version' => 'v2',
  'X-Request-ID' => SecureRandom.uuid
}

response = Typhoeus.get('https://api.example.com/data', headers: headers)

# Basic authentication
response = Typhoeus.get(
  'https://api.example.com/private',
  userpwd: 'username:password'
)

# Bearer Token authentication
response = Typhoeus.get(
  'https://api.example.com/protected',
  headers: { 'Authorization' => 'Bearer your-jwt-token' }
)

# Timeout configuration
response = Typhoeus.get(
  'https://api.example.com/slow',
  timeout: 30,           # Overall timeout (seconds)
  connecttimeout: 10     # Connection timeout (seconds)
)

# Redirect configuration
response = Typhoeus.get(
  'https://api.example.com/redirect',
  followlocation: true,  # Automatic redirect following
  maxredirs: 5          # Maximum redirect count
)

# Proxy configuration
response = Typhoeus.get(
  'https://api.example.com/data',
  proxy: 'http://proxy.example.com:8080'
)

# Authenticated proxy
response = Typhoeus.get(
  'https://api.example.com/data',
  proxy: 'http://proxy.example.com:8080',
  proxyuserpwd: 'proxy_user:proxy_password'
)

# SSL/TLS configuration
response = Typhoeus.get(
  'https://secure-api.example.com/data',
  ssl_verifypeer: true,  # SSL certificate verification
  ssl_verifyhost: 2,     # Host name verification
  cainfo: '/path/to/ca-bundle.crt'  # CA certificate bundle
)

# Disable SSL verification (development only)
response = Typhoeus.get(
  'https://self-signed.example.com/',
  ssl_verifypeer: false,
  ssl_verifyhost: 0
)

# Cookie configuration
response = Typhoeus.get(
  'https://api.example.com/user-data',
  cookiefile: '/tmp/cookies.txt',
  cookiejar: '/tmp/cookies.txt'
)

# Enable compression
response = Typhoeus.get(
  'https://api.example.com/large-data',
  accept_encoding: 'gzip'
)

# Set custom User-Agent (global)
Typhoeus::Config.user_agent = 'MyApp/1.0 (Custom Agent)'

# Enable verbose output (for debugging)
Typhoeus::Config.verbose = true

Error Handling and Retry Functionality

require 'typhoeus'

# Comprehensive error handling
def safe_request(url, options = {})
  request = Typhoeus::Request.new(url, options)
  
  request.on_complete do |response|
    if response.success?
      puts "Success: #{response.code}"
      return response
    elsif response.timed_out?
      puts "Timeout error: Request timed out"
    elsif response.code == 0
      puts "Connection error: #{response.return_message}"
    else
      puts "HTTP error: #{response.code} - #{response.body}"
    end
  end
  
  response = request.run
  response
end

# Usage example
response = safe_request('https://api.example.com/data', timeout: 10)

# Request with retry functionality
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  # Exponential backoff
      puts "Retry #{retries}/#{max_retries}: Retrying in #{wait_time} seconds"
      sleep(wait_time)
      retry
    else
      puts "Maximum retries reached: #{e.message}"
      raise
    end
  end
end

# Usage example
begin
  response = request_with_retry(
    'https://api.example.com/unstable',
    { timeout: 10 },
    3
  )
  puts "Success: #{response.body}"
rescue StandardError => e
  puts "Finally failed: #{e.message}"
end

# Status code-specific handling
response = Typhoeus.get('https://api.example.com/status-check')

case response.code
when 200
  puts "Success: #{JSON.parse(response.body)}"
when 401
  puts "Authentication error: Please check your token"
when 403
  puts "Permission error: Access denied"
when 404
  puts "Not found: Resource does not exist"
when 429
  puts "Rate limit: Please wait before retrying"
when 500..599
  puts "Server error: #{response.code} - #{response.body}"
else
  puts "Unexpected status: #{response.code}"
end

# Timeout check
response = Typhoeus.get('https://api.example.com/slow', timeout: 5)
if response.timed_out?
  puts "Request timed out"
end

Hydra Parallel Processing and Batch Requests

require 'typhoeus'

# Basic parallel processing
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'
]

# Add requests to queue
requests = urls.map do |url|
  request = Typhoeus::Request.new(url, followlocation: true)
  hydra.queue(request)
  request
end

# Execute in parallel
hydra.run

# Collect results
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

# Parallel processing with callbacks
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 "Item #{i}: #{data['name']}"
    else
      puts "Error Item #{i}: #{response.code}"
    end
  end
  
  hydra.queue(request)
end

hydra.run
puts "All requests completed"

# Parallel processing with concurrency limit
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 "Completed: #{i} - Status: #{response.code}"
  end
  
  hydra.queue(request)
end

hydra.run

# Dynamic request addition
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)
    
    # Generate additional requests based on response
    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

# Memoization setting (cache)
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  # Second request reuses first result

# Disable memoization
Typhoeus::Config.memoize = false

Streaming Processing and File Operations

require 'typhoeus'

# Streaming download of large files
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 "Download failed: #{response.code}"
  end
  
  content_length = response.headers['Content-Length']
  puts "File size: #{content_length} bytes" if content_length
end

total_downloaded = 0
request.on_body do |chunk|
  downloaded_file.write(chunk)
  total_downloaded += chunk.bytesize
  
  # Progress display
  print "\rDownloading: #{total_downloaded} bytes"
  
  # Conditional download abort
  if total_downloaded > 100 * 1024 * 1024  # 100MB limit
    puts "\nDownload aborted due to file size limit"
    :abort
  end
end

request.on_complete do |response|
  downloaded_file.close
  puts "\nDownload completed" if response.success?
end

request.run

# File upload
file_path = '/path/to/upload.pdf'
response = Typhoeus.post(
  'https://api.example.com/upload',
  body: {
    title: 'Upload File',
    description: 'This is a test file',
    file: File.open(file_path, 'rb')
  },
  headers: {
    'Authorization' => 'Bearer your-token'
  }
)

if response.success?
  upload_result = JSON.parse(response.body)
  puts "Upload completed: #{upload_result['file_id']}"
else
  puts "Upload failed: #{response.code}"
end

# Multiple file upload in multipart format
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'
  }
)

# Response streaming processing
buffer = StringIO.new
request = Typhoeus::Request.new('https://api.example.com/stream-data')

request.on_body do |chunk|
  buffer.write(chunk)
  
  # Process each chunk
  lines = buffer.string.split("\n")
  
  # Process only complete lines
  complete_lines = lines[0..-2]
  buffer.string = lines.last || ""
  
  complete_lines.each do |line|
    begin
      data = JSON.parse(line)
      puts "Received data: #{data['timestamp']} - #{data['message']}"
    rescue JSON::ParserError
      # Ignore non-JSON lines
    end
  end
end

request.run

Faraday Integration and Practical Usage Examples

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

# Single request
response = conn.get('/users/123')
puts response.body

# Parallel processing with 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

# Responses available after parallel processing completion
puts "User: #{@user_response.body['name']}"
puts "Posts count: #{@posts_response.body.size}"
puts "Comments count: #{@comments_response.body.size}"

# API client class
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

# Usage example
client = APIClient.new('https://api.example.com', 'your-token')

# Batch requests
client.batch_requests do
  @users = client.get('/users')
  @categories = client.get('/categories')
  @settings = client.get('/settings')
end

# Web scraping with 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 "Scraping failed: #{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)
    # Simple content extraction example
    html.gsub(/<[^>]*>/, '').strip[0..200]
  end
end

# Usage example
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