• No results found

HTTP request scope

Scope: a fresh breath of state

5.4 Domain-specific scopes: the web

5.4.1 HTTP request scope

The request scope is interesting because it isn’t strictly a segment in the life of an injector. Since requests can be concurrent, request scopes are also concurrent (but disparate from one another). Let’s take the example of an online comic book store.

Sandman is one of my favorite books, so I do a search for Sandman by typing in "sand-

man" at the appropriate page. To the comics store, this is a request for a document containing a list of Sandman titles. Let’s call the service that generates this document ComicSearch. ComicSearch must

Read my search criteria from the incoming requestQuery a database

Render a list of results

Let’s make another service that accesses the database and call this ComicAccess. ComicAccess will in turn depend on database-specific services like connections and statements in order to deliver results. Listing 5.4 describes the code for the comic store so far.

public class ComicSearch {

private final ComicAccess comicAccess; @Inject

public ComicSearch(ComicAccess comicAccess) { this.comicAccess = comicAccess;

}

public HTML searchForComics(String criteria) { ...

} }

Now it makes sense for the ComicAccess object to be a singleton—it has no state, con- nects to a database as needed, and will probably be shared by several clients (other web pages needing to access the comic store). The searchForComics() method takes search criteria (typed in by a user) and returns an HTML object.

NOTE Of course, this is a hypothetical class and the exact form of search- ForComics() may be different depending on the web framework you choose. But its semantics remain the same—it takes in search criteria and converts them to an HTML page displaying a list of matches.

ComicSearch is itself stateless (since its only dependency, ComicAccess, is immutable), so we could bind it as a singleton. Given this case, it actually works quite well. Since there is no request-specific context, binding ComicSearch either as a singleton or no scope is viable.

Let’s expand this example. Let’s say we add a requirement that the store shows me items of interest based on my prior purchases. It’s not important how it determines my interests, just that it does. Another service, UserContext, will handle this work:

public class UserContext { private String username;

private final ComicAccess comicAccess; @Inject

public UserContext(ComicAccess comicAccess) { this.comicAccess = comicAccess;

}

public List<Comic> getItemsOfInterest() { ...

}

public void signIn(String username) { this.username = username;

}

}

The method getItemsOfInterest()scans old purchases using ComicAccess and builds a list of suggestions. The interesting part about UserContext is its field user- name and method signIn():

public void signIn(String username) { this.username = username;

}

When called with an argument, signIn() stores the current user. You’ll notice that signIn() is more or less a setter method. But I’ve deliberately avoided calling it setUsername() to distinguish it from a dependency setter. signIn() will be called from ComicSearch, which itself is triggered by user interaction. Here’s the modified code from listing 5.4, reflecting the change:

public class ComicSearch {

private final ComicAccess comicAccess; private final UserContext user; @Inject

public ComicSearch(ComicAccess comicAccess, UserContext user) { this.comicAccess = comicAccess;

this.user = user; }

public HTML searchForComics(String criteria, String username) { user.signIn(username);

List<Comic> suggestions = user.getItemsOfInterest(); ...

} }

ComicSearch is triggered on a request from a user, and method searchForComics() is provided with an additional argument, username, also extracted from the HTTP request. The UserContext object is configured with this username. Now any results it returns will be specific to the current user.

Let’s put the UserContext to work and expand this another step. We’ll add a requirement that the list of results should not show any comics that a user has already

purchased. One way to do this is with two queries, one with the entire set of results and the second with a history of purchases, displaying only the difference. That sequence would be:

1 Query all matches for criteria from database. 2 Query history of purchases for current user.

3 Iterate every item in step 1, comparing them to every item in step 2, and

remove any matches.

4 Display the remainder.

This works, but it seems awfully complex. It is a lot of code to write and a bit superflu- ous. Furthermore, if these sets are reasonably large and contain a lot of overlap, it could mean doing a large amount of work to bring up results that are simply thrown away. Worse, two queries are two trips to the database, which is expensive and unnec- essary in a high-traffic environment.

Another solution is to create a special finder method on ComicAccess that accepts the username and builds a database query sensitive to this problem. This is much better, because only the relevant results come back and the impact to ComicSearch is very small: public HTML searchForComics(String criteria, String username) {

user.signIn(username);

List<Comic> suggestions = user.getItemsOfInterest();

List<Comic> results = comicAccess.searchNoPriorPurchases(criteria, username);

... }

But we can do one better. By moving this work off to UserContext, it avoids Comic- Search having to know the requisite details for querying comics:

public class UserContext { private String username;

private final ComicAccess comicAccess; @Inject

public UserContext(ComicAccess comicAccess) { this.comicAccess = comicAccess;

}

public List<Comic> getItemsOfInterest() { ...

}

public List<Comic> searchComics(String criteria) {

return comicAccess.searchNoPriorPurchases(criteria, username); }

public void signIn(String username) { this.username = username;

} }

Now ComicSearch is a lot simpler:

public HTML searchForComics(String criteria, String username) { user.signIn(username);

List<Comic> suggestions = user.getItemsOfInterest();

List<Comic> results = user.searchComics(criteria);

... }

The real saving comes with request scoping. We already need to bind ComicSearch and UserContext in the request scope (because their state is tied to a single user’s request). Now let’s say that instead of signing in a user in the searchForComics() method, we’re able to do it at the beginning of a request before the ComicSearch page (say, in a servlet filter). This is important because it means that authentication logic is separated from business logic. Furthermore, it means code to sign in the user need be written only once and won’t have to litter every page:

public class ComicSearch {

private final UserContext user; @Inject

public ComicSearch(UserContext user) { this.user = user;

}

public HTML searchForComics(String criteria) {

List<Comic> suggestions = user.getItemsOfInterest();

List<Comic> results = user.searchComics(criteria); ...

} }

Notice that it is much leaner now and focused on its core purpose. But where has the code for signing in a user gone? Here’s one possible way it may have disappeared.

REQUEST SCOPING IN GUICE WITH GUICE-SERVLET

Listing 5.5 shows one implementation using a Java servlet filter and the guice-servlet extension library for Guice.

Guice Servlet and Guice

Guice servlet is an extension to Guice that provides a lot of web-specific functional- ity, including web-domain scopes (request, session). Guice servlet is registered in web.xml as a filter itself and then later configured using a Guice module. It allows you to manage and intercept servlets or filters via Guice’s injector (which is not oth- erwise possible).

import javax.servlet.Filter;

@Singleton

public class UserFilter implements Filter {

private final Provider<UserContext> currentUser;

@Inject

public UserFilter(Provider<UserContext> currentUser) { this.currentUser = currentUser;

}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {

currentUser.get().signIn(...); chain.doFilter(request, response); }

... }

There are some interesting things to say about listing 5.5:

■ UserFilter is a servlet filter applied at the head of every incoming request.

■ It is declared under singleton scope (note the @Singleton annotation).

It is injected with a Provider<UserContext> so that a request-scoped UserContext may be obtained each time.

User credentials are extracted from the request and set on the current User-

Context.

I use a Provider<UserContext> instead of directly injecting a UserContext because UserFilter is a singleton, and once a singleton is wired with any object, that object gets held onto despite its scope. This is known as scope-widening injection and is a prob- lem that I discuss in some detail in the next chapter.

Interestingly enough, in listing 5.4 I was able to use constructor injection to get hold of the UserContext provider:

@Inject

public UserFilter(Provider<UserContext> currentUser) { this.currentUser = currentUser;

}

Ordinarily, this wouldn’t be possible for a filter registered in web.xml according to the Java Servlet Specification. However, guice-servlet gets around this by sitting between Java servlets and the Guice injector. Listing 5.6 shows how this is done, with a web.xml that is configured to use guice-servlet.

<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4"

Listing 5.5 An injector-managed servlet filter using guice-servlet (using Guice)

Listing 5.6 web.xml configured with guice-servlet and Guice

Set up current user Continue processing request

web.xml namespace boilerplate

xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" > <listener> <listener-class>example.MyGuiceCreator</listener-class </listener> <filter> <filter-name>guiceFilter</filter-name> <filter-class>com.google.inject.servlet.GuiceFilter</filter-class> </filter> <filter-mapping> <filter-name>guiceFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>

Guice-servlet’s architecture is depicted in figure 5.19.

Notice that in listing 5.6 a servlet context listener named MyGuiceCreator is regis- tered. This is a simple class that you create to handle the job of bootstrapping the injector. It is where you tell guice-servlet what filters and servlets you want the Guice injector to manage. Here’s what a MyGuiceCreator would look like if it were config- ured to filter all incoming requests with UserFilter (to do our authentication work): public class MyGuiceCreator extends GuiceServletContextListener {

@Override

protected Injector getInjector() {

return Guice.createInjector(new ServletModule() { @Override

protected void configureServlets() { filter("/*").through(UserFilter.class) } }); } } web.xml namespace boilerplate

Listener creates injector on web app deploy

Filter all URLs through guice-servlet

reroute

Injector

guice-servlet Incoming

request Filter Filter Servlet

Figure 5.19 Incoming requests are rerouted by guice-servlet to injector-managed filters or servlets.

This code is self-explanatory, but let’s go through it anyway. Remember, MyGuice- Creator is a class you provide to bootstrap and configure the injector (it must extend GuiceServletContextListener). The factory method Guice.createInjector() takes instances of Guice’s Module as argument. You configure filters in guice-servlet via a programmatic API (rather than web.xml):

filter("/*").through(UserFilter.class)

You could continue adding filters and servlets. Each servlet and filter must be anno- tated @Singleton.

Let’s get back to request scoping. By moving code out to the UserContext object, every request receives its own instance of ComicSearch and UserContext. We were able to achieve this transition without any impact to ComicAccess and minimal impact to ComicSearch. Declarative scoping of objects is thus a very powerful and unintrusive technique.

This is all very well. But how do we actually bind these scopes in Spring, Guice, and others? Let’s take a look:

import com.google.inject.servlet.RequestScoped;

public class ComicStoreModule extends AbstractModule { @Override

protected void configure() {

bindComicAccess.class).to(ComicAccessImpl.class).in(Singleton.class); bind(UserContext.class).in(RequestScoped.class); bind(ComicSearch.class).in(RequestScoped.class); ... } }

In the example code, I’ve bound both UserContext and ComicSearch in @Request- Scoped. This is a scope made available by guice-servlet and represents the HTTP request scope in the world of Guice.3 ComicAccess is a simple singleton, and so it is a straightforward binding (to its implementation, ComicAccessImpl):

bind(ComicAccess.class).to(ComicAccessImpl.class).in(Singleton.class); This same effect can be achieved in Spring with its own set of web-specific scoping util- ities. The following section explores the techniques involved there.

REQUEST SCOPING IN SPRING

In Spring’s XML configuration mode, these bindings are slightly different (see list- ing 5.7).

<beans ...>

<bean id="data.comics" class="ComicAccessImpl" scope="singleton">

3 Don’t forget that you need to register guice-servlet’s GuiceFilter in web.xml, as shown previously.

... </bean>

<bean id="web.user" class="UserContext" scope="request"> <constructor-arg ref="data.comics"/>

</bean>

<bean id="web.comicSearch" class="ComicSearch" scope="request"> <constructor-arg ref="data.comics"/>

<constructor-arg ref="web.user"/> </bean>

</beans>

The only new thing in listing 5.7 is the attribute scope="request" on bindings web.user and web.comicSearch. Of course, none of the three classes need change at all. Like Guice and guice-servlet, Spring requires additional configuration in web.xml. First off, you need to bootstrap a Spring injector when the web application is deployed. In Guice we used a ServletContextListener (recall MyGuiceCreator, the subclass of GuiceServletContextListener). You do this in Spring too:

<web-app ...> ... <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/comic-store.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener> ... </web-app>

This web.xml is similar to the one we saw earlier with guice-servlet. Notice that we needed to set a context parameter with the name of the XML configuration file. You can think of it as the web equivalent of the following:

BeanFactory injector = new FileSystemXmlApplicationContext("WEB-INF/comic-

➥ store.xml");

Now we’ve bootstrapped the injector and told it where to find its configuration. But that’s not all; as guice-servlet did for Guice, there’s still an integration layer that needs to be configured to get request scopes going:

<web-app ...> ... <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> ... </web-app>

This listener must appear in addition to the ContextLoaderListener shown previ- ously. Now you’re set up to shop comics with Spring.

TIP We haven’t quite looked at how to configure filters with Spring’s injector. Guice-servlet handled this for us via its GuiceFilter. Spring does not have this support out of the box. But a sister project, Spring Security (for- merly Acegi Security4) does. Spring Security’s FilterToBeanProxy does a similar job.

Apart from these practical aspects, there are things to keep in mind about request scoping:

A thread is typically dedicated to a request for the request’s entire span. This

means that request-scoped objects are not multithreaded.

■ It also means that they are generally not thread-safe.

■ Integration layers that provide request scoping often cache scoped objects in thread-locals.

■ In rare cases, web servers may use multiple threads to service a request (for instance, while processing long-running asynchronous requests). If you are designing a request-scoping library in such a scenario, be aware of thread- local assumptions.

Most of these are pretty low-level and specific to the architecture in question. In the Java servlet world, threads and requests are almost always the same thing (though once completed, a thread may clear its context and proceed to service other requests). So you should be careful to clean up at the end of a request. If you are

designing request scopes, you should carefully research these potential hazards. Per-

haps even more useful than the request scope is the HTTP session scope. This tech- nique allows you to keep objects around between requests in a semantic user session. Using dependency injection, a lot of the glue code to make this happen goes away. Session scoping is thus a powerful tool in the dependency injector’s toolbox.

5.4.2 HTTP session scope

An HTTP session scope is the next step up from a request scope. HTTP sessions are an abstraction invented to make up for the fact that the HTTP protocol is stateless. This means that it does not easily allow for long-running interactions with a user to be main- tained on the server side. To get around this, developers use clever techniques and string together a series of requests from the same user and call it a session (figure 5.20). An HTTP session has important characteristics:

■ A session represents a single, unique user’s interaction with a web application.

A session is composed of one or more requests from the same user.Not all requests are necessarily part of a session.

■ A session is a kind of store that preserves state between requests.

■ The two logical end points of a session are user login and logout.

You can visualize a session as made up of multiple independent requests from the same user (as in figure 5.20). In figure 5.21, each instance of a request-scoped object is unique to that request (R1 to R3). But a session-scoped instance is shared across all those requests.

Sessions are extremely useful for tracking state relative to a specific user, since a session always exists around one user. These uses include:

Tracking a user’s credentials for security

purposes

■ Tracking a user’s recent activity for quick navigation (for example, breadcrumbs)

Tracking preferences, to personalize a user’s

experience

■ Caching user-specific information for quick access

■ Caching general, constant data for quick access

All these use cases involve storing state temporarily, generally to improve a user’s experience through the site. Whether that is about presentation or under the covers, it’s about performance. And that’s essentially what sessions do. Objects scoped under a session retain their state across requests, essentially continuing from where the last request left off.

Typically, sessions start and end when a user logs in and logs out. This behavior may be customized as necessary. Some requests (for static content, for example) do not participate in a user session and are considered stateless. These requests are inde- pendent of sessions, and, generally speaking, services participating in them shouldn’t have any user-specific functionality.

Like requests, sessions may also be concurrent. For instance, multiple users who log in at once are said to be in different, unique sessions. While session-scoped instances are shared across requests inside a session, they are independent between sessions. Figure 5.22 shows how this might look in an injector’s timeline.

request Web server User request request HTTP session

Figure 5.20 A series of related requests from the same user forms an HTTP session.

Injector Session (request) (request) (request) (request) R1 (request) R2 (request) R3

Figure 5.21 A session is composed of independent requests from a user.

In this figure, U1 is an object that exists in the first user’s session, and U2 is a different instance of the same key that exists in the sec- ond user’s session (it also is an aging Irish rock band). U1 and U2 are completely independent of one another. But within the first user’s ses- sion, all requests share the same instance (U1)—likewise with the second user and U2. Another interesting point is that the second user’s session does not start for a while into the application’s life. So there is a time when U2 is out of scope while U1 is in scope, even though both are instances of the same key (let’s call it U) bound under session scope.

Another interesting thing about figure 5.22 is that the second user actually logs out and

logs back in (at the point marked re-login). This means that there are two instances of U2 for the second user because she started two sessions. The state of U2 prior to the