使用SSL的代理后面的Rails应用程序将路径呈现为" http://"

时间:2014-09-26 21:45:46

标签: ruby-on-rails ssl nginx proxy unicorn

首先,这听起来更像是一个错误,而不是其他任何东西。

我的rails应用程序由Unicorn提供服务。然后,使用Nginx作为反向代理,我使用SSL将应用程序提供给外部世界。

到目前为止这么好,没问题。我使用相对路径(Restful path helpers),所以生成它(https://www.example.com)应该没有问题:

new_entry_path => https://www.example.com/entries/new

大多数情况下都可以正常使用。

但是在控制器中我会尝试重定向到" show"动作(使用资源),让我们说成功更新后(假设输入ID为100):

redirect_to @entry, flash: {success: "Entry has been updated"}

redirect_to entry_path(@entry), flash: {success: "Entry has been updated"}

他们都产生了重定向:

http://www.example.com/entries/100 # missing 's' in https...

而不是

/entries/100 # implying https://www.example.com/entries/100

据我所知,这只会在show操作时发生,并且仅在控制器重定向中发生。

我通过做一些可怕和令人作呕的事情来绕过这个:

redirect_to entry_url(@entry).sub(/^http\:/,"https:"), flash: {success: "Entry has been updated"}

有没有人遇到类似的东西?任何想法都将被感激地接受......

3 个答案:

答案 0 :(得分:6)

我有类似的问题。默认情况下,Rails将使用*_url帮助程序的当前协议。

我们使用nginx作为Web服务器,unicorn作为应用服务器。 Nginx接受请求,解开SSL部分,然后将其传递给独角兽。因此,独角兽总是收到http请求。如果我们现在希望Rails知道原始协议,我们可能需要添加X-Forwarded-Proto标头。

示例配置:

upstream app {
    server unix:/var/run/myserver/unicorn.sock fail_timeout=0;
}
server {
    listen       443 ssl;
    server_name  myserver;

    ssl_certificate "/etc/pki/nginx/myserver.crt";
    ssl_certificate_key "/etc/pki/nginx/private/myserver.key";

    root /myserver/public;

    try_files $uri/index.html $uri @app;
    sendfile on;

    location @app {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto https; # <--- will be used
        proxy_set_header Host $http_host;
        proxy_redirect off;
        proxy_pass http://app;
   }
}

答案 1 :(得分:1)

到目前为止,我已经设法通过在简单的http:

下向Nginx添加重写规则来获得解决方法
rewrite ^/(.*)$  https://www.example.com/$1? permanent;

将所有普通http请求重定向到https服务器。

<强>更新

显然,因为我希望应用程序不关心前端Web服务器如何服务它,所以由同一个Web服务器来“清除重定向的混乱”应用程序本身无法处理,除非它是专门配置的(不希望如此)。所以,我会坚持这个答案(解决方法而不是......)

<强>更新
按照papirtiger的回答,我看到我最终错过了闪光,这些闪光应作为参数添加到覆盖的redirect_to

但是我已经找到了一种方法,可以简单地通过覆盖从redirect_to内部调用的不同功能来轻松完成我的生活。

def _compute_redirect_to_location(options) #:nodoc:
    case options
    # The scheme name consist of a letter followed by any combination of
    # letters, digits, and the plus ("+"), period ("."), or hyphen ("-")
    # characters; and is terminated by a colon (":").
    # See http://tools.ietf.org/html/rfc3986#section-3.1
    # The protocol relative scheme starts with a double slash "//".
    when /\A([a-z][a-z\d\-+\.]*:|\/\/).*/i
        options
    ## WHEN STRING: THIS IS REMOVED TO AVOID ADDING PROTOCOL AND HOST ##
    # when String
    #   request.protocol + request.host_with_port + options
    when :back
        request.headers["Referer"] or raise RedirectBackError
    when Proc
        _compute_redirect_to_location options.call
    else
        url_for(options)
    end.delete("\0\r\n")
end

因此,无需更改我的代码中的任何其他内容,我就有一个工作相对重定向。

答案 2 :(得分:1)

我认为force_ssl正是您所寻找的。

class AccountsController < ApplicationController
  force_ssl if: :ssl_configured?

  def ssl_configured?
    !Rails.env.development?
  end
end

编辑,如果您真的想要重定向到相对路径,您可以随时创建自己的帮助器:

module ActionController
  module RelativeRedirectingHelper
    extend ActiveSupport::Concern

    include AbstractController::Logger
    include ActionController::RackDelegation
    include ActionController::UrlFor

    def redirect_to_relative(path, response_status = 302) #:doc:
      raise ActionControllerError.new("Cannot redirect to nil!") unless options
      raise ActionControllerError.new("Cannot redirect to a parameter hash!") if options.is_a?(ActionController::Parameters)
      raise AbstractController::DoubleRenderError if response_body

      self.status        = response_status
      self.location      = path
      self.response_body = "<html><body>You are being <a href=\"#{ERB::Util.h(location)}\">redirected</a>.</body></html>"
    end
  end
end

这是一个快速复制的粘贴作业。如果您想要与redirect_to

具有相同的签名,则需要更多努力