canvas-lms/config/initializers/template_streaming.rb

121 lines
3.7 KiB
Ruby

# frozen_string_literal: true
module MarkTemplateStreaming
def render_to_body(options = {})
@streaming_template = true if options[:stream]
super
end
# credit to https://stackoverflow.com/questions/7986150/http-streaming-in-rails-not-working-when-using-rackdeflater/10596123#10596123
def _process_options(options)
stream = options.delete(:stream)
super
if stream && request.version != "HTTP/1.0"
# Same as org implmenation except don't set the transfer-encoding header
# The Rack::Chunked middleware will handle it
headers["Cache-Control"] ||= "no-cache"
headers["Last-Modified"] ||= Time.now.httpdate
headers.delete("Content-Length")
options[:stream] = stream
end
end
def _render_template(options)
if options.delete(:stream)
# Just render, don't wrap in a Chunked::Body, let
# Rack::Chunked middleware handle it
view_renderer.render_body(view_context, options)
else
super
end
end
end
ActionController::Base.include(MarkTemplateStreaming)
module StreamingViewExtensions
# still have stuff like `provide :wizard_box` work in the handful of places we do it
# but skip them for streaming templates (so we're not waiting for it)
def self.prepended(klass)
klass.send(:attr_reader, :skipped_keys)
end
# e.g. skip_for_streaming :wizard_box, except: "eportfolios/show"
# will skip the block for all streaming template other than the eportfolios/show endpoint
def skip_for_streaming(name, except: nil)
if @streaming_template && !Array(except).include?("#{controller_name}/#{action_name}")
@skipped_keys ||= []
@skipped_keys << name
else
yield
end
end
def provide(name, content = nil, &block)
if block
content = capture(&block) || "" # still carry on even if the block doesn't return anything
provide(name, content)
else
super
end
end
# short-hand to provide blank content for multiple keys at once
def provide_blank(*keys)
keys.each do |key|
provide(key, "")
end
end
end
ActionView::Base.prepend(StreamingViewExtensions)
module StreamingContentChecks
# (for non-prod) force a check at the end of streaming templates to make sure we provide
# content like `:page_title` (even if it's empty)
# so we don't wait forever for it
def append!(key, value)
if @view.skipped_keys&.include?(key)
raise "Streaming template tried to provide content for #{key.inspect} but it's currently being skipped -
may need to render normally or add an exception to `skip_for_streaming`"
end
@provided_keys ||= [:wizard_box, :keyboard_navigation]
@provided_keys << key
super
end
def append(key, _value)
raise "Streaming template used `content_for` with #{key.inspect} instead of `provide`,
which is preferred (`provide` unblocks the rendering)"
end
def get(key)
val = super
unless key == :layout || @provided_keys&.include?(key)
raise "We tried to render this view with streaming but it got stuck waiting for content -
add a `<% provide_blank #{key.inspect} %>` at the top of the template or consider using `skip_for_streaming`"
end
val
end
end
ActionView::StreamingFlow.prepend(StreamingContentChecks) unless Rails.env.production?
module SkipEmptyTemplateConcats
def initialize(original_block)
new_block = ->(value) { original_block.call(value) unless value.empty? }
super(new_block)
end
end
ActionView::StreamingBuffer.prepend(SkipEmptyTemplateConcats)
module ActivateShardsOnRender
def render(view, *, **)
if (active_shard = view.request&.env&.[]("canvas.active_shard"))
active_shard.activate do
super
end
else
super
end
end
end
ActionView::Template.prepend(ActivateShardsOnRender)