This is an demo project to show a simple technique to externalize HttpSession from application server's local cache to external data store which is JBoss DataGrid/Infinispan in this case. Session externalization is highly desirable technique to increase resilience of applications, so that applications can recover their sessions after unexpected crashes.
In some cases it's highly desirable to share session in between disparate applications and services. Nowadays more and more applications structured and deployed as multiple small components as in micro services and modular architectures. It's also not that rare to see application components are deployed on to different platforms. One service might be implemented and deployed as Spring Boot application and another service might be deployed to JBoss EAP. Heterogeneous environments everywhere.
Although JBoss Data Grid/Infinispan has an out of the box capability that you can externalize HttpSession to JBoss DataGrid/Infinispan on JBoss EAP/Wildfly, that out of the box capability allows session sharing in between applications that are only the part of the same enterprise application (ear). According to the JEE Servlet Specification HttpSession objects must be scoped at the application (or servlet context) level.
In this demo I'm using a simple filtering technique to wrap Http Session object to replace local cache with a JBoss Data Grid technique. In this example the application is deployed on to three distinct application server. The application is not a clustered /distributed application, so it's really not any different than deploying three different applications. The diagram below depicts the overall demo system context. The flow of a request is below:
- User sends a request (either a page or a service request)
- HttpRequestFilter intercepts that request and replaces the regular HttpSession object with the Grid one.
- Page or service components processes the request regularly and if it's needed uses the HttpSession in a regular way. The grid session object uses the data grid instead of the local cache.
I believe that the flow that I've explained above can be applicable to any other platform. All you need is a way to intercept requests, wrap session object and use the suitable data grid library to communicate with it. JBoss DataGrid/Infinispan offers a rich library set for different platforms.
Although the flow I've explained above is good enough to externalize HttpSession, session sharing in between application servers/platforms requires some extra tricks. Since JEE scopes HttpSession at the application level, session identities (like JSESSIONID) are not shared in between application servers. Therefore, application level Http Session does not offer required context and a higher level context is needed. That context should be bounded to a unique concept like user id, client id which can help us to distinguish the source of the requests. If you prefer to use the user identity as I do in this example, there are helpful SSO solutions like Red Hat SSO / JBoss Keycloak to implement this concept.
As I've mentioned, user id used to create a shared session concept in this example. However, for the sake of the simplicity of the example, there is no SSO solution in place and authentication should be done manually as entering username, password pair.
All of the application server in this demo hosted in docker containers, so you need docker to run images and also build some images. Docker must be installed and configured. See the following documentation to download and configure docker on your platform
A comprehensive platform list can be found here
To get started with Docker, please refer to the official user guide
Demo project is written in Java language as mavenized project. You need to have both JDK 1.8+ and Maven
All of my demo scripts prepared to run on Linux, you need to adopt run and destroy scripts for your OS.
Git is needed to clone this repo to your local
The first thing that you need to do is clonning this project to your local
git clone -r https://github.com/serhat-dirik/jboss-generic-http-session-externalization-to-jdg
If you've successfully installed all prerequisites and have an healthy internet connection, all you need to do is executing the start script.
./runCommunityDemo.sh
On the initial run it will download required docker images from docker hub, so this might be a time consuming operation depending your internet connection speed. As part of the execution maven build processes will be triggered and two deployable application will be prepared under deployments subdirectory."jdg-service.war" application is prepared to deploy wildfly containers and "jdg-service4tomcat.war" is prepared to deploy on to tomcat. Why different applications? This is simply because of tomcat has no rest library and additional libraries need to be deployed with web application.
Start script will add hostnames into your /etc/hosts file, so you can access containers by their host names. Open your favorite browser and goto http://wildfly-node1:8080/jdg-service url. It will ask your credentials to log into the application. user:demouser1 password:redhat1!
or user:demouser2 password:redhat1!
credentials can be used.
Place a session attribute as putting a key into first box and a value into the second box and click on the "save to session" button. Now switch to another server, either http://wildfly-node2:8080/jdg-service or http://tomcat-node1:8080/jdg-service one. Use the same credentials to login and click on the "Get Session Value" button and check what you've in "SessionAttributes" in your result set.
Assuming that you're on the http://wildfly-node1:8080/jdg-service app server, you can check what happens on an application server restart. Stop wildfly-node1:
docker stop wildfly-node1
docker rm wildfly-node1
Start it back
docker run -d --name wildfly-node1 --hostname wildfly-node1.docker --link jdg-node1:jdg-node1 --link jdg-node2:jdg-node2 --link jdg-node3:jdg-node3 -v $(pwd)/wildflyConfig:/wildflyConfig -v $(pwd)/deployments:/deployments --dns $(docker inspect -f '{{.NetworkSettings.IPAddress}}' docker-dns) --dns-search docker jboss/wildfly:10.1.0.Final /bin/bash -c "/wildflyConfig/executeDemo.sh"
Now just go back to your browser and refresh it
Simply execute the destroyCommunityDemo.sh
script
If you'd like to run this demo with RedHat products instead of community projects, there are additional steps before you execute the demo.
You need to build jboss-demo/jdk image first. This is the base image that other images extending.
cd docker-demo-images/image-jdk
./build.sh
After a successful build image should be placed in your local repo
docker images | grep jboss-demo/jdk
You need to build jboss-demo/eap7 image to run JBoss Enterprise Application Platform v7.
cd docker-demo-images/image-eap7
./build.sh
After a successful build image should be placed in your local repo
docker images | grep jboss-demo/eap
You need to build jboss-demo/jdg7 image to run JBoss Data Grid v7.
cd docker-demo-images/image-jdg7
./build.sh
After a successful build image should be placed in your local repo
docker images | grep jboss-demo/jdg
If you've successfully installed all prerequisites, complete the preliminary steps and have an healthy internet connection, all you need to do is executing the start script.
./runProductDemo.sh
On the initial run it will download required docker images from docker hub, so this might be a time consuming operation depending your internet connection speed. As part of the execution maven build processes will be triggered and two deployable application will be prepared under deployments subdirectory."jdg-service.war" application is prepared to deploy eap7 containers and "jdg-service4tomcat.war" is prepared to deploy on to tomcat. Why different applications? This is simply because of tomcat has no rest library and additional libraries need to be deployed with web application.
Start script will add hostnames into your /etc/hosts file, so you can access containers by their host names. Open your favorite browser and goto http://eap-node1:8080/jdg-service url. It will ask your credentials to log into the application. user:demouser1 password:redhat1!
or user:demouser2 password:redhat1!
credentials can be used.
Place a session attribute as putting a key into first box and a value into the second box and click on the "save to session" button. Now switch to another server, either http://eap-node2:8080/jdg-service or http://tomcat-node1:8080/jdg-service one. Use the same credentials to login and click on the "Get Session Value" button and check what you've in "SessionAttributes" in your result set.
Assuming that you're on the http://eap-node1:8080/jdg-service app server, you can check what happens on an application server restart. Stop eap-node1:
docker stop eap-node1
docker rm eap-node1
Start it back
docker run -d --name eap-node1 --hostname eap-node1.docker --link jdg-node1:jdg-node1 --link jdg-node2:jdg-node2 --link jdg-node3:jdg-node3 -v $(pwd)/eapConfig:/eapConfig -v $(pwd)/deployments:/deployments --dns $(docker inspect -f '{{.NetworkSettings.IPAddress}}' docker-dns) --dns-search docker jboss-demo/eap:7 /bin/bash -c "/eapConfig/executeDemo.sh"
Now just go back to your browser and refresh it
Simply execute the destroyProductDemo.sh
script
If you're familiar with the OpenShift container platform, you may want to give it a try this demo in there. OpenShift environment comes with out of the box JBoss EAP and JBoss WebServer (Tomcat) application servers, so we don't need to pull out any third party docker image to test this demo in OPenShift platform, what we have in OpenShift is enough to build & deploy our demo images. Before you start, please make sure that you've OpenShift platform 3.3 and higher version.
The first thing that you need to do is login into the OpenShift 3.x platform as usual:
oc login https://your openshift:8443
After you successfully logged into the platform, you need to create a project to test this demo
oc new-project jdg-demo
Next step is deploying Jboss Data Grid instances
#Deploy JDG
oc new-app --template=datagrid65-basic -p APPLICATION_NAME=jdg-service -p USERNAME=admin -p PASSWORD=redhat1! --name=jdg-service
# Scale to cluster size of three after ensuring the first single pod came up correctly
oc scale --replicas=3 dc jdg-service
Lets deploy it on Tomcat (JBoss WebServer) first:
#Build & deploy demo app with JWS s2i
oc new-app openshift/jboss-webserver30-tomcat8-openshift~https://github.com/serhat-dirik/jboss-generic-http-session-externalization-to-jdg --context-dir=projects/jdg-service --name=jws-app
The command above will build the source code, prepare a JBoss JWS image that contains the demo application, create a service definition to access demo application internally. We need to wait for build process to complete:
#Wait build to complete
oc logs jws-app-1-build -f
After your build successfully completed, a route definition need to be defined to access your application externally:
#Define route
oc expose service jws-app
At this point demo application should be accessible, just shoot oc get routes
command to see publicly accessible url. Open your favorite browser and goto http://$(route url). It will ask your credentials to log into the application. user:demouser1 password:redhat1!
or user:demouser2 password:redhat1!
credentials can be used.
Place a session attribute as putting a key into first box and a value into the second box and click on the "save to session" button.
Now its time to deploy it to EAP application server. First we need to create necessary service accounts:
# Create a service account by the name eap-service-account for EAP
oc create serviceaccount eap-service-account
# Assign a view permission to the service account in the current project namespace
oc policy add-role-to-user view system:serviceaccount:$(oc project -q):eap-service-account
# Also assing the default service account the view access
oc policy add-role-to-user view system:serviceaccount:$(oc project -q):default
Next we need to crate a build process to get EAP image ready to deploy
#Build EAP APP
oc new-build openshift/jboss-eap70-openshift~https://github.com/serhat-dirik/jboss-generic-http-session-externalization-to-jdg --context-dir=projects/jdg-service -eMAVEN_ARGS="package dependency:copy-dependencies -Popenshift-eap -DskipTests -e" --name=eap-app
#Wait build to complete
oc logs eap-app-1-build -f
# check if the image stream is there
oc get is
Next we just need to deploy EAP image
#Deploy
oc new-app eap-app --name=eap-app
#Define route
oc expose service eap-app
Once more you can check defined route definitions with oc get routes
command to see publicly accessible url.Now switch to that server, as placing http://$(eap route url) to your browser. Use the same credentials to login and click on the "Get Session Value" button and check what you've in "SessionAttributes" in your result set.
On a real production environment I strongly suggest that you handle sessions, session expiration, security and other concerns. Placing an SSO solution like Red Hat SSO/Keycloak into your solution design can complete the overall solution and improve the overall user experience.