This episode dissects Ruby network communication: TCPSocket and Socket.tcp with Happy Eyeballs v2 RFC 8305, UDPSocket, basic socket servers, and HTTP clients with Net::HTTP, URI, and the modern httparty and faraday gems for REST API calls.

Backend services rarely work alone. They call other APIs, communicate with caches, or accept connections from clients. All of that is rooted in one fundamental capability: networking. Ruby provides a mature socket layer as well as HTTP clients in the stdlib and the gem ecosystem.
This episode 13 dissects two layers. The first layer is sockets: TCPSocket and Socket.tcp with Happy Eyeballs v2 (RFC 8305), which has been the default since Ruby 3.4, UDPSocket, and a simple socket server. The second layer is the HTTP client: the built-in Net::HTTP and URI, plus modern gems like httparty and faraday for REST API calls.
TCP sockets are the foundation of almost every application protocol — HTTP, databases, caches. TCPSocket.new(host, port) opens a connection and returns a stream that can be written to and read from:
require "socket"
socket = TCPSocket.new("ruby-lang.org", 80)
socket.write "HEAD / HTTP/1.0\r\n\r\n"
puts socket.gets
socket.closeTCPSocket.new("ruby-lang.org", 80) opens a connection to port 80, socket.write sends a minimal HTTP request, and socket.gets reads one line of the response. Operations like this are the core of every HTTP client — all HTTP gems are just wrappers over the same mechanism.
Modern hosts often have many IP addresses — IPv4 and IPv6. Since Ruby 3.4, Socket.tcp applies Happy Eyeballs v2 (RFC 8305) by default: trying several addresses in parallel and using the first that responds. This eliminates the delay that occurs when one address family is slow:
require "socket"
Socket.tcp("ruby-lang.org", 443) do |s|
puts "terhubung ke #{s.remote_address.ip_address}"
endThe block Socket.tcp("ruby-lang.org", 443) do |s| opens a TLS-port connection while ensuring the socket is closed automatically after the block finishes. The best-address selection is handled behind the scenes per RFC 8305, so you don't need to write failover logic yourself.
Unlike TCP, UDP is a connectionless protocol — data is sent as datagrams without delivery guarantees and without a handshake. It suits telemetry logs, DNS, or probing tolerant of packet loss:
require "socket"
socket = UDPSocket.new
socket.send("ping", 0, "localhost", 1234)
socket.closeUDPSocket.new creates a UDP socket, and socket.send("ping", 0, "localhost", 1234) sends a datagram to the destination address without establishing a connection. Because there's no connection, UDP is very lightweight — but you must be prepared to lose packets in applications that use it.
On the receiving side, TCPServer listens for incoming connections and accept takes each client:
require "socket"
server = TCPServer.new(3000)
loop do
client = server.accept
client.puts "Halo dari server Ruby"
client.close
endTCPServer.new(3000) listens on port 3000, and server.accept blocks until a client connects. The server above answers each connection with a single line — a minimal foundation before we meet real web servers in episode 14.
For HTTP calls, the stdlib provides Net::HTTP and URI. The combination is enough for simple requests without extra dependencies:
ruby -r net/http -r uri -e 'uri = URI("https://api.github.com/users/octocat"); puts Net::HTTP.get(uri)[0, 60]'The command above loads net/http and uri, builds a URI object, then retrieves the response body. Net::HTTP.get(uri) returns the body as a string — the output is JSON truncated to 60 characters. For full control (headers, status, errors), use Net::HTTP.start with a block.
For production, modern gems are far more convenient. httparty offers a one-line API, while faraday provides swappable middleware and adapters:
require "httparty"
respons = HTTParty.get("https://api.github.com/users/octocat")
puts respons.code
puts respons["login"]HTTParty.get(...) returns a response object with the code method (HTTP status) and hash access to the parsed JSON body. respons["login"] directly retrieves a JSON field. Faraday provides something similar with middleware flexibility for logging, retry, and authentication — a favorite choice in large teams.
An HTTP request without a time limit is a time bomb in production — one slow service can stall the entire thread. Faraday uses the modern faraday-http adapter and allows timeout and retry to be configured explicitly:
require "faraday"
client = Faraday.new(url: "https://api.github.com") do |builder|
builder.request :retry, max: 3, interval: 0.5
builder.options.timeout = 5
builder.options.open_timeout = 2
end
respons = client.get("/users/octocat")
puts respons.statusbuilder.request :retry, max: 3 retries up to three times with a half-second pause, builder.options.timeout = 5 limits the total request time, and open_timeout = 2 limits the connection-opening time. A realistic retry policy is always accompanied by open_timeout and timeout — without both, retries can worsen the load on an already-slow service.
Info
Choose Net::HTTP for a light dependency footprint, httparty for development speed, and faraday when you need middleware and a structured retry policy. For a strict microservice architecture, faraday with retry and timeouts is almost always the answer.
Episode 13 equips you with network communication: TCPSocket and Socket.tcp with Happy Eyeballs v2, UDPSocket, basic socket servers, and HTTP clients from Net::HTTP to httparty and faraday.
Key takeaways:
TCPSocket and Socket.tcp are the foundation of TCP communication in Ruby.Socket.tcp uses Happy Eyeballs v2 (RFC 8305) by default since Ruby 3.4.TCPServer and accept are the basis for building socket servers.Net::HTTP and URI are enough for simple requests without dependencies.httparty is simple; faraday is flexible with middleware and retries.respons.code and JSON hash access are common patterns for reading API results.In the next episode, episode 14, we will discuss web frameworks and building APIs — the Rack interface concept, the thread-based Puma web server, middleware, the Sinatra framework for quick APIs, an overview of full-stack Rails 8.x, and building a REST API with routing, JSON responses, params, and error handling. This is the point where Ruby becomes a real application.