rack-rewrite 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +74 -6
- data/VERSION +1 -1
- data/lib/rack-rewrite.rb +16 -42
- data/lib/rack-rewrite/rule.rb +92 -0
- data/rack-rewrite.gemspec +5 -2
- data/test/rule_test.rb +84 -0
- metadata +5 -2
data/README.rdoc
CHANGED
@@ -1,21 +1,89 @@
|
|
1
1
|
= rack-rewrite
|
2
2
|
|
3
|
-
A rack middleware for
|
4
|
-
instead of writing Apache mod_rewrite rules.
|
3
|
+
A rack middleware for defining and applying rewrite rules. In many cases you
|
4
|
+
can get away with rack-rewrite instead of writing Apache mod_rewrite rules.
|
5
5
|
|
6
|
-
==
|
6
|
+
== Use Cases
|
7
|
+
|
8
|
+
=== Rebuild of existing site in a new technology
|
9
|
+
|
10
|
+
It's very common for sites built in older technologies to be rebuilt with the
|
11
|
+
latest and greatest. Let's consider a site that has already established quite
|
12
|
+
a bit of "google juice." When we launch the new site, we don't want to lose
|
13
|
+
that hard-earned reputation. By writing rewrite rules that issue 301's for
|
14
|
+
old URL's, we can "transfer" that google ranking to the new site. An example
|
15
|
+
rule might look like:
|
16
|
+
|
17
|
+
rewrite '/contact-us.php', '/contact-us'
|
18
|
+
rewrite '/wiki/John_Trupiano', '/john'
|
19
|
+
|
20
|
+
=== Retiring old routes
|
21
|
+
|
22
|
+
As a web application evolves you will undoubtedly reach a point where you need
|
23
|
+
to change the name of something (a model, e.g.). This name change will
|
24
|
+
typically require a similar change to your routing. The danger here is that
|
25
|
+
any URL's previously generated (in a transactional email for instance) will
|
26
|
+
have the URL hard-coded. In order for your rails app to continue to serve
|
27
|
+
this URL, you'll need to add an extra entry to your routes file.
|
28
|
+
Alternatively, you could use rack-rewrite to redirect or pass through requests
|
29
|
+
to these routes and keep your routes.rb clean.
|
30
|
+
|
31
|
+
rewrite %r{/features(.*)}, '/facial_features$1'
|
32
|
+
|
33
|
+
== Sample rackup file
|
34
|
+
|
35
|
+
use Rack::Rewrite do
|
36
|
+
rewrite '/wiki/John_Trupiano', '/john'
|
37
|
+
r301 '/wiki/Yair_Flicker', '/yair'
|
38
|
+
r302 '/wiki/Greg_Jastrab', '/greg'
|
39
|
+
r301 %r{/wiki/(\w+)_\w+}, '/$1'
|
40
|
+
end
|
41
|
+
|
42
|
+
== Sample usage in a rails app
|
7
43
|
config.gem 'rack-rewrite', '~> 0.1.0'
|
44
|
+
require 'rack-rewrite
|
8
45
|
config.middleware.insert_before(Rack::Lock, Rack::Rewrite) do
|
9
46
|
rewrite '/wiki/John_Trupiano', '/john'
|
10
47
|
r301 '/wiki/Yair_Flicker', '/yair'
|
11
48
|
r302 '/wiki/Greg_Jastrab', '/greg'
|
49
|
+
r301 %r{/wiki/(\w+)_\w+}, '/$1'
|
12
50
|
end
|
51
|
+
|
52
|
+
== Rewrite Rules
|
53
|
+
|
54
|
+
=== :rewrite
|
55
|
+
|
56
|
+
Calls to #rewrite will simply update the PATH_INFO and REQUEST_URI HTTP header
|
57
|
+
values and pass the request onto the next chain in the Rack stack. The URL
|
58
|
+
that a user's browser will show will not be changed. See these examples:
|
59
|
+
|
60
|
+
rewrite '/wiki/John_Trupiano', '/john' # [1]
|
61
|
+
rewrite %r{/wiki/(\w+)_\w+}, '/$1' # [2]
|
62
|
+
|
63
|
+
For [1], the user's browser will continue to display /wiki/John_Trupiano, but
|
64
|
+
the actual HTTP header values for PATH_INFO and REQUEST_URI in the request
|
65
|
+
will be changed to /john for subsequent nodes in the Rack stack. Rails
|
66
|
+
reads these headers to determine which routes will match.
|
67
|
+
|
68
|
+
Rule [2] showcases the use of regular expressions and substitutions. [2] is a
|
69
|
+
generalized version of [1] that will match any /wiki/FirstName_LastName URL's
|
70
|
+
and rewrite them as the first name only. This is an actual catch-all rule we
|
71
|
+
applied when we rebuilt our website in September 2009
|
72
|
+
( http://www.smartlogicsolutions.com ).
|
13
73
|
|
14
|
-
|
74
|
+
=== :r301, :302
|
15
75
|
|
16
|
-
|
17
|
-
|
76
|
+
Calls to #r301 and #r302 have the same signature as #rewrite. The difference,
|
77
|
+
however, is that these actually short-circuit the rack stack and send back
|
78
|
+
301's and 302's, respectively. See these examples:
|
18
79
|
|
80
|
+
r301 '/wiki/John_Trupiano', '/john' # [1]
|
81
|
+
r301 '/wiki/(.*)', 'http://www.google.com/?q=$1' # [2]
|
82
|
+
|
83
|
+
Recall that rules are interpreted from top to bottom. So you can install
|
84
|
+
"default" rewrite rules if you like. [2] is a sample default rule that
|
85
|
+
will redirect all other requests to the wiki to a google search.
|
86
|
+
|
19
87
|
== Copyright
|
20
88
|
|
21
89
|
Copyright (c) 2009 John Trupiano. See LICENSE for details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.1
|
data/lib/rack-rewrite.rb
CHANGED
@@ -1,4 +1,10 @@
|
|
1
|
+
$: << File.expand_path(File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require 'rack-rewrite/rule'
|
4
|
+
|
1
5
|
module Rack
|
6
|
+
# A rack middleware for defining and applying rewrite rules. In many cases you
|
7
|
+
# can get away with rack-rewrite instead of writing Apache mod_rewrite rules.
|
2
8
|
class Rewrite
|
3
9
|
def initialize(app, &rule_block)
|
4
10
|
@app = app
|
@@ -7,49 +13,17 @@ module Rack
|
|
7
13
|
end
|
8
14
|
|
9
15
|
def call(env)
|
10
|
-
if matched_rule =
|
11
|
-
apply(
|
12
|
-
|
13
|
-
|
16
|
+
if matched_rule = find_first_matching_rule(env)
|
17
|
+
rack_response = matched_rule.apply!(env)
|
18
|
+
# Don't invoke the app if applying the rule returns a rack response
|
19
|
+
return rack_response unless rack_response === true
|
14
20
|
end
|
21
|
+
@app.call(env)
|
15
22
|
end
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
[301, {'Location' => rule[2]}, ['Redirecting...']]
|
22
|
-
when :r302
|
23
|
-
[302, {'Location' => rule[2]}, ['Redirecting...']]
|
24
|
-
when :rewrite
|
25
|
-
# return [200, {}, {:content => env.inspect}]
|
26
|
-
env['PATH_INFO'] = env['REQUEST_URI'] = rule[2]
|
27
|
-
@app.call(env)
|
28
|
-
else
|
29
|
-
raise Exception.new("Unsupported rule: #{rule[0]}")
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
# This will probably have to change as rule matching gets more complicated
|
34
|
-
def find_rule(env)
|
35
|
-
@rule_set.rules.detect { |rule| rule[1] == env['PATH_INFO'] }
|
36
|
-
end
|
37
|
-
|
38
|
-
class RuleSet
|
39
|
-
|
40
|
-
attr_reader :rules
|
41
|
-
def initialize
|
42
|
-
@rules = []
|
43
|
-
end
|
44
|
-
|
45
|
-
private
|
46
|
-
# We're explicitly defining the functions for our DSL rather than using
|
47
|
-
# method_missing
|
48
|
-
%w(rewrite r301 r302).each do |meth|
|
49
|
-
define_method(meth) do |from, to|
|
50
|
-
@rules << [meth.to_sym, from, to]
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
23
|
+
|
24
|
+
private
|
25
|
+
def find_first_matching_rule(env) #:nodoc:
|
26
|
+
@rule_set.rules.detect { |rule| rule.matches?(env['PATH_INFO']) }
|
27
|
+
end
|
54
28
|
end
|
55
29
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module Rack
|
2
|
+
class Rewrite
|
3
|
+
class RuleSet
|
4
|
+
attr_reader :rules
|
5
|
+
def initialize #:nodoc:
|
6
|
+
@rules = []
|
7
|
+
end
|
8
|
+
|
9
|
+
protected
|
10
|
+
# We're explicitly defining private functions for our DSL rather than
|
11
|
+
# using method_missing
|
12
|
+
|
13
|
+
# Creates a rewrite rule that will simply rewrite the REQUEST_URI and
|
14
|
+
# PATH_INFO headers of the Rack environment. The user's browser
|
15
|
+
# will continue to show the initially requested URL.
|
16
|
+
#
|
17
|
+
# rewrite '/wiki/John_Trupiano', '/john'
|
18
|
+
# rewrite %r{/wiki/(\w+)_\w+}, '/$1'
|
19
|
+
def rewrite(from, to)
|
20
|
+
@rules << Rule.new(:rewrite, from, to)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Creates a redirect rule that will send a 301 when matching.
|
24
|
+
#
|
25
|
+
# r301 '/wiki/John_Trupiano', '/john'
|
26
|
+
# r301 '/contact-us.php', '/contact-us'
|
27
|
+
def r301(from, to)
|
28
|
+
@rules << Rule.new(:r301, from, to)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Creates a redirect rule that will send a 302 when matching.
|
32
|
+
#
|
33
|
+
# r302 '/wiki/John_Trupiano', '/john'
|
34
|
+
# r302 '/wiki/(.*)', 'http://www.google.com/?q=$1'
|
35
|
+
def r302(from, to)
|
36
|
+
@rules << Rule.new(:r302, from, to)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
# TODO: Break rules into subclasses
|
41
|
+
class Rule #:nodoc:
|
42
|
+
attr_reader :rule_type, :from, :to
|
43
|
+
def initialize(rule_type, from, to) #:nodoc:
|
44
|
+
@rule_type, @from, @to = rule_type, from, to
|
45
|
+
end
|
46
|
+
|
47
|
+
def matches?(path) #:nodoc:
|
48
|
+
case self.from
|
49
|
+
when Regexp
|
50
|
+
path =~ self.from
|
51
|
+
when String
|
52
|
+
path == self.from
|
53
|
+
else
|
54
|
+
false
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Either (a) return a Rack response (short-circuting the Rack stack), or
|
59
|
+
# (b) alter env as necessary and return true
|
60
|
+
def apply!(env) #:nodoc:
|
61
|
+
interpreted_to = self.send(:interpret_to, env['PATH_INFO'])
|
62
|
+
case self.rule_type
|
63
|
+
when :r301
|
64
|
+
[301, {'Location' => interpreted_to}, ['Redirecting...']]
|
65
|
+
when :r302
|
66
|
+
[302, {'Location' => interpreted_to}, ['Redirecting...']]
|
67
|
+
when :rewrite
|
68
|
+
# return [200, {}, {:content => env.inspect}]
|
69
|
+
env['PATH_INFO'] = env['REQUEST_URI'] = interpreted_to
|
70
|
+
true
|
71
|
+
else
|
72
|
+
raise Exception.new("Unsupported rule: #{rule.rule_type}")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
private
|
77
|
+
# is there a better way to do this?
|
78
|
+
def interpret_to(path) #:nodoc:
|
79
|
+
if self.from.is_a?(Regexp)
|
80
|
+
if from_match_data = self.from.match(path)
|
81
|
+
computed_to = self.to.dup
|
82
|
+
(from_match_data.size - 1).downto(1) do |num|
|
83
|
+
computed_to.gsub!("$#{num}", from_match_data[num])
|
84
|
+
end
|
85
|
+
return computed_to
|
86
|
+
end
|
87
|
+
end
|
88
|
+
self.to
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
data/rack-rewrite.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{rack-rewrite}
|
8
|
-
s.version = "0.1.
|
8
|
+
s.version = "0.1.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["John Trupiano"]
|
12
|
-
s.date = %q{2009-10-
|
12
|
+
s.date = %q{2009-10-12}
|
13
13
|
s.description = %q{A rack middleware for enforcing rewrite rules. In many cases you can get away with rack-rewrite instead of writing Apache mod_rewrite rules.}
|
14
14
|
s.email = %q{jtrupiano@gmail.com}
|
15
15
|
s.extra_rdoc_files = [
|
@@ -24,8 +24,10 @@ Gem::Specification.new do |s|
|
|
24
24
|
"Rakefile",
|
25
25
|
"VERSION",
|
26
26
|
"lib/rack-rewrite.rb",
|
27
|
+
"lib/rack-rewrite/rule.rb",
|
27
28
|
"rack-rewrite.gemspec",
|
28
29
|
"test/rack-rewrite_test.rb",
|
30
|
+
"test/rule_test.rb",
|
29
31
|
"test/test_helper.rb"
|
30
32
|
]
|
31
33
|
s.homepage = %q{http://github.com/jtrupiano/rack-rewrite}
|
@@ -36,6 +38,7 @@ Gem::Specification.new do |s|
|
|
36
38
|
s.summary = %q{A rack middleware for enforcing rewrite rules}
|
37
39
|
s.test_files = [
|
38
40
|
"test/rack-rewrite_test.rb",
|
41
|
+
"test/rule_test.rb",
|
39
42
|
"test/test_helper.rb"
|
40
43
|
]
|
41
44
|
|
data/test/rule_test.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class RuleTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
context '#Rule#apply' do
|
6
|
+
should 'set Location header to result of #interpret_to for a 301' do
|
7
|
+
rule = Rack::Rewrite::Rule.new(:r301, %r{/abc}, '/def')
|
8
|
+
env = {'PATH_INFO' => '/abc'}
|
9
|
+
assert_equal rule.send(:interpret_to, '/abc'), rule.apply!(env)[1]['Location']
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
context 'Rule#matches' do
|
14
|
+
|
15
|
+
context 'Given any rule with a "from" string of /features' do
|
16
|
+
setup do
|
17
|
+
@rule = Rack::Rewrite::Rule.new(:rewrite, '/features', '/facial_features')
|
18
|
+
end
|
19
|
+
|
20
|
+
should 'match PATH_INFO of /features' do
|
21
|
+
assert @rule.matches?("/features")
|
22
|
+
end
|
23
|
+
|
24
|
+
should 'not match PATH_INFO of /features.xml' do
|
25
|
+
assert !@rule.matches?("/features.xml")
|
26
|
+
end
|
27
|
+
|
28
|
+
should 'not match PATH_INFO of /my_features' do
|
29
|
+
assert !@rule.matches?("/my_features")
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'Given any rule with a "from" regular expression of /features(.*)' do
|
34
|
+
setup do
|
35
|
+
@rule = Rack::Rewrite::Rule.new(:rewrite, %r{/features(.*)}, '/facial_features$1')
|
36
|
+
end
|
37
|
+
|
38
|
+
should 'match PATH_INFO of /features' do
|
39
|
+
assert @rule.matches?("/features")
|
40
|
+
end
|
41
|
+
|
42
|
+
should 'match PATH_INFO of /features.xml' do
|
43
|
+
assert @rule.matches?('/features.xml')
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'match PATH_INFO of /features/1' do
|
47
|
+
assert @rule.matches?('/features/1')
|
48
|
+
end
|
49
|
+
|
50
|
+
should 'match PATH_INFO of /features?filter_by=name' do
|
51
|
+
assert @rule.matches?('/features?filter_by_name=name')
|
52
|
+
end
|
53
|
+
|
54
|
+
should 'match PATH_INFO of /features/1?hide_bio=1' do
|
55
|
+
assert @rule.matches?('/features/1?hide_bio=1')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'Rule#interpret_to' do
|
61
|
+
|
62
|
+
should 'return #to when #from is a string' do
|
63
|
+
rule = Rack::Rewrite::Rule.new(:rewrite, '/abc', '/def')
|
64
|
+
assert_equal '/def', rule.send(:interpret_to, '/abc')
|
65
|
+
end
|
66
|
+
|
67
|
+
should 'replace $1 on a match' do
|
68
|
+
rule = Rack::Rewrite::Rule.new(:rewrite, %r{/person_(\d+)}, '/people/$1')
|
69
|
+
assert_equal '/people/1', rule.send(:interpret_to, "/person_1")
|
70
|
+
end
|
71
|
+
|
72
|
+
should 'be able to catch querystrings with a regexp match' do
|
73
|
+
rule = Rack::Rewrite::Rule.new(:rewrite, %r{/person_(\d+)(.*)}, '/people/$1$2')
|
74
|
+
assert_equal '/people/1?show_bio=1', rule.send(:interpret_to, '/person_1?show_bio=1')
|
75
|
+
end
|
76
|
+
|
77
|
+
should 'be able to make 10 replacements' do
|
78
|
+
# regexp to reverse 10 characters
|
79
|
+
rule = Rack::Rewrite::Rule.new(:rewrite, %r{(\w)(\w)(\w)(\w)(\w)(\w)(\w)(\w)(\w)(\w)}, '$10$9$8$7$6$5$4$3$2$1')
|
80
|
+
assert_equal 'jihgfedcba', rule.send(:interpret_to, "abcdefghij")
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-rewrite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- John Trupiano
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-10-
|
12
|
+
date: 2009-10-12 00:00:00 -04:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -39,8 +39,10 @@ files:
|
|
39
39
|
- Rakefile
|
40
40
|
- VERSION
|
41
41
|
- lib/rack-rewrite.rb
|
42
|
+
- lib/rack-rewrite/rule.rb
|
42
43
|
- rack-rewrite.gemspec
|
43
44
|
- test/rack-rewrite_test.rb
|
45
|
+
- test/rule_test.rb
|
44
46
|
- test/test_helper.rb
|
45
47
|
has_rdoc: true
|
46
48
|
homepage: http://github.com/jtrupiano/rack-rewrite
|
@@ -72,4 +74,5 @@ specification_version: 3
|
|
72
74
|
summary: A rack middleware for enforcing rewrite rules
|
73
75
|
test_files:
|
74
76
|
- test/rack-rewrite_test.rb
|
77
|
+
- test/rule_test.rb
|
75
78
|
- test/test_helper.rb
|