proxy 1.3.3

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG ADDED
@@ -0,0 +1,94 @@
1
+ 2009-06-05 - Sean Huber (shuber@huberry.com)
2
+ * Add multiple domain support for rack
3
+ * Update gemspec
4
+ * Update README
5
+
6
+ 2009-06-04 - Sean Huber (shuber@huberry.com)
7
+ * Update test related rake tasks
8
+ * Remove huberry namespace
9
+ * Add support for multiple domains
10
+ * Update README
11
+
12
+ 2009-05-20 - Sean Huber (shuber@huberry.com)
13
+ * Update date in gemspec so github rebuilds gem
14
+
15
+ 2009-05-18 - Sean Huber (shuber@huberry.com)
16
+ * Fix compatibility issues with actionpack >= 2.2.2
17
+
18
+ 2009-01-24 - Sean Huber (shuber@huberry.com)
19
+ * Add compatibility for edge/upcoming rails versions
20
+
21
+ 2009-01-08 - Sean Huber (shuber@huberry.com)
22
+ * Add Proxy namespace
23
+ * Remove .gitignore
24
+
25
+ 2008-12-09 - Sean Huber (shuber@huberry.com)
26
+ * Update README
27
+
28
+ 2008-12-08 - Sean Huber (shuber@huberry.com)
29
+ * Fix bug - lib/proxy.rb was not included in gemspec
30
+
31
+ 2008-11-14 - Sean Huber (shuber@huberry.com)
32
+ * Fix bug - "redirect_to some_path" now uses a forwarded host if it exists
33
+ * Gemify
34
+ * Update README
35
+ * Update proxy.gemspec
36
+
37
+ 2008-10-30 - Sean Huber (shuber@huberry.com)
38
+ * Update documentation
39
+ * Move the logic that calculates the session_domain into its own method and update documentation
40
+ * Update README
41
+
42
+ 2008-10-27 - Sean Huber (shuber@huberry.com)
43
+ * rewrite_url also adds the :host option as options is a Hash
44
+ * Move the logic that calculates the proxy_relative_url_root into its own method
45
+
46
+ 2008-10-21 - Sean Huber (shuber@huberry.com)
47
+ * Fix bug - only add the :host option to url_helper if options is a Hash
48
+
49
+ 2008-10-06 - Sean Huber (shuber@huberry.com)
50
+ * Add tests to ensure asset helpers work correctly
51
+
52
+ 2008-10-05 - Sean Huber (shuber@huberry.com)
53
+ * Add failing named route test
54
+ * Named routes now work with forwarded hosts
55
+ * ActionView::Base#url_for uses forwarded hosts correctly
56
+ * Update README
57
+ * Update init.rb
58
+
59
+ 2008-10-03 - Sean Huber (shuber@huberry.com)
60
+ * Add tests to ensure default host and relative_url_root are restored when an action redirects
61
+ * Update README
62
+ * The original session_domain is now restored after each request
63
+
64
+ 2008-10-02 - Sean Huber (shuber@huberry.com)
65
+ * Remove Dispatcher and moved that logic into Base
66
+ * Add Rakefile
67
+ * Fix bug - referred to ActionController instead of ActionController::Base
68
+ * Fix bug - plugin was not being loaded properly
69
+ * Update README
70
+ * Fix bug - proxy_relative_url_root was not being set on every request
71
+ * Fix bug - relative_url_root was always being delegated to ActionController::AbstractRequest
72
+
73
+ 2008-10-01 - Sean Huber (shuber@huberry.com)
74
+ * Move some forwarded host logic into AbstractRequest
75
+ * Add AbstractRequest tests and renamed BaseTest file
76
+ * Add more AbstractRequest tests
77
+ * ActionController::Base will delegate relative_url_root to AbstractRequest if necessary to support older versions of actionpack
78
+ * Add more Base tests
79
+ * Reorder requires and inclusions in proxy.rb
80
+ * Update CHANGELOG
81
+ * Update README
82
+ * Add UrlRewriter
83
+ * Add more tests
84
+
85
+ 2008-09-29 - Sean Huber (shuber@huberry.com)
86
+ * Initial import
87
+ * Add some tests - still need to figure out how to test Dispatcher
88
+ * Base automatically detects HTTP_X_FORWARDED_URI and determines relative_url_root (can be overwritten)
89
+ * Update Base tests
90
+ * Base can now parse a comma separated string of forwarded uris
91
+ * The HTTP_X_FORWARDED_URI variable name can be changed to something else
92
+ * Update README
93
+ * Regex to determine relative root url is now properly escaped
94
+ * Fix bug - Base was not setting the class relative_url_root correctly
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Sean Huber (shuber@huberry.com)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,90 @@
1
+ # Proxy
2
+
3
+ A gem/plugin that allows rails applications to dynamically respond to multiple domains and proxied requests by detecting forwarded host/uri headers and setting the session domain, default host, and relative url root. The plugin adds this functionality to calls to url\_for, named route helpers, and view url helpers while still allowing you to specifically set the :host and :only\_path options to override this behavior.
4
+
5
+ The original session domain, default host, and relative url root will be restored after each request.
6
+
7
+ Requires actionpack >= 2.0.0
8
+
9
+
10
+ ## Installation
11
+
12
+ script/plugin install git://github.com/shuber/proxy.git
13
+ OR
14
+ gem install proxy
15
+
16
+
17
+ ## Usage
18
+
19
+ ### Proxied Requests
20
+
21
+ Let's say you have a suite of hosted applications all running on the same domain but mounted on different paths. One of them is an order/invoicing application located at:
22
+
23
+ http://client.example.com/orders
24
+
25
+ Imagine you sold an account to a client but the client wants the application to look like its running on his own domain, so they'll set up a proxy so they can access your application at:
26
+
27
+ http://clientdomain.com/orders
28
+
29
+ This plugin will automatically detect this forwarded host and set the session domain and default host (for url generation) accordingly.
30
+
31
+
32
+ ### Proxied Requests with Custom URIs
33
+
34
+ Now imagine the client had an existing ordering system already running at /orders, and he wants to slowly migrate his data into your application, so he'll need both applications running for awhile. He wants to keep his original ordering application running at /orders and he wants your application running at:
35
+
36
+ http://clientdomain.com/neworders
37
+
38
+ All the client has to do is proxy /neworders to http://client.example.com/orders and this plugin will automatically detect the forwarded request uri and set the relative url root for your application accordingly. Now whenever urls are generated, they will correctly use /neworders as the relative url root instead of /orders.
39
+
40
+ Note: this plugin looks for a request header called 'HTTP\_X\_FORWARDED\_URI' to detect the relative root url by default, but this can be overwritten like so:
41
+
42
+ ActionController::AbstractRequest.forwarded_uri_header_name = 'SOME_CUSTOM_HEADER_NAME'
43
+
44
+ You can add that line in environment.rb or an initializer.
45
+
46
+
47
+ #### Relative Url Root Proxy Setup
48
+
49
+ The client's proxy must forward the request uri header in order for this plugin to automatically set the relative url root correctly. Here is how the client would setup a proxy in apache for the example above:
50
+
51
+ RewriteRule ^neworders(.*) http://client.example.com/orders$1 [P,QSA,L,E=originalUri:%{REQUEST_URI}]
52
+ RequestHeader append X_FORWARDED_URI %{originalUri}e e=originalUri
53
+
54
+
55
+ ### Multiple Domains
56
+
57
+ Imagine you have a CMS that hosts multiple client sites. You want your users to manage their sites on your root domain `http://yourcmsapp.com` and you display a site's public content when it's accessed by its subdomain (e.g. `http://cool-site.yourcmsapp.com`). You'll probably be using [subdomain-fu](http://github.com/mbleigh/subdomain-fu) so you can route based on subdomains like:
58
+
59
+ ActionController::Routing::Routes.draw do |map|
60
+ # this routing controller has a before_filter callback that looks up a site by subdomain
61
+ map.public_page '*path', :controller => 'routing', :conditions => { :subdomain => /^[^\.]+$/ }
62
+
63
+ map.with_options :conditions => { :subdomain => nil } do |admin|
64
+ admin.resource :account, :controller => 'account'
65
+ admin.resources :sites
66
+ ...
67
+ end
68
+ end
69
+
70
+ Now, it gets tricky if you want `http://cool-site.com` to render `cool-site`'s public content because you can't tell if this request has a subdomain or not. In order for your routes to work, you must have all requests coming in from your domain `yourcmsapp.com`. You can accomplish this by calling the `Proxy.replace_host_with(&block)` method like so:
71
+
72
+ # config/initializers/proxy.rb
73
+
74
+ Proxy.replace_host_with do |request|
75
+ "#{Site.find_by_domain(request.host).try(:subdomain) || '-INVALID-'}.yourcmsapp.com" unless request.host =~ /(\.|^)yourcmsapp.com$/i
76
+ end
77
+
78
+ Let's examine what this block is doing:
79
+
80
+ * First, it checks if the current request's host is already on your domain. If it is, we don't need to do anything, otherwise...
81
+ * It checks if a site exists with a domain that matches the current request's host.
82
+ * If a site does exist, a new host is returned using the site's `subdomain` with your app domain and everything renders fine, otherwise...
83
+ * A fake host is returned (-INVALID-.yourcmsapp.com), and the request 404s once it gets to your `routing` controller and a site can't be found with the subdomain `-INVALID-`
84
+
85
+ If `nil, false, or an empty string` is returned when you call the `Proxy.replace_host_with` method, the current request's host is not modified. Otherwise, the `HTTP_X_FORWARDED_HOST` request header is set to: `"#{the_original_host}, #{the_new_host}"`. This allows your routes to use your domain when evaluating routing conditions and also allows all of the application's url generators to use the original host.
86
+
87
+
88
+ ## Contact
89
+
90
+ Problems, comments, and suggestions all welcome: [shuber@huberry.com](mailto:shuber@huberry.com)
data/Rakefile ADDED
@@ -0,0 +1,34 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+
5
+ desc 'Default: run the proxy tests'
6
+ task :default => :test
7
+
8
+ namespace :test do
9
+ desc 'Default: run the proxy tests for all versions of actionpack'
10
+ task :all do
11
+ versions = `gem list`.match(/actionpack \((.+)\)/).captures[0].split(/, /).select { |v| v[0,1].to_i > 1 }
12
+ versions.each do |version|
13
+ puts "\n\n============================================================="
14
+ puts "TESTING WITH ACTION PACK VERSION #{version}\n\n"
15
+ system "rake test ACTION_PACK_VERSION=#{version}"
16
+ end
17
+ end
18
+ end
19
+
20
+ desc 'Test the proxy plugin.'
21
+ Rake::TestTask.new(:test) do |t|
22
+ t.libs << 'lib'
23
+ t.pattern = 'test/*_test.rb'
24
+ t.verbose = true
25
+ end
26
+
27
+ desc 'Generate documentation for the proxy plugin.'
28
+ Rake::RDocTask.new(:rdoc) do |rdoc|
29
+ rdoc.rdoc_dir = 'rdoc'
30
+ rdoc.title = 'Proxy'
31
+ rdoc.options << '--line-numbers' << '--inline-source'
32
+ rdoc.rdoc_files.include('README.markdown')
33
+ rdoc.rdoc_files.include('lib/**/*.rb')
34
+ end
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require_dependency 'proxy'
data/lib/proxy.rb ADDED
@@ -0,0 +1,49 @@
1
+ require 'proxy/action_controller/abstract_request'
2
+ require 'proxy/action_controller/base'
3
+ require 'proxy/action_controller/named_route_collection'
4
+ require 'proxy/action_controller/url_rewriter'
5
+ require 'proxy/action_view/url_helper'
6
+
7
+ module Proxy
8
+ mattr_accessor :replace_host_with_proc
9
+ self.replace_host_with_proc = proc { |request| }
10
+
11
+ def self.replace_host_with(&block)
12
+ self.replace_host_with_proc = block
13
+ end
14
+
15
+ private
16
+
17
+ def self.before_dispatch(dispatcher)
18
+ request = dispatcher.instance_variable_get('@request') || dispatcher.instance_variable_get('@env')
19
+ request = Rack::Request.new(request) if request.is_a?(Hash)
20
+ new_host = replace_host_with_proc.call(request)
21
+ request.env['HTTP_X_FORWARDED_HOST'] = [request.host, new_host].join(', ') unless new_host.blank?
22
+ end
23
+ end
24
+
25
+ ActionController::Dispatcher.before_dispatch do |dispatcher|
26
+ Proxy.send :before_dispatch, dispatcher
27
+ end
28
+
29
+ ActionController::AbstractRequest = ActionController::Request if defined?(ActionController::Request)
30
+ ActionController::AbstractRequest.send :include, Proxy::ActionController::AbstractRequest
31
+ ActionController::Base.send :include, Proxy::ActionController::Base
32
+ ActionController::Routing::RouteSet::NamedRouteCollection.send :include, Proxy::ActionController::NamedRouteCollection
33
+ ActionController::UrlRewriter.send :include, Proxy::ActionController::UrlRewriter
34
+ ActionView::Base.send :include, Proxy::ActionView::UrlHelper
35
+
36
+ unless ActionController::UrlWriter.respond_to?(:default_url_options)
37
+ ActionController::Base.class_eval do
38
+ include ActionController::UrlWriter
39
+
40
+ def default_url_options_with_backwards_compatibility(*args)
41
+ default_url_options_without_backwards_compatibility
42
+ end
43
+ alias_method_chain :default_url_options, :backwards_compatibility
44
+ end
45
+
46
+ class << ActionController::UrlWriter
47
+ delegate :default_url_options, :default_url_options=, :to => ::ActionController::Base
48
+ end
49
+ end
@@ -0,0 +1,41 @@
1
+ module Proxy
2
+ module ActionController
3
+ module AbstractRequest
4
+ def self.included(base)
5
+ base.class_eval do
6
+ mattr_accessor :forwarded_uri_header_name
7
+ self.forwarded_uri_header_name = 'HTTP_X_FORWARDED_URI'
8
+ memoize :forwarded_hosts, :forwarded_uris if respond_to? :memoize
9
+ end
10
+ end
11
+
12
+ # Parses the forwarded host header and returns an array of forwarded hosts
13
+ #
14
+ # For example:
15
+ #
16
+ # If the HTTP_X_FORWARDED_HOST header was set to
17
+ # 'some-domain.com, some-other-domain.com, and-another-domain.com'
18
+ #
19
+ # This method would return ['some-domain.com', 'some-other-domain.com', 'and-another-domain.com']
20
+ #
21
+ # Returns an empty array if there aren't any forwarded hosts
22
+ def forwarded_hosts
23
+ env['HTTP_X_FORWARDED_HOST'].to_s.split(/,\s*/)
24
+ end
25
+
26
+ # Parses the forwarded uri header and returns an array of forwarded uris
27
+ #
28
+ # For example:
29
+ #
30
+ # If the HTTP_X_FORWARDED_URI header was set to
31
+ # '/some/path, /some/other/path, /and/another/path'
32
+ #
33
+ # This method would return ['/some/path, '/some/other/path', '/and/another/path']
34
+ #
35
+ # Returns an empty array if there aren't any forwarded uris
36
+ def forwarded_uris
37
+ env[self.forwarded_uri_header_name].to_s.split(/,\s*/)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,130 @@
1
+ module Proxy
2
+ module ActionController
3
+ module Base
4
+ def self.included(base)
5
+ base.class_eval do
6
+ before_filter :set_proxy_relative_url_root
7
+ around_filter :swap_default_host
8
+ around_filter :swap_relative_url_root
9
+ around_filter :swap_session_domain
10
+ cattr_accessor :original_relative_url_root
11
+ cattr_accessor :proxy_relative_url_root
12
+ alias_method_chain :redirect_to, :proxy
13
+ class << self; delegate :relative_url_root, :relative_url_root=, :to => ::ActionController::AbstractRequest unless ::ActionController::Base.respond_to?(:relative_url_root); end
14
+ end
15
+ end
16
+
17
+ protected
18
+
19
+ # Calculates the <tt>relative_url_root</tt> by parsing the request path out of the
20
+ # first forwarded uri
21
+ #
22
+ # For example:
23
+ #
24
+ # http://example.com/manage/videos/new
25
+ # gets proxied to
26
+ # http://your-domain.com/videos/new
27
+ #
28
+ # The first forwarded uri would be: /manage/videos/new
29
+ # and the request path would be: /videos/new
30
+ #
31
+ # So this method would return: /manage
32
+ def parse_proxy_relative_url_root
33
+ request.forwarded_uris.first.gsub(/#{Regexp.escape(request.path)}$/, '')
34
+ end
35
+
36
+ # Calculates the <tt>session_domain</tt> by parsing the first domain.tld out of the
37
+ # first forwarded host and prepending a '.'
38
+ #
39
+ # For example:
40
+ #
41
+ # http://example.com/manage/videos/new
42
+ # http://some.other-domain.com/videos/new
43
+ # both get proxied to
44
+ # http://your-domain.com/videos/new
45
+ #
46
+ # The resulting session domain for the first url would be: '.example.com'
47
+ # The resulting session domain for the second url would be: '.other-domain.com'
48
+ def parse_session_domain
49
+ ".#{$1}" if /([^\.]+\.[^\.]+)$/.match(request.forwarded_hosts.first)
50
+ end
51
+
52
+ # Forces redirects to use the <tt>default_url_options[:host]</tt> if it exists unless a host
53
+ # is already set
54
+ #
55
+ # For example:
56
+ #
57
+ # http://example.com
58
+ # gets proxied to
59
+ # http://your-domain.com
60
+ #
61
+ # If you have an action that calls <tt>redirect_to new_videos_path</tt>, the example.com domain
62
+ # would be used instead of your-domain.com
63
+ def redirect_to_with_proxy(*args)
64
+ args[0] = request.protocol + url_options[:host] + args.first if args.first.is_a?(String) && !%r{^\w+://.*}.match(args.first) && !url_options[:host].blank?
65
+ redirect_to_without_proxy(*args)
66
+ end
67
+
68
+ # Returns ::ActionController::Base.session_options
69
+ def session_options
70
+ ::ActionController::Base.session_options
71
+ end
72
+
73
+ # Sets the <tt>proxy_relative_url_root</tt> using the +parse_proxy_relative_url_root+ method
74
+ # to calculate it
75
+ #
76
+ # Sets the <tt>proxy_relative_url_root</tt> to nil if there aren't any forwarded uris
77
+ def set_proxy_relative_url_root
78
+ ::ActionController::Base.proxy_relative_url_root = request.forwarded_uris.empty? ? nil : parse_proxy_relative_url_root
79
+ end
80
+
81
+ # Sets the <tt>default_url_options[:host]</tt> to the first forwarded host if there are any
82
+ #
83
+ # The original default host is restored after each request and can be accessed by calling
84
+ # <tt>ActionController::UrlWriter.default_url_options[:original_host]</tt>
85
+ def swap_default_host
86
+ url_options[:original_host] = url_options[:host]
87
+ url_options[:host] = request.forwarded_hosts.first unless request.forwarded_hosts.empty?
88
+ begin
89
+ yield
90
+ ensure
91
+ url_options[:host] = url_options[:original_host]
92
+ end
93
+ end
94
+
95
+ # Sets the <tt>relative_url_root</tt> to the <tt>proxy_relative_url_root</tt> unless it's nil
96
+ #
97
+ # The original relative url root is restored after each request and can be accessed by calling
98
+ # <tt>ActionController::Base.original_relative_url_root</tt>
99
+ def swap_relative_url_root
100
+ ::ActionController::Base.original_relative_url_root = ::ActionController::Base.relative_url_root
101
+ ::ActionController::Base.relative_url_root = ::ActionController::Base.proxy_relative_url_root unless ::ActionController::Base.proxy_relative_url_root.nil?
102
+ begin
103
+ yield
104
+ ensure
105
+ ::ActionController::Base.relative_url_root = ::ActionController::Base.original_relative_url_root
106
+ end
107
+ end
108
+
109
+ # Sets the <tt>session_options[:session_domain]</tt> to the result of the +parse_session_domain+ method
110
+ # unless there aren't any forwarded hosts
111
+ #
112
+ # The original session domain is restored after each request and can be accessed by calling
113
+ # <tt>ActionController::Base.session_options[:original_session_domain]</tt>
114
+ def swap_session_domain
115
+ session_options[:original_session_domain] = session_options[:session_domain] || session_options[:domain]
116
+ session_options[:session_domain] = session_options[:domain] = parse_session_domain unless request.forwarded_hosts.empty?
117
+ begin
118
+ yield
119
+ ensure
120
+ session_options[:session_domain] = session_options[:domain] = session_options[:original_session_domain]
121
+ end
122
+ end
123
+
124
+ # Returns ::ActionController::UrlWriter.default_url_options
125
+ def url_options
126
+ ::ActionController::UrlWriter.default_url_options
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,30 @@
1
+ module Proxy
2
+ module ActionController
3
+ module NamedRouteCollection
4
+ def self.included(base)
5
+ base.class_eval { alias_method_chain :define_url_helper, :proxy }
6
+ end
7
+
8
+ # Named route url helpers (not path helpers) don't seem to work correctly
9
+ # with forwarded hosts unless we explicitly set the option:
10
+ #
11
+ # :only_path => false
12
+ #
13
+ # This method only sets that option if it isn't set already
14
+ def define_url_helper_with_proxy(route, name, kind, options)
15
+ define_url_helper_without_proxy(route, name, kind, options)
16
+ if kind == :url
17
+ selector = url_helper_name(name, kind)
18
+ @module.module_eval do
19
+ define_method "#{selector}_with_proxy" do |*args|
20
+ args << {} unless args.last.is_a? Hash
21
+ args.last[:only_path] ||= false
22
+ send "#{selector}_without_proxy", *args
23
+ end
24
+ alias_method_chain selector, :proxy
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,21 @@
1
+ module Proxy
2
+ module ActionController
3
+ module UrlRewriter
4
+ def self.included(base)
5
+ base.class_eval { alias_method_chain :rewrite_url, :proxy }
6
+ end
7
+
8
+ # Adds the default :host option unless already specified
9
+ #
10
+ # It will not set the :host option if <tt>options</tt> is not a hash or
11
+ # if the <tt>ActionController::UrlWriter.default_url_options[:host]</tt> is blank
12
+ def rewrite_url_with_proxy(options)
13
+ if options.is_a?(Hash)
14
+ options[:host] ||= ::ActionController::UrlWriter.default_url_options[:host] unless ::ActionController::UrlWriter.default_url_options[:host].blank?
15
+ options.delete(:original_host)
16
+ end
17
+ rewrite_url_without_proxy(options)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,21 @@
1
+ module Proxy
2
+ module ActionView
3
+ module UrlHelper
4
+ def self.included(base)
5
+ base.class_eval { alias_method_chain :url_for, :proxy }
6
+ end
7
+
8
+ # Adds the default :host option unless already specified
9
+ #
10
+ # It will not set the :host option if <tt>options</tt> is not a hash or
11
+ # if the <tt>ActionController::UrlWriter.default_url_options[:host]</tt> is blank
12
+ def url_for_with_proxy(options = {})
13
+ if options.is_a?(Hash)
14
+ options[:host] ||= ::ActionController::UrlWriter.default_url_options[:host] unless ::ActionController::UrlWriter.default_url_options[:host].blank?
15
+ options.delete(:original_host)
16
+ end
17
+ url_for_without_proxy(options)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,68 @@
1
+ require File.dirname(__FILE__) + '/init'
2
+
3
+ class AbstractRequestTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @request = ActionController::TestRequest.new
7
+ @request.host = 'example.com'
8
+ ActionController::UrlWriter.default_url_options[:host] = nil
9
+ ActionController::Base.relative_url_root = nil
10
+ end
11
+
12
+ def test_forwarded_hosts_should_be_empty
13
+ assert_equal [], @request.forwarded_hosts
14
+ end
15
+
16
+ def test_should_parse_empty_forwarded_hosts_into_an_array
17
+ @request.env['HTTP_X_FORWARDED_HOST'] = ''
18
+ assert_equal [], @request.forwarded_hosts
19
+ end
20
+
21
+ def test_should_parse_nil_forwarded_hosts_into_an_array
22
+ @request.env['HTTP_X_FORWARDED_HOST'] = nil
23
+ assert_equal [], @request.forwarded_hosts
24
+ end
25
+
26
+ def test_forwarded_host_string_should_be_parsed_into_an_array
27
+ @request.env['HTTP_X_FORWARDED_HOST'] = 'forwarded.com'
28
+ assert_equal ['forwarded.com'], @request.forwarded_hosts
29
+ end
30
+
31
+ def test_comma_separated_forwarded_host_string_should_be_parsed_into_an_array
32
+ @request.env['HTTP_X_FORWARDED_HOST'] = 'forwarded.com,something.com, test.com, testing.com'
33
+ assert_equal ['forwarded.com', 'something.com', 'test.com', 'testing.com'], @request.forwarded_hosts
34
+ end
35
+
36
+ def test_forwarded_uris_should_be_empty
37
+ assert_equal [], @request.forwarded_uris
38
+ end
39
+
40
+ def test_should_parse_empty_forwarded_uris_into_an_array
41
+ @request.env['HTTP_X_FORWARDED_URI'] = ''
42
+ assert_equal [], @request.forwarded_uris
43
+ end
44
+
45
+ def test_should_parse_nil_forwarded_uris_into_an_array
46
+ @request.env['HTTP_X_FORWARDED_URI'] = nil
47
+ assert_equal [], @request.forwarded_uris
48
+ end
49
+
50
+ def test_forwarded_uri_string_should_be_parsed_into_an_array
51
+ @request.env['HTTP_X_FORWARDED_URI'] = '/test/ing'
52
+ assert_equal ['/test/ing'], @request.forwarded_uris
53
+ end
54
+
55
+ def test_comma_separated_forwarded_uri_string_should_be_parsed_into_an_array
56
+ @request.env['HTTP_X_FORWARDED_URI'] = '/test/ing,/whoa, /uh/oh, /mmm/kay'
57
+ assert_equal ['/test/ing', '/whoa', '/uh/oh', '/mmm/kay'], @request.forwarded_uris
58
+ end
59
+
60
+ def test_should_be_able_to_use_custom_forwarded_uri_header_name
61
+ @original_header_name = ActionController::AbstractRequest.forwarded_uri_header_name
62
+ @request.env['HTTP_CUSTOM_FORWARDED_URI'] = '/test/ing'
63
+ ActionController::AbstractRequest.forwarded_uri_header_name = 'HTTP_CUSTOM_FORWARDED_URI'
64
+ assert_equal ['/test/ing'], @request.forwarded_uris
65
+ ActionController::AbstractRequest.forwarded_uri_header_name = @original_header_name
66
+ end
67
+
68
+ end
data/test/base_test.rb ADDED
@@ -0,0 +1,182 @@
1
+ require File.dirname(__FILE__) + '/init'
2
+
3
+ class BaseTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @controller = TestController.new
7
+ @request = ActionController::TestRequest.new
8
+ @response = ActionController::TestResponse.new
9
+ @request.host = 'example.com'
10
+ ActionController::UrlWriter.default_url_options[:host] = nil
11
+ ActionController::Base.relative_url_root = '/app'
12
+ end
13
+
14
+ def test_should_get_normal_action
15
+ get :normal_action
16
+ assert_equal "http://#{@request.host}/normal_action", @response.body
17
+ end
18
+
19
+ def test_should_set_a_forwarded_host_as_a_default_host
20
+ add_forwarded_host_headers
21
+ get :normal_action
22
+ assert_equal "http://domain.com/app/normal_action", @response.body
23
+ end
24
+
25
+ def test_should_set_forwarded_host_in_a_named_route
26
+ add_forwarded_host_headers
27
+ get :named_route_action
28
+ assert_equal "http://domain.com/app/normal_action", @response.body
29
+ end
30
+
31
+ def test_should_restore_the_original_default_host
32
+ ::ActionController::UrlWriter.default_url_options[:host] = 'some-other-domain.com'
33
+ add_forwarded_host_headers
34
+ get :normal_action
35
+ assert_equal "http://domain.com/app/normal_action", @response.body
36
+ end
37
+
38
+ def test_url_generators_in_views_should_use_forwarded_host
39
+ add_forwarded_host_headers
40
+ get :view_action
41
+ assert_equal "/app/normal_action, http://domain.com/app/normal_action, http://domain.com/app/normal_action, /app/normal_action", @response.body
42
+ end
43
+
44
+ def test_asset_tag_helpers_should_not_use_forwarded_host
45
+ add_forwarded_host_headers
46
+ get :asset_action
47
+ assert_nil(/domain\.com/.match(@response.body))
48
+ end
49
+
50
+ def test_should_set_the_session_domain_with_a_forwarded_host
51
+ add_forwarded_host_headers
52
+ get :session_action
53
+ assert_equal '.domain.com', @response.body
54
+ end
55
+
56
+ def test_should_restore_original_session_domain
57
+ ::ActionController::Base.session_options[:session_domain] = '.example.com'
58
+ add_forwarded_host_headers
59
+ get :session_action
60
+ assert_equal '.domain.com', @response.body
61
+ assert_equal '.example.com', ::ActionController::Base.session_options[:session_domain]
62
+ end
63
+
64
+ def test_should_restore_original_session_domain_with_domain_key
65
+ ::ActionController::Base.session_options[:domain] = '.example.com'
66
+ add_forwarded_host_headers
67
+ get :session_action
68
+ assert_equal '.domain.com', @response.body
69
+ assert_equal '.example.com', ::ActionController::Base.session_options[:domain]
70
+ end
71
+
72
+ def test_should_restore_original_session_if_exception_is_raised
73
+ ::ActionController::Base.session_options[:session_domain] = '.example.com'
74
+ add_forwarded_host_headers
75
+ assert_raises(RuntimeError) { get :exception_action }
76
+ assert_equal '.example.com', ::ActionController::Base.session_options[:session_domain]
77
+ end
78
+
79
+ def test_should_restore_original_session_if_redirected
80
+ ::ActionController::Base.session_options[:session_domain] = '.example.com'
81
+ add_forwarded_host_headers
82
+ get :redirect_action
83
+ assert_equal '.example.com', ::ActionController::Base.session_options[:session_domain]
84
+ end
85
+
86
+ def test_should_restore_original_host_if_exception_is_raised
87
+ ::ActionController::UrlWriter.default_url_options[:host] = 'some-other-domain.com'
88
+ add_forwarded_host_headers
89
+ assert_raises(RuntimeError) { get :exception_action }
90
+ assert_equal 'some-other-domain.com', ::ActionController::UrlWriter.default_url_options[:host]
91
+ end
92
+
93
+ def test_should_restore_original_host_if_redirected
94
+ ::ActionController::UrlWriter.default_url_options[:host] = 'some-other-domain.com'
95
+ add_forwarded_host_headers
96
+ get :redirect_action
97
+ assert_equal 'some-other-domain.com', ::ActionController::UrlWriter.default_url_options[:host]
98
+ end
99
+
100
+ def test_should_get_normal_action
101
+ get :normal_action
102
+ assert_equal "http://#{@request.host}/app/normal_action", @response.body
103
+ end
104
+
105
+ def test_should_swap_relative_url_root
106
+ add_forwarded_uri_headers
107
+ get :normal_action
108
+ assert_equal "http://#{@request.host}/test/ing/normal_action", @response.body
109
+ assert_equal '/app', ActionController::Base.relative_url_root
110
+ end
111
+
112
+ def test_should_set_proxy_relative_url_root
113
+ add_forwarded_uri_headers
114
+ get :normal_action
115
+ assert_equal '/test/ing', ActionController::Base.proxy_relative_url_root
116
+ end
117
+
118
+ def test_should_set_proxy_relative_url_root_in_a_named_route
119
+ @request.env['HTTP_X_FORWARDED_URI'] = '/test/ing/named_route_action'
120
+ @request.env['PATH_INFO'] = '/named_route_action'
121
+ get :named_route_action
122
+ assert_equal "http://#{@request.host}/test/ing/normal_action", @response.body
123
+ end
124
+
125
+ def test_should_set_original_relative_url_root
126
+ add_forwarded_uri_headers
127
+ get :normal_action
128
+ assert_equal '/app', ActionController::Base.original_relative_url_root
129
+ end
130
+
131
+ def test_should_restore_relative_url_root_if_exception_is_raised
132
+ add_forwarded_uri_headers
133
+ assert_raises(RuntimeError) { get :exception_action }
134
+ assert_equal '/app', ActionController::Base.relative_url_root
135
+ end
136
+
137
+ def test_should_restore_relative_url_root_if_exception_is_raised
138
+ add_forwarded_uri_headers
139
+ get :redirect_action
140
+ assert_equal '/app', ActionController::Base.relative_url_root
141
+ end
142
+
143
+ def test_url_generators_in_views_should_use_forwarded_uri
144
+ @request.env['HTTP_X_FORWARDED_URI'] = '/test/ing/view_action'
145
+ @request.env['PATH_INFO'] = '/view_action'
146
+ get :view_action
147
+ assert_equal "/test/ing/normal_action, http://example.com/test/ing/normal_action, /test/ing/normal_action, /test/ing/normal_action", @response.body
148
+ end
149
+
150
+ def test_asset_tag_helpers_should_use_forwarded_uri
151
+ @request.env['HTTP_X_FORWARDED_URI'] = '/test/ing/asset_action'
152
+ @request.env['PATH_INFO'] = '/asset_action'
153
+ get :asset_action
154
+ assert !@response.body.scan(/img[^>]+src\="\/test\/ing\/images\/test\.gif"/).empty?
155
+ assert !@response.body.scan(/script[^>]+src\="\/test\/ing\/javascripts\/test\.js"/).empty?
156
+ assert !@response.body.scan(/link[^>]+href\="\/test\/ing\/stylesheets\/test\.css"/).empty?
157
+ end
158
+
159
+ def test_should_use_forwarded_host_in_a_redirect
160
+ add_forwarded_host_headers
161
+ get :redirect_action
162
+ assert_redirected_to 'http://domain.com/app/normal_action'
163
+ end
164
+
165
+ def test_should_use_forwarded_host_in_a_redirect_with_named_routes
166
+ add_forwarded_host_headers
167
+ get :redirect_with_named_route_action
168
+ assert_redirected_to 'http://domain.com/app/normal_action'
169
+ end
170
+
171
+ protected
172
+
173
+ def add_forwarded_host_headers
174
+ @request.env['HTTP_X_FORWARDED_HOST'] = 'domain.com'
175
+ end
176
+
177
+ def add_forwarded_uri_headers
178
+ @request.env['HTTP_X_FORWARDED_URI'] = '/test/ing/normal_action'
179
+ @request.env['PATH_INFO'] = '/normal_action'
180
+ end
181
+
182
+ end
data/test/init.rb ADDED
@@ -0,0 +1,103 @@
1
+ $:.reject! { |path| path.include? 'TextMate' }
2
+ require 'test/unit'
3
+
4
+ # Load rubygems
5
+ #
6
+ require 'rubygems'
7
+
8
+ # Load ActionPack
9
+ #
10
+ args = ['actionpack']
11
+ args << ENV['ACTION_PACK_VERSION'] if ENV['ACTION_PACK_VERSION']
12
+ gem *args
13
+ require 'action_pack'
14
+ require 'action_controller'
15
+ require 'action_controller/dispatcher'
16
+ require 'action_controller/routing'
17
+ require 'action_controller/session_management'
18
+ require 'action_controller/url_rewriter'
19
+ require 'action_controller/test_process'
20
+ require 'action_view'
21
+
22
+ Test::Unit::TestCase.class_eval do
23
+ include ActionController::TestProcess
24
+
25
+ unless instance_methods.include?('assert_redirected_to')
26
+ def assert_redirected_to(url)
27
+ assert @response.redirect?
28
+ assert_equal url, @response.location
29
+ end
30
+ end
31
+ end
32
+
33
+ # Routing
34
+ #
35
+ class ActionController::Routing::RouteSet
36
+ def append
37
+ yield Mapper.new(self)
38
+ install_helpers
39
+ end
40
+ end
41
+
42
+ ActionController::Base.session_options.merge! :key => '_proxy_session', :secret => '60447f093cd3f5d57686dd5ca1ced83216c846047db0ec97480a5e85411d9bd41ec5587cc4aafac27a4f0e649f0c628b0955b315856b78787a96168671dc96d4'
43
+
44
+ # Require the main proxy.rb file
45
+ #
46
+ require File.join(File.dirname(File.dirname(__FILE__)), 'lib', 'proxy')
47
+
48
+ # Test controller
49
+ #
50
+ class TestController < ActionController::Base
51
+
52
+ def asset_action
53
+ render :inline => '<%= image_tag "test.gif" %>, <%= javascript_include_tag "test" %>, <%= stylesheet_link_tag "test" %>'
54
+ end
55
+
56
+ def exception_action
57
+ raise 'Uh oh'
58
+ end
59
+
60
+ def named_route_action
61
+ render :text => normal_action_url
62
+ end
63
+
64
+ def normal_action
65
+ render :text => url_for(:controller => 'test', :action => 'normal_action')
66
+ end
67
+
68
+ def redirect_action
69
+ redirect_to :action => 'normal_action'
70
+ end
71
+
72
+ def redirect_with_named_route_action
73
+ redirect_to normal_action_path
74
+ end
75
+
76
+ def session_action
77
+ render :text => ActionController::Base.session_options[:session_domain]
78
+ end
79
+
80
+ def view_action
81
+ render :inline => '<%= normal_action_path %>, <%= normal_action_url %>, <%= url_for(:controller => "test", :action => "normal_action") %>, <%= url_for(:controller => "test", :action => "normal_action", :only_path => true) %>'
82
+ end
83
+
84
+ protected
85
+
86
+ def rescue_action(e)
87
+ raise e
88
+ end
89
+ end
90
+
91
+ # Test routes
92
+ #
93
+ ActionController::Routing::Routes.append do |map|
94
+ map.connect 'asset_action', :controller => 'test', :action => 'asset_action'
95
+ map.connect 'exception_action', :controller => 'test', :action => 'exception_action'
96
+ map.connect 'named_route_action', :controller => 'test', :action => 'named_route_action'
97
+ map.connect 'normal_action', :controller => 'test', :action => 'normal_action'
98
+ map.connect 'redirect_action', :controller => 'test', :action => 'redirect_action'
99
+ map.connect 'redirect_with_named_route_action', :controller => 'test', :action => 'redirect_with_named_route_action'
100
+ map.connect 'session_action', :controller => 'test', :action => 'session_action'
101
+ map.connect 'view_action', :controller => 'test', :action => 'view_action'
102
+ map.normal_action 'normal_action', :controller => 'test', :action => 'normal_action'
103
+ end
@@ -0,0 +1,28 @@
1
+ require File.dirname(__FILE__) + '/init'
2
+
3
+ class NamedRouteCollectionTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @controller = TestController.new
7
+ @request = ActionController::TestRequest.new
8
+ @response = ActionController::TestResponse.new
9
+ @request.host = 'example.com'
10
+ ActionController::UrlWriter.default_url_options[:host] = nil
11
+ ActionController::Base.relative_url_root = nil
12
+ end
13
+
14
+ def test_should_alias_method_chain_url_helpers
15
+ [ActionController::Base, ActionView::Base].each do |klass|
16
+ assert klass.method_defined?(:normal_action_url_with_proxy)
17
+ assert klass.method_defined?(:normal_action_url_without_proxy)
18
+ end
19
+ end
20
+
21
+ def test_should_not_alias_method_chain_path_helpers
22
+ [ActionController::Base, ActionView::Base].each do |klass|
23
+ assert !klass.method_defined?(:normal_action_path_with_proxy)
24
+ assert !klass.method_defined?(:normal_action_path_without_proxy)
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,34 @@
1
+ require File.dirname(__FILE__) + '/init'
2
+
3
+ class ProxyTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @controller = TestController.new
7
+ @request = ActionController::TestRequest.new
8
+ def @request.host; env['HTTP_X_FORWARDED_HOST'].blank? ? env['HTTP_HOST'] : env['HTTP_X_FORWARDED_HOST'].split(/,\s/).last; end
9
+ @response = ActionController::TestResponse.new
10
+ @dispatcher = ActionController::Dispatcher.new(StringIO.new)
11
+ @dispatcher.instance_variable_set('@request', @request)
12
+ ActionController::UrlWriter.default_url_options[:host] = nil
13
+ ActionController::Base.relative_url_root = nil
14
+
15
+ Proxy.replace_host_with do |request|
16
+ 'replaced.com' if request.host == 'replace-me.com'
17
+ end
18
+ end
19
+
20
+ def test_should_not_replace_host
21
+ @request.env['HTTP_HOST'] = 'dont-replace-me.com'
22
+ assert_equal 'dont-replace-me.com', @request.host
23
+ Proxy.send(:before_dispatch, @dispatcher)
24
+ assert_equal 'dont-replace-me.com', @request.host
25
+ end
26
+
27
+ def test_should_replace_host
28
+ @request.env['HTTP_HOST'] = 'replace-me.com'
29
+ assert_equal 'replace-me.com', @request.host
30
+ Proxy.send(:before_dispatch, @dispatcher)
31
+ assert_equal 'replaced.com', @request.host
32
+ end
33
+
34
+ end
@@ -0,0 +1,40 @@
1
+ require File.dirname(__FILE__) + '/init'
2
+
3
+ class UrlHelperTest < Test::Unit::TestCase
4
+
5
+ def setup
6
+ @controller = TestController.new
7
+ @request = ActionController::TestRequest.new
8
+ @response = ActionController::TestResponse.new
9
+ @request.host = 'example.com'
10
+ ActionController::UrlWriter.default_url_options[:host] = nil
11
+ ActionController::Base.relative_url_root = nil
12
+ end
13
+
14
+ def test_should_render_urls_normally
15
+ get :view_action
16
+ assert_equal '/normal_action, http://example.com/normal_action, /normal_action, /normal_action', @response.body
17
+ end
18
+
19
+ def test_should_render_urls_with_forwarded_hosts_while_respecting_the_only_path_option
20
+ @request.env['HTTP_X_FORWARDED_HOST'] = 'domain.com'
21
+ get :view_action
22
+ assert_equal '/normal_action, http://domain.com/normal_action, http://domain.com/normal_action, /normal_action', @response.body
23
+ end
24
+
25
+ def test_should_render_urls_with_forwarded_uris
26
+ @request.env['HTTP_X_FORWARDED_URI'] = '/test/ing/view_action'
27
+ @request.env['PATH_INFO'] = '/view_action'
28
+ get :view_action
29
+ assert_equal '/test/ing/normal_action, http://example.com/test/ing/normal_action, /test/ing/normal_action, /test/ing/normal_action', @response.body
30
+ end
31
+
32
+ def test_should_render_urls_with_forwarded_hosts_and_uris
33
+ @request.env['HTTP_X_FORWARDED_HOST'] = 'domain.com'
34
+ @request.env['HTTP_X_FORWARDED_URI'] = '/test/ing/view_action'
35
+ @request.env['PATH_INFO'] = '/view_action'
36
+ get :view_action
37
+ assert_equal '/test/ing/normal_action, http://domain.com/test/ing/normal_action, http://domain.com/test/ing/normal_action, /test/ing/normal_action', @response.body
38
+ end
39
+
40
+ end
@@ -0,0 +1,36 @@
1
+ require File.dirname(__FILE__) + '/init'
2
+
3
+ class UrlRewriter < ::ActionController::UrlRewriter
4
+ public
5
+ def rewrite_url
6
+ super :action => :normal_action, :controller => :test
7
+ end
8
+ end
9
+
10
+ class UrlRewriterTest < Test::Unit::TestCase
11
+
12
+ def setup
13
+ @controller = TestController.new
14
+ @request = ActionController::TestRequest.new
15
+ @response = ActionController::TestResponse.new
16
+ @request.host = 'example.com'
17
+ @url_rewriter = UrlRewriter.new(@request, {})
18
+ ActionController::UrlWriter.default_url_options[:host] = nil
19
+ ActionController::Base.relative_url_root = nil
20
+ end
21
+
22
+ def test_should_rewrite_normal_action_with_request_host
23
+ assert_equal 'http://example.com/normal_action', @url_rewriter.rewrite_url
24
+ end
25
+
26
+ def test_should_rewrite_normal_action_with_request_host_if_default_host_is_empty
27
+ ActionController::UrlWriter.default_url_options[:host] = ''
28
+ assert_equal 'http://example.com/normal_action', @url_rewriter.rewrite_url
29
+ end
30
+
31
+ def test_should_rewrite_normal_action_with_default_host
32
+ ActionController::UrlWriter.default_url_options[:host] = 'test.com'
33
+ assert_equal 'http://test.com/normal_action', @url_rewriter.rewrite_url
34
+ end
35
+
36
+ end
metadata ADDED
@@ -0,0 +1,74 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: proxy
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.3.3
5
+ platform: ruby
6
+ authors:
7
+ - Sean Huber
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-08 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: A gem/plugin that allows rails applications to respond to multiple domains and proxied requests
17
+ email: shuber@huberry.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - CHANGELOG
26
+ - init.rb
27
+ - lib/proxy/action_controller/abstract_request.rb
28
+ - lib/proxy/action_controller/base.rb
29
+ - lib/proxy/action_controller/named_route_collection.rb
30
+ - lib/proxy/action_controller/url_rewriter.rb
31
+ - lib/proxy/action_view/url_helper.rb
32
+ - lib/proxy.rb
33
+ - MIT-LICENSE
34
+ - Rakefile
35
+ - README.markdown
36
+ - test/init.rb
37
+ has_rdoc: true
38
+ homepage: http://github.com/shuber/proxy
39
+ licenses: []
40
+
41
+ post_install_message:
42
+ rdoc_options:
43
+ - --line-numbers
44
+ - --inline-source
45
+ - --main
46
+ - README.markdown
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ version:
55
+ required_rubygems_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: "0"
60
+ version:
61
+ requirements: []
62
+
63
+ rubyforge_project:
64
+ rubygems_version: 1.3.5
65
+ signing_key:
66
+ specification_version: 3
67
+ summary: A gem/plugin that allows rails applications to respond to multiple domains and proxied requests
68
+ test_files:
69
+ - test/abstract_request_test.rb
70
+ - test/base_test.rb
71
+ - test/named_route_collection_test.rb
72
+ - test/proxy_test.rb
73
+ - test/url_helper_test.rb
74
+ - test/url_rewriter_test.rb