rack-reverse-proxy 0.4.1 → 0.4.3

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.
data/README.rdoc CHANGED
@@ -6,6 +6,9 @@ This is a simple reverse proxy for Rack that pretty heavily rips off Rack Forwar
6
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
7
  gem install rack-reverse-proxy
8
8
 
9
+ For your Gemfile use:
10
+ gem "rack-reverse-proxy", :require => "rack/reverse_proxy"
11
+
9
12
  == Usage
10
13
  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 $.
11
14
 
@@ -16,11 +19,14 @@ Below is an example for configuring the middleware:
16
19
  require 'rack/reverse_proxy'
17
20
 
18
21
  use Rack::ReverseProxy do
22
+ # Set :preserve_host to true globally (default is true already)
23
+ reverse_proxy_options :preserve_host => true
24
+
19
25
  # Forward the path /test* to http://example.com/test*
20
26
  reverse_proxy '/test', 'http://example.com/'
21
27
 
22
28
  # Forward the path /foo/* to http://example.com/bar/*
23
- reverse_proxy /^\/foo(\/.*)$/, 'http://example.com/bar$1'
29
+ reverse_proxy /^\/foo(\/.*)$/, 'http://example.com/bar$1', :username => 'name', :password => 'basic_auth_secret'
24
30
  end
25
31
 
26
32
  app = proc do |env|
@@ -28,6 +34,13 @@ Below is an example for configuring the middleware:
28
34
  end
29
35
  run app
30
36
 
37
+ reverse_proxy_options sets global options for all reverse proxies. Available options are:
38
+ * :preserve_host Set to false to omit Host headers
39
+ * :username username for basic auth
40
+ * :password password for basic auth
41
+ * :matching is a global only option, if set to :first the first matched url will be requested (no ambigous error). Default: :all.
42
+ * :timeout seconds to timout the requests
43
+
31
44
  == Note on Patches/Pull Requests
32
45
 
33
46
  * Fork the project.
data/Rakefile CHANGED
@@ -1,25 +1,6 @@
1
1
  require 'rubygems'
2
2
  require 'rake'
3
-
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "rack-reverse-proxy"
8
- gem.summary = %Q{A Simple Reverse Proxy for Rack}
9
- gem.description = %Q{A Rack based reverse proxy for basic needs. Useful for testing or in cases where webserver configuration is unavailable.}
10
- gem.email = "jaswope@gmail.com"
11
- gem.homepage = "http://github.com/jaswope/rack-reverse-proxy"
12
- gem.authors = ["Jon Swope"]
13
- gem.add_development_dependency "rspec", ">= 0"
14
- gem.add_development_dependency "rack-test", ">= 0"
15
- gem.add_development_dependency "webmock", "~> 1.5.0"
16
- gem.add_dependency "rack", ">= 1.0.0"
17
- # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
18
- end
19
- Jeweler::GemcutterTasks.new
20
- rescue LoadError
21
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
22
- end
3
+ require 'bundler/gem_tasks'
23
4
 
24
5
  require 'spec/rake/spectask'
25
6
  Spec::Rake::SpecTask.new(:spec) do |spec|
@@ -33,8 +14,6 @@ Spec::Rake::SpecTask.new(:rcov) do |spec|
33
14
  spec.rcov = true
34
15
  end
35
16
 
36
- task :spec => :check_dependencies
37
-
38
17
  task :default => :spec
39
18
 
40
19
  require 'rake/rdoctask'
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.1
1
+ 0.4.3
@@ -4,39 +4,56 @@ require 'net/https'
4
4
  module Rack
5
5
  class ReverseProxy
6
6
  def initialize(app = nil, &b)
7
- @app = app || lambda { [404, [], []] }
8
- @paths = {}
9
- @opts = {:preserve_host => false}
7
+ @app = app || lambda {|env| [404, [], []] }
8
+ @matchers = []
9
+ @global_options = {:preserve_host => true, :matching => :all, :verify_ssl => true}
10
10
  instance_eval &b if block_given?
11
11
  end
12
12
 
13
13
  def call(env)
14
14
  rackreq = Rack::Request.new(env)
15
- matcher, url = get_matcher_and_url rackreq.fullpath
15
+ matcher = get_matcher rackreq.fullpath
16
16
  return @app.call(env) if matcher.nil?
17
17
 
18
- uri = get_uri(url, matcher, rackreq.fullpath)
18
+ uri = matcher.get_uri(rackreq.fullpath,env)
19
+ all_opts = @global_options.dup.merge(matcher.options)
19
20
  headers = Rack::Utils::HeaderHash.new
20
21
  env.each { |key, value|
21
22
  if key =~ /HTTP_(.*)/
22
23
  headers[$1] = value
23
24
  end
24
25
  }
25
- headers['HOST'] = uri.host if @opts[:preserve_host]
26
+ headers['HOST'] = uri.host if all_opts[:preserve_host]
26
27
 
27
- session = Net::HTTP.new(uri.host, uri.port)
28
- session.use_ssl = (uri.scheme == 'https')
29
- session.verify_mode = OpenSSL::SSL::VERIFY_NONE
30
- session.start { |http|
28
+ session = Net::HTTP.new(uri.host, uri.port)
29
+ session.read_timeout=all_opts[:timeout] if all_opts[:timeout]
30
+
31
+ session.use_ssl = (uri.scheme == 'https')
32
+ if uri.scheme == 'https' && all_opts[:verify_ssl]
33
+ session.verify_mode = OpenSSL::SSL::VERIFY_PEER
34
+ else
35
+ # DO NOT DO THIS IN PRODUCTION !!!
36
+ session.verify_mode = OpenSSL::SSL::VERIFY_NONE
37
+ end
38
+ session.start { |http|
31
39
  m = rackreq.request_method
32
40
  case m
33
41
  when "GET", "HEAD", "DELETE", "OPTIONS", "TRACE"
34
42
  req = Net::HTTP.const_get(m.capitalize).new(uri.request_uri, headers)
35
- req.basic_auth @opts[:username], @opts[:password] if @opts[:username] and @opts[:password]
43
+ req.basic_auth all_opts[:username], all_opts[:password] if all_opts[:username] and all_opts[:password]
36
44
  when "PUT", "POST"
37
45
  req = Net::HTTP.const_get(m.capitalize).new(uri.request_uri, headers)
38
- req.basic_auth @opts[:username], @opts[:password] if @opts[:username] and @opts[:password]
39
- req.content_length = rackreq.body.length
46
+ req.basic_auth all_opts[:username], all_opts[:password] if all_opts[:username] and all_opts[:password]
47
+
48
+ if rackreq.body.respond_to?(:read) && rackreq.body.respond_to?(:rewind)
49
+ body = rackreq.body.read
50
+ req.content_length = body.size
51
+ rackreq.body.rewind
52
+ else
53
+ req.content_length = rackreq.body.size
54
+ end
55
+
56
+ req.content_type = rackreq.content_type unless rackreq.content_type.nil?
40
57
  req.body_stream = rackreq.body
41
58
  else
42
59
  raise "method not supported: #{m}"
@@ -52,20 +69,20 @@ module Rack
52
69
  [res.code, create_response_headers(res), [body]]
53
70
  }
54
71
  end
55
-
72
+
56
73
  private
57
74
 
58
- def get_matcher_and_url path
59
- matches = @paths.select do |matcher, url|
60
- match_path(path, matcher)
75
+ def get_matcher path
76
+ matches = @matchers.select do |matcher|
77
+ matcher.match?(path)
61
78
  end
62
79
 
63
80
  if matches.length < 1
64
81
  nil
65
- elsif matches.length > 1
82
+ elsif matches.length > 1 && @global_options[:matching] != :first
66
83
  raise AmbiguousProxyMatch.new(path, matches)
67
84
  else
68
- matches.first.map{|a| a.dup}
85
+ matches.first
69
86
  end
70
87
  end
71
88
 
@@ -79,27 +96,14 @@ module Rack
79
96
  response_headers
80
97
  end
81
98
 
82
- def match_path(path, matcher)
83
- if matcher.is_a?(Regexp)
84
- path.match(matcher)
85
- else
86
- path.match(/^#{matcher.to_s}/)
87
- end
88
- end
89
99
 
90
- def get_uri(url, matcher, path)
91
- if url =~/\$\d/
92
- match_path(path, matcher).to_a.each_with_index { |m, i| url.gsub!("$#{i.to_s}", m) }
93
- URI(url)
94
- else
95
- URI.join(url, path)
96
- end
100
+ def reverse_proxy_options(options)
101
+ @global_options=options
97
102
  end
98
103
 
99
104
  def reverse_proxy matcher, url, opts={}
100
- raise GenericProxyURI.new(url) if matcher.is_a?(String) && URI(url).class == URI::Generic
101
- @paths.merge!(matcher => url)
102
- @opts.merge!(opts)
105
+ raise GenericProxyURI.new(url) if matcher.is_a?(String) && url.is_a?(String) && URI(url).class == URI::Generic
106
+ @matchers << ReverseProxyMatcher.new(matcher,url,opts)
103
107
  end
104
108
  end
105
109
 
@@ -129,8 +133,41 @@ module Rack
129
133
  private
130
134
 
131
135
  def formatted_matches
132
- matches.map {|m| %Q("#{m[0].to_s}" => "#{m[1]}")}.join(', ')
136
+ matches.map {|matcher| matcher.to_s}.join(', ')
133
137
  end
134
138
  end
135
139
 
140
+ class ReverseProxyMatcher
141
+ def initialize(matching,url,options)
142
+ @matching=matching
143
+ @url=url
144
+ @options=options
145
+ @matching_regexp= matching.kind_of?(Regexp) ? matching : /^#{matching.to_s}/
146
+ end
147
+
148
+ attr_reader :matching,:matching_regexp,:url,:options
149
+
150
+ def match?(path)
151
+ match_path(path) ? true : false
152
+ end
153
+
154
+ def get_uri(path,env)
155
+ _url=(url.respond_to?(:call) ? url.call(env) : url)
156
+ if _url =~/\$\d/
157
+ match_path(path).to_a.each_with_index { |m, i| _url.gsub!("$#{i.to_s}", m) }
158
+ URI(_url)
159
+ else
160
+ URI.join(_url, path)
161
+ end
162
+ end
163
+ def to_s
164
+ %Q("#{matching.to_s}" => "#{url}")
165
+ end
166
+ private
167
+ def match_path(path)
168
+ path.match(matching_regexp)
169
+ end
170
+
171
+
172
+ end
136
173
  end
@@ -1,11 +1,6 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
- # -*- encoding: utf-8 -*-
5
-
6
1
  Gem::Specification.new do |s|
7
2
  s.name = %q{rack-reverse-proxy}
8
- s.version = "0.4.1"
3
+ s.version = "0.4.3"
9
4
 
10
5
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
6
  s.authors = ["Jon Swope"]
@@ -37,26 +32,11 @@ Gem::Specification.new do |s|
37
32
  "spec/spec_helper.rb"
38
33
  ]
39
34
 
40
- if s.respond_to? :specification_version then
41
- current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
42
- s.specification_version = 3
43
-
44
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
45
- s.add_development_dependency(%q<rspec>, [">= 0"])
46
- s.add_development_dependency(%q<rack-test>, [">= 0"])
47
- s.add_development_dependency(%q<webmock>, ["~> 1.5.0"])
48
- s.add_runtime_dependency(%q<rack>, [">= 1.0.0"])
49
- else
50
- s.add_dependency(%q<rspec>, [">= 0"])
51
- s.add_dependency(%q<rack-test>, [">= 0"])
52
- s.add_dependency(%q<webmock>, ["~> 1.5.0"])
53
- s.add_dependency(%q<rack>, [">= 1.0.0"])
54
- end
55
- else
56
- s.add_dependency(%q<rspec>, [">= 0"])
57
- s.add_dependency(%q<rack-test>, [">= 0"])
58
- s.add_dependency(%q<webmock>, ["~> 1.5.0"])
59
- s.add_dependency(%q<rack>, [">= 1.0.0"])
60
- end
35
+ s.add_development_dependency "rspec", "~> 1.3.2"
36
+ s.add_development_dependency "bundler", "~> 1.0.15"
37
+ s.add_development_dependency "rake", "~> 0.8.7"
38
+ s.add_development_dependency "rack-test", "~> 0.5.7"
39
+ s.add_development_dependency "webmock", "~> 1.5.0"
40
+ s.add_dependency "rack", ">= 1.0.0"
61
41
  end
62
42
 
@@ -9,13 +9,14 @@ describe Rack::ReverseProxy do
9
9
  end
10
10
 
11
11
  def dummy_app
12
- lambda { [200, {}, ['Dummy App']] }
12
+ lambda { |env| [200, {}, ['Dummy App']] }
13
13
  end
14
14
 
15
15
  describe "as middleware" do
16
16
  def app
17
17
  Rack::ReverseProxy.new(dummy_app) do
18
18
  reverse_proxy '/test', 'http://example.com/', {:preserve_host => true}
19
+ reverse_proxy '/2test', lambda{ |env| 'http://example.com/'}
19
20
  end
20
21
  end
21
22
 
@@ -31,34 +32,41 @@ describe Rack::ReverseProxy do
31
32
  last_response.body.should == "Proxied App"
32
33
  end
33
34
 
34
- it "the response header should never contain Status" do
35
- stub_request(:any, 'example.com/test/stuff').to_return(:headers => {'Status' => '200 OK'})
36
- get '/test/stuff'
37
- last_response.headers['Status'].should == nil
38
- end
35
+ it "should proxy requests to a lambda url when a pattern is matched" do
36
+ stub_request(:get, 'http://example.com/2test').to_return({:body => "Proxied App2"})
37
+ get '/2test'
38
+ last_response.body.should == "Proxied App2"
39
+ end
39
40
 
40
- it "the response header should never transfer-encoding" do
41
- stub_request(:any, 'example.com/test/stuff').to_return(:headers => {'transfer-encoding' => 'Chunked'})
42
- get '/test/stuff'
43
- last_response.headers['transfer-encoding'].should == nil
44
- end
41
+ it "the response header should never contain Status" do
42
+ stub_request(:any, 'example.com/test/stuff').to_return(:headers => {'Status' => '200 OK'})
43
+ get '/test/stuff'
44
+ last_response.headers['Status'].should == nil
45
+ end
45
46
 
46
- it "should set the Host header" do
47
- stub_request(:any, 'example.com/test/stuff')
48
- get '/test/stuff'
49
- a_request(:get, 'http://example.com/test/stuff').with(:headers => {"Host" => "example.com"}).should have_been_made
50
- end
47
+ it "the response header should never transfer-encoding" do
48
+ stub_request(:any, 'example.com/test/stuff').to_return(:headers => {'transfer-encoding' => 'Chunked'})
49
+ get '/test/stuff'
50
+ last_response.headers['transfer-encoding'].should == nil
51
+ end
52
+
53
+ it "should set the Host header" do
54
+ stub_request(:any, 'example.com/test/stuff')
55
+ get '/test/stuff'
56
+ a_request(:get, 'http://example.com/test/stuff').with(:headers => {"Host" => "example.com"}).should have_been_made
57
+ end
51
58
 
52
59
  describe "with preserve host turned off" do
53
60
  def app
54
61
  Rack::ReverseProxy.new(dummy_app) do
55
- reverse_proxy '/test', 'http://example.com/'
62
+ reverse_proxy '/test', 'http://example.com/', {:preserve_host => false}
56
63
  end
57
64
  end
58
65
 
59
66
  it "should not set the Host header" do
60
67
  stub_request(:any, 'example.com/test/stuff')
61
68
  get '/test/stuff'
69
+ a_request(:get, 'http://example.com/test/stuff').with(:headers => {"Host" => "example.com"}).should_not have_been_made
62
70
  a_request(:get, 'http://example.com/test/stuff').should have_been_made
63
71
  end
64
72
  end
@@ -77,9 +85,10 @@ describe Rack::ReverseProxy do
77
85
  end
78
86
  end
79
87
 
80
- describe "with ambiguous routes" do
88
+ describe "with ambiguous routes and all matching" do
81
89
  def app
82
90
  Rack::ReverseProxy.new(dummy_app) do
91
+ reverse_proxy_options :matching => :all
83
92
  reverse_proxy '/test', 'http://example.com/'
84
93
  reverse_proxy /^\/test/, 'http://example.com/'
85
94
  end
@@ -90,6 +99,22 @@ describe Rack::ReverseProxy do
90
99
  end
91
100
  end
92
101
 
102
+ describe "with ambiguous routes and first matching" do
103
+ def app
104
+ Rack::ReverseProxy.new(dummy_app) do
105
+ reverse_proxy_options :matching => :first
106
+ reverse_proxy '/test', 'http://example1.com/'
107
+ reverse_proxy /^\/test/, 'http://example2.com/'
108
+ end
109
+ end
110
+
111
+ it "should throw an exception" do
112
+ stub_request(:get, 'http://example1.com/test').to_return({:body => "Proxied App"})
113
+ get '/test'
114
+ last_response.body.should == "Proxied App"
115
+ end
116
+ end
117
+
93
118
  describe "with a route as a regular expression" do
94
119
  def app
95
120
  Rack::ReverseProxy.new(dummy_app) do
@@ -104,20 +129,20 @@ describe Rack::ReverseProxy do
104
129
  end
105
130
  end
106
131
 
107
- describe "with a https route" do
132
+ describe "with a https route" do
108
133
  def app
109
134
  Rack::ReverseProxy.new(dummy_app) do
110
135
  reverse_proxy '/test', 'https://example.com'
111
136
  end
112
137
  end
113
138
 
114
- it "should make a secure request" do
139
+ it "should make a secure request" do
115
140
  stub_request(:get, 'https://example.com/test/stuff').to_return({:body => "Proxied Secure App"})
116
141
  get '/test/stuff'
117
142
  last_response.body.should == "Proxied Secure App"
118
- end
143
+ end
119
144
 
120
- end
145
+ end
121
146
 
122
147
  describe "with a route as a string" do
123
148
  def app
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-reverse-proxy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.3
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,34 +9,55 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2011-04-04 00:00:00.000000000 -04:00
13
- default_executable:
12
+ date: 2011-04-04 00:00:00.000000000Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: rspec
17
- requirement: &20805780 !ruby/object:Gem::Requirement
16
+ requirement: &23654620 !ruby/object:Gem::Requirement
18
17
  none: false
19
18
  requirements:
20
- - - ! '>='
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.3.2
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *23654620
25
+ - !ruby/object:Gem::Dependency
26
+ name: bundler
27
+ requirement: &23653440 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
21
31
  - !ruby/object:Gem::Version
22
- version: '0'
32
+ version: 1.0.15
23
33
  type: :development
24
34
  prerelease: false
25
- version_requirements: *20805780
35
+ version_requirements: *23653440
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &23652760 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 0.8.7
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *23652760
26
47
  - !ruby/object:Gem::Dependency
27
48
  name: rack-test
28
- requirement: &20805260 !ruby/object:Gem::Requirement
49
+ requirement: &23652060 !ruby/object:Gem::Requirement
29
50
  none: false
30
51
  requirements:
31
- - - ! '>='
52
+ - - ~>
32
53
  - !ruby/object:Gem::Version
33
- version: '0'
54
+ version: 0.5.7
34
55
  type: :development
35
56
  prerelease: false
36
- version_requirements: *20805260
57
+ version_requirements: *23652060
37
58
  - !ruby/object:Gem::Dependency
38
59
  name: webmock
39
- requirement: &20804660 !ruby/object:Gem::Requirement
60
+ requirement: &23650540 !ruby/object:Gem::Requirement
40
61
  none: false
41
62
  requirements:
42
63
  - - ~>
@@ -44,10 +65,10 @@ dependencies:
44
65
  version: 1.5.0
45
66
  type: :development
46
67
  prerelease: false
47
- version_requirements: *20804660
68
+ version_requirements: *23650540
48
69
  - !ruby/object:Gem::Dependency
49
70
  name: rack
50
- requirement: &20804080 !ruby/object:Gem::Requirement
71
+ requirement: &23649320 !ruby/object:Gem::Requirement
51
72
  none: false
52
73
  requirements:
53
74
  - - ! '>='
@@ -55,7 +76,7 @@ dependencies:
55
76
  version: 1.0.0
56
77
  type: :runtime
57
78
  prerelease: false
58
- version_requirements: *20804080
79
+ version_requirements: *23649320
59
80
  description: A Rack based reverse proxy for basic needs. Useful for testing or in
60
81
  cases where webserver configuration is unavailable.
61
82
  email: jaswope@gmail.com
@@ -75,7 +96,6 @@ files:
75
96
  - spec/rack/reverse_proxy_spec.rb
76
97
  - spec/spec.opts
77
98
  - spec/spec_helper.rb
78
- has_rdoc: true
79
99
  homepage: http://github.com/jaswope/rack-reverse-proxy
80
100
  licenses: []
81
101
  post_install_message:
@@ -96,7 +116,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
96
116
  version: '0'
97
117
  requirements: []
98
118
  rubyforge_project:
99
- rubygems_version: 1.6.1
119
+ rubygems_version: 1.8.10
100
120
  signing_key:
101
121
  specification_version: 3
102
122
  summary: A Simple Reverse Proxy for Rack