rack-rewrite 1.3.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +2 -2
- data/LICENSE +3 -1
- data/README.markdown +332 -0
- data/VERSION +1 -1
- data/lib/rack/rewrite/rule.rb +8 -1
- data/rack-rewrite.gemspec +1 -2
- data/test/rule_test.rb +23 -1
- metadata +3 -4
- data/README.rdoc +0 -266
data/Gemfile.lock
CHANGED
data/LICENSE
CHANGED
data/README.markdown
ADDED
@@ -0,0 +1,332 @@
|
|
1
|
+
# rack-rewrite
|
2
|
+
|
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
|
+
|
6
|
+
## Usage Examples
|
7
|
+
|
8
|
+
* [Rack::Rewrite for Site Maintenance and Downtime](http://blog.smartlogicsolutions.com/2009/11/16/rack-rewrite-for-site-maintenance-and-downtime/)
|
9
|
+
* [Rack::Rewrite + Google Analytics Makes Site Transitions Seamless](http://blog.smartlogicsolutions.com/2009/11/24/rack-rewrite-google-analytics-makes-site-transitions-seamless/)
|
10
|
+
|
11
|
+
## Usage Details
|
12
|
+
|
13
|
+
### Sample rackup file
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem 'rack-rewrite', '~> 1.2.1'
|
17
|
+
require 'rack/rewrite'
|
18
|
+
use Rack::Rewrite do
|
19
|
+
rewrite '/wiki/John_Trupiano', '/john'
|
20
|
+
r301 '/wiki/Yair_Flicker', '/yair'
|
21
|
+
r302 '/wiki/Greg_Jastrab', '/greg'
|
22
|
+
r301 %r{/wiki/(\w+)_\w+}, '/$1'
|
23
|
+
end
|
24
|
+
```
|
25
|
+
|
26
|
+
### Sample usage in a rails app
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
config.middleware.insert_before(Rack::Lock, Rack::Rewrite) do
|
30
|
+
rewrite '/wiki/John_Trupiano', '/john'
|
31
|
+
r301 '/wiki/Yair_Flicker', '/yair'
|
32
|
+
r302 '/wiki/Greg_Jastrab', '/greg'
|
33
|
+
r301 %r{/wiki/(\w+)_\w+}, '/$1'
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
## Redirection codes
|
38
|
+
|
39
|
+
All redirect status codes from the [HTTP spec](http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html) are supported:
|
40
|
+
|
41
|
+
* 301 moved permanently
|
42
|
+
* 302 found
|
43
|
+
* 303 see other
|
44
|
+
* 307 temporary redirect
|
45
|
+
|
46
|
+
These translate to the following methods inside the Rack::Rewrite block:
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
r301 '/wiki/John_Trupiano', '/john'
|
50
|
+
moved_permanently '/wiki/John_Trupiano', '/john'
|
51
|
+
p '/wiki/John_Trupiano', '/john' # shortcut alias
|
52
|
+
|
53
|
+
r302 '/wiki/John_Trupiano', '/john'
|
54
|
+
found '/wiki/John_Trupiano', '/john'
|
55
|
+
|
56
|
+
r303 '/wiki/John_Trupiano', '/john'
|
57
|
+
see_other '/wiki/John_Trupiano', '/john'
|
58
|
+
|
59
|
+
r307 '/wiki/John_Trupiano', '/john'
|
60
|
+
temporary_redirect '/wiki/John_Trupiano', '/john'
|
61
|
+
t '/wiki/John_Trupiano', '/john' # shortcut alias
|
62
|
+
```
|
63
|
+
|
64
|
+
The 303 and 307 codes were added to the HTTP spec to make unambiguously clear
|
65
|
+
what clients should do with the request method. 303 means that the new request
|
66
|
+
should always be made via GET. 307 means that the new request should use the
|
67
|
+
same method as the original request. Status code 302 was left as it is, since
|
68
|
+
it was already in use by the time these issues came to light. In practice it
|
69
|
+
behaves the same as 303.
|
70
|
+
|
71
|
+
## Use Cases
|
72
|
+
|
73
|
+
### Rebuild of existing site in a new technology
|
74
|
+
|
75
|
+
It's very common for sites built in older technologies to be rebuilt with the
|
76
|
+
latest and greatest. Let's consider a site that has already established quite
|
77
|
+
a bit of "google juice." When we launch the new site, we don't want to lose
|
78
|
+
that hard-earned reputation. By writing rewrite rules that issue 301's for
|
79
|
+
old URL's, we can "transfer" that google ranking to the new site. An example
|
80
|
+
rule might look like:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
r301 '/contact-us.php', '/contact-us'
|
84
|
+
r301 '/wiki/John_Trupiano', '/john'
|
85
|
+
```
|
86
|
+
|
87
|
+
### Retiring old routes
|
88
|
+
|
89
|
+
As a web application evolves you will undoubtedly reach a point where you need
|
90
|
+
to change the name of something (a model, e.g.). This name change will
|
91
|
+
typically require a similar change to your routing. The danger here is that
|
92
|
+
any URL's previously generated (in a transactional email for instance) will
|
93
|
+
have the URL hard-coded. In order for your rails app to continue to serve
|
94
|
+
this URL, you'll need to add an extra entry to your routes file.
|
95
|
+
Alternatively, you could use rack-rewrite to redirect or pass through requests
|
96
|
+
to these routes and keep your routes.rb clean.
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
rewrite %r{/features(.*)}, '/facial_features$1'
|
100
|
+
```
|
101
|
+
|
102
|
+
### CNAME alternative
|
103
|
+
|
104
|
+
In the event that you do not control your DNS, you can leverage Rack::Rewrite
|
105
|
+
to redirect to a canonical domain. In the following rule we utilize the
|
106
|
+
$& substitution operator to capture the entire request URI.
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
r301 %r{.*}, 'http://mynewdomain.com$&', :if => Proc.new {|rack_env|
|
110
|
+
rack_env['SERVER_NAME'] != 'mynewdomain.com'
|
111
|
+
}
|
112
|
+
```
|
113
|
+
|
114
|
+
### Site Maintenance
|
115
|
+
|
116
|
+
Most capistrano users will be familiar with the following Apache rewrite rules:
|
117
|
+
|
118
|
+
```
|
119
|
+
RewriteCond %{REQUEST_URI} !\.(css|jpg|png)$
|
120
|
+
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
|
121
|
+
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
|
122
|
+
RewriteRule ^.*$ /system/maintenance.html [L]
|
123
|
+
```
|
124
|
+
|
125
|
+
This rewrite rule says to render a maintenance page for all non-asset requests
|
126
|
+
if the maintenance file exists. In capistrano, you can quickly upload a
|
127
|
+
maintenance file using:
|
128
|
+
|
129
|
+
`cap deploy:web:disable REASON=upgrade UNTIL=12:30PM`
|
130
|
+
|
131
|
+
We can replace the mod_rewrite rules with the following Rack::Rewrite rule:
|
132
|
+
|
133
|
+
```ruby
|
134
|
+
maintenance_file = File.join(RAILS_ROOT, 'public', 'system', 'maintenance.html')
|
135
|
+
send_file /.*/, maintenance_file, :if => Proc.new { |rack_env|
|
136
|
+
File.exists?(maintenance_file) && rack_env['PATH_INFO'] !~ /\.(css|jpg|png)/
|
137
|
+
}
|
138
|
+
```
|
139
|
+
|
140
|
+
If you're running Ruby 1.9, this rule is simplified:
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
maintenance_file = File.join(RAILS_ROOT, 'public', 'system', 'maintenance.html')
|
144
|
+
send_file /(.*)$(?<!css|png|jpg)/, maintenance_file, :if => Proc.new { |rack_env|
|
145
|
+
File.exists?(maintenance_file)
|
146
|
+
}
|
147
|
+
```
|
148
|
+
|
149
|
+
For those using the oniguruma gem with their ruby 1.8 installation, you can
|
150
|
+
get away with:
|
151
|
+
|
152
|
+
```ruby
|
153
|
+
maintenance_file = File.join(RAILS_ROOT, 'public', 'system', 'maintenance.html')
|
154
|
+
send_file Oniguruma::ORegexp.new("(.*)$(?<!css|png|jpg)"), maintenance_file, :if => Proc.new { |rack_env|
|
155
|
+
File.exists?(maintenance_file)
|
156
|
+
}
|
157
|
+
```
|
158
|
+
|
159
|
+
## Rewrite Rules
|
160
|
+
|
161
|
+
### :rewrite
|
162
|
+
|
163
|
+
Calls to #rewrite will simply update the PATH_INFO, QUERY_STRING and
|
164
|
+
REQUEST_URI HTTP header values and pass the request onto the next chain in
|
165
|
+
the Rack stack. The URL that a user's browser will show will not be changed.
|
166
|
+
See these examples:
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
rewrite '/wiki/John_Trupiano', '/john' # [1]
|
170
|
+
rewrite %r{/wiki/(\w+)_\w+}, '/$1' # [2]
|
171
|
+
```
|
172
|
+
|
173
|
+
For [1], the user's browser will continue to display /wiki/John_Trupiano, but
|
174
|
+
the actual HTTP header values for PATH_INFO and REQUEST_URI in the request
|
175
|
+
will be changed to /john for subsequent nodes in the Rack stack. Rails
|
176
|
+
reads these headers to determine which routes will match.
|
177
|
+
|
178
|
+
Rule [2] showcases the use of regular expressions and substitutions. [2] is a
|
179
|
+
generalized version of [1] that will match any /wiki/FirstName_LastName URL's
|
180
|
+
and rewrite them as the first name only. This is an actual catch-all rule we
|
181
|
+
applied when we rebuilt our website in September 2009
|
182
|
+
( http://www.smartlogicsolutions.com ).
|
183
|
+
|
184
|
+
### :r301, :r302, :r303, :r307
|
185
|
+
|
186
|
+
Calls to #r301 and #r302 have the same signature as #rewrite. The difference,
|
187
|
+
however, is that these actually short-circuit the rack stack and send back
|
188
|
+
their respective status codes. See these examples:
|
189
|
+
|
190
|
+
```ruby
|
191
|
+
r301 '/wiki/John_Trupiano', '/john' # [1]
|
192
|
+
r301 '/wiki/(.*)', 'http://www.google.com/?q=$1' # [2]
|
193
|
+
```
|
194
|
+
|
195
|
+
Recall that rules are interpreted from top to bottom. So you can install
|
196
|
+
"default" rewrite rules if you like. [2] is a sample default rule that
|
197
|
+
will redirect all other requests to the wiki to a google search.
|
198
|
+
|
199
|
+
### :send_file, :x_send_file
|
200
|
+
|
201
|
+
Calls to #send_file and #x_send_file also have the same signature as #rewrite.
|
202
|
+
If the rule matches, the 'to' parameter is interpreted as a path to a file
|
203
|
+
to be rendered instead of passing the application call up the rack stack.
|
204
|
+
|
205
|
+
```ruby
|
206
|
+
send_file /*/, 'public/spammers.htm', :if => Proc.new { |rack_env|
|
207
|
+
rack_env['HTTP_REFERER'] =~ 'spammers.com'
|
208
|
+
}
|
209
|
+
x_send_file /^blog\/.*/, 'public/blog_offline.htm', :if => Proc.new { |rack_env|
|
210
|
+
File.exists?('public/blog_offline.htm')
|
211
|
+
}
|
212
|
+
```
|
213
|
+
|
214
|
+
## Options Parameter
|
215
|
+
|
216
|
+
Each rewrite rule takes an optional options parameter. The following options
|
217
|
+
are supported.
|
218
|
+
|
219
|
+
### :host
|
220
|
+
|
221
|
+
Using the :host option you can match requests to a specific hostname.
|
222
|
+
|
223
|
+
```ruby
|
224
|
+
r301 "/features", "/facial_features", :host => "facerecognizer.com"
|
225
|
+
```
|
226
|
+
This rule will only match when the hostname is "facerecognizer.com"
|
227
|
+
|
228
|
+
### :headers
|
229
|
+
|
230
|
+
Using the :headers option you can set custom response headers e.g. for HTTP
|
231
|
+
caching instructions.
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
r301 "/features", "/facial_features", :headers => {'Cache-Control' => 'no-cache'}
|
235
|
+
```
|
236
|
+
|
237
|
+
Please be aware that the :headers value above is evaluated only once at app boot and shared amongst all matching requests.
|
238
|
+
|
239
|
+
Use a Proc as the :headers option if you wish to determine the additional headers at request-time. For example:
|
240
|
+
|
241
|
+
```ruby
|
242
|
+
# We want the Expires value to always be 1 year in the future from now. If
|
243
|
+
# we didn't use a Proc here, then the Expires value would be set just once
|
244
|
+
# at app startup. The Proc will be evaluated for each matching request.
|
245
|
+
send_file /^.+\.(?:ico|jpg|jpeg|png|gif|)$/,
|
246
|
+
'public/$&',
|
247
|
+
:headers => lambda { { 'Expires' => 1.year.from_now.httpdate } }
|
248
|
+
```
|
249
|
+
|
250
|
+
### :method
|
251
|
+
|
252
|
+
Using the :method option you can restrict the matching of a rule by the HTTP
|
253
|
+
method of a given request.
|
254
|
+
|
255
|
+
```ruby
|
256
|
+
# redirect GET's one way
|
257
|
+
r301 "/players", "/current_players", :method => :get
|
258
|
+
|
259
|
+
# and redirect POST's another way
|
260
|
+
r302 "/players", "/no_longer_available.html?message=No&longer&supported", :method => :post
|
261
|
+
```
|
262
|
+
|
263
|
+
### :if
|
264
|
+
|
265
|
+
Using the :if option you can define arbitrary rule guards. Guards are any
|
266
|
+
object responding to #call that return true or false indicating whether the
|
267
|
+
rule matches. The following example demonstrates how the presence of a
|
268
|
+
maintenance page on the filesystem can be utilized to take your site(s) offline.
|
269
|
+
|
270
|
+
```ruby
|
271
|
+
maintenance_file = File.join(RAILS_ROOT, 'public', 'system', 'maintenance.html')
|
272
|
+
x_send_file /.*/, maintenance_file, :if => Proc.new { |rack_env|
|
273
|
+
File.exists?(maintenance_file)
|
274
|
+
}
|
275
|
+
```
|
276
|
+
|
277
|
+
### :not
|
278
|
+
|
279
|
+
Using the :not option you can negatively match against the path. This can
|
280
|
+
be useful when writing a regular expression match is difficult.
|
281
|
+
|
282
|
+
```ruby
|
283
|
+
rewrite %r{^\/features}, '/facial_features', :not => '/features'
|
284
|
+
```
|
285
|
+
|
286
|
+
This will not match the relative URL /features but would match /features.xml.
|
287
|
+
|
288
|
+
## Tips
|
289
|
+
|
290
|
+
### Keeping your querystring
|
291
|
+
|
292
|
+
When rewriting a URL, you may want to keep your querystring in tact (for
|
293
|
+
example if you're tracking traffic sources). You will need to include a
|
294
|
+
capture group and substitution pattern in your rewrite rule to achieve this.
|
295
|
+
|
296
|
+
```ruby
|
297
|
+
rewrite %r{/wiki/John_Trupiano(\?.*)?}, '/john$1'
|
298
|
+
```
|
299
|
+
|
300
|
+
This rule will store the querystring in a capture group (via `(?.*)` ) and
|
301
|
+
will substitute the querystring back into the rewritten URL (via `$1`).
|
302
|
+
|
303
|
+
### Arbitrary Rewriting
|
304
|
+
|
305
|
+
All rules support passing a Proc as the second argument allowing you to
|
306
|
+
perform arbitrary rewrites. The following rule will rewrite all requests
|
307
|
+
received between 12AM and 8AM to an unavailable page.
|
308
|
+
|
309
|
+
```ruby
|
310
|
+
rewrite %r{(.*)}, lambda { |match, rack_env|
|
311
|
+
Time.now.hour < 8 ? "/unavailable.html" : match[1]
|
312
|
+
}
|
313
|
+
```
|
314
|
+
|
315
|
+
## Contribute
|
316
|
+
|
317
|
+
rack-rewrite is maintained by [@travisjeffery](http://github.com/travisjeffery).
|
318
|
+
|
319
|
+
Here's the most direct way to get your work merged into the project.
|
320
|
+
|
321
|
+
- Fork the project
|
322
|
+
- Clone down your fork
|
323
|
+
- Create a feature branch
|
324
|
+
- Hack away and add tests, not necessarily in that order
|
325
|
+
- Make sure everything still passes by running tests
|
326
|
+
- If necessary, rebase your commits into logical chunks without errors
|
327
|
+
- Push the branch up to your fork
|
328
|
+
- Send a pull request for your branch
|
329
|
+
|
330
|
+
## Copyright
|
331
|
+
|
332
|
+
Copyright (c) 2012 — John Trupiano, Travis Jeffery. See LICENSE for details.
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.3.
|
1
|
+
1.3.1
|
data/lib/rack/rewrite/rule.rb
CHANGED
@@ -115,7 +115,14 @@ module Rack
|
|
115
115
|
# (b) alter env as necessary and return true
|
116
116
|
def apply!(env) #:nodoc:
|
117
117
|
interpreted_to = self.interpret_to(env)
|
118
|
-
additional_headers =
|
118
|
+
additional_headers = {}
|
119
|
+
if @options[:headers]
|
120
|
+
if @options[:headers].respond_to?(:call)
|
121
|
+
additional_headers = @options[:headers].call || {}
|
122
|
+
else
|
123
|
+
additional_headers = @options[:headers] || {}
|
124
|
+
end
|
125
|
+
end
|
119
126
|
case self.rule_type
|
120
127
|
when :r301
|
121
128
|
[301, {'Location' => interpreted_to, 'Content-Type' => Rack::Mime.mime_type(::File.extname(interpreted_to))}.merge!(additional_headers), [redirect_message(interpreted_to)]]
|
data/rack-rewrite.gemspec
CHANGED
data/test/rule_test.rb
CHANGED
@@ -97,6 +97,28 @@ class RuleTest < Test::Unit::TestCase
|
|
97
97
|
assert_equal 'no-cache', rule.apply!(env)[1]['Cache-Control']
|
98
98
|
end
|
99
99
|
end
|
100
|
+
|
101
|
+
should 'evaluate additional headers block once per redirect request' do
|
102
|
+
[:r301, :r302].each do |rule_type|
|
103
|
+
header_val = 'foo'
|
104
|
+
rule = Rack::Rewrite::Rule.new(rule_type, %r{/abc}, '/def.css', {:headers => lambda { {'X-Foobar' => header_val} } })
|
105
|
+
env = {'PATH_INFO' => '/abc'}
|
106
|
+
assert_equal 'foo', rule.apply!(env)[1]['X-Foobar']
|
107
|
+
header_val = 'bar'
|
108
|
+
assert_equal 'bar', rule.apply!(env)[1]['X-Foobar']
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
should 'evaluate additional headers block once per send file request' do
|
113
|
+
[:send_file, :x_send_file].each do |rule_type|
|
114
|
+
header_val = 'foo'
|
115
|
+
rule = Rack::Rewrite::Rule.new(rule_type, /.*/, File.join(TEST_ROOT, 'geminstaller.yml'), {:headers => lambda { {'X-Foobar' => header_val} } })
|
116
|
+
env = {'PATH_INFO' => '/abc'}
|
117
|
+
assert_equal 'foo', rule.apply!(env)[1]['X-Foobar']
|
118
|
+
header_val = 'bar'
|
119
|
+
assert_equal 'bar', rule.apply!(env)[1]['X-Foobar']
|
120
|
+
end
|
121
|
+
end
|
100
122
|
|
101
123
|
context 'Given an :x_send_file rule that matches' do
|
102
124
|
setup do
|
@@ -125,7 +147,7 @@ class RuleTest < Test::Unit::TestCase
|
|
125
147
|
should 'return additional headers' do
|
126
148
|
assert_equal 'no-cache', @response[1]['Cache-Control']
|
127
149
|
end
|
128
|
-
|
150
|
+
|
129
151
|
should 'return empty content' do
|
130
152
|
assert_equal [], @response[2]
|
131
153
|
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: 1.3.
|
4
|
+
version: 1.3.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-
|
13
|
+
date: 2012-11-03 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: bundler
|
@@ -84,11 +84,10 @@ extensions: []
|
|
84
84
|
extra_rdoc_files:
|
85
85
|
- LICENSE
|
86
86
|
- History.rdoc
|
87
|
-
- README.rdoc
|
88
87
|
files:
|
89
88
|
- History.rdoc
|
90
89
|
- LICENSE
|
91
|
-
- README.
|
90
|
+
- README.markdown
|
92
91
|
- Rakefile
|
93
92
|
- VERSION
|
94
93
|
- Gemfile
|
data/README.rdoc
DELETED
@@ -1,266 +0,0 @@
|
|
1
|
-
= rack-rewrite
|
2
|
-
|
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
|
-
|
6
|
-
== References
|
7
|
-
|
8
|
-
* Source[http://github.com/jtrupiano/rack-rewrite]
|
9
|
-
* {Rack::Rewrite for Site Maintenance and Downtime}[http://blog.smartlogicsolutions.com/2009/11/16/rack-rewrite-for-site-maintenance-and-downtime/]
|
10
|
-
* {Rack::Rewrite + Google Analytics Makes Site Transitions Seamless}[http://blog.smartlogicsolutions.com/2009/11/24/rack-rewrite-google-analytics-makes-site-transitions-seamless/]
|
11
|
-
|
12
|
-
== Usage
|
13
|
-
|
14
|
-
=== Sample rackup file
|
15
|
-
|
16
|
-
gem 'rack-rewrite', '~> 1.2.1'
|
17
|
-
require 'rack/rewrite'
|
18
|
-
use Rack::Rewrite do
|
19
|
-
rewrite '/wiki/John_Trupiano', '/john'
|
20
|
-
r301 '/wiki/Yair_Flicker', '/yair'
|
21
|
-
r302 '/wiki/Greg_Jastrab', '/greg'
|
22
|
-
r301 %r{/wiki/(\w+)_\w+}, '/$1'
|
23
|
-
end
|
24
|
-
|
25
|
-
=== Sample usage in a rails app
|
26
|
-
|
27
|
-
config.middleware.insert_before(Rack::Lock, Rack::Rewrite) do
|
28
|
-
rewrite '/wiki/John_Trupiano', '/john'
|
29
|
-
r301 '/wiki/Yair_Flicker', '/yair'
|
30
|
-
r302 '/wiki/Greg_Jastrab', '/greg'
|
31
|
-
r301 %r{/wiki/(\w+)_\w+}, '/$1'
|
32
|
-
end
|
33
|
-
|
34
|
-
== Redirection codes
|
35
|
-
|
36
|
-
All +redirect+ status codes from the HTTP spec[http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html] are supported:
|
37
|
-
|
38
|
-
* 301 moved permanently
|
39
|
-
* 302 found
|
40
|
-
* 303 see other
|
41
|
-
* 307 temporary redirect
|
42
|
-
|
43
|
-
These translate to the following methods inside the Rack::Rewrite block:
|
44
|
-
|
45
|
-
r301 '/wiki/John_Trupiano', '/john'
|
46
|
-
moved_permanently '/wiki/John_Trupiano', '/john'
|
47
|
-
p '/wiki/John_Trupiano', '/john' # shortcut alias
|
48
|
-
|
49
|
-
r302 '/wiki/John_Trupiano', '/john'
|
50
|
-
found '/wiki/John_Trupiano', '/john'
|
51
|
-
|
52
|
-
r303 '/wiki/John_Trupiano', '/john'
|
53
|
-
see_other '/wiki/John_Trupiano', '/john'
|
54
|
-
|
55
|
-
r307 '/wiki/John_Trupiano', '/john'
|
56
|
-
temporary_redirect '/wiki/John_Trupiano', '/john'
|
57
|
-
t '/wiki/John_Trupiano', '/john' # shortcut alias
|
58
|
-
|
59
|
-
The 303 and 307 codes were added to the HTTP spec to make unambiguously clear
|
60
|
-
what clients should do with the request method. 303 means that the new request
|
61
|
-
should always be made via GET. 307 means that the new request should use the
|
62
|
-
same method as the original request. Status code 302 was left as it is, since
|
63
|
-
it was already in use by the time these issues came to light. In practice it
|
64
|
-
behaves the same as 303.
|
65
|
-
|
66
|
-
== Use Cases
|
67
|
-
|
68
|
-
=== Rebuild of existing site in a new technology
|
69
|
-
|
70
|
-
It's very common for sites built in older technologies to be rebuilt with the
|
71
|
-
latest and greatest. Let's consider a site that has already established quite
|
72
|
-
a bit of "google juice." When we launch the new site, we don't want to lose
|
73
|
-
that hard-earned reputation. By writing rewrite rules that issue 301's for
|
74
|
-
old URL's, we can "transfer" that google ranking to the new site. An example
|
75
|
-
rule might look like:
|
76
|
-
|
77
|
-
r301 '/contact-us.php', '/contact-us'
|
78
|
-
r301 '/wiki/John_Trupiano', '/john'
|
79
|
-
|
80
|
-
=== Retiring old routes
|
81
|
-
|
82
|
-
As a web application evolves you will undoubtedly reach a point where you need
|
83
|
-
to change the name of something (a model, e.g.). This name change will
|
84
|
-
typically require a similar change to your routing. The danger here is that
|
85
|
-
any URL's previously generated (in a transactional email for instance) will
|
86
|
-
have the URL hard-coded. In order for your rails app to continue to serve
|
87
|
-
this URL, you'll need to add an extra entry to your routes file.
|
88
|
-
Alternatively, you could use rack-rewrite to redirect or pass through requests
|
89
|
-
to these routes and keep your routes.rb clean.
|
90
|
-
|
91
|
-
rewrite %r{/features(.*)}, '/facial_features$1'
|
92
|
-
|
93
|
-
=== CNAME alternative
|
94
|
-
|
95
|
-
In the event that you do not control your DNS, you can leverage Rack::Rewrite
|
96
|
-
to redirect to a canonical domain. In the following rule we utilize the
|
97
|
-
$& substitution operator to capture the entire request URI.
|
98
|
-
|
99
|
-
r301 %r{.*}, 'http://mynewdomain.com$&', :if => Proc.new {|rack_env|
|
100
|
-
rack_env['SERVER_NAME'] != 'mynewdomain.com'
|
101
|
-
}
|
102
|
-
|
103
|
-
=== Site Maintenance
|
104
|
-
|
105
|
-
Most capistrano users will be familiar with the following Apache rewrite rules:
|
106
|
-
|
107
|
-
RewriteCond %{REQUEST_URI} !\.(css|jpg|png)$
|
108
|
-
RewriteCond %{DOCUMENT_ROOT}/system/maintenance.html -f
|
109
|
-
RewriteCond %{SCRIPT_FILENAME} !maintenance.html
|
110
|
-
RewriteRule ^.*$ /system/maintenance.html [L]
|
111
|
-
|
112
|
-
This rewrite rule says to render a maintenance page for all non-asset requests
|
113
|
-
if the maintenance file exists. In capistrano, you can quickly upload a
|
114
|
-
maintenance file using:
|
115
|
-
|
116
|
-
cap deploy:web:disable REASON=upgrade UNTIL=12:30PM
|
117
|
-
|
118
|
-
We can replace the mod_rewrite rules with the following Rack::Rewrite rule:
|
119
|
-
|
120
|
-
maintenance_file = File.join(RAILS_ROOT, 'public', 'system', 'maintenance.html')
|
121
|
-
send_file /.*/, maintenance_file, :if => Proc.new { |rack_env|
|
122
|
-
File.exists?(maintenance_file) && rack_env['PATH_INFO'] !~ /\.(css|jpg|png)/
|
123
|
-
}
|
124
|
-
|
125
|
-
If you're running Ruby 1.9, this rule is simplified:
|
126
|
-
|
127
|
-
maintenance_file = File.join(RAILS_ROOT, 'public', 'system', 'maintenance.html')
|
128
|
-
send_file /(.*)$(?<!css|png|jpg)/, maintenance_file, :if => Proc.new { |rack_env|
|
129
|
-
File.exists?(maintenance_file)
|
130
|
-
}
|
131
|
-
|
132
|
-
For those using the oniguruma gem with their ruby 1.8 installation, you can
|
133
|
-
get away with:
|
134
|
-
|
135
|
-
maintenance_file = File.join(RAILS_ROOT, 'public', 'system', 'maintenance.html')
|
136
|
-
send_file Oniguruma::ORegexp.new("(.*)$(?<!css|png|jpg)"), maintenance_file, :if => Proc.new { |rack_env|
|
137
|
-
File.exists?(maintenance_file)
|
138
|
-
}
|
139
|
-
|
140
|
-
== Rewrite Rules
|
141
|
-
|
142
|
-
=== :rewrite
|
143
|
-
|
144
|
-
Calls to #rewrite will simply update the PATH_INFO, QUERY_STRING and
|
145
|
-
REQUEST_URI HTTP header values and pass the request onto the next chain in
|
146
|
-
the Rack stack. The URL that a user's browser will show will not be changed.
|
147
|
-
See these examples:
|
148
|
-
|
149
|
-
rewrite '/wiki/John_Trupiano', '/john' # [1]
|
150
|
-
rewrite %r{/wiki/(\w+)_\w+}, '/$1' # [2]
|
151
|
-
|
152
|
-
For [1], the user's browser will continue to display /wiki/John_Trupiano, but
|
153
|
-
the actual HTTP header values for PATH_INFO and REQUEST_URI in the request
|
154
|
-
will be changed to /john for subsequent nodes in the Rack stack. Rails
|
155
|
-
reads these headers to determine which routes will match.
|
156
|
-
|
157
|
-
Rule [2] showcases the use of regular expressions and substitutions. [2] is a
|
158
|
-
generalized version of [1] that will match any /wiki/FirstName_LastName URL's
|
159
|
-
and rewrite them as the first name only. This is an actual catch-all rule we
|
160
|
-
applied when we rebuilt our website in September 2009
|
161
|
-
( http://www.smartlogicsolutions.com ).
|
162
|
-
|
163
|
-
=== :r301, :r302, :r303, :r307
|
164
|
-
|
165
|
-
Calls to #r301 and #r302 have the same signature as #rewrite. The difference,
|
166
|
-
however, is that these actually short-circuit the rack stack and send back
|
167
|
-
their respective status codes. See these examples:
|
168
|
-
|
169
|
-
r301 '/wiki/John_Trupiano', '/john' # [1]
|
170
|
-
r301 '/wiki/(.*)', 'http://www.google.com/?q=$1' # [2]
|
171
|
-
|
172
|
-
Recall that rules are interpreted from top to bottom. So you can install
|
173
|
-
"default" rewrite rules if you like. [2] is a sample default rule that
|
174
|
-
will redirect all other requests to the wiki to a google search.
|
175
|
-
|
176
|
-
=== :send_file, :x_send_file
|
177
|
-
|
178
|
-
Calls to #send_file and #x_send_file also have the same signature as #rewrite.
|
179
|
-
If the rule matches, the 'to' parameter is interpreted as a path to a file
|
180
|
-
to be rendered instead of passing the application call up the rack stack.
|
181
|
-
|
182
|
-
send_file /*/, 'public/spammers.htm', :if => Proc.new { |rack_env|
|
183
|
-
rack_env['HTTP_REFERER'] =~ 'spammers.com'
|
184
|
-
}
|
185
|
-
x_send_file /^blog\/.*/, 'public/blog_offline.htm', :if => Proc.new { |rack_env|
|
186
|
-
File.exists?('public/blog_offline.htm')
|
187
|
-
}
|
188
|
-
|
189
|
-
== Options Parameter
|
190
|
-
|
191
|
-
Each rewrite rule takes an optional options parameter. The following options
|
192
|
-
are supported.
|
193
|
-
|
194
|
-
=== :host
|
195
|
-
|
196
|
-
Using the :host option you can match requests to a specific hostname.
|
197
|
-
|
198
|
-
r301 "/features", "/facial_features", :host => "facerecognizer.com"
|
199
|
-
|
200
|
-
This rule will only match when the hostname is "facerecognizer.com"
|
201
|
-
|
202
|
-
=== :headers
|
203
|
-
|
204
|
-
Using the :headers option you can set custom response headers e.g. for HTTP
|
205
|
-
caching instructions.
|
206
|
-
|
207
|
-
r301 "/features", "/facial_features", :headers => {'Cache-Control' => 'no-cache'}
|
208
|
-
|
209
|
-
=== :method
|
210
|
-
|
211
|
-
Using the :method option you can restrict the matching of a rule by the HTTP
|
212
|
-
method of a given request.
|
213
|
-
|
214
|
-
# redirect GET's one way
|
215
|
-
r301 "/players", "/current_players", :method => :get
|
216
|
-
|
217
|
-
# and redirect POST's another way
|
218
|
-
r302 "/players", "/no_longer_available.html?message=No&longer&supported", :method => :post
|
219
|
-
|
220
|
-
=== :if
|
221
|
-
|
222
|
-
Using the :if option you can define arbitrary rule guards. Guards are any
|
223
|
-
object responding to #call that return true or false indicating whether the
|
224
|
-
rule matches. The following example demonstrates how the presence of a
|
225
|
-
maintenance page on the filesystem can be utilized to take your site(s) offline.
|
226
|
-
|
227
|
-
maintenance_file = File.join(RAILS_ROOT, 'public', 'system', 'maintenance.html')
|
228
|
-
x_send_file /.*/, maintenance_file, :if => Proc.new { |rack_env|
|
229
|
-
File.exists?(maintenance_file)
|
230
|
-
}
|
231
|
-
|
232
|
-
=== :not
|
233
|
-
|
234
|
-
Using the :not option you can negatively match against the path. This can
|
235
|
-
be useful when writing a regular expression match is difficult.
|
236
|
-
|
237
|
-
rewrite %r{^\/features}, '/facial_features', :not => '/features'
|
238
|
-
|
239
|
-
This will not match the relative URL /features but would match /features.xml.
|
240
|
-
|
241
|
-
== Tips
|
242
|
-
|
243
|
-
=== Keeping your querystring
|
244
|
-
|
245
|
-
When rewriting a URL, you may want to keep your querystring in tact (for
|
246
|
-
example if you're tracking traffic sources). You will need to include a
|
247
|
-
capture group and substitution pattern in your rewrite rule to achieve this.
|
248
|
-
|
249
|
-
rewrite %r{/wiki/John_Trupiano(\?.*)?}, '/john$1'
|
250
|
-
|
251
|
-
This rule will store the querystring in a capture group (via '(?.*)' ) and
|
252
|
-
will substitute the querystring back into the rewritten URL (via $1).
|
253
|
-
|
254
|
-
=== Arbitrary Rewriting
|
255
|
-
|
256
|
-
All rules support passing a Proc as the second argument allowing you to
|
257
|
-
perform arbitrary rewrites. The following rule will rewrite all requests
|
258
|
-
received between 12AM and 8AM to an unavailable page.
|
259
|
-
|
260
|
-
rewrite %r{(.*)}, lambda { |match, rack_env|
|
261
|
-
Time.now.hour < 8 ? "/unavailable.html" : match[1]
|
262
|
-
}
|
263
|
-
|
264
|
-
== Copyright
|
265
|
-
|
266
|
-
Copyright (c) 2009-2011 John Trupiano. See LICENSE for details.
|