rack-proxy 0.7.7 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 888784aa8d1d28ae0dc2a1352aa44ba8e639d5cd604043facbb31da3fa1dc759
4
- data.tar.gz: 9ba49effcffcacb930ab08fe2f6a9fd08040b60800b8aa8e5ccc274053f36c4e
3
+ metadata.gz: 40c24e4b559fc7669c3b675184af8d7f0b9ba693d3a7b41e6433274c16429adc
4
+ data.tar.gz: 6f8790c5c391e102275c11225a869e2572d109f2e52e4783cdc5cbbb50733579
5
5
  SHA512:
6
- metadata.gz: 606ed720fb5b8c67cd1fc3058b9644e88fb2e7768d4fce4606ba0332fac24cadca11a36ab50d97cb7ff5767664864b1c1a2cf5108cd58a66fecfb3b93de37517
7
- data.tar.gz: a91cc8541d7af6c390fe1c0faa3c923942a14cce746eebc3d170b95b45aafc5871a04ad1ec9fee6f0c07500534755c794f76d0c14bccdcf5fdaad06e239aeb07
6
+ metadata.gz: 2885f787b2f08c712c99b938218749d7f5d35348588290414f058c446104e10ba80b7f0b439ea60c3fc4daea1a0b0f991c92e853a3a9de4ef238d5f864d85d20
7
+ data.tar.gz: 3f70b5ab5eee94517d10e82f72f517a7e4cd3fec346ba78142ed6d93bd34bb7d175f6e857c037c7b0beffd710889651f15d107920e38da7208bdc132f246f712
data/Gemfile.lock CHANGED
@@ -1,28 +1,31 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rack-proxy (0.7.7)
4
+ rack-proxy (0.8.2)
5
5
  rack
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
10
  power_assert (2.0.3)
11
- rack (3.0.8)
11
+ rack (3.2.6)
12
12
  rack-test (2.1.0)
13
13
  rack (>= 1.3)
14
14
  rake (13.0.6)
15
15
  test-unit (3.6.1)
16
16
  power_assert
17
+ webrick (1.9.2)
17
18
 
18
19
  PLATFORMS
19
20
  arm64-darwin-22
21
+ x86_64-linux
20
22
 
21
23
  DEPENDENCIES
22
24
  rack-proxy!
23
25
  rack-test
24
26
  rake
25
27
  test-unit
28
+ webrick
26
29
 
27
30
  BUNDLED WITH
28
31
  2.4.17
data/README.md CHANGED
@@ -6,7 +6,7 @@ Installation
6
6
  Add the following to your `Gemfile`:
7
7
 
8
8
  ```
9
- gem 'rack-proxy', '~> 0.7.7'
9
+ gem 'rack-proxy', '~> 0.8.0'
10
10
  ```
11
11
 
12
12
  Or install:
@@ -38,10 +38,12 @@ Options can be set when initializing the middleware or overriding a method.
38
38
 
39
39
 
40
40
  * `:streaming` - defaults to `true`, but does not work on all Ruby versions, recommend to set to `false`
41
- * `:ssl_verify_none` - tell `Net::HTTP` to not validate certs
41
+ * `:ssl_verify_none` - tell `Net::HTTP` to skip TLS certificate verification (defaults to verifying — see [Upgrading](#upgrading) for the 0.8 change)
42
+ * `:verify_mode` - explicit `OpenSSL::SSL::VERIFY_*` constant; wins over `ssl_verify_none`
42
43
  * `:ssl_version` - tell `Net::HTTP` to set a specific `ssl_version`
43
44
  * `:backend` - the URI parseable format of host and port of the target proxy backend. If not set it will assume the backend target is the same as the source.
44
45
  * `:read_timeout` - set proxy timeout it defaults to 60 seconds
46
+ * `:logger` - any object responding to `#<<` (e.g. `$stdout`, a `StringIO`, or a Ruby `Logger`). Wired through to `Net::HTTP#set_debug_output` so the full HTTP wire-level conversation is written to the sink. Useful for debugging.
45
47
 
46
48
  To pass in options, when you configure your middleware you can pass them in as an optional hash.
47
49
 
@@ -98,9 +100,6 @@ class TrustingProxy < Rack::Proxy
98
100
 
99
101
  def rewrite_env(env)
100
102
  env["HTTP_HOST"] = "self-signed.badssl.com"
101
-
102
- # We are going to trust the self-signed SSL
103
- env["rack.ssl_verify_none"] = true
104
103
  env
105
104
  end
106
105
 
@@ -116,11 +115,8 @@ class TrustingProxy < Rack::Proxy
116
115
  end
117
116
 
118
117
  end
119
- ```
120
-
121
- The same can be achieved for *all* requests going through the `Rack::Proxy` instance by using
122
118
 
123
- ```ruby
119
+ # Pass ssl_verify_none: true to skip TLS certificate verification.
124
120
  Rack::Proxy.new(ssl_verify_none: true)
125
121
  ```
126
122
 
@@ -327,6 +323,29 @@ class TLSProxy < Rack::Proxy
327
323
  end
328
324
  ```
329
325
 
326
+ Upgrading
327
+ ----
328
+
329
+ ### 0.7.x → 0.8.0
330
+
331
+ **TLS certificate verification is now on by default.** Prior versions silently used `OpenSSL::SSL::VERIFY_NONE` whenever the backend was HTTPS, which disabled certificate checks. 0.8.0 defaults to `VERIFY_PEER` to match Ruby's `Net::HTTP`.
332
+
333
+ If you proxy to a backend with a self-signed or otherwise untrusted certificate, you'll now get an `OpenSSL::SSL::SSLError` unless you opt out explicitly:
334
+
335
+ ```ruby
336
+ Rack::Proxy.new(ssl_verify_none: true) # or
337
+ Rack::Proxy.new(verify_mode: OpenSSL::SSL::VERIFY_NONE)
338
+ ```
339
+
340
+ For internal services with a private CA, prefer setting `cert`/`verify_mode` over disabling verification altogether.
341
+
342
+ A note on header keys (#96)
343
+ ----
344
+
345
+ Per the standard Rack/CGI convention, header names received by your proxy are exposed in the env with underscores (`HTTP_X_CUSTOM_HEADER`), and rack-proxy rewrites them with dashes (`X-Custom-Header`) when forwarding. This conversion is lossy: by the time a request reaches rack-proxy, the upstream web server (nginx, Apache, Caddy, Puma) has already collapsed both `X-Custom-Header` and `X_Custom_Header` into the same env key, and rack-proxy cannot recover the original spelling.
346
+
347
+ If you need underscore-style headers preserved end-to-end, configure your fronting web server (e.g. `underscores_in_headers on;` in nginx, or `HTTPProtocolOptions` in Apache) — rack-proxy is not the right layer to fix this.
348
+
330
349
  WARNING
331
350
  ----
332
351
 
@@ -10,7 +10,7 @@ module Rack
10
10
  304 => true
11
11
  }.freeze
12
12
 
13
- attr_accessor :use_ssl, :verify_mode, :read_timeout, :ssl_version, :cert, :key
13
+ attr_accessor :use_ssl, :verify_mode, :read_timeout, :ssl_version, :cert, :key, :logger
14
14
 
15
15
  def initialize(request, host, port = nil)
16
16
  @request, @host, @port = request, host, port
@@ -61,6 +61,7 @@ module Rack
61
61
  http.ssl_version = ssl_version if ssl_version
62
62
  http.cert = cert if cert
63
63
  http.key = key if key
64
+ http.set_debug_output(logger) if logger
64
65
  http.start
65
66
  end
66
67
  end
data/lib/rack/proxy.rb CHANGED
@@ -5,7 +5,7 @@ module Rack
5
5
 
6
6
  # Subclass and bring your own #rewrite_request and #rewrite_response
7
7
  class Proxy
8
- VERSION = "0.7.7".freeze
8
+ VERSION = "0.8.2".freeze
9
9
 
10
10
  HOP_BY_HOP_HEADERS = {
11
11
  'connection' => true,
@@ -39,7 +39,9 @@ module Rack
39
39
  end
40
40
 
41
41
  def build_header_hash(pairs)
42
- if Rack.const_defined?(:Headers)
42
+ # Pass inherit: false so we only check Rack's own constants — otherwise
43
+ # a top-level ::Headers defined by the host app would falsely match.
44
+ if Rack.const_defined?(:Headers, false)
43
45
  # Rack::Headers is only available from Rack 3 onward
44
46
  Headers.new.tap { |headers| pairs.each { |k, v| headers[k] = v } }
45
47
  else
@@ -69,17 +71,24 @@ module Rack
69
71
  end
70
72
 
71
73
  @streaming = opts.fetch(:streaming, true)
72
- @ssl_verify_none = opts.fetch(:ssl_verify_none, false)
73
74
  @backend = opts[:backend] ? URI(opts[:backend]) : nil
74
75
  @read_timeout = opts.fetch(:read_timeout, 60)
75
76
  @ssl_version = opts[:ssl_version]
76
77
  @cert = opts[:cert]
77
78
  @key = opts[:key]
79
+ # SSL verification: defaults to VERIFY_PEER (Ruby's Net::HTTP default).
80
+ # Pass ssl_verify_none: true to explicitly disable cert verification, or
81
+ # pass verify_mode: <OpenSSL::SSL::VERIFY_*> for full control.
78
82
  @verify_mode = opts[:verify_mode]
83
+ @verify_mode ||= OpenSSL::SSL::VERIFY_NONE if opts[:ssl_verify_none]
79
84
 
80
85
  @username = opts[:username]
81
86
  @password = opts[:password]
82
87
 
88
+ # Optional logger for Net::HTTP debug output. Accepts anything with a #<< method
89
+ # (e.g. $stdout, a StringIO, or a Ruby Logger instance).
90
+ @logger = opts[:logger]
91
+
83
92
  @opts = opts
84
93
  end
85
94
 
@@ -130,33 +139,42 @@ module Rack
130
139
  read_timeout = env.delete('http.read_timeout') || @read_timeout
131
140
 
132
141
  # Create the response
133
- if @streaming
134
- # streaming response (the actual network communication is deferred, a.k.a. streamed)
135
- target_response = HttpStreamingResponse.new(target_request, backend.host, backend.port)
136
- target_response.use_ssl = use_ssl
137
- target_response.read_timeout = read_timeout
138
- target_response.ssl_version = @ssl_version if @ssl_version
139
- target_response.verify_mode = (@verify_mode || OpenSSL::SSL::VERIFY_NONE) if use_ssl
140
- target_response.cert = @cert if @cert
141
- target_response.key = @key if @key
142
- else
143
- http = Net::HTTP.new(backend.host, backend.port)
144
- http.use_ssl = use_ssl if use_ssl
145
- http.read_timeout = read_timeout
146
- http.ssl_version = @ssl_version if @ssl_version
147
- http.verify_mode = (@verify_mode || OpenSSL::SSL::VERIFY_NONE if use_ssl) if use_ssl
148
- http.cert = @cert if @cert
149
- http.key = @key if @key
150
-
151
- target_response = http.start do
152
- http.request(target_request)
142
+ begin
143
+ if @streaming
144
+ # streaming response (the actual network communication is deferred, a.k.a. streamed)
145
+ target_response = HttpStreamingResponse.new(target_request, backend.host, backend.port)
146
+ target_response.use_ssl = use_ssl
147
+ target_response.read_timeout = read_timeout
148
+ target_response.ssl_version = @ssl_version if @ssl_version
149
+ target_response.verify_mode = (@verify_mode || OpenSSL::SSL::VERIFY_PEER) if use_ssl
150
+ target_response.cert = @cert if @cert
151
+ target_response.key = @key if @key
152
+ target_response.logger = @logger if @logger
153
+ else
154
+ http = Net::HTTP.new(backend.host, backend.port)
155
+ http.use_ssl = use_ssl if use_ssl
156
+ http.read_timeout = read_timeout
157
+ http.ssl_version = @ssl_version if @ssl_version
158
+ http.verify_mode = @verify_mode || OpenSSL::SSL::VERIFY_PEER if use_ssl
159
+ http.cert = @cert if @cert
160
+ http.key = @key if @key
161
+ http.set_debug_output(@logger) if @logger
162
+
163
+ target_response = http.start do
164
+ http.request(target_request)
165
+ end
153
166
  end
167
+
168
+ code = target_response.code
169
+ headers = self.class.normalize_headers(target_response.respond_to?(:headers) ? target_response.headers : target_response.to_hash)
170
+ body = target_response.body || []
171
+ body = [body] unless body.respond_to?(:each)
172
+ rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH, Errno::ENETUNREACH, Errno::ETIMEDOUT, Net::OpenTimeout, SocketError
173
+ return [502, {}, []]
154
174
  end
155
175
 
156
- code = target_response.code
157
- headers = self.class.normalize_headers(target_response.respond_to?(:headers) ? target_response.headers : target_response.to_hash)
158
- body = target_response.body || [""]
159
- body = [body] unless body.respond_to?(:each)
176
+ # No entity body for status codes that don't allow one (1xx, 204, 304)
177
+ body = [] if Rack::Utils::STATUS_WITH_NO_ENTITY_BODY[code.to_i]
160
178
 
161
179
  # According to https://tools.ietf.org/html/draft-ietf-httpbis-p1-messaging-14#section-7.1.3.1Acc
162
180
  # should remove hop-by-hop header fields
@@ -2,15 +2,12 @@ class TrustingProxy < Rack::Proxy
2
2
 
3
3
  def rewrite_env(env)
4
4
  env["HTTP_HOST"] = "self-signed.badssl.com"
5
-
6
- # We are going to trust the self-signed SSL
7
- env["rack.ssl_verify_none"] = true
8
5
  env
9
6
  end
10
7
 
11
8
  def rewrite_response(triplet)
12
9
  status, headers, body = triplet
13
-
10
+
14
11
  # if you rewrite env, it appears that content-length isn't calculated correctly
15
12
  # resulting in only partial responses being sent to users
16
13
  # you can remove it or recalculate it here
@@ -21,4 +18,8 @@ class TrustingProxy < Rack::Proxy
21
18
 
22
19
  end
23
20
 
24
- Rails.application.config.middleware.use TrustingProxy, backend: 'https://self-signed.badssl.com', streaming: false
21
+ # Pass ssl_verify_none: true to skip TLS certificate verification.
22
+ Rails.application.config.middleware.use TrustingProxy,
23
+ backend: 'https://self-signed.badssl.com',
24
+ streaming: false,
25
+ ssl_verify_none: true
data/rack-proxy.gemspec CHANGED
@@ -22,4 +22,5 @@ Gem::Specification.new do |s|
22
22
  s.add_dependency("rack")
23
23
  s.add_development_dependency("rack-test")
24
24
  s.add_development_dependency("test-unit")
25
+ s.add_development_dependency("webrick")
25
26
  end
@@ -19,9 +19,8 @@ class HttpStreamingResponseTest < Test::Unit::TestCase
19
19
 
20
20
  assert headers.size.positive?
21
21
 
22
- assert_match %r{text/html; ?charset=utf-8}, headers["content-type"].first.downcase
22
+ assert_match %r{text/html}, headers["content-type"].first.downcase
23
23
  assert_equal headers["content-type"], headers["CoNtEnT-TyPe"]
24
- assert headers["content-length"].first.to_i.positive?
25
24
 
26
25
  # Body
27
26
  chunks = []
@@ -37,7 +36,10 @@ class HttpStreamingResponseTest < Test::Unit::TestCase
37
36
  end
38
37
 
39
38
  def test_to_s
40
- assert_equal @response.headers["Content-Length"].first.to_i, @response.body.to_s.bytesize
39
+ body_string = @response.body.to_s
40
+ assert body_string.bytesize.positive?
41
+ content_length = @response.headers["Content-Length"]
42
+ assert_equal content_length.first.to_i, body_string.bytesize if content_length
41
43
  end
42
44
 
43
45
  def test_to_s_called_twice
@@ -31,9 +31,9 @@ class RackProxyTest < Test::Unit::TestCase
31
31
 
32
32
  def test_http_full_request_headers
33
33
  app(:streaming => false)
34
- app.host = 'www.google.com'
35
- get "/"
36
- assert !Array(last_response['Set-Cookie']).empty?, 'Google always sets a cookie, yo. Where my cookies at?!'
34
+ app.host = 'httpbin.org'
35
+ get "/cookies/set?test=1"
36
+ assert !Array(last_response['Set-Cookie']).empty?, 'httpbin.org/cookies/set should set a cookie'
37
37
  end
38
38
 
39
39
  def test_https_streaming
@@ -44,7 +44,7 @@ class RackProxyTest < Test::Unit::TestCase
44
44
  end
45
45
 
46
46
  def test_https_streaming_tls
47
- app(:ssl_version => :TLSv1).host = 'www.apple.com'
47
+ app(:ssl_version => :TLSv1_2).host = 'www.apple.com'
48
48
  get 'https://example.com'
49
49
  assert last_response.ok?
50
50
  assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)
@@ -58,7 +58,7 @@ class RackProxyTest < Test::Unit::TestCase
58
58
  end
59
59
 
60
60
  def test_https_full_request_tls
61
- app({:streaming => false, :ssl_version => :TLSv1}).host = 'www.apple.com'
61
+ app({:streaming => false, :ssl_version => :TLSv1_2}).host = 'www.apple.com'
62
62
  get 'https://example.com'
63
63
  assert last_response.ok?
64
64
  assert_match(/(itunes|iphone|ipod|mac|ipad)/, last_response.body)
@@ -69,7 +69,8 @@ class RackProxyTest < Test::Unit::TestCase
69
69
  headers = { 'header_array' => ['first_entry'], 'header_non_array' => :entry }
70
70
 
71
71
  normalized_headers = proxy_class.send(:normalize_headers, headers)
72
- assert normalized_headers.instance_of?(Rack::Utils::HeaderHash)
72
+ expected_class = Rack.const_defined?(:Headers) ? Rack::Headers : Rack::Utils::HeaderHash
73
+ assert normalized_headers.instance_of?(expected_class)
73
74
  assert normalized_headers['header_array'] == 'first_entry'
74
75
  assert normalized_headers['header_non_array'] == :entry
75
76
  end
@@ -124,4 +125,197 @@ class RackProxyTest < Test::Unit::TestCase
124
125
  get 'https://example.com/oauth2/token/info?access_token=123'
125
126
  assert !last_response.headers.key?('transfer-encoding')
126
127
  end
128
+
129
+ # Issue #58: connection errors should return 502, not raise.
130
+ def test_connection_refused_returns_502
131
+ # Bind a socket to find a free port, then close it so connection is refused.
132
+ server = TCPServer.new('127.0.0.1', 0)
133
+ closed_port = server.addr[1]
134
+ server.close
135
+
136
+ app({:streaming => false}).host = "127.0.0.1:#{closed_port}"
137
+ get '/'
138
+ assert_equal 502, last_response.status
139
+ assert_equal '', last_response.body
140
+ end
141
+
142
+ def test_connection_refused_returns_502_streaming
143
+ server = TCPServer.new('127.0.0.1', 0)
144
+ closed_port = server.addr[1]
145
+ server.close
146
+
147
+ app({:streaming => true}).host = "127.0.0.1:#{closed_port}"
148
+ get '/'
149
+ assert_equal 502, last_response.status
150
+ assert_equal '', last_response.body
151
+ end
152
+
153
+ def test_unknown_host_returns_502
154
+ app({:streaming => false}).host = 'no-such-host.invalid'
155
+ get '/'
156
+ assert_equal 502, last_response.status
157
+ end
158
+
159
+ # Issues #122/#123: body should be [] for empty responses and for status
160
+ # codes that don't allow an entity body (1xx, 204, 304).
161
+ def test_no_entity_body_for_204
162
+ with_webrick_proxy(streaming: false) do |port, proxy|
163
+ proxy.host = "127.0.0.1:#{port}"
164
+ get '/no-content'
165
+ assert_equal 204, last_response.status
166
+ assert_equal '', last_response.body
167
+ end
168
+ end
169
+
170
+ def test_no_entity_body_for_304
171
+ with_webrick_proxy(streaming: false) do |port, proxy|
172
+ proxy.host = "127.0.0.1:#{port}"
173
+ get '/not-modified'
174
+ assert_equal 304, last_response.status
175
+ assert_equal '', last_response.body
176
+ end
177
+ end
178
+
179
+ def test_empty_body_is_not_array_with_empty_string
180
+ with_webrick_proxy(streaming: false) do |port, proxy|
181
+ proxy.host = "127.0.0.1:#{port}"
182
+ get '/empty'
183
+ assert_equal 200, last_response.status
184
+ assert_equal '', last_response.body
185
+ end
186
+ end
187
+
188
+ # Issue #65: header values must be strings, not single-element arrays,
189
+ # for both streaming and non-streaming paths.
190
+ def test_header_values_are_strings_streaming
191
+ assert_no_array_header_values(streaming: true)
192
+ end
193
+
194
+ def test_header_values_are_strings_non_streaming
195
+ assert_no_array_header_values(streaming: false)
196
+ end
197
+
198
+ # Issue #113: SSL cert verification must default to VERIFY_PEER (Ruby's
199
+ # Net::HTTP default), not VERIFY_NONE.
200
+ def test_ssl_default_is_verify_peer
201
+ proxy = Rack::Proxy.new
202
+ assert_nil proxy.instance_variable_get(:@verify_mode),
203
+ "@verify_mode should be unset by default so VERIFY_PEER applies at request time"
204
+ end
205
+
206
+ def test_ssl_verify_none_opt_in
207
+ proxy = Rack::Proxy.new(ssl_verify_none: true)
208
+ assert_equal OpenSSL::SSL::VERIFY_NONE, proxy.instance_variable_get(:@verify_mode)
209
+ end
210
+
211
+ def test_explicit_verify_mode_wins_over_ssl_verify_none
212
+ proxy = Rack::Proxy.new(ssl_verify_none: true, verify_mode: OpenSSL::SSL::VERIFY_PEER)
213
+ assert_equal OpenSSL::SSL::VERIFY_PEER, proxy.instance_variable_get(:@verify_mode)
214
+ end
215
+
216
+ def test_https_default_rejects_invalid_certificate
217
+ # self-signed cert on a public test host should be rejected with the new default
218
+ app({:streaming => false}).host = 'self-signed.badssl.com'
219
+ error = assert_raise(OpenSSL::SSL::SSLError) { get 'https://example.com/' }
220
+ assert_match(/certificate verify failed/, error.message)
221
+ end
222
+
223
+ def test_https_with_ssl_verify_none_accepts_invalid_certificate
224
+ app({:streaming => false, :ssl_verify_none => true}).host = 'self-signed.badssl.com'
225
+ get 'https://example.com/'
226
+ assert last_response.ok?
227
+ end
228
+
229
+ # Issue #80: a :logger option should pipe Net::HTTP debug output to the
230
+ # given sink (anything responding to #<<). We use a StringIO to capture it.
231
+ def test_logger_captures_request_in_non_streaming
232
+ sink = StringIO.new
233
+ with_webrick_proxy(streaming: false, logger: sink) do |port, proxy|
234
+ proxy.host = "127.0.0.1:#{port}"
235
+ get '/empty'
236
+ assert last_response.ok?
237
+ end
238
+ assert_match(/GET \/empty/, sink.string,
239
+ "expected debug output to include request line, got: #{sink.string.inspect}")
240
+ end
241
+
242
+ def test_logger_captures_request_in_streaming
243
+ sink = StringIO.new
244
+ with_webrick_proxy(streaming: true, logger: sink) do |port, proxy|
245
+ proxy.host = "127.0.0.1:#{port}"
246
+ get '/empty'
247
+ assert last_response.ok?
248
+ end
249
+ assert_match(/GET \/empty/, sink.string,
250
+ "expected debug output to include request line, got: #{sink.string.inspect}")
251
+ end
252
+
253
+ # Regression: build_header_hash must not match a top-level ::Headers
254
+ # constant defined by the host app (would happen with inherit: true).
255
+ def test_build_header_hash_ignores_toplevel_headers_constant
256
+ Object.send(:remove_const, :Headers) if Object.const_defined?(:Headers, false)
257
+ Object.const_set(:Headers, Class.new)
258
+ begin
259
+ result = Rack::Proxy.send(:build_header_hash, [['X-Test', 'value']])
260
+ # On Rack 3+ we get Rack::Headers; on Rack 2 we get Rack::Utils::HeaderHash.
261
+ # In neither case should we get the bogus top-level ::Headers.
262
+ assert_not_equal ::Headers, result.class,
263
+ "build_header_hash leaked into top-level ::Headers"
264
+ ensure
265
+ Object.send(:remove_const, :Headers)
266
+ end
267
+ end
268
+
269
+ def test_no_logger_means_no_debug_output
270
+ # Without a :logger option, Net::HTTP's set_debug_output should never be
271
+ # called. We can't directly assert that, but we can confirm requests still
272
+ # work when no logger is configured (covered by every other test).
273
+ with_webrick_proxy(streaming: false) do |port, proxy|
274
+ proxy.host = "127.0.0.1:#{port}"
275
+ get '/empty'
276
+ assert last_response.ok?
277
+ end
278
+ end
279
+
280
+ private
281
+
282
+ def assert_no_array_header_values(streaming:)
283
+ with_webrick_proxy(streaming: streaming) do |port, proxy|
284
+ proxy.host = "127.0.0.1:#{port}"
285
+ get '/echo-headers'
286
+ array_valued = last_response.headers.select { |_, v| v.is_a?(Array) }
287
+ assert_empty array_valued,
288
+ "expected no Array-valued headers (#65), got: #{array_valued.inspect}"
289
+ assert_equal 'value-here', last_response['x-custom']
290
+ end
291
+ end
292
+
293
+
294
+ # Spin up a tiny WEBrick server with fixed routes so we can exercise the
295
+ # proxy against real Net::HTTP requests without depending on a remote host.
296
+ def with_webrick_proxy(**proxy_opts)
297
+ require 'webrick'
298
+ server = WEBrick::HTTPServer.new(
299
+ Port: 0,
300
+ BindAddress: '127.0.0.1',
301
+ Logger: WEBrick::Log.new(File::NULL),
302
+ AccessLog: []
303
+ )
304
+ server.mount_proc('/no-content') { |_req, res| res.status = 204 }
305
+ server.mount_proc('/not-modified') { |_req, res| res.status = 304 }
306
+ server.mount_proc('/empty') { |_req, res| res.body = '' }
307
+ server.mount_proc('/echo-headers') do |_req, res|
308
+ res['x-custom'] = 'value-here'
309
+ res.body = 'ok'
310
+ end
311
+ Thread.new { server.start }
312
+ port = server.config[:Port]
313
+
314
+ proxy = HostProxy.new(**proxy_opts)
315
+ @app = proxy
316
+ yield port, proxy
317
+ ensure
318
+ server&.shutdown
319
+ @app = nil
320
+ end
127
321
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-proxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.7
4
+ version: 0.8.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jacek Becela
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2023-09-01 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: rack
@@ -52,6 +51,20 @@ dependencies:
52
51
  - - ">="
53
52
  - !ruby/object:Gem::Version
54
53
  version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: webrick
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
55
68
  description: A Rack app that provides request/response rewriting proxy capabilities
56
69
  with streaming.
57
70
  email:
@@ -85,7 +98,6 @@ homepage: https://github.com/ncr/rack-proxy
85
98
  licenses:
86
99
  - MIT
87
100
  metadata: {}
88
- post_install_message:
89
101
  rdoc_options: []
90
102
  require_paths:
91
103
  - lib
@@ -100,8 +112,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
100
112
  - !ruby/object:Gem::Version
101
113
  version: '0'
102
114
  requirements: []
103
- rubygems_version: 3.2.3
104
- signing_key:
115
+ rubygems_version: 3.6.9
105
116
  specification_version: 4
106
117
  summary: A request/response rewriting HTTP proxy. A Rack app.
107
118
  test_files: