diff --git a/README.md b/README.md index ba1b00a..e2d1709 100644 --- a/README.md +++ b/README.md @@ -71,7 +71,44 @@ python python/run_tests.py requests-proxy-headers httpx-proxy-headers ## Ruby Proxy Examples -* [requests_proxy.rb](ruby/requests_proxy.rb) - Ruby HTTP with proxy, from [rpolley](https://github.com/rpolley) +**Installation:** + +```bash +cd ruby +bundle install +``` + +**Running Examples:** + +```bash +# Required: Set your proxy URL +export PROXY_URL='http://user:pass@proxy.example.com:8080' + +# Run a single example +ruby ruby/faraday_proxy.rb + +# Run all examples as tests +ruby ruby/run_tests.rb + +# Run specific examples +ruby ruby/run_tests.rb faraday httparty +``` + +**Examples:** + +| Library | Example | Description | +|---------|---------|-------------| +| [Net::HTTP](https://ruby-doc.org/stdlib/libdoc/net/http/rdoc/Net/HTTP.html) | [net_http_proxy.rb](ruby/net_http_proxy.rb) | Ruby standard library HTTP client | +| [Faraday](https://lostisland.github.io/faraday/) | [faraday_proxy.rb](ruby/faraday_proxy.rb) | HTTP client with middleware support | +| [HTTParty](https://github.com/jnunemaker/httparty) | [httparty_proxy.rb](ruby/httparty_proxy.rb) | Makes HTTP fun again | +| [RestClient](https://github.com/rest-client/rest-client) | [rest_client_proxy.rb](ruby/rest_client_proxy.rb) | Simple REST client | +| [Typhoeus](https://typhoeus.github.io/) | [typhoeus_proxy.rb](ruby/typhoeus_proxy.rb) | Fast HTTP client (libcurl wrapper) | +| [HTTP.rb](https://github.com/httprb/http) | [http_rb_proxy.rb](ruby/http_rb_proxy.rb) | Simple Ruby DSL for HTTP | +| [Excon](https://github.com/excon/excon) | [excon_proxy.rb](ruby/excon_proxy.rb) | Fast, simple HTTP(S) client | +| [HTTPClient](https://github.com/nahi/httpclient) | [httpclient_proxy.rb](ruby/httpclient_proxy.rb) | LWP-like HTTP client | +| [Mechanize](https://github.com/sparklemotion/mechanize) | [mechanize_proxy.rb](ruby/mechanize_proxy.rb) | Web automation library | + +> **Note:** None of these libraries currently support sending custom headers to the proxy during HTTPS CONNECT tunneling or reading proxy response headers. See [ruby-proxy-headers](https://github.com/proxymeshai/ruby-proxy-headers) for extension modules that add this capability. ## Documentation diff --git a/ruby/Gemfile b/ruby/Gemfile new file mode 100644 index 0000000..4133e0b --- /dev/null +++ b/ruby/Gemfile @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +source 'https://rubygems.org' + +# HTTP clients +gem 'faraday', '~> 2.9' +gem 'httparty', '~> 0.21' +gem 'rest-client', '~> 2.1' +gem 'typhoeus', '~> 1.4' +gem 'http', '~> 5.2' +gem 'excon', '~> 0.110' +gem 'httpclient', '~> 2.8' +gem 'patron', '~> 0.13' +gem 'mechanize', '~> 2.10' + +# For running tests +gem 'minitest', '~> 5.22' diff --git a/ruby/excon_proxy.rb b/ruby/excon_proxy.rb new file mode 100644 index 0000000..05f2433 --- /dev/null +++ b/ruby/excon_proxy.rb @@ -0,0 +1,31 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Excon with proxy example. +# +# Configuration via environment variables: +# PROXY_URL - Proxy URL (required), e.g., http://user:pass@proxy:8080 +# TEST_URL - URL to request (default: https://api.ipify.org?format=json) +# +# Excon is a fast, simple HTTP(S) client. It supports proxies but does NOT +# support sending custom headers during HTTPS CONNECT or reading proxy response headers. + +require 'excon' + +proxy_url = ENV['PROXY_URL'] || ENV['HTTPS_PROXY'] +unless proxy_url + warn 'Error: Set PROXY_URL environment variable' + exit 1 +end + +test_url = ENV['TEST_URL'] || 'https://api.ipify.org?format=json' + +begin + response = Excon.get(test_url, proxy: proxy_url) + + puts "Status: #{response.status}" + puts "Body: #{response.body}" +rescue StandardError => e + warn "Error: #{e.message}" + exit 1 +end diff --git a/ruby/faraday_proxy.rb b/ruby/faraday_proxy.rb new file mode 100644 index 0000000..62f196b --- /dev/null +++ b/ruby/faraday_proxy.rb @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Faraday with proxy example. +# +# Configuration via environment variables: +# PROXY_URL - Proxy URL (required), e.g., http://user:pass@proxy:8080 +# TEST_URL - URL to request (default: https://api.ipify.org?format=json) +# +# Faraday is a popular HTTP client with middleware support. It supports proxies +# but does NOT support sending custom headers during HTTPS CONNECT or reading +# proxy response headers. + +require 'faraday' +require 'json' + +proxy_url = ENV['PROXY_URL'] || ENV['HTTPS_PROXY'] +unless proxy_url + warn 'Error: Set PROXY_URL environment variable' + exit 1 +end + +test_url = ENV['TEST_URL'] || 'https://api.ipify.org?format=json' + +begin + conn = Faraday.new(url: test_url) do |f| + f.proxy = proxy_url + f.adapter Faraday.default_adapter + end + + response = conn.get + + puts "Status: #{response.status}" + puts "Body: #{response.body}" +rescue StandardError => e + warn "Error: #{e.message}" + exit 1 +end diff --git a/ruby/http_rb_proxy.rb b/ruby/http_rb_proxy.rb new file mode 100644 index 0000000..76ac973 --- /dev/null +++ b/ruby/http_rb_proxy.rb @@ -0,0 +1,41 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# HTTP.rb (http gem) with proxy example. +# +# Configuration via environment variables: +# PROXY_URL - Proxy URL (required), e.g., http://user:pass@proxy:8080 +# TEST_URL - URL to request (default: https://api.ipify.org?format=json) +# +# HTTP.rb is a simple Ruby DSL for making HTTP requests. It supports proxies +# but does NOT support sending custom headers during HTTPS CONNECT or reading +# proxy response headers. + +require 'http' +require 'uri' + +proxy_url = ENV['PROXY_URL'] || ENV['HTTPS_PROXY'] +unless proxy_url + warn 'Error: Set PROXY_URL environment variable' + exit 1 +end + +test_url = ENV['TEST_URL'] || 'https://api.ipify.org?format=json' + +proxy_uri = URI.parse(proxy_url) + +proxy_options = [proxy_uri.host, proxy_uri.port] +if proxy_uri.user + proxy_options << proxy_uri.user + proxy_options << proxy_uri.password +end + +begin + response = HTTP.via(*proxy_options).get(test_url) + + puts "Status: #{response.status}" + puts "Body: #{response.body}" +rescue StandardError => e + warn "Error: #{e.message}" + exit 1 +end diff --git a/ruby/httparty_proxy.rb b/ruby/httparty_proxy.rb new file mode 100644 index 0000000..65a3830 --- /dev/null +++ b/ruby/httparty_proxy.rb @@ -0,0 +1,44 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# HTTParty with proxy example. +# +# Configuration via environment variables: +# PROXY_URL - Proxy URL (required), e.g., http://user:pass@proxy:8080 +# TEST_URL - URL to request (default: https://api.ipify.org?format=json) +# +# HTTParty makes HTTP fun! It supports proxies but does NOT support sending +# custom headers during HTTPS CONNECT or reading proxy response headers. + +require 'httparty' +require 'uri' + +proxy_url = ENV['PROXY_URL'] || ENV['HTTPS_PROXY'] +unless proxy_url + warn 'Error: Set PROXY_URL environment variable' + exit 1 +end + +test_url = ENV['TEST_URL'] || 'https://api.ipify.org?format=json' + +proxy_uri = URI.parse(proxy_url) + +http_proxy_options = { + http_proxyaddr: proxy_uri.host, + http_proxyport: proxy_uri.port +} + +if proxy_uri.user + http_proxy_options[:http_proxyuser] = proxy_uri.user + http_proxy_options[:http_proxypass] = proxy_uri.password +end + +begin + response = HTTParty.get(test_url, **http_proxy_options) + + puts "Status: #{response.code}" + puts "Body: #{response.body}" +rescue StandardError => e + warn "Error: #{e.message}" + exit 1 +end diff --git a/ruby/httpclient_proxy.rb b/ruby/httpclient_proxy.rb new file mode 100644 index 0000000..a152380 --- /dev/null +++ b/ruby/httpclient_proxy.rb @@ -0,0 +1,32 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# HTTPClient with proxy example. +# +# Configuration via environment variables: +# PROXY_URL - Proxy URL (required), e.g., http://user:pass@proxy:8080 +# TEST_URL - URL to request (default: https://api.ipify.org?format=json) +# +# HTTPClient provides LWP-like functionality. It has advanced proxy support +# but does NOT expose custom CONNECT headers or proxy response headers. + +require 'httpclient' + +proxy_url = ENV['PROXY_URL'] || ENV['HTTPS_PROXY'] +unless proxy_url + warn 'Error: Set PROXY_URL environment variable' + exit 1 +end + +test_url = ENV['TEST_URL'] || 'https://api.ipify.org?format=json' + +begin + client = HTTPClient.new(proxy_url) + response = client.get(test_url) + + puts "Status: #{response.status}" + puts "Body: #{response.body}" +rescue StandardError => e + warn "Error: #{e.message}" + exit 1 +end diff --git a/ruby/mechanize_proxy.rb b/ruby/mechanize_proxy.rb new file mode 100644 index 0000000..da088ae --- /dev/null +++ b/ruby/mechanize_proxy.rb @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Mechanize with proxy example. +# +# Configuration via environment variables: +# PROXY_URL - Proxy URL (required), e.g., http://user:pass@proxy:8080 +# TEST_URL - URL to request (default: https://example.com) +# +# Mechanize is a web automation library. It uses Net::HTTP internally and +# supports proxies, but does NOT support custom CONNECT headers or proxy +# response headers. + +require 'mechanize' +require 'uri' + +proxy_url = ENV['PROXY_URL'] || ENV['HTTPS_PROXY'] +unless proxy_url + warn 'Error: Set PROXY_URL environment variable' + exit 1 +end + +test_url = ENV['TEST_URL'] || 'https://example.com' + +proxy_uri = URI.parse(proxy_url) + +begin + agent = Mechanize.new + agent.set_proxy(proxy_uri.host, proxy_uri.port, proxy_uri.user, proxy_uri.password) + + page = agent.get(test_url) + + puts "Status: #{page.code}" + puts "Title: #{page.title}" +rescue StandardError => e + warn "Error: #{e.message}" + exit 1 +end diff --git a/ruby/net_http_proxy.rb b/ruby/net_http_proxy.rb new file mode 100644 index 0000000..e433aca --- /dev/null +++ b/ruby/net_http_proxy.rb @@ -0,0 +1,45 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Net::HTTP with proxy example. +# +# Configuration via environment variables: +# PROXY_URL - Proxy URL (required), e.g., http://user:pass@proxy:8080 +# TEST_URL - URL to request (default: https://api.ipify.org?format=json) +# +# Net::HTTP is Ruby's built-in HTTP client. It supports proxies but does NOT +# support sending custom headers during HTTPS CONNECT or reading proxy response headers. + +require 'net/http' +require 'uri' + +proxy_url = ENV['PROXY_URL'] || ENV['HTTPS_PROXY'] +unless proxy_url + warn 'Error: Set PROXY_URL environment variable' + exit 1 +end + +test_url = ENV['TEST_URL'] || 'https://api.ipify.org?format=json' + +proxy_uri = URI.parse(proxy_url) +target_uri = URI.parse(test_url) + +proxy_options = { + p_addr: proxy_uri.host, + p_port: proxy_uri.port, + p_user: proxy_uri.user, + p_pass: proxy_uri.password +} + +begin + Net::HTTP.start(target_uri.host, target_uri.port, **proxy_options, use_ssl: target_uri.scheme == 'https') do |http| + request = Net::HTTP::Get.new(target_uri) + response = http.request(request) + + puts "Status: #{response.code}" + puts "Body: #{response.body}" + end +rescue StandardError => e + warn "Error: #{e.message}" + exit 1 +end diff --git a/ruby/rest_client_proxy.rb b/ruby/rest_client_proxy.rb new file mode 100644 index 0000000..25edf36 --- /dev/null +++ b/ruby/rest_client_proxy.rb @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# RestClient with proxy example. +# +# Configuration via environment variables: +# PROXY_URL - Proxy URL (required), e.g., http://user:pass@proxy:8080 +# TEST_URL - URL to request (default: https://api.ipify.org?format=json) +# +# RestClient is a simple REST client. It reads proxy from environment variables +# (HTTP_PROXY/HTTPS_PROXY) or can be set via RestClient.proxy. +# Does NOT support custom CONNECT headers or proxy response headers. + +require 'rest-client' + +proxy_url = ENV['PROXY_URL'] || ENV['HTTPS_PROXY'] +unless proxy_url + warn 'Error: Set PROXY_URL environment variable' + exit 1 +end + +test_url = ENV['TEST_URL'] || 'https://api.ipify.org?format=json' + +# Set proxy globally +RestClient.proxy = proxy_url + +begin + response = RestClient.get(test_url) + + puts "Status: #{response.code}" + puts "Body: #{response.body}" +rescue RestClient::ExceptionWithResponse => e + warn "HTTP Error: #{e.response.code}" + exit 1 +rescue StandardError => e + warn "Error: #{e.message}" + exit 1 +end diff --git a/ruby/run_tests.rb b/ruby/run_tests.rb new file mode 100644 index 0000000..6f24eaf --- /dev/null +++ b/ruby/run_tests.rb @@ -0,0 +1,175 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Run all Ruby proxy examples as tests. +# +# Configuration via environment variables: +# PROXY_URL - Proxy URL (required), e.g., http://user:pass@proxy:8080 +# TEST_URL - URL to request (default: https://api.ipify.org?format=json) +# PROXY_HEADER - Header name to send to proxy (optional) +# PROXY_VALUE - Header value to send to proxy (optional) +# +# Usage: +# ruby run_tests.rb # Run all examples +# ruby run_tests.rb faraday # Run specific example +# ruby run_tests.rb -l # List available examples +# ruby run_tests.rb -v # Verbose output + +EXAMPLES = %w[ + net_http_proxy.rb + faraday_proxy.rb + httparty_proxy.rb + rest_client_proxy.rb + typhoeus_proxy.rb + http_rb_proxy.rb + excon_proxy.rb + httpclient_proxy.rb + mechanize_proxy.rb +].freeze + +def parse_args(args) + options = { + verbose: false, + list: false, + help: false, + examples: [] + } + + args.each do |arg| + case arg + when '-v', '--verbose' + options[:verbose] = true + when '-l', '--list' + options[:list] = true + when '-h', '--help' + options[:help] = true + else + options[:examples] << arg unless arg.start_with?('-') + end + end + + options +end + +def show_help + puts <<~HELP + Run all Ruby proxy examples as tests. + + Usage: + ruby run_tests.rb [options] [example1] [example2] ... + + Options: + -v, --verbose Show full output from each example + -l, --list List available examples + -h, --help Show this help message + + Environment Variables: + PROXY_URL Proxy URL (required), e.g., http://user:pass@proxy:8080 + TEST_URL URL to request (default: https://api.ipify.org?format=json) + PROXY_HEADER Header name to send to proxy (optional) + PROXY_VALUE Header value to send to proxy (optional) + + Examples: + PROXY_URL='http://proxy:8080' ruby run_tests.rb + ruby run_tests.rb faraday httparty + HELP +end + +def list_examples + puts 'Available examples:' + EXAMPLES.each do |example| + puts " #{example.sub('_proxy.rb', '')}" + end +end + +def mask_password(url) + url.sub(/:[^:@]+@/, ':****@') +end + +def run_example(name, verbose) + script_dir = File.dirname(__FILE__) + script_path = File.join(script_dir, name) + + unless File.exist?(script_path) + return { success: false, error: "Script not found: #{script_path}" } + end + + output = `ruby #{script_path} 2>&1` + success = $?.success? + + if verbose + puts output + end + + { success: success, output: output } +end + +def main + options = parse_args(ARGV) + + if options[:help] + show_help + exit 0 + end + + if options[:list] + list_examples + exit 0 + end + + proxy_url = ENV['PROXY_URL'] || ENV['HTTPS_PROXY'] + unless proxy_url + warn 'Error: Set PROXY_URL environment variable' + warn "\nExample:" + warn " export PROXY_URL='http://user:pass@proxy:8080'" + exit 1 + end + + examples_to_run = if options[:examples].any? + EXAMPLES.select { |e| options[:examples].any? { |arg| e.include?(arg) } } + else + EXAMPLES + end + + test_url = ENV['TEST_URL'] || 'https://api.ipify.org?format=json' + + puts '=' * 60 + puts 'Ruby Proxy Examples - Test Runner' + puts '=' * 60 + puts "Proxy URL: #{mask_password(proxy_url)}" + puts "Test URL: #{test_url}" + puts "Examples: #{examples_to_run.length}" + puts '=' * 60 + puts + + passed = 0 + failed = 0 + + examples_to_run.each do |example| + name = example.sub('_proxy.rb', '') + print "Testing #{name}... " + + result = run_example(example, options[:verbose]) + + if result[:success] + puts 'OK' + passed += 1 + else + puts 'FAILED' + unless options[:verbose] + error_line = result[:output]&.lines&.first&.strip + puts " Error: #{error_line}" if error_line + end + failed += 1 + end + end + + puts + puts '=' * 60 + puts "Results: #{passed} passed, #{failed} failed" + puts '=' * 60 + + exit(failed.positive? ? 1 : 0) +end + +main if __FILE__ == $PROGRAM_NAME diff --git a/ruby/typhoeus_proxy.rb b/ruby/typhoeus_proxy.rb new file mode 100644 index 0000000..59a3dfd --- /dev/null +++ b/ruby/typhoeus_proxy.rb @@ -0,0 +1,41 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# Typhoeus with proxy example. +# +# Configuration via environment variables: +# PROXY_URL - Proxy URL (required), e.g., http://user:pass@proxy:8080 +# TEST_URL - URL to request (default: https://api.ipify.org?format=json) +# +# Typhoeus wraps libcurl for fast HTTP requests. It has the BEST proxy support +# of all Ruby HTTP libraries because libcurl supports CURLOPT_PROXYHEADER. +# However, the Ruby binding does not expose this option directly. +# Proxy response headers may be accessible via response.headers with some configuration. + +require 'typhoeus' + +proxy_url = ENV['PROXY_URL'] || ENV['HTTPS_PROXY'] +unless proxy_url + warn 'Error: Set PROXY_URL environment variable' + exit 1 +end + +test_url = ENV['TEST_URL'] || 'https://api.ipify.org?format=json' + +begin + response = Typhoeus.get(test_url, proxy: proxy_url) + + if response.success? + puts "Status: #{response.code}" + puts "Body: #{response.body}" + elsif response.timed_out? + warn 'Error: Request timed out' + exit 1 + else + warn "Error: #{response.return_message}" + exit 1 + end +rescue StandardError => e + warn "Error: #{e.message}" + exit 1 +end