net/http

HTTP client and server package in Go standard library. Provides robustness, flexibility, and comprehensive documentation. Built-in HTTP/1.1, HTTP/2 support, detailed configuration options, cookie management, redirect handling, and timeout control. No external dependencies required.

HTTP ClientRubyStandard LibraryRESTful APISSL/TLSSession Management

GitHub Overview

ruby/ruby

The Ruby Programming Language

Stars23,021
Watchers1,081
Forks5,489
Created:February 27, 2010
Language:Ruby
License:Other

Topics

cjitlanguageobject-orientedprogramming-languagerubyruby-languagerust

Star History

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

Library

Net::HTTP

Overview

Net::HTTP is "the HTTP client implementation in Ruby's standard library" developed as a foundational HTTP client library for the Ruby ecosystem. As part of Ruby's standard library, it has provided stable operation for many years, enabling HTTP communication through simple and intuitive APIs. Utilized in various use cases including RESTful API development, web scraping, and external service integration, it functions as the foundation for many Ruby HTTP libraries. With the high reliability and compatibility characteristic of standard libraries, it has established itself as an essential networking tool for Ruby developers.

Details

Net::HTTP 2025 edition is a mature HTTP client library with nearly 30 years of development experience as Ruby's standard library. While maintaining support for the latest features in Ruby 3.x series, it provides consistent operation across a wide range of Ruby versions through backward compatibility-focused design. It comprehensively supports everything from basic GET/POST/PUT/DELETE requests to advanced features such as SSL/TLS communication, proxy server support, cookie management, session management, form data transmission, and file upload. It provides both simple class method APIs and instance APIs that allow detailed control, enabling selection of optimal implementation methods according to use cases.

Key Features

  • Ruby Standard Library: Pre-installed library requiring no additional installation
  • Comprehensive HTTP Support: Support for GET, POST, PUT, DELETE, WebDAV, and other methods
  • Complete SSL/TLS Support: HTTPS communication, certificate verification, encryption settings
  • Session Management: Efficient connection pooling and persistent connections
  • Rich Authentication Methods: Basic authentication, Digest authentication, proxy authentication
  • Form Processing: Support for both multipart and URL encoded formats

Pros and Cons

Pros

  • Available immediately without additional dependencies as Ruby's standard library
  • Outstanding stability and maturity from nearly 30 years of development experience
  • High performance and reliability through deep integration with Ruby language
  • Low learning cost due to simple and intuitive API
  • Capable of handling enterprise-level HTTP communication requirements with rich features
  • High compatibility and ecosystem integration with other Ruby HTTP libraries

Cons

  • Synchronization-based processing unsuitable for large numbers of parallel requests
  • Requires dedicated libraries like em-http-request for asynchronous processing
  • Some APIs are low-level, requiring more code compared to modern high-level libraries
  • Convenience features like automatic JSON response parsing not provided by default
  • Exception-based error handling requires detailed control
  • Limited support for latest protocols like HTTP/2 and HTTP/3

Reference Pages

Code Examples

Installation and Basic Setup

# Only require needed as Ruby standard library
require 'net/http'
require 'uri'
require 'json'
require 'openssl'

# Explicit require may be needed in Ruby 3.0+
require 'net/http'

# Enable SSL certificate verification (recommended)
Net::HTTP.verify_mode = OpenSSL::SSL::VERIFY_PEER

# Global configuration (optional)
Net::HTTP.class_eval do
  # Default timeout setting
  @@default_timeout = 30
  
  def self.default_timeout=(value)
    @@default_timeout = value
  end
  
  def self.default_timeout
    @@default_timeout
  end
end

# Detailed log output for debugging (development only)
# Net::HTTP.start('example.com', 80, debug: true)

puts "Net::HTTP version: #{Net::HTTP::VERSION}"
puts "Ruby version: #{RUBY_VERSION}"

Basic HTTP Requests (GET/POST/PUT/DELETE)

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

# Basic GET request
def simple_get_request
  uri = URI('https://api.example.com/users')
  
  begin
    response = Net::HTTP.get_response(uri)
    
    puts "Status: #{response.code} #{response.message}"
    puts "Headers: #{response.to_hash}"
    puts "Body: #{response.body}"
    
    if response.is_a?(Net::HTTPSuccess)
      data = JSON.parse(response.body)
      puts "Parsed JSON: #{data}"
      return data
    else
      puts "Request failed: #{response.code}"
      return nil
    end
  rescue StandardError => e
    puts "Error: #{e.message}"
    return nil
  end
end

# GET request with query parameters
def get_request_with_params
  base_uri = 'https://api.example.com/users'
  params = {
    page: 1,
    limit: 20,
    sort: 'created_at',
    filter: 'active'
  }
  
  # Convert parameters to query string
  query_string = URI.encode_www_form(params)
  uri = URI("#{base_uri}?#{query_string}")
  
  puts "Request URL: #{uri}"
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  
  request = Net::HTTP::Get.new(uri)
  request['User-Agent'] = 'MyApp/1.0 (Ruby Net::HTTP)'
  request['Accept'] = 'application/json'
  request['Authorization'] = 'Bearer your-access-token'
  
  response = http.request(request)
  
  case response
  when Net::HTTPSuccess
    puts "Success: #{response.code}"
    JSON.parse(response.body)
  when Net::HTTPRedirection
    puts "Redirected: #{response['location']}"
    # Handle redirection
    nil
  when Net::HTTPClientError
    puts "Client Error: #{response.code} - #{response.message}"
    nil
  when Net::HTTPServerError
    puts "Server Error: #{response.code} - #{response.message}"
    nil
  else
    puts "Unknown response: #{response.code}"
    nil
  end
end

# POST request (sending JSON)
def post_json_request
  uri = URI('https://api.example.com/users')
  
  user_data = {
    name: 'John Doe',
    email: '[email protected]',
    age: 30,
    department: 'Development'
  }
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  http.read_timeout = 30
  http.open_timeout = 10
  
  request = Net::HTTP::Post.new(uri)
  request['Content-Type'] = 'application/json'
  request['Accept'] = 'application/json'
  request['Authorization'] = 'Bearer your-access-token'
  request['User-Agent'] = 'MyApp/1.0 (Ruby)'
  
  request.body = JSON.generate(user_data)
  
  begin
    response = http.request(request)
    
    puts "POST Response: #{response.code}"
    puts "Response Headers: #{response.to_hash}"
    
    if response.code == '201'
      created_user = JSON.parse(response.body)
      puts "User created successfully: #{created_user}"
      return created_user
    else
      puts "Error creating user: #{response.body}"
      return nil
    end
  rescue Net::TimeoutError => e
    puts "Request timeout: #{e.message}"
    return nil
  rescue StandardError => e
    puts "Request error: #{e.message}"
    return nil
  end
end

# POST request (sending form data)
def post_form_data
  uri = URI('https://api.example.com/login')
  
  form_data = {
    'username' => 'testuser',
    'password' => 'secret123',
    'remember_me' => 'true'
  }
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  request = Net::HTTP::Post.new(uri)
  request['Content-Type'] = 'application/x-www-form-urlencoded'
  request['User-Agent'] = 'MyApp/1.0 (Ruby)'
  
  request.set_form_data(form_data)
  
  response = http.request(request)
  
  puts "Login Response: #{response.code}"
  puts "Set-Cookie: #{response['Set-Cookie']}" if response['Set-Cookie']
  
  response
end

# PUT request (data update)
def put_request
  uri = URI('https://api.example.com/users/123')
  
  update_data = {
    name: 'Jane Doe',
    email: '[email protected]',
    age: 31
  }
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  request = Net::HTTP::Put.new(uri)
  request['Content-Type'] = 'application/json'
  request['Authorization'] = 'Bearer your-access-token'
  request.body = JSON.generate(update_data)
  
  response = http.request(request)
  
  if response.is_a?(Net::HTTPSuccess)
    puts "User updated successfully"
    JSON.parse(response.body)
  else
    puts "Update failed: #{response.code} - #{response.body}"
    nil
  end
end

# DELETE request
def delete_request
  uri = URI('https://api.example.com/users/123')
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  request = Net::HTTP::Delete.new(uri)
  request['Authorization'] = 'Bearer your-access-token'
  
  response = http.request(request)
  
  case response.code
  when '204'
    puts "User deleted successfully"
    true
  when '404'
    puts "User not found"
    false
  else
    puts "Delete failed: #{response.code} - #{response.body}"
    false
  end
end

# Detailed response analysis
def analyze_response(response)
  puts "=== Response Analysis ==="
  puts "Status: #{response.code} #{response.message}"
  puts "HTTP Version: #{response.http_version}"
  puts "Content-Type: #{response.content_type}"
  puts "Content-Length: #{response.content_length}"
  puts "Last-Modified: #{response['Last-Modified']}"
  puts "ETag: #{response['ETag']}"
  puts "Cache-Control: #{response['Cache-Control']}"
  
  # Response body character encoding
  if response.body
    puts "Body Encoding: #{response.body.encoding}"
    puts "Body Size: #{response.body.bytesize} bytes"
  end
  
  puts "========================"
end

# Usage examples
simple_get_request
get_request_with_params
post_json_request
post_form_data
put_request
delete_request

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

require 'net/http'
require 'openssl'
require 'base64'

# Basic authentication
def basic_auth_request
  uri = URI('https://api.example.com/private')
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  request = Net::HTTP::Get.new(uri)
  request.basic_auth('username', 'password')
  
  # Or manually set header
  # credentials = Base64.strict_encode64("username:password")
  # request['Authorization'] = "Basic #{credentials}"
  
  response = http.request(request)
  puts "Basic Auth Response: #{response.code}"
  
  response
end

# Custom headers and timeout configuration
def custom_headers_and_timeout
  uri = URI('https://api.example.com/data')
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  # Timeout settings
  http.open_timeout = 10      # Connection timeout (seconds)
  http.read_timeout = 30      # Read timeout (seconds)
  http.write_timeout = 30     # Write timeout (seconds)
  http.continue_timeout = 1   # 100-continue timeout (seconds)
  
  request = Net::HTTP::Get.new(uri)
  
  # Custom header settings
  request['User-Agent'] = 'MyApp/1.0 (Ruby Net::HTTP)'
  request['Accept'] = 'application/json, application/xml'
  request['Accept-Language'] = 'en-US, ja-JP'
  request['Accept-Encoding'] = 'gzip, deflate, br'
  request['Cache-Control'] = 'no-cache'
  request['X-API-Version'] = 'v2'
  request['X-Request-ID'] = SecureRandom.uuid
  request['X-Client-Info'] = 'Ruby/3.2.0 Net::HTTP/0.4.0'
  
  begin
    response = http.request(request)
    puts "Custom Request Response: #{response.code}"
    
    # Check response compression
    if response['Content-Encoding']
      puts "Content-Encoding: #{response['Content-Encoding']}"
    end
    
    response
  rescue Net::OpenTimeout => e
    puts "Connection timeout: #{e.message}"
    nil
  rescue Net::ReadTimeout => e
    puts "Read timeout: #{e.message}"
    nil
  end
end

# SSL/TLS configuration and client certificates
def ssl_configuration
  uri = URI('https://secure-api.example.com/data')
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  # SSL settings
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  http.verify_depth = 5
  
  # CA certificate specification
  http.ca_file = '/path/to/ca-bundle.crt'  # CA certificate file
  # http.ca_path = '/path/to/ca-certs/'    # CA certificate directory
  
  # Client certificate configuration
  http.cert = OpenSSL::X509::Certificate.new(File.read('/path/to/client.crt'))
  http.key = OpenSSL::PKey::RSA.new(File.read('/path/to/client.key'), 'key-password')
  
  # Encryption settings
  http.ssl_version = :TLSv1_2  # TLS version specification
  http.ciphers = 'HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA'
  
  # Detailed SSL certificate verification (disable verification in development environment)
  # http.verify_mode = OpenSSL::SSL::VERIFY_NONE  # Not recommended for production
  
  # SSL certificate verification callback
  http.verify_callback = proc do |preverify_ok, ssl_context|
    if preverify_ok
      puts "SSL Certificate verification: OK"
    else
      puts "SSL Certificate verification failed: #{ssl_context.error_string}"
    end
    preverify_ok
  end
  
  request = Net::HTTP::Get.new(uri)
  
  begin
    response = http.request(request)
    puts "SSL Request Response: #{response.code}"
    
    # Display SSL connection information
    puts "SSL Cipher: #{http.peer_cert.to_text}" if http.peer_cert
    
    response
  rescue OpenSSL::SSL::SSLError => e
    puts "SSL Error: #{e.message}"
    nil
  end
end

# Proxy server configuration
def proxy_configuration
  # Proxy information
  proxy_host = 'proxy.example.com'
  proxy_port = 8080
  proxy_user = 'proxy_username'
  proxy_pass = 'proxy_password'
  
  uri = URI('https://api.example.com/data')
  
  # HTTP connection with proxy
  http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port, proxy_user, proxy_pass)
  http.use_ssl = true
  
  # When proxy authentication is not required
  # http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port)
  
  request = Net::HTTP::Get.new(uri)
  
  begin
    response = http.request(request)
    puts "Proxy Request Response: #{response.code}"
    response
  rescue Net::ProxyError => e
    puts "Proxy Error: #{e.message}"
    nil
  end
end

# Session management and cookie processing
class HTTPSession
  def initialize(base_url)
    @uri = URI(base_url)
    @cookies = {}
    @http = create_http_connection
  end
  
  def create_http_connection
    http = Net::HTTP.new(@uri.host, @uri.port)
    http.use_ssl = @uri.scheme == 'https'
    http.verify_mode = OpenSSL::SSL::VERIFY_PEER
    http.read_timeout = 30
    http.open_timeout = 10
    http
  end
  
  def get(path, headers = {})
    request = Net::HTTP::Get.new(path)
    add_headers(request, headers)
    add_cookies(request)
    
    response = @http.request(request)
    extract_cookies(response)
    
    response
  end
  
  def post(path, data = nil, headers = {})
    request = Net::HTTP::Post.new(path)
    add_headers(request, headers)
    add_cookies(request)
    
    if data
      if headers['Content-Type'] == 'application/json'
        request.body = JSON.generate(data)
      else
        request.set_form_data(data)
      end
    end
    
    response = @http.request(request)
    extract_cookies(response)
    
    response
  end
  
  def login(username, password, login_path = '/login')
    login_data = {
      username: username,
      password: password
    }
    
    response = post(login_path, login_data)
    
    if response.is_a?(Net::HTTPSuccess)
      puts "Login successful"
      true
    else
      puts "Login failed: #{response.code}"
      false
    end
  end
  
  def close
    @http.finish if @http.started?
  end
  
  private
  
  def add_headers(request, headers)
    default_headers = {
      'User-Agent' => 'Ruby HTTPSession/1.0',
      'Accept' => 'application/json'
    }
    
    default_headers.merge(headers).each do |key, value|
      request[key] = value
    end
  end
  
  def add_cookies(request)
    return if @cookies.empty?
    
    cookie_string = @cookies.map { |name, value| "#{name}=#{value}" }.join('; ')
    request['Cookie'] = cookie_string
  end
  
  def extract_cookies(response)
    return unless response['Set-Cookie']
    
    response.get_fields('Set-Cookie').each do |cookie|
      # Cookie parsing (simplified version)
      cookie_parts = cookie.split(';').first.split('=', 2)
      next unless cookie_parts.size == 2
      
      name, value = cookie_parts
      @cookies[name.strip] = value.strip
    end
  end
end

# Session usage example
def session_example
  session = HTTPSession.new('https://api.example.com')
  
  begin
    # Login
    if session.login('testuser', 'password123')
      # Call API that requires authentication
      response = session.get('/profile')
      puts "Profile Response: #{response.code}"
      
      # Post data
      post_data = { message: 'Hello from Ruby!' }
      response = session.post('/posts', post_data, { 'Content-Type' => 'application/json' })
      puts "Post Response: #{response.code}"
    end
  ensure
    session.close
  end
end

# Usage examples
basic_auth_request
custom_headers_and_timeout
ssl_configuration
proxy_configuration
session_example

Error Handling and Retry Functionality

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

# Comprehensive error handling
class HTTPClient
  class << self
    def safe_request(method, url, options = {})
      uri = URI(url)
      retries = options[:retries] || 3
      timeout = options[:timeout] || 30
      headers = options[:headers] || {}
      body = options[:body]
      
      (retries + 1).times do |attempt|
        begin
          http = Net::HTTP.new(uri.host, uri.port)
          http.use_ssl = uri.scheme == 'https'
          http.read_timeout = timeout
          http.open_timeout = timeout / 3
          
          request = create_request(method, uri, headers, body)
          
          response = http.request(request)
          
          # Success response determination
          if response.is_a?(Net::HTTPSuccess)
            return parse_response(response, options[:parse_json])
          elsif response.is_a?(Net::HTTPServerError) && attempt < retries
            puts "Server error (#{response.code}), retrying in #{attempt + 1} seconds..."
            sleep(attempt + 1)
            next
          else
            handle_http_error(response)
            return nil
          end
          
        rescue Net::TimeoutError => e
          if attempt < retries
            puts "Timeout error, retrying in #{attempt + 1} seconds..."
            sleep(attempt + 1)
            next
          else
            puts "Request timeout after #{retries + 1} attempts: #{e.message}"
            return nil
          end
          
        rescue Net::ConnectionRefused => e
          if attempt < retries
            puts "Connection refused, retrying in #{(attempt + 1) * 2} seconds..."
            sleep((attempt + 1) * 2)
            next
          else
            puts "Connection refused after #{retries + 1} attempts: #{e.message}"
            return nil
          end
          
        rescue SocketError => e
          puts "DNS/Socket error: #{e.message}"
          return nil
          
        rescue OpenSSL::SSL::SSLError => e
          puts "SSL error: #{e.message}"
          return nil
          
        rescue StandardError => e
          puts "Unexpected error: #{e.class} - #{e.message}"
          puts e.backtrace.first(5) if options[:debug]
          return nil
        end
      end
      
      nil
    end
    
    private
    
    def create_request(method, uri, headers, body)
      case method.to_s.upcase
      when 'GET'
        request = Net::HTTP::Get.new(uri)
      when 'POST'
        request = Net::HTTP::Post.new(uri)
      when 'PUT'
        request = Net::HTTP::Put.new(uri)
      when 'DELETE'
        request = Net::HTTP::Delete.new(uri)
      when 'PATCH'
        request = Net::HTTP::Patch.new(uri)
      else
        raise ArgumentError, "Unsupported HTTP method: #{method}"
      end
      
      # Header settings
      headers.each { |key, value| request[key] = value }
      
      # Body settings
      if body
        if headers['Content-Type'] == 'application/json'
          request.body = body.is_a?(String) ? body : JSON.generate(body)
        else
          request.body = body
        end
      end
      
      request
    end
    
    def parse_response(response, parse_json = true)
      content_type = response.content_type
      
      if parse_json && content_type&.include?('application/json')
        begin
          JSON.parse(response.body)
        rescue JSON::ParserError => e
          puts "JSON parse error: #{e.message}"
          response.body
        end
      else
        response.body
      end
    end
    
    def handle_http_error(response)
      case response.code.to_i
      when 400
        puts "Bad Request (400): Invalid request"
      when 401
        puts "Unauthorized (401): Authentication required"
      when 403
        puts "Forbidden (403): Access denied"
      when 404
        puts "Not Found (404): Resource not found"
      when 409
        puts "Conflict (409): Resource conflict occurred"
      when 422
        puts "Unprocessable Entity (422): Validation error"
        puts response.body if response.body
      when 429
        puts "Too Many Requests (429): Rate limit reached"
        puts "Retry-After: #{response['Retry-After']}" if response['Retry-After']
      when 500
        puts "Internal Server Error (500): Server internal error"
      when 502
        puts "Bad Gateway (502): Proxy server error"
      when 503
        puts "Service Unavailable (503): Service unavailable"
      when 504
        puts "Gateway Timeout (504): Gateway timeout"
      else
        puts "HTTP Error #{response.code}: #{response.message}"
        puts response.body if response.body
      end
    end
  end
end

# Detailed response analysis class
class ResponseAnalyzer
  def self.analyze(response)
    puts "=== Response Analysis ==="
    puts "Status: #{response.code} #{response.message}"
    puts "HTTP Version: #{response.http_version}"
    
    # Header analysis
    analyze_headers(response)
    
    # Body analysis
    analyze_body(response)
    
    # Performance analysis
    analyze_performance(response)
    
    puts "========================"
  end
  
  private
  
  def self.analyze_headers(response)
    puts "\n--- Headers Analysis ---"
    puts "Content-Type: #{response.content_type}"
    puts "Content-Length: #{response.content_length}"
    puts "Content-Encoding: #{response['Content-Encoding']}" if response['Content-Encoding']
    puts "Cache-Control: #{response['Cache-Control']}" if response['Cache-Control']
    puts "ETag: #{response['ETag']}" if response['ETag']
    puts "Last-Modified: #{response['Last-Modified']}" if response['Last-Modified']
    puts "Server: #{response['Server']}" if response['Server']
    puts "Set-Cookie: #{response['Set-Cookie']}" if response['Set-Cookie']
  end
  
  def self.analyze_body(response)
    puts "\n--- Body Analysis ---"
    if response.body
      puts "Body Size: #{response.body.bytesize} bytes"
      puts "Body Encoding: #{response.body.encoding}"
      
      # Check if JSON
      if response.content_type&.include?('application/json')
        begin
          json_data = JSON.parse(response.body)
          puts "JSON Structure: #{analyze_json_structure(json_data)}"
        rescue JSON::ParserError
          puts "Invalid JSON format"
        end
      end
    else
      puts "No body content"
    end
  end
  
  def self.analyze_performance(response)
    puts "\n--- Performance Info ---"
    # Note: Detailed timing information is not available by default in Net::HTTP
    puts "Response Headers Count: #{response.to_hash.keys.length}"
    puts "Keep-Alive: #{response['Connection']}" if response['Connection']
  end
  
  def self.analyze_json_structure(data, level = 0)
    indent = "  " * level
    case data
    when Hash
      "Hash(#{data.keys.length} keys: #{data.keys.first(3).join(', ')}#{data.keys.length > 3 ? '...' : ''})"
    when Array
      "Array(#{data.length} items)"
    else
      data.class.name
    end
  end
end

# Practical API client example
class RestAPIClient
  def initialize(base_url, api_key = nil)
    @base_url = base_url.chomp('/')
    @api_key = api_key
  end
  
  def get(endpoint, params = {})
    url = build_url(endpoint, params)
    HTTPClient.safe_request(:get, url, {
      headers: default_headers,
      parse_json: true,
      retries: 3,
      timeout: 30
    })
  end
  
  def post(endpoint, data = {})
    url = build_url(endpoint)
    HTTPClient.safe_request(:post, url, {
      headers: default_headers.merge('Content-Type' => 'application/json'),
      body: data,
      parse_json: true,
      retries: 2,
      timeout: 45
    })
  end
  
  def put(endpoint, data = {})
    url = build_url(endpoint)
    HTTPClient.safe_request(:put, url, {
      headers: default_headers.merge('Content-Type' => 'application/json'),
      body: data,
      parse_json: true,
      retries: 2,
      timeout: 45
    })
  end
  
  def delete(endpoint)
    url = build_url(endpoint)
    HTTPClient.safe_request(:delete, url, {
      headers: default_headers,
      parse_json: false,
      retries: 2,
      timeout: 30
    })
  end
  
  private
  
  def build_url(endpoint, params = {})
    url = "#{@base_url}/#{endpoint.gsub(/^\//, '')}"
    
    unless params.empty?
      query_string = URI.encode_www_form(params)
      url += "?#{query_string}"
    end
    
    url
  end
  
  def default_headers
    headers = {
      'User-Agent' => 'RestAPIClient/1.0 (Ruby Net::HTTP)',
      'Accept' => 'application/json'
    }
    
    headers['Authorization'] = "Bearer #{@api_key}" if @api_key
    
    headers
  end
end

# Usage examples
def error_handling_examples
  # Basic usage example
  result = HTTPClient.safe_request(:get, 'https://api.example.com/users', {
    headers: { 'Authorization' => 'Bearer token123' },
    parse_json: true,
    retries: 3,
    timeout: 30,
    debug: true
  })
  
  puts "Result: #{result}"
  
  # API client usage example
  client = RestAPIClient.new('https://api.example.com', 'your-api-key')
  
  users = client.get('users', { page: 1, limit: 10 })
  puts "Users: #{users}"
  
  new_user = client.post('users', { name: 'John Doe', email: '[email protected]' })
  puts "Created user: #{new_user}"
end

error_handling_examples

File Upload, Download, and Form Processing

require 'net/http'
require 'net/http/post/multipart'  # gem install multipart-post
require 'uri'
require 'mime/types'

# File upload (multipart format)
def upload_file(file_path, upload_url, additional_fields = {})
  uri = URI(upload_url)
  
  # Check file existence
  unless File.exist?(file_path)
    puts "File not found: #{file_path}"
    return nil
  end
  
  # MIME type detection
  mime_type = MIME::Types.type_for(file_path).first&.content_type || 'application/octet-stream'
  
  # Prepare multipart data
  File.open(file_path, 'rb') do |file|
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    http.read_timeout = 300  # 5-minute timeout
    
    request = Net::HTTP::Post.new(uri)
    request['Authorization'] = 'Bearer your-upload-token'
    
    # Build form data
    form_data = additional_fields.merge(
      'file' => UploadIO.new(file, mime_type, File.basename(file_path))
    )
    
    request.set_form(form_data, 'multipart/form-data')
    
    begin
      puts "Uploading file: #{file_path} (#{File.size(file_path)} bytes)"
      
      response = http.request(request)
      
      case response.code
      when '200', '201'
        puts "Upload successful: #{response.code}"
        JSON.parse(response.body) if response.body
      else
        puts "Upload failed: #{response.code} - #{response.body}"
        nil
      end
      
    rescue StandardError => e
      puts "Upload error: #{e.message}"
      nil
    end
  end
end

# Manual multipart form (implementation without external gems)
def upload_file_manual(file_path, upload_url, fields = {})
  uri = URI(upload_url)
  boundary = "----WebKitFormBoundary#{SecureRandom.hex(16)}"
  
  File.open(file_path, 'rb') do |file|
    # Build multipart body
    body = []
    
    # Additional fields
    fields.each do |name, value|
      body << "--#{boundary}\r\n"
      body << "Content-Disposition: form-data; name=\"#{name}\"\r\n"
      body << "\r\n"
      body << "#{value}\r\n"
    end
    
    # File part
    body << "--#{boundary}\r\n"
    body << "Content-Disposition: form-data; name=\"file\"; filename=\"#{File.basename(file_path)}\"\r\n"
    body << "Content-Type: #{MIME::Types.type_for(file_path).first&.content_type || 'application/octet-stream'}\r\n"
    body << "\r\n"
    body << file.read
    body << "\r\n"
    body << "--#{boundary}--\r\n"
    
    request_body = body.join
    
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    
    request = Net::HTTP::Post.new(uri)
    request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
    request['Content-Length'] = request_body.bytesize.to_s
    request['Authorization'] = 'Bearer your-token'
    request.body = request_body
    
    response = http.request(request)
    
    puts "Manual upload response: #{response.code}"
    response
  end
end

# File download
def download_file(download_url, output_path, chunk_size = 8192)
  uri = URI(download_url)
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = uri.scheme == 'https'
  
  request = Net::HTTP::Get.new(uri)
  request['User-Agent'] = 'Ruby FileDownloader/1.0'
  
  begin
    # Create directory
    FileUtils.mkdir_p(File.dirname(output_path))
    
    http.request(request) do |response|
      case response.code
      when '200'
        total_size = response['Content-Length']&.to_i
        downloaded = 0
        
        puts "Downloading: #{download_url}"
        puts "Total size: #{total_size ? "#{total_size} bytes" : "unknown"}"
        
        File.open(output_path, 'wb') do |file|
          response.read_body do |chunk|
            file.write(chunk)
            downloaded += chunk.bytesize
            
            # Progress display
            if total_size
              progress = (downloaded.to_f / total_size * 100).round(1)
              print "\rProgress: #{progress}% (#{downloaded}/#{total_size} bytes)"
            else
              print "\rDownloaded: #{downloaded} bytes"
            end
          end
        end
        
        puts "\nDownload completed: #{output_path}"
        return true
        
      when '404'
        puts "File not found: #{download_url}"
        return false
        
      else
        puts "Download failed: #{response.code} - #{response.message}"
        return false
      end
    end
    
  rescue StandardError => e
    puts "Download error: #{e.message}"
    return false
  end
end

# Large file streaming download
def stream_download(url, output_path)
  uri = URI(url)
  
  Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
    request = Net::HTTP::Get.new(uri)
    
    http.request(request) do |response|
      if response.code == '200'
        File.open(output_path, 'wb') do |file|
          response.read_body { |chunk| file.write(chunk) }
        end
        puts "Stream download completed: #{output_path}"
      else
        puts "Stream download failed: #{response.code}"
      end
    end
  end
end

# Form data processing
class FormProcessor
  def self.submit_form(form_url, form_data, file_fields = {})
    uri = URI(form_url)
    
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    
    request = Net::HTTP::Post.new(uri)
    
    if file_fields.any?
      # Form with files
      submit_multipart_form(request, form_data, file_fields)
    else
      # Regular form
      request.set_form_data(form_data)
    end
    
    response = http.request(request)
    
    puts "Form submission response: #{response.code}"
    response
  end
  
  def self.submit_json_form(form_url, json_data)
    uri = URI(form_url)
    
    http = Net::HTTP.new(uri.host, uri.port)
    http.use_ssl = uri.scheme == 'https'
    
    request = Net::HTTP::Post.new(uri)
    request['Content-Type'] = 'application/json'
    request.body = JSON.generate(json_data)
    
    response = http.request(request)
    
    puts "JSON form submission response: #{response.code}"
    response
  end
  
  private
  
  def self.submit_multipart_form(request, form_data, file_fields)
    # multipart/form-data implementation
    boundary = "----FormBoundary#{SecureRandom.hex(16)}"
    body = []
    
    # Text fields
    form_data.each do |name, value|
      body << "--#{boundary}\r\n"
      body << "Content-Disposition: form-data; name=\"#{name}\"\r\n"
      body << "\r\n"
      body << "#{value}\r\n"
    end
    
    # File fields
    file_fields.each do |name, file_path|
      next unless File.exist?(file_path)
      
      File.open(file_path, 'rb') do |file|
        body << "--#{boundary}\r\n"
        body << "Content-Disposition: form-data; name=\"#{name}\"; filename=\"#{File.basename(file_path)}\"\r\n"
        body << "Content-Type: #{MIME::Types.type_for(file_path).first&.content_type || 'application/octet-stream'}\r\n"
        body << "\r\n"
        body << file.read
        body << "\r\n"
      end
    end
    
    body << "--#{boundary}--\r\n"
    
    request['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
    request.body = body.join
  end
end

# Usage examples and tests
def file_operations_examples
  # File upload example
  upload_result = upload_file(
    '/path/to/document.pdf',
    'https://api.example.com/upload',
    {
      'category' => 'documents',
      'public' => 'false',
      'description' => 'Test document'
    }
  )
  
  puts "Upload result: #{upload_result}"
  
  # File download example
  download_success = download_file(
    'https://api.example.com/files/sample.zip',
    '/tmp/downloads/sample.zip'
  )
  
  puts "Download success: #{download_success}"
  
  # Form submission example
  form_response = FormProcessor.submit_form(
    'https://example.com/contact',
    {
      'name' => 'John Doe',
      'email' => '[email protected]',
      'message' => 'Contact inquiry'
    },
    {
      'attachment' => '/path/to/attachment.pdf'
    }
  )
  
  puts "Form response: #{form_response.code}"
  
  # JSON form submission example
  json_response = FormProcessor.submit_json_form(
    'https://api.example.com/feedback',
    {
      user_id: 123,
      rating: 5,
      comment: 'Excellent service',
      timestamp: Time.now.iso8601
    }
  )
  
  puts "JSON form response: #{json_response.code}"
end

file_operations_examples