Thursday 28 April 2022

API Gateway with Netflix Zuul, Spring-Boot 2.6.6 & Spring-Cloud

In this post, I am going to explain how to use Netflix zuul as api gateway.




As shown in the above image, I am going to develop two web services and configure an api-gateway to access these services.

 

a.   User Service

b.   Welcome Service

 

 

Create user-service

Create user-service maven project in Eclipse (or) any other IDE.

 

Update pom.xml with maven dependencies.

 

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.sample.app</groupId>
	<artifactId>user-service</artifactId>
	<version>1</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.6</version>
	</parent>


	<dependencies>
		<!--Spring Boot-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Define UserController class like below.

 

UserController.java

package com.sample.app.controller;

import java.util.HashMap;
import java.util.Map;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/v1/users")
public class UserController {

	@GetMapping
	public ResponseEntity<Map<String, Object>> all() {
		Map<String, Object> user1 = new HashMap();
		user1.put("id", 1);
		user1.put("name", "Joel");

		Map<String, Object> user2 = new HashMap();
		user2.put("id", 2);
		user2.put("name", "Narasimha");

		Map<String, Object> users = new HashMap<>();
		users.put("1", user1);
		users.put("2", user2);

		return ResponseEntity.ok(users);
	}

}

App.java

package com.sample.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {
	
	public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }

}

Create Welcome service.

Create welcome-service maven project in Eclipse.

 

Update pom.xml with maven dependencies.

 

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.sample.app</groupId>
	<artifactId>welcome-service</artifactId>
	<version>1</version>
	
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.6</version>
	</parent>


	<dependencies>
		<!--Spring Boot-->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Define WelcomeController class.

 

WelcomeController.java

package com.sample.app.controller;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping(value = "/v1/")
public class WelcomeController {

	@GetMapping("say-hi")
	public ResponseEntity<String> sayHello() {
		return ResponseEntity.ok("Hi!!!!!!!!!!!");
	}
}

Define main application class.

 

App.java

package com.sample.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}

}

Create api-gateway service

Create new maven project ‘api-gateway’ in Eclipse.

 

Update pom.xml with maven dependencies.

 

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>com.sample.app</groupId>
	<artifactId>api-gateway</artifactId>
	<version>1</version>

	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.6.6</version>
	</parent>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>2021.0.1</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
			<version>2.2.10.RELEASE</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Create application.yml file in src/main/resources folder and define routes.

 

application.yml

zuul:
  host:
    connect-timeout-millis: 60000
    socket-timeout-millis: 60000
  routes:
    user.url: http://localhost:1234
    welcome.url: http://localhost:2345

Define ZuulConfiguration class.

 

ZuulConfiguration.java

package com.sample.app.configuration;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.CallbackFilter;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.cloud.netflix.zuul.filters.RouteLocator;
import org.springframework.cloud.netflix.zuul.web.ZuulController;
import org.springframework.cloud.netflix.zuul.web.ZuulHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * Fix for Zuul configuration with Spring Boot 2.5.x + Zuul -
 * "NoSuchMethodError: ErrorController.getErrorPath()":
 */
@Configuration
public class ZuulConfiguration {
	/**
	 * The path returned by ErrorController.getErrorPath() with Spring Boot < 2.5
	 * (and no longer available on Spring Boot >= 2.5).
	 */
	private static final String ERROR_PATH = "/error";
	private static final String METHOD = "lookupHandler";

	/**
	 * Constructs a new bean post-processor for Zuul.
	 *
	 * @param routeLocator    the route locator.
	 * @param zuulController  the Zuul controller.
	 * @param errorController the error controller.
	 * @return the new bean post-processor.
	 */
	@Bean
	public ZuulPostProcessor zuulPostProcessor(@Autowired RouteLocator routeLocator,
			@Autowired ZuulController zuulController, @Autowired(required = false) ErrorController errorController) {
		return new ZuulPostProcessor(routeLocator, zuulController, errorController);
	}

	private enum LookupHandlerCallbackFilter implements CallbackFilter {
		INSTANCE;

		@Override
		public int accept(Method method) {
			if (METHOD.equals(method.getName())) {
				return 0;
			}
			return 1;
		}
	}

	private enum LookupHandlerMethodInterceptor implements MethodInterceptor {
		INSTANCE;

		@Override
		public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
			if (ERROR_PATH.equals(args[0])) {
				// by entering this branch we avoid the ZuulHandlerMapping.lookupHandler method
				// to trigger the
				// NoSuchMethodError
				return null;
			}
			return methodProxy.invokeSuper(target, args);
		}
	}

	private static final class ZuulPostProcessor implements BeanPostProcessor {

		private final RouteLocator routeLocator;
		private final ZuulController zuulController;
		private final boolean hasErrorController;

		ZuulPostProcessor(RouteLocator routeLocator, ZuulController zuulController, ErrorController errorController) {
			this.routeLocator = routeLocator;
			this.zuulController = zuulController;
			this.hasErrorController = (errorController != null);
		}

		@Override
		public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
			if (hasErrorController && (bean instanceof ZuulHandlerMapping)) {
				Enhancer enhancer = new Enhancer();
				enhancer.setSuperclass(ZuulHandlerMapping.class);
				enhancer.setCallbackFilter(LookupHandlerCallbackFilter.INSTANCE); // only for lookupHandler
				enhancer.setCallbacks(new Callback[] { LookupHandlerMethodInterceptor.INSTANCE, NoOp.INSTANCE });
				Constructor<?> ctor = ZuulHandlerMapping.class.getConstructors()[0];
				return enhancer.create(ctor.getParameterTypes(), new Object[] { routeLocator, zuulController });
			}
			return bean;
		}
	}
}

Define main application class.

 

App.java

package com.sample.app;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableZuulProxy
public class App {

	public static void main(String[] args) {
		SpringApplication.run(App.class, args);
	}
}

Start user service

Navigate to user-service project and build the project by executing the command ‘mvn clean install’.

 

Once the build successful, execute below command to start user service on port 1234.

 

java -Dserver.port=1234 -jar ./target/user-service-1.jar

 

Start welcome service

Navigate to welcome-service project and build the project by executing the command ‘mvn clean install’.

 

Once the build successful, execute below command to start welcome service on port 2345.

 

java -Dserver.port=2345 -jar ./target/welcome-service-1.jar

 

Start api-gateway servie

Navigate to api-gateway project and build the project by executing the command ‘mvn clean install’.

 

Once the build successful, execute below command to start api-gateway

 

java -jar ./target/api-gateway-1.jar

 

Get all the users information using api-gateway

Open the url ‘http://localhost:8080/user/v1/users’ in browser.





Get welcome message using api-gateway

Open the url ‘http://localhost:8080/welcome/v1/say-hi’ in browser.





You can download complete working application from this link.


 

No comments:

Post a Comment