|
1 | | -# Implementation Priority for ruby-proxy-headers |
| 1 | +# ruby-proxy-headers — implementation plan |
2 | 2 |
|
3 | | -This document outlines the implementation plan for the ruby-proxy-headers gem. |
| 3 | +Prioritized roadmap for extension modules, aligned with [javascript-proxy-headers](https://github.com/proxymesh/javascript-proxy-headers) and [python-proxy-headers](https://github.com/proxymesh/python-proxy-headers). |
4 | 4 |
|
5 | | -## Architecture Overview |
| 5 | +## Architecture |
6 | 6 |
|
7 | 7 | ``` |
8 | | -┌─────────────────────────────────────────────────────────────────┐ |
9 | | -│ ruby-proxy-headers gem │ |
10 | | -├─────────────────────────────────────────────────────────────────┤ |
11 | | -│ ┌─────────────────────────────────────────────────────────┐ │ |
12 | | -│ │ Core: ProxyHeadersConnection │ │ |
13 | | -│ │ - Direct socket handling for CONNECT with headers │ │ |
14 | | -│ │ - TLS upgrade after CONNECT │ │ |
15 | | -│ │ - Response header capture │ │ |
16 | | -│ └─────────────────────────────────────────────────────────┘ │ |
17 | | -│ ▲ │ |
18 | | -│ │ │ |
19 | | -│ ┌─────────────────────────────────────────────────────────┐ │ |
20 | | -│ │ Net::HTTP Extension │ │ |
21 | | -│ │ - Prepend module to override #connect │ │ |
22 | | -│ │ - Store proxy headers on Net::HTTPResponse │ │ |
23 | | -│ └─────────────────────────────────────────────────────────┘ │ |
24 | | -│ ▲ │ |
25 | | -│ ┌───────────────┼───────────────┐ │ |
26 | | -│ │ │ │ │ |
27 | | -│ ┌───────────┴───┐ ┌───────┴───────┐ ┌───┴───────────┐ │ |
28 | | -│ │ Faraday │ │ HTTParty │ │ RestClient │ │ |
29 | | -│ │ Middleware │ │ Extension │ │ Extension │ │ |
30 | | -│ └───────────────┘ └───────────────┘ └───────────────┘ │ |
31 | | -│ │ |
32 | | -│ ┌─────────────────────────────────────────────────────────┐ │ |
33 | | -│ │ Typhoeus Extension (via Ethon FFI) │ │ |
34 | | -│ │ - Direct libcurl CURLOPT_PROXYHEADER support │ │ |
35 | | -│ └─────────────────────────────────────────────────────────┘ │ |
36 | | -└─────────────────────────────────────────────────────────────────┘ |
| 8 | +┌─────────────────────────────────────────────────────────────┐ |
| 9 | +│ ruby-proxy-headers │ |
| 10 | +├─────────────────────────────────────────────────────────────┤ |
| 11 | +│ Library wrappers (Faraday, HTTParty, Mechanize, …) │ |
| 12 | +│ │ │ |
| 13 | +│ ▼ │ |
| 14 | +│ Net::HTTP patch (Phase 1) — CONNECT send + capture │ |
| 15 | +│ │ │ |
| 16 | +│ ├──► Typhoeus / Ethon — libcurl options (Phase 4) │ |
| 17 | +│ └──► Excon — custom tunnel (Phase 5) │ |
| 18 | +└─────────────────────────────────────────────────────────────┘ |
37 | 19 | ``` |
38 | 20 |
|
39 | | -## Phase 1: Core Implementation |
| 21 | +## Phase 1 — Net::HTTP core (**done in v0.1**) |
40 | 22 |
|
41 | | -### 1.1 ProxyHeadersConnection (lib/ruby_proxy_headers/connection.rb) |
42 | | -- Pure Ruby socket handling |
43 | | -- Build CONNECT request with custom headers |
44 | | -- Parse CONNECT response and capture headers |
45 | | -- TLS upgrade handling |
46 | | -- Thread-safe header storage |
| 23 | +**Goal:** `RubyProxyHeaders::NetHTTP.patch!` extends `Net::HTTP#connect` for `use_ssl? && proxy?` to: |
47 | 24 |
|
48 | | -### 1.2 Net::HTTP Extension (lib/ruby_proxy_headers/net_http.rb) |
49 | | -- Prepend module to `Net::HTTP` |
50 | | -- Override `connect` method to use ProxyHeadersConnection |
51 | | -- Add `proxy_response_headers` accessor to responses |
52 | | -- Configuration via class-level or instance-level options |
| 25 | +- Send optional `proxy_connect_request_headers` on `CONNECT` |
| 26 | +- Store `last_proxy_connect_response_headers` from the `CONNECT` response |
53 | 27 |
|
54 | | -## Phase 2: Popular Library Integration |
| 28 | +**Files:** |
55 | 29 |
|
56 | | -### 2.1 Faraday Middleware (lib/ruby_proxy_headers/faraday.rb) |
57 | | -- Register as Faraday middleware |
58 | | -- Wrap connection with proxy header support |
59 | | -- Store proxy headers in `env[:proxy_response_headers]` |
60 | | -- Access via `response.env[:proxy_response_headers]` |
| 30 | +- `lib/ruby_proxy_headers/net_http.rb` |
61 | 31 |
|
62 | | -### 2.2 HTTParty Extension (lib/ruby_proxy_headers/httparty.rb) |
63 | | -- Module that can be included in HTTParty classes |
64 | | -- Add `proxy_headers` option to requests |
65 | | -- Store proxy response headers on response object |
66 | | -- Works with existing HTTParty patterns |
| 32 | +**Tests:** |
67 | 33 |
|
68 | | -### 2.3 HTTP.rb Extension (lib/ruby_proxy_headers/http_gem.rb) |
69 | | -- Extend HTTP::Client or use feature injection |
70 | | -- Add `.with_proxy_headers(headers)` chainable method |
71 | | -- Access via `response.proxy_headers` |
| 34 | +- `test/test_proxy_headers.rb` module `net_http` |
72 | 35 |
|
73 | | -## Phase 3: Additional Libraries |
| 36 | +**Success criteria:** |
74 | 37 |
|
75 | | -### 3.1 Typhoeus/Ethon Extension (lib/ruby_proxy_headers/typhoeus.rb) |
76 | | -- Extend Ethon::Easy to expose `CURLOPT_PROXYHEADER` |
77 | | -- Use `CURLOPT_HEADERFUNCTION` for response capture |
78 | | -- Leverage native libcurl support for best performance |
| 38 | +- Live test against `PROXY_URL` with `X-ProxyMesh-IP` visible in `last_proxy_connect_response_headers` |
79 | 39 |
|
80 | | -### 3.2 Excon Extension (lib/ruby_proxy_headers/excon.rb) |
81 | | -- Middleware/interceptor for Excon |
82 | | -- Wrap connection handling |
83 | | -- Store headers in connection data |
| 40 | +--- |
84 | 41 |
|
85 | | -### 3.3 RestClient Extension (lib/ruby_proxy_headers/rest_client.rb) |
86 | | -- Leverages Net::HTTP extension |
87 | | -- Add accessor for proxy headers on response |
| 42 | +## Phase 2 — Faraday |
88 | 43 |
|
89 | | -## File Structure |
| 44 | +**Goal:** Ergonomic API: `proxy_headers: { ... }` on the connection or per-request, backed by patched `Net::HTTP`. |
90 | 45 |
|
91 | | -``` |
92 | | -ruby-proxy-headers/ |
93 | | -├── lib/ |
94 | | -│ ├── ruby_proxy_headers.rb # Main entry point |
95 | | -│ ├── ruby_proxy_headers/ |
96 | | -│ │ ├── version.rb # Gem version |
97 | | -│ │ ├── connection.rb # Core proxy connection handler |
98 | | -│ │ ├── net_http.rb # Net::HTTP extension |
99 | | -│ │ ├── faraday.rb # Faraday middleware |
100 | | -│ │ ├── httparty.rb # HTTParty extension |
101 | | -│ │ ├── http_gem.rb # HTTP.rb extension |
102 | | -│ │ ├── typhoeus.rb # Typhoeus/Ethon extension |
103 | | -│ │ ├── excon.rb # Excon extension |
104 | | -│ │ └── rest_client.rb # RestClient extension |
105 | | -├── spec/ |
106 | | -│ ├── spec_helper.rb |
107 | | -│ ├── connection_spec.rb |
108 | | -│ ├── net_http_spec.rb |
109 | | -│ ├── faraday_spec.rb |
110 | | -│ ├── httparty_spec.rb |
111 | | -│ └── ... |
112 | | -├── examples/ |
113 | | -│ ├── net_http_example.rb |
114 | | -│ ├── faraday_example.rb |
115 | | -│ └── ... |
116 | | -├── ruby_proxy_headers.gemspec |
117 | | -├── Gemfile |
118 | | -├── Rakefile |
119 | | -├── README.md |
120 | | -├── LICENSE |
121 | | -├── CHANGELOG.md |
122 | | -└── docs/ # ReadTheDocs documentation |
123 | | - ├── index.md |
124 | | - ├── getting-started.md |
125 | | - ├── faraday.md |
126 | | - └── ... |
127 | | -``` |
| 46 | +**Approach:** |
128 | 47 |
|
129 | | -## Technical Challenges |
130 | | - |
131 | | -### 1. Socket Interception |
132 | | -Ruby's Net::HTTP creates sockets internally. We need to: |
133 | | -- Intercept before TLS upgrade |
134 | | -- Inject CONNECT request with custom headers |
135 | | -- Parse CONNECT response |
136 | | -- Continue with normal TLS handshake |
137 | | - |
138 | | -### 2. Thread Safety |
139 | | -Multiple concurrent requests may share connection pools: |
140 | | -- Use thread-local storage for proxy response headers |
141 | | -- Or attach headers to response object directly |
142 | | - |
143 | | -### 3. Connection Pooling |
144 | | -Libraries like Faraday may reuse connections: |
145 | | -- Ensure headers are reset per-request |
146 | | -- Don't interfere with keep-alive |
147 | | - |
148 | | -### 4. libcurl Integration (Typhoeus) |
149 | | -- Ethon uses FFI, may need to define additional functions |
150 | | -- `CURLOPT_PROXYHEADER` requires curl >= 7.37.0 |
151 | | -- Need to handle version detection |
152 | | - |
153 | | -## API Design |
154 | | - |
155 | | -### Basic Usage |
156 | | - |
157 | | -```ruby |
158 | | -require 'ruby_proxy_headers' |
159 | | - |
160 | | -# Net::HTTP |
161 | | -response = RubyProxyHeaders::NetHTTP.get( |
162 | | - 'https://example.com', |
163 | | - proxy: 'http://user:pass@proxy:8080', |
164 | | - proxy_headers: { 'X-ProxyMesh-Country' => 'US' } |
165 | | -) |
166 | | -puts response.proxy_response_headers['X-ProxyMesh-IP'] |
167 | | - |
168 | | -# Faraday |
169 | | -conn = Faraday.new(url: 'https://example.com') do |f| |
170 | | - f.use RubyProxyHeaders::Faraday::Middleware, |
171 | | - proxy_headers: { 'X-ProxyMesh-Country' => 'US' } |
172 | | - f.proxy = 'http://user:pass@proxy:8080' |
173 | | -end |
174 | | -response = conn.get('/') |
175 | | -puts response.env[:proxy_response_headers]['X-ProxyMesh-IP'] |
176 | | - |
177 | | -# HTTParty |
178 | | -class ProxyClient |
179 | | - include HTTParty |
180 | | - include RubyProxyHeaders::HTTParty |
181 | | - |
182 | | - http_proxy 'proxy', 8080, 'user', 'pass' |
183 | | - proxy_headers 'X-ProxyMesh-Country' => 'US' |
184 | | -end |
185 | | -response = ProxyClient.get('https://example.com') |
186 | | -puts response.proxy_response_headers['X-ProxyMesh-IP'] |
187 | | -``` |
| 48 | +- `Faraday.new(...) { |f| f.adapter :net_http }` and ensure the adapter’s `Net::HTTP` instance receives `proxy_connect_request_headers` |
| 49 | +- Or `Faraday::Connection` subclass / middleware that sets headers on the underlying `Net::HTTP` before `connect` |
| 50 | + |
| 51 | +**Files (planned):** |
| 52 | + |
| 53 | +- `lib/ruby_proxy_headers/faraday.rb` |
| 54 | + |
| 55 | +--- |
| 56 | + |
| 57 | +## Phase 3 — HTTParty |
| 58 | + |
| 59 | +**Goal:** Document + optional helper to set `proxy_connect_request_headers` on the internal `Net::HTTP` (or class-level hooks). |
| 60 | + |
| 61 | +**Files (planned):** |
| 62 | + |
| 63 | +- `lib/ruby_proxy_headers/httparty.rb` (thin wrapper or documentation module) |
| 64 | + |
| 65 | +--- |
| 66 | + |
| 67 | +## Phase 4 — Typhoeus / Ethon |
| 68 | + |
| 69 | +**Goal:** Map Ruby header hash to libcurl proxy header options; capture CONNECT-related output if feasible. |
| 70 | + |
| 71 | +**Files (planned):** |
| 72 | + |
| 73 | +- `lib/ruby_proxy_headers/typhoeus.rb` or `ethon.rb` |
| 74 | + |
| 75 | +**Risk:** libcurl version differences; may need feature detection. |
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +## Phase 5 — Excon |
| 80 | + |
| 81 | +**Goal:** Custom CONNECT path or middleware mirroring the Node `ProxyHeadersAgent` behavior. |
| 82 | + |
| 83 | +**Files (planned):** |
| 84 | + |
| 85 | +- `lib/ruby_proxy_headers/excon.rb` |
| 86 | + |
| 87 | +--- |
| 88 | + |
| 89 | +## Phase 6 — Mechanize |
| 90 | + |
| 91 | +**Goal:** Ensure Mechanize sessions use patched `Net::HTTP` behavior and document how to read `last_proxy_connect_response_headers` from the right object. |
| 92 | + |
| 93 | +**Files (planned):** |
| 94 | + |
| 95 | +- `lib/ruby_proxy_headers/mechanize.rb` (wrapper or patches) |
| 96 | + |
| 97 | +--- |
| 98 | + |
| 99 | +## Testing |
188 | 100 |
|
189 | | -## Dependencies |
| 101 | +All phases should plug into `test/test_proxy_headers.rb` with the same env vars as Python/JS: |
190 | 102 |
|
191 | | -### Required |
192 | | -- Ruby >= 2.7 (for pattern matching, numbered block params) |
193 | | -- No external gems for core functionality |
| 103 | +- `PROXY_URL`, `PROXY_HEADER`, `SEND_PROXY_HEADER`, `SEND_PROXY_VALUE`, `TEST_URL` |
194 | 104 |
|
195 | | -### Optional (for specific integrations) |
196 | | -- faraday (>= 1.0) for Faraday middleware |
197 | | -- httparty (>= 0.18) for HTTParty extension |
198 | | -- http (>= 5.0) for HTTP.rb extension |
199 | | -- typhoeus (>= 1.4) for Typhoeus extension |
200 | | -- excon (>= 0.80) for Excon extension |
| 105 | +--- |
201 | 106 |
|
202 | | -## Testing Strategy |
| 107 | +## Success criteria (project-wide) |
203 | 108 |
|
204 | | -### Unit Tests |
205 | | -- Mock socket connections |
206 | | -- Test CONNECT request building |
207 | | -- Test response parsing |
| 109 | +1. At least one production-quality path for **Net::HTTP** (done). |
| 110 | +2. Faraday + HTTParty documented with working examples. |
| 111 | +3. Optional: Typhoeus, Excon, Mechanize where demand and maintenance cost align. |
208 | 112 |
|
209 | | -### Integration Tests |
210 | | -- Use actual proxy (local or test service) |
211 | | -- Verify headers sent and received |
212 | | -- Test with each supported library |
| 113 | +--- |
213 | 114 |
|
214 | | -### Compatibility Tests |
215 | | -- Test with multiple Ruby versions (2.7, 3.0, 3.1, 3.2, 3.3) |
216 | | -- Test with multiple versions of each library |
| 115 | +*Plan created: March 2026* |
0 commit comments