This content originally appeared on DEV Community and was authored by Davide Santangelo
Ruby, known for its elegant syntax and developer-friendly ecosystem, offers robust tools for networking tasks. Whether you're building web clients, servers, or handling low-level socket communications, Ruby provides built-in modules and libraries that make networking straightforward and powerful. This comprehensive guide explores Ruby's networking capabilities, from basic socket operations to HTTP requests, with practical examples to get you started.
Table of Contents
- Introduction to Networking in Ruby
- Understanding Ruby's Net Module
- Low-Level Networking: Socket Programming
- High-Level Networking: Using Net::HTTP
- Advanced Networking Libraries and Gems
- Best Practices and Security
- Real-World Applications
- Conclusion
Introduction to Networking in Ruby {#introduction}
Networking in Ruby revolves around sending and receiving data over networks using protocols like TCP, UDP, and HTTP. Ruby's standard library includes the Net
module, which provides high-level abstractions for common networking tasks, particularly HTTP interactions. For more specialized needs, Ruby also supports lower-level socket programming.
Why Choose Ruby for Networking?
Ruby offers several advantages for networking applications:
- Rich ecosystem: Extensive collection of gems for specialized networking tasks
- Built-in tools: No external dependencies required for basic HTTP operations
- Elegant syntax: Clean, readable code that's easy to maintain
- Cross-platform: Works consistently across different operating systems
- Active community: Well-documented libraries and community support
Understanding Ruby's Net Module {#net-module}
The Net
module serves as a namespace for networking-related classes in Ruby's standard library. It's designed primarily for client-server interactions, with HTTP being the primary focus.
Core Components
Net::HTTP
The flagship class for handling HTTP requests and responses. It provides methods for all standard HTTP operations (GET, POST, PUT, DELETE, etc.) and is RFC 2616 compliant.
require 'net/http'
require 'uri'
# Basic usage
uri = URI('https://httpbin.org/get')
response = Net::HTTP.get_response(uri)
puts response.code # => "200"
puts response.body # => JSON response
Net::HTTPHeader
Manages HTTP headers, allowing you to set and retrieve header information:
require 'net/http'
http = Net::HTTP.new('httpbin.org', 443)
http.use_ssl = true
request = Net::HTTP::Get.new('/headers')
request['User-Agent'] = 'Ruby HTTP Client'
request['Accept'] = 'application/json'
response = http.request(request)
puts response.body
Error Handling
The Net module provides comprehensive error handling for network operations:
require 'net/http'
require 'uri'
begin
uri = URI('https://httpbin.org/delay/10')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.read_timeout = 5 # 5 seconds timeout
response = http.get(uri.path)
puts response.body
rescue Net::ReadTimeout => e
puts "Request timed out: #{e.message}"
rescue Net::HTTPError => e
puts "HTTP error: #{e.message}"
rescue StandardError => e
puts "Unexpected error: #{e.message}"
end
Low-Level Networking: Socket Programming {#socket-programming}
For scenarios requiring fine-grained control, Ruby's Socket
class provides direct access to network sockets, supporting both TCP and UDP protocols.
TCP Server Example
Here's a robust TCP server that handles multiple clients:
require 'socket'
class EchoServer
def initialize(host = 'localhost', port = 8080)
@host = host
@port = port
@server = nil
end
def start
@server = TCPServer.new(@host, @port)
puts "Server listening on #{@host}:#{@port}"
loop do
Thread.start(@server.accept) do |client|
handle_client(client)
end
end
rescue Interrupt
puts "\nShutting down server..."
@server&.close
end
private
def handle_client(client)
client_info = client.peeraddr
puts "Client connected: #{client_info[2]}:#{client_info[1]}"
loop do
data = client.gets
break if data.nil? || data.strip.downcase == 'quit'
client.puts "Echo: #{data}"
end
client.close
puts "Client disconnected: #{client_info[2]}:#{client_info[1]}"
rescue StandardError => e
puts "Error handling client: #{e.message}"
client.close
end
end
# Usage
server = EchoServer.new
server.start
TCP Client Example
A corresponding client to interact with the server:
require 'socket'
class EchoClient
def initialize(host = 'localhost', port = 8080)
@host = host
@port = port
end
def connect
@socket = TCPSocket.new(@host, @port)
puts "Connected to #{@host}:#{@port}"
loop do
print "Enter message (or 'quit' to exit): "
message = gets.chomp
@socket.puts message
break if message.downcase == 'quit'
response = @socket.gets
puts "Server response: #{response}"
end
@socket.close
puts "Connection closed"
rescue StandardError => e
puts "Error: #{e.message}"
@socket&.close
end
end
# Usage
client = EchoClient.new
client.connect
UDP Example
For connectionless communication, here's a UDP example:
require 'socket'
# UDP Server
class UDPServer
def initialize(host = 'localhost', port = 8080)
@socket = UDPSocket.new
@socket.bind(host, port)
puts "UDP Server listening on #{host}:#{port}"
end
def start
loop do
data, addr = @socket.recvfrom(1024)
puts "Received from #{addr[2]}:#{addr[1]}: #{data}"
@socket.send("Echo: #{data}", 0, addr[2], addr[1])
end
rescue Interrupt
puts "\nShutting down server..."
@socket.close
end
end
# UDP Client
class UDPClient
def initialize(host = 'localhost', port = 8080)
@socket = UDPSocket.new
@host = host
@port = port
end
def send_message(message)
@socket.send(message, 0, @host, @port)
response, addr = @socket.recvfrom(1024)
puts "Server response: #{response}"
end
def close
@socket.close
end
end
High-Level Networking: Using Net::HTTP {#net-http}
Net::HTTP provides a comprehensive interface for HTTP operations. Here are practical examples for common use cases:
GET Requests with Parameters
require 'net/http'
require 'uri'
def fetch_with_params(base_url, params = {})
uri = URI(base_url)
uri.query = URI.encode_www_form(params) unless params.empty?
response = Net::HTTP.get_response(uri)
case response
when Net::HTTPSuccess
response.body
when Net::HTTPRedirection
location = response['location']
puts "Redirected to: #{location}"
fetch_with_params(location)
else
raise "HTTP Error: #{response.code} #{response.message}"
end
end
# Usage
data = fetch_with_params('https://httpbin.org/get', {
name: 'Ruby',
version: '3.0'
})
puts data
POST Requests with JSON
require 'net/http'
require 'json'
require 'uri'
def post_json(url, data)
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
request = Net::HTTP::Post.new(uri.path)
request['Content-Type'] = 'application/json'
request['Accept'] = 'application/json'
request.body = data.to_json
response = http.request(request)
if response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)
else
raise "HTTP Error: #{response.code} #{response.message}"
end
end
# Usage
payload = {
name: "Ruby Developer",
email: "ruby@example.com",
skills: ["Ruby", "Rails", "Networking"]
}
result = post_json('https://httpbin.org/post', payload)
puts result
File Upload Example
require 'net/http'
require 'uri'
def upload_file(url, file_path, field_name = 'file')
uri = URI(url)
File.open(file_path, 'rb') do |file|
boundary = "----WebKitFormBoundary#{Time.now.to_i}"
post_body = []
post_body << "--#{boundary}\r\n"
post_body << "Content-Disposition: form-data; name=\"#{field_name}\"; filename=\"#{File.basename(file_path)}\"\r\n"
post_body << "Content-Type: application/octet-stream\r\n\r\n"
post_body << file.read
post_body << "\r\n--#{boundary}--\r\n"
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
request = Net::HTTP::Post.new(uri.path)
request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
request.body = post_body.join
response = http.request(request)
response.body
end
end
HTTP Client with Connection Pooling
require 'net/http'
require 'uri'
class HTTPClient
def initialize(base_url, options = {})
@uri = URI(base_url)
@options = {
open_timeout: 10,
read_timeout: 30,
max_retries: 3
}.merge(options)
@http = Net::HTTP.new(@uri.host, @uri.port)
@http.use_ssl = @uri.scheme == 'https'
@http.open_timeout = @options[:open_timeout]
@http.read_timeout = @options[:read_timeout]
@http.start
end
def get(path, params = {})
uri = @uri.dup
uri.path = path
uri.query = URI.encode_www_form(params) unless params.empty?
request = Net::HTTP::Get.new(uri.request_uri)
execute_request(request)
end
def post(path, data, content_type = 'application/json')
request = Net::HTTP::Post.new(path)
request['Content-Type'] = content_type
request.body = data.is_a?(String) ? data : data.to_json
execute_request(request)
end
def close
@http.finish if @http.started?
end
private
def execute_request(request)
retries = 0
begin
response = @http.request(request)
case response
when Net::HTTPSuccess
response.body
when Net::HTTPRedirection
raise "Redirection not supported in this client"
else
raise "HTTP Error: #{response.code} #{response.message}"
end
rescue Net::TimeoutError, Errno::ECONNRESET => e
retries += 1
if retries <= @options[:max_retries]
sleep(2 ** retries) # Exponential backoff
retry
else
raise "Max retries exceeded: #{e.message}"
end
end
end
end
# Usage
client = HTTPClient.new('https://httpbin.org')
result = client.get('/get', { key: 'value' })
puts result
client.close
Advanced Networking Libraries and Gems {#advanced-libraries}
While Ruby's standard library covers basic networking needs, the ecosystem offers powerful gems for specialized tasks:
Popular HTTP Clients
Faraday
A flexible HTTP client library that supports middleware:
require 'faraday'
require 'json'
conn = Faraday.new(url: 'https://httpbin.org') do |faraday|
faraday.request :json
faraday.response :json
faraday.response :logger
faraday.adapter Faraday.default_adapter
end
response = conn.get('/get', { param: 'value' })
puts response.body
HTTParty
A simplified HTTP client with a clean DSL:
require 'httparty'
class APIClient
include HTTParty
base_uri 'https://api.example.com'
def self.get_user(id)
get("/users/#{id}")
end
def self.create_user(user_data)
post('/users', body: user_data.to_json, headers: { 'Content-Type' => 'application/json' })
end
end
SSH and Secure File Transfer
Net::SSH
For remote command execution:
require 'net/ssh'
Net::SSH.start('hostname', 'username', password: 'password') do |ssh|
# Execute a command
result = ssh.exec!('ls -la')
puts result
# Open a channel for more complex operations
ssh.open_channel do |channel|
channel.exec('tail -f /var/log/syslog') do |ch, success|
raise "Command failed" unless success
channel.on_data do |ch, data|
puts data
end
end
end
end
Net::SCP
For secure file transfers:
require 'net/scp'
Net::SCP.start('hostname', 'username', password: 'password') do |scp|
# Upload a file
scp.upload!('/local/file.txt', '/remote/file.txt')
# Download a file
scp.download!('/remote/file.txt', '/local/downloaded_file.txt')
end
Concurrent HTTP Requests
Typhoeus
For high-performance concurrent requests:
require 'typhoeus'
urls = [
'https://httpbin.org/get',
'https://httpbin.org/uuid',
'https://httpbin.org/ip'
]
hydra = Typhoeus::Hydra.new(max_concurrency: 3)
requests = urls.map do |url|
request = Typhoeus::Request.new(url)
hydra.queue(request)
request
end
hydra.run
requests.each do |request|
puts "Response for #{request.url}: #{request.response.code}"
end
Best Practices and Security {#best-practices}
Error Handling and Timeouts
Always implement proper error handling and timeouts:
require 'net/http'
require 'timeout'
def robust_http_request(url, options = {})
uri = URI(url)
timeout_duration = options[:timeout] || 10
Timeout.timeout(timeout_duration) do
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.read_timeout = timeout_duration
http.open_timeout = timeout_duration
response = http.get(uri.path)
case response
when Net::HTTPSuccess
response.body
when Net::HTTPRedirection
# Handle redirects (with loop prevention)
redirect_url = response['location']
raise "Too many redirects" if options[:redirect_count].to_i > 5
robust_http_request(redirect_url, options.merge(redirect_count: options[:redirect_count].to_i + 1))
else
raise "HTTP Error: #{response.code} #{response.message}"
end
end
rescue Timeout::Error
raise "Request timed out after #{timeout_duration} seconds"
rescue StandardError => e
raise "Network error: #{e.message}"
end
Input Validation and Sanitization
require 'uri'
def validate_url(url)
uri = URI.parse(url)
# Check scheme
unless %w[http https].include?(uri.scheme)
raise "Invalid scheme: #{uri.scheme}"
end
# Check for private IP ranges (basic check)
if uri.host =~ /^(10\.|172\.(1[6-9]|2\d|3[01])\.|192\.168\.)/
raise "Private IP addresses not allowed"
end
# Check for localhost
if uri.host =~ /^(localhost|127\.0\.0\.1|::1)$/
raise "Localhost not allowed"
end
uri
rescue URI::InvalidURIError => e
raise "Invalid URL: #{e.message}"
end
SSL/TLS Configuration
require 'net/http'
require 'openssl'
def secure_http_request(url)
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
# Configure SSL options
http.ssl_version = :TLSv1_2
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
http.ca_file = '/path/to/ca-bundle.crt' # System CA bundle
# Optional: Certificate pinning
http.verify_callback = proc do |preverify_ok, ssl_context|
if preverify_ok
cert = ssl_context.current_cert
# Verify certificate fingerprint
expected_fingerprint = 'AA:BB:CC:DD:EE:FF:00:11:22:33:44:55:66:77:88:99:AA:BB:CC:DD'
actual_fingerprint = Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(':')
actual_fingerprint == expected_fingerprint
else
false
end
end
response = http.get(uri.path)
response.body
end
Rate Limiting and Throttling
class RateLimitedClient
def initialize(requests_per_second = 10)
@requests_per_second = requests_per_second
@last_request_time = Time.now
@request_count = 0
end
def make_request(url)
enforce_rate_limit
# Make the actual request
uri = URI(url)
response = Net::HTTP.get_response(uri)
@request_count += 1
response.body
end
private
def enforce_rate_limit
now = Time.now
time_since_last_request = now - @last_request_time
if time_since_last_request < (1.0 / @requests_per_second)
sleep_time = (1.0 / @requests_per_second) - time_since_last_request
sleep(sleep_time)
end
@last_request_time = Time.now
end
end
Real-World Applications {#real-world-applications}
Web Scraper with Retry Logic
require 'net/http'
require 'nokogiri'
require 'uri'
class WebScraper
def initialize(options = {})
@max_retries = options[:max_retries] || 3
@retry_delay = options[:retry_delay] || 1
@user_agent = options[:user_agent] || 'Ruby WebScraper 1.0'
end
def scrape(url)
retries = 0
begin
uri = URI(url)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
request = Net::HTTP::Get.new(uri.path)
request['User-Agent'] = @user_agent
response = http.request(request)
case response
when Net::HTTPSuccess
parse_content(response.body)
when Net::HTTPRedirection
new_url = response['location']
scrape(new_url)
else
raise "HTTP Error: #{response.code}"
end
rescue StandardError => e
retries += 1
if retries <= @max_retries
puts "Retry #{retries}/#{@max_retries} for #{url}: #{e.message}"
sleep(@retry_delay * retries)
retry
else
raise "Failed to scrape #{url} after #{@max_retries} retries: #{e.message}"
end
end
end
private
def parse_content(html)
doc = Nokogiri::HTML(html)
{
title: doc.css('title').text.strip,
links: doc.css('a').map { |link| link['href'] }.compact,
headings: doc.css('h1, h2, h3').map(&:text).map(&:strip)
}
end
end
# Usage
scraper = WebScraper.new(max_retries: 5)
result = scraper.scrape('https://example.com')
puts result
API Client with Caching
require 'net/http'
require 'json'
require 'digest'
class CachedAPIClient
def initialize(base_url, cache_ttl = 300)
@base_url = base_url
@cache_ttl = cache_ttl
@cache = {}
end
def get(endpoint, params = {})
cache_key = generate_cache_key(endpoint, params)
if cached_response = get_from_cache(cache_key)
puts "Cache hit for #{endpoint}"
return cached_response
end
response = make_request(endpoint, params)
store_in_cache(cache_key, response)
response
end
private
def generate_cache_key(endpoint, params)
content = "#{endpoint}#{params.to_json}"
Digest::MD5.hexdigest(content)
end
def get_from_cache(key)
cached_item = @cache[key]
return nil unless cached_item
if Time.now - cached_item[:timestamp] < @cache_ttl
cached_item[:data]
else
@cache.delete(key)
nil
end
end
def store_in_cache(key, data)
@cache[key] = {
data: data,
timestamp: Time.now
}
end
def make_request(endpoint, params)
uri = URI("#{@base_url}#{endpoint}")
uri.query = URI.encode_www_form(params) unless params.empty?
response = Net::HTTP.get_response(uri)
if response.is_a?(Net::HTTPSuccess)
JSON.parse(response.body)
else
raise "API Error: #{response.code} #{response.message}"
end
end
end
# Usage
client = CachedAPIClient.new('https://api.example.com', 600)
data = client.get('/users', { limit: 10 })
puts data
Simple Load Balancer
require 'net/http'
require 'uri'
class LoadBalancer
def initialize(servers)
@servers = servers
@current_index = 0
@failed_servers = Set.new
end
def make_request(path, method = :get, body = nil)
attempts = 0
max_attempts = @servers.length
while attempts < max_attempts
server = next_server
begin
response = send_request(server, path, method, body)
mark_server_healthy(server)
return response
rescue StandardError => e
puts "Request to #{server} failed: #{e.message}"
mark_server_failed(server)
attempts += 1
end
end
raise "All servers failed"
end
private
def next_server
available_servers = @servers - @failed_servers.to_a
raise "No healthy servers available" if available_servers.empty?
server = available_servers[@current_index % available_servers.length]
@current_index += 1
server
end
def send_request(server, path, method, body)
uri = URI("#{server}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.read_timeout = 5
request = case method
when :get
Net::HTTP::Get.new(uri.path)
when :post
req = Net::HTTP::Post.new(uri.path)
req.body = body if body
req
else
raise "Unsupported method: #{method}"
end
response = http.request(request)
if response.is_a?(Net::HTTPSuccess)
response.body
else
raise "HTTP Error: #{response.code}"
end
end
def mark_server_failed(server)
@failed_servers.add(server)
end
def mark_server_healthy(server)
@failed_servers.delete(server)
end
end
# Usage
lb = LoadBalancer.new([
'https://server1.example.com',
'https://server2.example.com',
'https://server3.example.com'
])
response = lb.make_request('/api/health')
puts response
Conclusion {#conclusion}
Ruby's networking capabilities provide a robust foundation for building connected applications. From the high-level abstractions in the Net module to the fine-grained control offered by socket programming, Ruby offers flexibility and power for various networking scenarios.
Key Takeaways:
- Start with Net::HTTP for basic HTTP operations—it's included in the standard library and handles most common use cases
- Use sockets when you need low-level control or custom protocols
- Leverage gems like Faraday, HTTParty, or Typhoeus for advanced features and better developer experience
- Always implement proper error handling, timeouts, and security measures
- Consider performance implications and use appropriate concurrency patterns for high-throughput applications
Next Steps:
- Explore WebSocket support with gems like
websocket-client-simple
- Learn about async networking with
async-http
oreventmachine
- Investigate GraphQL clients like
graphql-client
- Consider message queues and pub/sub patterns with gems like
bunny
(RabbitMQ) orredis
Ruby's networking ecosystem continues to evolve, with new gems and improvements being added regularly. The examples in this guide provide a solid foundation for building robust, scalable networked applications in Ruby.
Happy networking!
This content originally appeared on DEV Community and was authored by Davide Santangelo

Davide Santangelo | Sciencx (2025-07-11T06:36:48+00:00) Exploring Ruby’s Networking Capabilities: From Basics to Advanced Implementations. Retrieved from https://www.scien.cx/2025/07/11/exploring-rubys-networking-capabilities-from-basics-to-advanced-implementations/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.