Here are a few tips that you should keep in mind when developing web applications in
any framework not just Spark and FreeMarker; these are general principles.
Read this article first then read the following three articles and then with that context re-read
this article again and it should make more sense. This FAQ is a compression of knowledge
and wisdom that could fill a couple of courses in web development and concurrent programming.
Limit the use of instance variables in UI Controllers.
(Spark Route classes)
If you are tempted to store any data as an
attribute (aka instance variables) on a UI Controller don't! The only
attributes should be objects that are wired (injected) into the
route object at creation-time. See How do I know where to put data?
FAQ for suggestions for proper scoping of data in a web app.
The reason behind this proclamation is because this component must be
thread safe. UI Controllers in a webapp are likely to be used by multiple concurrent
Java threads;
a thread for each concurrent HTTP request. There is only one Route object for each
HTTP Verb-URL combination; thus every thread inside of a given handle(Request, Response)
method has access to that object's attributes. So if one user is modifying attributes
but then another other user's thread jumps in and also modifies these same attributes,
then things will get cross-contaminated and become very confusing for your users.
Instead, as mentioned above, store data in some appropriate scope and not
in an attribute.
Abuse of the Session scope leads to complex UI Controller code and violations
in the Information Expert principle. Instead of storing raw data in the Session
consider finding a more appropriate location for the data. Look first to the Model tier
and then look to the Application tier.
UI Controllers should limit the code in the handle method.
Controllers should focus on a very limited set of activities:
Extract request data in the HTTP request body or query parameters.
Perform conversion of the request data.
HTTP passes all data in the form of simple
strings. The Controller should convert any data into a Java primitive or object that
is appropriate for the action the code needs to perform. For example, if a query parameter
is a number "42" then the controller should convert it to a Java int primitive.
If the data is a complex JSON string, then the Controller should use a JSON parser
(like Google's GSON library) to convert the string to a Java object.
Delegate the domain logic of the request to a Model (or Application) tier component.
Select an appropriate UI View to respond to the request.
If the View is unique to this controller then render it. If the selected View is
native to a different Controller than consider using an HTTP redirect
to have the native Controller render that View.
Render the View
For an HTML View the Route must create a View-Model map of data that is then used by
the template engine to render the selected View. For an Ajax Route the body should be
a JSON object representation.
All other activities should be delegated to components in other tiers.
First consider the Model tier components and then if there's not a good fit there
consider the Application tier components.
Limit the logic inside of UI View components.
Template engines are powerful tools that can perform a lot of logic, but restrain from the
temptation to put a lot of logic here. Specifically, View logic should not:
perform calculations
invoke methods that have side-effects
Both of these fall into the realm of the UI Controller or other Java objects in other tiers.
This is just scratching the surface, if you have more general questions about web development
use the myCourses Discussion forum to post your questions.
How do I know where to put data?
There are roughly three scopes in web development:
Request scope
This is data that is only relevant to a single HTTP request for a single user.
This data must be stored as local variables in the Route
handle method.
Session scope
This is data that is only relevant to a single user but is required to exist
across multiple HTTP requests. This data should be stored in either a Model or Application tier object that
is accessible from the session scope.
This data can also be stored in the Session object, but this should be reserved only for data that is needed temporarily, such as during an HTTP redirect. It should be deleted as soon as it is no longer needed. Developers regularly abuse the Session object by storing large amounts of data making for a poor design.
Application scope
This is data that is accessible to all HTTP requests for all users for all time.
This data must be stored in Application tier components.
Knowing exactly where to place data (information or objects) is the hardest job of
system development. Follow these heuristics: (A) Put short-term (request scope) data in the local
variables of methods starting at the Route.handler method and all methods
it calls. (B) Put long-term data in Model (or Application) tier components.
Lastly, you usually put one essential object in the Spark Session that is the
root of interaction with the rest of the system; that object is usually some form of User.
With that object as a key you can find other application objects within other tiers.
How does an HTTP request really work?
Note: this information will not help you with your coding effort but it is useful
to understand the Layers involved in a particular Tier; in this case the UI tier.
Your UI components interact with the layer below: Spark. And Spark interacts with the
layer below it: Jetty. The layer below Jetty is the
Java EE Web specification and
core Java and the operating system (OS), of course.
As we talked about in the
Web Architecture and Development
lesson, HTTP is a network protocol that makes a TCP/IP connection to the server and sends
a request. The server then generates a response that is sent back along the TCP connection
and the browser renders the HTML in the response body.
The simplified view of an HTTP request.
As you can guess there is quite a lot going on behind the scenes. The following diagram
shows a few of the more critical aspects of making and processing an HTTP request.
A detailed look at an HTTP request.
Let's look at each step in the process.
Some user action triggers an HTTP request.
For example, clicking on a link will trigger a GET HTTP request for
the resource at the URL in the href attribute of the link tag.
Request the web page
The browser has a block of code that initiates the HTTP request by creating a TCP/IP
Socket that establishes a network connection to the server.
The HTTP request is sent to the server
The HTTP request is sent on the output stream of the TCP socket connection. The
browser waits for the HTTP response to arrive on the client's input stream.
Jetty's Server awaits HTTP connections
When the webapp starts up Jetty creates a ServerSocket listening to port 4567.
It is this action that turns a regular Java application into a web server.
When a client attempts to establish a TCP connection the Jetty server will
"accept" the connection by creating a server-side Socket. This socket
automatically associates the client's output stream to the server's input stream; and
the server's output stream to the client's input stream; thus creating a path way from
the request to the response.
The Jetty Socket invokes the Spark Servlet
According to the Java EE Web Specification, a Servlet is a software
component that handles an HTTP request. Thus Jetty parses the HTTP input stream to
an HttpServletRequest object and connects the output stream to
an HttpServletResponse object. These two objects are passed to the
Spark Servlet.
Also at this point Jetty will assign a unique Java thread to process this HTTP request.
Thus every invocation of the Spark Servlet will be in its own thread.
The Spark Servlet dispatches to an application Route object
Based upon the HTTP verb and URL in the HttpServletRequest object Spark
will select an appropriate Application layerRoute object. It will also
wrap the servlet request and response objects in Spark's own Request
and Response classes. These two objects are passed to the handle
method of the selected Route object.
The Route object uses FreeMaker to render an HTML response
The UI Controller Route object handles the HTTP request and determines which
UI View to render. It delegates rendering to the FreeMaker template engine by passing
it the file name of the template and a View-Model map of view attributes.
The HTML is sent back to the browser
The HTML text that the template engine renders is passed as the return value of
the Application layer's Route handle method. The Spark servlet knows
how to push that text content onto the HttpServletResponse object that
ultimately pushes the text as the body of the HTTP response output stream.
The browser renders the new HTML content
The HTTP response stream is then parsed by the client's socket on its input stream.
This HTML content is then rendered by the browser.
The user enjoys the new HTML view
Finally, the browser will close the TCP socket connection and that closes the server
socket connection. Closing the server-side socket will also free up the thread used
by that HTTP request; the thread goes back to a pool for the next HTTP request to be
processed.
How does the Jetty web server get started?
As mentioned in the previous FAQ "How does an HTTP request really work?"
Jetty is a Java library that provides a server that listens to a TCP port (default: 4567)
and handles HTTP requests. Spark builds on top of this library by providing an API for
configuring pairs of HTTP verbs (GET, POST, and so on) with URL patterns. For example,
in the Guessing Game web application you use the Spark get and post
methods to build the routes of the webapp.
import static spark.Spark.*;
import spark.TemplateEngine;
import spark.template.freemarker.FreeMarkerEngine;
public class HelloApp {
public static void main(String[] args) {
final TemplateEngine templateEngine = new FreeMarkerEngine();
get("/", new GetHomeRoute(templateEngine));
post("/hello", new PostHelloRoute(templateEngine));
}
}
The Spark get method call on line 8 adds the first route of the application
that "ignites" the Spark framework.
The SparkServer uses dependency injection to have the appropriate Jetty
Handler that knows how to dispatch HTTP requests to Spark router handlers.
That is the connection between the HTTP/TCP processing by Jetty
and the Spark framework. Ultimately, the Jetty server passes up each HTTP request that
Spark then "routes" to Router objects based upon the HTTP verb and URL combination.
How to share a View between multiple UI Controllers?
There are times when the result of a given UI Controller is to render a View that is also
generated by another existing UI Controller. As the developer you have a few choices. First
you could copy the code that builds the Model-View data map from the original Controller into
the new Controller. This leads to duplicated code and thus is not very
DRY.
Alternatively, you can use an HTTP trick to tell the browser to render the view by redirecting
the browser to perform an additional HTTP request that invokes the other Controller that already
knows how to render this View. How to perform a redirect is the next FAQ.
There is a third possibility: You can put this common code into a parent class abstraction
or in a helper class.
How do you do a redirect?
First you have to ask yourself: What is an HTTP redirect?. It is when the Route
handler for one URL pattern responds by telling the browser to navigate to a different URL;
thus a re-direction.
To demonstrate Spark code for doing an HTTP redirect we will illustrate the use case described
in the previous FAQ: delegating View rendering to another Controller.
Imagine you are building an eCommerce webapp what happens if the user clicks the Home
link while filling-out an order? Wouldn't you want the user to be sent back to the order form?
Let's assume the answer is yes.
public class GetHomeRoute implements Route {
public String handle(Request request, Response response) {
// retrieve the HTTP session
final Session httpSession = request.session();
// retrieve an Order, if in-progress
final Order order = shoppingCart.getActiveOrder();
// if there is no active order
if (order == null) {
// render the Home page with plenty of product placement
return templateEngine.render(new ModelAndView(vm, VIEW_NAME));
}
else {
// there is an active order so go back to the Order form
// so redirect the user to the Game view
response.redirect(WebServer.ORDER_FORM_URL);
halt();
return null;
}
}
}
In reality this scenario has dubious value and could really frustrate the customer,
but this is just a mock example.
The redirect magic occurs on lines 17-19. The redirect
method on the Response object add an HTTP header to give the browser a new
Location to navigate to. Recall that Spark is expecting that the return
value of a Route is the body of the response (an HTML page or a JSON object) but in this
situation there is no body. You might think that the null returned by this
Route's handle method tells Spark that there is no HTTP response body but
that is not exactly true. You see the Spark halt method actually throws
an exception that the Spark framework catches
and handles; thus circumventing the body generation step of the handling the HTTP request.
There, now aren't you glad you asked?
How to share information across an HTTP redirect?
If you do choose to use an HTTP redirect to have a different UI Controller generate
the UI View, there is one major gotcha: Frequently you might need to send the user a message.
But the other UI Controller won't know about this message.
First let's look at a sketch of the Controller doing the redirect:
public class PostAddItemToCartRoute implements Route {
public String handle(Request request, Response response) {
// retrieve the HTTP session
final Session httpSession = request.session();
// process adding a line item to the user's shopping cart
// NOT SHOWN
// create the user message and put into Session scope
final String myMessage = ""; // MESSAGE NOT SHOWN
httpSession.attribute("myMessage", myMessage);
// use the GetShoppingCartRoute controller to display the View
response.redirect("/shoppingCart");
halt();
return null;
}
}
Now let's look at a sketch of the Controller handling the redirect:
public class GetShoppingCartRoute implements Route {
public String handle(Request request, Response response) {
// retrieve the HTTP session
final Session httpSession = request.session();
// start building the View-Model
final Map<String, Object> vm = new HashMap<>();
vm.put(TITLE_ATTR, TITLE);
// is there a session-scoped message to display?
final String myMessage = httpSession.attribute("myMessage");
if (myMessage != null) {
vm.put("message", myMessage);
httpSession.removeAttribute("myMessage");
}
// render the View
return templateEngine.render(new ModelAndView(vm, VIEW_NAME));
}
}
The solution is to use the Session scope to store (temporarily) a single message object.
The primary Controller generates the message and stores it on the Session. Then that
Controller performs a redirect that another Controller will handle (in a different HTTP request thread).
This second Controller looks into the Session to see if an outstanding message has been
put on the Session scope. If such a message exists it is added to the View-Model
attribute map and then removes the message from the Session. The second
action is necessary so the same message isn't displayed too many times.
How does a View refresh it's data?
Imagine you have a View that displays a list of users. Once the view has been generated
by the server and rendered by the browser, that list of users can become stale as new users
sign-in and other sign-out. What you want is for the View to update itself periodically
so that the current user sees a fresh set of data every few seconds.
The solution has nothing to do with any server-side code but the refresh must be requested
by the browser. This can be accomplished with a meta tag in the HTML of the View; like this:
<!DOCTYPE html>
<head>
<meta http-equiv="refresh" content="10">
<title>${title} | My App</title>
</head>
<body>
<!-- BODY NOT SHOWN -->
</body>
</html>
Line 3 is the HTML metadata tag that tells the browser that this View would be refreshed
after 10 seconds.
There is one gotcha with this technique. The HTTP request that generated
this response becomes subsequent HTTP requests to perform the refresh. Thus if this View
was generated from a POST /someAction request then the browser will attempt
to re-POST the same URL (and form data if applicable) and this is usually a bad idea because
a POST request usually makes some change to the backend data and typically you don't want
to repeat these changes. Therefore, if you need to generate a View that refreshes from
a POST UI Controller then that Controller should perform a redirect to an HTTP GET
request that renders the View that can then be refreshed from that same GET HTTP Route component.
There are other techniques for making sure a page has up-to-date information such
as Ajax and Web Sockets. However, we urge teams to use the easiest technique to
accomplish this goal; the HTTP refresh meta tag is by far the easiest, no coding
required.
How can I connect multiple users to my application?
To thoroughly test your application, you will need to have multiple users working with the application. Each user will have to connect to the server in a different HTTP session. The different sessions are needed so that the Spark framework will use a different Session object for each connection and your application can distinguish between the different users when your routes process HTTP requests.
Here are several ways that you can get multiple connections to use different sessions:
Connect through a different localhost address within one browser. You usually use localhost:4567/ to connect to your application. This defaults to the "localhost" IP address of 127.0.0.0. You can also explicitly use a different address in the reserved localhost IP address range by changing the last number in the IP address. For example, within one browser, if you connect to 127.0.0.x:4567/ where x is 2, 3, 4, ... 127. this will use a different session than the connection to localhost:4567/ in a different window of that browser.
This works easily on Windows machines. If the application is running on a Mac, there is setup work that you have to do to get it to recognize other that 127.0.0.1 as a local IP address. Information is available at How do you get loopback addresses other than 127.0.0.1 to work on OS X. You can place multiple ifconfig commands to setup multiple 127.0.0.x ports in a script file that you run using sudo before you start a testing session.
Connect with different browsers, i.e. Firefox, IE, Chrome, Safari. Connections from different browsers will be in different sessions.
Connect through the private/incognito window. A connection in a private/incognito window will be in a different session than the connection in a regular window. With some browsers, each private/incognito window connects in a different session while in others all private/incognito windows share the same session.
Connect from different machines. Start the application on one machine and make sure that port 4567 is open to external connection requests. Determine the IP address for the machine running the application. From other machines, connect to port 4567 on the server IP address, such as, 192.168.217.128:4567/.
How do you handle when a client connection breaks?
There are two scenarios relevant to the concept of a "broken HTTP connection".
First, the user initiates an HTTP request and then immediately kills the browser's
tab or exits the browser application altogether. In either situation, the server only
knows that the TCP connection has been severed. The web server then closes the TCP
socket and terminates the Java thread that is processing that HTTP request.
At no time will the Application layer code be aware of this situation.
So there is nothing that you need to code to handle a broken connection. However, continue reading the next FAQ question.
How to recognize (handle) session timeout?
In the "How do you handle when a client connection breaks?" FAQ above
we talked about what happens when an HTTP connection is lost. What is more
interesting is the other side of the question: How do you know when a user has left the
application running on the browser but has walked away?
The Java EE Servlet specification provides a wide variety of events that a web application
can listen to. Unfortunately, Spark doesn't provide any hooks to register such event listeners.
However, there is one type of event listener that doesn't require an explicit event
registration hook: HttpSessionBindingListener. This is a Java interface that
you implement on your own class and whenever an object of this type is added to the session
Jetty will fire the valueBound method of this interface. When the object is
removed from the session the valueUnbound method is called.
The next piece of information is that every session has a built-in timeout.
The Jetty web server will track the timeout count-down and when the inactivity has
lasted past the timeout threshold then Jetty will invalidate that user's session.
Part of invalidating a session Jetty will remove (un-bind) each session
attribute. This is how the binding listener API can be used to discover when a session
times-out.
So what you can do is create a UI tier helper class that contains some Application tier
component that can handle the session timeout. Here is an example session timeout watchdog
helper class that uses the PlayerServices object to handle the end of the player's
session and do any cleanup.
public class SessionTimeoutWatchdog implements HttpSessionBindingListener {
private static final Logger LOG = Logger.getLogger(SessionTimeoutWatchdog.class.getName());
private final PlayerServices playerServices;
public SessionTimeoutWatchdog(final PlayerServices playerServices) {
LOG.fine("Watch dog created.");
this.playerServices = Objects.requireNonNull(playerServices);
}
@Override
public void valueBound(HttpSessionBindingEvent event) {
// ignore this event
LOG.fine("Player session started.");
}
@Override
public void valueUnbound(HttpSessionBindingEvent event) {
// the session is being terminated do some cleanup
playerServices.endSession();
//
LOG.fine("Player session ended.");
}
}
So to use this helper class you just need to add a single instance of this watchdog component
to the session when the session is first created (see line 12).
public class GetHomeRoute implements Route {
public String handle(Request request, Response response) {
// retrieve the HTTP session
final Session httpSession = request.session();
// if this is a brand new browser session
if (httpSession.isNew()) {
// get the object that will provide client-specific services for this player
final PlayerServices playerService = gameCenter.newPlayerServices();
httpSession.attribute(PLAYERSERVICES_KEY, playerService);
httpSession.attribute(TIMEOUT_WATCHDOG_KEY, new SessionTimeoutWatchdog(playerService));
// FIXME remove this line after testing the timeout hook with a short session lifetime
httpSession.maxInactiveInterval(60); // session terminates after 1 minute of inactivity
// render the Home view
// CODE NOT SHOWN
}
else {
// redirect to Game View
// CODE NOT SHOWN
}
}
}
Notice also line 14; here the code is explicitly declaring that the timeout threshold is
60 seconds. I did this just to test this watchdog component without having to wait a long
time. BTW: Jetty does not have a default timeout; meaning sessions never
timeout by default. So you can use code like line 14 to set a desired timeout for each
newly created session. 30 minutes is a common timeout threshold; don't make the timeout
too short.
How do I keep data across server restarts?
All of your data will disappear when you restart the application. How do I keep data across server restarts?
The short answer is: You don't. At least for this project, you are not required to do that. In a full-blown
Enterprise-scale web application you would use some form of database, but for this project we
decided to eliminate the need for persistent storage requiring integration with a database, which is a big undertaking.
Do's and Dont's of Ajax Development
As mentioned in the
Web Architecture and Development lecture slides,
Ajax is the process sending an HTTP request that does not render
and HTML page but rather provides an action that returns a JSON object representation.
Always return some JSON content in the response.
At the very least an Ajax response should contain a message about what the server
action performed. Typically you might also want to know whether the action was
successful or not; therefore the response should be a JSON object with two attributes:
(a) the text of the message and (b) some status code. In the WebCheckers project
the UserMessage class serves this purpose.
Never attempt to do an HTTP redirect from an Ajax call.
An HTTP redirect response on an Ajax request is usually a bad idea. If your goal
is to tell browser, "Hey, browser, get this URL to display the next View," this will
not work because a redirect on an Ajax call does not result in
browser redirection. Don't do it!