如何在 Rails 3 / Rack 中实现无 www 重定向?

36

我知道有很多问题回答了这个。我熟悉.htaccessnginx.conf方法,但我在Heroku上没有访问这些传统配置方法的权限。

Simone Carletti 给出了一个利用 Rails 2.x Metals 的答案,但是我正在使用 Rails 3,所以它不兼容:

将非 www 请求重定向到 Ruby on Rails 中的 www URL

请注意:

我不想在我的 ApplicationController 中使用简单的 before_filter。我想要实现类似 Simone 的重写。我相信这是 Web 服务器或像 Rack 这样的中间件的工作,因此我想将这一点留出实际应用程序代码的范畴。

目标

redirect                to                  status
----------------------------------------------------
www.foo.com             foo.com             301
www.foo.com/whatever    foo.com/whatever    301

只有匹配/^www\./的主机应该被重定向,所有其他请求应该被忽略。


我不理解每个方法的必要性。我的中间件没有它,但是它们能够很好地工作。 - radiospiel
啊,现在我明白了:在重定向的情况下,self作为响应体返回,并且该体必须响应respond_to? :each。这可能不是规范的方式。我建议只返回一个空字符串或[]作为响应体。 - radiospiel
2
我知道我来晚了,但对于那些在以后搜索和使用此内容的用户:请注意,这可能会导致您的测试以奇怪的方式失败,因为rspec测试的DEFAULT_HOST是“www.example.com”,所有请求都将被重定向,即使在您的测试中也是如此! - valscion
9个回答

51

Ruby on Rails 4中,保留路径的情况下从任何URL中去除www.可以通过以下方式实现:

# config/routes.rb

constraints subdomain: 'www' do
  get ':any', to: redirect(subdomain: nil, path: '/%{any}'), any: /.*/
end

相比之下,如果URL中原本没有www.,可以通过以下方式添加:

# config/routes.rb

constraints subdomain: false do
  get ':any', to: redirect(subdomain: 'www', path: '/%{any}'), any: /.*/
end

将此答案标记为适用于Rails 4用户的已接受答案。如果您使用的是Rails <= 3,请参阅本主题中的其他答案。 - maček
这真是太棒了! - alexzg
2
我不确定是我的问题还是使用constraints subdomain: false时,由于某种原因它实际上会进行两次重定向,最终导致在浏览器中看到重定向localhost:3000 => www.localhost:3000 => www.www.localhost:3000 - Leo Correa
2
它无法工作的原因是路由器检查顶级域名和域名,因此localhostwww.localhost没有子域名。 - Leo Correa
我想仅在子域为空时重定向,如果已经有子域,则不要重定向。可能吗? - Blankman
显示剩余2条评论

13

如果你正在使用Rails 3,有更好的方法。只需利用路由功能。

Foo::Application.routes.draw do
  constraints(:host => /^example.com/) do
    root :to => redirect("http://www.example.com")
    match '/*path', :to => redirect {|params| "http://www.example.com/#{params[:path]}"}
  end
end

当指定了www时,我想重定向不带www。你能否调整你的答案。如果有效,我会将其标记为已接受。 :) - maček
1
你能否将它变成领域无关的呢?例如,类似于 "#{params[:protocol]}://#{params[:host]}/#{params[:path]}" 这样的形式? - maček

12

我非常喜欢使用Rails Router来处理这种事情。之前的答案很好,但我想要一个通用的解决方案,可以用于任何以“www”开头的URL。

我认为这是一个不错的解决方案:

constraints(:host => /^www\./) do
  match "(*x)" => redirect { |params, request|
    URI.parse(request.url).tap {|url| url.host.sub!('www.', '') }.to_s
  }
end

7

杜克(Duke)方案的一行代码版本。只需将其添加到routes.rb文件的顶部即可。

match '(*any)' => redirect { |p, req| req.url.sub('www.', '') }, :constraints => { :host => /^www\./ }

谢谢!你能否再做一个版本,包括从onedomain.com重定向到onedomain.net?如果两个域名共享相同的名称,是否可以像req.url.sub('www.','').sub('net','com')这样? - Daniel Dener

7

Ilya,我将此标记为已接受,但我想到了一个稍微更加优雅/健壮的解决方案。根据Rack规范,“Body本身不应该是String的实例,因为这在Ruby 1.9中会出错。”所以我在这里进行了调整。此外,我使用Rack::Request对象处理URL并使代码更加简洁。 - maček

6

在Rails 3中

#config/routes.rb
Example::Application.routes.draw do
  constraints(:host => "www.example.net") do
    match "(*x)" => redirect { |params, request|
      URI.parse(request.url).tap { |x| x.host = "example.net" }.to_s
    }
  end
  # .... 
  # .. more routes ..
  # ....
end

链接已损坏。 - Reinstate Monica -- notmaynard
@iamnotmaynard,我在我的回答中已经包含了相关的代码。 - Vikrant Chaudhary

3
如果您想将顶级域名(TLD)重定向到www子域名,请使用以下代码:
constraints :subdomain => '' do
  match '(*any)' => redirect { |p, req| req.url.sub('//', '//www.') }
end

注意:这段代码使用sub而非gsub,因为sub只替换第一次出现的双斜杠,而gsub会替换所有的双斜杠。

这个问题的反向回答非常有用 - 谢谢! - Troy

3

对于Rails 4,上述解决方案必须添加动词结构,例如via: [:get, :post]。 Duke的解决方案变为:

  constraints(:host => /^www\./) do
    match "(*x)" => redirect { |params, request|
      URI.parse(request.url).tap {|url| url.host.sub!('www.', '') }.to_s
    }, via: [:get, :post]
  end

2

以上方法没有问题,但也有一些宝石提供了Rack中间件来实现这个功能。

我喜欢他们将此行为与应用程序本身分开的方式,但无论如何都不是一个特别强的论点。当我使用Sinatra时,我也使用中间件来完成这个工作,因此更喜欢使用一种可以在构建自Rails和/或Sinatra的应用程序上使用的技术(我经常在Rails中嵌入运行Nesta)。

无论如何,以下是它们:

第一个更简单(也是我一直在使用的),而第二个则提供了更多功能(我尚未需要,但很感激)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接