动态加载:动态更新内部逻辑,比如动态修改路由规则、动态添加/删除过滤器等。通过Zuul实现的API网关服务具备动态了动态路由和动态过滤器的能力。我们可以在不重启API网关服务的前提下,为其动态修改路由规则和添加或删除过滤器等。
--------------------------------------------------动态路由----------------------------------------------
这里要使用Spring Cloud Config的动态刷新机制,只需要将API网关服务的配置文件通过Spring Cloud Config连接的Git仓库存储和管理,我们就能轻松实现动态刷新路由规则的功能。
前提:
- 需要一个连接到Git仓库的分布式配置中心config-server应用,可参考:Spring Cloud(十六)、通过Spring Cloud Config构建配置中心
- 还需要一个注册中心,可参考:Spring Cloud (一)、搭建服务注册中心
- 另外,需要一个具体的微服务应用,作路由匹配规则(hello-service)。
具备了分布式配置中心之后,我们需要构建一个API网关服务,该服务的配置不再配置于本地工程中,而是从config-server中获取,构建过程如下:
一、创建一个基础的Spring Cloud工程,命名为api-gateway-dynamic-route。
二、编写pom.xml,具体内容如下:
<?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>api-gateway-dynamic-route</artifactId> <version>0.0.1-SNAPSHOT</version> <name>api-gateway-dynamic-route</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> </properties> <dependencies> <!--config依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-config</artifactId> </dependency> <!--eureka依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!--zuul--> <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-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
二、在/resource目录下创建配置文件bootstrap.properties,并在该文件中指定config-server(获取应用的配置文件)和eureka-server(实现服务注册与发现)具体地址:
spring.application.name=api-gateway server.port=5556 #config-server具体地址,获取应用的配置文件 spring.cloud.config.uri=http://localhost:7001/ #eureka-server具体地址,实现服务注册与发现 eureka.client.service-url.defaultZone=http://localhost:1111/eureka/
三、在应用主类上使用@RefreshScope注解来将Zuul的配置内容动态化。
@EnableZuulProxy
@SpringCloudApplication
public class ApiGatewayDynamicRouteApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayDynamicRouteApplication.class, args);
}
//@Bean
@RefreshScope
@ConfigurationProperties("zuul")
public ZuulProperties zuulProperties(){
return new ZuulProperties();
}
}
四、在Git仓库中添加网关的配置文件,取名为api-gateway.properties。在配置文件中编写以下内容:
对于API网关服务的Git仓库中的配置文件名称完全取决于网关应用配置文件bootstrap.properties中的apring.application.name属性的配置值。
五、测试验证
1、我们分别来启动config-server、eureka-server、api-gateway-dynamic-route以及配置文件中路由规则指向的具体微服务,比如hello-service。在API网关的控制台中,我们可以看到它输出了从config-server中获取配置文件过程中的日志信息,根据这些信息,可以判断获取配置信息的路径等内容是否正确。具体如下:
待api-gateway-dynamic-route启动完成之后。我们可以调用API网关的/routes接口来获取当前网关上的路由规则,这里需要现在application.properties配置文件中将端点暴露出来:management.endpoints.web.exposure.include=*,访问/routes我们可以得到如下信息:
访问对应路由服务提供的接口,则可访问成功:
2、开始尝试动态修改这些路由规则:
(1)、修改Git仓库中api-gateway.properties配置文件,修改为一下内容:
(2)、修改完之后,将修改内容推送到远程仓库。而案后通过向api-gateway-dynamic-route的/refresh接口发送POST请求来刷新配置信息。当配置文件有修改的时候,该接口会返回被修改的属性名称,根据上面的修改,我们会得到如下的信息:
图上出现了两条service-b的信息,是因为修改了service-b的url方式为serviceId,相当于删除了url参数配置,增加了serviceId参数配置。
我们再次访问API的/route接口,可得到:
我们可以看到路由匹配规则已改变。
--------------------------------------------------动态过滤器----------------------------------------------
在做这个实现的时候遇到了问题,没能实现预期的效果,希望能够遇到大神帮忙解答,非常感谢!!!
对于请求过滤器的动态加载与请求路由的动态加载在实现机制上会有所不同,因为请求路由通过配置文件就能实现,而请求过滤器则是通过编码实现。所以,对于实现请求过滤器动态加载,我们需要借助基于JVM实现的动态语言的帮助,比如Groovy。
前提:
- 需要一个服务注册中心:eureka-server,可参考:Spring Cloud (一)、搭建服务注册中心
- 另外,需要一个具体的微服务应用,作路由匹配规则(hello-service)。
有了以上具备的应用之后,我们来构建一个具有动态加载Groovy过滤器能力的API网关服务,步骤如下:
一、创建一个Spring Boot工程,命名为api-gateway-dynamic-filter。
二、编写pom.xml,内容如下:
<?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> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.1.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.example</groupId> <artifactId>api-gateway-dynamic-filter</artifactId> <version>0.0.1-SNAPSHOT</version> <name>api-gateway-dynamic-filter</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> <spring-cloud.version>Greenwich.SR1</spring-cloud.version> </properties> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.codehaus.groovy/groovy-all --> <dependency> <groupId>org.codehaus.groovy</groupId> <artifactId>groovy-all</artifactId> <version>3.0.0-beta-1</version> <type>pom</type> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </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-dependencies</artifactId> <version>${spring-cloud.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
三、编写application.properties配置文件,内容如下:
spring.application.name=api-gateway
server.port=5555#eureka注册中心的地址
eureka.client.service-url..defaultZone=http://localhost:1111/eureka/#用于测试的路由规则
zuul.routes.hello.path=/hello-service/**
zuul.routes.hello.serviceId=hello-service#用来指定动态加载的过滤器存储路径
zuul.filter.root=filter
#配置动态加载的间隔时间,单位为秒
zuul.filter.interval=5
四、创建用来加载自定义属性的配置类,命名为FilterConfiguration,具体内容如下:
/**
* @author HDN
* @date 2019/6/30 13:50
*/
@ConfigurationProperties(prefix = "zuul.filter")
public class FilterConfiguration {
private String root;
private Integer interval;
public String getRoot() {
return root;
}
public void setRoot(String root) {
this.root = root;
}
public Integer getInterval() {
return interval;
}
public void setInterval(Integer interval) {
this.interval = interval;
}
}
四、下启动类上定义FilterConfiguration配置,并创建动态加载过滤器的实例,内容如下:
@EnableZuulProxy
@EnableConfigurationProperties({FilterConfiguration.class})
@SpringCloudApplication
public class ApiGatewayDynamicFilterApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayDynamicFilterApplication.class, args);
}
@Bean
public FilterLoader filterLoader(FilterConfiguration filterConfiguration){
FilterLoader filterLoader = FilterLoader.getInstance();
filterLoader.setCompiler(new GroovyCompiler());
//System.out.println(filterConfiguration.getInterval());
//System.out.println(filterConfiguration.getRoot());
try {
FilterFileManager.setFilenameFilter(new GroovyFileFilter());
FilterFileManager.init(filterConfiguration.getInterval(),filterConfiguration.getRoot()+"/pre",filterConfiguration.getRoot()+"/post");
} catch (Exception e) {
throw new RuntimeException(e);
}
return filterLoader;
}
}
五、在filter目录下创建pre目录和post目录,并创建pre类型的过滤器,命名为PreFilter.groovy、post类型的过滤器,命名为PostFilter.groovy。
至此,我们就完成了了为基础的API网关服务增加动态加载过滤器的能力。
六、启动所需要的项目,我们根据路由规则来访问http://localhost:5555/hello-service/hello,可得到返回结果:
七、我们来编写PreFilter.groovy,内容如下:
package filter.pre
import com.netflix.zuul.ZuulFilter
import com.netflix.zuul.context.RequestContext
import javax.servlet.http.HttpServletRequest
import java.util.logging.Logger
class PreFilter extends ZuulFilter {
private Logger logger = Logger.getLogger(PreFilter.class);
@Override
String filterType() {
return "pre"
}
@Override
int filterOrder() {
return 1000
}
@Override
boolean shouldFilter() {
return true
}
@Override
Object run() {
HttpServletRequest request = RequestContext.getCurrentContext().getRequest()
logger.info("this is a pre filter: Send "+request.getMethod()+" request to "+request.getRequestURL().toString())
return null
}
}
遇到的问题:加入过滤器之后,按理说不需要重启API网关服务,只需要等待几秒就会生效。继续发送请求:http://localhost:5555/hello-service/hello,在API网关服务的控制台可以看到this is a pre filter: Send GET request to http://localhost:5555/hello-service/hello,但是这里控制台并没有输出。
八、编写PostFilter.groovy过滤器,内容如下:
package filter.post
import com.netflix.zuul.ZuulFilter
import com.netflix.zuul.context.RequestContext
import javax.servlet.http.HttpServletResponse
import java.util.logging.Logger
class PostFilter extends ZuulFilter {
private Logger logger = Logger.getLogger(PostFilter.class);
@Override
String filterType() {
return "post"
}
@Override
int filterOrder() {
return 2000
}
@Override
boolean shouldFilter() {
return true
}
@Override
Object run() {
logger.info("this is a post filter:Receive response")
HttpServletResponse response = RequestContext.getCurrentContext().getResponse()
response.getOutputStream().print(", I am hedanning")
response.flushBuffer()
}
}
遇到的问题:加入过滤器之后,按理说不需要重启API网关服务,只需要等待几秒就会生效。继续发送请求:http://localhost:5555/hello-service/hello,此时不仅可以在API网关服务的控制台中看到PostFilter.groovy过滤器中定义的日志信息,也可以从请求返回的内容发现过滤器的效果,该接口返回的内容不再是hello world!,而应该是加工后处理后的hello world!,I am hedanning,但是这里并没有出现想要的结果。
API网关服务的动态过滤器功能可以帮助我们增强API网关的持续服务能力,对于网关中的处理逻辑维护也变得更为灵活,不仅可以动态地实现请求检验,还可以动态地实现对请求内容的干预。