I’ve just open sourced a library called ping-play which brings BigPipe streaming to the Play Framework. It includes tools for a) splitting your pages up into small “pagelets”, which makes it easier to maintain large websites, and b) streaming those pagelets down to the browser as soon as they are ready, which can significantly reduce page load time.
Check out ping-play on GitHub now!
In this blog post, I’ll describe what BigPipe streaming is all about, how to add BigPipe streaming to your Play apps, and where to get more info.
What is BigPipe?
In a typical web app, before you can render a page, you have to make requests to multiple remote backend services to fetch data (e.g. RESTful HTTP calls to a profile service, a search service, an ads service, etc). You then have to wait for all of these remote calls to come back before you can send any data back to the browser. For example, the following screen capture shows a page that makes 6 remote service calls, most of which complete in few hundred milliseconds, but one takes over 5 seconds. As a result, the time to first byte is 5 seconds, during which the user sees a completely blank page:
With BigPipe, you can start streaming data back to the browser without waiting for the backends at all, and fill in the page incrementally as each backend responds. For example, the following screen capture shows the same page making the same 6 remote service calls, but this time rendered using BigPipe. The header and much of the markup is sent back instantly, so time to first byte is 10 milliseconds (instead of 5 seconds), static content (i.e. CSS, JS, images) can start loading right away, and then, as each backend service responds, the corresponding part of the page (i.e. the pagelet) is sent to the browser and rendered on the screen:
How to use ping-play
To understand how to transform your Play app to use BigPipe, it’s helpful to first see an example that does not use BigPipe (note, the example is in Scala, but ping-play supports Java too!). Here is the controller code for the example mentioned above:
This controller makes 6 remote service calls, gets back 6 Future
objects, and
when they have all redeemed, it uses them to render the following template:
When you load this page, nothing will show up on the screen until all of the backend calls complete, which will take about 5 seconds.
To transform this page to use BigPipe, you first add the big-pipe dependency to your build (note, ping-play requires Play 2.4, Scala 2.11.6, SBT 0.13.8, and Java 8):
Next, add support for the .scala.stream
template type and some imports for it
to your build:
Now you can create streaming templates. These templates can mix normal HTML
markup, which will be streamed to the browser immediately, with the HtmlStream
class, which is a wrapper for an Enumerator[Html]
that will be streamed to the
browser whenever the Enumerator
has data. Here is is the streaming version of
the template above:
The key changes to notice from the original template are:
- Most of the markup in the page is wrapped in a call to the
BigPipe.render
method. - The
BigPipe.render
method gives you a parameter, namedpagelets
in the example above, that is aMap
from Pageletid
to theHtmlStream
for that Pagelet. The idea is to place theHtmlStream
for each of your Pagelets into the proper place in the markup where that Pagelet should appear. - You need to include
big-pipe.js
in thehead
of the document.
The key changes to notice from the original controller are:
- Instead of waiting for all of the service calls to redeem, you render each
one individually into
Html
as soon as the data is available, giving you aFuture[Html]
. - Each
Future[Html]
, plus the DOM id of where in the DOM it should be inserted, is wrapped in anHtmlPagelet
object. - The
HtmlPagelet
objects are composed into aBigPipe
object, and told to use client-side rendering. - This
BigPipe
instance and all theHtmlPagelet
objects are passed to the streaming template for rendering.
When you load this page, you will see the outline of the page almost immediately, and each pagelet will fill in this outline as soon as the corresponding remote service responds.
More info
The ping-play project started as the sample app for one of my talks (you can find the slides here):
I only recently had a chance to turn the code in the sample app into a reusable library, complete with tests and documentation. It’s still in the alpha stage, but I’d love to get your feedback, bug reports, and pull requests in the ping-play GitHub repo. And if you end up using ping-play in production, I’d love to hear about your experiences, so send me an email!
Yevgeniy Brikman
If you enjoyed this post, you may also like my books, Hello, Startup and Terraform: Up & Running. If you need help with DevOps or infrastructure, reach out to me at Gruntwork.