net/http

Go標準ライブラリのHTTPクライアント・サーバーパッケージ。堅牢性、柔軟性、包括的なドキュメントを提供。HTTP/1.1、HTTP/2サポート、詳細な設定オプション、Cookie管理、リダイレクト処理、タイムアウト制御機能を内蔵。外部依存関係不要。

HTTPクライアントRuby標準ライブラリRESTful APISSL/TLSセッション管理

GitHub概要

ruby/ruby

The Ruby Programming Language

スター23,021
ウォッチ1,081
フォーク5,489
作成日:2010年2月27日
言語:Ruby
ライセンス:Other

トピックス

cjitlanguageobject-orientedprogramming-languagerubyruby-languagerust

スター履歴

ruby/ruby Star History
データ取得日時: 2025/10/22 09:54

ライブラリ

Net::HTTP

概要

Net::HTTPは「Ruby標準ライブラリのHTTPクライアント実装」として開発された、Rubyエコシステムの基盤となるHTTPクライアントライブラリです。Ruby標準ライブラリの一部として長年にわたり安定した動作を提供し、シンプルで直感的なAPIを通じてHTTP通信を実現。RESTful API開発、Webスクレイピング、外部サービス連携など様々な用途で活用され、多くのRuby HTTPライブラリの基盤として機能。標準ライブラリならではの高い信頼性と互換性により、Ruby開発者にとって必須のネットワーキングツールとしての地位を確立しています。

詳細

Net::HTTP 2025年版はRuby標準ライブラリとして30年近い開発実績を持つ成熟したHTTPクライアントライブラリです。Ruby 3.x系での最新機能サポートを維持しながら、後方互換性を重視した設計により幅広いRubyバージョンで一貫した動作を提供。基本的なGET/POST/PUT/DELETEリクエストから、SSL/TLS通信、プロキシサーバー対応、Cookie管理、セッション管理、フォームデータ送信、ファイルアップロード等の高度な機能まで包括的にサポート。シンプルなクラスメソッドAPIと詳細制御可能なインスタンスAPIの両方を提供し、用途に応じた最適な実装方法を選択可能です。

主な特徴

  • Ruby標準ライブラリ: 追加インストール不要の標準搭載ライブラリ
  • 包括的HTTPサポート: GET、POST、PUT、DELETE、WebDAV等のメソッド対応
  • SSL/TLS完全対応: HTTPS通信、証明書検証、暗号化設定
  • セッション管理: 効率的な接続プールと永続的接続
  • 豊富な認証方式: Basic認証、Digest認証、プロキシ認証
  • フォーム処理: マルチパート形式とURL encoded形式の両対応

メリット・デメリット

メリット

  • Ruby標準ライブラリのため追加依存関係なしで即座に利用可能
  • 30年近い開発実績による圧倒的な安定性と成熟度
  • Ruby言語との深い統合により高いパフォーマンスと信頼性を実現
  • シンプルで直感的なAPIによる学習コストの低さ
  • 豊富な機能により企業レベルのHTTP通信要件に対応
  • 他のRuby HTTPライブラリとの高い互換性とエコシステム統合

デメリット

  • 同期処理ベースのため大量の並列リクエストに不向き
  • 非同期処理には別途em-http-request等の専用ライブラリが必要
  • APIの一部が低レベルで、現代的な高級ライブラリと比較して記述量が多い
  • JSONレスポンスの自動パースなどの便利機能が標準では未提供
  • エラーハンドリングが例外ベースで詳細な制御が必要
  • HTTP/2やHTTP/3などの最新プロトコルサポートに制限

参考ページ

書き方の例

インストールと基本セットアップ

# Ruby標準ライブラリのため require のみ
require 'net/http'
require 'uri'
require 'json'
require 'openssl'

# Ruby 3.0以降では明示的なrequireが必要な場合がある
require 'net/http'

# SSL証明書の検証を有効化(推奨)
Net::HTTP.verify_mode = OpenSSL::SSL::VERIFY_PEER

# グローバル設定(オプション)
Net::HTTP.class_eval do
  # デフォルトタイムアウト設定
  @@default_timeout = 30
  
  def self.default_timeout=(value)
    @@default_timeout = value
  end
  
  def self.default_timeout
    @@default_timeout
  end
end

# デバッグ用の詳細ログ出力(開発時のみ)
# Net::HTTP.start('example.com', 80, debug: true)

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

基本的なHTTPリクエスト(GET/POST/PUT/DELETE)

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

# 基本的なGETリクエスト
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リクエスト
def get_request_with_params
  base_uri = 'https://api.example.com/users'
  params = {
    page: 1,
    limit: 20,
    sort: 'created_at',
    filter: 'active'
  }
  
  # パラメータをクエリ文字列に変換
  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']}"
    # リダイレクト処理
    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リクエスト(JSON送信)
def post_json_request
  uri = URI('https://api.example.com/users')
  
  user_data = {
    name: '田中太郎',
    email: '[email protected]',
    age: 30,
    department: '開発部'
  }
  
  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リクエスト(フォームデータ送信)
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リクエスト(データ更新)
def put_request
  uri = URI('https://api.example.com/users/123')
  
  update_data = {
    name: '田中次郎',
    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リクエスト
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

# レスポンス詳細分析
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']}"
  
  # レスポンスボディの文字エンコーディング
  if response.body
    puts "Body Encoding: #{response.body.encoding}"
    puts "Body Size: #{response.body.bytesize} bytes"
  end
  
  puts "========================"
end

# 使用例
simple_get_request
get_request_with_params
post_json_request
post_form_data
put_request
delete_request

高度な設定とカスタマイズ(認証、SSL、セッション等)

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

# Basic認証
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')
  
  # または手動でヘッダー設定
  # credentials = Base64.strict_encode64("username:password")
  # request['Authorization'] = "Basic #{credentials}"
  
  response = http.request(request)
  puts "Basic Auth Response: #{response.code}"
  
  response
end

# カスタムヘッダーとタイムアウト設定
def custom_headers_and_timeout
  uri = URI('https://api.example.com/data')
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  # タイムアウト設定
  http.open_timeout = 10      # 接続タイムアウト(秒)
  http.read_timeout = 30      # 読み込みタイムアウト(秒)
  http.write_timeout = 30     # 書き込みタイムアウト(秒)
  http.continue_timeout = 1   # 100-continue タイムアウト(秒)
  
  request = Net::HTTP::Get.new(uri)
  
  # カスタムヘッダー設定
  request['User-Agent'] = 'MyApp/1.0 (Ruby Net::HTTP)'
  request['Accept'] = 'application/json, application/xml'
  request['Accept-Language'] = 'ja-JP, en-US'
  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}"
    
    # レスポンス圧縮の確認
    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設定とクライアント証明書
def ssl_configuration
  uri = URI('https://secure-api.example.com/data')
  
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  
  # SSL設定
  http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  http.verify_depth = 5
  
  # CA証明書の指定
  http.ca_file = '/path/to/ca-bundle.crt'  # CA証明書ファイル
  # http.ca_path = '/path/to/ca-certs/'    # CA証明書ディレクトリ
  
  # クライアント証明書設定
  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')
  
  # 暗号化設定
  http.ssl_version = :TLSv1_2  # TLSバージョン指定
  http.ciphers = 'HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!SRP:!CAMELLIA'
  
  # SSL証明書の詳細検証(開発環境での検証無効化)
  # http.verify_mode = OpenSSL::SSL::VERIFY_NONE  # 本番では非推奨
  
  # SSL証明書の検証コールバック
  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}"
    
    # SSL接続情報の表示
    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

# プロキシサーバー設定
def proxy_configuration
  # プロキシ情報
  proxy_host = 'proxy.example.com'
  proxy_port = 8080
  proxy_user = 'proxy_username'
  proxy_pass = 'proxy_password'
  
  uri = URI('https://api.example.com/data')
  
  # プロキシ付きHTTP接続
  http = Net::HTTP.new(uri.host, uri.port, proxy_host, proxy_port, proxy_user, proxy_pass)
  http.use_ssl = true
  
  # プロキシ認証が不要な場合
  # 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

# セッション管理とCookie処理
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_parts = cookie.split(';').first.split('=', 2)
      next unless cookie_parts.size == 2
      
      name, value = cookie_parts
      @cookies[name.strip] = value.strip
    end
  end
end

# セッション使用例
def session_example
  session = HTTPSession.new('https://api.example.com')
  
  begin
    # ログイン
    if session.login('testuser', 'password123')
      # 認証が必要なAPIの呼び出し
      response = session.get('/profile')
      puts "Profile Response: #{response.code}"
      
      # データの投稿
      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

# 使用例
basic_auth_request
custom_headers_and_timeout
ssl_configuration
proxy_configuration
session_example

エラーハンドリングとリトライ機能

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

# 包括的なエラーハンドリング
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)
          
          # 成功レスポンスの判定
          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
      
      # ヘッダー設定
      headers.each { |key, value| request[key] = value }
      
      # ボディ設定
      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): リクエストが不正です"
      when 401
        puts "Unauthorized (401): 認証が必要です"
      when 403
        puts "Forbidden (403): アクセス権限がありません"
      when 404
        puts "Not Found (404): リソースが見つかりません"
      when 409
        puts "Conflict (409): リソースの競合が発生しました"
      when 422
        puts "Unprocessable Entity (422): バリデーションエラー"
        puts response.body if response.body
      when 429
        puts "Too Many Requests (429): レート制限に達しました"
        puts "Retry-After: #{response['Retry-After']}" if response['Retry-After']
      when 500
        puts "Internal Server Error (500): サーバー内部エラー"
      when 502
        puts "Bad Gateway (502): プロキシサーバーエラー"
      when 503
        puts "Service Unavailable (503): サービス利用不可"
      when 504
        puts "Gateway Timeout (504): ゲートウェイタイムアウト"
      else
        puts "HTTP Error #{response.code}: #{response.message}"
        puts response.body if response.body
      end
    end
  end
end

# レスポンス詳細分析クラス
class ResponseAnalyzer
  def self.analyze(response)
    puts "=== Response Analysis ==="
    puts "Status: #{response.code} #{response.message}"
    puts "HTTP Version: #{response.http_version}"
    
    # ヘッダー分析
    analyze_headers(response)
    
    # ボディ分析
    analyze_body(response)
    
    # パフォーマンス分析
    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}"
      
      # 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: 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

# 実践的なAPIクライアント例
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

# 使用例
def error_handling_examples
  # 基本的な使用例
  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 = 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: '田中太郎', email: '[email protected]' })
  puts "Created user: #{new_user}"
end

error_handling_examples

ファイルアップロード・ダウンロード・フォーム処理

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

# ファイルアップロード(マルチパート形式)
def upload_file(file_path, upload_url, additional_fields = {})
  uri = URI(upload_url)
  
  # ファイルの存在確認
  unless File.exist?(file_path)
    puts "File not found: #{file_path}"
    return nil
  end
  
  # MIMEタイプの検出
  mime_type = MIME::Types.type_for(file_path).first&.content_type || 'application/octet-stream'
  
  # マルチパートデータの準備
  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分のタイムアウト
    
    request = Net::HTTP::Post.new(uri)
    request['Authorization'] = 'Bearer your-upload-token'
    
    # フォームデータの構築
    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

# 手動マルチパートフォーム(外部gemなしでの実装)
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|
    # マルチパートボディの構築
    body = []
    
    # 追加フィールド
    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
    
    # ファイル部分
    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

# ファイルダウンロード
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
    # ディレクトリの作成
    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
            
            # 進捗表示
            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

# 大容量ファイルのストリーミングダウンロード
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

# フォームデータ処理
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?
      # ファイル付きフォーム
      submit_multipart_form(request, form_data, file_fields)
    else
      # 通常のフォーム
      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 の実装
    boundary = "----FormBoundary#{SecureRandom.hex(16)}"
    body = []
    
    # テキストフィールド
    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.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

# 使用例とテスト
def file_operations_examples
  # ファイルアップロード例
  upload_result = upload_file(
    '/path/to/document.pdf',
    'https://api.example.com/upload',
    {
      'category' => 'documents',
      'public' => 'false',
      'description' => 'テストドキュメント'
    }
  )
  
  puts "Upload result: #{upload_result}"
  
  # ファイルダウンロード例
  download_success = download_file(
    'https://api.example.com/files/sample.zip',
    '/tmp/downloads/sample.zip'
  )
  
  puts "Download success: #{download_success}"
  
  # フォーム送信例
  form_response = FormProcessor.submit_form(
    'https://example.com/contact',
    {
      'name' => '田中太郎',
      'email' => '[email protected]',
      'message' => 'お問い合わせ内容'
    },
    {
      'attachment' => '/path/to/attachment.pdf'
    }
  )
  
  puts "Form response: #{form_response.code}"
  
  # JSON フォーム送信例
  json_response = FormProcessor.submit_json_form(
    'https://api.example.com/feedback',
    {
      user_id: 123,
      rating: 5,
      comment: 'とても良いサービスです',
      timestamp: Time.now.iso8601
    }
  )
  
  puts "JSON form response: #{json_response.code}"
end

file_operations_examples