rack-reverse-proxy 0.4.1 → 0.4.3

Sign up to get free protection for your applications and to get access to all the features.
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