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.
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
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.rendermethod gives you a parameter, named
pageletsin the example above, that is a
HtmlStreamfor that Pagelet. The idea is to place the
HtmlStreamfor each of your Pagelets into the proper place in the markup where that Pagelet should appear.
- You need to include
headof 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
Htmlas soon as the data is available, giving you a
Future[Html], plus the DOM id of where in the DOM it should be inserted, is wrapped in an
HtmlPageletobjects are composed into a
BigPipeobject, and told to use client-side rendering.
BigPipeinstance and all the
HtmlPageletobjects 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.
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!