Merge pull request #7898 from kamleshkr/BAEL-1363

BAEL-1363 Fallback for Zuul Routes
This commit is contained in:
rpvilao
2019-10-20 11:49:21 +01:00
committed by GitHub
18 changed files with 468 additions and 3 deletions

View File

@@ -25,7 +25,7 @@
<module>spring-cloud-zookeeper</module>
<module>spring-cloud-gateway</module>
<module>spring-cloud-stream</module>
<module>spring-cloud-stream-starters/twitterhdfs</module>
<module>spring-cloud-stream-starters/twitterhdfs</module>
<module>spring-cloud-connectors-heroku</module>
<module>spring-cloud-aws</module>
<module>spring-cloud-consul</module>
@@ -35,9 +35,10 @@
<module>spring-cloud-archaius</module>
<module>spring-cloud-functions</module>
<module>spring-cloud-vault</module>
<!-- <module>spring-cloud-security</module> --> <!-- Fixing in BAEL-10887 -->
<module>spring-cloud-task</module>
<!-- <module>spring-cloud-security</module> --> <!-- Fixing in BAEL-10887 -->
<module>spring-cloud-task</module>
<module>spring-cloud-zuul</module>
<module>spring-cloud-zuul-fallback</module>
</modules>
<build>

View File

@@ -0,0 +1,2 @@
### Relevant Articles:
- [Fallback for Zuul Route](TODO)

View File

@@ -0,0 +1,43 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>api-gateway</artifactId>
<name>api-gateway</name>
<description>API Gateway using Zuul</description>
<packaging>jar</packaging>
<parent>
<groupId>com.baeldung.spring.cloud</groupId>
<artifactId>spring-cloud-zuul-fallback</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@@ -0,0 +1,15 @@
package com.baeldung.spring.cloud.apigateway;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
@SpringBootApplication
@EnableZuulProxy
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}

View File

@@ -0,0 +1,69 @@
package com.baeldung.spring.cloud.apigateway.fallback;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpResponse;
public class GatewayClientResponse implements ClientHttpResponse {
private HttpStatus status;
private String message;
public GatewayClientResponse(HttpStatus status, String message) {
this.status = status;
this.message = message;
}
@Override
public HttpStatus getStatusCode() throws IOException {
return status;
}
@Override
public int getRawStatusCode() throws IOException {
return status.value();
}
@Override
public String getStatusText() throws IOException {
return status.getReasonPhrase();
}
@Override
public void close() {
}
@Override
public InputStream getBody() throws IOException {
return new ByteArrayInputStream(message.getBytes());
}
@Override
public HttpHeaders getHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
return headers;
}
public HttpStatus getStatus() {
return status;
}
public void setStatus(HttpStatus status) {
this.status = status;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@@ -0,0 +1,29 @@
package com.baeldung.spring.cloud.apigateway.fallback;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import com.netflix.hystrix.exception.HystrixTimeoutException;
@Component
class GatewayServiceFallback implements FallbackProvider {
private static final String DEFAULT_MESSAGE = "Service not available.";
@Override
public String getRoute() {
return "*"; // or return null;
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return new GatewayClientResponse(HttpStatus.GATEWAY_TIMEOUT, DEFAULT_MESSAGE);
} else {
return new GatewayClientResponse(HttpStatus.INTERNAL_SERVER_ERROR, DEFAULT_MESSAGE);
}
}
}

View File

@@ -0,0 +1,29 @@
package com.baeldung.spring.cloud.apigateway.fallback;
import org.springframework.cloud.netflix.zuul.filters.route.FallbackProvider;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import com.netflix.hystrix.exception.HystrixTimeoutException;
@Component
class WeatherServiceFallback implements FallbackProvider {
private static final String DEFAULT_MESSAGE = "Weather information is not available.";
@Override
public String getRoute() {
return "weather-service";
}
@Override
public ClientHttpResponse fallbackResponse(String route, Throwable cause) {
if (cause instanceof HystrixTimeoutException) {
return new GatewayClientResponse(HttpStatus.GATEWAY_TIMEOUT, DEFAULT_MESSAGE);
} else {
return new GatewayClientResponse(HttpStatus.INTERNAL_SERVER_ERROR, DEFAULT_MESSAGE);
}
}
}

View File

@@ -0,0 +1,22 @@
spring:
application:
name: api-gateway
server:
port: 7070
zuul:
igoredServices: '*'
routes:
weather-service:
path: /weather/**
serviceId: weather-service
strip-prefix: false
ribbon:
eureka:
enabled: false
weather-service:
ribbon:
listOfServers: localhost:8080

View File

@@ -0,0 +1,16 @@
package com.baeldung.spring.cloud.apigateway;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class ApiGatewayApplicationIntegrationTest {
@Test
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}

View File

@@ -0,0 +1,46 @@
package com.baeldung.spring.cloud.apigateway.fallback;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.test.context.junit4.SpringRunner;
import com.netflix.hystrix.exception.HystrixTimeoutException;
@RunWith(SpringRunner.class)
@SpringBootTest
public class GatewayServiceFallbackUnitTest {
private static final String ROUTE = "*";
@Autowired
private GatewayServiceFallback fallback;
@Test
public void testWhenGetRouteThenReturnWeatherServiceRoute() {
assertEquals(ROUTE, fallback.getRoute());
}
@Test
public void testFallbackResponse_whenHystrixException_thenGatewayTimeout() throws Exception {
HystrixTimeoutException exception = new HystrixTimeoutException();
ClientHttpResponse response = fallback.fallbackResponse(ROUTE, exception);
assertEquals(HttpStatus.GATEWAY_TIMEOUT, response.getStatusCode());
}
@Test
public void testFallbackResponse_whenNonHystrixException_thenInternalServerError() throws Exception {
RuntimeException exception = new RuntimeException("Test exception");
ClientHttpResponse response = fallback.fallbackResponse(ROUTE, exception);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
}
}

View File

@@ -0,0 +1,46 @@
package com.baeldung.spring.cloud.apigateway.fallback;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.test.context.junit4.SpringRunner;
import com.netflix.hystrix.exception.HystrixTimeoutException;
@RunWith(SpringRunner.class)
@SpringBootTest
public class WeatherServiceFallbackUnitTest {
private static final String ROUTE = "weather-service";
@Autowired
private WeatherServiceFallback fallback;
@Test
public void testWhenGetRouteThenReturnWeatherServiceRoute() {
assertEquals(ROUTE, fallback.getRoute());
}
@Test
public void testFallbackResponse_whenHystrixException_thenGatewayTimeout() throws Exception {
HystrixTimeoutException exception = new HystrixTimeoutException();
ClientHttpResponse response = fallback.fallbackResponse(ROUTE, exception);
assertEquals(HttpStatus.GATEWAY_TIMEOUT, response.getStatusCode());
}
@Test
public void testFallbackResponse_whenNonHystrixException_thenInternalServerError() throws Exception {
RuntimeException exception = new RuntimeException("Test exception");
ClientHttpResponse response = fallback.fallbackResponse(ROUTE, exception);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
}
}

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>spring-cloud-zuul-fallback</artifactId>
<packaging>pom</packaging>
<name>spring-cloud-zuul-fallback</name>
<description>Spring Cloud Zuul Fallback</description>
<parent>
<groupId>com.baeldung.spring.cloud</groupId>
<artifactId>spring-cloud</artifactId>
<version>1.0.0-SNAPSHOT</version>
<relativePath>..</relativePath>
</parent>
<modules>
<module>api-gateway</module>
<module>weather-service</module>
</modules>
<properties>
<java.version>1.8</java.version>
<spring-cloud-dependencies.version>Finchley.SR2</spring-cloud-dependencies.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
</properties>
</project>

View File

@@ -0,0 +1,39 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>weather-service</artifactId>
<name>Weather Service</name>
<description>Weather Service for Zuul Fallback Test</description>
<parent>
<groupId>com.baeldung.spring.cloud</groupId>
<artifactId>spring-cloud-zuul-fallback</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-parent</artifactId>
<version>${spring-cloud-dependencies.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>

View File

@@ -0,0 +1,16 @@
package com.baeldung.spring.cloud.weatherservice;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/weather")
public class WeatherController {
@GetMapping("/today")
public String getMessage() {
return "It's a bright sunny day today!";
}
}

View File

@@ -0,0 +1,13 @@
package com.baeldung.spring.cloud.weatherservice;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class WeatherServiceApplication {
public static void main(String[] args) {
SpringApplication.run(WeatherServiceApplication.class, args);
}
}

View File

@@ -0,0 +1,4 @@
spring:
application:
name: weather-service

View File

@@ -0,0 +1,31 @@
package com.baeldung.spring.cloud.weatherservice;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.web.servlet.MockMvc;
@RunWith(SpringRunner.class)
@WebMvcTest(WeatherController.class)
public class WeatherControllerIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Test
public void whenWeatherControllerInvoked_thenReturnWeatherInformation() throws Exception {
this.mockMvc.perform(get("/weather/today"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("bright sunny day")));
}
}

View File

@@ -0,0 +1,16 @@
package com.baeldung.spring.cloud.weatherservice;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
public class WeatherServiceApplicationIntegrationTest {
@Test
public void whenSpringContextIsBootstrapped_thenNoExceptions() {
}
}