rack-reverse-proxy 0.9.1 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 19ce368e32c93e8d11b28dd20f7c6ff5ac840988
4
- data.tar.gz: 2c82dc8ff6c2dd5bd07767058af7e2c97b6b49d6
3
+ metadata.gz: ff26702f215fd146c4a043bc583a058f7102001b
4
+ data.tar.gz: c451349b33d1715e57ce4da1652bba7f4a7b9192
5
5
  SHA512:
6
- metadata.gz: 484743f8df4f78ce6ea52fcda1ff2ca49abf9ad1f0ed0b89532db9ec2a6486c6ea1185fd658a657750f24515cc46335609c33bd4c2d07f03a68d14078ffbb7f6
7
- data.tar.gz: ea395476254d838bd1a495937970f3005c0ae59260d0f07b009cfe97d4aabd97fb13b75e58e00b738dd5799bd116a526825fcd2c25efd008732d7293aeaee0c9
6
+ metadata.gz: 0916fd37a7f4f5c4a63d3a51d01ad07f60c6a687fa45db392f1816dcb63dd82608b466f6451b7a169f7c66b252dceb697e0e0bdd8c86cad4a7fa145ce9895a4a
7
+ data.tar.gz: fb4a7b6158ffb9ae52c6689d0e71f95fdcbb1b0a1e131e7d3e5c7c2677096121af205b04be922d1521a075a35ae5ab9ba8ae9875ffab3aece726763a3b27a50c
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/examples.txt
10
+ /tmp/
11
+ *.bundle
12
+ *.so
13
+ *.o
14
+ *.a
15
+ mkmf.log
16
+ tests.lock
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,21 @@
1
+ Style/StringLiterals:
2
+ EnforcedStyle: double_quotes
3
+
4
+ Metrics/LineLength:
5
+ Max: 100
6
+
7
+ #begin ruby 1.8 support
8
+ Style/HashSyntax:
9
+ EnforcedStyle: hash_rockets
10
+
11
+ Style/Lambda:
12
+ Enabled: false
13
+
14
+ Style/TrailingCommaInLiteral:
15
+ EnforcedStyleForMultiline: no_comma
16
+
17
+ Style/TrailingCommaInArguments:
18
+ EnforcedStyleForMultiline: no_comma
19
+
20
+ Style/EachWithObject:
21
+ Enabled: false
@@ -0,0 +1,19 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - 2.0.0
6
+ - 2.1
7
+ - 2.2
8
+ - jruby-18mode
9
+ - jruby-19mode
10
+ - rbx
11
+
12
+ before_install:
13
+ - gem install bundler
14
+
15
+ bundler_args: --without development
16
+
17
+ script:
18
+ - bundle exec rspec
19
+ - script/rubocop
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## 0.10.0
4
+
5
+ - Feature: `options[:verify_mode]` to set SSL verification mode. - [Marv Cool](https://github.com/MrMarvin) [#24](https://github.com/waterlink/rack-reverse-proxy/pull/24) and [#25](https://github.com/waterlink/rack-reverse-proxy/pull/25)
6
+
7
+ ## 0.9.1
8
+
9
+ - Enhancement: Remove `Status` key from response headers as per Rack protocol (see [rack/lint](https://github.com/rack/rack/blob/master/lib/rack/lint.rb#L639)) - [Jan Raasch](https://github.com/janraasch) [#7](https://github.com/waterlink/rack-reverse-proxy/pull/7)
10
+
11
+ ## 0.9.0
12
+
13
+ - Bugfix: Timeout option matches the documentation - [Paul Hepworth](https://github.com/peppyheppy)
14
+ - Ruby 1.8 compatibility - [anujdas](https://github.com/anujdas)
15
+ - Bugfix: Omit port in host header for default ports (80, 443), so that it doesn't break some web servers, like "Apache Coyote" - [Peter Suschlik](https://github.com/splattael)
16
+ - Bugfix: Don't drop source request's port in response's location header - [Eric Koslow](https://github.com/ekosz)
17
+ - Bugfix: Capitalize headers correctly to prevent duplicate headers when used together with other proxies - [Eric Koslow](https://github.com/ekosz)
18
+ - Bugfix: Normalize headers from HttpStreamingResponse in order not to break other middlewares - [Jan Raasch](https://github.com/janraasch)
data/Gemfile ADDED
@@ -0,0 +1,20 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ ruby_version = RUBY_VERSION.to_f
6
+ rubocop_platform = [:ruby_20, :ruby_21, :ruby_22]
7
+ rubocop_platform = [:ruby_20, :ruby_21] if ruby_version < 2.0
8
+
9
+ group :test do
10
+ gem "rspec"
11
+ gem "rack-test"
12
+ gem "webmock"
13
+ gem "rubocop", :platform => rubocop_platform
14
+
15
+ gem "addressable", "< 2.4" if ruby_version < 1.9
16
+ end
17
+
18
+ group :development, :test do
19
+ gem "simplecov"
20
+ end
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  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.
5
5
 
6
6
  ## Installation
7
- The gem is available on gemcutter. Assuming you have a recent version of Rubygems you should just be able to install it via:
7
+ The gem is available on rubygems. Assuming you have a recent version of Rubygems you should just be able to install it via:
8
8
 
9
9
  ```
10
10
  gem install rack-reverse-proxy
@@ -17,9 +17,10 @@ gem "rack-reverse-proxy", require: "rack/reverse_proxy"
17
17
  ```
18
18
 
19
19
  ## Usage
20
- 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 `$`.
21
20
 
22
- 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.
21
+ Rules 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 `$`.
22
+
23
+ Right now if more than one rule 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.
23
24
 
24
25
  Below is an example for configuring the middleware:
25
26
 
@@ -43,12 +44,37 @@ end
43
44
  run app
44
45
  ```
45
46
 
46
- reverse_proxy_options sets global options for all reverse proxies. Available options are:
47
+ `reverse_proxy_options` sets global options for all reverse proxies. Available options are:
48
+
47
49
  * `:preserve_host` Set to false to omit Host headers
48
50
  * `:username` username for basic auth
49
51
  * `:password` password for basic auth
50
52
  * `:matching` is a global only option, if set to :first the first matched url will be requested (no ambigous error). Default: :all.
51
53
  * `:timeout` seconds to timout the requests
54
+ * `:force_ssl` redirects to ssl version, if not already using it (requires `:replace_response_host`). Default: false.
55
+ * `:verify_mode` the `OpenSSL::SSL` verify mode passed to Net::HTTP. Default: `OpenSSL::SSL::VERIFY_PEER`.
56
+
57
+ ### Sample usage in a Ruby on Rails app
58
+
59
+ Rails 3 or less:
60
+
61
+ ```ruby
62
+ # config/application.rb
63
+ config.middleware.insert_before(Rack::Lock, Rack::ReverseProxy) do
64
+ reverse_proxy_options preserve_host: true
65
+ reverse_proxy '/wiki', 'http://wiki.example.com/'
66
+ end
67
+ ```
68
+
69
+ Rails 4+ or if you use `config.threadsafe`, you'll need to `insert_before(Rack::Runtime, Rack::ReverseProxy)` as `Rack::Lock` does not exist when `config.allow_concurrency == true`:
70
+
71
+ ```ruby
72
+ # config/application.rb
73
+ config.middleware.insert_before(Rack::Runtime, Rack::ReverseProxy) do
74
+ reverse_proxy_options preserve_host: true
75
+ reverse_proxy '/wiki', 'http://wiki.example.com/'
76
+ end
77
+ ```
52
78
 
53
79
  ## Note on Patches/Pull Requests
54
80
  * Fork the project.
@@ -0,0 +1,10 @@
1
+ require "rubygems"
2
+ require "rake"
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec) do |spec|
7
+ spec.pattern = "spec/**/*_spec.rb"
8
+ end
9
+
10
+ task :default => :spec
@@ -1,131 +1,6 @@
1
- require 'net/http'
2
- require 'net/https'
3
- require "rack-proxy"
4
- require "rack/reverse_proxy_matcher"
5
- require "rack/exception"
6
- require "rack/reverse_proxy/http_streaming_response"
1
+ require "rack_reverse_proxy"
7
2
 
3
+ # Re-opening Rack module only to define ReverseProxy constant
8
4
  module Rack
9
- class ReverseProxy
10
- include NewRelic::Agent::Instrumentation::ControllerInstrumentation if defined? NewRelic
11
-
12
- def initialize(app = nil, &b)
13
- @app = app || lambda {|env| [404, [], []] }
14
- @matchers = []
15
- @global_options = {:preserve_host => true, :x_forwarded_host => true, :matching => :all, :replace_response_host => false}
16
- instance_eval(&b) if block_given?
17
- end
18
-
19
- def call(env)
20
- rackreq = Rack::Request.new(env)
21
- matcher = get_matcher(rackreq.fullpath, Proxy.extract_http_request_headers(rackreq.env), rackreq)
22
- return @app.call(env) if matcher.nil?
23
-
24
- if @global_options[:newrelic_instrumentation]
25
- action_name = "#{rackreq.path.gsub(/\/\d+/,'/:id').gsub(/^\//,'')}/#{rackreq.request_method}" # Rack::ReverseProxy/foo/bar#GET
26
- perform_action_with_newrelic_trace(:name => action_name, :request => rackreq) do
27
- proxy(env, rackreq, matcher)
28
- end
29
- else
30
- proxy(env, rackreq, matcher)
31
- end
32
- end
33
-
34
- private
35
-
36
- def proxy(env, source_request, matcher)
37
- uri = matcher.get_uri(source_request.fullpath,env)
38
- if uri.nil?
39
- return @app.call(env)
40
- end
41
- options = @global_options.dup.merge(matcher.options)
42
-
43
- # Initialize request
44
- target_request = Net::HTTP.const_get(source_request.request_method.capitalize).new(uri.request_uri)
45
-
46
- # Setup headers
47
- target_request_headers = Proxy.extract_http_request_headers(source_request.env)
48
-
49
- if options[:preserve_host]
50
- if uri.port == uri.default_port
51
- target_request_headers['HOST'] = uri.host
52
- else
53
- target_request_headers['HOST'] = "#{uri.host}:#{uri.port}"
54
- end
55
- end
56
-
57
- if options[:x_forwarded_host]
58
- target_request_headers['X-Forwarded-Host'] = source_request.host
59
- target_request_headers['X-Forwarded-Port'] = "#{source_request.port}"
60
- end
61
-
62
- target_request.initialize_http_header(target_request_headers)
63
-
64
- # Basic auth
65
- target_request.basic_auth options[:username], options[:password] if options[:username] and options[:password]
66
-
67
- # Setup body
68
- if target_request.request_body_permitted? && source_request.body
69
- source_request.body.rewind
70
- target_request.body_stream = source_request.body
71
- end
72
-
73
- target_request.content_length = source_request.content_length || 0
74
- target_request.content_type = source_request.content_type if source_request.content_type
75
-
76
- # Create a streaming response (the actual network communication is deferred, a.k.a. streamed)
77
- target_response = HttpStreamingResponse.new(target_request, uri.host, uri.port)
78
-
79
- # pass the timeout configuration through
80
- target_response.set_read_timeout(options[:timeout]) if options[:timeout].to_i > 0
81
-
82
- target_response.use_ssl = "https" == uri.scheme
83
-
84
- # Let rack set the transfer-encoding header
85
- response_headers = Rack::Utils::HeaderHash.new Proxy.normalize_headers(format_headers(target_response.headers))
86
- response_headers.delete('Transfer-Encoding')
87
- response_headers.delete('Status')
88
-
89
- # Replace the location header with the proxy domain
90
- if response_headers['Location'] && options[:replace_response_host]
91
- response_location = URI(response_headers['location'])
92
- response_location.host = source_request.host
93
- response_location.port = source_request.port
94
- response_headers['Location'] = response_location.to_s
95
- end
96
-
97
- [target_response.status, response_headers, target_response.body]
98
- end
99
-
100
- def get_matcher(path, headers, rackreq)
101
- matches = @matchers.select do |matcher|
102
- matcher.match?(path, headers, rackreq)
103
- end
104
-
105
- if matches.length < 1
106
- nil
107
- elsif matches.length > 1 && @global_options[:matching] != :first
108
- raise AmbiguousProxyMatch.new(path, matches)
109
- else
110
- matches.first
111
- end
112
- end
113
-
114
- def reverse_proxy_options(options)
115
- @global_options=options
116
- end
117
-
118
- def reverse_proxy(matcher, url=nil, opts={})
119
- raise GenericProxyURI.new(url) if matcher.is_a?(String) && url.is_a?(String) && URI(url).class == URI::Generic
120
- @matchers << ReverseProxyMatcher.new(matcher,url,opts)
121
- end
122
-
123
- def format_headers(headers)
124
- headers.reduce({}) do |acc, (key, val)|
125
- formated_key = key.split('-').map(&:capitalize).join('-')
126
- acc[formated_key] = Array(val)
127
- acc
128
- end
129
- end
130
- end
5
+ ReverseProxy = RackReverseProxy::Middleware
131
6
  end
@@ -0,0 +1,8 @@
1
+ require "rack_reverse_proxy/version"
2
+ require "rack_reverse_proxy/errors"
3
+ require "rack_reverse_proxy/rule"
4
+ require "rack_reverse_proxy/middleware"
5
+
6
+ # A Reverse Proxy for Rack
7
+ module RackReverseProxy
8
+ end
@@ -0,0 +1,36 @@
1
+ module RackReverseProxy
2
+ module Errors
3
+ # GenericURI indicates that url is too generic
4
+ class GenericURI < Exception
5
+ attr_reader :url
6
+
7
+ def intialize(url)
8
+ @url = url
9
+ end
10
+
11
+ def to_s
12
+ %(Your URL "#{@url}" is too generic. Did you mean "http://#{@url}"?)
13
+ end
14
+ end
15
+
16
+ # AmbiguousMatch indicates that path matched more than one endpoint
17
+ class AmbiguousMatch < Exception
18
+ attr_reader :path, :matches
19
+
20
+ def initialize(path, matches)
21
+ @path = path
22
+ @matches = matches
23
+ end
24
+
25
+ def to_s
26
+ %(Path "#{path}" matched multiple endpoints: #{formatted_matches})
27
+ end
28
+
29
+ private
30
+
31
+ def formatted_matches
32
+ matches.map(&:to_s).join(", ")
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,40 @@
1
+ require "net/http"
2
+ require "net/https"
3
+ require "rack-proxy"
4
+ require "rack_reverse_proxy/roundtrip"
5
+
6
+ module RackReverseProxy
7
+ # Rack middleware for handling reverse proxying
8
+ class Middleware
9
+ include NewRelic::Agent::Instrumentation::ControllerInstrumentation if defined? NewRelic
10
+
11
+ def initialize(app = nil, &b)
12
+ @app = app || lambda { |_| [404, [], []] }
13
+ @rules = []
14
+ @global_options = {
15
+ :preserve_host => true,
16
+ :x_forwarded_host => true,
17
+ :matching => :all,
18
+ :replace_response_host => false
19
+ }
20
+ instance_eval(&b) if block_given?
21
+ end
22
+
23
+ def call(env)
24
+ RoundTrip.new(@app, env, @global_options, @rules).call
25
+ end
26
+
27
+ private
28
+
29
+ def reverse_proxy_options(options)
30
+ @global_options = options
31
+ end
32
+
33
+ def reverse_proxy(rule, url = nil, opts = {})
34
+ if rule.is_a?(String) && url.is_a?(String) && URI(url).class == URI::Generic
35
+ raise Errors::GenericURI.new, url
36
+ end
37
+ @rules << Rule.new(rule, url, opts)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,60 @@
1
+ module RackReverseProxy
2
+ # ResponseBuilder knows target response building process
3
+ class ResponseBuilder
4
+ def initialize(target_request, uri, options)
5
+ @target_request = target_request
6
+ @uri = uri
7
+ @options = options
8
+ end
9
+
10
+ def fetch
11
+ setup_response
12
+ target_response
13
+ end
14
+
15
+ private
16
+
17
+ def setup_response
18
+ set_read_timeout
19
+ handle_https
20
+ handle_verify_mode
21
+ end
22
+
23
+ def set_read_timeout
24
+ return unless read_timeout?
25
+ target_response.read_timeout = options[:timeout]
26
+ end
27
+
28
+ def read_timeout?
29
+ options[:timeout].to_i > 0
30
+ end
31
+
32
+ def handle_https
33
+ return unless https?
34
+ target_response.use_ssl = true
35
+ end
36
+
37
+ def https?
38
+ "https" == uri.scheme
39
+ end
40
+
41
+ def handle_verify_mode
42
+ return unless verify_mode?
43
+ target_response.verify_mode = options[:verify_mode]
44
+ end
45
+
46
+ def verify_mode?
47
+ options.key?(:verify_mode)
48
+ end
49
+
50
+ def target_response
51
+ @_target_response ||= Rack::HttpStreamingResponse.new(
52
+ target_request,
53
+ uri.host,
54
+ uri.port
55
+ )
56
+ end
57
+
58
+ attr_reader :target_request, :uri, :options
59
+ end
60
+ end