diff --git a/docs/src/docs/asciidoc/guides/boot-gemfire-with-scoped-proxies.adoc b/docs/src/docs/asciidoc/guides/boot-gemfire-with-scoped-proxies.adoc new file mode 100644 index 0000000..8008ba6 --- /dev/null +++ b/docs/src/docs/asciidoc/guides/boot-gemfire-with-scoped-proxies.adoc @@ -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, +<>, +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"] +---- + + + + + org.springframework.session + spring-session-data-geode + ${spring-session-data-geode-version} + pom + + + org.springframework.boot + spring-boot-starter-web + + +---- + +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] +---- + + + + + spring-libs-snapshot + https://repo.spring.io/libs-snapshot + + +---- +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] +---- + + + + + spring-libs-milestone + https://repo.spring.io/libs-milestone + + +---- +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.