在API网关服务入门示例中,我们来构建一个网关,来实现请求路由和请求过滤的功能。
一、构建网关
1、创建一个基础的Spring Boot工程,命名为api-gateway。
2、编写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</artifactId> <version>0.0.1-SNAPSHOT</version> <name>api-gateway</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-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>
对于spring-cloud-starter-zuul依赖,可以通过查看它的内容了解到:该模块不仅包含了Netflix Zuul的核心依赖zuul-core,还包含了spring-cloud-starter-netflix-hystrix、spring-cloud-starter-netflix-ribbon、spring-boot-starter-actuator。
3、在应用主类上使用@EnableZuulProxy注解来开启Zuul的API网关服务功能。
@EnableZuulProxy
@SpringCloudApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
4、在application.properties中配置Zuul应用的基础信息:
spring.application.name=api-gateway
server.port=5555
完成以上的步骤我们就将网关服务构建完成。
二、请求路由
我们使用已有的微服务应用,比如我在之前实验中的hello-service、fegin-consume。其他为服务也可以,没影响。
1、传统路由方式:需要维护微服务应用的具体实例位置。
我们只需要在api-gateway服务中增加一些关于路由,规则的配置:
zuul.routes.api-a-url.path=/api-a/**
zuul.routes.api-a-url.url=http://localhost:5555/
来访问http://localhost:5555/api-a/hello。API网关会将该请求路由到http://localhost:5555/hello提供的微服务接口上。
2、面向服务的路由
Spring Cloud Zuul实现了与Spring Cloud Eureka的无缝整合,我们可以让路由的path不是映射具体的url,而是让它映射到某个具体的服务,而具体的url则交给Eureka的服务发现机制去自动维护。
步骤如下:
(1)、在api-gateway的pom.xml中引入eureka的依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
(2)、在api-gateway的application.properties配置文件中指定Eureka注册中心的位置,并且配置服务路由。
#面向服务的路由
zuul.routes.api-a.path=/api-a/**
zuul.routes.api-a.serviceId=hello-servicezuul.routes.api-b.path=/api-c/**
zuul.routes.api-b.serviceId=feign-consumereureka.client.service-url.defaultZone=http://localhost:1111/eureka/
通过指定Eureka Server服务注册中心的位置,除了将自己注册成为服务之外,同时也让Zuul能够获取hello-service和fegin-consume服务的实例清单,以实现path映射服务,再从服务中挑选实例来进行请求转发的完整路由机制。
以上分别定义了两个名为api-a、api-b的路由来映射hello-service和feig-consumer微服务应用。
(3)测试验证
- 访问http://localhost:5555/api-a/hello:该url符合/api-a/**规则,由api-a路由负责转发,该路由映射的serviceId为hello-service,所以最终/hello请求会被转发到hello-service服务的某个实例上。
- 访问http://localhost:5555/api-c/feign-consumer2:该url符合/api-c/**规则,由api-b路由负责转发,该路由映射的serviceId为feign-consumer,所以最终/feign-consumer2请求会被转发到feign-consumer服务的某个实例上。
三、请求过滤
在API网关中实现啊对客户端请求的校验。通过定义过滤器来实现对请求的拦截与过滤,我们只需要继承ZuulFilter抽象类并实现它定义的4个抽象函数就可以完成请求的拦截和过滤了。
(1)、来定义一个简单的Zuul过滤器,它实现了在请求被路由之前检查HttpServletRequest中是否有accessToken参数,若有就进行路由,若没有就拒绝访问,返回401 Unauthorized错误。
public class AccessFilter extends ZuulFilter {
private Logger logger = Logger.getLogger(String.valueOf(AccessFilter.class));
/*过滤器的类型:决定过滤器在请求的哪个生命周期中执行。定义为pre,带包在请求被路由之前执行*/
@Override
public String filterType() {
return "pre";
}
/*过滤器的执行顺序:当请求在一个阶段中存在多个过滤器时,需要根据该方法返回的值来一次执行*/
@Override
public int filterOrder() {
return 0;
}
/*判断过滤器是否需要执行:为true表示该过滤器对所有的请求都会有效。实际运用中可以利用该函数来指定过滤器的有效范围*/
@Override
public boolean shouldFilter() {
return true;
}
/*过滤器的具体逻辑*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
logger.info("send "+request.getMethod()+" request to "+request.getRequestURI().toString());
Object accessToken = request.getParameter("accessToken");
if(accessToken == null){
logger.warning("access token is empty");
//令zuul过滤该请求,不对其进行路由
ctx.setSendZuulResponse(false);
//设置返回的错误码
ctx.setResponseStatusCode(401);
return null;
}
logger.info("access tiken ok");
return null;
}
}
(2)、实现了自定义过滤器之后不会自动生效,需要为其创建具体的Bean才能启动该过滤器,比如,在应用主类增加以下内容:
@EnableZuulProxy
@SpringCloudApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
@Bean
public AccessFilter accessFilter(){
return new AccessFilter();
}
}
(3)、测试验证
重新启动api-gateway应用,进行以下请求进行测试:
- 访问http://localhost:5555/api-a/hello
- 访问http://localhost:5555/api-a/hello?accessToken=token