High Availability / Clustering HTTP Sessions using Spring Session, Redis and NGINX Example

High Availability / Clustering HTTP Sessions using Spring Session, Redis and NGINX Example with AngularJS2

In this tutorial we discuss how to use Spring Session, Redis and NGINX to build a fault tolerant high-availability environment. Spring Session allows us to create clustered sessions that span across multiple application servers. In the event that one application server crashes or is taken off-line the session will continue to be managed via Redis and the remaining application server. Unbeknownst to the application using our high-availability environment they would be unaware that we had ever lost an application server. There will be future tutorial that will focus on the Angular 2 user interface for those interested on the front-end, but for the time being, we will discuss the backend.

Please Note

This post will not focus on any type of database backend including MongoDB, MySql, SQL Server, Oracle or DB2 for our datastore. We will be using a simple Map that will contain our store inventory. For database related tutorials, please visit the Database Category on this website. Our focus for this tutorial is on how to setup and configure Spring Session, NGINX and REDIS for clustering HTTP Sessions.

Managing Http Sessions using Spring Session

What’s Covered

  1. Getting Started
  2. Introduction
  3. Required Libraries
  4. Complete Project Overview
  5. RESTful Web Service End Points
  6. Setting up our Project
  7. Typical Production Configuration
  8. Localhost Configuration
  9. Local NGINX Configuration
  10. The Product Model
  11. Our Controller Class (RestController.java)
  12. LogBack Configuration File (logback.xml)
  13. Web Deployment Descriptor (web.xml)
  14. Configure Spring Web DispatcherServlet (dispatcher-servlet.xml)
  15. Root Context Configuration (root-context.xml)
  16. Product JSON Array from REST Service
  17. Running the Angular2 Application in Development
  18. Angular 2 Prerequisites
  19. Let’s create our MyApp Folder and Skeleton
  20. Angular2 Proxy Setup (proxy.json)
  21. Starting the Server with Angular CLI
  22. Building Angular2 Application for Production
  23. Web Deployment Descriptor
  24. Configure Spring Web DispatcherServlet
  25. Testing out SpringSession with NGINX, Tomcat and AngularJS2 using REST Services

Getting Started

In order to run this tutorial yourself, you will need the following:

  • Java JDK 1.7 or greater
  • Favorite IDE Spring Tool Suite (STS), Eclipse IDE or NetBeans (I happen to be using STS because it comes with a Tomcat server built-in)
  • Tomcat 7 or greater or other popular container (Weblogic, Websphere, Glassfish, JBoss, VMWare vFabric, etc). For this tutorial I am using VMware vFabric tc Server Developer Edition which is essentially an enhanced Tomcat instance integrated with Spring STS
  • Spring Session provides an API and implementations for managing a user’s session information across multiple servers eliminating the requirement to be tied to specific application servers and improving server availability.
  • Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. According to the official documentation, Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.
  • NGINX pronounced “Engine X” is an HTTP and reverse proxy server, a mail proxy server, and a generic TCP/UDP proxy server developed for high performance heaviliy loaded sites.
  • Jackson Mapper for Object to JSON and vice-versa serialization/deserialization
  • Logback (for logging purposes)

Introduction

Spring Session is a fairly new Spring project that provides an API to manage http sessions. The main features of Spring Session include its ability to integrate with Apache Tomcat and supply a custom implementation of HttpSession, support for Clustered Sessions which may span multiple application servers, support for Multiple Browser Sessions in a single browser instance and added functionality that makes it easy to enable HttpSession with RESTful endpoints and Spring Session.

Required Libraries

Copy all of the following jars to WebContent->WEB-INF->lib folder.

aopalliance-1.0.jar
aspectjrt-1.8.9.jar
commons-logging-1.2.jar
commons-pool2-2.4.2.jar
jackson-annotations-2.5.4.jar
jackson-core-2.5.4.jar
jackson-databind-2.5.4.jar
javax.servlet-api-3.1.0.jar
jedis-2.8.1.jar
logback-access-1.1.3.jar
logback-classic-1.1.3.jar
logback-core-1.1.3.jar
slf4j-api-1.7.10.jar
slf4j-simple-1.7.10.jar
spring-aop-4.2.9.RELEASE.jar
spring-aspects-4.2.9.RELEASE.jar
spring-beans-4.2.9.RELEASE.jar
spring-context-4.2.9.RELEASE.jar
spring-context-support-4.2.9.RELEASE.jar
spring-core-4.2.9.RELEASE.jar
spring-data-commons-1.12.1.RELEASE.jar
spring-data-keyvalue-1.1.1.RELEASE.jar
spring-data-redis-1.7.1.RELEASE.jar
spring-expression-4.2.9.RELEASE.jar
spring-security-core-4.0.4.RELEASE.jar
spring-security-web-4.0.4.RELEASE.jar
spring-session-1.2.2.RELEASE.jar
spring-test-4.2.9.RELEASE.jar
spring-tx-4.2.9.RELEASE.jar
spring-web-4.2.9.RELEASE.jar
spring-webmvc-4.2.9.RELEASE.jar

Complete Project Overview

I have added the project overview to give you a full view of the structure and show you all files contained in this sample project.

spring_session_proj_struct

RESTful Web Service End Points

#URIMethodDescription
1/rest/statusGETDisplays the standard status message.
2/rest/addProductToCartPOSTAdds the product to the cart in the HttpSession object. Spring Session uses Redis will persist the session for our Tomcat Servers.
3/rest/getCartGETRetrieves the CART from the HttpSession and retrieves all the values as an array.
4/rest/removeProductFromCartDELETERemoves the product from the CART given the ProductId, returning the CART as a JSON object.
5/rest/emptyCartDELETERemoves the CART attribute from the HttpSession.
5/rest/getAllInventoryGETGets all Products from our Datastore and return it as an array.

Setting up our Project

Before we begin, let’s start by downloading the required components that this project will be using.

  • Download and install NGINX choosing either the Linux or Windows versions. Follow the instructions and configure NGINX (nginx.conf) detailed below.
  • For REDIS you can download the source directly from the official Redis website
  • If you have a Windows 64-bit operating system, you can download the Redis binaries from MSOpenTech. Although, this is not an “official” version from Redis these folks have done a great job with the Windows port. Download either the Windows MSI or Zip file. In my case, I chose to go with the MSI file and installed Redis as a Service.

Typical Production Configuration

A production configuration would consist of a NGINX high availability cluster comprised of active/passive instances. The active NGINX instance processes the traffic while the passive would constantly monitor the health of the active, should it fail the passive would become the active and take over that role.

In our configuration, we would have a Tomcat High-Availability Cluster comprised of anywhere from two to four application servers — depending on your production needs.

Since we are using Redis to store our session information not having a cluster here would represent a single point of failure which would not be acceptable, especially for a production environment. Our Redis cluster would consist of a master-slave configuration having one master and N replicas.

spring_session_redis_nginx

Localhost Configuration

For this Spring Session tutorial I installed NGINX, Redis, and two instances of Apache Tomcat. NGINX was configured to listen for connections on the standard HTTP port of 80. Redis was configured to use the default port of 6379. We configured one Tomcat instance on port 9090 and the second instance on port 9191.

spring_session_localhost

Local NGINX Configuration (nginx.conf)

For this tutorial we are using NGINX for automated failover and load balancing. The configuration is quite straight forward but we will explain in more detail.

The worker_connections defines the number of simultaneous connections that can be opened by a worker process. In our case, we have defined 1024 simultaneous connections.

The upstream name directive inside of the http context will allow us to define the group of servers and details about their configuration. This group will be referenced in other sections of the NGINX configuration file.

The server address syntax defines the address, port, weight, max connections, max_fails, fail_timeout, etc.

The server syntax defines NGINX listening on port 80.

The location / will send a all requests to our tomcat servers. This is defined by the group defined in the upstream directive. We use the proxy_pass to pass all requests to the group called ‘tomcat’.

The proxy_next_upstream specifies when a request should be sent to the next server in the chain. In our case, we check for error condition which occurs when an we cannot establish a connection with the server, a timeout condition, which occurs when establishing a connection does not occur in a timely manner, an invalid header which occurs when a server returned an empty or invalid response, and http_500 which a server returns a response with an error code of 500.

The proxy_connect_timeout defines the timeout period in which establishing a connection to the proxied servers (tomcat) should not exceed. If this time is exceeded, we will consider it timed out and move on to the next server.

events {
  worker_connections  1024;
}

http {
  upstream tomcat {
    server localhost:9090;
    server localhost:9191;
  }

 server {
   listen 80;

  location / {
   proxy_pass http://tomcat;
   proxy_redirect        off;
   proxy_next_upstream   error timeout invalid_header http_500;
   proxy_connect_timeout 2;
  }
 }
}

The Product Model (Product.java)

The Product model is used as a basis for storing our Product object into our Shopping Cart which then goes into the HTTP session. For the sake of simplicity, I am reusing the Product object which come from the inventory and using the data in this object for insertion into myShoppingCart which is defined as a Map<String, Product>.

package com.avaldes.model;

import java.io.Serializable;

public class Product implements Serializable {
 private static final long serialVersionUID = 2695828913725773456L;
 private String productId;
 private String name;
 private String description;
 private String imageUrl;
 private boolean isTaxable;
 private int qty;
 private float price;
 
 public Product(String productId, String name, String description,
   String imageUrl, boolean isTaxable, int qty, float price) {
  super();
  this.productId = productId;
  this.name = name;
  this.description = description;
  this.imageUrl = imageUrl;
  this.isTaxable = isTaxable;
  this.qty = qty;
  this.price = price;
 }

 public Product() {}
 
 public String getProductId() {
  return productId;
 }

 public void setProductId(String productId) {
  this.productId = productId;
 }

 public String getName() {
  return name;
 }

 public void setName(String name) {
  this.name = name;
 }

 public String getDescription() {
  return description;
 }

 public void setDescription(String description) {
  this.description = description;
 }

 public String getImageUrl() {
  return imageUrl;
 }

 public void setImageUrl(String imageUrl) {
  this.imageUrl = imageUrl;
 }

 
 public boolean isTaxable() {
  return isTaxable;
 }

 public void setTaxable(boolean isTaxable) {
  this.isTaxable = isTaxable;
 }

 public int getQty() {
  return qty;
 }

 public void setQty(int qty) {
  this.qty = qty;
 }

 public float getPrice() {
  return price;
 }

 public void setPrice(float price) {
  this.price = price;
 }

 @Override
 public String toString() {
  return "Product [productId=" + productId + ", name=" + name
    + ", description=" + description + ", imageUrl=" + imageUrl
    + ", isTaxable=" + isTaxable + ", qty=" + qty + ", price="
    + price + "]";
 }
}

Our Controller Class (RestController.java)

Our RestController class is the main class that contains all web service mapping end points defined in our table above. The @Controller annotation indicates that this particular class is playing the role of a controller.

package com.avaldes.tutorial;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;

import com.avaldes.model.Product;
import com.avaldes.model.StatusMessage;

@Controller
public class RestController {

 private static final Logger logger = LoggerFactory
   .getLogger(RestController.class);
 public static final String APPLICATION_HTML = "text/html";
 public static final String APPLICATION_JSON = "application/json";
 private Map<String, Product> storeInventory 
                                = new HashMap<String, Product>();

 public RestController() {
  logger.info("Starting RestController...");

  storeInventory.put("B01M18UZF5",
    new Product("B01M18UZF5",
     "ASUS ZenBook UX330UA-AH54 13.3-inch Ultra-Slim Laptop",
     "ASUS Craftsmanship delivers an Ultra-Thin 0.5-inch profile with a 2-pound lightweight unibody design Our Toughest ZenBook designed with Premium Aerospace-grade Aluminum and Corning Gorilla Glass 4", "http://www.asus.com/zenbook/img/07-ux501-a.png", true, 13, 679.95f));
  storeInventory.put("B015P3SKHQ",
    new Product("B015P3SKHQ",
     "Dell Inspiron i7359-8404SLV 13.3 Inch 2-in-1 Touchscreen Laptop",
     "Intel Dual Core i7-6500U 2.5 GHz Processor, 8 GB DDR3L SDRAM, 256 GB SSD Storage; Optical Drive Not included, 13.3 Inch FHD (1920 x 1080 pixels) LED-lit Truelife Touchscreen",
     "http://ecx.images-amazon.com/images/I/414xadUGA5L._AC_SL230_.jpg", true, 7, 728.50f));
  storeInventory.put("B012DTEMQ8",
    new Product("B012DTEMQ8",
     "Microsoft Surface Pro 3 Tablet (12-Inch, 128 GB, Intel Core i5)",
     "Windows 10, 12-Inch Display, Intel Core i5 1.9 GHz Processor, 128 GB Flash Storage, 4 GB RAM, 1.76 pounds, 36W Power Supply and Surface Pen Included; Keyboard sold separately",
     "https://images-na.ssl-images-amazon.com/images/I/81hXY3b5jgL._SL1500_.jpg", true, 11, 544.60f));
  storeInventory.put("B01EIUEGXO",
    new Product("B01EIUEGXO", "Apple MacBook MLHA2LL/A 12-Inch Laptop with Retina Display", "1.1GHz Dual Core Intel m3, 8GB RAM, 256GB HD, OS X",  "http://pisces.bbystatic.com/image2/BestBuy_US/images/products/5229/5229700_sd.jpg", true, 3, 1249.00f));
 }

 @RequestMapping(value = "/status", 
      method = RequestMethod.GET, produces = APPLICATION_HTML)
 public @ResponseBody String status() {
  logger.info("Inside of status() method...");
  return "application OK...";
 }

 @SuppressWarnings("unchecked")
 @RequestMapping(value = "/addProductToCart", 
    method = RequestMethod.POST, consumes = APPLICATION_JSON)
 
 public @ResponseBody Map<String, Product> addProductToCart(
   HttpSession session, @RequestBody Product product) {

  logger.info("Inside of addProductToCart() method...");
  Map<String, Product> myShoppingCart = null;
  myShoppingCart = (Map<String, Product>) session
    .getAttribute("cart");
  if (myShoppingCart == null) {
   logger.info("myShoppingCart is empty...");
   myShoppingCart = new HashMap<String, Product>();
  }
  if (product != null) {
   if (myShoppingCart.containsKey(product.getProductId())) {
    Product cProd = myShoppingCart.get(product.getProductId());
    int cQty = cProd.getQty() + 1;
    product.setQty(cQty);
    logger.info("product Key found..: " + product.getProductId()+", Qty..: " + cQty);
    myShoppingCart.remove(product.getProductId());
    myShoppingCart.put(product.getProductId(), product);
   } else {
    logger.info("Inserting product into myShoppingCart...");
    myShoppingCart.put(product.getProductId(), product);
   }
   logger.info("myShoppingCart..: " + myShoppingCart);
   session.setAttribute("developer", "Amaury");
   session.setAttribute("cart", myShoppingCart);
   showSessionAttributes(session);
  }
  return myShoppingCart;
 }

 @RequestMapping(value = "/emptyCart", 
    method = RequestMethod.DELETE)
 
 public @ResponseBody StatusMessage emptyCart(
   HttpSession session) {

  logger.info("Inside of emptyCart() method...");
  session.removeAttribute("cart");
  StatusMessage statusMessage = new StatusMessage();
  statusMessage.setStatus(200);
  statusMessage.setMessage("Successfully emptied cart.");
  
  return statusMessage;
 } 
 
 @SuppressWarnings("unchecked")
 @RequestMapping(value = "/getCart", method = RequestMethod.GET)
 public @ResponseBody ArrayList<Product> getCart(
   HttpSession session) {

  logger.info("Inside of getCart() method...");
  Map<String, Product> myShoppingCart = null;
  myShoppingCart = (Map<String, Product>) session
    .getAttribute("cart");

  if (myShoppingCart == null) {
   myShoppingCart = new HashMap<String, Product>();
  }
 
  return new ArrayList<Product>(myShoppingCart.values());
 }

 @SuppressWarnings("unchecked")
 @RequestMapping(value = "/removeProductFromCart", method = RequestMethod.DELETE)
 public @ResponseBody Map<String, Product> removeCart(
   @RequestParam("productId") String productId, HttpSession session) {

  logger.info("Inside of removeCart() method...");
  Map<String, Product> myShoppingCart = null;
  myShoppingCart = (Map<String, Product>) session.getAttribute("cart");

  if (myShoppingCart == null) {
   myShoppingCart = new HashMap<String, Product>();
  }

  if (productId != null) {
   if (myShoppingCart.containsKey(productId)) {
    logger.info(
     "Found product with key " + productId + ", removing... ");
    myShoppingCart.remove(productId);
    session.setAttribute("cart", myShoppingCart);
   }
  }

  return myShoppingCart;
 }

 @RequestMapping(value = "/getAllInventory", method = RequestMethod.GET)
 public @ResponseBody ArrayList<Product> getAllInventory() {

  logger.info("Inside of getAllInventory() method...");
  ArrayList<Product> inventoryList = new ArrayList<Product>(
    storeInventory.values());
  return inventoryList;
 }

 @RequestMapping(value = "/getName", method = RequestMethod.GET)
 public @ResponseBody String getName(HttpSession httpSession) {

  String details = httpSession.getId() + ":" + httpSession.getAttribute("name");
  return details;
 }

 private void showSessionAttributes(HttpSession session) {

  logger.info("Inside of showSessionAttributes() method...");
  Enumeration<String> keys = session.getAttributeNames();
  while (keys.hasMoreElements()) {
   String key = keys.nextElement();
   logger.info(key);
  }
 }
}

LogBack Configuration File (logback.xml)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE xml>
<configuration>
 <appender name="STDOUT" 
   class="ch.qos.logback.core.ConsoleAppender">
   <layout 
      class="ch.qos.logback.classic.PatternLayout">
    <Pattern>
      %d{YYYY-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n
    </Pattern>
   </layout>
  </appender>
   
  <logger name="com.avaldes.*" level="info"/>
 
  <root level="info">
   <appender-ref ref="STDOUT" />
  </root>
</configuration>

LogBack Output

SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
2016-12-27 20:22:38.359 INFO  com.avaldes.tutorial.RestController - Starting RestController...
Dec 27, 2016 8:22:38 PM org.apache.catalina.startup.Catalina start
INFO: Server startup in 4416 ms
2016-12-27 20:26:59.813 INFO  com.avaldes.tutorial.RestController - Inside of getAllInventory() method...
2016-12-27 20:27:13.691 INFO  com.avaldes.tutorial.RestController - Inside of getCart() method...

Web Deployment Descriptor (web.xml)

To configure Spring Session, we first need to configure the springSessionRepositoryFilter filter and ensure that we use the org.springframework.web.filter.DelegatingFilterProxy class (lines 8 ~ 17).

Since we want our Spring Session backed custom implmentation to replace the standard HttpSession we must include additional configuration in our root-context.xml file. We define the file location using the contextConfigLocation context param tags (lines 18 ~ 26).

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns="http://java.sun.com/xml/ns/javaee" 
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" 
id="WebApp_ID" version="2.5">
  <display-name>SpringSession</display-name>
  <filter>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <filter-class>
      org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
  </filter>
  <filter-mapping>
    <filter-name>springSessionRepositoryFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>/WEB-INF/root-context.xml</param-value>
  </context-param>
  <listener>
    <listener-class>
      org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  <session-config>
    <session-timeout>1</session-timeout>
  </session-config>
  <servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/rest/*</url-pattern>
  </servlet-mapping>
</web-app>

Spring Web DispatcherServlet (dispatcher-servlet.xml)

In our Spring Web DispatcherServet XML file we use a request mapping handler adapter to configure message converters. We are using MappingJackson2HttpMessageConverter to convert Java Objects to and from JSON using Jackson’s ObjectMapper.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
  
 <!-- Enables the Spring MVC @Controller programming model -->
 <mvc:annotation-driven />
 
 <bean 
   class="org.springframework.web.servlet.mvc.method.
	        annotation.RequestMappingHandlerAdapter">
   <property name="messageConverters">
    <list>
     <bean class="org.springframework.http.converter.
	          json.MappingJackson2HttpMessageConverter" />
    </list>
   </property>
  </bean> 
 
  <context:component-scan base-package="com.avaldes" />
</beans>

Root Context Configuration (root-context.xml)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
  
<context:annotation-config/>
    
<bean class="org.springframework.session.data.redis
    .config.annotation.web.http.RedisHttpSessionConfiguration"/>

  <bean class="org.springframework.data.redis.
          connection.jedis.JedisConnectionFactory">
    <property name="port" value="6379" />
  </bean>  
</beans>

Product JSON Array from REST Service

[
  {
    "productId": "B01M18UZF5",
    "name": "ASUS ZenBook UX330UA-AH54 13.3-inch Ultra-Slim Laptop",
    "description": "ASUS Craftsmanship delivers an Ultra-Thin 0.5-inch profile with a 2-pound lightweight unibody design Our Toughest ZenBook designed with Premium Aerospace-grade Aluminum and Corning Gorilla Glass 4",
    "imageUrl": "http://www.asus.com/zenbook/img/07-ux501-a.png",
    "qty": 13,
    "price": 679.95,
    "taxable": true
  },
  {
    "productId": "B012DTEMQ8",
    "name": "Microsoft Surface Pro 3 Tablet (12-Inch, 128 GB, Intel Core i5)",
    "description": "Windows 10, 12-Inch Display, Intel Core i5 1.9 GHz Processor, 128 GB Flash Storage, 4 GB RAM, 1.76 pounds, 36W Power Supply and Surface Pen Included; Keyboard sold separately",
    "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/81hXY3b5jgL._SL1500_.jpg",
    "qty": 11,
    "price": 544.6,
    "taxable": true
  },
  {
    "productId": "B01EIUEGXO",
    "name": "Apple MacBook MLHA2LL/A 12-Inch Laptop with Retina Display",
    "description": "1.1GHz Dual Core Intel m3, 8GB RAM, 256GB HD, OS X",
    "imageUrl": "https://images-na.ssl-images-amazon.com/images/I/51IPj3z9%2BBL._SY450_.jpg",
    "qty": 3,
    "price": 1249,
    "taxable": true
  },
  {
    "productId": "B015P3SKHQ",
    "name": "Dell Inspiron i7359-8404SLV 13.3 Inch 2-in-1 Touchscreen Laptop",
    "description": "Intel Dual Core i7-6500U 2.5 GHz Processor, 8 GB DDR3L SDRAM, 256 GB SSD Storage; Optical Drive Not included, 13.3 Inch FHD (1920 x 1080 pixels) LED-lit Truelife Touchscreen",
    "imageUrl": "http://ecx.images-amazon.com/images/I/414xadUGA5L._AC_SL230_.jpg",
    "qty": 7,
    "price": 728.5,
    "taxable": true
  }
]

Running the Angular2 Application in Development

We will discuss the Angular2 application in detail in the subsequent tutorial but for the time being here are just some of the basics to get you started using the application.

Angular 2 Prerequisites

  • Install NodeJS as AngularJS uses NPM (Node Package Manager)
  • Once you have installed NodeJS and NPM you can install the Angular-CLI globally.
npm install -g angular-cli

Let’s create our MyApp Folder and Skeleton

The following will create the application folder and src files, and install Angular 2 packages using NPM packages. This may take a few minutes.

open a new command prompt windows or terminal window
 cd root_folder
 ng new MyApp

Once this is done, change into the newly created MyApp folder and delete the folder called src. We will use the source code from the file you downloaded in this tutorial called MyApp to overwrite the contents of your local MyApp folder.

spring-session-example-zip

Please Note

The MyApp folder is NOT a SpringSession folder and is not associated with any of the backend server components.

Angular2 Proxy Setup (proxy.json)

In order to avoid the CORS (Cross Origin Resource Sharing) issues when using our Angular2 application, you will need to create a file which will used when ng serve command is used.

proxy.json

{
“/SpringSessionExample”: “http://localhost:80”
}

Starting the Server with Angular CLI

Once we start our Angular2 development server instance you will note that it is using port 4200. Since our application servers are running on different ports, 9090 and 9191 respectively we needed to run the command line with the –proxy-config option and pass in the file detailed above.

ng serve –proxy-config proxy.json

ng_server

Building Angular2 Application for Production

When you are ready to deploy your application into production environment you can use the following Angular CLI command.

ng build –prod –aot

This will create the necessary prod bundle and place the files in the dist folder inside of your current application folder.

By adding the aot flag we enable ahead of time compilation.

Testing out SpringSession with NGINX, Tomcat and AngularJS2 using REST Services

Testing out SpringSession, NGINX and multiple instances of Tomcat consisted of ensuring that we were able create a session in either application server (Tomcat) and ensure that session information was persisted in the Redis server. We tested by using both the Angular2 UI Sample Application and by calling the services directly by using localhost (port 80) via NGINX, and by accessing our application servers directly via localhost:9090 and localhost:9191. Next we brought down one instance of Tomcat to ensure that our application would continue working properly and without any interruption in service. We again tested the services directly using localhost, localhost:9090 and localhost:9191. Obviously, the server that was down generated the “This site can’t be reached” error page. But by using NGINX for failover and SpringSession accessing the page through localhost (port 80) remained unaffected. Then we brought the server that was down back up and it immediately started handling traffic just as it had prior to the shutdown. Afterwards we reversed and brought down the other server and retested everything again to verify clustered sessions was working properly.

Download the Complete Source Code for Spring Session, Redis, NGINX and Angular2 Java Example

That’s It!

I hope you enjoyed this tutorial. It was certainly a lot of fun putting it together and testing it out. Please continue to share the love and like us so that we can continue bringing you quality tutorials. Happy Coding!!!

angular_spring_session

Related Spring Posts

  • Creating Hello World Application using Spring MVC on Eclipse IDE
    In this tutorial we will go into some detail on how to set up your Eclipse IDE environment so that you can develop Spring MVC projects. In this post, we will create our first Spring MVC project with the all to familiar “Hello World” sample program.
  • Spring MVC Form Handling Example
    The following tutorial will guide you on writing a simple web based application which makes use of forms using Spring Web MVC framework. With this web application you will be able to interact with the customer entry form and enter all of the required values and submit them to the backend processes. I have taken the liberty of using CSS to beautify and transform the HTML page from a standard drab look and feel to a more appealing view.
  • Spring @RequestHeader Annotation Example
    In this tutorial, we will discuss the different ways that Spring MVC allow us to access HTTP headers using annotation. We will discuss how to access individual header fields from the request object as well accessing all the headers by supplying Map and then iterating through the LinkedHashMap collection. We will also show you how to set the headers in the response object.
  • Spring MVC Exception Handling using @ExceptionHandler with AngularJS GUI
    Good exception handling is a essential part of any well developed Application Framework and Spring MVC is no exception — pardon the pun. Spring MVC provides several different ways to handle exceptions in our applications. In this tutorial, we will cover Controller Based Exception Handling using the @ExceptionHandler annotation above the method that will handle it.
  • Spring RESTful Web Service Example with JSON and Jackson using Spring Tool Suite
    For this example, I will be using Spring Tool Suite (STS) as it is the best integrated development environment for building the Spring framework projects. Spring is today's leading framework for building Java, Enterprise Edition (Java EE) applications. One additional feature that makes Spring MVC so appealing is that it now also supports REST (REpresentational State Transfer) for build Web Services.
  • Spring MVC RESTful Web Service Example with Spring Data for MongoDB and ExtJS GUI
    This post will show another example of how to build a RESTful web service using Spring MVC 4.0.6, Spring Data for MongoDB 1.6.1 so that we can integrate the web application with a highly efficient datastore (MongoDB 2.6). In this tutorial we will walk you through building the web service and NoSQL database backend and show you how to implement CRUD (Create, Read, Update and Delete) operations.
  • Building DHTMLX Grid Panel User Interface with Spring MVC Rest and MongoDB Backend
    In this tutorial we will show how easy it is to use DHTMLX dhtmlxGrid component while loading JSON data with Ajax pulling in data from the Spring MVC REST web service from our MongoDB data source. You will see how simple it is to create a visually appealing experience for your client(s) with minimal javascript coding.
  • Spring MVC with JNDI Datasource for DB2 on AS/400 using Tomcat
    In this tutorial we will discuss how to set up Spring MVC web services and configure a JNDI Datasource using Tomcat and connect to IBM DB2 Database on a AS/400. JNDI (Java Naming and Directory Interface) provides and interface to multiple naming and directory services.
  • Java Spring MVC Email Example using Apache Velocity
    In this tutorial we will discuss how to set up a Java Spring MVC RESTful Webservice with Email using Apache Velocity to create a Velocity template that is used to create an HTML email message and embed an image, as shown below, using MIME Multipart Message.
  • Implementing Basic and Advanced Search using Angular Material Design, Grid-UI, Spring MVC REST API and MongoDB Example
    In this tutorial we will discuss how to implement basic and advanced search techniques in MongoDB using AngularJS and Google’s Material Design with Spring MVC REST API backend. The advanced search user interface (UI) will use logical operators and build a JSON object which contains the search field name, boolean or logical operator and the search value.
  • Spring MVC Interceptor using HandlerInterceptorAdapter Example
    In this tutorial we will discuss how to use the HandlerInterceptorAdapter abstract class to create a Spring MVC interceptor. These interceptors are used to apply some type of processing to the requests either before, after or after the complete request has finished executing.

Please Share Us on Social Media

Facebooktwittergoogle_plusredditpinterestlinkedinmail

Leave a Reply

Your email address will not be published. Required fields are marked *