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 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