Spring Cloud Zuul是什么?
1、微服务当中有很多独立服务都要对外提供,那么我们要如何去管理这些接口?
2、在微服务中,一个独立的系统被拆分成了很多个独立的服务,为了确保安全,权限管理是不可避免的问题,如果在每一个服务器上添加相同的权限验证来确保不会被非法访问,工作量大且不易维护。
为了解决以上问题:微服务架构中提出了API网关概念,它就像一个安监站所有的外部请求都需要经过它的调度与过滤,然后API官网来实现路由、负载均衡、权限验证等。Spring Cloud一站式微服务开发框架基于Netflix Zuul实现了Spring Cloud Zuul既可以实现一套API网关服务。
使用 Zuul 构建 API 网关:
1、创建一个Spring Boot项目并且导入依赖:
<!--添加spring cloud的zuul的起步依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-zuul</artifactId>
</dependency>
<!--添加spring cloud的eureka的客户端依赖-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
2、 在Spring Boot启动入口类上添加注解:
@SpringBootApplication
@EnableZuulProxy //开启zuul的API网关功能
public class SpringCloudZuulApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudZuulApplication.class,args);
}
}
3、在application.properties配置文件中配置路由规则:
#内嵌tomcat
server.port=8080
#配置服务名称
spring.application.name=06springcloudapigateway
#配置 API 网关到注册中心上,API 网关也将作为一个服务注册到 eureka-server 上
eureka.client.service-url.defaultZone=http://erueka8761:8761/eureka/,http://erueka8762:8762/eureka/
#配置路由规则
#如下配置,我们的路由规则就是匹配所有符合/api-nj/**的请求,只要路径中带有/api-nj/都将被转发到05springcloudfeign服务上,
#至于05springcloudfeign服务的地址到底是什么则由 eureka-server 注册中心去分析,我们只需要写上服务名即可。(就相当于请求走到网关之后配置网关接收到哪类请求转发到哪个微服务上)
zuul.routes.api-nj.path=/api-nj/**
zuul.routes.api-nj.serviceId=05springcloudfeign
路由规则中配置的 api-nj 是路由的名字,可以任意定义,但是一组 path 和serviceId 映射关系的路由名要相同。
使用 Zuul 进行请求过滤:
Spring Cloud Zuul就像一个安监站,所有的请求都会经过它,我们可以在它当中实现对请求的过滤,下面小案例就是验证token
1、自定义一个过滤器类AuthFilter并继承自 ZuulFilter,并将该 Filter 作为一个 Bean:
@Component
public class AuthFilter extends ZuulFilter {
/**
* filterType 方法的返回值为过滤器的类型,过滤器的类型决定了过滤器在哪个生命周期执行,pre 表示在路由之前执行过滤器
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* filterOrder 方法表示过滤器的执行顺序,当过滤器很多时,我们可以通过
* 该方法的返回值来指定过滤器的执行顺序。
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* shouldFilter 方法用来判断过滤器是否执行,true 表示执行,false 表示不执行。
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* run 方法则表示过滤的具体逻辑
* 本次测试:如果请求地址中携带了 token 参数的话,则认为是合法请求,否则为非法请求,如果是非法请求的话
* 首先设置ctx.setSendZuulResponse(false); 表示不对该请求进行路由
* 然后设置响应码和响应值
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
//制造运行时异常信息
// int a = 10/0;
HttpServletRequest request = ctx.getRequest();
String token = request.getParameter("token");
if (token == null){
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.addZuulResponseHeader("content-type","text/html;charset=utf-8");
ctx.setResponseBody("非法访问!!!");
}
return null;
}
}
解释如下:
(1)filterType 方法的返回值为过滤器类型,过滤器的类型决定了它在哪个生命周期执行,pre表示路由之前执行;route表示路由请求时执行;error表示请求时发生错误执行;post在route和error过滤之后执行;
(2)filterOrder 方法表示过滤器的执行顺序,当过滤器很多时,可以通过制定该方法的返回值来制定过滤器执行顺序;
(3)shouldFilter 方法用来判断过滤器是否执行,true表示执行,false表示不执行
(4)run 方法则表示过滤的具体逻辑,如果请求当中携带token参数的话,则认为是合法请求,否则为非法请求,如果是非法请求的话,首先设置ctx.setSendZuulResponse(false)表示不对该请求进行路由,然后设置响应码和响应值。
Zuul 的路由规则:
1、上面的例子当中配置路由为:
zuul.routes.api-nj.path=/api-nj/**
zuul.routes.api-nj.serviceId=05springcloudfeign
当访问请求符合/api-nj/**规则的时候会被转发到05springcloudfeign里面去定位相应的方法,还可以使用zuul.routes 后面跟着的是服务名,服务名后面跟着的是路径规则:
#等价于上面那两行配置
zuul.routes.05springcloudfeign=/api-nj/**
2、如果映射规则我们什么都不写,系统也给我们提供了一套默认的配置规则
#默认规则(使用服务名称作为路径)
zuul.routes.05springcloudfeign.path=/05-springcloud-service-feign/**
zuul.routes.05springcloudfeign.serviceId=05-springcloud-service-feign
3、默认情况下,Eureka上所有注册的服务都会被Zuul创建映射关系来进行路由:(此时我们就要进行忽略掉某些服务不让她创建默认路由)
#忽略掉服务提供者的默认规则(忽略掉提供者不能使用默认规则进行访问)
zuul.ignored-services=01springcloudprovider
不给某个服务设置映射规则, 这个配置还可以进一步直接不给接口路由,可以如下配置:
#忽略掉某一些接口路径
zuul.ignored-patterns=/**/hello/**
也可以统一路由规则增加前缀:
#配置网关路由的前缀
zuul.prefix=/myapi
4、路由规则通配符的含义:
通配符 | 含义 | 举例 | 说明 |
---|---|---|---|
? | 匹配任意单个字符 | /api-nj2/? | 匹配:/api-nj2/a /api-nj2/b等 |
* | 匹配任意数量的字符 | /api-nj2/* | 匹配:/api-nj2/ab /api-nj2/abc等 |
** | 匹配任意数量的字符 | /api-nj2/** | 匹配:/api-nj2/ab /api-nj2/ab/ac/abc等 |
5、一般情况API网关只是作为微服务的统一入口,但是有时候我们可能也需要在网关服务做一些特殊业务逻辑处理,那么可以让请求到达网关之后在转发给自己,由API网关自己来处理:
@RestController
public class GateWayController {
@RequestMapping("/api/local")
public String hello(){
System.out.println("在 api gateWay当中执行业务逻辑!!!");
return "the api gateWay";
}
}
#配置路由规则
zuul.routes.gateway.path=/gateway/**
zuul.routes.gateway.url=forward:/api/local
Zuul 的异常处理:
Spring Cloud Zuul对异常的处理是非常方便的,本案例以Finchley.RELEASE 版本为例,来说明Spring Cloud Zuul 中的异常处理问题。
正常情况下的请求都是按照pre,routing,post的顺序来执行,然后由post返回response
在pre阶段,如果有自定义的过滤器则先执行
pre,routing,post的任一阶段如果出抛出异常,则执行error过滤器
有两种方式统一处理异常:
(1)禁用zuul默认的异常处理 SendErrorFilter 过滤器,然后自定义ErrorFilter集成SendErrorFilter
#禁用 zuul 默认的异常处理 SendErrorFilter 过滤器,然后自定义我们自己的ErrorFilter过滤器
zuul.SendErrorFilter.error.disable=true
@Component
public class ErrorFilter extends SendErrorFilter {
private static final Logger logger = LoggerFactory.getLogger(ErrorFilter.class);
@Override
public String filterType() {
return "error";
}
@Override
public int filterOrder() {
return 1;
}
@Override
public boolean shouldFilter() {
return true;
}
@Override
public Object run() {
try {
RequestContext context = RequestContext.getCurrentContext();
ZuulException exception = (ZuulException)context.getThrowable();
logger.error("进入系统异常拦截", exception);
HttpServletResponse response = context.getResponse();
response.setContentType("application/json;charset=utf-8");
response.setStatus(exception.nStatusCode);
PrintWriter writer = null;
try {
writer = response.getWriter();
writer.print("{code:"+ exception.nStatusCode +",message:\""+
exception.getMessage() +"\"}");
}catch (IOException e){
e.printStackTrace();
}finally {
if (writer!=null){
writer.close();
}
}
}catch (Exception e){
ReflectionUtils.rethrowRuntimeException(e);
}
return null;
}
}
(2)自定义全局 error 错误页面
@RestController
public class ErrorHandlerController implements ErrorController {
@Override
public String getErrorPath() {
return "/error";
}
@RequestMapping("/error")
public Object error(){
RequestContext context = RequestContext.getCurrentContext();
ZuulException exception = (ZuulException)context.getThrowable();
return exception.nStatusCode + "===" + exception.getMessage();
}
}