10

It is a simple issue, but I can't seem to find an answer doing some quick googling.

What's the Ruby on Rails way of doing this 301 direct (http://x.com/abc > http://www.x.com/abc). A before_filter?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Newy
  • 38,977
  • 9
  • 43
  • 59

7 Answers7

19

Ideally you'd do this in your web server (Apache, nginx etc.) configuation so that the request doesn't even touch Rails at all.

Add the following before_filter to your ApplicationController:

class ApplicationController < ActionController::Base
  before_filter :add_www_subdomain

  private
  def add_www_subdomain
    unless /^www/.match(request.host)
      redirect_to("#{request.protocol}x.com#{request.request_uri}",
                  :status => 301)
    end
  end
end

If you did want to do the redirect using Apache, you could use this:

RewriteEngine on
RewriteCond %{HTTP_HOST} !^www\.x\.com [NC]
RewriteRule ^(.*)$ http://www.x.com/$1 [R=301,L]
John Topley
  • 113,588
  • 46
  • 195
  • 237
  • Great answer John. If you are using Rails >= 2.3, I would suggest to use a Metal instead. :) – Simone Carletti Nov 10 '09 at 08:07
  • 2
    For a 301 redirect in the code above, you need to include the status. Otherwise it's just a 302 Temporary Redirect. redirect_to("#{request.protocol}x.com#{request.request_uri}", :status=>301) – Terrell Miller Apr 12 '10 at 18:25
  • I really think this should be best done in the server config as you said in apache, not in Rails. No reason to have this code in your rails app when it more optimally done on the http server (Nginx, Apache...). – Matt Smith Feb 01 '12 at 22:25
  • Rails 3.2 and above routing is the better solution. Use the `redirect` option for `match` to do the proper HTTP 301 redirect to the "canonical" page. If that page may be several of the same, (e.g. `search?page=2&count=10`) then make sure to add the correct "canonical" links to the head of your document. – Tom Harrison Aug 12 '13 at 02:32
  • @TomHarrisonJr You don't say why it is the better solution. I can see that there are maintenance benefits to keeping everything in Rails, but there are also performance benefits to bypassing the Rails stack completely for some things. – John Topley Aug 12 '13 at 08:13
  • 1
    @JohnTopley Fair enough. Here's why: having just completed a major overhaul of canonicalization/redirection of URLs on a large Rails site, we're not seeing significant performance differences from when we did standard Apache redirection (perhaps 1 or 2ms). Our case, at least is complex: we have https, a multi-tenant application (customer.example.com and www.example.com redirect to example.com, but are handled differently) and so on. In my experience maintaining rewrites/redirects in Apache becomes cumbersome. Rails routing provides excellent facilities for all of these nuances. – Tom Harrison Aug 13 '13 at 12:33
  • @TomHarrisonJr Thanks. It's interesting to learn that there's not much performance difference nowadays. – John Topley Aug 13 '13 at 13:23
  • `request.fullpath` is for Rails 3+ what used to be `request.request_uri` for Rails 2. – pierrea Mar 24 '17 at 00:23
9

For rails 4, use it -

  before_filter :add_www_subdomain

  private
  def add_www_subdomain
    unless /^www/.match(request.host)
      redirect_to("#{request.protocol}www.#{request.host_with_port}",status: 301)
    end
  end
Tim Kretschmer
  • 2,272
  • 1
  • 22
  • 35
Mashpy Rahman
  • 698
  • 8
  • 16
7

While John's answer is perfectly fine, if you are using Rails >= 2.3 I would suggest to create a new Metal. Rails Metals are more efficient and they offers better performance.

$ ruby script/generate metal NotWwwToWww

Then open the file and paste the following code.

# Allow the metal piece to run in isolation
require(File.dirname(__FILE__) + "/../../config/environment") unless defined?(Rails)

class NotWwwToWww
  def self.call(env)
    if env["HTTP_HOST"] != 'www.example.org'
      [301, {"Content-Type" => "text/html", "Location" => "www.#{env["HTTP_HOST"]}"}, ["Redirecting..."]]
    else
      [404, {"Content-Type" => "text/html"}, ["Not Found"]]
    end
  end
end

Of course, you can customize further the Metal.

If you want to use Apache, here's a few configurations.

Simone Carletti
  • 173,507
  • 49
  • 363
  • 364
3

There is a better Rails 3 way - put this in your routes.rb file:

  constraints(:host => "example.com") do
    # Won't match root path without brackets around "*x". (using Rails 3.0.3)
    match "(*x)" => redirect { |params, request|
      URI.parse(request.url).tap { |x| x.host = "www.example.com" }.to_s
    }
  end

Update

Here is how to make it domain agnostic:

  constraints(subdomain: '') do
    match "(*x)" => redirect do |params, request|
      URI.parse(request.url).tap { |x| x.host = "www.#{x.host}" }.to_s
    end
  end
Pavel Nikolov
  • 9,401
  • 5
  • 43
  • 55
1

An alternative solution might be to use the rack-canonical-host gem, which has a lot of additional flexibility. Adding a line to config.ru:

use Rack::CanonicalHost, 'www.example.com', if: 'example.com'

will redirect to www.example.com only if the host matches example.com. Lots of other examples in the github README.

MZB
  • 2,071
  • 3
  • 21
  • 38
0

You can try the below code -

location / {
  if ($http_host ~* "^example.com"){
    rewrite ^(.*)$ http://www.example.com$1 redirect;
  }
}
Anand S Kumar
  • 88,551
  • 18
  • 188
  • 176
Daniel Filho
  • 91
  • 1
  • 1
  • 3
0

I found this article when trying to achieve the opposite (www to root domain redirection). So I wrote the piece of code that redirects all pages from www to the root domain.

Aymeric
  • 233
  • 2
  • 6