Add user guide for the 'boot-gemfire-with-scoped-proxies' sample.
Resolves Issue #3.
This commit is contained in:
@@ -0,0 +1,349 @@
|
||||
= Spring Session - HttpSession with Apache Geode Client/Server using Spring Boot
|
||||
John Blum
|
||||
:toc:
|
||||
|
||||
This guide describes how to build a _Spring Boot_ application configured with _Spring Session_ that transparently
|
||||
delegates to Apache Geode for managing a Web application's `javax.servlet.http.HttpSession` in a clustered/replicated,
|
||||
highly available and optionally, durable fashion.
|
||||
|
||||
In addition, this samples explores the effects of using _Spring Session_ and Apache Geode (or Pivotal GemFire) to
|
||||
transparently manage the (HTTP) Session when the _Spring Boot_, Web application also declares both Session and Request
|
||||
scoped bean definitions used by the application when processing requests.
|
||||
|
||||
This sample originated from a https://stackoverflow.com/questions/45674137/can-session-scope-beans-be-used-with-spring-session-and-gemfire[_StackOverflow_ post],
|
||||
which posed the following question...
|
||||
|
||||
> _Can session scope beans be used with Spring Session and GemFire?_
|
||||
|
||||
The poster when on to state...
|
||||
|
||||
> If using Spring Session for "session" scoped beans Spring creates an extra HttpSession for this bean,
|
||||
is this an existing issue? What is the solution for this?
|
||||
|
||||
Well, in a nutshell, the answer to the first question is most definitely, *yes*. And, the second statement/question
|
||||
is not correct/valid, as explained in the answer.
|
||||
|
||||
This sample uses Apache Geode's client/server topology with a pair of _Spring Boot_ applications, one to configure
|
||||
and run a Geode Server, and another to configure and run a GemFire-based cache client, Spring MVC Web application
|
||||
making use of the `HttpSession`.
|
||||
|
||||
NOTE: The completed guide can be found in the section,
|
||||
<<spring-session-sample-boot-geode-with-scoped-proxies,Spring Boot Sample Web Application with Apache Geode-managed HttpSessions and Request and Session Scoped Proxy Beans>>,
|
||||
below.
|
||||
|
||||
== Updating Dependencies
|
||||
|
||||
Before using _Spring Session_, you must ensure that the required dependencies are included.
|
||||
If you are using _Maven_, include the following `dependencies` in your `pom.xml`:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
[subs="verbatim,attributes"]
|
||||
----
|
||||
<dependencies>
|
||||
<!-- ... -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.session</groupId>
|
||||
<artifactId>spring-session-data-geode</artifactId>
|
||||
<version>${spring-session-data-geode-version}</version>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
----
|
||||
|
||||
NOTE: if using Pivotal GemFire, you may substitute the `spring-session-data-gemfire` artifact
|
||||
for `spring-session-data-geode`.
|
||||
|
||||
ifeval::["{version-snapshot}" == "true"]
|
||||
Since we are using a SNAPSHOT version, we need to add the _Spring_ Snapshot Maven Repository.
|
||||
If you are using _Maven_, include the following `repository` declaration in your `pom.xml`:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
ifeval::["{version-milestone}" == "true"]
|
||||
Since we are using a Milestone version, we need to add the _Spring_ Milestone Maven Repository.
|
||||
If you are using _Maven_, include the following `repository` declaration in your `pom.xml`:
|
||||
|
||||
.pom.xml
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<!-- ... -->
|
||||
|
||||
<repository>
|
||||
<id>spring-libs-milestone</id>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
endif::[]
|
||||
|
||||
// tag::config[]
|
||||
[[httpsession-spring-java-configuration-gemfire-boot]]
|
||||
== Spring Boot Configuration
|
||||
|
||||
After adding the required dependencies and repository declarations, we can create the _Spring_ configuration
|
||||
for both our Apache Geode client and server using _Spring Boot_. The _Spring_ configuration is responsible for
|
||||
creating a `Servlet Filter` that replaces the `HttpSession` with an implementation backed by _Spring Session_
|
||||
and Apace Geode.
|
||||
|
||||
=== Spring Boot, Apache Geode Cache Server
|
||||
|
||||
We start with a _Spring Boot_ application to configure and bootstrap the Apache Geode server process...
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}boot/gemfire-with-scoped-proxies/src/main/java/sample/server/GemFireServer.java[tags=class]
|
||||
----
|
||||
|
||||
<1> First, we annotate the Apache Geode server configuration class with `@SpringBootApplication` to declare this as a
|
||||
_Spring Boot_ application allowing us to leverage all of _Spring Boot's_ features (e.g. _auto-configuration_).
|
||||
<2> Next, we declare _Spring Data Geode's_ `@CacheServerApplication`, which creates a peer, cache server that allows
|
||||
our cache client to connect.
|
||||
<3> (Optional) Then, we declare the `@EnableGemFireHttpSession` annotation to create the necessary server-side `Region`
|
||||
(by default, "_ClusteredSpringSessions_") used to store the `HttpSessions` state. This step is optional since
|
||||
the `Region` used to store `Session` state could be manually created. Using `@EnableGemFireHttpSession` is easy
|
||||
and convenient, and ensure that our client and server-side Region match by name, which is required by GemFire/Geode.
|
||||
<4> Additionally, and optionally, we also enable Apache Geode's embedded Management service, which allows JMX clients
|
||||
(e.g. Apache Geode's _Gfsh_ shell tool) to connect to the server and manage the server/cluster.
|
||||
<5> Finally, we adjust the port that the `CacheServer` will use to listen for cache clients by declaring
|
||||
a `CacheServerConfigurer` bean that modified the port using SDG's `CacheServerFactoryBean` with property placeholders.
|
||||
Defaults to *40404* if not adjusted.
|
||||
|
||||
The sample also makes use of _Spring's_ `PropertySourcesPlaceholderConfigurer` in order to externalize
|
||||
the sample application's configuration using a properties file or with JVM System properties.
|
||||
|
||||
=== Spring Boot, Apache Geode Cache Client Web application
|
||||
|
||||
Now, we create a _Spring Boot_ Web application exposing our Web service with _Spring_ MVC, running as an Apache Geode
|
||||
cache client connected to our _Spring Boot_, Apache Geode server. The Web application will use _Spring Session_
|
||||
backed by Apache Geode to manage (HTTP) Session state in a clustered (i.e. distributed) and replicated,
|
||||
as well as highly available manner.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}boot/gemfire-with-scoped-proxies/src/main/java/sample/client/Application.java[tags=class]
|
||||
----
|
||||
|
||||
<1> Like the server, we declare our Web application to be a _Spring Boot_ application
|
||||
by annotating our application class with `@SpringBootApplication`.
|
||||
<2> `@Controller` is a _Spring_ Web MVC annotation enabling our MVC handler mapping methods (i.e. methods annotated
|
||||
with `@RequestMapping`) to process client HTTP requests (e.g. <6>)
|
||||
<3> We also declare our Web application to be a Geode cache client by annotating our application class with
|
||||
`@ClientCacheApplication`. Additionally, we adjust a few basic, "DEFAULT" Geode client `Pool` settings.
|
||||
<4> Next, we declare that the Web application will use _Spring Session_ backed by Apache Geode to manage the (HTTP)
|
||||
`Session's` state by annotating the nested `ClientCacheConfiguration` class with `@EnableGemFireHttpSession`.
|
||||
This will create the necessary client-side `PROXY` `Region` (by default, "ClusteredSpringSessions`) corresponding to
|
||||
the same server `Region` by name. All Session state will be sent from the client to the server through `Region`
|
||||
data access operations. The client-side `Region` uses the "DEFAULT" `Pool` (of connections) to communicate
|
||||
with the server.
|
||||
<5> Then, we adjust the port used by the cache client `Pool` to connect to the `CacheServer` using a SDG
|
||||
`ClientCacheConfigurer`.
|
||||
<6> We adjust the _Spring_ Web MVC configuration to set the home page, and...
|
||||
<7> Finally, we declare the `/counts` HTTP request handler method to keep track of the number of instances created by
|
||||
the Sprig container of both Request and Session scoped proxy beans, of types `RequestScopedProxyBean`
|
||||
and `SessionScopedProxyBean`, respectively, every time a request is processed by this Web service endpoint.
|
||||
|
||||
TIP: In typical Geode production deployments, where the cluster includes potentially hundreds or thousands of servers
|
||||
(a.k.a. data nodes), it is more common for clients to connect to 1 or more Geode Locators running in the cluster.
|
||||
A Locator passes meta-data to clients about the servers available in the cluster, the individual server load
|
||||
and which servers have the client's data of interest, which is particularly important for direct, single-hop data access
|
||||
and latency-sensitive operations. See more details about the
|
||||
http://geode.apache.org/docs/guide/12/topologies_and_comm/cs_configuration/standard_client_server_deployment.html[Client/Server Deployment]
|
||||
in the Apache Geode User Guide.
|
||||
|
||||
NOTE: For more information on configuring _Spring Data Geode, refer to the
|
||||
http://docs.spring.io/spring-data-gemfire/docs/current/reference/html/[Reference Guide].
|
||||
|
||||
==== Enabling GemFire HttpSession Management
|
||||
|
||||
`@EnableGemFireHttpSession` enables a developer to configure certain aspects of both _Spring Session_ and Apache Geode
|
||||
out-of-the-box using the following attributes:
|
||||
|
||||
* `clientRegionShortcut` - specifies the Apache Geode http://geode.apache.org/docs/guide/12/developing/region_options/region_types.html[data management policy]
|
||||
used on the client with http://geode.apache.org/releases/latest/javadoc/org/apache/geode/cache/client/ClientRegionShortcut.html[ClientRegionShortcut]
|
||||
(default is `PROXY`). This attribute is only used when configuring the client `Region`.
|
||||
* `indexableSessionAttributes` - Identifies the Session attributes by name that should be indexed for querying purposes.
|
||||
Only Session attributes explicitly identified by name will be indexed. This is useful in situations where your application
|
||||
is looking up the `HttpSession` by the currently authenticated user's, or principal's, name.
|
||||
* `maxInactiveIntervalInSeconds` - controls _HttpSession_ idle-timeout expiration (defaults to **30 minutes**).
|
||||
* `poolName` - name of the dedicated Apache Geode `Pool` used to connect a client to the cluster of servers.
|
||||
This attribute is only used when the application is a cache client. Defaults to `gemfirePool`.
|
||||
* `regionName` - specifies the name of the Apache Geode `Region` used to store and manage `HttpSession` state
|
||||
(default is "*ClusteredSpringSessions*").
|
||||
* `serverRegionShortcut` - specifies the Apache Geode http://geode.apache.org/docs/guide/12/developing/region_options/region_types.html[data management policy]
|
||||
used on the server with http://geode.apache.org/releases/latest/javadoc/org/apache/geode/cache/RegionShortcut.html[RegionShortcut]
|
||||
(default is `PARTITION`). This attribute is only used when configuring server `Regions`, or when a P2P topology
|
||||
is employed.
|
||||
* `sessionSerializerBeanName` - refers to the name of the bean that handles serializing the (HTTP) `Session` state
|
||||
between the client and the server.
|
||||
|
||||
NOTE: It is important to remember that the Apache Geode client `Region` name must match a server `Region`
|
||||
by the same name if the client `Region` is a `PROXY` or `CACHING_PROXY`. Client and server `Region` names
|
||||
are not required to match if the client `Region` used to store Sessions is `LOCAL` only. However, keep in mind
|
||||
that Session state will not be propagated to the server when the client Region is only `LOCAL` to the client
|
||||
and you lose all the benefits of using Apache Geode to store and manage Session state information on the servers
|
||||
in a cluster in a highly available and replicated manner.
|
||||
|
||||
=== Session-scoped Proxy Bean
|
||||
|
||||
The Geode cache client, Spring Web application defines the `SessionScopedProxyBean` class.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/SessionScopedProxyBean[tags=class]
|
||||
----
|
||||
|
||||
<1> First, the `SessionScopedProxyBean` domain class is stereotyped as a Spring `@Component` to be picked up in
|
||||
_Spring's_ classpath component-scan.
|
||||
<2> Additionally, instances of this class are scoped to the (HTTP) `Session`. That is, each time a client interaction
|
||||
results in a (HTTP) Session being created (such as by a login event), a single instance of this type will be created
|
||||
and will last for the duration of the (HTTP) Session. When the Session ends or expires, this instance is destroyed
|
||||
by the _Spring_ container. If the client re-establishes a new (HTTP) Session, then another, new instance of this type
|
||||
will be provided to the application's beans. However only ever 1 instance of this type exists for the duration
|
||||
of the (HTTP) Session, no more!
|
||||
<3> Finally, this class keeps track of how many instances of this type are created by the _Spring_ container throughout
|
||||
the entire lifecycle of the application.
|
||||
|
||||
TIP: More information on Spring's `@SessionScope` (i.e. Session-scoped proxy beans) can be found in the
|
||||
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-scopes-other[Reference Documentation]
|
||||
and specifically https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-scopes-session[this].
|
||||
|
||||
=== Request-scoped Proxy Bean
|
||||
|
||||
The Geode cache client, Spring Web application defines the `RequestScopedProxyBean` class.
|
||||
|
||||
[source,java]
|
||||
----
|
||||
include::{samples-dir}boot/gemfire-with-scoped-proxies/src/main/java/sample/client/model/RequestScopedProxyBean[tags=class]
|
||||
----
|
||||
|
||||
<1> First, this `RequestScopedProxyBean` domain class is stereotyped as a Spring `@Component` to be picked up in
|
||||
_Spring's_ classpath component-scan.
|
||||
<2> Additionally, instances of this class are scoped to the (HTTP) Request. That is, each time a client request
|
||||
is sent (e.g. to process a Thread-scoped transaction), a single instance of this type will be created and will last
|
||||
for the duration of the (HTTP) Request. When the Request ends, this instance is destroyed by the _Spring_ container.
|
||||
Any subsequent client (HTTP) Requests results in another, new instance of this type, which will be provided to
|
||||
the application's beans. However, only ever 1 instance of this type exists for the duration of the (HTTP) Request,
|
||||
no more!
|
||||
<3> Finally, this class keeps track of how many instances of this type are created by the _Spring_ container throughout
|
||||
the entire lifecycle of the application.
|
||||
|
||||
TIP: More information on Spring's `@RequestScope` (i.e. Request-scoped proxy beans) can be found in the
|
||||
https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-scopes-other[Reference Documentation]
|
||||
and specifically, https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#beans-factory-scopes-request[this].
|
||||
|
||||
[spring-session-sample-boot-geode-with-scoped-proxies]]
|
||||
== Spring Boot Sample Web Application with Apache Geode-managed HttpSessions and Request and Session Scoped Proxy Beans
|
||||
|
||||
=== Running the Boot Sample Application
|
||||
|
||||
You can run the sample by obtaining the {download-url}[source code] and invoking the following commands.
|
||||
|
||||
First, you must run the server:
|
||||
|
||||
----
|
||||
$ ./gradlew :spring-session-sample-boot-gemfire:run [-Dgemfire.log-level=config]
|
||||
----
|
||||
|
||||
Then, in a separate terminal, run the client:
|
||||
|
||||
----
|
||||
$ ./gradlew :spring-session-sample-boot-gemfire:bootRun [-Dgemfire.log-level=config]
|
||||
----
|
||||
|
||||
You should now be able to access the application at http://localhost:8080/.
|
||||
|
||||
In this sample, the Web application is the _Spring Boot_, Apache Geode cache client
|
||||
and the server is standalone, separate (JVM) process.
|
||||
|
||||
=== Exploring the Boot Sample Application
|
||||
|
||||
Try using the application. Fill out the form with the following information:
|
||||
|
||||
* **Attribute Name:** _username_
|
||||
* **Attribute Value:** _test_
|
||||
|
||||
Now click the **Set Attribute** button. You should now see the attribute name and value displayed in the table
|
||||
along with an additional attribute (`requestCount`) indicating the number of Session interactions (via HTTP requests).
|
||||
|
||||
=== How does it work?
|
||||
|
||||
We interact with the standard `javax.servlet.http.HttpSession` in the the Spring Web MVC service endpoint,
|
||||
shown here for convenience:
|
||||
|
||||
.src/main/java/sample/client/Application.java
|
||||
[source,java]
|
||||
----
|
||||
@RequestMapping(method = RequestMethod.POST, path = "/session")
|
||||
public String session(HttpSession session, ModelMap modelMap,
|
||||
@RequestParam(name = "attributeName", required = false) String name,
|
||||
@RequestParam(name = "attributeValue", required = false) String value) {
|
||||
|
||||
modelMap.addAttribute("sessionAttributes",
|
||||
attributes(setAttribute(updateRequestCount(session), name, value)));
|
||||
|
||||
return INDEX_TEMPLATE_VIEW_NAME;
|
||||
}
|
||||
----
|
||||
|
||||
Instead of using the embedded HTTP server's `HttpSession`, we are actually persisting the Session state in Apache Geode.
|
||||
_Spring Session_ creates a cookie named SESSION in your browser that contains the id of your Session.
|
||||
Go ahead and view the cookies (click for help with https://developer.chrome.com/devtools/docs/resources#cookies[Chrome]
|
||||
or https://getfirebug.com/wiki/index.php/Cookies_Panel#Cookies_List[Firefox]).
|
||||
|
||||
NOTE: The following instructions assume you have a local Apache Geode installation. For more information on installation,
|
||||
see http://geode.apache.org/docs/guide/12/prereq_and_install.html[Prerequisites and Installation Instructions].
|
||||
|
||||
If you like, you can easily remove the Session using `gfsh`.
|
||||
|
||||
For example, on a Linux-based system type the following at the command-line:
|
||||
|
||||
$ gfsh
|
||||
|
||||
Then, enter the following commands in _Gfsh_, ensuring to replace `70002719-3c54-4c20-82c3-e7faa6b718f3` with the value
|
||||
of your SESSION cookie, or the Session id returned by the Apache Geode OQL query (which should match):
|
||||
|
||||
....
|
||||
gfsh>connect --jmx-manager=localhost[1099]
|
||||
|
||||
gfsh>query --query='SELECT * FROM /ClusteredSpringSessions.keySet'
|
||||
|
||||
Result : true
|
||||
startCount : 0
|
||||
endCount : 20
|
||||
Rows : 1
|
||||
|
||||
Result
|
||||
------------------------------------
|
||||
70002719-3c54-4c20-82c3-e7faa6b718f3
|
||||
|
||||
NEXT_STEP_NAME : END
|
||||
|
||||
gfsh>remove --region=/ClusteredSpringSessions --key="70002719-3c54-4c20-82c3-e7faa6b718f3"
|
||||
....
|
||||
|
||||
NOTE: The _Apache Geode User Guide_ contains more detailed instructions on using
|
||||
http://geode.apache.org/docs/guide/12/tools_modules/gfsh/chapter_overview.html[gfsh].
|
||||
|
||||
Now visit the application at `http://localhost:8080/` again and observe the attribute we added is no longer displayed.
|
||||
|
||||
Alternatively, you can wait **20 seconds** for the Session to timeout and expire and then refresh the page.
|
||||
The attribute we added should no longer be displayed in the table.
|
||||
Reference in New Issue
Block a user