-
Notifications
You must be signed in to change notification settings - Fork 60
/
MoreBigPipeExamples.scala
140 lines (124 loc) · 6.89 KB
/
MoreBigPipeExamples.scala
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
package controllers
import com.ybrikman.ping.javaapi.bigpipe.PageletRenderOptions
import com.ybrikman.ping.scalaapi.bigpipe.HtmlStreamImplicits._
import com.ybrikman.ping.scalaapi.bigpipe._
import data.{FakeServiceClient, Response}
import play.api.libs.concurrent.Execution.Implicits._
import play.api.mvc.{Action, Controller}
import play.twirl.api.Html
import scala.concurrent.Future
/**
* A few more BigPipe examples
*
* @param serviceClient
*/
class MoreBigPipeExamples(serviceClient: FakeServiceClient) extends Controller {
/**
* Renders the exact same page as WithBigPipe#index, but this time with server-side rendering. This will render all
* pagelets server-side and send them down in-order. The page load time will be longer than with out-of-order
* client-side rendering (albeit still faster than not using BigPipe at all), but the advantage is that server-side
* rendering does not depend on JavaScript, which is important for certain use cases (e.g. older browsers, search
* engine crawlers, SEO).
*
* @return
*/
def serverSideRendering = Action {
// Make several fake service calls in parallel to represent fetching data from remote backends. Some of the calls
// will be fast, some medium, and some slow.
val profileFuture = serviceClient.fakeRemoteCallMedium("profile")
val graphFuture = serviceClient.fakeRemoteCallMedium("graph")
val feedFuture = serviceClient.fakeRemoteCallSlow("feed")
val inboxFuture = serviceClient.fakeRemoteCallSlow("inbox")
val adsFuture = serviceClient.fakeRemoteCallFast("ads")
val searchFuture = serviceClient.fakeRemoteCallFast("search")
// Convert each Future into a Pagelet which will be rendered as HTML as soon as the data is available
val profile = HtmlPagelet("profile", profileFuture.map(views.html.helpers.module.apply))
val graph = HtmlPagelet("graph", graphFuture.map(views.html.helpers.module.apply))
val feed = HtmlPagelet("feed", feedFuture.map(views.html.helpers.module.apply))
val inbox = HtmlPagelet("inbox", inboxFuture.map(views.html.helpers.module.apply))
val ads = HtmlPagelet("ads", adsFuture.map(views.html.helpers.module.apply))
val search = HtmlPagelet("search", searchFuture.map(views.html.helpers.module.apply))
// Use BigPipe to compose the pagelets and render them immediately using a streaming template. Note that we're using
// ServerSide rendering in this case.
val bigPipe = new BigPipe(PageletRenderOptions.ServerSide, profile, graph, feed, inbox, ads, search)
Ok.chunked(views.stream.withBigPipe(bigPipe, profile, graph, feed, inbox, ads, search))
}
/**
* Instead of rendering each pagelet server-side with Play's templating, you can send back JSON and render each
* pagelet with a client-side templating library such as mustache.js
*
* @return
*/
def clientSideTemplating = Action {
// Make several fake service calls in parallel to represent fetching data from remote backends. Some of the calls
// will be fast, some medium, and some slow.
val profileFuture = serviceClient.fakeRemoteCallJsonMedium("profile")
val graphFuture = serviceClient.fakeRemoteCallJsonMedium("graph")
val feedFuture = serviceClient.fakeRemoteCallJsonSlow("feed")
val inboxFuture = serviceClient.fakeRemoteCallJsonSlow("inbox")
val adsFuture = serviceClient.fakeRemoteCallJsonFast("ads")
val searchFuture = serviceClient.fakeRemoteCallJsonFast("search")
// Convert each Future into a Pagelet which will send the JSON to the browser as soon as it's available
val profile = JsonPagelet("profile", profileFuture)
val graph = JsonPagelet("graph", graphFuture)
val feed = JsonPagelet("feed", feedFuture)
val inbox = JsonPagelet("inbox", inboxFuture)
val ads = JsonPagelet("ads", adsFuture)
val search = JsonPagelet("search", searchFuture)
// Use BigPipe to compose the pagelets and render them immediately using a streaming template
val bigPipe = new BigPipe(PageletRenderOptions.ClientSide, profile, graph, feed, inbox, ads, search)
Ok.chunked(views.stream.clientSideTemplating(bigPipe, profile, graph, feed, inbox, ads, search))
}
/**
* Shows an example of how to handle an error that occurs part way through streaming a response to the browser. Since
* you've already sent back the headers with a 200 OK, it's too late to send back a 500 error page, so instead, you
* have to inject JavaScript into the stream that will show an appropriate error page.
*
* @return
*/
def errorHandling = Action {
// Make several fake service calls in parallel to represent fetching data from remote backends. Some of the calls
// will be fast, some medium, and some slow. One of the calls (the feed) will fail with an error.
val profileFuture = serviceClient.fakeRemoteCallMedium("profile")
val graphFuture = serviceClient.fakeRemoteCallMedium("graph")
val feedFuture = serviceClient.fakeRemoteCallErrorSlow("feed")
val inboxFuture = serviceClient.fakeRemoteCallSlow("inbox")
val adsFuture = serviceClient.fakeRemoteCallFast("ads")
val searchFuture = serviceClient.fakeRemoteCallFast("search")
// Convert each Future into a Pagelet which will be rendered as HTML as soon as the data is available. Note that
// the render method used here will also handle the case where the Future completes with an error by rendering an
// error message.
val profile = HtmlPagelet("profile", render(profileFuture))
val graph = HtmlPagelet("graph", render(graphFuture))
val feed = HtmlPagelet("feed", render(feedFuture))
val inbox = HtmlPagelet("inbox", render(inboxFuture))
val ads = HtmlPagelet("ads", render(adsFuture))
val search = HtmlPagelet("search", render(searchFuture))
// Use BigPipe to compose the pagelets and render them immediately using a streaming template
val bigPipe = new BigPipe(PageletRenderOptions.ClientSide, profile, graph, feed, inbox, ads, search)
Ok.chunked(views.stream.withBigPipe(bigPipe, profile, graph, feed, inbox, ads, search))
}
/**
* Shows an example of how BigPipe escapes the contents of your pagelets so
* they cannot break out of their containing HTML elements (which are
* intentionally invisible).
*
* @return
*/
def escaping = Action {
val shouldBeEscapedFuture = serviceClient.fakeRemoteCallJsonFast(FakeServiceClient.RESPONSE_TO_TEST_ESCAPING)
val shouldBeEscaped = JsonPagelet("shouldBeEscaped", shouldBeEscapedFuture)
val bigPipe = new BigPipe(PageletRenderOptions.ClientSide, shouldBeEscaped)
Ok.chunked(views.stream.escaping(bigPipe, shouldBeEscaped))
}
/**
* When the given Future redeems, render it with the module template. If the Future fails, render it with the
* error template.
*
* @param dataFuture
* @return
*/
private def render(dataFuture: Future[Response]): Future[Html] = {
dataFuture.map(views.html.helpers.module.apply).recover { case t: Throwable => views.html.helpers.error(t) }
}
}