Reputation: 5740
To start with, this sounds more like a bug then anything else.
My rails application is served by Unicorn. Then, using Nginx as a reverse proxy, I serve the application to the outside world using SSL.
So far so good, no problem. I'm using relative paths (Restful path helpers), so there should be no problem to produce this (for https://www.example.com):
new_entry_path => https://www.example.com/entries/new
This works fine in most cases.
The problem however appears when in a controller I try to redirect to a "show" action (using resources), let's say after a successful update (suppose Entry with id 100):
redirect_to @entry, flash: {success: "Entry has been updated"}
or
redirect_to entry_path(@entry), flash: {success: "Entry has been updated"}
they both produce a redirect to:
http://www.example.com/entries/100 # missing 's' in https...
instead of
/entries/100 # implying https://www.example.com/entries/100
As far as I've noticed, this only happens with show
action and only in controller redirects.
I'm bypassing this by doing something horrible and disgusting:
redirect_to entry_url(@entry).sub(/^http\:/,"https:"), flash: {success: "Entry has been updated"}
Has anyone ever confronted something similar? Any ideas will be gratefully accepted...
Upvotes: 4
Views: 3313
Reputation: 1862
I had a similar problem. By default Rails will use the current protocol for the *_url
helpers.
We use nginx as web server and unicorn as application server. Nginx takes the request, unwrapps the SSL part and then passes it on to unicorn. Hence, unicorn always receives a http
request. If we now want Rails to know about the original protocol, we can need to add the X-Forwarded-Proto
header.
Example config:
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;
}
}
Upvotes: 7
Reputation: 5740
So far, I've managed to get a workaround by adding a rewrite rule to Nginx, under plain http:
rewrite ^/(.*)$ https://www.example.com/$1? permanent;
Which redirects all plain http
requests to the https
server.
Update:
Apparently, as I want the app not to care about how the front web server is serving it, it's up to the same web server to "clean the mess" of redirections the app by itself cannot handle unless it's been specifically configured (not desired). So, I will stick to this answer (workaround rather...)
UPDATE
Following papirtiger's answer, I saw that I ended up missing the flashes, which should be added to the overriding redirect_to
as a parameter.
But I've found a way to do my life a lot easier, simply by overriding a different function, that is called from within 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
Thus, without having to change anything else in my code, I have a working relative redirect.
Upvotes: 1
Reputation: 101976
I think force_ssl
is what you are looking for.
class AccountsController < ApplicationController
force_ssl if: :ssl_configured?
def ssl_configured?
!Rails.env.development?
end
end
Edit, if you really want to do redirects to relative paths you could always create your own helper:
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
This is a quick'n'dirty copy paste job . Will take a little more effort if you want have the same signature as redirect_to
Upvotes: 1