rack-reverse-proxy 0.4.4 → 0.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +64 -0
- data/lib/rack/exception.rb +31 -0
- data/lib/rack/reverse_proxy.rb +84 -124
- data/lib/rack/reverse_proxy_matcher.rb +53 -0
- metadata +100 -58
- data/.document +0 -5
- data/README.rdoc +0 -56
- data/Rakefile +0 -27
- data/VERSION +0 -1
- data/rack-reverse-proxy.gemspec +0 -42
- data/spec/rack/reverse_proxy_spec.rb +0 -209
- data/spec/spec.opts +0 -2
- data/spec/spec_helper.rb +0 -13
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d51045cd47dd0cee3f5afb010a2915f9dd6367ba
|
4
|
+
data.tar.gz: c675c804802eb806d6fca45b471630ed0d980268
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dada6c70259a95d63705ffad9ad59f54d492b721b98d2496322ba33cec38d1005a778830091dfe44c2f5a2b23a9d4b91ff002d761f90a93a37dc32f4de38c7ad
|
7
|
+
data.tar.gz: 7aa447d71a827b37b3a53818723b426c8c0135b90b7c7dcdc2b850dcd1717531e5dddd99afc82ffdcee57f6ebc4a761bb4fba320529f793ad239e21b08a99c3b
|
data/README.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# A Reverse Proxy for Rack
|
2
|
+
[![TravisCI](https://secure.travis-ci.org/pex/rack-reverse-proxy.png "Build Status")](http://travis-ci.org/pex/rack-reverse-proxy "Build Status")
|
3
|
+
|
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
|
+
|
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:
|
8
|
+
|
9
|
+
```
|
10
|
+
gem install rack-reverse-proxy
|
11
|
+
```
|
12
|
+
|
13
|
+
For your Gemfile use:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem "rack-reverse-proxy", require: "rack/reverse_proxy"
|
17
|
+
```
|
18
|
+
|
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
|
+
|
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.
|
23
|
+
|
24
|
+
Below is an example for configuring the middleware:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'rack/reverse_proxy'
|
28
|
+
|
29
|
+
use Rack::ReverseProxy do
|
30
|
+
# Set :preserve_host to true globally (default is true already)
|
31
|
+
reverse_proxy_options preserve_host: true
|
32
|
+
|
33
|
+
# Forward the path /test* to http://example.com/test*
|
34
|
+
reverse_proxy '/test', 'http://example.com/'
|
35
|
+
|
36
|
+
# Forward the path /foo/* to http://example.com/bar/*
|
37
|
+
reverse_proxy /^\/foo(\/.*)$/, 'http://example.com/bar$1', username: 'name', password: 'basic_auth_secret'
|
38
|
+
end
|
39
|
+
|
40
|
+
app = proc do |env|
|
41
|
+
[ 200, {'Content-Type' => 'text/plain'}, "b" ]
|
42
|
+
end
|
43
|
+
run app
|
44
|
+
```
|
45
|
+
|
46
|
+
reverse_proxy_options sets global options for all reverse proxies. Available options are:
|
47
|
+
* `:preserve_host` Set to false to omit Host headers
|
48
|
+
* `:username` username for basic auth
|
49
|
+
* `:password` password for basic auth
|
50
|
+
* `:matching` is a global only option, if set to :first the first matched url will be requested (no ambigous error). Default: :all.
|
51
|
+
* `:timeout` seconds to timout the requests
|
52
|
+
|
53
|
+
## Note on Patches/Pull Requests
|
54
|
+
* Fork the project.
|
55
|
+
* Make your feature addition or bug fix.
|
56
|
+
* Add tests for it. This is important so I don't break it in a
|
57
|
+
future version unintentionally.
|
58
|
+
* Commit, do not mess with rakefile, version, or history.
|
59
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
60
|
+
* Send me a pull request. Bonus points for topic branches.
|
61
|
+
|
62
|
+
## Copyright
|
63
|
+
|
64
|
+
Copyright (c) 2010 Jon Swope. See LICENSE for details.
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Rack
|
2
|
+
class GenericProxyURI < Exception
|
3
|
+
attr_reader :url
|
4
|
+
|
5
|
+
def intialize(url)
|
6
|
+
@url = url
|
7
|
+
end
|
8
|
+
|
9
|
+
def to_s
|
10
|
+
%Q(Your URL "#{@url}" is too generic. Did you mean "http://#{@url}"?)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class AmbiguousProxyMatch < Exception
|
15
|
+
attr_reader :path, :matches
|
16
|
+
def initialize(path, matches)
|
17
|
+
@path = path
|
18
|
+
@matches = matches
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
%Q(Path "#{path}" matched multiple endpoints: #{formatted_matches})
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def formatted_matches
|
28
|
+
matches.map {|matcher| matcher.to_s}.join(', ')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/lib/rack/reverse_proxy.rb
CHANGED
@@ -1,173 +1,133 @@
|
|
1
1
|
require 'net/http'
|
2
2
|
require 'net/https'
|
3
|
+
require "rack-proxy"
|
4
|
+
require "rack/reverse_proxy_matcher"
|
5
|
+
require "rack/exception"
|
3
6
|
|
4
7
|
module Rack
|
5
8
|
class ReverseProxy
|
9
|
+
include NewRelic::Agent::Instrumentation::ControllerInstrumentation if defined? NewRelic
|
10
|
+
|
6
11
|
def initialize(app = nil, &b)
|
7
12
|
@app = app || lambda {|env| [404, [], []] }
|
8
13
|
@matchers = []
|
9
|
-
@global_options = {:preserve_host => true, :matching => :all, :
|
14
|
+
@global_options = {:preserve_host => true, :x_forwarded_host => true, :matching => :all, :replace_response_host => false}
|
10
15
|
instance_eval &b if block_given?
|
11
16
|
end
|
12
17
|
|
13
18
|
def call(env)
|
14
19
|
rackreq = Rack::Request.new(env)
|
15
|
-
matcher = get_matcher rackreq.
|
20
|
+
matcher = get_matcher(rackreq.fullpath, extract_http_request_headers(rackreq.env), rackreq)
|
16
21
|
return @app.call(env) if matcher.nil?
|
17
22
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
if key =~ /HTTP_(.*)/
|
23
|
-
headers[$1] = value
|
23
|
+
if @global_options[:newrelic_instrumentation]
|
24
|
+
action_name = "#{rackreq.path.gsub(/\/\d+/,'/:id').gsub(/^\//,'')}/#{rackreq.request_method}" # Rack::ReverseProxy/foo/bar#GET
|
25
|
+
perform_action_with_newrelic_trace(:name => action_name, :request => rackreq) do
|
26
|
+
proxy(env, rackreq, matcher)
|
24
27
|
end
|
25
|
-
}
|
26
|
-
headers['HOST'] = uri.host if all_opts[:preserve_host]
|
27
|
-
|
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
28
|
else
|
35
|
-
|
36
|
-
session.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
29
|
+
proxy(env, rackreq, matcher)
|
37
30
|
end
|
38
|
-
session.start { |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.request_uri, headers)
|
43
|
-
req.basic_auth all_opts[:username], all_opts[:password] if all_opts[:username] and all_opts[:password]
|
44
|
-
when "PUT", "POST"
|
45
|
-
req = Net::HTTP.const_get(m.capitalize).new(uri.request_uri, headers)
|
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?
|
57
|
-
req.body_stream = rackreq.body
|
58
|
-
else
|
59
|
-
raise "method not supported: #{m}"
|
60
|
-
end
|
61
|
-
|
62
|
-
body = ''
|
63
|
-
res = http.request(req) do |res|
|
64
|
-
res.read_body do |segment|
|
65
|
-
body << segment
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
[res.code, create_response_headers(res), [body]]
|
70
|
-
}
|
71
31
|
end
|
72
32
|
|
73
33
|
private
|
74
34
|
|
75
|
-
def
|
76
|
-
|
77
|
-
|
35
|
+
def proxy(env, source_request, matcher)
|
36
|
+
uri = matcher.get_uri(source_request.fullpath,env)
|
37
|
+
if uri.nil?
|
38
|
+
return @app.call(env)
|
78
39
|
end
|
40
|
+
options = @global_options.dup.merge(matcher.options)
|
79
41
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
42
|
+
# Initialize request
|
43
|
+
target_request = Net::HTTP.const_get(source_request.request_method.capitalize).new(uri.request_uri)
|
44
|
+
|
45
|
+
# Setup headers
|
46
|
+
target_request_headers = extract_http_request_headers(source_request.env)
|
47
|
+
|
48
|
+
if options[:preserve_host]
|
49
|
+
target_request_headers['HOST'] = "#{uri.host}:#{uri.port}"
|
86
50
|
end
|
87
|
-
end
|
88
51
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
# TODO: figure out how to handle chunked responses
|
94
|
-
response_headers.delete('transfer-encoding')
|
95
|
-
# TODO: Verify Content Length, and required Rack headers
|
96
|
-
response_headers
|
97
|
-
end
|
52
|
+
if options[:x_forwarded_host]
|
53
|
+
target_request_headers['X-Forwarded-Host'] = source_request.host
|
54
|
+
target_request_headers['X-Forwarded-Port'] = "#{source_request.port}"
|
55
|
+
end
|
98
56
|
|
57
|
+
target_request.initialize_http_header(target_request_headers)
|
99
58
|
|
100
|
-
|
101
|
-
|
102
|
-
end
|
59
|
+
# Basic auth
|
60
|
+
target_request.basic_auth options[:username], options[:password] if options[:username] and options[:password]
|
103
61
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
62
|
+
# Setup body
|
63
|
+
if target_request.request_body_permitted? && source_request.body
|
64
|
+
source_request.body.rewind
|
65
|
+
target_request.body_stream = source_request.body
|
66
|
+
end
|
109
67
|
|
110
|
-
|
111
|
-
|
68
|
+
target_request.content_length = source_request.content_length || 0
|
69
|
+
target_request.content_type = source_request.content_type if source_request.content_type
|
112
70
|
|
113
|
-
|
114
|
-
|
115
|
-
end
|
71
|
+
# Create a streaming response (the actual network communication is deferred, a.k.a. streamed)
|
72
|
+
target_response = HttpStreamingResponse.new(target_request, uri.host, uri.port)
|
116
73
|
|
117
|
-
|
118
|
-
%Q(Your URL "#{@url}" is too generic. Did you mean "http://#{@url}"?)
|
119
|
-
end
|
120
|
-
end
|
74
|
+
target_response.use_ssl = "https" == uri.scheme
|
121
75
|
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
@path = path
|
126
|
-
@matches = matches
|
127
|
-
end
|
76
|
+
# Let rack set the transfer-encoding header
|
77
|
+
response_headers = target_response.headers
|
78
|
+
response_headers.delete('transfer-encoding')
|
128
79
|
|
129
|
-
|
130
|
-
|
80
|
+
# Replace the location header with the proxy domain
|
81
|
+
if response_headers['location'] && options[:replace_response_host]
|
82
|
+
response_location = URI(response_headers['location'][0])
|
83
|
+
response_location.host = source_request.host
|
84
|
+
response_headers['location'] = response_location.to_s
|
85
|
+
end
|
86
|
+
|
87
|
+
[target_response.status, response_headers, target_response.body]
|
131
88
|
end
|
132
89
|
|
133
|
-
|
90
|
+
def extract_http_request_headers(env)
|
91
|
+
headers = env.reject do |k, v|
|
92
|
+
!(/^HTTP_[A-Z_]+$/ === k) || v.nil?
|
93
|
+
end.map do |k, v|
|
94
|
+
[reconstruct_header_name(k), v]
|
95
|
+
end.inject(Utils::HeaderHash.new) do |hash, k_v|
|
96
|
+
k, v = k_v
|
97
|
+
hash[k] = v
|
98
|
+
hash
|
99
|
+
end
|
134
100
|
|
135
|
-
|
136
|
-
matches.map {|matcher| matcher.to_s}.join(', ')
|
137
|
-
end
|
138
|
-
end
|
101
|
+
x_forwarded_for = (headers["X-Forwarded-For"].to_s.split(/, +/) << env["REMOTE_ADDR"]).join(", ")
|
139
102
|
|
140
|
-
|
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}/
|
103
|
+
headers.merge!("X-Forwarded-For" => x_forwarded_for)
|
146
104
|
end
|
147
105
|
|
148
|
-
|
149
|
-
|
150
|
-
def match?(path)
|
151
|
-
match_path(path) ? true : false
|
106
|
+
def reconstruct_header_name(name)
|
107
|
+
name.sub(/^HTTP_/, "").gsub("_", "-")
|
152
108
|
end
|
153
109
|
|
154
|
-
def
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
110
|
+
def get_matcher(path, headers, rackreq)
|
111
|
+
matches = @matchers.select do |matcher|
|
112
|
+
matcher.match?(path, headers, rackreq)
|
113
|
+
end
|
114
|
+
|
115
|
+
if matches.length < 1
|
116
|
+
nil
|
117
|
+
elsif matches.length > 1 && @global_options[:matching] != :first
|
118
|
+
raise AmbiguousProxyMatch.new(path, matches)
|
159
119
|
else
|
160
|
-
|
120
|
+
matches.first
|
161
121
|
end
|
162
122
|
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
123
|
|
124
|
+
def reverse_proxy_options(options)
|
125
|
+
@global_options=options
|
126
|
+
end
|
171
127
|
|
128
|
+
def reverse_proxy(matcher, url=nil, opts={})
|
129
|
+
raise GenericProxyURI.new(url) if matcher.is_a?(String) && url.is_a?(String) && URI(url).class == URI::Generic
|
130
|
+
@matchers << ReverseProxyMatcher.new(matcher,url,opts)
|
131
|
+
end
|
172
132
|
end
|
173
133
|
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Rack
|
2
|
+
class ReverseProxyMatcher
|
3
|
+
def initialize(matcher,url=nil,options)
|
4
|
+
@default_url=url
|
5
|
+
@url=url
|
6
|
+
@options=options
|
7
|
+
|
8
|
+
if matcher.kind_of?(String)
|
9
|
+
@matcher = /^#{matcher.to_s}/
|
10
|
+
elsif matcher.respond_to?(:match)
|
11
|
+
@matcher = matcher
|
12
|
+
else
|
13
|
+
raise "Invalid Matcher for reverse_proxy"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
attr_reader :matcher,:url, :default_url,:options
|
18
|
+
|
19
|
+
def match?(path, *args)
|
20
|
+
match_path(path, *args) ? true : false
|
21
|
+
end
|
22
|
+
|
23
|
+
def get_uri(path,env)
|
24
|
+
return nil if url.nil?
|
25
|
+
_url=(url.respond_to?(:call) ? url.call(env) : url.clone)
|
26
|
+
if _url =~/\$\d/
|
27
|
+
match_path(path).to_a.each_with_index { |m, i| _url.gsub!("$#{i.to_s}", m) }
|
28
|
+
URI(_url)
|
29
|
+
else
|
30
|
+
default_url.nil? ? URI.parse(_url) : URI.join(_url, path)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
%Q("#{matcher.to_s}" => "#{url}")
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
def match_path(path, *args)
|
40
|
+
headers = args[0]
|
41
|
+
rackreq = args[1]
|
42
|
+
arity = matcher.method(:match).arity
|
43
|
+
if arity == -1
|
44
|
+
match = matcher.match(path)
|
45
|
+
else
|
46
|
+
params = [path, (@options[:accept_headers] ? headers : nil), rackreq]
|
47
|
+
match = matcher.match(*params[0..(arity - 1)])
|
48
|
+
end
|
49
|
+
@url = match.url(path) if match && default_url.nil?
|
50
|
+
match
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
metadata
CHANGED
@@ -1,125 +1,167 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-reverse-proxy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.8.1
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Jon Swope
|
8
|
+
- Ian Ehlert
|
9
|
+
- Roman Ernst
|
10
|
+
- Oleksii Fedorov
|
9
11
|
autorequire:
|
10
12
|
bindir: bin
|
11
13
|
cert_chain: []
|
12
|
-
date:
|
14
|
+
date: 2015-05-24 00:00:00.000000000 Z
|
13
15
|
dependencies:
|
14
16
|
- !ruby/object:Gem::Dependency
|
15
17
|
name: rspec
|
16
|
-
requirement:
|
17
|
-
none: false
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
18
19
|
requirements:
|
19
|
-
- - ~>
|
20
|
+
- - "~>"
|
20
21
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
22
|
+
version: '3.1'
|
22
23
|
type: :development
|
23
24
|
prerelease: false
|
24
|
-
version_requirements:
|
25
|
-
- !ruby/object:Gem::Dependency
|
26
|
-
name: bundler
|
27
|
-
requirement: &21581540 !ruby/object:Gem::Requirement
|
28
|
-
none: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
29
26
|
requirements:
|
30
|
-
- - ~>
|
27
|
+
- - "~>"
|
31
28
|
- !ruby/object:Gem::Version
|
32
|
-
version: 1
|
33
|
-
type: :development
|
34
|
-
prerelease: false
|
35
|
-
version_requirements: *21581540
|
29
|
+
version: '3.1'
|
36
30
|
- !ruby/object:Gem::Dependency
|
37
31
|
name: rake
|
38
|
-
requirement:
|
39
|
-
none: false
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
40
33
|
requirements:
|
41
|
-
- - ~>
|
34
|
+
- - "~>"
|
42
35
|
- !ruby/object:Gem::Version
|
43
|
-
version:
|
36
|
+
version: '10.3'
|
44
37
|
type: :development
|
45
38
|
prerelease: false
|
46
|
-
version_requirements:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - "~>"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '10.3'
|
47
44
|
- !ruby/object:Gem::Dependency
|
48
45
|
name: rack-test
|
49
|
-
requirement:
|
50
|
-
none: false
|
46
|
+
requirement: !ruby/object:Gem::Requirement
|
51
47
|
requirements:
|
52
|
-
- - ~>
|
48
|
+
- - "~>"
|
53
49
|
- !ruby/object:Gem::Version
|
54
|
-
version: 0.
|
50
|
+
version: '0.6'
|
55
51
|
type: :development
|
56
52
|
prerelease: false
|
57
|
-
version_requirements:
|
53
|
+
version_requirements: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - "~>"
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0.6'
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: webmock
|
60
|
-
requirement:
|
61
|
-
|
60
|
+
requirement: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - "~>"
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '1.18'
|
65
|
+
type: :development
|
66
|
+
prerelease: false
|
67
|
+
version_requirements: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - "~>"
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '1.18'
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: guard-rspec
|
74
|
+
requirement: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
type: :development
|
80
|
+
prerelease: false
|
81
|
+
version_requirements: !ruby/object:Gem::Requirement
|
62
82
|
requirements:
|
63
|
-
- -
|
83
|
+
- - ">="
|
64
84
|
- !ruby/object:Gem::Version
|
65
|
-
version:
|
85
|
+
version: '0'
|
86
|
+
- !ruby/object:Gem::Dependency
|
87
|
+
name: guard-bundler
|
88
|
+
requirement: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
66
93
|
type: :development
|
67
94
|
prerelease: false
|
68
|
-
version_requirements:
|
95
|
+
version_requirements: !ruby/object:Gem::Requirement
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
version: '0'
|
69
100
|
- !ruby/object:Gem::Dependency
|
70
101
|
name: rack
|
71
|
-
requirement:
|
72
|
-
none: false
|
102
|
+
requirement: !ruby/object:Gem::Requirement
|
73
103
|
requirements:
|
74
|
-
- -
|
104
|
+
- - ">="
|
75
105
|
- !ruby/object:Gem::Version
|
76
106
|
version: 1.0.0
|
77
107
|
type: :runtime
|
78
108
|
prerelease: false
|
79
|
-
version_requirements:
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - ">="
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: 1.0.0
|
114
|
+
- !ruby/object:Gem::Dependency
|
115
|
+
name: rack-proxy
|
116
|
+
requirement: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - "~>"
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '0.5'
|
121
|
+
type: :runtime
|
122
|
+
prerelease: false
|
123
|
+
version_requirements: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - "~>"
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '0.5'
|
80
128
|
description: A Rack based reverse proxy for basic needs. Useful for testing or in
|
81
129
|
cases where webserver configuration is unavailable.
|
82
|
-
email:
|
130
|
+
email:
|
131
|
+
- jaswope@gmail.com
|
132
|
+
- ehlertij@gmail.com
|
133
|
+
- rernst@farbenmeer.net
|
134
|
+
- waterlink000@gmail.com
|
83
135
|
executables: []
|
84
136
|
extensions: []
|
85
|
-
extra_rdoc_files:
|
86
|
-
- LICENSE
|
87
|
-
- README.rdoc
|
137
|
+
extra_rdoc_files: []
|
88
138
|
files:
|
89
|
-
- .document
|
90
139
|
- LICENSE
|
91
|
-
- README.
|
92
|
-
-
|
93
|
-
- VERSION
|
140
|
+
- README.md
|
141
|
+
- lib/rack/exception.rb
|
94
142
|
- lib/rack/reverse_proxy.rb
|
95
|
-
- rack
|
96
|
-
|
97
|
-
- spec/spec.opts
|
98
|
-
- spec/spec_helper.rb
|
99
|
-
homepage: http://github.com/jaswope/rack-reverse-proxy
|
143
|
+
- lib/rack/reverse_proxy_matcher.rb
|
144
|
+
homepage: http://github.com/waterlink/rack-reverse-proxy
|
100
145
|
licenses: []
|
146
|
+
metadata: {}
|
101
147
|
post_install_message:
|
102
148
|
rdoc_options: []
|
103
149
|
require_paths:
|
104
150
|
- lib
|
105
151
|
required_ruby_version: !ruby/object:Gem::Requirement
|
106
|
-
none: false
|
107
152
|
requirements:
|
108
|
-
- -
|
153
|
+
- - ">="
|
109
154
|
- !ruby/object:Gem::Version
|
110
155
|
version: '0'
|
111
156
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
112
|
-
none: false
|
113
157
|
requirements:
|
114
|
-
- -
|
158
|
+
- - ">="
|
115
159
|
- !ruby/object:Gem::Version
|
116
160
|
version: '0'
|
117
161
|
requirements: []
|
118
162
|
rubyforge_project:
|
119
|
-
rubygems_version:
|
163
|
+
rubygems_version: 2.2.2
|
120
164
|
signing_key:
|
121
|
-
specification_version:
|
165
|
+
specification_version: 4
|
122
166
|
summary: A Simple Reverse Proxy for Rack
|
123
|
-
test_files:
|
124
|
-
- spec/rack/reverse_proxy_spec.rb
|
125
|
-
- spec/spec_helper.rb
|
167
|
+
test_files: []
|
data/.document
DELETED
data/README.rdoc
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
= A Reverse Proxy for Rack
|
2
|
-
|
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
|
-
|
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
|
-
For your Gemfile use:
|
10
|
-
gem "rack-reverse-proxy", :require => "rack/reverse_proxy"
|
11
|
-
|
12
|
-
== Usage
|
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 $.
|
14
|
-
|
15
|
-
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.
|
16
|
-
|
17
|
-
Below is an example for configuring the middleware:
|
18
|
-
|
19
|
-
require 'rack/reverse_proxy'
|
20
|
-
|
21
|
-
use Rack::ReverseProxy do
|
22
|
-
# Set :preserve_host to true globally (default is true already)
|
23
|
-
reverse_proxy_options :preserve_host => true
|
24
|
-
|
25
|
-
# Forward the path /test* to http://example.com/test*
|
26
|
-
reverse_proxy '/test', 'http://example.com/'
|
27
|
-
|
28
|
-
# Forward the path /foo/* to http://example.com/bar/*
|
29
|
-
reverse_proxy /^\/foo(\/.*)$/, 'http://example.com/bar$1', :username => 'name', :password => 'basic_auth_secret'
|
30
|
-
end
|
31
|
-
|
32
|
-
app = proc do |env|
|
33
|
-
[ 200, {'Content-Type' => 'text/plain'}, "b" ]
|
34
|
-
end
|
35
|
-
run app
|
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
|
-
|
44
|
-
== Note on Patches/Pull Requests
|
45
|
-
|
46
|
-
* Fork the project.
|
47
|
-
* Make your feature addition or bug fix.
|
48
|
-
* Add tests for it. This is important so I don't break it in a
|
49
|
-
future version unintentionally.
|
50
|
-
* Commit, do not mess with rakefile, version, or history.
|
51
|
-
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
52
|
-
* Send me a pull request. Bonus points for topic branches.
|
53
|
-
|
54
|
-
== Copyright
|
55
|
-
|
56
|
-
Copyright (c) 2010 Jon Swope. See LICENSE for details.
|
data/Rakefile
DELETED
@@ -1,27 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
require 'rake'
|
3
|
-
require 'bundler/gem_tasks'
|
4
|
-
|
5
|
-
require 'spec/rake/spectask'
|
6
|
-
Spec::Rake::SpecTask.new(:spec) do |spec|
|
7
|
-
spec.libs << 'lib' << 'spec'
|
8
|
-
spec.spec_files = FileList['spec/**/*_spec.rb']
|
9
|
-
end
|
10
|
-
|
11
|
-
Spec::Rake::SpecTask.new(:rcov) do |spec|
|
12
|
-
spec.libs << 'lib' << 'spec'
|
13
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
14
|
-
spec.rcov = true
|
15
|
-
end
|
16
|
-
|
17
|
-
task :default => :spec
|
18
|
-
|
19
|
-
require 'rake/rdoctask'
|
20
|
-
Rake::RDocTask.new do |rdoc|
|
21
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
22
|
-
|
23
|
-
rdoc.rdoc_dir = 'rdoc'
|
24
|
-
rdoc.title = "rack-reverse-proxy #{version}"
|
25
|
-
rdoc.rdoc_files.include('README*')
|
26
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
27
|
-
end
|
data/VERSION
DELETED
@@ -1 +0,0 @@
|
|
1
|
-
0.4.4
|
data/rack-reverse-proxy.gemspec
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
Gem::Specification.new do |s|
|
2
|
-
s.name = %q{rack-reverse-proxy}
|
3
|
-
s.version = "0.4.4"
|
4
|
-
|
5
|
-
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
6
|
-
s.authors = ["Jon Swope"]
|
7
|
-
s.date = %q{2012-01-26}
|
8
|
-
s.description = %q{A Rack based reverse proxy for basic needs. Useful for testing or in cases where webserver configuration is unavailable.}
|
9
|
-
s.email = %q{jaswope@gmail.com}
|
10
|
-
s.extra_rdoc_files = [
|
11
|
-
"LICENSE",
|
12
|
-
"README.rdoc"
|
13
|
-
]
|
14
|
-
s.files = [
|
15
|
-
".document",
|
16
|
-
"LICENSE",
|
17
|
-
"README.rdoc",
|
18
|
-
"Rakefile",
|
19
|
-
"VERSION",
|
20
|
-
"lib/rack/reverse_proxy.rb",
|
21
|
-
"rack-reverse-proxy.gemspec",
|
22
|
-
"spec/rack/reverse_proxy_spec.rb",
|
23
|
-
"spec/spec.opts",
|
24
|
-
"spec/spec_helper.rb"
|
25
|
-
]
|
26
|
-
s.homepage = %q{http://github.com/jaswope/rack-reverse-proxy}
|
27
|
-
s.require_paths = ["lib"]
|
28
|
-
s.rubygems_version = %q{1.3.7}
|
29
|
-
s.summary = %q{A Simple Reverse Proxy for Rack}
|
30
|
-
s.test_files = [
|
31
|
-
"spec/rack/reverse_proxy_spec.rb",
|
32
|
-
"spec/spec_helper.rb"
|
33
|
-
]
|
34
|
-
|
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"
|
41
|
-
end
|
42
|
-
|
@@ -1,209 +0,0 @@
|
|
1
|
-
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
|
-
|
3
|
-
describe Rack::ReverseProxy do
|
4
|
-
include Rack::Test::Methods
|
5
|
-
include WebMock::API
|
6
|
-
|
7
|
-
def app
|
8
|
-
Rack::ReverseProxy.new
|
9
|
-
end
|
10
|
-
|
11
|
-
def dummy_app
|
12
|
-
lambda { |env| [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/', {:preserve_host => true}
|
19
|
-
reverse_proxy '/2test', lambda{ |env| 'http://example.com/'}
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
it "should forward requests to the calling app when the path is not matched" do
|
24
|
-
get '/'
|
25
|
-
last_response.body.should == "Dummy App"
|
26
|
-
last_response.should be_ok
|
27
|
-
end
|
28
|
-
|
29
|
-
it "should proxy requests when a pattern is matched" do
|
30
|
-
stub_request(:get, 'http://example.com/test').to_return({:body => "Proxied App"})
|
31
|
-
get '/test'
|
32
|
-
last_response.body.should == "Proxied App"
|
33
|
-
end
|
34
|
-
|
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
|
40
|
-
|
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
|
46
|
-
|
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
|
58
|
-
|
59
|
-
describe "with preserve host turned off" do
|
60
|
-
def app
|
61
|
-
Rack::ReverseProxy.new(dummy_app) do
|
62
|
-
reverse_proxy '/test', 'http://example.com/', {:preserve_host => false}
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
it "should not set the Host header" do
|
67
|
-
stub_request(:any, 'example.com/test/stuff')
|
68
|
-
get '/test/stuff'
|
69
|
-
a_request(:get, 'http://example.com/test/stuff').with(:headers => {"Host" => "example.com"}).should_not have_been_made
|
70
|
-
a_request(:get, 'http://example.com/test/stuff').should have_been_made
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
describe "with basic auth turned on" do
|
75
|
-
def app
|
76
|
-
Rack::ReverseProxy.new(dummy_app) do
|
77
|
-
reverse_proxy '/test', 'http://example.com/', {:username => "joe", :password => "shmoe"}
|
78
|
-
end
|
79
|
-
end
|
80
|
-
|
81
|
-
it "should make request with basic auth" do
|
82
|
-
stub_request(:get, "http://joe:shmoe@example.com/test/stuff").to_return(:body => "secured content")
|
83
|
-
get '/test/stuff'
|
84
|
-
last_response.body.should == "secured content"
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
describe "with ambiguous routes and all matching" do
|
89
|
-
def app
|
90
|
-
Rack::ReverseProxy.new(dummy_app) do
|
91
|
-
reverse_proxy_options :matching => :all
|
92
|
-
reverse_proxy '/test', 'http://example.com/'
|
93
|
-
reverse_proxy /^\/test/, 'http://example.com/'
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
|
-
it "should throw an exception" do
|
98
|
-
lambda { get '/test' }.should raise_error(Rack::AmbiguousProxyMatch)
|
99
|
-
end
|
100
|
-
end
|
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
|
-
|
118
|
-
describe "with a route as a regular expression" do
|
119
|
-
def app
|
120
|
-
Rack::ReverseProxy.new(dummy_app) do
|
121
|
-
reverse_proxy %r|^/test(/.*)$|, 'http://example.com$1'
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
it "should support subcaptures" do
|
126
|
-
stub_request(:get, 'http://example.com/path').to_return({:body => "Proxied App"})
|
127
|
-
get '/test/path'
|
128
|
-
last_response.body.should == "Proxied App"
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
describe "with a https route" do
|
133
|
-
def app
|
134
|
-
Rack::ReverseProxy.new(dummy_app) do
|
135
|
-
reverse_proxy '/test', 'https://example.com'
|
136
|
-
end
|
137
|
-
end
|
138
|
-
|
139
|
-
it "should make a secure request" do
|
140
|
-
stub_request(:get, 'https://example.com/test/stuff').to_return({:body => "Proxied Secure App"})
|
141
|
-
get '/test/stuff'
|
142
|
-
last_response.body.should == "Proxied Secure App"
|
143
|
-
end
|
144
|
-
|
145
|
-
end
|
146
|
-
|
147
|
-
describe "with a route as a string" do
|
148
|
-
def app
|
149
|
-
Rack::ReverseProxy.new(dummy_app) do
|
150
|
-
reverse_proxy '/test', 'http://example.com'
|
151
|
-
reverse_proxy '/path', 'http://example.com/foo$0'
|
152
|
-
end
|
153
|
-
end
|
154
|
-
|
155
|
-
it "should append the full path to the uri" do
|
156
|
-
stub_request(:get, 'http://example.com/test/stuff').to_return({:body => "Proxied App"})
|
157
|
-
get '/test/stuff'
|
158
|
-
last_response.body.should == "Proxied App"
|
159
|
-
end
|
160
|
-
|
161
|
-
end
|
162
|
-
|
163
|
-
describe "with a generic url" do
|
164
|
-
def app
|
165
|
-
Rack::ReverseProxy.new(dummy_app) do
|
166
|
-
reverse_proxy '/test', 'example.com'
|
167
|
-
end
|
168
|
-
end
|
169
|
-
|
170
|
-
it "should throw an exception" do
|
171
|
-
lambda{ app }.should raise_error(Rack::GenericProxyURI)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
describe "with a matching route" do
|
176
|
-
def app
|
177
|
-
Rack::ReverseProxy.new(dummy_app) do
|
178
|
-
reverse_proxy '/test', 'http://example.com/'
|
179
|
-
end
|
180
|
-
end
|
181
|
-
|
182
|
-
%w|get head delete put post|.each do |method|
|
183
|
-
describe "and using method #{method}" do
|
184
|
-
it "should forward the correct request" do
|
185
|
-
stub_request(method.to_sym, 'http://example.com/test').to_return({:body => "Proxied App for #{method}"})
|
186
|
-
eval "#{method} '/test'"
|
187
|
-
last_response.body.should == "Proxied App for #{method}"
|
188
|
-
end
|
189
|
-
|
190
|
-
if %w|put post|.include?(method)
|
191
|
-
it "should forward the request payload" do
|
192
|
-
stub_request(method.to_sym, 'http://example.com/test').to_return { |req| {:body => req.body} }
|
193
|
-
eval "#{method} '/test', {:test => 'test'}"
|
194
|
-
last_response.body.should == "test=test"
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
describe "as a rack app" do
|
203
|
-
it "should respond with 404 when the path is not matched" do
|
204
|
-
get '/'
|
205
|
-
last_response.should be_not_found
|
206
|
-
end
|
207
|
-
end
|
208
|
-
|
209
|
-
end
|
data/spec/spec.opts
DELETED
data/spec/spec_helper.rb
DELETED
@@ -1,13 +0,0 @@
|
|
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
|
-
require 'webmock/rspec'
|
10
|
-
|
11
|
-
Spec::Runner.configure do |config|
|
12
|
-
WebMock.disable_net_connect!
|
13
|
-
end
|