rack-rewrite 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -1,21 +1,89 @@
1
1
  = rack-rewrite
2
2
 
3
- A rack middleware for enforcing rewrite rules. In many cases you can get away with rack-rewrite
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
- == Usage in a rails app
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
- == Notes
74
+ === :r301, :302
15
75
 
16
- * Haven't tested full paths for :rewrite, :r301, :r302
17
- * No regular expression support yet
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.0
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 = find_rule(env)
11
- apply(matched_rule, env)
12
- else
13
- @app.call(env)
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
- # This logic needs to be pushed into Rule subclasses
18
- def apply(rule, env)
19
- case rule[0]
20
- when :r301
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.0"
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-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.0
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-10 00:00:00 -04:00
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