rack-reverse-proxy-pact 1.0.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.
@@ -0,0 +1,768 @@
1
+ require "spec_helper"
2
+ require "cgi"
3
+ require "base64"
4
+
5
+ RSpec.describe Rack::ReverseProxy do
6
+ include Rack::Test::Methods
7
+
8
+ def app
9
+ Rack::ReverseProxy.new
10
+ end
11
+
12
+ def dummy_app
13
+ lambda { |_| [200, {}, ["Dummy App"]] }
14
+ end
15
+
16
+ let(:http_streaming_response) do
17
+ double(
18
+ "Rack::HttpStreamingResponse",
19
+ :use_ssl= => nil,
20
+ :verify_mode= => nil,
21
+ :headers => {},
22
+ :status => 200,
23
+ :body => "OK"
24
+ )
25
+ end
26
+
27
+ describe "global options" do
28
+ it "starts with default global options" do
29
+ m = Rack::ReverseProxy.new(dummy_app) do
30
+ reverse_proxy "/test", "http://example.com/"
31
+ end
32
+ expect(m.instance_variable_get(:@global_options)).to eq(RackReverseProxy::Middleware::DEFAULT_OPTIONS)
33
+ end
34
+ it "allows options to be set via reverse_proxy_options, maintains global defaults" do
35
+ m = Rack::ReverseProxy.new(dummy_app) do
36
+ reverse_proxy "/test", "http://example.com/"
37
+ reverse_proxy_options preserve_host: "preserve_host_val"
38
+ end
39
+ expect(m.instance_variable_get(:@global_options)).to_not eq(RackReverseProxy::Middleware::DEFAULT_OPTIONS)
40
+ expect(m.instance_variable_get(:@global_options)[:preserve_host]).to eq("preserve_host_val")
41
+ raise "necessary condition for test is missing" if RackReverseProxy::Middleware::DEFAULT_OPTIONS[:x_forwarded_headers].nil?
42
+ expect(m.instance_variable_get(:@global_options)[:x_forwarded_headers]).to eq(RackReverseProxy::Middleware::DEFAULT_OPTIONS[:x_forwarded_headers])
43
+ end
44
+ it "supports multiple commulative invocations of reverse_proxy_options" do
45
+ m = Rack::ReverseProxy.new(dummy_app) do
46
+ reverse_proxy "/test", "http://example.com/"
47
+ reverse_proxy_options preserve_host: "preserve_host_val", stripped_headers: ["foo"]
48
+ reverse_proxy_options replace_response_host: "replace_response_host_val", stripped_headers: ["bar"]
49
+ end
50
+ expect(m.instance_variable_get(:@global_options)[:preserve_host]).to eq("preserve_host_val")
51
+ expect(m.instance_variable_get(:@global_options)[:replace_response_host]).to eq("replace_response_host_val")
52
+ expect(m.instance_variable_get(:@global_options)[:stripped_headers]).to eq(["bar"])
53
+ end
54
+ end
55
+
56
+ describe "as middleware" do
57
+ def app
58
+ Rack::ReverseProxy.new(dummy_app) do
59
+ reverse_proxy "/test", "http://example.com/", :preserve_host => true
60
+ reverse_proxy "/2test", lambda { |_| "http://example.com/" }
61
+ end
62
+ end
63
+
64
+ it "forwards requests to the calling app when the path is not matched" do
65
+ get "/"
66
+ expect(last_response.body).to eq("Dummy App")
67
+ expect(last_response).to be_ok
68
+ end
69
+
70
+ it "proxies requests when a pattern is matched" do
71
+ stub_request(:get, "http://example.com/test").to_return(:body => "Proxied App")
72
+ get "/test"
73
+ expect(last_response.body).to eq("Proxied App")
74
+ end
75
+
76
+ it "produces a response header of type Headers" do
77
+ stub_request(:get, "http://example.com/test")
78
+ get "/test"
79
+ expected_class = rack_version_less_than_three ? Rack::Utils::HeaderHash : Rack::Headers
80
+ expect(last_response.headers).to be_an_instance_of(expected_class)
81
+ end
82
+
83
+ it "parses the headers as a Hash with values of type String" do
84
+ stub_request(:get, "http://example.com/test").to_return(
85
+ :headers => { "cache-control" => "max-age=300, public" }
86
+ )
87
+ get "/test"
88
+ expect(last_response.headers["cache-control"]).to be_an_instance_of(String)
89
+ expect(last_response.headers["cache-control"]).to eq("max-age=300, public")
90
+ end
91
+
92
+ it "proxies requests to a lambda url when a pattern is matched" do
93
+ stub_request(:get, "http://example.com/2test").to_return(:body => "Proxied App2")
94
+ get "/2test"
95
+ expect(last_response.body).to eq("Proxied App2")
96
+ end
97
+
98
+ it "returns headers from proxied app as strings" do
99
+ stub_request(:get, "http://example.com/test").to_return(
100
+ :body => "Proxied App",
101
+ :headers => { "Proxied-Header" => "TestValue" }
102
+ )
103
+ get "/test"
104
+ expect(last_response.headers["Proxied-Header"]).to eq("TestValue")
105
+ end
106
+
107
+ it "sets the Host header w/o default port" do
108
+ stub_request(:any, "example.com/test/stuff")
109
+ get "/test/stuff"
110
+ expect(
111
+ a_request(:get, "http://example.com/test/stuff").with(
112
+ :headers => { "Host" => "example.com" }
113
+ )
114
+ ).to have_been_made
115
+ end
116
+
117
+ it "sets the X-Forwarded-Host header to the proxying host by default" do
118
+ stub_request(:any, "example.com/test/stuff")
119
+ get "/test/stuff"
120
+ expect(
121
+ a_request(:get, "http://example.com/test/stuff").with(
122
+ :headers => { "X-Forwarded-Host" => "example.org" }
123
+ )
124
+ ).to have_been_made
125
+ end
126
+
127
+ it "sets the X-Forwarded-Port header to the proxying port by default" do
128
+ stub_request(:any, "example.com/test/stuff")
129
+ get "/test/stuff"
130
+ expect(
131
+ a_request(:get, "http://example.com/test/stuff").with(
132
+ :headers => { "X-Forwarded-Port" => "80" }
133
+ )
134
+ ).to have_been_made
135
+ end
136
+
137
+ it "sets the X-Forwarded-Proto header to the proxying scheme by default" do
138
+ stub_request(:any, "example.com/test/stuff")
139
+ get "https://example.com/test/stuff"
140
+ expect(
141
+ a_request(:get, "example.com/test/stuff").with(
142
+ :headers => { "X-Forwarded-Proto" => "https" }
143
+ )
144
+ ).to have_been_made
145
+ end
146
+
147
+ it "does not produce headers with a Status key" do
148
+ stub_request(:get, "http://example.com/2test").to_return(
149
+ :status => 301, :headers => { :status => "301 Moved Permanently" }
150
+ )
151
+
152
+ get "/2test"
153
+
154
+ headers = last_response.headers.to_hash
155
+ expect(headers["Status"]).to be_nil
156
+ end
157
+
158
+ it "compares keys case-insensitive" do
159
+ stub_request(:get, "http://example.com/2test").to_return(
160
+ :headers => { :date => "Wed, 22 Jul 2015 11:27:21 GMT" }
161
+ )
162
+
163
+ get "/2test"
164
+
165
+ headers = last_response.headers.to_hash
166
+ expect(headers["Date"]).to eq("Wed, 22 Jul 2015 11:27:21 GMT")
167
+ expect(headers["date"]).to rack_version_less_than_three ? be_nil : eq("Wed, 22 Jul 2015 11:27:21 GMT")
168
+ end
169
+
170
+ it "formats the headers with dashes correctly" do
171
+ stub_request(:get, "http://example.com/2test").to_return(
172
+ :status => 301,
173
+ :headers => { :status => "301 Moved Permanently", :"x-additional-info" => "something" }
174
+ )
175
+
176
+ get "/2test"
177
+
178
+ headers = last_response.headers.to_hash
179
+ expect(headers["x-additional-info"]).to eq(rack_version_less_than_three ? nil : "something")
180
+ end
181
+
182
+ it "the response header includes content-length" do
183
+ body = "this is the test body"
184
+ stub_request(:any, "example.com/test/stuff").to_return(
185
+ :body => body, :headers => { "Content-Length" => "10" }
186
+ )
187
+ get "/test/stuff"
188
+ expect(last_response.headers["Content-Length"]).to eq(body.length.to_s)
189
+ end
190
+
191
+ describe "with non-default port" do
192
+ def app
193
+ Rack::ReverseProxy.new(dummy_app) do
194
+ reverse_proxy "/test", "http://example.com:8080/"
195
+ end
196
+ end
197
+
198
+ it "sets the Host header including non-default port" do
199
+ stub_request(:any, "example.com:8080/test/stuff")
200
+ get "/test/stuff"
201
+ expect(
202
+ a_request(:get, "http://example.com:8080/test/stuff").with(
203
+ :headers => { "Host" => "example.com:8080" }
204
+ )
205
+ ).to have_been_made
206
+ end
207
+ end
208
+
209
+ describe "with preserve host turned off" do
210
+ def app
211
+ Rack::ReverseProxy.new(dummy_app) do
212
+ reverse_proxy "/test", "http://example.com/", :preserve_host => false
213
+ end
214
+ end
215
+
216
+ it "does not set the Host header" do
217
+ stub_request(:any, "example.com/test/stuff")
218
+ get "/test/stuff"
219
+
220
+ expect(
221
+ a_request(:get, "http://example.com/test/stuff").with(
222
+ :headers => { "Host" => "example.com" }
223
+ )
224
+ ).not_to have_been_made
225
+
226
+ expect(a_request(:get, "http://example.com/test/stuff")).to have_been_made
227
+ end
228
+ end
229
+
230
+ context "stripped_headers option" do
231
+ subject do
232
+ stub_request(:any, "http://example.com/test")
233
+ get "/test", {}, "HTTP_ACCEPT_ENCODING" => "gzip, deflate", "HTTP_FOO_BAR" => "baz"
234
+ end
235
+
236
+ describe "with stripped_headers not set" do
237
+ def app
238
+ Rack::ReverseProxy.new(dummy_app) do
239
+ reverse_proxy "/test", "http://example.com/"
240
+ end
241
+ end
242
+
243
+ it "forwards the headers" do
244
+ subject
245
+ expect(
246
+ a_request(:get, "http://example.com/test").with(
247
+ :headers => { "Accept-Encoding" => "gzip, deflate", "Foo-Bar" => "baz" }
248
+ )
249
+ ).to have_been_made
250
+ end
251
+ end
252
+
253
+ describe "with stripped_headers set" do
254
+ before do
255
+ @stripped_headers = ["Accept-Encoding", "Foo-Bar"]
256
+ def app
257
+ # so the value is constant in the closure below
258
+ stripped_headers = @stripped_headers
259
+ Rack::ReverseProxy.new(dummy_app) do
260
+ reverse_proxy "/test", "http://example.com/", :stripped_headers => stripped_headers
261
+ end
262
+ end
263
+ end
264
+
265
+ it "removes the stripped headers" do
266
+ subject
267
+ expect(
268
+ a_request(:get, "http://example.com/test").with{ |req|
269
+ req.headers.each do |header, value|
270
+ if @stripped_headers.include?(header)
271
+ fail "expected #{header} to not be present"
272
+ end
273
+ end
274
+ }
275
+ ).to have_been_made
276
+ end
277
+ end
278
+ end
279
+
280
+ describe "with x_forwarded_headers turned off" do
281
+ def app
282
+ Rack::ReverseProxy.new(dummy_app) do
283
+ reverse_proxy_options :x_forwarded_headers => false
284
+ reverse_proxy "/test", "http://example.com/"
285
+ end
286
+ end
287
+
288
+ it "does not set the X-Forwarded-Host header to the proxying host" do
289
+ stub_request(:any, "example.com/test/stuff")
290
+ get "/test/stuff"
291
+ expect(
292
+ a_request(:get, "http://example.com/test/stuff").with(
293
+ :headers => { "X-Forwarded-Host" => "example.org" }
294
+ )
295
+ ).not_to have_been_made
296
+ expect(a_request(:get, "http://example.com/test/stuff")).to have_been_made
297
+ end
298
+
299
+ it "does not set the X-Forwarded-Port header to the proxying port" do
300
+ stub_request(:any, "example.com/test/stuff")
301
+ get "/test/stuff"
302
+ expect(
303
+ a_request(:get, "http://example.com/test/stuff").with(
304
+ :headers => { "X-Forwarded-Port" => "80" }
305
+ )
306
+ ).not_to have_been_made
307
+ expect(a_request(:get, "http://example.com/test/stuff")).to have_been_made
308
+ end
309
+
310
+ it "does not set the X-Forwarded-Proto header to the proxying scheme" do
311
+ stub_request(:any, "example.com/test/stuff")
312
+ get "https://example.com/test/stuff"
313
+ expect(
314
+ a_request(:get, "example.com/test/stuff").with(
315
+ :headers => { "X-Forwarded-Proto" => "https" }
316
+ )
317
+ ).not_to have_been_made
318
+ expect(a_request(:get, "example.com/test/stuff")).to have_been_made
319
+ end
320
+ end
321
+
322
+ describe "with timeout configuration" do
323
+ def app
324
+ Rack::ReverseProxy.new(dummy_app) do
325
+ reverse_proxy "/test/slow", "http://example.com/", :timeout => 99
326
+ end
327
+ end
328
+
329
+ it "makes request with basic auth" do
330
+ stub_request(:get, "http://example.com/test/slow")
331
+ allow(Rack::HttpStreamingResponse).to receive(:new).and_return(http_streaming_response)
332
+ expect(http_streaming_response).to receive(:read_timeout=).with(99)
333
+ get "/test/slow"
334
+ end
335
+ end
336
+
337
+ describe "without timeout configuration" do
338
+ def app
339
+ Rack::ReverseProxy.new(dummy_app) do
340
+ reverse_proxy "/test/slow", "http://example.com/"
341
+ end
342
+ end
343
+
344
+ it "makes request with basic auth" do
345
+ stub_request(:get, "http://example.com/test/slow")
346
+ allow(Rack::HttpStreamingResponse).to receive(:new).and_return(http_streaming_response)
347
+ expect(http_streaming_response).not_to receive(:read_timeout=)
348
+ get "/test/slow"
349
+ end
350
+ end
351
+
352
+ describe "with basic auth turned on" do
353
+ def app
354
+ Rack::ReverseProxy.new(dummy_app) do
355
+ reverse_proxy "/test", "http://example.com/", :username => "joe", :password => "shmoe"
356
+ end
357
+ end
358
+
359
+ it "makes request with basic auth" do
360
+ stub_request(:get, "http://example.com/test/stuff").with(
361
+ :basic_auth => %w(joe shmoe)
362
+ ).to_return(
363
+ :body => "secured content"
364
+ )
365
+ get "/test/stuff"
366
+ expect(last_response.body).to eq("secured content")
367
+ end
368
+ end
369
+
370
+ describe "with replace response host turned on" do
371
+ def app
372
+ Rack::ReverseProxy.new(dummy_app) do
373
+ reverse_proxy "/test", "http://example.com/", :replace_response_host => true
374
+ end
375
+ end
376
+
377
+ it "replaces the location response header" do
378
+ stub_request(:get, "http://example.com/test/stuff").to_return(
379
+ :headers => { "location" => "http://test.com/bar" }
380
+ )
381
+ get "http://example.com/test/stuff"
382
+ expect(last_response.headers["location"]).to eq("http://example.com/bar")
383
+ end
384
+
385
+ it "keeps the port of the location" do
386
+ stub_request(:get, "http://example.com/test/stuff").to_return(
387
+ :headers => { "location" => "http://test.com/bar" }
388
+ )
389
+ get "http://example.com:3000/test/stuff"
390
+ expect(last_response.headers["location"]).to eq("http://example.com:3000/bar")
391
+ end
392
+
393
+ it "doesn't keep the port when it's default for the protocol" do
394
+ # webmock doesn't allow to stub an https URI, but this is enough to
395
+ # reply to the https code path
396
+ stub_request(:get, "http://example.com/test/stuff").to_return(
397
+ :headers => { "location" => "http://test.com/bar" }
398
+ )
399
+ get "https://example.com/test/stuff"
400
+ expect(last_response.headers["location"]).to eq("https://example.com/bar")
401
+ end
402
+
403
+ it "doesn't replaces the location response header if it has no host" do
404
+ stub_request(:get, "http://example.com/test/stuff").to_return(
405
+ :headers => { "location" => "/bar" }
406
+ )
407
+ get "http://example.com/test/stuff"
408
+ expect(last_response.headers["location"]).to eq("/bar")
409
+ end
410
+ end
411
+
412
+ describe "with ambiguous routes and all matching" do
413
+ def app
414
+ Rack::ReverseProxy.new(dummy_app) do
415
+ reverse_proxy_options :matching => :all
416
+ reverse_proxy "/test", "http://example.com/"
417
+ reverse_proxy(%r{^/test}, "http://example.com/")
418
+ end
419
+ end
420
+
421
+ it "raises an exception" do
422
+ expect { get "/test" }.to raise_error(RackReverseProxy::Errors::AmbiguousMatch)
423
+ end
424
+ end
425
+
426
+ # FIXME: descriptions are not consistent with examples
427
+ describe "with ambiguous routes and first matching" do
428
+ def app
429
+ Rack::ReverseProxy.new(dummy_app) do
430
+ reverse_proxy_options :matching => :first
431
+ reverse_proxy "/test", "http://example1.com/"
432
+ reverse_proxy(%r{^/test}, "http://example2.com/")
433
+ end
434
+ end
435
+
436
+ it "raises an exception" do
437
+ stub_request(:get, "http://example1.com/test").to_return(:body => "Proxied App")
438
+ get "/test"
439
+ expect(last_response.body).to eq("Proxied App")
440
+ end
441
+ end
442
+
443
+ describe "with force ssl turned on" do
444
+ def app
445
+ Rack::ReverseProxy.new(dummy_app) do
446
+ reverse_proxy "/test", "http://example1.com/",
447
+ :force_ssl => true, :replace_response_host => true
448
+ end
449
+ end
450
+
451
+ it "redirects to the ssl version when requesting non-ssl" do
452
+ stub_request(:get, "http://example1.com/test/stuff").to_return(:body => "proxied")
453
+ get "http://example.com/test/stuff"
454
+ expect(last_response.headers["Location"]).to eq("https://example.com/test/stuff")
455
+ end
456
+
457
+ it "does nothing when already ssl" do
458
+ stub_request(:get, "http://example1.com/test/stuff").to_return(:body => "proxied")
459
+ get "https://example.com/test/stuff"
460
+ expect(last_response.body).to eq("proxied")
461
+ end
462
+ end
463
+
464
+ describe "with a route as a regular expression" do
465
+ def app
466
+ Rack::ReverseProxy.new(dummy_app) do
467
+ reverse_proxy %r{^/test(/.*)$}, "http://example.com$1"
468
+ end
469
+ end
470
+
471
+ it "supports subcaptures" do
472
+ stub_request(:get, "http://example.com/path").to_return(:body => "Proxied App")
473
+ get "/test/path"
474
+ expect(last_response.body).to eq("Proxied App")
475
+ end
476
+ end
477
+
478
+ describe "with a https route" do
479
+ def app
480
+ Rack::ReverseProxy.new(dummy_app) do
481
+ reverse_proxy "/test", "https://example.com"
482
+ end
483
+ end
484
+
485
+ it "makes a secure request" do
486
+ stub_request(:get, "https://example.com/test/stuff").to_return(
487
+ :body => "Proxied Secure App"
488
+ )
489
+ get "/test/stuff"
490
+ expect(last_response.body).to eq("Proxied Secure App")
491
+ end
492
+
493
+ it "sets the Host header w/o default port" do
494
+ stub_request(:any, "https://example.com/test/stuff")
495
+ get "/test/stuff"
496
+ expect(
497
+ a_request(:get, "https://example.com/test/stuff").with(
498
+ :headers => { "Host" => "example.com" }
499
+ )
500
+ ).to have_been_made
501
+ end
502
+ end
503
+
504
+ describe "with a https route on non-default port" do
505
+ def app
506
+ Rack::ReverseProxy.new(dummy_app) do
507
+ reverse_proxy "/test", "https://example.com:8443"
508
+ end
509
+ end
510
+
511
+ it "sets the Host header including non-default port" do
512
+ stub_request(:any, "https://example.com:8443/test/stuff")
513
+ get "/test/stuff"
514
+ expect(
515
+ a_request(:get, "https://example.com:8443/test/stuff").with(
516
+ :headers => { "Host" => "example.com:8443" }
517
+ )
518
+ ).to have_been_made
519
+ end
520
+ end
521
+
522
+ describe "with a route as a string" do
523
+ def app
524
+ Rack::ReverseProxy.new(dummy_app) do
525
+ reverse_proxy "/test", "http://example.com"
526
+ reverse_proxy "/path", "http://example.com/foo$0"
527
+ end
528
+ end
529
+
530
+ it "appends the full path to the uri" do
531
+ stub_request(:get, "http://example.com/test/stuff").to_return(:body => "Proxied App")
532
+ get "/test/stuff"
533
+ expect(last_response.body).to eq("Proxied App")
534
+ end
535
+ end
536
+
537
+ describe "with a generic url" do
538
+ def app
539
+ Rack::ReverseProxy.new(dummy_app) do
540
+ reverse_proxy "/test", "example.com"
541
+ end
542
+ end
543
+
544
+ it "throws an exception" do
545
+ expect { app }.to raise_error(RackReverseProxy::Errors::GenericURI)
546
+ end
547
+ end
548
+
549
+ describe "with a matching route" do
550
+ def app
551
+ Rack::ReverseProxy.new(dummy_app) do
552
+ reverse_proxy "/test", "http://example.com/"
553
+ end
554
+ end
555
+
556
+ %w(get head delete put post).each do |method|
557
+ describe "and using method #{method}" do
558
+ it "forwards the correct request" do
559
+ stub_request(method.to_sym, "http://example.com/test").to_return(
560
+ :body => "Proxied App for #{method}"
561
+ )
562
+ send(method, "/test")
563
+ expect(last_response.body).to eq("Proxied App for #{method}")
564
+ end
565
+
566
+ if %w(put post).include?(method)
567
+ it "forwards the request payload" do
568
+ stub_request(
569
+ method.to_sym,
570
+ "http://example.com/test"
571
+ ).to_return { |req| { :body => req.body } }
572
+ send(method, "/test", :test => "test")
573
+ expect(last_response.body).to eq("test=test")
574
+ end
575
+ end
576
+ end
577
+ end
578
+ end
579
+
580
+ describe "with a matching lambda" do
581
+ def app
582
+ Rack::ReverseProxy.new(dummy_app) do
583
+ reverse_proxy lambda { |path| path.match(%r{^/test}) }, "http://lambda.example.org"
584
+ end
585
+ end
586
+
587
+ it "forwards requests to the calling app when path is not matched" do
588
+ get "/users"
589
+ expect(last_response).to be_ok
590
+ expect(last_response.body).to eq("Dummy App")
591
+ end
592
+
593
+ it "proxies requests when a pattern is matched" do
594
+ stub_request(:get, "http://lambda.example.org/test").to_return(:body => "Proxied App")
595
+
596
+ get "/test"
597
+ expect(last_response.body).to eq("Proxied App")
598
+ end
599
+ end
600
+
601
+ describe "with a matching class" do
602
+ #:nodoc:
603
+ class Matcher
604
+ def self.match(path)
605
+ return unless path =~ %r{^/(test|users)}
606
+ Matcher.new
607
+ end
608
+
609
+ def url(path)
610
+ return "http://users-example.com" + path if path.include?("user")
611
+ "http://example.com" + path
612
+ end
613
+ end
614
+
615
+ def app
616
+ Rack::ReverseProxy.new(dummy_app) do
617
+ reverse_proxy Matcher
618
+ end
619
+ end
620
+
621
+ it "forwards requests to the calling app when the path is not matched" do
622
+ get "/"
623
+ expect(last_response.body).to eq("Dummy App")
624
+ expect(last_response).to be_ok
625
+ end
626
+
627
+ it "proxies requests when a pattern is matched" do
628
+ stub_request(:get, "http://example.com/test").to_return(:body => "Proxied App")
629
+ stub_request(:get, "http://users-example.com/users").to_return(:body => "User App")
630
+
631
+ get "/test"
632
+ expect(last_response.body).to eq("Proxied App")
633
+
634
+ get "/users"
635
+ expect(last_response.body).to eq("User App")
636
+ end
637
+ end
638
+
639
+ describe "with a matching and transforming class" do
640
+ #:nodoc:
641
+ class MatcherAndTransformer
642
+ def self.match(_path)
643
+ MatcherAndTransformer.new
644
+ end
645
+
646
+ def url(_path)
647
+ "http://example.org/redirecting"
648
+ end
649
+
650
+ def transform(response, request_uri)
651
+ status, headers, body = response
652
+ location = headers["Location"]
653
+ headers["Location"] = "?url=" + CGI.escape(location) +
654
+ "&request_uri=" + CGI.escape(request_uri.to_s)
655
+ [status, headers, body]
656
+ end
657
+ end
658
+
659
+ def app
660
+ Rack::ReverseProxy.new(dummy_app) do
661
+ reverse_proxy MatcherAndTransformer
662
+ end
663
+ end
664
+
665
+ it "transforms the proxied response" do
666
+ stub_request(:get, "http://example.org/redirecting").to_return(
667
+ :headers => {
668
+ "Location" => "http://example.org/target"
669
+ }
670
+ )
671
+
672
+ get "/"
673
+ expect(last_response.headers["Location"])
674
+ .to eq("?url=http%3A%2F%2Fexample.org%2Ftarget" \
675
+ "&request_uri=http%3A%2F%2Fexample.org%2Fredirecting")
676
+ end
677
+ end
678
+
679
+ describe "with a matching class" do
680
+ #:nodoc:
681
+ class RequestMatcher
682
+ attr_accessor :rackreq
683
+
684
+ def initialize(rackreq)
685
+ self.rackreq = rackreq
686
+ end
687
+
688
+ def self.match(path, _headers, rackreq)
689
+ return nil unless path =~ %r{^/(test|users)}
690
+ RequestMatcher.new(rackreq)
691
+ end
692
+
693
+ def url(path)
694
+ return nil unless rackreq.params["user"] == "omer"
695
+ "http://users-example.com" + path
696
+ end
697
+ end
698
+
699
+ def app
700
+ Rack::ReverseProxy.new(dummy_app) do
701
+ reverse_proxy RequestMatcher
702
+ end
703
+ end
704
+
705
+ it "forwards requests to the calling app when the path is not matched" do
706
+ get "/"
707
+ expect(last_response.body).to eq("Dummy App")
708
+ expect(last_response).to be_ok
709
+ end
710
+
711
+ it "proxies requests when a pattern is matched" do
712
+ stub_request(:get, "http://users-example.com/users?user=omer").to_return(
713
+ :body => "User App"
714
+ )
715
+
716
+ get "/test", :user => "mark"
717
+ expect(last_response.body).to eq("Dummy App")
718
+
719
+ get "/users", :user => "omer"
720
+ expect(last_response.body).to eq("User App")
721
+ end
722
+ end
723
+
724
+ describe "with a matching class that accepts headers" do
725
+ #:nodoc:
726
+ class MatcherHeaders
727
+ def self.match(path, headers)
728
+ MatcherHeaders.new if path.match(%r{^/test}) &&
729
+ headers["ACCEPT"] &&
730
+ headers["ACCEPT"] == "foo.bar"
731
+ end
732
+
733
+ def url(path)
734
+ "http://example.com" + path
735
+ end
736
+ end
737
+
738
+ def app
739
+ Rack::ReverseProxy.new(dummy_app) do
740
+ reverse_proxy MatcherHeaders, nil, :accept_headers => true
741
+ end
742
+ end
743
+
744
+ it "proxies requests when a pattern is matched and correct headers are passed" do
745
+ stub_request(:get, "http://example.com/test").to_return(
746
+ :body => "Proxied App with Headers"
747
+ )
748
+ get "/test", {}, "HTTP_ACCEPT" => "foo.bar"
749
+ expect(last_response.body).to eq("Proxied App with Headers")
750
+ end
751
+
752
+ it "does not proxy requests when a pattern is matched and incorrect headers are passed" do
753
+ stub_request(:get, "http://example.com/test").to_return(
754
+ :body => "Proxied App with Headers"
755
+ )
756
+ get "/test", {}, "HTTP_ACCEPT" => "bar.foo"
757
+ expect(last_response.body).not_to eq("Proxied App with Headers")
758
+ end
759
+ end
760
+ end
761
+
762
+ describe "as a rack app", skip: rack_version_less_than_three do
763
+ it "responds with 404 when the path is not matched" do
764
+ get "/"
765
+ expect(last_response).to be_not_found
766
+ end
767
+ end
768
+ end