API网关服务 Spring Cloud Zuul
API网关是一个更为智能的应用服务器,它的存在就像是整个微服务架构系统的门面,所有的外部客户端访问都需要经过它来进行调度和过滤。除了需要实现请求路由,负载均衡及校验过滤等功能外还需要与服务治理框架的结合,请求转发时的熔断机制,服务的聚合等一系列高级功能。
在Spring Cloud中提供了基于Netflix Zuul实现的API网关组件Spring Cloud Zuul。对于路由规则与服务实例的维护,Spring Cloud Zuul通过与Spring Cloud Eureka进行整合,将自身注册为Eureka治理下的应用,同时从Eureka获得了所有其他微服务的实例信息,使得将维护服务实例的工作交给了服务治理框架自动完成。不在需要人工介入。对于路由规则的维护,Zuul默认将通过以<font color=#FF0000>服务名</font>作为ContextPath的方式创建路由映射。对于类似签名校验及登录校验,可以独立成一个单独的服务存在,只是它并不是给各个微服务调用,而是在API网关服务上进行统一调用来对微服务接口做前置过滤,实现对微服务接口的拦截和校验。Spring Cloud Zuul提供了一套过滤器机制,可以很好地支持这样的任务。
1. 快速集成 Spring Cloud Zuul
1.1 构建网关
创建基础Spring Boot工程
添加依赖: spring-cloud-starter-zuul
在主类上使用
@EnableZuulProxy
注解开启Zuul的API网关服务功能-
配置Zuul应用的基础信息
spring.application.name=api-gateway spring.port=8080
1.2 请求路由
启动Eureka服务注册中心和微服务应用
-
传统路由方式
对api-gateway服务增加路由配置规则
zuul.routes.api-a-url.path=/api-a-url/** zuul.routes.api-a-url.url=http://localhost:8081/
定义发往API网关的请求中,所有符合
/api-a-url/**
规则的访问都将被路由转发到http://localhost:8081/
地址上。api-a-url
部分为路由的名称,可任意定义,一组path与url映射关系的路由名要相同。 -
面向服务的路由
Spring Cloud Zuul实现了与Spring Cloud Eureka的无缝整合,可以让路由的path不是映射具体的url,而是映射某个具体的服务,而具体的服务则交给Eureka的服务发现机制去自动维护,称之为面向服务的路由。
添加依赖:spring-cloud-starter-eureka
配置Zuul的Eureka注册中心的位置
-
配置服务路由
zuul.routes.api-a.path=/api-a/** zuul.routes.api-a.serviceId=hello-service
简洁配置方式
zuul.routes.hello-service=/api-a/**
1.3 请求过滤
Zuul允许在API网关上通过定义过滤器实现对请求的拦截与过滤,只需要继承ZuulFilter
抽象类并定义它的四个抽象函数。
public class AccessFilter extends ZuulFilter {
/**
* 过滤器类型,决定过滤器在请求的哪个生命周期执行
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器的执行顺序,当同一阶段存在多个过滤器时,需要根据该返回值指定执行顺序
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 过滤器是否执行
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器任务
*/
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
HttpServletRequest request = ctx.getRequest();
String token = request.getHeader("Authorization");
if (token == null) {
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
// ctx.setResponseBody(body);
}
return null;
}
}
定义Zuul拦截器配置,并添加拦截器
@Configuration
public class ZuulFilterConfiguration {
@Bean
public AccessFilter accessFilter() {
return new AccessFilter();
}
}
2. 路由详解
2.1 服务路由配置
简洁配置方式
zuul.routes.hello-service=/api-a/**
2.2 服务路由默认规则
为Spring Cloud Zuul构建的API网关服务引入Spring Cloud Eureka之后,它为Eureka中的每个服务都自动创建一个默认路由规则,默认路由规则的path使用serviceId配置的服务名作为请求前缀,以hello-service为例:
zull.routes.hello-service.path=/hello-service/**
zull.routes.hello-service.serviceId=hello-service
2.3 路径匹配
在Spring Cloud Zuul中,路由匹配的路径表达式采用了Ant风格定义
通配符 | 说明 |
---|---|
? | 匹配任意单个字符 |
* | 匹配任意数量字符 |
** | 匹配任意数量字符,支持多级目录 |
2.4 忽略表达式
Spring Cloud Zuul忽略表达式参数zuul.ignored-patterns
用来设置不希望被API网关进行路由的URL表达式。
zuul.ignored-patterns=/**/hello/**
2.5 Cookie与头信息
默认情况下,Spring Cloud Zuul请求路由时,会过滤掉HTTP请求头信息中的一些敏感信息,防止它们被传递到下游的外部服务器。默认的敏感头信息通过zuul.sensitiveheaders
参数定义,包括Cookie, Set-Cookie, Authorization三个属性。
解决方案:
-
设置全局参数为空覆盖默认值
zuul.sensitiveheaders=
-
设置指定路由的参数
// 对指定路由开启自定义敏感头 zuul.routes.<router>.customSensitiveHeaders=true // 将指定路由的敏感头设置为空 zuul.routes.<router>.sensitiveheaders=
2.6 Hystrix与Ribbon支持
Zuul自身包含了对Hystrix和Ribbon的模块依赖,因此Zuul拥有线程隔离和断路器的自我保护功能,以及对服务调用的客户端负载均衡功能。当使用path和url的映射关系配置路由规则时没有上述功能,因此使用Zuul时尽量使用path和serviceId的组合进行配置。参数配置如下:
ribbon:
ReadTimeout: 600000 // 设置路由转发请求的超时时间
ConnectTimeout: 600000 // 设置路由转发请求,创建请求连接的超时时间
hystrix:
command:
default:
execution:
isolation:
thread:
timeoutInMilliseconds: 60000 // 设置API网关路由转发请求HystrixCommand执行超时时间
2.7 过滤器详解
在Zuul中,路由功能真正运行时,它的路由映射和请求转发由几个不同的过滤器完成。路由映射主要通过pre类过滤器完成,它将请求路径与配置的路由规则进行匹配,找到需要转发的路由地址。请求转发的部分由route类过滤器完成。
在Spring Cloud Zuul中实现的过滤器必须包含4个基本特征:过滤类型,执行顺序,执行条件,具体操作。实际上它们就是ZuulFilter接口中定义的4个抽象方法:
String filterType();
int filterOrder();
boolean shouldFilter();
Object run();
- filterType:过滤器类型
- pre:可以再请求被路由前调用
- routing:在路由请求时被调用
- post:在routing和error过滤器之后被调用
- error:处理请求时发生错误时被调用
- filterOrder:过滤器执行顺序,数值越小优先级越高
- shouldFilter:是否执行过滤器
- run:过滤逻辑
2.8 异常处理
在Spring Cloud Zuul中,在HTTP请求生命周期的各个阶段默认实现了一批核心过滤器,在API网关服务启动的时候被自动加载和启用,但核心过滤器中没有实现error阶段的过滤器,因此需要在自定义过滤器中处理异常内容:
-
try-catch处理
error相关三个主要参数:
- error.status_code:错误码
- error.exception:Exception异常对象
- error.message:错误信息(优先级高于Exception对象中的message)
public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); try { // TODO } catch (Exception exception) { ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); ctx.set("error.exception", exception); } return null; }
-
ErrorFilter处理
在请求生命周期的pre,route,post三个阶段有异常抛出的时候都会进入到error阶段处理,通过创建error类型过滤器捕获异常信息并处理。
public Object run() { RequestContext ctx = RequestContext.getCurrentContext(); Throwable throwable = ctx.getThrowable(); ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR); ctx.set("error.exception", throwable.getCause()); return null; }