CrackedRuby CrackedRuby

HTTP Request/Response Cycle

Overview

The HTTP request/response cycle describes the complete process of communication between a client and server using the Hypertext Transfer Protocol. When a client needs data from a server, it initiates this cycle by sending an HTTP request message. The server processes the request and returns an HTTP response message containing the requested resource or an error status. This cycle forms the foundation of web communication and API interactions.

The cycle operates at the application layer of the TCP/IP model. Each cycle is stateless, meaning the server retains no memory of previous requests from the same client. This stateless design enables scalability but requires additional mechanisms like cookies or tokens to maintain user sessions across multiple request/response cycles.

A typical cycle follows this sequence: the client establishes a TCP connection to the server, sends an HTTP request, waits for the server to process the request, receives the HTTP response, and closes or reuses the connection. Modern HTTP implementations support persistent connections that allow multiple request/response cycles over a single TCP connection, reducing overhead.

require 'net/http'
require 'uri'

# Basic HTTP GET request demonstrating the cycle
uri = URI('https://api.example.com/users/1')
response = Net::HTTP.get_response(uri)

puts "Status: #{response.code}"
puts "Body: #{response.body}"
# Status: 200
# Body: {"id":1,"name":"John Doe"}

Understanding this cycle helps developers debug network issues, optimize application performance, implement proper error handling, and design secure APIs. The cycle's mechanics directly impact latency, throughput, security, and reliability of distributed systems.

Key Principles

Request Structure: An HTTP request consists of three main components: the request line, headers, and an optional body. The request line specifies the HTTP method (GET, POST, PUT, DELETE, etc.), the target URI path, and the protocol version. Headers provide metadata about the request such as content type, authentication credentials, and caching directives. The body contains data sent to the server, primarily used with POST, PUT, and PATCH methods.

Response Structure: HTTP responses mirror the request structure with a status line, headers, and optional body. The status line includes the protocol version, a three-digit status code, and a reason phrase. Status codes range from 100-599, categorized into informational (1xx), successful (2xx), redirection (3xx), client errors (4xx), and server errors (5xx). Response headers convey information about the server, caching policies, and content characteristics.

Connection Management: The request/response cycle depends on an underlying TCP connection. HTTP/1.0 used a new connection for each request/response cycle, creating significant overhead. HTTP/1.1 introduced persistent connections through the Connection: keep-alive header, allowing connection reuse. HTTP/2 multiplexes multiple request/response pairs over a single TCP connection simultaneously, eliminating head-of-line blocking at the HTTP layer.

Statelessness: Each request contains all information needed for the server to process it. The server does not maintain client state between requests. Session management requires explicit mechanisms like cookies, URL parameters, or authorization tokens. This design simplifies server architecture and enables horizontal scaling but shifts state management responsibility to the client or external storage systems.

Content Negotiation: Clients and servers negotiate content representation through headers. The Accept header indicates preferred response formats (application/json, text/html). The Accept-Language header specifies language preferences. The Accept-Encoding header requests compression formats. Servers select appropriate representations based on these preferences and their capabilities, returning 406 Not Acceptable if no suitable representation exists.

Idempotency: HTTP methods have defined idempotency guarantees. GET, PUT, DELETE, HEAD, OPTIONS, and TRACE are idempotent—multiple identical requests produce the same server state as a single request. POST and PATCH are not idempotent. Understanding idempotency is critical for retry logic and error recovery.

require 'net/http'

# Demonstrating request components
uri = URI('https://api.example.com/posts')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

request = Net::HTTP::Post.new(uri.path)
request['Content-Type'] = 'application/json'
request['Authorization'] = 'Bearer token123'
request.body = { title: 'New Post', content: 'Content here' }.to_json

response = http.request(request)
# Request line: POST /posts HTTP/1.1
# Headers: Content-Type, Authorization
# Body: JSON payload

Redirects: Servers use 3xx status codes to redirect clients to different URIs. 301 indicates permanent relocation, 302 indicates temporary redirection. Clients automatically follow redirects, but the number of redirects should be limited to prevent infinite loops. The Location header specifies the redirect target.

Conditional Requests: Clients can make requests conditional on resource state using headers like If-Modified-Since, If-None-Match, and If-Match. These enable caching and prevent race conditions. Servers respond with 304 Not Modified if the resource hasn't changed, saving bandwidth.

Ruby Implementation

Ruby provides multiple approaches for implementing HTTP clients, from low-level standard library classes to high-level abstraction gems. The Net::HTTP class in the standard library handles basic HTTP operations without external dependencies. It manages TCP connections, constructs request messages, and parses response messages.

Net::HTTP Standard Library: This class provides direct control over HTTP request construction and response handling. Developers create URI objects, initialize HTTP instances, configure SSL settings, build request objects, and execute the request/response cycle.

require 'net/http'
require 'json'

# Detailed Net::HTTP usage
uri = URI('https://api.github.com/users/octocat')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.open_timeout = 5  # Connection timeout
http.read_timeout = 10 # Read timeout

request = Net::HTTP::Get.new(uri.request_uri)
request['Accept'] = 'application/vnd.github.v3+json'
request['User-Agent'] = 'Ruby HTTP Client'

response = http.request(request)

case response
when Net::HTTPSuccess
  data = JSON.parse(response.body)
  puts "User: #{data['login']}"
when Net::HTTPRedirection
  puts "Redirected to: #{response['location']}"
when Net::HTTPClientError
  puts "Client error: #{response.code}"
when Net::HTTPServerError
  puts "Server error: #{response.code}"
end

HTTParty Gem: HTTParty simplifies HTTP interactions with a declarative API. It handles JSON parsing, query parameter encoding, and response object wrapping automatically. The gem reduces boilerplate for common HTTP operations.

require 'httparty'

class GitHubClient
  include HTTParty
  base_uri 'https://api.github.com'
  
  def initialize(token)
    @options = {
      headers: {
        'Authorization' => "token #{token}",
        'User-Agent' => 'Ruby HTTParty Client'
      }
    }
  end
  
  def user(username)
    self.class.get("/users/#{username}", @options)
  end
  
  def create_issue(repo, title, body)
    self.class.post(
      "/repos/#{repo}/issues",
      @options.merge(body: { title: title, body: body }.to_json)
    )
  end
end

client = GitHubClient.new('token123')
response = client.user('octocat')
puts response.parsed_response['name'] if response.success?

Faraday Gem: Faraday provides middleware-based HTTP client architecture. Middleware components handle concerns like authentication, logging, retry logic, and response parsing. This design enables composition and reuse of HTTP client behaviors.

require 'faraday'
require 'faraday/retry'

# Faraday with middleware stack
conn = Faraday.new(url: 'https://api.example.com') do |f|
  f.request :json  # Encode request body as JSON
  f.request :retry, max: 3, interval: 0.5
  f.response :json # Parse response body as JSON
  f.response :logger # Log requests and responses
  f.adapter Faraday.default_adapter
end

response = conn.get('/users/1') do |req|
  req.headers['Authorization'] = 'Bearer token123'
  req.params['include'] = 'posts,comments'
end

puts response.body['name'] if response.success?

Rack Interface: Rack defines a minimal interface between web servers and Ruby applications. While primarily used for server-side request handling, understanding Rack helps grasp how HTTP requests map to Ruby data structures. Rack represents requests as environment hashes and responses as arrays.

# Rack application structure
class SimpleRackApp
  def call(env)
    request = Rack::Request.new(env)
    
    # Access request components
    method = request.request_method  # GET, POST, etc.
    path = request.path_info
    params = request.params
    headers = request.env.select { |k,v| k.start_with?('HTTP_') }
    
    # Build response
    status = 200
    headers = { 'Content-Type' => 'application/json' }
    body = [{ message: 'Hello', path: path }.to_json]
    
    [status, headers, body]
  end
end

# Rack environment hash example
env = {
  'REQUEST_METHOD' => 'GET',
  'PATH_INFO' => '/users/1',
  'QUERY_STRING' => 'format=json',
  'HTTP_ACCEPT' => 'application/json',
  'rack.input' => StringIO.new
}

Async HTTP with async-http Gem: For high-concurrency scenarios, the async-http gem leverages Ruby's Fiber-based concurrency to handle multiple HTTP requests efficiently without blocking threads.

require 'async'
require 'async/http/internet'

Async do
  internet = Async::HTTP::Internet.new
  
  # Concurrent requests
  responses = [
    'https://api.example.com/users/1',
    'https://api.example.com/users/2',
    'https://api.example.com/users/3'
  ].map do |url|
    Async do
      internet.get(url)
    end
  end.map(&:wait)
  
  responses.each do |response|
    puts "Status: #{response.status}"
    puts "Body: #{response.read}"
  end
ensure
  internet&.close
end

Practical Examples

API Client with Error Handling: Real-world HTTP clients require comprehensive error handling for network failures, timeouts, invalid responses, and rate limiting. This example demonstrates a production-ready approach.

require 'net/http'
require 'json'
require 'uri'

class ResilientAPIClient
  MAX_RETRIES = 3
  RETRY_DELAY = 1
  
  def initialize(base_url, api_key)
    @base_url = base_url
    @api_key = api_key
  end
  
  def get(path, params = {})
    uri = build_uri(path, params)
    request = build_request(Net::HTTP::Get, uri)
    execute_with_retry(uri, request)
  end
  
  def post(path, body)
    uri = build_uri(path)
    request = build_request(Net::HTTP::Post, uri)
    request['Content-Type'] = 'application/json'
    request.body = body.to_json
    execute_with_retry(uri, request)
  end
  
  private
  
  def build_uri(path, params = {})
    uri = URI.join(@base_url, path)
    uri.query = URI.encode_www_form(params) unless params.empty?
    uri
  end
  
  def build_request(request_class, uri)
    request = request_class.new(uri.request_uri)
    request['Authorization'] = "Bearer #{@api_key}"
    request['Accept'] = 'application/json'
    request['User-Agent'] = 'ResilientClient/1.0'
    request
  end
  
  def execute_with_retry(uri, request, attempt = 1)
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    http.open_timeout = 5
    http.read_timeout = 10
    
    response = http.request(request)
    
    case response
    when Net::HTTPSuccess
      JSON.parse(response.body)
    when Net::HTTPTooManyRequests
      raise "Rate limited" if attempt >= MAX_RETRIES
      sleep(response['Retry-After']&.to_i || RETRY_DELAY * attempt)
      execute_with_retry(uri, request, attempt + 1)
    when Net::HTTPServerError
      raise "Server error" if attempt >= MAX_RETRIES
      sleep(RETRY_DELAY * attempt)
      execute_with_retry(uri, request, attempt + 1)
    when Net::HTTPClientError
      raise "Client error: #{response.code} - #{response.body}"
    else
      raise "Unexpected response: #{response.code}"
    end
  rescue Net::OpenTimeout, Net::ReadTimeout => e
    raise "Timeout" if attempt >= MAX_RETRIES
    sleep(RETRY_DELAY * attempt)
    execute_with_retry(uri, request, attempt + 1)
  rescue SocketError, Errno::ECONNREFUSED => e
    raise "Connection failed: #{e.message}"
  end
end

# Usage
client = ResilientAPIClient.new('https://api.example.com', 'key123')
begin
  user_data = client.get('/users/1', { include: 'posts' })
  puts "User: #{user_data['name']}"
rescue => e
  puts "Request failed: #{e.message}"
end

Streaming Large Response Bodies: When downloading large files or processing streaming APIs, loading the entire response body into memory causes issues. Ruby supports streaming responses in chunks.

require 'net/http'

def download_file(url, destination)
  uri = URI(url)
  
  Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
    request = Net::HTTP::Get.new(uri.request_uri)
    
    http.request(request) do |response|
      unless response.is_a?(Net::HTTPSuccess)
        raise "Download failed: #{response.code}"
      end
      
      total_size = response['Content-Length']&.to_i || 0
      downloaded = 0
      
      File.open(destination, 'wb') do |file|
        response.read_body do |chunk|
          file.write(chunk)
          downloaded += chunk.bytesize
          print "\rDownloaded: #{downloaded} / #{total_size} bytes"
        end
      end
      puts "\nDownload complete"
    end
  end
end

download_file('https://example.com/large-file.zip', 'output.zip')

Connection Pool Management: Applications making frequent HTTP requests benefit from connection pooling. Reusing TCP connections eliminates handshake overhead. This example shows persistent connection management.

require 'net/http'

class ConnectionPool
  def initialize(host, port, pool_size: 5)
    @host = host
    @port = port
    @pool_size = pool_size
    @pool = []
    @mutex = Mutex.new
  end
  
  def with_connection
    connection = acquire_connection
    yield connection
  ensure
    release_connection(connection)
  end
  
  private
  
  def acquire_connection
    @mutex.synchronize do
      if @pool.empty?
        create_connection
      else
        @pool.pop
      end
    end
  end
  
  def release_connection(connection)
    @mutex.synchronize do
      if @pool.size < @pool_size && connection.started?
        @pool.push(connection)
      else
        connection.finish if connection.started?
      end
    end
  end
  
  def create_connection
    http = Net::HTTP.new(@host, @port)
    http.use_ssl = true
    http.start
    http
  end
end

# Usage
pool = ConnectionPool.new('api.example.com', 443, pool_size: 5)

10.times do |i|
  pool.with_connection do |http|
    request = Net::HTTP::Get.new("/users/#{i}")
    response = http.request(request)
    puts "Request #{i}: #{response.code}"
  end
end

WebSocket Upgrade from HTTP: WebSockets begin with an HTTP request that upgrades the connection. This example demonstrates the upgrade handshake.

require 'socket'
require 'digest/sha1'
require 'base64'

# Simplified WebSocket client handshake
def websocket_handshake(host, port, path)
  socket = TCPSocket.new(host, port)
  
  # Generate WebSocket key
  key = Base64.strict_encode64(Random.bytes(16))
  
  # Send HTTP upgrade request
  request = [
    "GET #{path} HTTP/1.1",
    "Host: #{host}:#{port}",
    "Upgrade: websocket",
    "Connection: Upgrade",
    "Sec-WebSocket-Key: #{key}",
    "Sec-WebSocket-Version: 13",
    "\r\n"
  ].join("\r\n")
  
  socket.write(request)
  
  # Read response
  response = ""
  while line = socket.gets
    response += line
    break if line == "\r\n"
  end
  
  # Verify upgrade response
  if response.include?('101 Switching Protocols')
    puts "WebSocket connection established"
    socket
  else
    socket.close
    raise "WebSocket upgrade failed"
  end
end

socket = websocket_handshake('echo.websocket.org', 80, '/')
# WebSocket connection established
socket.close

Security Implications

Transport Layer Security: HTTPS encrypts the entire request/response cycle using TLS, preventing eavesdropping and tampering. Ruby's Net::HTTP enables HTTPS by setting use_ssl to true. Certificate verification should always be enabled in production to prevent man-in-the-middle attacks. Custom certificate stores can be configured for self-signed certificates in development.

require 'net/http'

uri = URI('https://api.example.com/data')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

# Enable certificate verification (default in modern Ruby)
http.verify_mode = OpenSSL::SSL::VERIFY_PEER

# Custom certificate store for self-signed certs (development only)
# http.cert_store = OpenSSL::X509::Store.new
# http.cert_store.add_file('/path/to/cert.pem')

response = http.get(uri.path)

Authentication Mechanisms: HTTP supports multiple authentication schemes. Basic authentication transmits credentials base64-encoded in the Authorization header, requiring HTTPS for security. Bearer tokens provide stateless authentication for APIs. OAuth 2.0 implements token-based authorization with refresh capabilities.

require 'net/http'
require 'base64'

# Basic authentication
uri = URI('https://api.example.com/secure')
request = Net::HTTP::Get.new(uri)
credentials = Base64.strict_encode64('username:password')
request['Authorization'] = "Basic #{credentials}"

# Bearer token authentication
request = Net::HTTP::Get.new(uri)
request['Authorization'] = "Bearer eyJhbGciOiJIUzI1NiIs..."

# API key authentication
request = Net::HTTP::Get.new(uri)
request['X-API-Key'] = 'secret_key_here'

Cookie Security: Cookies maintain session state across requests. The Secure flag restricts cookies to HTTPS connections. The HttpOnly flag prevents JavaScript access, mitigating XSS attacks. SameSite attributes control cross-site request behavior, preventing CSRF attacks. Ruby automatically handles cookie storage and transmission.

Header Injection Vulnerabilities: Applications constructing HTTP requests from user input risk header injection attacks. Attackers insert newline characters to inject arbitrary headers or split the request. Always validate and sanitize user input before including it in headers.

# Vulnerable code - DO NOT USE
user_input = params[:user_agent]
request['User-Agent'] = user_input  # Dangerous if user_input contains \r\n

# Secure approach - validate and sanitize
def sanitize_header(value)
  value.gsub(/[\r\n]/, '')
end

request['User-Agent'] = sanitize_header(params[:user_agent])

Server-Side Request Forgery (SSRF): Applications that fetch URLs provided by users may be exploited to access internal resources or perform unauthorized requests. Always validate URLs against a whitelist and block requests to private IP ranges.

require 'net/http'
require 'ipaddr'

def safe_fetch(url_string)
  uri = URI(url_string)
  
  # Validate scheme
  unless ['http', 'https'].include?(uri.scheme)
    raise "Invalid URL scheme"
  end
  
  # Resolve hostname to IP
  ip = IPSocket.getaddress(uri.host)
  
  # Block private IP ranges
  private_ranges = [
    IPAddr.new('10.0.0.0/8'),
    IPAddr.new('172.16.0.0/12'),
    IPAddr.new('192.168.0.0/16'),
    IPAddr.new('127.0.0.0/8')
  ]
  
  if private_ranges.any? { |range| range.include?(ip) }
    raise "Access to private IP denied"
  end
  
  Net::HTTP.get(uri)
rescue SocketError
  raise "Invalid hostname"
end

Cross-Origin Resource Sharing (CORS): Browsers enforce same-origin policy, blocking JavaScript from making requests to different origins. Servers use CORS headers to explicitly allow cross-origin requests. Overly permissive CORS configurations create security vulnerabilities.

TLS Version and Cipher Selection: Older TLS versions and weak cipher suites have known vulnerabilities. Configure HTTP clients to use TLS 1.2 or higher and strong cipher suites.

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
http.min_version = OpenSSL::SSL::TLS1_2_VERSION
http.ciphers = 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384'

Performance Considerations

Connection Reuse: Establishing TCP connections involves significant overhead—DNS resolution, TCP handshake, and TLS handshake for HTTPS. HTTP/1.1 persistent connections reuse TCP connections for multiple requests. Configure keep-alive timeouts appropriately to balance connection reuse with server resource consumption.

require 'net/http'

uri = URI('https://api.example.com')
Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
  # All requests reuse the same connection
  10.times do |i|
    response = http.get("/users/#{i}")
    puts response.code
  end
end  # Connection automatically closed

Timeout Configuration: HTTP requests can hang indefinitely without timeouts. Configure separate timeouts for connection establishment and data reading. Connection timeouts prevent hanging on unreachable hosts. Read timeouts prevent hanging on slow responses.

http = Net::HTTP.new(uri.host, uri.port)
http.open_timeout = 5   # 5 seconds to establish connection
http.read_timeout = 30  # 30 seconds to read response
http.write_timeout = 10 # 10 seconds to write request body

Response Buffering: By default, HTTP clients buffer entire response bodies in memory. For large responses, streaming prevents memory exhaustion.

Compression: HTTP supports response body compression via Content-Encoding. Clients indicate compression support through Accept-Encoding headers. Gzip compression typically reduces transfer size by 60-80% for text content.

require 'net/http'
require 'zlib'

uri = URI('https://api.example.com/data')
request = Net::HTTP::Get.new(uri)
request['Accept-Encoding'] = 'gzip'

http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
response = http.request(request)

body = if response['Content-Encoding'] == 'gzip'
  Zlib::GzipReader.new(StringIO.new(response.body)).read
else
  response.body
end

DNS Caching: DNS lookups add latency to each request. Operating systems cache DNS results, but applications can implement additional caching for frequently accessed hosts. Connection pooling implicitly caches DNS results by reusing connections.

HTTP/2 Multiplexing: HTTP/2 sends multiple requests over a single connection simultaneously, eliminating head-of-line blocking. Ruby's Net::HTTP does not support HTTP/2; use gems like httpx for HTTP/2 support.

Request Batching: APIs that support batch operations reduce network overhead. Instead of 100 individual requests, send one request with 100 operations.

Conditional Requests for Caching: ETags and Last-Modified headers enable conditional requests. Clients store these values and send If-None-Match or If-Modified-Since headers in subsequent requests. Servers respond with 304 Not Modified if the resource hasn't changed, saving bandwidth.

require 'net/http'

uri = URI('https://api.example.com/resource')
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true

# Initial request
response = http.get(uri.path)
etag = response['ETag']
data = response.body

# Subsequent request with conditional header
request = Net::HTTP::Get.new(uri.path)
request['If-None-Match'] = etag
response = http.request(request)

if response.code == '304'
  puts "Using cached data"
  # Use previously stored data
else
  data = response.body
  etag = response['ETag']
end

Connection Pool Sizing: Connection pools reduce connection establishment overhead but consume server resources. Pool size should match expected concurrency. Too small causes contention; too large wastes resources.

Tools & Ecosystem

Faraday Middleware Ecosystem: Faraday's architecture supports extensive middleware for cross-cutting concerns. Popular middleware includes authentication handlers, retry logic, response parsing, caching, and instrumentation.

require 'faraday'
require 'faraday/retry'
require 'faraday_middleware'

conn = Faraday.new(url: 'https://api.example.com') do |f|
  # Request middleware
  f.request :oauth2, 'token123'
  f.request :json
  f.request :instrumentation
  
  # Response middleware
  f.response :json, content_type: /\bjson$/
  f.response :follow_redirects
  f.response :raise_error
  f.use :instrumentation
  
  # Retry with exponential backoff
  f.request :retry,
    max: 3,
    interval: 0.5,
    backoff_factor: 2,
    retry_statuses: [429, 503]
  
  f.adapter Faraday.default_adapter
end

REST Client Gem: Rest-client provides a simpler API than Net::HTTP for basic HTTP operations. It raises exceptions for error status codes and handles redirects automatically.

require 'rest-client'

# Simple GET request
response = RestClient.get(
  'https://api.example.com/users/1',
  { Authorization: 'Bearer token123' }
)
puts response.body

# POST with error handling
begin
  response = RestClient.post(
    'https://api.example.com/users',
    { name: 'John', email: 'john@example.com' }.to_json,
    { content_type: :json, accept: :json }
  )
rescue RestClient::ExceptionWithResponse => e
  puts "Error: #{e.response}"
end

HTTP.rb Gem: The http gem provides a chainable API with sensible defaults and good performance characteristics.

require 'http'

response = HTTP
  .timeout(10)
  .headers(accept: 'application/json')
  .auth("Bearer token123")
  .get('https://api.example.com/users/1')

if response.status.success?
  puts response.parse # Automatically parses JSON
end

WebMock for Testing: WebMock stubs HTTP requests in tests, eliminating external dependencies and improving test reliability and speed.

require 'webmock/rspec'

RSpec.describe 'API Client' do
  before do
    stub_request(:get, 'https://api.example.com/users/1')
      .with(headers: { 'Authorization' => 'Bearer token123' })
      .to_return(
        status: 200,
        body: { id: 1, name: 'John' }.to_json,
        headers: { 'Content-Type' => 'application/json' }
      )
  end
  
  it 'fetches user data' do
    # Test code here
  end
end

VCR for Record/Replay: VCR records HTTP interactions and replays them in tests, capturing real API responses while maintaining test speed and isolation.

require 'vcr'

VCR.configure do |c|
  c.cassette_library_dir = 'spec/vcr_cassettes'
  c.hook_into :webmock
  c.filter_sensitive_data('<TOKEN>') { ENV['API_TOKEN'] }
end

RSpec.describe 'GitHub API' do
  it 'fetches user', :vcr do
    # First run records actual API call
    # Subsequent runs replay from cassette
    response = client.get_user('octocat')
    expect(response['login']).to eq('octocat')
  end
end

HTTPie for Command Line Testing: While not Ruby-specific, httpie provides an intuitive CLI for testing HTTP APIs during development.

Rack::Test for Application Testing: Rack::Test simulates HTTP requests to Rack applications without network overhead, ideal for integration testing web applications.

require 'rack/test'

class AppTest < Minitest::Test
  include Rack::Test::Methods
  
  def app
    MyRackApp.new
  end
  
  def test_get_user
    get '/users/1', {}, { 'HTTP_AUTHORIZATION' => 'Bearer token' }
    assert last_response.ok?
    assert_equal 'application/json', last_response.content_type
  end
end

Reference

HTTP Request Methods

Method Purpose Idempotent Request Body
GET Retrieve resource Yes No
POST Create resource No Yes
PUT Replace resource Yes Yes
PATCH Partial update No Yes
DELETE Remove resource Yes Optional
HEAD Retrieve headers only Yes No
OPTIONS Describe options Yes No
TRACE Echo request Yes No

HTTP Status Code Categories

Range Category Description Common Codes
1xx Informational Request received, continuing 100 Continue, 101 Switching Protocols
2xx Success Request successfully processed 200 OK, 201 Created, 204 No Content
3xx Redirection Further action required 301 Moved Permanently, 302 Found, 304 Not Modified
4xx Client Error Request contains error 400 Bad Request, 401 Unauthorized, 404 Not Found, 429 Too Many Requests
5xx Server Error Server failed to fulfill request 500 Internal Server Error, 502 Bad Gateway, 503 Service Unavailable

Common Request Headers

Header Purpose Example
Accept Acceptable response formats application/json
Authorization Authentication credentials Bearer token123
Content-Type Request body format application/json
User-Agent Client identification Ruby/3.0
Accept-Encoding Supported compression gzip, deflate
If-None-Match Conditional request by ETag "abc123"
If-Modified-Since Conditional request by date Sat, 29 Oct 2024 19:43:31 GMT
Cookie Session state session_id=xyz789

Common Response Headers

Header Purpose Example
Content-Type Response body format application/json; charset=utf-8
Content-Length Response body size in bytes 1234
Content-Encoding Compression applied gzip
Cache-Control Caching directives max-age=3600, public
ETag Resource version identifier "abc123"
Last-Modified Resource modification time Sat, 29 Oct 2024 19:43:31 GMT
Location Redirect target https://example.com/new-location
Set-Cookie Session state session_id=xyz789; Secure; HttpOnly
Access-Control-Allow-Origin CORS policy https://example.com

Ruby HTTP Client Library Comparison

Library Standard Library API Style HTTP/2 Support Async Support
Net::HTTP Yes Imperative No No
HTTParty No Declarative No No
Faraday No Middleware-based Via adapter Via adapter
rest-client No Method-based No No
http.rb No Chainable No No
httpx No Chainable Yes Yes
async-http No Fiber-based Yes Yes

Timeout Configuration Recommendations

Timeout Type Typical Range Purpose
Open Timeout 2-10 seconds Connection establishment
Read Timeout 10-60 seconds Reading response data
Write Timeout 5-30 seconds Writing request data
Keep-Alive Timeout 60-300 seconds Persistent connection idle time

Net::HTTP Core Classes

Class Purpose
Net::HTTP HTTP client connection manager
Net::HTTP::Get GET request object
Net::HTTP::Post POST request object
Net::HTTP::Put PUT request object
Net::HTTP::Delete DELETE request object
Net::HTTP::Patch PATCH request object
Net::HTTPResponse Response base class
Net::HTTPSuccess 2xx response category
Net::HTTPRedirection 3xx response category
Net::HTTPClientError 4xx response category
Net::HTTPServerError 5xx response category

Security Header Recommendations

Header Recommended Value Purpose
Strict-Transport-Security max-age=31536000; includeSubDomains Force HTTPS
X-Content-Type-Options nosniff Prevent MIME sniffing
X-Frame-Options DENY Prevent clickjacking
Content-Security-Policy default-src 'self' Restrict resource loading
X-XSS-Protection 1; mode=block Enable XSS filtering