rack-reverse-proxy 0.9.1 → 0.10.0
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 +4 -4
- data/.document +5 -0
- data/.gitignore +16 -0
- data/.rspec +1 -0
- data/.rubocop.yml +21 -0
- data/.travis.yml +19 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +20 -0
- data/README.md +30 -4
- data/Rakefile +10 -0
- data/lib/rack/reverse_proxy.rb +3 -128
- data/lib/rack_reverse_proxy.rb +8 -0
- data/lib/rack_reverse_proxy/errors.rb +36 -0
- data/lib/rack_reverse_proxy/middleware.rb +40 -0
- data/lib/rack_reverse_proxy/response_builder.rb +60 -0
- data/lib/rack_reverse_proxy/roundtrip.rb +263 -0
- data/lib/rack_reverse_proxy/rule.rb +182 -0
- data/lib/rack_reverse_proxy/version.rb +4 -0
- data/rack-reverse-proxy.gemspec +42 -0
- data/script/rubocop +5 -0
- data/spec/rack/reverse_proxy_spec.rb +586 -0
- data/spec/rack_reverse_proxy/response_builder_spec.rb +37 -0
- data/spec/spec_helper.rb +106 -0
- data/spec/support/http_streaming_response_patch.rb +32 -0
- metadata +58 -42
- data/lib/rack/exception.rb +0 -31
- data/lib/rack/reverse_proxy/http_streaming_response.rb +0 -7
- data/lib/rack/reverse_proxy_matcher.rb +0 -53
@@ -0,0 +1,42 @@
|
|
1
|
+
lib = File.expand_path("../lib", __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require "rack_reverse_proxy/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "rack-reverse-proxy"
|
7
|
+
spec.version = RackReverseProxy::VERSION
|
8
|
+
|
9
|
+
spec.authors = [
|
10
|
+
"Jon Swope",
|
11
|
+
"Ian Ehlert",
|
12
|
+
"Roman Ernst",
|
13
|
+
"Oleksii Fedorov"
|
14
|
+
]
|
15
|
+
|
16
|
+
spec.email = [
|
17
|
+
"jaswope@gmail.com",
|
18
|
+
"ehlertij@gmail.com",
|
19
|
+
"rernst@farbenmeer.net",
|
20
|
+
"waterlink000@gmail.com"
|
21
|
+
]
|
22
|
+
|
23
|
+
spec.summary = "A Simple Reverse Proxy for Rack"
|
24
|
+
spec.description = <<eos
|
25
|
+
A Rack based reverse proxy for basic needs.
|
26
|
+
Useful for testing or in cases where webserver configuration is unavailable.
|
27
|
+
eos
|
28
|
+
|
29
|
+
spec.homepage = "https://github.com/waterlink/rack-reverse-proxy"
|
30
|
+
spec.license = "MIT"
|
31
|
+
|
32
|
+
spec.files = `git ls-files -z`.split("\x0")
|
33
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
34
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
35
|
+
spec.require_paths = ["lib"]
|
36
|
+
|
37
|
+
spec.add_dependency "rack", ">= 1.0.0"
|
38
|
+
spec.add_dependency "rack-proxy", "~> 0.5", ">= 0.5.14"
|
39
|
+
|
40
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
41
|
+
spec.add_development_dependency "rake", "~> 10.3"
|
42
|
+
end
|
data/script/rubocop
ADDED
@@ -0,0 +1,586 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
RSpec.describe Rack::ReverseProxy do
|
4
|
+
include Rack::Test::Methods
|
5
|
+
|
6
|
+
def app
|
7
|
+
Rack::ReverseProxy.new
|
8
|
+
end
|
9
|
+
|
10
|
+
def dummy_app
|
11
|
+
lambda { |_| [200, {}, ["Dummy App"]] }
|
12
|
+
end
|
13
|
+
|
14
|
+
let(:http_streaming_response) do
|
15
|
+
double(
|
16
|
+
"Rack::HttpStreamingResponse",
|
17
|
+
:use_ssl= => nil,
|
18
|
+
:verify_mode= => nil,
|
19
|
+
:headers => {},
|
20
|
+
:status => 200,
|
21
|
+
:body => "OK"
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "as middleware" do
|
26
|
+
def app
|
27
|
+
Rack::ReverseProxy.new(dummy_app) do
|
28
|
+
reverse_proxy "/test", "http://example.com/", :preserve_host => true
|
29
|
+
reverse_proxy "/2test", lambda { |_| "http://example.com/" }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "forwards requests to the calling app when the path is not matched" do
|
34
|
+
get "/"
|
35
|
+
expect(last_response.body).to eq("Dummy App")
|
36
|
+
expect(last_response).to be_ok
|
37
|
+
end
|
38
|
+
|
39
|
+
it "proxies requests when a pattern is matched" do
|
40
|
+
stub_request(:get, "http://example.com/test").to_return(:body => "Proxied App")
|
41
|
+
get "/test"
|
42
|
+
expect(last_response.body).to eq("Proxied App")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "produces a response header of type HeaderHash" do
|
46
|
+
stub_request(:get, "http://example.com/test")
|
47
|
+
get "/test"
|
48
|
+
expect(last_response.headers).to be_an_instance_of(Rack::Utils::HeaderHash)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "parses the headers as a Hash with values of type String" do
|
52
|
+
stub_request(:get, "http://example.com/test").to_return(
|
53
|
+
:headers => { "cache-control" => "max-age=300, public" }
|
54
|
+
)
|
55
|
+
get "/test"
|
56
|
+
expect(last_response.headers["cache-control"]).to be_an_instance_of(String)
|
57
|
+
expect(last_response.headers["cache-control"]).to eq("max-age=300, public")
|
58
|
+
end
|
59
|
+
|
60
|
+
it "proxies requests to a lambda url when a pattern is matched" do
|
61
|
+
stub_request(:get, "http://example.com/2test").to_return(:body => "Proxied App2")
|
62
|
+
get "/2test"
|
63
|
+
expect(last_response.body).to eq("Proxied App2")
|
64
|
+
end
|
65
|
+
|
66
|
+
it "sets the Host header w/o default port" do
|
67
|
+
stub_request(:any, "example.com/test/stuff")
|
68
|
+
get "/test/stuff"
|
69
|
+
expect(
|
70
|
+
a_request(:get, "http://example.com/test/stuff").with(
|
71
|
+
:headers => { "Host" => "example.com" }
|
72
|
+
)
|
73
|
+
).to have_been_made
|
74
|
+
end
|
75
|
+
|
76
|
+
it "sets the X-Forwarded-Host header to the proxying host by default" do
|
77
|
+
stub_request(:any, "example.com/test/stuff")
|
78
|
+
get "/test/stuff"
|
79
|
+
expect(
|
80
|
+
a_request(:get, "http://example.com/test/stuff").with(
|
81
|
+
:headers => { "X-Forwarded-Host" => "example.org" }
|
82
|
+
)
|
83
|
+
).to have_been_made
|
84
|
+
end
|
85
|
+
|
86
|
+
it "does not produce headers with a Status key" do
|
87
|
+
stub_request(:get, "http://example.com/2test").to_return(
|
88
|
+
:status => 301, :headers => { :status => "301 Moved Permanently" }
|
89
|
+
)
|
90
|
+
|
91
|
+
get "/2test"
|
92
|
+
|
93
|
+
headers = last_response.headers.to_hash
|
94
|
+
expect(headers["Status"]).to be_nil
|
95
|
+
end
|
96
|
+
|
97
|
+
it "formats the headers correctly to avoid duplicates" do
|
98
|
+
stub_request(:get, "http://example.com/2test").to_return(
|
99
|
+
:headers => { :date => "Wed, 22 Jul 2015 11:27:21 GMT" }
|
100
|
+
)
|
101
|
+
|
102
|
+
get "/2test"
|
103
|
+
|
104
|
+
headers = last_response.headers.to_hash
|
105
|
+
expect(headers["Date"]).to eq("Wed, 22 Jul 2015 11:27:21 GMT")
|
106
|
+
expect(headers["date"]).to be_nil
|
107
|
+
end
|
108
|
+
|
109
|
+
it "formats the headers with dashes correctly" do
|
110
|
+
stub_request(:get, "http://example.com/2test").to_return(
|
111
|
+
:status => 301,
|
112
|
+
:headers => { :status => "301 Moved Permanently", :"x-additional-info" => "something" }
|
113
|
+
)
|
114
|
+
|
115
|
+
get "/2test"
|
116
|
+
|
117
|
+
headers = last_response.headers.to_hash
|
118
|
+
expect(headers["X-Additional-Info"]).to eq("something")
|
119
|
+
expect(headers["x-additional-info"]).to be_nil
|
120
|
+
end
|
121
|
+
|
122
|
+
it "the response header includes content-length" do
|
123
|
+
body = "this is the test body"
|
124
|
+
stub_request(:any, "example.com/test/stuff").to_return(
|
125
|
+
:body => body, :headers => { "Content-Length" => "10" }
|
126
|
+
)
|
127
|
+
get "/test/stuff"
|
128
|
+
expect(last_response.headers["Content-Length"]).to eq(body.length.to_s)
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "with non-default port" do
|
132
|
+
def app
|
133
|
+
Rack::ReverseProxy.new(dummy_app) do
|
134
|
+
reverse_proxy "/test", "http://example.com:8080/"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
it "sets the Host header including non-default port" do
|
139
|
+
stub_request(:any, "example.com:8080/test/stuff")
|
140
|
+
get "/test/stuff"
|
141
|
+
expect(
|
142
|
+
a_request(:get, "http://example.com:8080/test/stuff").with(
|
143
|
+
:headers => { "Host" => "example.com:8080" }
|
144
|
+
)
|
145
|
+
).to have_been_made
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
describe "with preserve host turned off" do
|
150
|
+
def app
|
151
|
+
Rack::ReverseProxy.new(dummy_app) do
|
152
|
+
reverse_proxy "/test", "http://example.com/", :preserve_host => false
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
it "does not set the Host header" do
|
157
|
+
stub_request(:any, "example.com/test/stuff")
|
158
|
+
get "/test/stuff"
|
159
|
+
|
160
|
+
expect(
|
161
|
+
a_request(:get, "http://example.com/test/stuff").with(
|
162
|
+
:headers => { "Host" => "example.com" }
|
163
|
+
)
|
164
|
+
).not_to have_been_made
|
165
|
+
|
166
|
+
expect(a_request(:get, "http://example.com/test/stuff")).to have_been_made
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe "with x_forwarded_host turned off" do
|
171
|
+
def app
|
172
|
+
Rack::ReverseProxy.new(dummy_app) do
|
173
|
+
reverse_proxy_options :x_forwarded_host => false
|
174
|
+
reverse_proxy "/test", "http://example.com/"
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
it "does not set the X-Forwarded-Host header to the proxying host" do
|
179
|
+
stub_request(:any, "example.com/test/stuff")
|
180
|
+
get "/test/stuff"
|
181
|
+
expect(
|
182
|
+
a_request(:get, "http://example.com/test/stuff").with(
|
183
|
+
:headers => { "X-Forwarded-Host" => "example.org" }
|
184
|
+
)
|
185
|
+
).not_to have_been_made
|
186
|
+
expect(a_request(:get, "http://example.com/test/stuff")).to have_been_made
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
190
|
+
describe "with timeout configuration" do
|
191
|
+
def app
|
192
|
+
Rack::ReverseProxy.new(dummy_app) do
|
193
|
+
reverse_proxy "/test/slow", "http://example.com/", :timeout => 99
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
it "makes request with basic auth" do
|
198
|
+
stub_request(:get, "http://example.com/test/slow")
|
199
|
+
allow(Rack::HttpStreamingResponse).to receive(:new).and_return(http_streaming_response)
|
200
|
+
expect(http_streaming_response).to receive(:read_timeout=).with(99)
|
201
|
+
get "/test/slow"
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe "without timeout configuration" do
|
206
|
+
def app
|
207
|
+
Rack::ReverseProxy.new(dummy_app) do
|
208
|
+
reverse_proxy "/test/slow", "http://example.com/"
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
it "makes request with basic auth" do
|
213
|
+
stub_request(:get, "http://example.com/test/slow")
|
214
|
+
allow(Rack::HttpStreamingResponse).to receive(:new).and_return(http_streaming_response)
|
215
|
+
expect(http_streaming_response).not_to receive(:read_timeout=)
|
216
|
+
get "/test/slow"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
describe "with basic auth turned on" do
|
221
|
+
def app
|
222
|
+
Rack::ReverseProxy.new(dummy_app) do
|
223
|
+
reverse_proxy "/test", "http://example.com/", :username => "joe", :password => "shmoe"
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
it "makes request with basic auth" do
|
228
|
+
stub_request(:get, "http://joe:shmoe@example.com/test/stuff").to_return(
|
229
|
+
:body => "secured content"
|
230
|
+
)
|
231
|
+
get "/test/stuff"
|
232
|
+
expect(last_response.body).to eq("secured content")
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
describe "with preserve response host turned on" do
|
237
|
+
def app
|
238
|
+
Rack::ReverseProxy.new(dummy_app) do
|
239
|
+
reverse_proxy "/test", "http://example.com/", :replace_response_host => true
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
it "replaces the location response header" do
|
244
|
+
stub_request(:get, "http://example.com/test/stuff").to_return(
|
245
|
+
:headers => { "location" => "http://test.com/bar" }
|
246
|
+
)
|
247
|
+
get "http://example.com/test/stuff"
|
248
|
+
expect(last_response.headers["location"]).to eq("http://example.com/bar")
|
249
|
+
end
|
250
|
+
|
251
|
+
it "keeps the port of the location" do
|
252
|
+
stub_request(:get, "http://example.com/test/stuff").to_return(
|
253
|
+
:headers => { "location" => "http://test.com/bar" }
|
254
|
+
)
|
255
|
+
get "http://example.com:3000/test/stuff"
|
256
|
+
expect(last_response.headers["location"]).to eq("http://example.com:3000/bar")
|
257
|
+
end
|
258
|
+
|
259
|
+
it "doesn't keep the port when it's default for the protocol" do
|
260
|
+
# webmock doesn't allow to stub an https URI, but this is enough to
|
261
|
+
# reply to the https code path
|
262
|
+
stub_request(:get, "http://example.com/test/stuff").to_return(
|
263
|
+
:headers => { "location" => "http://test.com/bar" }
|
264
|
+
)
|
265
|
+
get "https://example.com/test/stuff"
|
266
|
+
expect(last_response.headers["location"]).to eq("https://example.com/bar")
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe "with ambiguous routes and all matching" do
|
271
|
+
def app
|
272
|
+
Rack::ReverseProxy.new(dummy_app) do
|
273
|
+
reverse_proxy_options :matching => :all
|
274
|
+
reverse_proxy "/test", "http://example.com/"
|
275
|
+
reverse_proxy(%r{^/test}, "http://example.com/")
|
276
|
+
end
|
277
|
+
end
|
278
|
+
|
279
|
+
it "raises an exception" do
|
280
|
+
expect { get "/test" }.to raise_error(RackReverseProxy::Errors::AmbiguousMatch)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# FIXME: descriptions are not consistent with examples
|
285
|
+
describe "with ambiguous routes and first matching" do
|
286
|
+
def app
|
287
|
+
Rack::ReverseProxy.new(dummy_app) do
|
288
|
+
reverse_proxy_options :matching => :first
|
289
|
+
reverse_proxy "/test", "http://example1.com/"
|
290
|
+
reverse_proxy(%r{^/test}, "http://example2.com/")
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
it "raises an exception" do
|
295
|
+
stub_request(:get, "http://example1.com/test").to_return(:body => "Proxied App")
|
296
|
+
get "/test"
|
297
|
+
expect(last_response.body).to eq("Proxied App")
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
describe "with force ssl turned on" do
|
302
|
+
def app
|
303
|
+
Rack::ReverseProxy.new(dummy_app) do
|
304
|
+
reverse_proxy "/test", "http://example1.com/",
|
305
|
+
:force_ssl => true, :replace_response_host => true
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
it "redirects to the ssl version when requesting non-ssl" do
|
310
|
+
stub_request(:get, "http://example1.com/test/stuff").to_return(:body => "proxied")
|
311
|
+
get "http://example.com/test/stuff"
|
312
|
+
expect(last_response.headers["Location"]).to eq("https://example.com/test/stuff")
|
313
|
+
end
|
314
|
+
|
315
|
+
it "does nothing when already ssl" do
|
316
|
+
stub_request(:get, "http://example1.com/test/stuff").to_return(:body => "proxied")
|
317
|
+
get "https://example.com/test/stuff"
|
318
|
+
expect(last_response.body).to eq("proxied")
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
describe "with a route as a regular expression" do
|
323
|
+
def app
|
324
|
+
Rack::ReverseProxy.new(dummy_app) do
|
325
|
+
reverse_proxy %r{^/test(/.*)$}, "http://example.com$1"
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
it "supports subcaptures" do
|
330
|
+
stub_request(:get, "http://example.com/path").to_return(:body => "Proxied App")
|
331
|
+
get "/test/path"
|
332
|
+
expect(last_response.body).to eq("Proxied App")
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
describe "with a https route" do
|
337
|
+
def app
|
338
|
+
Rack::ReverseProxy.new(dummy_app) do
|
339
|
+
reverse_proxy "/test", "https://example.com"
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
it "makes a secure request" do
|
344
|
+
stub_request(:get, "https://example.com/test/stuff").to_return(
|
345
|
+
:body => "Proxied Secure App"
|
346
|
+
)
|
347
|
+
get "/test/stuff"
|
348
|
+
expect(last_response.body).to eq("Proxied Secure App")
|
349
|
+
end
|
350
|
+
|
351
|
+
it "sets the Host header w/o default port" do
|
352
|
+
stub_request(:any, "https://example.com/test/stuff")
|
353
|
+
get "/test/stuff"
|
354
|
+
expect(
|
355
|
+
a_request(:get, "https://example.com/test/stuff").with(
|
356
|
+
:headers => { "Host" => "example.com" }
|
357
|
+
)
|
358
|
+
).to have_been_made
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
describe "with a https route on non-default port" do
|
363
|
+
def app
|
364
|
+
Rack::ReverseProxy.new(dummy_app) do
|
365
|
+
reverse_proxy "/test", "https://example.com:8443"
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
it "sets the Host header including non-default port" do
|
370
|
+
stub_request(:any, "https://example.com:8443/test/stuff")
|
371
|
+
get "/test/stuff"
|
372
|
+
expect(
|
373
|
+
a_request(:get, "https://example.com:8443/test/stuff").with(
|
374
|
+
:headers => { "Host" => "example.com:8443" }
|
375
|
+
)
|
376
|
+
).to have_been_made
|
377
|
+
end
|
378
|
+
end
|
379
|
+
|
380
|
+
describe "with a route as a string" do
|
381
|
+
def app
|
382
|
+
Rack::ReverseProxy.new(dummy_app) do
|
383
|
+
reverse_proxy "/test", "http://example.com"
|
384
|
+
reverse_proxy "/path", "http://example.com/foo$0"
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
it "appends the full path to the uri" do
|
389
|
+
stub_request(:get, "http://example.com/test/stuff").to_return(:body => "Proxied App")
|
390
|
+
get "/test/stuff"
|
391
|
+
expect(last_response.body).to eq("Proxied App")
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
describe "with a generic url" do
|
396
|
+
def app
|
397
|
+
Rack::ReverseProxy.new(dummy_app) do
|
398
|
+
reverse_proxy "/test", "example.com"
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
it "throws an exception" do
|
403
|
+
expect { app }.to raise_error(RackReverseProxy::Errors::GenericURI)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
describe "with a matching route" do
|
408
|
+
def app
|
409
|
+
Rack::ReverseProxy.new(dummy_app) do
|
410
|
+
reverse_proxy "/test", "http://example.com/"
|
411
|
+
end
|
412
|
+
end
|
413
|
+
|
414
|
+
%w(get head delete put post).each do |method|
|
415
|
+
describe "and using method #{method}" do
|
416
|
+
it "forwards the correct request" do
|
417
|
+
stub_request(method.to_sym, "http://example.com/test").to_return(
|
418
|
+
:body => "Proxied App for #{method}"
|
419
|
+
)
|
420
|
+
send(method, "/test")
|
421
|
+
expect(last_response.body).to eq("Proxied App for #{method}")
|
422
|
+
end
|
423
|
+
|
424
|
+
if %w(put post).include?(method)
|
425
|
+
it "forwards the request payload" do
|
426
|
+
stub_request(
|
427
|
+
method.to_sym,
|
428
|
+
"http://example.com/test"
|
429
|
+
).to_return { |req| { :body => req.body } }
|
430
|
+
send(method, "/test", :test => "test")
|
431
|
+
expect(last_response.body).to eq("test=test")
|
432
|
+
end
|
433
|
+
end
|
434
|
+
end
|
435
|
+
end
|
436
|
+
end
|
437
|
+
|
438
|
+
describe "with a matching lambda" do
|
439
|
+
def app
|
440
|
+
Rack::ReverseProxy.new(dummy_app) do
|
441
|
+
reverse_proxy lambda { |path| path.match(%r{^/test}) }, "http://lambda.example.org"
|
442
|
+
end
|
443
|
+
end
|
444
|
+
|
445
|
+
it "forwards requests to the calling app when path is not matched" do
|
446
|
+
get "/users"
|
447
|
+
expect(last_response).to be_ok
|
448
|
+
expect(last_response.body).to eq("Dummy App")
|
449
|
+
end
|
450
|
+
|
451
|
+
it "proxies requests when a pattern is matched" do
|
452
|
+
stub_request(:get, "http://lambda.example.org/test").to_return(:body => "Proxied App")
|
453
|
+
|
454
|
+
get "/test"
|
455
|
+
expect(last_response.body).to eq("Proxied App")
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
describe "with a matching class" do
|
460
|
+
#:nodoc:
|
461
|
+
class Matcher
|
462
|
+
def self.match(path)
|
463
|
+
return unless path =~ %r{^/(test|users)}
|
464
|
+
Matcher.new
|
465
|
+
end
|
466
|
+
|
467
|
+
def url(path)
|
468
|
+
return "http://users-example.com" + path if path.include?("user")
|
469
|
+
"http://example.com" + path
|
470
|
+
end
|
471
|
+
end
|
472
|
+
|
473
|
+
def app
|
474
|
+
Rack::ReverseProxy.new(dummy_app) do
|
475
|
+
reverse_proxy Matcher
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
it "forwards requests to the calling app when the path is not matched" do
|
480
|
+
get "/"
|
481
|
+
expect(last_response.body).to eq("Dummy App")
|
482
|
+
expect(last_response).to be_ok
|
483
|
+
end
|
484
|
+
|
485
|
+
it "proxies requests when a pattern is matched" do
|
486
|
+
stub_request(:get, "http://example.com/test").to_return(:body => "Proxied App")
|
487
|
+
stub_request(:get, "http://users-example.com/users").to_return(:body => "User App")
|
488
|
+
|
489
|
+
get "/test"
|
490
|
+
expect(last_response.body).to eq("Proxied App")
|
491
|
+
|
492
|
+
get "/users"
|
493
|
+
expect(last_response.body).to eq("User App")
|
494
|
+
end
|
495
|
+
end
|
496
|
+
|
497
|
+
describe "with a matching class" do
|
498
|
+
#:nodoc:
|
499
|
+
class RequestMatcher
|
500
|
+
attr_accessor :rackreq
|
501
|
+
|
502
|
+
def initialize(rackreq)
|
503
|
+
self.rackreq = rackreq
|
504
|
+
end
|
505
|
+
|
506
|
+
def self.match(path, _headers, rackreq)
|
507
|
+
return nil unless path =~ %r{^/(test|users)}
|
508
|
+
RequestMatcher.new(rackreq)
|
509
|
+
end
|
510
|
+
|
511
|
+
def url(path)
|
512
|
+
return nil unless rackreq.params["user"] == "omer"
|
513
|
+
"http://users-example.com" + path
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
def app
|
518
|
+
Rack::ReverseProxy.new(dummy_app) do
|
519
|
+
reverse_proxy RequestMatcher
|
520
|
+
end
|
521
|
+
end
|
522
|
+
|
523
|
+
it "forwards requests to the calling app when the path is not matched" do
|
524
|
+
get "/"
|
525
|
+
expect(last_response.body).to eq("Dummy App")
|
526
|
+
expect(last_response).to be_ok
|
527
|
+
end
|
528
|
+
|
529
|
+
it "proxies requests when a pattern is matched" do
|
530
|
+
stub_request(:get, "http://users-example.com/users?user=omer").to_return(
|
531
|
+
:body => "User App"
|
532
|
+
)
|
533
|
+
|
534
|
+
get "/test", :user => "mark"
|
535
|
+
expect(last_response.body).to eq("Dummy App")
|
536
|
+
|
537
|
+
get "/users", :user => "omer"
|
538
|
+
expect(last_response.body).to eq("User App")
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
describe "with a matching class that accepts headers" do
|
543
|
+
#:nodoc:
|
544
|
+
class MatcherHeaders
|
545
|
+
def self.match(path, headers)
|
546
|
+
if path.match(%r{^/test}) && headers["ACCEPT"] && headers["ACCEPT"] == "foo.bar"
|
547
|
+
MatcherHeaders.new
|
548
|
+
end
|
549
|
+
end
|
550
|
+
|
551
|
+
def url(path)
|
552
|
+
"http://example.com" + path
|
553
|
+
end
|
554
|
+
end
|
555
|
+
|
556
|
+
def app
|
557
|
+
Rack::ReverseProxy.new(dummy_app) do
|
558
|
+
reverse_proxy MatcherHeaders, nil, :accept_headers => true
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
it "proxies requests when a pattern is matched and correct headers are passed" do
|
563
|
+
stub_request(:get, "http://example.com/test").to_return(
|
564
|
+
:body => "Proxied App with Headers"
|
565
|
+
)
|
566
|
+
get "/test", {}, "HTTP_ACCEPT" => "foo.bar"
|
567
|
+
expect(last_response.body).to eq("Proxied App with Headers")
|
568
|
+
end
|
569
|
+
|
570
|
+
it "does not proxy requests when a pattern is matched and incorrect headers are passed" do
|
571
|
+
stub_request(:get, "http://example.com/test").to_return(
|
572
|
+
:body => "Proxied App with Headers"
|
573
|
+
)
|
574
|
+
get "/test", {}, "HTTP_ACCEPT" => "bar.foo"
|
575
|
+
expect(last_response.body).not_to eq("Proxied App with Headers")
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
describe "as a rack app" do
|
581
|
+
it "responds with 404 when the path is not matched" do
|
582
|
+
get "/"
|
583
|
+
expect(last_response).to be_not_found
|
584
|
+
end
|
585
|
+
end
|
586
|
+
end
|