rack-reverse-proxy 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -2,13 +2,18 @@
2
2
 
3
3
  This is a simple reverse proxy for Rack that pretty heavily rips off Rack Forwarder. It is not meant for production systems (although it may work), as the webserver fronting your app is generally much better at this sort of thing.
4
4
 
5
+ == Installation
6
+ The gem is available on gemcutter. Assuming you have a recent version of Rubygems you should just be able to install it via:
7
+ gem install rack-reverse-proxy
8
+
9
+ == Usage
5
10
  Matchers can be a regex or a string. If a regex is used, you can use the subcaptures in your forwarding url by denoting them with a $.
6
11
 
7
12
  Right now if more than one matcher matches any given route, it throws an exception for an ambiguous match. This will probably change later. If no match is found, the call is forwarded to your application.
8
13
 
9
14
  Below is an example for configuring the middleware:
10
15
 
11
- require 'lib/rack/reverse_proxy'
16
+ require 'rack/reverse_proxy'
12
17
 
13
18
  use Rack::ReverseProxy do
14
19
  # Forward the path /test* to http://example.com/test*
data/Rakefile CHANGED
@@ -10,7 +10,9 @@ begin
10
10
  gem.email = "jaswope@gmail.com"
11
11
  gem.homepage = "http://github.com/jaswope/rack-reverse-proxy"
12
12
  gem.authors = ["Jon Swope"]
13
- gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
13
+ gem.add_development_dependency "rspec", ">= 0"
14
+ gem.add_development_dependency "rack-test", ">= 0"
15
+ gem.add_development_dependency "webmock", ">= 0"
14
16
  gem.add_dependency "rack", ">= 1.0.0"
15
17
  # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
18
  end
@@ -19,29 +21,21 @@ rescue LoadError
19
21
  puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
22
  end
21
23
 
22
- require 'rake/testtask'
23
- Rake::TestTask.new(:test) do |test|
24
- test.libs << 'lib' << 'test'
25
- test.pattern = 'test/**/test_*.rb'
26
- test.verbose = true
24
+ require 'spec/rake/spectask'
25
+ Spec::Rake::SpecTask.new(:spec) do |spec|
26
+ spec.libs << 'lib' << 'spec'
27
+ spec.spec_files = FileList['spec/**/*_spec.rb']
27
28
  end
28
29
 
29
- begin
30
- require 'rcov/rcovtask'
31
- Rcov::RcovTask.new do |test|
32
- test.libs << 'test'
33
- test.pattern = 'test/**/test_*.rb'
34
- test.verbose = true
35
- end
36
- rescue LoadError
37
- task :rcov do
38
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
- end
30
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
31
+ spec.libs << 'lib' << 'spec'
32
+ spec.pattern = 'spec/**/*_spec.rb'
33
+ spec.rcov = true
40
34
  end
41
35
 
42
- task :test => :check_dependencies
36
+ task :spec => :check_dependencies
43
37
 
44
- task :default => :test
38
+ task :default => :spec
45
39
 
46
40
  require 'rake/rdoctask'
47
41
  Rake::RDocTask.new do |rdoc|
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.0
1
+ 0.2.0
@@ -13,73 +13,99 @@ module Rack
13
13
  matcher, url = get_matcher_and_url rackreq.fullpath
14
14
  return @app.call(env) if matcher.nil?
15
15
 
16
- path = rackreq.fullpath
17
- match = case matcher
18
- when String
19
- path.match(/^#{matcher}/)
20
- when Regexp
21
- path.match(matcher)
22
- end
23
- uri = case url
24
- when /\$\d/
25
- match.to_a.each_with_index { |m, i| url.gsub!("$#{i.to_s}", m) }
26
- URI(url)
27
- else
28
- URI.join(url, path)
29
- end
30
-
31
- headers = Rack::Utils::HeaderHash.new
32
- env.each { |key, value|
33
- if key =~ /HTTP_(.*)/
34
- headers[$1] = value
35
- end
36
- }
37
-
38
- res = Net::HTTP.start(uri.host, uri.port) { |http|
39
- m = rackreq.request_method
40
- case m
41
- when "GET", "HEAD", "DELETE", "OPTIONS", "TRACE"
42
- req = Net::HTTP.const_get(m.capitalize).new(uri.path, headers)
43
- when "PUT", "POST"
44
- req = Net::HTTP.const_get(m.capitalize).new(uri.path, headers)
45
- req.body_stream = rackreq.body
46
- else
47
- raise "method not supported: #{method}"
48
- end
16
+ uri = get_uri(url, matcher, rackreq.fullpath)
17
+ headers = Rack::Utils::HeaderHash.new
18
+ env.each { |key, value|
19
+ if key =~ /HTTP_(.*)/
20
+ headers[$1] = value
21
+ end
22
+ }
49
23
 
50
- http.request(req)
51
- }
24
+ res = Net::HTTP.start(uri.host, uri.port) { |http|
25
+ m = rackreq.request_method
26
+ case m
27
+ when "GET", "HEAD", "DELETE", "OPTIONS", "TRACE"
28
+ req = Net::HTTP.const_get(m.capitalize).new(uri.path, headers)
29
+ when "PUT", "POST"
30
+ req = Net::HTTP.const_get(m.capitalize).new(uri.path, headers)
31
+ req.body_stream = rackreq.body
32
+ else
33
+ raise "method not supported: #{method}"
34
+ end
35
+
36
+ http.request(req)
37
+ }
52
38
 
53
- [res.code, Rack::Utils::HeaderHash.new(res.to_hash), [res.body]]
39
+ [res.code, Rack::Utils::HeaderHash.new(res.to_hash), [res.body]]
54
40
  end
55
41
 
56
42
  private
57
43
 
58
44
  def get_matcher_and_url path
59
45
  matches = @paths.select do |matcher, url|
60
- case matcher
61
- when String
62
- path =~ /^#{matcher}/
63
- when Regexp
64
- path =~ matcher
65
- end
46
+ match_path(path, matcher)
66
47
  end
67
48
 
68
49
  if matches.length < 1
69
50
  nil
70
51
  elsif matches.length > 1
71
- raise AmbiguousProxyMatch
52
+ raise AmbiguousProxyMatch.new(path, matches)
72
53
  else
73
54
  matches.first.map{|a| a.dup}
74
55
  end
75
56
  end
76
57
 
58
+ def match_path(path, matcher)
59
+ if matcher.is_a?(Regexp)
60
+ path.match(matcher)
61
+ else
62
+ path.match(/^#{matcher.to_s}/)
63
+ end
64
+ end
65
+
66
+ def get_uri(url, matcher, path)
67
+ if url =~/\$\d/
68
+ match_path(path, matcher).to_a.each_with_index { |m, i| url.gsub!("$#{i.to_s}", m) }
69
+ URI(url)
70
+ else
71
+ URI.join(url, path)
72
+ end
73
+ end
74
+
77
75
  def reverse_proxy matcher, url
76
+ raise GenericProxyURI.new(url) if matcher.is_a?(String) && URI(url).class == URI::Generic
78
77
  @paths.merge!(matcher => url)
79
78
  end
80
79
  end
81
80
 
81
+ class GenericProxyURI < Exception
82
+ attr_reader :url
83
+
84
+ def intialize(url)
85
+ @url = url
86
+ end
87
+
88
+ def to_s
89
+ %Q(Your URL "#{@url}" is too generic. Did you mean "http://#{@url}"?)
90
+ end
91
+ end
92
+
82
93
  class AmbiguousProxyMatch < Exception
94
+ attr_reader :path, :matches
95
+ def initialize(path, matches)
96
+ @path = path
97
+ @matches = matches
98
+ end
99
+
100
+ def to_s
101
+ %Q(Path "#{path}" matched multiple endpoints: #{formatted_matches})
102
+ end
103
+
104
+ private
105
+
106
+ def formatted_matches
107
+ matches.map {|m| %Q("#{m[0].to_s}" => "#{m[1]}")}.join(', ')
108
+ end
83
109
  end
84
110
 
85
111
  end
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{rack-reverse-proxy}
8
- s.version = "0.1.0"
8
+ s.version = "0.2.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Jon Swope"]
12
- s.date = %q{2010-04-10}
12
+ s.date = %q{2010-04-11}
13
13
  s.description = %q{A Rack based reverse proxy for basic needs. Useful for testing or in cases where webserver configuration is unavailable.}
14
14
  s.email = %q{jaswope@gmail.com}
15
15
  s.extra_rdoc_files = [
@@ -26,17 +26,18 @@ Gem::Specification.new do |s|
26
26
  "VERSION",
27
27
  "lib/rack/reverse_proxy.rb",
28
28
  "rack-reverse-proxy.gemspec",
29
- "test/helper.rb",
30
- "test/test_rack-reverse-proxy.rb"
29
+ "spec/rack/reverse_proxy_spec.rb",
30
+ "spec/spec.opts",
31
+ "spec/spec_helper.rb"
31
32
  ]
32
33
  s.homepage = %q{http://github.com/jaswope/rack-reverse-proxy}
33
34
  s.rdoc_options = ["--charset=UTF-8"]
34
35
  s.require_paths = ["lib"]
35
- s.rubygems_version = %q{1.3.5}
36
+ s.rubygems_version = %q{1.3.6}
36
37
  s.summary = %q{A Simple Reverse Proxy for Rack}
37
38
  s.test_files = [
38
- "test/helper.rb",
39
- "test/test_rack-reverse-proxy.rb"
39
+ "spec/spec_helper.rb",
40
+ "spec/rack/reverse_proxy_spec.rb"
40
41
  ]
41
42
 
42
43
  if s.respond_to? :specification_version then
@@ -44,14 +45,20 @@ Gem::Specification.new do |s|
44
45
  s.specification_version = 3
45
46
 
46
47
  if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
47
- s.add_development_dependency(%q<thoughtbot-shoulda>, [">= 0"])
48
+ s.add_development_dependency(%q<rspec>, [">= 0"])
49
+ s.add_development_dependency(%q<rack-test>, [">= 0"])
50
+ s.add_development_dependency(%q<webmock>, [">= 0"])
48
51
  s.add_runtime_dependency(%q<rack>, [">= 1.0.0"])
49
52
  else
50
- s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
53
+ s.add_dependency(%q<rspec>, [">= 0"])
54
+ s.add_dependency(%q<rack-test>, [">= 0"])
55
+ s.add_dependency(%q<webmock>, [">= 0"])
51
56
  s.add_dependency(%q<rack>, [">= 1.0.0"])
52
57
  end
53
58
  else
54
- s.add_dependency(%q<thoughtbot-shoulda>, [">= 0"])
59
+ s.add_dependency(%q<rspec>, [">= 0"])
60
+ s.add_dependency(%q<rack-test>, [">= 0"])
61
+ s.add_dependency(%q<webmock>, [">= 0"])
55
62
  s.add_dependency(%q<rack>, [">= 1.0.0"])
56
63
  end
57
64
  end
@@ -0,0 +1,125 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Rack::ReverseProxy do
4
+ include Rack::Test::Methods
5
+ include WebMock
6
+
7
+ def app
8
+ Rack::ReverseProxy.new
9
+ end
10
+
11
+ def dummy_app
12
+ lambda { [200, {}, ['Dummy App']] }
13
+ end
14
+
15
+ describe "as middleware" do
16
+ def app
17
+ Rack::ReverseProxy.new(dummy_app) do
18
+ reverse_proxy '/test', 'http://example.com/'
19
+ end
20
+ end
21
+
22
+ it "should forward requests to the calling app when the path is not matched" do
23
+ get '/'
24
+ last_response.body.should == "Dummy App"
25
+ last_response.should be_ok
26
+ end
27
+
28
+ it "should proxy requests when a pattern is matched" do
29
+ stub_request(:get, 'http://example.com/test').to_return({:body => "Proxied App"})
30
+ get '/test'
31
+ last_response.body.should == "Proxied App"
32
+ end
33
+
34
+ describe "with ambiguous routes" do
35
+ def app
36
+ Rack::ReverseProxy.new(dummy_app) do
37
+ reverse_proxy '/test', 'http://example.com/'
38
+ reverse_proxy /^\/test/, 'http://example.com/'
39
+ end
40
+ end
41
+
42
+ it "should throw an exception" do
43
+ lambda { get '/test' }.should raise_error(Rack::AmbiguousProxyMatch)
44
+ end
45
+ end
46
+
47
+ describe "with a route as a regular expression" do
48
+ def app
49
+ Rack::ReverseProxy.new(dummy_app) do
50
+ reverse_proxy %r|^/test(/.*)$|, 'http://example.com$1'
51
+ end
52
+ end
53
+
54
+ it "should support subcaptures" do
55
+ stub_request(:get, 'http://example.com/path').to_return({:body => "Proxied App"})
56
+ get '/test/path'
57
+ last_response.body.should == "Proxied App"
58
+ end
59
+ end
60
+
61
+ describe "with a route as a string" do
62
+ def app
63
+ Rack::ReverseProxy.new(dummy_app) do
64
+ reverse_proxy '/test', 'http://example.com'
65
+ reverse_proxy '/path', 'http://example.com/foo$0'
66
+ end
67
+ end
68
+
69
+ it "should append the full path to the uri" do
70
+ stub_request(:get, 'http://example.com/test/stuff').to_return({:body => "Proxied App"})
71
+ get '/test/stuff'
72
+ last_response.body.should == "Proxied App"
73
+ end
74
+
75
+ end
76
+
77
+ describe "with a generic url" do
78
+ def app
79
+ Rack::ReverseProxy.new(dummy_app) do
80
+ reverse_proxy '/test', 'example.com'
81
+ end
82
+ end
83
+
84
+ it "should throw an exception" do
85
+ lambda{ app }.should raise_error(Rack::GenericProxyURI)
86
+ end
87
+ end
88
+
89
+ describe "with a matching route" do
90
+ def app
91
+ Rack::ReverseProxy.new(dummy_app) do
92
+ reverse_proxy '/test', 'http://example.com/'
93
+ end
94
+ end
95
+
96
+ %w|get head delete put post|.each do |method|
97
+ describe "and using method #{method}" do
98
+ it "should forward the correct request" do
99
+ stub_request(method.to_sym, 'http://example.com/test').to_return({:body => "Proxied App for #{method}"})
100
+ eval "#{method} '/test'"
101
+ last_response.body.should == "Proxied App for #{method}"
102
+ end
103
+
104
+ if %w|put post|.include?(method)
105
+ it "should forward the request payload" do
106
+ pending "valid test with next release of WebMock" do
107
+ stub_request(method.to_sym, 'http://example.com/test').to_return { |req| {:body => req.body} }
108
+ eval "#{method} '/test', {:test => 'test'}"
109
+ last_response.body.should == "test=test"
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+
118
+ describe "as a rack app" do
119
+ it "should respond with 404 when the path is not matched" do
120
+ get '/'
121
+ last_response.should be_not_found
122
+ end
123
+ end
124
+
125
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format specdoc
@@ -0,0 +1,12 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+ require 'rack/reverse_proxy'
4
+ require 'spec'
5
+ require 'spec/autorun'
6
+ require 'rubygems'
7
+ require 'rack/test'
8
+ require 'webmock'
9
+
10
+ Spec::Runner.configure do |config|
11
+ WebMock.disable_net_connect!
12
+ end
metadata CHANGED
@@ -1,7 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-reverse-proxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 2
8
+ - 0
9
+ version: 0.2.0
5
10
  platform: ruby
6
11
  authors:
7
12
  - Jon Swope
@@ -9,29 +14,59 @@ autorequire:
9
14
  bindir: bin
10
15
  cert_chain: []
11
16
 
12
- date: 2010-04-10 00:00:00 -04:00
17
+ date: 2010-04-11 00:00:00 -04:00
13
18
  default_executable:
14
19
  dependencies:
15
20
  - !ruby/object:Gem::Dependency
16
- name: thoughtbot-shoulda
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :development
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: rack-test
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
17
42
  type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
43
+ version_requirements: *id002
44
+ - !ruby/object:Gem::Dependency
45
+ name: webmock
46
+ prerelease: false
47
+ requirement: &id003 !ruby/object:Gem::Requirement
20
48
  requirements:
21
49
  - - ">="
22
50
  - !ruby/object:Gem::Version
51
+ segments:
52
+ - 0
23
53
  version: "0"
24
- version:
54
+ type: :development
55
+ version_requirements: *id003
25
56
  - !ruby/object:Gem::Dependency
26
57
  name: rack
27
- type: :runtime
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
58
+ prerelease: false
59
+ requirement: &id004 !ruby/object:Gem::Requirement
30
60
  requirements:
31
61
  - - ">="
32
62
  - !ruby/object:Gem::Version
63
+ segments:
64
+ - 1
65
+ - 0
66
+ - 0
33
67
  version: 1.0.0
34
- version:
68
+ type: :runtime
69
+ version_requirements: *id004
35
70
  description: A Rack based reverse proxy for basic needs. Useful for testing or in cases where webserver configuration is unavailable.
36
71
  email: jaswope@gmail.com
37
72
  executables: []
@@ -51,8 +86,9 @@ files:
51
86
  - VERSION
52
87
  - lib/rack/reverse_proxy.rb
53
88
  - rack-reverse-proxy.gemspec
54
- - test/helper.rb
55
- - test/test_rack-reverse-proxy.rb
89
+ - spec/rack/reverse_proxy_spec.rb
90
+ - spec/spec.opts
91
+ - spec/spec_helper.rb
56
92
  - README
57
93
  has_rdoc: true
58
94
  homepage: http://github.com/jaswope/rack-reverse-proxy
@@ -67,21 +103,23 @@ required_ruby_version: !ruby/object:Gem::Requirement
67
103
  requirements:
68
104
  - - ">="
69
105
  - !ruby/object:Gem::Version
106
+ segments:
107
+ - 0
70
108
  version: "0"
71
- version:
72
109
  required_rubygems_version: !ruby/object:Gem::Requirement
73
110
  requirements:
74
111
  - - ">="
75
112
  - !ruby/object:Gem::Version
113
+ segments:
114
+ - 0
76
115
  version: "0"
77
- version:
78
116
  requirements: []
79
117
 
80
118
  rubyforge_project:
81
- rubygems_version: 1.3.5
119
+ rubygems_version: 1.3.6
82
120
  signing_key:
83
121
  specification_version: 3
84
122
  summary: A Simple Reverse Proxy for Rack
85
123
  test_files:
86
- - test/helper.rb
87
- - test/test_rack-reverse-proxy.rb
124
+ - spec/spec_helper.rb
125
+ - spec/rack/reverse_proxy_spec.rb
data/test/helper.rb DELETED
@@ -1,10 +0,0 @@
1
- require 'rubygems'
2
- require 'test/unit'
3
- require 'shoulda'
4
-
5
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
- $LOAD_PATH.unshift(File.dirname(__FILE__))
7
- require 'rack/reverse-proxy'
8
-
9
- class Test::Unit::TestCase
10
- end
@@ -1,4 +0,0 @@
1
- require 'helper'
2
-
3
- class TestRackReverseProxy < Test::Unit::TestCase
4
- end