Merge pull request #46649 from jonathanhefner/action_controller-renderer-default_url_options

Use `routes.default_url_options` in `AC::Renderer` env
This commit is contained in:
Jonathan Hefner 2022-12-15 16:45:02 -06:00 committed by GitHub
commit 12c0c0a4c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 216 additions and 15 deletions

View File

@ -1,3 +1,25 @@
* When a host is not specified for an `ActionController::Renderer`'s env,
the host and related options will now be derived from the routes'
`default_url_options` and `ActionDispatch::Http::URL.secure_protocol`.
This means that for an application with a configuration like:
```ruby
Rails.application.default_url_options = { host: "rubyonrails.org" }
Rails.application.config.force_ssl = true
```
rendering a URL like:
```ruby
ApplicationController.renderer.render inline: "<%= blog_url %>"
```
will now return `"https://rubyonrails.org/blog"` instead of
`"http://example.org/blog"`.
*Jonathan Hefner*
* Add details of cookie name and size to `CookieOverflow` exception.
*Andy Waite*

View File

@ -23,10 +23,7 @@ module ActionController
attr_reader :controller
DEFAULTS = {
http_host: "example.org",
https: false,
method: "get",
script_name: "",
input: ""
}.freeze
@ -46,7 +43,14 @@ module ActionController
new_env[key] = value
end
new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http"
if new_env["HTTP_HOST"]
new_env["HTTPS"] ||= "off"
new_env["SCRIPT_NAME"] ||= ""
end
if new_env["HTTPS"]
new_env["rack.url_scheme"] = new_env["HTTPS"] == "on" ? "https" : "http"
end
new_env
end
@ -90,6 +94,13 @@ module ActionController
# * +defaults+ - Default values for the Rack env. Entries are specified in
# the same format as +env+. +env+ will be merged on top of these values.
# +defaults+ will be retained when calling #new on a renderer instance.
#
# If no +http_host+ is specified, the env HTTP host will be derived from the
# routes' +default_url_options+. In this case, the +https+ boolean and the
# +script_name+ will also be derived from +default_url_options+ if they were
# not specified. Additionally, the +https+ boolean will fall back to
# +Rails.application.config.force_ssl+ if +default_url_options+ does not
# specify a +protocol+.
def initialize(controller, env, defaults)
@controller = controller
@defaults = defaults
@ -128,9 +139,7 @@ module ActionController
#
# Otherwise, a partial is rendered using the second parameter as the locals hash.
def render(*args)
raise "missing controller" unless controller
request = ActionDispatch::Request.new(@env.dup)
request = ActionDispatch::Request.new(env_for_request)
request.routes = controller._routes
instance = controller.new
@ -152,5 +161,13 @@ module ActionController
DEFAULT_ENV = normalize_env(DEFAULTS).freeze # :nodoc:
delegate :normalize_env, to: :class
def env_for_request
if @env.key?("HTTP_HOST") || controller._routes.nil?
@env.dup
else
controller._routes.default_env.merge(@env)
end
end
end
end

View File

@ -375,6 +375,7 @@ module ActionDispatch
@disable_clear_and_finalize = false
@finalized = false
@env_key = "ROUTES_#{object_id}_SCRIPT_NAME"
@default_env = nil
@set = Journey::Routes.new
@router = Journey::Router.new @set
@ -405,6 +406,25 @@ module ActionDispatch
end
private :make_request
def default_env
if default_url_options != @default_env&.[]("action_dispatch.routes.default_url_options")
url_options = default_url_options.dup.freeze
uri = URI(ActionDispatch::Http::URL.full_url_for(host: "example.org", **url_options))
@default_env = {
"action_dispatch.routes" => self,
"action_dispatch.routes.default_url_options" => url_options,
"HTTPS" => uri.scheme == "https" ? "on" : "off",
"rack.url_scheme" => uri.scheme,
"HTTP_HOST" => uri.port == uri.default_port ? uri.host : "#{uri.host}:#{uri.port}",
"SCRIPT_NAME" => uri.path.chomp("/"),
"rack.input" => "",
}.freeze
end
@default_env
end
def draw(&block)
clear! unless @disable_clear_and_finalize
eval_block(block)

View File

@ -108,18 +108,17 @@ class RendererTest < ActiveSupport::TestCase
html = "Hello world!"
xml = "<p>Hello world!</p>\n"
assert_equal html, render["respond_to/using_defaults"]
assert_equal xml, render["respond_to/using_defaults", formats: :xml]
assert_equal html, render("respond_to/using_defaults")
assert_equal xml, render("respond_to/using_defaults", formats: :xml)
end
test "rendering with helpers" do
assert_equal "<p>1\n<br />2</p>", render[inline: '<%= simple_format "1\n2" %>']
assert_equal "<p>1\n<br />2</p>", render(inline: '<%= simple_format "1\n2" %>')
end
test "rendering with user specified defaults" do
ApplicationController.renderer.defaults.merge!(hello: "hello", https: true)
renderer = ApplicationController.renderer.new
content = renderer.render inline: "<%= request.ssl? %>"
renderer.defaults.merge!(hello: "hello", https: true)
content = renderer.new.render inline: "<%= request.ssl? %>"
assert_equal "true", content
end
@ -138,8 +137,81 @@ class RendererTest < ActiveSupport::TestCase
assert_equal "https://example.org/asset.jpg", content
end
test "uses default_url_options from the controller's routes when env[:http_host] not specified" do
with_default_url_options(
protocol: "https",
host: "foo.example.com",
port: 9001,
script_name: "/bar",
) do
assert_equal "https://foo.example.com:9001/bar/posts", render_url_for(controller: :posts)
end
end
test "uses config.force_ssl when env[:http_host] not specified" do
with_default_url_options(host: "foo.example.com") do
with_force_ssl do
assert_equal "https://foo.example.com/posts", render_url_for(controller: :posts)
end
end
end
test "can specify env[:https] when using default_url_options" do
with_default_url_options(host: "foo.example.com") do
@renderer = renderer.new(https: true)
assert_equal "https://foo.example.com/posts", render_url_for(controller: :posts)
end
end
test "env[:https] overrides default_url_options[:protocol]" do
with_default_url_options(host: "foo.example.com", protocol: "https") do
@renderer = renderer.new(https: false)
assert_equal "http://foo.example.com/posts", render_url_for(controller: :posts)
end
end
test "can specify env[:script_name] when using default_url_options" do
with_default_url_options(host: "foo.example.com") do
@renderer = renderer.new(script_name: "/bar")
assert_equal "http://foo.example.com/bar/posts", render_url_for(controller: :posts)
end
end
test "env[:script_name] overrides default_url_options[:script_name]" do
with_default_url_options(host: "foo.example.com", script_name: "/bar") do
@renderer = renderer.new(script_name: "")
assert_equal "http://foo.example.com/posts", render_url_for(controller: :posts)
end
end
private
def render
@render ||= ApplicationController.renderer.method(:render)
def renderer
@renderer ||= ApplicationController.renderer.new
end
def render(...)
renderer.render(...)
end
def render_url_for(*args)
render inline: "<%= full_url_for(*#{args.inspect}) %>"
end
def with_default_url_options(default_url_options)
original_default_url_options = renderer.controller._routes.default_url_options
renderer.controller._routes.default_url_options = default_url_options
yield
ensure
renderer.controller._routes.default_url_options = original_default_url_options
renderer.controller._routes.default_env # refresh
end
def with_force_ssl(force_ssl = true)
# In a real app, an initializer will set `URL.secure_protocol = app.config.force_ssl`.
original_secure_protocol = ActionDispatch::Http::URL.secure_protocol
ActionDispatch::Http::URL.secure_protocol = force_ssl
yield
ensure
ActionDispatch::Http::URL.secure_protocol = original_secure_protocol
end
end

View File

@ -1,3 +1,9 @@
* Action Text attachment URLs rendered in a background job (a la Turbo
Streams) now use `Rails.application.default_url_options` and
`Rails.application.config.force_ssl` instead of `http://example.org`.
*Jonathan Hefner*
* Focus rich-text editor after calling `fill_in_rich_text_area`
*Sean Doyle*

View File

@ -0,0 +1,9 @@
class BroadcastJob < ApplicationJob
def perform(file, message)
File.write(file, <<~HTML)
<turbo-stream action="replace" target="message_#{message.id}">
<template>#{message.content}</template>
</turbo-stream>
HTML
end
end

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
require "test_helper"
class ActionText::JobRenderTest < ActiveJob::TestCase
include Rails::Dom::Testing::Assertions::SelectorAssertions
test "uses app default_url_options" do
blob = create_file_blob(filename: "racecar.jpg", content_type: "image/jpeg")
message = Message.create!(content: ActionText::Content.new.append_attachables(blob))
Dir.mktmpdir do |dir|
file = File.join(dir, "broadcast.html")
BroadcastJob.perform_later(file, message)
with_default_url_options(host: "foo.example.com", port: 9001) do
perform_enqueued_jobs
end
rendered = Nokogiri::HTML::DocumentFragment.parse(File.read(file))
assert_select rendered, "img:match('src', ?)", %r"//foo.example.com:9001/.+/racecar"
end
end
private
def with_default_url_options(default_url_options)
original_default_url_options = Dummy::Application.default_url_options
Dummy::Application.default_url_options = default_url_options
yield
ensure
Dummy::Application.default_url_options = original_default_url_options
end
end

View File

@ -1437,6 +1437,27 @@ module ApplicationTests
assert_deprecated(Rails.deprecator) { Rails.application.config.enable_dependency_loading = true }
end
test "ActionController::Base::renderer uses Rails.application.default_url_options and config.force_ssl" do
add_to_config <<~RUBY
config.force_ssl = true
Rails.application.default_url_options = {
host: "foo.example.com",
port: 9001,
script_name: "/bar",
}
routes.prepend do
resources :posts
end
RUBY
app "development"
posts_url = ApplicationController.renderer.render(inline: "<%= posts_url %>")
assert_equal "https://foo.example.com:9001/bar/posts", posts_url
end
test "ActionController::Base.raise_on_open_redirects is true by default for new apps" do
app "development"