Overview
Net::HTTP provides Ruby's standard HTTP client implementation for communicating with web servers and APIs. The library handles HTTP/1.1 protocol operations including request methods, response processing, connection management, and SSL/TLS encryption. Ruby includes Net::HTTP in its standard library, making it available without external dependencies.
The primary class Net::HTTP
manages connections to HTTP servers, while request classes like Net::HTTP::Get
and Net::HTTP::Post
represent specific HTTP methods. Response objects inherit from Net::HTTPResponse
and provide access to status codes, headers, and body content. The library supports persistent connections, chunked transfer encoding, and various authentication mechanisms.
require 'net/http'
# Basic GET request
uri = URI('https://api.example.com/users')
response = Net::HTTP.get_response(uri)
puts response.code # => "200"
puts response.body # => JSON response
Net::HTTP operates through three main patterns: convenience methods for simple requests, instance-based connections for multiple requests, and start blocks for fine-grained control. The convenience methods handle connection lifecycle automatically, while instance methods provide control over connection reuse and configuration.
# Instance-based approach
http = Net::HTTP.new('api.example.com', 443)
http.use_ssl = true
response = http.get('/users/1')
The library integrates with Ruby's URI class for URL parsing and supports both block-based and direct response handling. Request customization occurs through headers, request bodies, and connection parameters. Net::HTTP handles common HTTP features including redirects, cookies through manual header management, and content encoding.
Basic Usage
Net::HTTP supports multiple request methods through dedicated classes and convenience methods. GET requests retrieve data from servers, while POST requests send data for processing. PUT and DELETE methods handle resource updates and removal respectively.
require 'net/http'
require 'uri'
# GET request with URI parsing
uri = URI('https://jsonplaceholder.typicode.com/posts/1')
response = Net::HTTP.get_response(uri)
if response.is_a?(Net::HTTPSuccess)
puts response.body
else
puts "Request failed: #{response.code}"
end
POST requests require request body data and appropriate headers. The library supports form data, JSON payloads, and custom content types. Headers set through the request object control server interpretation of the request body.
# POST request with JSON data
require 'json'
uri = URI('https://jsonplaceholder.typicode.com/posts')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request['Content-Type'] = 'application/json'
request.body = {
title: 'New Post',
body: 'Post content here',
userId: 1
}.to_json
response = http.request(request)
puts response.code
puts response.body
Form data submissions use URL-encoded format or multipart encoding. Net::HTTP handles URL encoding through the URI module, while form data requires manual construction or external libraries for complex multipart data.
# Form data POST
uri = URI('https://httpbin.org/post')
response = Net::HTTP.post_form(uri, {
'field1' => 'value1',
'field2' => 'value2'
})
puts response.body
Persistent connections improve performance for multiple requests to the same server. The start block ensures proper connection closure, while the instance approach requires manual connection management.
# Persistent connection with start block
Net::HTTP.start('api.example.com', 443, use_ssl: true) do |http|
# Multiple requests share the same connection
response1 = http.get('/users/1')
response2 = http.get('/users/2')
response3 = http.post('/users', 'name=John&email=john@example.com')
end
Request headers customize server behavior and client identification. Common headers include Accept for content negotiation, Authorization for authentication, and User-Agent for client identification.
# Custom headers and authentication
uri = URI('https://api.github.com/user')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Get.new(uri)
request['Authorization'] = 'Bearer your-token-here'
request['Accept'] = 'application/vnd.github.v3+json'
request['User-Agent'] = 'MyApp/1.0'
response = http.request(request)
Error Handling & Debugging
Net::HTTP raises specific exceptions for different failure conditions. Network-level problems generate Errno
exceptions, while HTTP-level issues create Net::HTTP
exceptions. Timeout conditions raise Net::TimeoutError
, and SSL problems generate OpenSSL::SSL::SSLError
.
require 'net/http'
require 'timeout'
def robust_http_request(uri)
begin
response = Net::HTTP.get_response(uri)
case response
when Net::HTTPSuccess
response.body
when Net::HTTPRedirection
handle_redirect(response)
when Net::HTTPClientError
raise "Client error: #{response.code} #{response.message}"
when Net::HTTPServerError
raise "Server error: #{response.code} #{response.message}"
end
rescue Net::TimeoutError => e
raise "Request timeout: #{e.message}"
rescue Net::HTTPError => e
raise "HTTP error: #{e.message}"
rescue Errno::ECONNREFUSED => e
raise "Connection refused: #{e.message}"
rescue Errno::EHOSTUNREACH => e
raise "Host unreachable: #{e.message}"
rescue OpenSSL::SSL::SSLError => e
raise "SSL error: #{e.message}"
rescue StandardError => e
raise "Unexpected error: #{e.message}"
end
end
Response validation prevents processing of invalid or unexpected responses. Status code checking determines successful requests, while header validation ensures expected content types and encoding.
def validate_response(response)
# Check status code ranges
unless (200..299).include?(response.code.to_i)
raise "HTTP #{response.code}: #{response.message}"
end
# Validate content type
content_type = response['Content-Type']
unless content_type&.include?('application/json')
raise "Unexpected content type: #{content_type}"
end
# Check required headers
if response['Content-Length'].nil? && response['Transfer-Encoding'] != 'chunked'
warn "Response missing content length information"
end
response
end
Connection debugging involves examining network traffic and connection states. Ruby's logging capabilities help track request progression and identify bottlenecks or failures.
# Enable debug output
http = Net::HTTP.new('example.com', 443)
http.use_ssl = true
http.set_debug_output($stdout) # Prints HTTP traffic
# Custom debugging with logging
require 'logger'
logger = Logger.new('http_debug.log')
def debug_request(uri, logger)
start_time = Time.now
begin
response = Net::HTTP.get_response(uri)
duration = Time.now - start_time
logger.info "Request: #{uri}"
logger.info "Status: #{response.code}"
logger.info "Duration: #{duration.round(3)}s"
logger.info "Response size: #{response.body.length} bytes"
response
rescue => e
logger.error "Request failed: #{e.message}"
logger.error "Duration: #{(Time.now - start_time).round(3)}s"
raise
end
end
SSL certificate validation requires proper configuration to avoid security vulnerabilities. Development environments often disable verification, but production code must validate certificates properly.
# SSL configuration and validation
uri = URI('https://example.com')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
# Production: verify certificates (default)
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.ca_file = '/path/to/ca-bundle.crt' # System CA bundle
# Custom certificate validation
http.verify_callback = proc do |preverify_ok, ssl_context|
if preverify_ok
true
else
cert = ssl_context.current_cert
puts "SSL Certificate verification failed: #{cert.subject}"
false # Reject invalid certificates
end
end
Production Patterns
Production deployment of Net::HTTP requires timeout configuration to prevent indefinite blocking. Connection timeouts control initial connection establishment, while read timeouts limit response waiting periods. Write timeouts prevent hanging during request body transmission.
class HttpClient
def initialize(base_url)
@uri = URI(base_url)
@http = Net::HTTP.new(@uri.host, @uri.port)
if @uri.scheme == 'https'
@http.use_ssl = true
@http.verify_mode = OpenSSL::SSL::VERIFY_PEER
end
# Production timeout settings
@http.open_timeout = 10 # Connection establishment
@http.read_timeout = 30 # Response reading
@http.write_timeout = 10 # Request body writing
@http.ssl_timeout = 10 # SSL handshake
end
def get(path, headers = {})
request = Net::HTTP::Get.new(path)
headers.each { |key, value| request[key] = value }
execute_request(request)
end
private
def execute_request(request)
retries = 0
begin
@http.request(request)
rescue Net::TimeoutError, Errno::ECONNRESET => e
retries += 1
if retries <= 3
sleep(0.5 * retries) # Exponential backoff
retry
else
raise e
end
end
end
end
Connection pooling improves performance by reusing established connections. Ruby's Net::HTTP supports persistent connections within start blocks, but application-level pooling provides better resource management across multiple requests.
class ConnectionPool
def initialize(host, port, pool_size: 5)
@host = host
@port = port
@pool_size = pool_size
@pool = Queue.new
@created = 0
@mutex = Mutex.new
end
def with_connection
connection = acquire_connection
begin
yield connection
ensure
release_connection(connection)
end
end
private
def acquire_connection
if @pool.empty? && @created < @pool_size
@mutex.synchronize do
if @created < @pool_size
@created += 1
return create_connection
end
end
end
@pool.pop # Blocks until connection available
end
def release_connection(connection)
@pool.push(connection) if connection_valid?(connection)
end
def create_connection
http = Net::HTTP.new(@host, @port)
http.use_ssl = (@port == 443)
http.start
http
end
def connection_valid?(connection)
connection.started? && !connection.finished?
rescue
false
end
end
Monitoring and observability require request tracking, performance measurement, and error aggregation. Production applications instrument HTTP requests to identify slow endpoints and connection problems.
module HttpInstrumentation
def self.instrument_request(uri, &block)
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
request_id = SecureRandom.uuid
begin
Rails.logger.info "HTTP_REQUEST_START", {
request_id: request_id,
uri: uri.to_s,
timestamp: Time.current.iso8601
}
result = yield
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
Rails.logger.info "HTTP_REQUEST_COMPLETE", {
request_id: request_id,
status: result.code,
duration_ms: (duration * 1000).round(2),
response_size: result.body.length
}
# Send metrics to monitoring system
StatsD.histogram('http_client.request_duration', duration * 1000, tags: [
"host:#{uri.host}",
"status:#{result.code}"
])
result
rescue => error
duration = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time
Rails.logger.error "HTTP_REQUEST_ERROR", {
request_id: request_id,
error_class: error.class.name,
error_message: error.message,
duration_ms: (duration * 1000).round(2)
}
StatsD.increment('http_client.request_error', tags: [
"host:#{uri.host}",
"error:#{error.class.name}"
])
raise error
end
end
end
Circuit breaker patterns protect against cascading failures by temporarily disabling requests to failing services. This prevents resource exhaustion and allows failing services time to recover.
class CircuitBreaker
STATES = [:closed, :open, :half_open].freeze
def initialize(failure_threshold: 5, recovery_timeout: 30)
@failure_threshold = failure_threshold
@recovery_timeout = recovery_timeout
@failure_count = 0
@last_failure_time = nil
@state = :closed
@mutex = Mutex.new
end
def call
case @state
when :closed
execute_request { yield }
when :open
if time_to_attempt_reset?
attempt_reset { yield }
else
raise CircuitBreakerOpenError, "Circuit breaker is open"
end
when :half_open
execute_request { yield }
end
end
private
def execute_request
begin
result = yield
on_success
result
rescue => error
on_failure
raise error
end
end
def on_success
@mutex.synchronize do
@failure_count = 0
@state = :closed
end
end
def on_failure
@mutex.synchronize do
@failure_count += 1
@last_failure_time = Time.current
if @failure_count >= @failure_threshold
@state = :open
end
end
end
def time_to_attempt_reset?
Time.current - @last_failure_time > @recovery_timeout
end
def attempt_reset
@mutex.synchronize { @state = :half_open }
execute_request { yield }
end
end
class CircuitBreakerOpenError < StandardError; end
Reference
Core Classes
Class | Description |
---|---|
Net::HTTP |
Main HTTP client class for connection management |
Net::HTTP::Get |
GET request representation |
Net::HTTP::Post |
POST request representation |
Net::HTTP::Put |
PUT request representation |
Net::HTTP::Delete |
DELETE request representation |
Net::HTTP::Head |
HEAD request representation |
Net::HTTP::Options |
OPTIONS request representation |
Net::HTTP::Patch |
PATCH request representation |
Net::HTTP Instance Methods
Method | Parameters | Returns | Description |
---|---|---|---|
#new(address, port) |
address (String), port (Integer) |
Net::HTTP |
Creates new HTTP connection |
#start(&block) |
Block (optional) | Net::HTTP or block result |
Opens connection, yields to block if given |
#finish |
None | Net::HTTP |
Closes connection |
#started? |
None | Boolean |
Connection status check |
#use_ssl=(flag) |
flag (Boolean) |
Boolean |
Enables/disables SSL |
#request(request) |
request (Net::HTTPRequest) |
Net::HTTPResponse |
Executes HTTP request |
#get(path, headers) |
path (String), headers (Hash) |
Net::HTTPResponse |
Performs GET request |
#post(path, data, headers) |
path (String), data (String), headers (Hash) |
Net::HTTPResponse |
Performs POST request |
Timeout Configuration
Property | Type | Default | Description |
---|---|---|---|
open_timeout |
Integer |
60 | Connection establishment timeout (seconds) |
read_timeout |
Integer |
60 | Response reading timeout (seconds) |
write_timeout |
Integer |
60 | Request writing timeout (seconds) |
ssl_timeout |
Integer |
nil | SSL handshake timeout (seconds) |
continue_timeout |
Integer |
nil | 100 Continue response timeout (seconds) |
keep_alive_timeout |
Integer |
2 | Keep-alive probe timeout (seconds) |
SSL Configuration
Property | Type | Description |
---|---|---|
use_ssl |
Boolean |
Enable SSL/TLS connection |
verify_mode |
Integer |
Certificate verification mode |
ca_file |
String |
Path to CA certificate file |
ca_path |
String |
Path to CA certificate directory |
cert |
OpenSSL::X509::Certificate |
Client certificate |
key |
OpenSSL::PKey |
Client private key |
ssl_version |
Symbol |
SSL/TLS protocol version |
ciphers |
String/Array |
Allowed cipher suites |
Response Classes
Class | Status Range | Description |
---|---|---|
Net::HTTPSuccess |
200-299 | Successful responses |
Net::HTTPRedirection |
300-399 | Redirection responses |
Net::HTTPClientError |
400-499 | Client error responses |
Net::HTTPServerError |
500-599 | Server error responses |
Net::HTTPInformation |
100-199 | Informational responses |
Common Response Status Codes
Code | Class | Constant |
---|---|---|
200 | Net::HTTPOK |
Net::HTTP::OK |
201 | Net::HTTPCreated |
Net::HTTP::Created |
301 | Net::HTTPMovedPermanently |
Net::HTTP::MovedPermanently |
302 | Net::HTTPFound |
Net::HTTP::Found |
400 | Net::HTTPBadRequest |
Net::HTTP::BadRequest |
401 | Net::HTTPUnauthorized |
Net::HTTP::Unauthorized |
404 | Net::HTTPNotFound |
Net::HTTP::NotFound |
500 | Net::HTTPInternalServerError |
Net::HTTP::InternalServerError |
Exception Hierarchy
StandardError
├── Net::HTTPError
│ ├── Net::HTTPClientException
│ ├── Net::HTTPFatalError
│ └── Net::HTTPRetriableError
├── Net::TimeoutError
├── Errno::ECONNREFUSED
├── Errno::EHOSTUNREACH
└── OpenSSL::SSL::SSLError
Convenience Methods
Method | Parameters | Returns | Description |
---|---|---|---|
Net::HTTP.get(uri) |
uri (URI/String) |
String |
GET request body |
Net::HTTP.get_response(uri) |
uri (URI/String) |
Net::HTTPResponse |
GET request response |
Net::HTTP.post_form(uri, params) |
uri (URI), params (Hash) |
Net::HTTPResponse |
POST form data |
Net::HTTP.start(host, port, &block) |
host (String), port (Integer), block |
Block result | Connection with start block |
Authentication Methods
Type | Header Format | Example |
---|---|---|
Basic | Authorization: Basic <base64> |
request['Authorization'] = 'Basic ' + ['user:pass'].pack('m0') |
Bearer | Authorization: Bearer <token> |
request['Authorization'] = 'Bearer token123' |
Digest | Authorization: Digest <params> |
Calculated using challenge response |
API Key | X-API-Key: <key> |
request['X-API-Key'] = 'your-api-key' |
Content-Type Headers
Type | Header Value | Usage |
---|---|---|
JSON | application/json |
API requests with JSON payloads |
Form | application/x-www-form-urlencoded |
HTML form submissions |
Multipart | multipart/form-data |
File uploads with forms |
XML | application/xml |
XML document transmission |
Plain Text | text/plain |
Simple text content |
Binary | application/octet-stream |
Binary file transfers |