Security News
Fluent Assertions Faces Backlash After Abandoning Open Source Licensing
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
Progressive rendering for Rails.
A typical Rails client-side profile looks something like this:
This is highly suboptimal. Many resources, such as external stylesheets, are completely static and could be loaded by the client while it's waiting for the server response.
The trick is to stream the response--flushing the markup for the static resources to the client before it has rendered the rest of the page. In addition to being able to render styles and images earlier, the browser can download javascripts, making the page responsive to input events sooner.
The main barrier to this in Rails is that layouts are rendered before the content of the page. The control flow must thus be altered to render the page in the order the client needs to receive it - layout first.
With streaming, your profiles can look more like this:
Just add the template_streaming
gem to your application, and add a stream
call for the actions you'd like to stream. For example, to stream just the
index
action of your HomeController
, it would look like this:
class HomeController
stream :only => :index
def index
...
end
end
To stream everything, just add stream
to your ApplicationController
.
Now you may pepper flush
calls strategically throughout your views to force a
flush, such as just after the stylesheet and javascript tags. flush
may occur
in both templates and their layouts.
The API is simple, but it's important to understand the change in control flow
when a template is streamed. A controller's render
no longer results in
rendering templates immediately; instead, response.body
is set to a
StreamingBody
object which will render the template when the server calls
#each
on the body after the action returns, as per the Rack specification.
This has several implications:
flush
- flush what has been rendered in the current template out to the
client immediately.push(data)
- send the given data to the client immediately.Template Streaming currently only supports Rails 2.3.11. Rails 3.0 support is planned in the near future. Rails 3.1 will ship with support for streaming. This gem will be updated to meet the API of Rails 3.1 as it evolves, to help you migrate.
Streaming also requires a web server that does not buffer Rack responses. It has
been tested successfully with Passenger, Unicorn,
and Mongrel. Note that Unicorn requires the :tcp_nopush => false
configuration option. Thin is only supported if the
Event Machine Flush gem is installed. WEBrick does
not support streaming. Please send me your experiences with other
web servers!
Class methods:
stream
- stream responses for these actions. Takes :only
or :except
options, like before_filter
.
when_streaming_template
- registers a callback to be called during render
when rendering progressively. This is before the body is rendered, or any
data is sent to the client.
Instance methods:
render
has been modified to accept a :stream
option. If true, the
response will be streamed, otherwise it won't. This overrides the setting set
by the stream
method above.As mentioned above, headers are sent to the client before view rendering starts, which means it's not possible to send an error response in the event of an uncaught exception. Instead, the innermost template which raised the error simply renders nothing. This has the added advantage of minimizing the impact on your visitors, as the rest of the page will render fine.
When an error is swallowed like this, it is passed to an error hander callback, which you can set as follows.
TemplateStreaming.on_streaming_error do |controller, exception|
...
end
This is where you should hook in your error notification system. Errors are also logged to the application log.
In addition, in development mode, error information is injected into the foot of the page. This is presented over the top of the rendered page, so the result looks much like when not streaming.
Conventional wisdom says to put your external stylesheets in the HEAD of your page, and your external javascripts at the bottom of the BODY (markup in HAML):
app/views/layouts/application.html.haml
!!! 5
%html
%head
= stylesheet_link_tag 'one'
= stylesheet_link_tag 'two'
- flush
%body
= yield
= javascript_include_tag 'one'
= javascript_include_tag 'two'
When streaming, however, you can do better: put the javascripts at the top of the page too, and fetch them asynchronously. This can be done by appending a script tag to the HEAD of the page in a small piece of inline javascript:
app/views/layouts/application.html.haml
!!! 5
%html
%head
= stylesheet_link_tag 'one'
= stylesheet_link_tag 'two'
= javascript_tag do
= File.read(Rails.public_path + '/javascripts/get_script.js')
$.getScript('#{javascript_path('jquery')}');
$.getScript('#{javascript_path('application')}');
%body
- flush
= yield
public/javascripts/get_script.js
//
// Credit: Sam Cole [https://gist.github.com/364746]
//
window.$ = {
getScript: function(script_src, callback) {
var done = false;
var head = document.getElementsByTagName("head")[0] || document.documentElement;
var script = document.createElement("script");
script.src = script_src;
script.onload = script.onreadystatechange = function() {
if ( !done && (!this.readyState ||
this.readyState === "loaded" || this.readyState === "complete") ) {
if(callback) callback();
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
if ( head && script.parentNode ) {
head.removeChild( script );
}
done = true;
}
};
head.insertBefore( script, head.firstChild );
}
};
If you have inline javascript that depends on the fetched scripts, you'll need to delay its execution until the scripts have been run. You can do this by wrapping the javascript in a function, with a guard which will delay execution until the script is loaded, unless the script has already been loaded. Example:
!!! 5
%html
%head
= stylesheet_link_tag 'one'
= stylesheet_link_tag 'two'
= javascript_tag do
= File.read(Rails.public_path + '/javascripts/get_script.js')
$.getScript('#{javascript_path('jquery')}', function() {
window.script_loaded = 1;
// If the inline code has been loaded (but not yet run), run it
// now. Otherwise, it will be run immediately when it's available.
if (window.inline)
inline();
});
%body
- flush
= yield
- javascript_tag do
window.inline() {
// ... inline javascript code ...
}
// If the script is already loaded, run it now. Otherwise, the callback
// above will run it after the script is loaded.
if (window.script_loaded)
inline();
public/javascripts/get_script.js
//
// Credit: Sam Cole [https://gist.github.com/364746]
//
window.$ = {
getScript: function(script_src, callback) {
var done = false;
var head = document.getElementsByTagName("head")[0] || document.documentElement;
var script = document.createElement("script");
script.src = script_src;
script.onload = script.onreadystatechange = function() {
if ( !done && (!this.readyState ||
this.readyState === "loaded" || this.readyState === "complete") ) {
if(callback) callback();
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
if ( head && script.parentNode ) {
head.removeChild( script );
}
done = true;
}
};
head.insertBefore( script, head.firstChild );
}
};
Copyright (c) George Ogata. See LICENSE for details.
FAQs
Unknown package
We found that template_streaming demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.
Research
Security News
Socket researchers uncover the risks of a malicious Python package targeting Discord developers.
Security News
The UK is proposing a bold ban on ransomware payments by public entities to disrupt cybercrime, protect critical services, and lead global cybersecurity efforts.