{"id":15628320,"url":"https://github.com/brikis98/ping-play","last_synced_at":"2025-04-07T11:08:42.199Z","repository":{"id":13285063,"uuid":"15970855","full_name":"brikis98/ping-play","owner":"brikis98","description":"BigPipe streaming for the Play Framework","archived":false,"fork":false,"pushed_at":"2016-11-06T23:25:42.000Z","size":32600,"stargazers_count":307,"open_issues_count":10,"forks_count":59,"subscribers_count":37,"default_branch":"master","last_synced_at":"2025-03-31T10:03:37.932Z","etag":null,"topics":["bigpipe","java","play-framework","scala","streaming"],"latest_commit_sha":null,"homepage":"","language":"Scala","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/brikis98.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-01-16T14:55:36.000Z","updated_at":"2024-08-29T05:41:27.000Z","dependencies_parsed_at":"2022-09-10T22:22:57.913Z","dependency_job_id":null,"html_url":"https://github.com/brikis98/ping-play","commit_stats":null,"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brikis98%2Fping-play","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brikis98%2Fping-play/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brikis98%2Fping-play/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/brikis98%2Fping-play/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/brikis98","download_url":"https://codeload.github.com/brikis98/ping-play/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247640465,"owners_count":20971557,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bigpipe","java","play-framework","scala","streaming"],"created_at":"2024-10-03T10:21:54.621Z","updated_at":"2025-04-07T11:08:42.163Z","avatar_url":"https://github.com/brikis98.png","language":"Scala","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ping-Play\n\nThe ping-play project brings [BigPipe](https://www.facebook.com/note.php?note_id=389414033919) streaming to the\n[Play Framework](http://playframework.com/). It includes tools for a) splitting your pages up into small \"pagelets\",\nwhich makes it easier to maintain large websites, and b) streaming those pagelets down to the browser as soon as they\nare ready, which can significantly reduce page load time.\n\nTo fetch the data for a page, modern apps often have to make requests to multiple remote backend services (e.g. RESTful\nHTTP calls to a profile service, a search service, an ads service, etc). You then have to wait for *all* of these\nremote calls to come back before you can send *any* data back to the browser. For example, the following screen capture\nshows a page that makes 6 remote service calls, most of which complete in few hundred milliseconds, but one takes\nover 5 seconds. As a result, the time to first byte is 5 seconds, during which the user sees a completely blank page:\n\n![Page loading without BigPipe](images/without-big-pipe.gif)\n\nWith BigPipe, you can start streaming data back to the browser without waiting for the backends at all, and fill in the\npage incrementally as each backend responds. For example, the following screen capture shows the same page making the\nsame 6 remote service calls, but this time rendered using BigPipe. The header and much of the markup is sent back\ninstantly, so time to first byte is 10 milliseconds (instead of 5 seconds), static content (i.e. CSS, JS, images) can\nstart loading right away, and then, as each backend service responds, the corresponding part of the page (i.e. the\npagelet) is sent to the browser and rendered on the screen:\n\n![Page loading with BigPipe](images/with-big-pipe.gif)\n\n# Quick start\n\nTo understand how to transform your Play app to use BigPipe, it's helpful to first see an example that does *not* use\nBigPipe (note, the example is in Scala, but ping-play supports Java too!). Here is the controller code,\n[controllers/WithoutBigPipe.scala](sample-app-scala/app/controllers/WithoutBigPipe.scala), for the example mentioned\nearlier:\n\n```scala\nclass WithoutBigPipe(serviceClient: FakeServiceClient) extends Controller {\n  def index = Action.async { implicit request =\u003e\n    // Make several fake service calls in parallel to represent fetching data from remote backends. Some of the calls\n    // will be fast, some medium, and some slow.\n    val profileFuture = serviceClient.fakeRemoteCallMedium(\"profile\")\n    val graphFuture = serviceClient.fakeRemoteCallMedium(\"graph\")\n    val feedFuture = serviceClient.fakeRemoteCallSlow(\"feed\")\n    val inboxFuture = serviceClient.fakeRemoteCallSlow(\"inbox\")\n    val adsFuture = serviceClient.fakeRemoteCallFast(\"ads\")\n    val searchFuture = serviceClient.fakeRemoteCallFast(\"search\")\n\n    // Wait for all the remote calls to complete\n    for {\n      profile \u003c- profileFuture\n      graph \u003c- graphFuture\n      feed \u003c- feedFuture\n      inbox \u003c- inboxFuture\n      ads \u003c- adsFuture\n      search \u003c- searchFuture\n    } yield {\n      // Render the template once all the data is available\n      Ok(views.html.withoutBigPipe(profile, graph, feed, inbox, ads, search))\n    }\n  }\n}\n```\n\nThis controller makes 6 remote service calls, gets back 6 `Future` objects, and when they have all redeemed, it uses\nthem to render the following template, [views/withoutBigPipe.scala.html](sample-app-common/src/main/twirl/views/withoutBigPipe.scala.html):\n\n```html\n@(profile: data.Response, graph: data.Response, feed: data.Response, inbox: data.Response, ads: data.Response, search: data.Response)\n\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003clink rel=\"stylesheet\" href=\"/assets/stylesheets/main.css\"\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch1\u003eWithout Big Pipe\u003c/h1\u003e\n    \u003ctable class=\"wrapper\"\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003cdiv id=\"profile\"\u003e@views.html.helpers.module(profile)\u003c/div\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003cdiv id=\"ads\"\u003e@views.html.helpers.module(ads)\u003c/div\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003cdiv id=\"feed\"\u003e@views.html.helpers.module(feed)\u003c/div\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n      \u003ctr\u003e\n        \u003ctd\u003e\u003cdiv id=\"search\"\u003e@views.html.helpers.module(search)\u003c/div\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003cdiv id=\"inbox\"\u003e@views.html.helpers.module(inbox)\u003c/div\u003e\u003c/td\u003e\n        \u003ctd\u003e\u003cdiv id=\"graph\"\u003e@views.html.helpers.module(graph)\u003c/div\u003e\u003c/td\u003e\n      \u003c/tr\u003e\n    \u003c/table\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nWhen you load this page, nothing will show up on the screen until all of the backend calls complete, which will take\nabout 5 seconds.\n\nTo transform this page to use BigPipe, you first add the big-pipe dependency to your build (note, this project requires\nPlay 2.4, Scala 2.11.6, SBT 0.13.8, and Java 8):\n\n```scala\nlibraryDependencies += \"com.ybrikman.ping\" %% \"big-pipe\" % \"0.0.13\"\n```\n\nNext, add support for the `.scala.stream` template type and some imports for it to your build:\n\n```scala\nTwirlKeys.templateFormats ++= Map(\"stream\" -\u003e \"com.ybrikman.ping.scalaapi.bigpipe.HtmlStreamFormat\"),\nTwirlKeys.templateImports ++= Vector(\"com.ybrikman.ping.scalaapi.bigpipe.HtmlStream\", \"com.ybrikman.ping.scalaapi.bigpipe._\")\n```\n\nNow you can create streaming templates. These templates can mix normal HTML markup, which will be streamed to the\nbrowser immediately, with the `HtmlStream` class, which is a wrapper for an `Enumerator[Html]` that will be streamed\nto the browser whenever the `Enumerator` has data. Here is [views/withBigPipe.scala.stream](sample-app-common/src/main/twirl/views/withBigPipe.scala.stream),\nwhich is the streaming version of the template above:\n\n```html\n@(bigPipe: BigPipe, profile: Pagelet, graph: Pagelet, feed: Pagelet, inbox: Pagelet, ads: Pagelet, search: Pagelet)\n\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003clink rel=\"stylesheet\" href=\"/assets/stylesheets/main.css\"\u003e\n    \u003c!-- You need to include the BigPipe JavaScript at the top of the page --\u003e\n    \u003cscript src=\"/assets/com/ybrikman/ping/big-pipe.js\"\u003e\u003c/script\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch1\u003eWith Big Pipe\u003c/h1\u003e\n    @HtmlStream.fromHtml(views.html.helpers.timing())\n\n    \u003c!--\n      Wrap the entire body of your page with a bigPipe.render call. The pagelets parameter contains a Map from\n      Pagelet id to the HtmlStream for that Pagelet. You should put the HtmlStream for each of your Pagelets\n      into the appropriate place in the markup.\n    --\u003e\n    @bigPipe.render { pagelets =\u003e\n      \u003ctable class=\"wrapper\"\u003e\n        \u003ctr\u003e\n          \u003ctd\u003e@pagelets(profile.id)\u003c/td\u003e\n          \u003ctd\u003e@pagelets(ads.id)\u003c/td\u003e\n          \u003ctd\u003e@pagelets(feed.id)\u003c/td\u003e\n        \u003c/tr\u003e\n        \u003ctr\u003e\n          \u003ctd\u003e@pagelets(search.id)\u003c/td\u003e\n          \u003ctd\u003e@pagelets(inbox.id)\u003c/td\u003e\n          \u003ctd\u003e@pagelets(graph.id)\u003c/td\u003e\n        \u003c/tr\u003e\n      \u003c/table\u003e\n    }\n\n    \u003c/body\u003e\n\u003c/html\u003e\n```\n\nThe key changes to notice from the original template are:\n\n1. Most of the markup in the page is wrapped in a call to the `BigPipe.render` method.\n2. The `BigPipe.render` method gives you a parameter, named `pagelets` in the example above, that is a `Map`\n   from Pagelet `id` to the `HtmlStream` for that Pagelet. The idea is to place the `HtmlStream` for each of your\n   Pagelets into the proper place in the markup where that Pagelet should appear.\n3. You need to include `big-pipe.js` in the `head` of the document.\n\nNow, let's look at the controller you can use with this template, called [controllers/WithBigPipe.scala](sample-app-scala/app/controllers/WithBigPipe.scala):\n\n```scala\nclass WithBigPipe(serviceClient: FakeServiceClient) extends Controller {\n\n  def index = Action {\n    // Make several fake service calls in parallel to represent fetching data from remote backends. Some of the calls\n    // will be fast, some medium, and some slow.\n    val profileFuture = serviceClient.fakeRemoteCallMedium(\"profile\")\n    val graphFuture = serviceClient.fakeRemoteCallMedium(\"graph\")\n    val feedFuture = serviceClient.fakeRemoteCallSlow(\"feed\")\n    val inboxFuture = serviceClient.fakeRemoteCallSlow(\"inbox\")\n    val adsFuture = serviceClient.fakeRemoteCallFast(\"ads\")\n    val searchFuture = serviceClient.fakeRemoteCallFast(\"search\")\n\n    // Convert each Future into a Pagelet which will be rendered as HTML as soon as the data is available\n    val profile = HtmlPagelet(\"profile\", profileFuture.map(views.html.helpers.module.apply))\n    val graph = HtmlPagelet(\"graph\", graphFuture.map(views.html.helpers.module.apply))\n    val feed = HtmlPagelet(\"feed\", feedFuture.map(views.html.helpers.module.apply))\n    val inbox = HtmlPagelet(\"inbox\", inboxFuture.map(views.html.helpers.module.apply))\n    val ads = HtmlPagelet(\"ads\", adsFuture.map(views.html.helpers.module.apply))\n    val search = HtmlPagelet(\"search\", searchFuture.map(views.html.helpers.module.apply))\n\n    // Use BigPipe to compose the pagelets and render them immediately using a streaming template\n    val bigPipe = new BigPipe(PageletRenderOptions.ClientSide, profile, graph, feed, inbox, ads, search)\n    Ok.chunked(views.stream.withBigPipe(bigPipe, profile, graph, feed, inbox, ads, search))\n  }\n}\n```\n\nThe key changes to notice from the original controller are:\n\n1. Instead of waiting for *all* of the service calls to redeem, you render each one individually into `Html` as soon as\n   the data is available, giving you a `Future[Html]`.\n2. Each `Future[Html]`, plus the DOM id of where in the DOM it should be inserted, is wrapped in an `HtmlPagelet`\n   object.\n3. The `HtmlPagelet` objects are composed into a `BigPipe` object, and told to use client-side rendering.\n4. This `BigPipe` instance and all the `HtmlPagelet` objects are passed to the streaming template for rendering.\n\nWhen you load this page, you will see the outline of the page almost immediately, and each piece of the page will\nfill in this outline as soon as the corresponding remote service responds.\n\n# More examples\n\nThere are several BigPipe examples, including the one described above, in [sample-app-scala](sample-app-scala) and\n[sample-app-java](sample-app-java) in this repo (yes, BigPipe streaming works with both Scala and Java). You'll also\nwant to browse [sample-app-common](sample-app-common), which has some code shared by both sample apps, including all of\ntheir templates. For example, here is how to run the Scala sample app (assuming you have\n[Typesafe Activator](https://www.typesafe.com/community/core-tools/activator-and-sbt) installed already):\n\n1. `git clone` this repo.\n2. `activator shell`\n3. `project sampleAppScala`\n4. `run`\n5. Open `http://localhost:9000/withoutBigPipe` to see how long the page takes to load without BigPipe streaming.\n6. Open `http://localhost:9000/withBigPipe` to see how much faster the page loads with BigPipe streaming.\n\nCheck out the [Documentation](#Documentation) to see what APIs are available and [FAQ](#FAQ) to learn more about\nBigPipe.\n\n# Documentation\n\n## Scala vs Java\n\nBigPipe streaming is supported for both Scala and Java developers.\n\nScala developers should primarily be using classes in the `com.ybrikman.ping.scalaapi` package. In particular, use the\n`com.ybrikman.ping.scalaapi.bigpipe.HtmlPagelet` class to wrap your `Future[Html]` objects as `Pagelet` objects, and\nuse the `com.ybrikman.ping.scalaapi.bigpipe.BigPipe` class to compose and render your `Pagelet` objects. See\n[sample-app-scala](sample-app-scala) for examples.\n\nJava developers should primarily be using classes in the `com.ybrikman.ping.javaapi` package. In particular, use the\n`com.ybrikman.ping.javaapi.bigpipe.HtmlPagelet` class to wrap your and `Promise\u003cHtml\u003e` as `Pagelet` objects and use the\n`com.ybrikman.ping.javaapi.bigpipe.BigPipe` class to compose and render your `Pagelet` objects. See\n[sample-app-java](sample-app-java) for examples.\n\n## Client-side vs server-side rendering\n\nPing-Play supports both client-side and server-side BigPipe streaming. Client-side streaming sends down the\npagelets in whatever order they complete and uses JavaScript to insert each pagelet into the correct spot in the DOM.\nThis gives you the fastest possible loading time, but it does add a dependency on JavaScript. For use cases where you\nwant to avoid JavaScript, such as slower browsers or search engine crawlers (i.e. SEO), you can use server-side\nrendering, which sends all the pagelets down already rendered as HTML and in the proper order. This will have a longer\npage-load time than client-side rendering, but still much faster than not using BigPipe at all.\n\nThe *only* part of your code that you have to change to switch between server-side and client-side rendering is the\n`PageletRenderOptions` parameter you pass into the `BigPipe` constructor. Here is an example of how you could check\nthe `User-Agent` header and select `PageletRenderOptions.ServerSide` if you detect GoogleBot and\n`PageletRenderOptions.ClientSide` otherwise:\n\n```scala\ndef index = Action { request =\u003e\n  // ... fetch data, create pagelets ...\n\n  val bigPipe = new BigPipe(renderOptions(request), pagelet1, pagelet2, ...)\n\n  // ... render a streaming template ...\n}\n\nprivate def renderOptions(request: RequestHeader): PageletRenderOptions = {\n  request.headers.get(HeaderNames.USER_AGENT) match {\n    case Some(header) if header.contains(\"GoogleBot\") =\u003e PageletRenderOptions.ServerSide\n    case _ =\u003e PageletRenderOptions.ClientSide\n  }\n}\n\n```\n\n## HtmlStream and .scala.stream templates\n\nPlay's built-in `.scala.html` templates are compiled into functions that append together and return `Html`, which is\njust a wrapper for a `StringBuilder`, and cannot be streamed. This is why this project introduces a new `.scala.stream`\ntemplate that appends together and returns `HtmlStream` objects, which are a wrapper for an `Enumerator[Html]`, which\ncan be streamed. Note that this new template type still uses Play's [Twirl](https://github.com/playframework/twirl)\ntemplate compiler and its syntax. The only things that are different are:\n\n1. The extension is `.scala.stream` instead of `.scala.html`.\n2. When you are using the template in a controller, the package name will be `views.stream.XXX` instead of\n   `views.html.XXX`.\n3. To include raw, unescaped HTML, instead of wrapping the content in an `Html` object (e.g.\n   `Html(someStringWithMarkup)`), wrap it in an `HtmlStream` object (e.g. `HtmlStream.fromString(someStringWithMarkup)`).\n4. You can include an `HtmlStream` object anywhere in the markup of a `.scala.stream` template and Play will stream the\n   content down from the `HtmlStream`'s `Enumerator` whenever the content is available.\n\nThe last point is how you get BigPipe style streaming. The `HtmlStream` class has many helper methods to create an\n`HtmlStream`, including `fromHtml` and `fromHtmlFuture`, and to compose several streams into one, such as `interleave`.\n\n## Pagelet and BigPipe classes\n\nAlthough you can use the `HtmlStream` class directly, this project also comes with `Pagelet` and `BigPipe` classes that\noffer a higher level API for working with `HtmlStream`. The idea is to break your page down into small \"pagelets\" that\nknow how to fetch their own data independently and render themselves. For example, you might have one pagelet that\nfetches data from a profile service and knows how to render a user's profile, another pagelet that fetches data from an\nads service and knows how to render an ad unit, and so on. For each pagelet, you make your backend calls, get back\nsome `Future` (Scala) or `Promise` (Java) objects, render them into a `Future[Html]` or `Promise\u003cHtml\u003e`, and then use\n`new HtmlPagelet(id, future)` or `new HtmlPagelet(id, promise)` to wrap them in a `Pagelet` class. You can then compose\nmultiple `Pagelet` instances together using the `BigPipe` constructor.\n\nThe `BigPipe` instance you get back has a `render` method that you use to actually render your pagelets. The `render`\nmethod processes your `Pagelets` as necessary for server-side or client-side rendering and gives you a `Map` from\n`Pagelet` id to the `HtmlStream` for that `Pagelet`. In your template, you should extract the `HtmlStream` for each of\nyour `Pagelets` from this map and put it into the proper place in the markup:\n\n```html\n@bigPipe.render { pagelets =\u003e\n  \u003ch2\u003eThe foo pagelet should go here\u003c/h2\u003e\n  \u003cdiv\u003e@pagelets(fooPagelet.id)\u003c/div\u003e\n\n  \u003ch2\u003eThe bar pagelet should go here\u003c/h2\u003e\n  \u003cdiv\u003e@pagelets(barPagelet.id)\u003c/div\u003e\n}\n```\n\nWhen doing server-side rendering, the `HtmlStream` you get back from the `pagelets` `Map` will contain the fully\nrendered HTML. When doing client-side rendering, the `HtmlStream` will instead contain an empty placeholder that looks\nsomething like this:\n\n```html\n\u003cdiv id=\"foo-pagelet\"\u003e\u003c/div\u003e\n```\n\nThe actual content for your `Pagelet` will be streamed down at the very end (ie, at the bottom of all the markup you\npass to the `BigPipe.render` method) and it will be wrapped in markup that makes it invisible when it first arrives in\nthe browser. It will also include some JavaScript that knows how to extract the content and inject it into the right\nplaceholder in the DOM. This is what allows the pagelets to be sent down in any order, but still render correctly on\nthe page. The markup sent back by each `Pagelet` is in\n[com.ybrikman.bigpipe.pagelet.scala.html](big-pipe/src/main/twirl/com/ybrikman/bigpipe/pageletClientSide.scala.html)\nand looks roughly like this:\n\n```html\n\u003ccode id=\"pagelet1\"\u003e\u003c!--Your content--\u003e\u003c/code\u003e\n\u003cscript\u003eBigPipe.onPagelet(\"pagelet1\");\u003c/script\u003e\n```\n\nThe `BigPipe.onPagelet` method is part of [big-pipe.js](big-pipe/src/main/resources/public/com/ybrikman/ping/big-pipe.js),\nso make sure to include that script on every page.\n\n## big-pipe.js\n\nThe `BigPipe.onPagelet` method will extract the content from the `code` tag and call `BigPipe.renderPagelet` to render\nit client-side into the DOM node with the specified id (e.g. `pagelet1` in the example above). The default\n`BigPipe.renderPagelet` just inserts the content into the DOM using the `innerHTML` method. If you wish to use a more\nsophisticated method for client-side rendering, simply override the `BigPipe.renderPagelet` with your own:\n\n```javascript\nBigPipe.renderPagelet = function(id, content) {\n  // Provide a custom way to insert the specified content into the DOM node with the given id\n}\n```\n\nThe `id` parameter will be the id of the DOM node and `content` will be your content. Note that if your content was\nJSON instead of HTML, `big-pipe.js` will automatically call `JSON.parse` on it before passing it to you. This can be\nconvenient if you use client-side templating.\n\n## Client-side templating\n\nYou can use a client-side templating technology, such as mustache.js or handlebars.js to render most of your page\nin the browser. To do that, all you need to do is create a `Pagelet` that contains JSON (a `JsValue` for Scala\ndevelopers or `JsonNode` for Java developers) instead of HTML:\n\n```scala\nclass MoreBigPipeExamples(serviceClient: FakeServiceClient) extends Controller {\n\n  /**\n   * Instead of rendering each pagelet server-side with Play's templating, you can send back JSON and render each\n   * pagelet with a client-side templating library such as mustache.js\n   *\n   * @return\n   */\n  def clientSideTemplating = Action {\n    // Make several fake service calls in parallel to represent fetching data from remote backends. Some of the calls\n    // will be fast, some medium, and some slow.\n    val profileFuture = serviceClient.fakeRemoteCallJsonMedium(\"profile\")\n    val graphFuture = serviceClient.fakeRemoteCallJsonMedium(\"graph\")\n    val feedFuture = serviceClient.fakeRemoteCallJsonSlow(\"feed\")\n    val inboxFuture = serviceClient.fakeRemoteCallJsonSlow(\"inbox\")\n    val adsFuture = serviceClient.fakeRemoteCallJsonFast(\"ads\")\n    val searchFuture = serviceClient.fakeRemoteCallJsonFast(\"search\")\n\n    // Convert each Future into a Pagelet which will send the JSON to the browser as soon as it's available\n    val profile = JsonPagelet(\"profile\", profileFuture)\n    val graph = JsonPagelet(\"graph\", graphFuture)\n    val feed = JsonPagelet(\"feed\", feedFuture)\n    val inbox = JsonPagelet(\"inbox\", inboxFuture)\n    val ads = JsonPagelet(\"ads\", adsFuture)\n    val search = JsonPagelet(\"search\", searchFuture)\n\n    // Use BigPipe to compose the pagelets and render them immediately using a streaming template\n    val bigPipe = new BigPipe(PageletRenderOptions.ClientSide, profile, graph, feed, inbox, ads, search)\n    Ok.chunked(views.stream.clientSideTemplating(bigPipe, profile, graph, feed, inbox, ads, search))\n  }\n}\n```\n\nNext, create your custom `BigPipe.renderPagelet` method:\n\n```javascript\n// Override the original BigPipe.renderPagelet method with one that uses mustache.js for client-side rendering\nBigPipe.renderPagelet = function(id, json) {\n  var domElement = document.getElementById(id);\n  if (domElement) {\n    domElement.innerHTML = Mustache.render(template, json);\n  } else {\n    console.log(\"ERROR: cannot render pagelet because DOM node with id \" + id + \" does not exist\");\n  }\n};\n```\n\nSee the `clientSideTemplating` method in\n[controllers/MoreBigPipeExamples.scala](sample-app-scala/app/controllers/MoreBigPipeExamples.scala) (Scala developers) or\n[controllers/MoreBigPipeExamples.java](sample-app-java/app/controllers/MoreBigPipeExamples.java) (Java developers) and\n[big-pipe-with-mustache.js](sample-app-common/src/main/resources/public/javascripts/big-pipe-with-mustache.js) for working examples.\n\n## Composing independent pagelets\n\nTODO: write documentation\n\n## De-duping remote service calls\n\nIf your page is built out of composable, independent pagelets, then each pagelet will know how to fetch all the data it\nneeds from backend services. If each pagelet is truly independent, that means you may have duplicated service calls.\nFor example, several pagelets may make the exact same backend call to fetch the current user's profile. This is\ninefficient and increases the load on downstream services.\n\nThis project comes with a `DedupingCache` library that makes it easy to *de-dupe* service calls. You can use it to\nensure that if several pagelets request the exact same data, you only make one call to a backend service, and all the\nother calls get the same cached response. This class has a single method called `get` that takes a key and a way to\ngenerate the value for that key if it isn't already in the cache.\n\nFor example, if you are using Play's `WSClient` to make remote calls, you could wrap any calls to it with this `get`\nmethod to ensure that any duplicate calls for a given URL get back a cached value:\n\n```scala\nclass ServiceClient {\n  val cache = new DedupingCache[String, Future[WSResponse]]\n\n  def makeRequest(url: String): Future[WSResponse] = {\n    cache.get(url, wsClient.url(url).get())\n  }\n}\n```\n\nSee [controllers/Deduping.scala](sample-app-scala/app/controllers/Deduping.scala) (Scala developers) or\n[controllers/Deduping.java](sample-app-java/app/controllers/Deduping.java) (Java developers) for a complete example of\nhow to setup and use the `DedupingCache`. You will also have to add the `CacheFilter` to your filter chain, as shown in\n[loader/PingApplicationLoader.scala](sample-app-scala/app/loader/PingApplicationLoader.scala) (Scala developers) or\n[loader/Filters.java](sample-app-java/app/loader/Filters.java) (Java developers).\n\n# FAQ\n\n## What are the caveats and drawbacks to BigPipe?\n\nBigPipe is not for everyone. There are some serious drawbacks and caveats you should be aware of before using it:\n\n### HTTP headers and error handling\n\nWith BigPipe streaming, you typically start sending the response back to the browser before your backend calls are\nfinished. The first part of that response is the HTTP headers and once you've sent them back to the browser, it's too\nlate to change your mind. If one of those backend calls fails, you've already sent your 200 OK, so you can no longer\njust send the browser a 500 error or a redirect!\n\nInstead, you must handle errors by injecting JavaScript code into your stream that displays the message when it arrives\nin the browser or redirects the user as necessary. See the `errorHandling` method in\n[controllers/MoreBigPipeExamples.scala](sample-app-scala/app/controllers/MoreBigPipeExamples.scala) (Scala developers) or\n[controllers/MoreBigPipeExamples.java](sample-app-java/app/controllers/MoreBigPipeExamples.java) (Java developers) for\na working example.\n\n### Caching\n\nBecause of the the way headers and error handling work, be extra careful using BigPipe if you cache entire\npages, especially at the CDN level. Otherwise, you may stream out a 200 OK to the CDN, hit an error with a backend call,\nand accidentally end up caching a page with an error on it.\n\nIf your pages are mostly static and can be cached for a long time (e.g. blogs), BigPipe is probably not for you. If\nyour pages are mostly dynamic and cannot be cached (e.g. the news feeds at Facebook, LinkedIn, Twitter), then BigPipe\ncan help.\n\n### Pop-in\n\nPagelets can be sent down to the browser and rendered client-side in any order. Therefore, you have to be careful to\navoid too much \"pop-in\", where rendering each pagelet causes random parts of the page to pop in and move around, which\nmakes the page hard to use.\n\nTo avoid annoying your users, use CSS to size the placeholder elements appropriately so they don't resize or move much\nas the actual content pops in. Alternatively, use JavaScript to ensure that the elements on a page render from top to\nbottom, even if they show up in a different order (e.g. set `display: none` until all the pagelets above the current\none have been filled in).\n\n## Why not AJAX?\n\nYou could try to accomplish something similar to BigPipe by sending back a page that's empty and makes lots of AJAX\ncalls to fill in each pagelet. This approach is much slower than BigPipe for a number of reasons:\n\n1. Each AJAX call requires an extra roundtrip to your server, which adds a lot of latency. This latency is especially\n   bad on mobile or slower connections.\n2. Each extra roundtrip also increases the load on your server. Instead of 1 QPS to load a page, you now have 6 QPS to\n   load a page with 6 pagelets.\n3. Older browsers severly limit how many AJAX calls you can do and most browsers give AJAX calls a low priority during\n   the initial page load.\n4. You have to download, parse, and execute a bunch of JavaScript code before you can even make the AJAX calls.\n5. It only works with JavaScript enabled.\n\nBigPipe gives you all the benefits of an AJAX portal, but without the downsides, by using a single connection\u0026mdash;that\nis, the original connection used to request the page\u0026mdash;and streaming down each pagelet using\n[HTTP Chunked Encoding](https://en.wikipedia.org/wiki/Chunked_transfer_encoding), which works in almost all browsers.\n\n## Where can I find more info?\n\n1. [Composable and Streamable Play Apps](https://engineering.linkedin.com/play/composable-and-streamable-play-apps):\n   a talk that introduces how BigPipe streaming works on top of Play (see the\n   [video](https://www.youtube.com/watch?v=4b1XLka0UIw) and\n   [slides](http://www.slideshare.net/brikis98/composable-and-streamable-play-apps)).\n2. [BigPipe: Pipelining web pages for high performance](https://www.facebook.com/note.php?note_id=389414033919): the\n   original blog post by Facebook that introduces BigPipe on PHP.\n3. [New technologies for the new LinkedIn home page](http://engineering.linkedin.com/frontend/new-technologies-new-linkedin-home-page):\n   the new LinkedIn homepage is using BigPipe style streaming with Play. This ping-play project is loosely based off of\n   the work done originally at LinkedIn.\n\n# Project info\n\n## Status\n\nThis project is in alpha status. It has been used on small projects and is reasonably well coded, tested, and\ndocumented, but it needs more real world usage before it can be considered a mature library. Until the project hits\nversion 1.0.0, backwards compatibility is *not* guaranteed, so expect APIs to change.\n\n## Contributing\n\nContributions in the form of bug reports and pull requests are very welcome.\nCheck out the [help wanted label](https://github.com/brikis98/ping-play/labels/help%20wanted)\nfor ideas.\n\nAlso, if you're using this project in production, [drop me a line](mailto:jim@ybrikman.com),\nas I'd love to hear about your experiences!\n\n## Changelog\n\n### 0.13 (10/22/15)\n\n* Fix issue where the pagelet body was not being escaped correctly\n\n### 0.12 (07/06/15)\n\n* Added support for server-side rendering.\n* Refactored the `Pagelet` API into a trait and subclasses\n* Added the `BigPipe` class for composing and rendering `Pagelets`\n\n### 0.11 (06/30/15)\n\n* First public release.\n\n## Release process\n\nThis project is published to Sonatype as described in the\n[SBT Deploying to Sonatype](http://www.scala-sbt.org/release/docs/Using-Sonatype.html) documentation. To do that, this\nproject uses the [sbt-sonatype](https://github.com/xerial/sbt-sonatype), [sbt-pgp](http://www.scala-sbt.org/sbt-pgp),\nand [sbt-release](https://github.com/sbt/sbt-release) plugins.\n\nTo release a new version:\n\n1. Add an entry to the [Changelog](#Changelog) in this README.\n2. Make sure your PGP keys are setup\n([docs here](http://www.scala-sbt.org/release/docs/Using-Sonatype.html#First+-+PGP+Signatures))\n3. Run the SBT `release` command:\n\n```\nactivator shell\nset credentials += Credentials(\"Sonatype Nexus Repository Manager\", \"oss.sonatype.org\", \"\u003cusername\u003e\", \"\u003cpassword\u003e\")\nrelease\n```\n\nCurrently, only the maintainer, [Yevgeniy Brikman](http://www.ybrikman.com) has the credentials for publishing new\nversions.\n\n## TODO\n\n1. The implementation, tests, and documentation for \"composing\" pagelets are not\n   yet finished. See [#18](https://github.com/brikis98/ping-play/issues/18). Note, you can find an implementation of composable\n   pagelets in the [splink/pagelets](https://github.com/splink/pagelets) library. Perhaps the two can be merged?\n2. There are a number of feature requests. See the [enhancement label](https://github.com/brikis98/ping-play/labels/enhancement)\n   in issues.\n\n# License\n\nThis code is available under the MIT license. See the LICENSE file for more info.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrikis98%2Fping-play","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbrikis98%2Fping-play","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbrikis98%2Fping-play/lists"}