Skip to content

Latest commit

 

History

History
133 lines (94 loc) · 4.32 KB

File metadata and controls

133 lines (94 loc) · 4.32 KB

Ruby Proxy Headers

Send and receive custom proxy headers during HTTPS CONNECT tunneling in modern Ruby HTTP workflows (for example ProxyMesh X-ProxyMesh-IP / X-ProxyMesh-Country).

The problem

Most Ruby HTTP clients use Net::HTTP, Faraday, or libcurl without exposing:

  1. Extra headers on the CONNECT request to the proxy.
  2. Headers from the proxy’s CONNECT response (often discarded after the tunnel is established).

This library adds opt-in support for Net::HTTP, Faraday (2.x via faraday-net_http), HTTParty, and documents Excon’s built-in :ssl_proxy_headers for sends.

Why teams use this

  • Geo-targeting at tunnel setup: Send country/session directives on CONNECT.
  • Sticky-session observability: Read proxy-assigned headers like X-ProxyMesh-IP.
  • Works with common Ruby stacks: Useful for scraping and API clients using Net::HTTP-based flows.

Installation

gem install ruby-proxy-headers

Or add to your Gemfile:

gem 'ruby-proxy-headers'

The Net::HTTP patch is pure Ruby. Install faraday, faraday-net_http, httparty, and/or excon when you use those integrations.

Net::HTTP

require 'uri'
require 'openssl'
require 'ruby_proxy_headers/net_http'

RubyProxyHeaders::NetHTTP.patch!

uri = URI('https://api.ipify.org?format=json')
proxy = URI(ENV.fetch('PROXY_URL'))

http = Net::HTTP.new(uri.host, uri.port, proxy.host, proxy.port, proxy.user, proxy.password)
http.use_ssl = true

# Optional: headers to send on CONNECT (e.g. sticky IP)
http.proxy_connect_request_headers = { 'X-ProxyMesh-IP' => '203.0.113.1' }

res = http.request(Net::HTTP::Get.new(uri))
puts res.body
puts http.last_proxy_connect_response_headers['X-ProxyMesh-IP']

Call RubyProxyHeaders::NetHTTP.patch! once before creating connections. You can also read the last CONNECT response headers on the current thread via RubyProxyHeaders.proxy_connect_response_headers.

Faraday

require 'ruby_proxy_headers/faraday'

conn = RubyProxyHeaders::FaradayIntegration.connection(
  proxy: ENV.fetch('PROXY_URL'),
  proxy_connect_headers: { 'X-ProxyMesh-Country' => 'US' } # optional
)
res = conn.get('https://api.ipify.org?format=json')
puts res.headers['X-ProxyMesh-IP']

Uses the registered adapter :ruby_proxy_headers_net_http, which merges proxy CONNECT response headers into Faraday’s response headers.

HTTParty

require 'httparty'
require 'ruby_proxy_headers/httparty'

RubyProxyHeaders::NetHTTP.patch!

proxy = URI(ENV.fetch('PROXY_URL'))
HTTParty.get(
  'https://api.ipify.org?format=json',
  http_proxyaddr: proxy.host,
  http_proxyport: proxy.port,
  http_proxyuser: proxy.user,
  http_proxypass: proxy.password,
  proxy_connect_request_headers: { 'X-ProxyMesh-IP' => '203.0.113.1' }, # optional
  connection_adapter: RubyProxyHeaders::ProxyHeadersConnectionAdapter
)

puts RubyProxyHeaders.proxy_connect_response_headers['X-ProxyMesh-IP']

Excon (send-only; CONNECT response headers)

Excon supports sending extra headers on CONNECT with :ssl_proxy_headers. Reading X-ProxyMesh-IP from the CONNECT response is not exposed on the origin response object — see DEFERRED.md.

require 'ruby_proxy_headers/excon'

RubyProxyHeaders::ExconIntegration.get(
  'https://api.ipify.org?format=json',
  proxy_url: ENV.fetch('PROXY_URL'),
  proxy_connect_headers: { 'X-ProxyMesh-Country' => 'US' } # optional
)

Testing (live proxy)

Same environment variables as python-proxy-headers:

Variable Role
PROXY_URL Proxy URL (required for tests)
TEST_URL Target HTTPS URL (default https://api.ipify.org?format=json)
PROXY_HEADER Header to read from CONNECT response (default X-ProxyMesh-IP)
SEND_PROXY_HEADER Optional header name to send on CONNECT
SEND_PROXY_VALUE Optional value for that header
cd ruby-proxy-headers
bundle install
export PROXY_URL=http://user:pass@proxyhost:port
bundle exec ruby test/test_proxy_headers.rb -v

Related