summaryrefslogtreecommitdiff
path: root/book/src/main/scalatex/book/handson/ClientServer.scalatex
diff options
context:
space:
mode:
Diffstat (limited to 'book/src/main/scalatex/book/handson/ClientServer.scalatex')
-rw-r--r--book/src/main/scalatex/book/handson/ClientServer.scalatex42
1 files changed, 24 insertions, 18 deletions
diff --git a/book/src/main/scalatex/book/handson/ClientServer.scalatex b/book/src/main/scalatex/book/handson/ClientServer.scalatex
index 3ddb88b..8cbf9cf 100644
--- a/book/src/main/scalatex/book/handson/ClientServer.scalatex
+++ b/book/src/main/scalatex/book/handson/ClientServer.scalatex
@@ -21,6 +21,9 @@
@p
Scala.js lets you share code between client and server relatively straightforwardly. As we saw in the previous chapter, where we made a shared module. Let's work to turn that shared module into a working client-server application!
+@val server = wd/'examples/'crossBuilds/'clientserver/'server/'src/'main/'scala/'simple
+@val client = wd/'examples/'crossBuilds/'clientserver/'client/'src/'main/'scala/'simple
+
@sect{A Client-Server Setup}
@p
Getting started with client-server integration, let's go with the simplest configuration possible: a Spray server and a Scala.js client. Most of the other web-frameworks (@lnk.misc.Play, @lnk.misc.Scalatra, etc.) will have more complex configurations, but the basic mechanism of wiring up Scala.js to your web framework will be the same. Our project will look like this:
@@ -43,7 +46,7 @@
@p
First, let's do the wiring in @code{build.sbt}:
- @hl.ref("examples/crossBuilds/clientserver/build.sbt")
+ @hl.ref(wd/'examples/'crossBuilds/'clientserver/"build.sbt")
@p
We have two projects: @code{client} and @code{server}, one of which is a Scala.js project (indicated by the presence of @hl.scala{scalaJSSettings}). Both projects share a number of settings: the presence of the @code{shared/} folder, which shared code can live in (similar to what we saw in @sect.ref{Cross Publishing Libraries}) and the settings to add @lnk.github.Scalatags and @lnk.github.uPickle to the build. Note that those two dependencies use the triple @code{%%%} instead of the double @code{%%} to declare: this means that for each dependency, we will pull in the Scala-JVM or Scala.js version depending on whether it's being used in a Scala.js project. Note also the @hl.scala{packageArchetype.java_application} setting, which isn't strictly necessary depending on what you want to do with the application, but this example needs it as part of the deployment to Heroku.
@@ -54,7 +57,8 @@
@p
Next, let's kick off the Spray server in our Scala-JVM main method:
- @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala")
+
+ @hl.ref(server/"Server.scala")
@p
This is a not-very-interesting @lnk("spray-routing", "http://spray.io/documentation/1.2.2/spray-routing/") application: we set up a server on @code{localhost:8080}, have the root URL serve the main page on GET, and have other GET URLs serve resources. This includes the @code{js-fastopt.js} file that is now in our resources because of our @code{build.sbt} config earlier! We also add a POST route to allow the client ask the server to list files various directories.
@@ -62,14 +66,14 @@
@p
The HTML template @hl.scala{Page.skeleton} is not shown above; I put it in a separate file for neatness:
- @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Page.scala")
+ @hl.ref(server/"Page.scala")
@p
This is a typical @lnk.github.Scalatags HTML snippet. Note that since we're serving it directly from the server in Scala code, we do not need to leave a @code{.html} file somewhere on the filesystem! We can declare all HTML, including the skeleton of the page, in Scalatags. Otherwise it's the same as what we saw in earlier chapters: A simple HTML page which includes a script tag to run our Scala.js application.
@p
Lastly, we'll set up the Scala.js main method, which we are calling in the @hl.html{<script>} tag above to kick off the client-side application.
- @hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala")
+ @hl.ref(client/"Client.scala")
@p
Again this is a simple Scala.js application, not unlike what we saw in earlier chapters. However, there is one difference: earlier, we made our Ajax calls to @code{api.openweathermap.org/...}. Here, we're making it to @code{/ajax}: the same server the page is served from!
@@ -77,7 +81,7 @@
@p
You may have noticed in both client and server, we have made reference to a mysterious @hl.scala{FileData} type which holds the name and size of each file. @hl.scala{FileData} is defined in the @code{shared/} folder, so it can be accessed from both Scala-JVM and Scala.js:
- @hl.ref("examples/crossBuilds/clientserver/client/shared/main/scala/simple/FileData.scala")
+ @hl.ref(client/"FileData.scala")
@p
Now, if we go to the browser at @code{localhost:8080}, we should see our web-page!
@@ -120,10 +124,10 @@
@p
However, there is still some amount of duplication in the code. In particular, the definition of the endpoint name "list" is duplicated 4 times:
- @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala", """path("ajax" / "list")""", "")
- @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala", "list(", "")
- @hl.ref("examples/crossBuilds/clientserver/server/src/main/scala/simple/Server.scala", "def list", "")
- @hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala", "ajax/list", "")
+ @hl.ref(server/"Server.scala", """path("ajax" / "list")""", "")
+ @hl.ref(server/"Server.scala", "list(", "")
+ @hl.ref(server/"Server.scala", "def list", "")
+ @hl.ref(client/"Client.scala", "ajax/list", "")
@p
Three times on the server and once on the client! What's worse, two of the appearances of @hl.scala{"list"} are in string literals, which are not checked by the compiler to match up with themselves or the name of the method @hl.scala{list}. Apart from this, there is one other piece of duplication that is unchecked: the type being returned from @hl.scala{list} (@hl.scala{Seq[FileData]}) is being repeated on the client in @hl.scala{upickle.read[Seq[FileData]]} in order to de-serialize the serialized data. This leaves three opportunities for error wide-open:
@@ -146,12 +150,14 @@
@p
@lnk("Autowire", "https://github.com/lihaoyi/autowire") is a library that turns your request routing layer from a fragile, hand-crafted mess into a solid, type-checked, boilerplate-free experience. Autowire basically turns what was previously a stringly-typed, hand-crafted Ajax call and route:
- @hl.ref("examples/crossBuilds/clientserver/client/src/main/scala/simple/Client.scala", "ajax/list", "")
+ @hl.ref(client/"Client.scala", "ajax/list", "")
@p
Into a safe, type-checked function call:
- @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala", ".call()", "")
+ @val client2 = wd/'examples/'crossBuilds/'clientserver2/'client/'src/'main/'scala/'simple
+ @val server2 = wd/'examples/'crossBuilds/'clientserver2/'server/'src/'main/'scala/'simple
+ @hl.ref(client2/"Client.scala", ".call()", "")
@p
Let's see how we can do that.
@@ -172,7 +178,7 @@
@p
Let's start with our client-server interface definition
- @hl.ref("examples/crossBuilds/clientserver2/client/shared/main/scala/simple/Shared.scala")
+ @hl.ref(client2/"Shared.scala")
@p
Here, you can see that in addition to sharing the @hl.scala{FileData} class, we are also creating an @hl.scala{Api} trait which contains the signature of our @hl.scala{list} method. The exact name of the trait doesn't matter. We need it to be in @code{shared/} so that the code in both client and server can reference it.
@@ -180,16 +186,16 @@
@p
Next, let's look at modifying our server code to make use of Autowire:
- @hl.ref("examples/crossBuilds/clientserver2/server/src/main/scala/simple/Server.scala")
+ @hl.ref(server2/"Server.scala")
@p
Now, instead of hard-coding the route @hl.scala{"ajax" / "list"}, we now take in any route matching @hl.scala{"ajax" / Segments}, feeding the resultant path segments into the @hl.scala{Router} object:
- @hl.ref("examples/crossBuilds/clientserver2/server/src/main/scala/simple/Server.scala", "path(")
+ @hl.ref(server2/"Server.scala", "path(")
@p
The @hl.scala{Router} object in turn simply defines how you intend the objects to be serialized and deserialized:
- @hl.ref("examples/crossBuilds/clientserver2/server/src/main/scala/simple/Server.scala", "object Router", "object Server")
+ @hl.ref(server2/"Server.scala", "object Router", "object Server")
@p
In this case using uPickle. Note how the @hl.scala{route} call explicitly states the type (here @hl.scala{Api}) that it is to generate routes against; this ensures that only methods which you explicitly put in your public interface @hl.scala{Api} are publically reachable.
@@ -197,12 +203,12 @@
@p
Next, let's look at the modified client code:
- @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala")
+ @hl.ref(client2/"Client.scala")
@p
There are two main modifications here: the existence of the new @hl.scala{Ajaxer} object, and the modification to the Ajax call-site. Let's first look at @hl.scala{Ajaxer}:
- @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala", "object Ajaxer", "@JSExport")
+ @hl.ref(client2/"Client.scala", "object Ajaxer", "@JSExport")
@p
Like the @hl.scala{Router} object, @hl.scala{Ajaxer} also defines how you perform the serialization and deserialization of data-structures, again using uPickle. Unlike the @hl.scala{Router} object, @hl.scala{Ajaxer} also defines how the out-going Ajax call gets sent over the network. Here we're doing it using the @hl.scala{Ajax.post} method.
@@ -210,7 +216,7 @@
@p
Lastly, let's look at the modified callsite for the ajax call itself:
- @hl.ref("examples/crossBuilds/clientserver2/client/src/main/scala/simple/Client.scala", "def update", "")
+ @hl.ref(client2/"Client.scala", "def update", "")
@p
There are a few things of note here: