API网关是一个智能的应用服务器,它的定义类似于面向对象设计模式中的Facade模式,它的存在就像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过他来进行调度和过滤。它除了要实现请求路由,负载均衡,校验过滤等功能之外,还需要更多能力,比如与服务治理框架的结合,请求转发时的熔断机制,服务的聚合等一系列高级功能。
构建网关
1.首先创建一个SpringBoot工程,命名为api-gateway并引入zuul的依赖,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>
<groupId>com.example</groupId>
<artifactId>api-gateway</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>api-gateway</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.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-netflix-zuul依赖可以看到,该模块不仅包含了Netfliz Zuul的核心依赖zuul-core它还包括了下面网关需要的重要依赖。
spring-cloud-starter-netflix-hystrix:该依赖用来在网关服务中实现对微服务转发时候的保护机制,通过线程隔离和断路器,防止微服务的故障引发API网关资源无法释放,从而影响其他应用的对外服务。
spring-cloud-starter-netflix-ribbon:该依赖用来实现在网关服务进行路由转发时候的客户端负载均衡以及请求重试。
spring-boot-starter-actuator:该依赖用来提供常规的微服务管理端点。另外在Spring Cloud Zuul中还特别提供/routes端点来返回当前所有路由规则。
2.创建应用应用主类,使用@EnableZuulProxy注解开启Zuul的API网关服务功能。
@EnableZuulProxy
@SpringBootApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
}
3.在application.properties中配置Zuul应用的基础信息,如应用名,服务端端口号:
spring.application.name=api-gateway
server.port=5555
这样Zuul实现的API网关服务就构建完毕了。
请求路由
首先开启服务提供者和服务消费者以及服务注册中心:
传统路由方式
使用传统路由方式只需要在application.properties文件中添加:
zuul.routes.api-a-url.path=/api-a-url/**
zuul.routes.api-a-url.url=http://localhost:8082/
该配置定义了所有服务/api-a-url/**规则的访问都将被路由转发到http://localhost:8082地址上如:
也就是说访问http://localhost:5555/api-a-url/refactor/hello时候api网关会将该请求路由到http://localhost:8082/refactor/hello提供的微服务接口上。其中配置属性zuul.routes.api-a-url.path中的api-a-url部分为路由的名字,可以任意定义,但是一组path和url映射关系的路由名要相同。
面向服务的路由
1.为了与Eureka整合,需要在api-gateway的pom.xml中引入spring-cloud-starter-netflix-eureka-client:
<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-service
zuul.routes.api-b.path=/api-b/**
zuul.routes.api-b.serviceId=feign-consumer
eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
http://localhost:5555/api-a/refactor/hello:该url符合/api-a/**规则,由api-a路由进行转发,该路由映射的serviceId为hello-service,所以最终/hello请求会被发送到hello-service服务的某个实例上。
http://localhost:5555/api-b/feign-consumer:该url符合/api-b/**规则,由api-b路由负责转发,该路由映射的serviceId为feign-consumer,所以最终/feign-consumer会被发送到feign-consumer服务的某个实例上。
通过面向服务的路由配置方式,我们不需要再为各个路由维护微服务应用的具体实例位置,而是通过path与serviceId的映射组合,使维护工作变得简单。
请求过滤
在实现了请求路由功能之后,我们的微服务应用提供的接口就可以通过统一的API网关入口被客户端访问到了。为了解决鉴权逻辑的分离问题,更好的做法是通过前置网关服务来完成这些非业务性质的校验。为了在API网关中实现对客户端请求的校验,下面演示Spring Cloud Zuul的另外一个核心功能:请求过滤。Zuul允许开发者在API网关上通过定义过滤器来实现对请求的拦截与过滤,实现的方式只需要继承ZuulFilter抽象类并实现它定义的4个抽象函数就可以完成对请求的过滤与拦截。
首先定义个简单的Zuul过滤器,它实现了在请求被路由之前检查HttpServletRequest中是否有accessToken参数,若有就进行路由,若没有就拒绝访问,返回401 Unauthorized错误。
public class AccessFilter extends ZuulFilter {
private static Logger log=LoggerFactory.getLogger(AccessFilter.class);
@Override
public String filterType() {
return "pre";
}
@Override
public int filterOrder() {
return 0;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() throws ZuulException {
RequestContext ctx=RequestContext.getCurrentContext();
HttpServletRequest request=ctx.getRequest();
log.info("send {} request to {}",request.getMethod(),request.getRequestURL().toString());
Object accessToken=request.getParameter("accessToken");
if (accessToken==null){
log.warn("access token is empty");
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
return null;
}
log.info("access token ok");
return null;
}
}
filterType:过滤器的类型,它决定了过滤器在请求的哪个生命周期中执行。这里定义为pre,代表会在请求被路由之前执行。
filterOrder:过滤器的执行顺序。当请求在一个阶段中存在过个过滤器时,需要根据该方法返回的值来依次执行。
shouldFilter:判断该过滤器是否需要被执行。这里我们直接返回了true,因此该过滤器对所有请求都会生效。实际运用中我们可以利用该函数来指定过滤器的有效范围。
run:过滤器的具体逻辑。这里通过ctx.setSendZuulResponse(false)令zuul过滤该请求,不对其进行路由,然后通过ctx.setResponseStatusCode(401)设置了其返回的错误码,当然也可以进一步优化我们的返回,比如通过ctx.setResponseBody(body)对返回的body内容进行编辑。
为了开启过滤器,必须要在应用主类改为如下内容:
@EnableZuulProxy
@SpringCloudApplication
public class ApiGatewayApplication {
public static void main(String[] args) {
SpringApplication.run(ApiGatewayApplication.class, args);
}
@Bean
public AccessFilter accessFilter(){
return new AccessFilter();
}
}
下面进行测试:
Ok~API网关服务的快速入门实例完成了。下面总结一下:
1.它作为系统的统一入口,屏蔽了系统内部各个微服务的细节。
2.他可以与服务治理框架结合,实现了自动化的服务实例维护以及负责均衡的路由转发。
3.他可以实现接口权限校验与微服务业务逻辑的解耦。
4.通过服务网关中的过滤器,在各生命周期中去校验请求的内容,将原本在对外服务层做的校验前移,保证了微服务的无状态性,同时降低了微服务的测试难度,让服务本身更集中关注了业务逻辑的处理。
参考《Spring Cloud 微服务实战》