大纲
- 默认实现
- 服务名路由
- 路由排除
- 自定义路由映射
- 过滤器
1. Zuul的默认实现
- 这里新建一个项目吧,比起Eureka客户端,需要额外引入Zuul
- 配置application.yml,服务中心和服务提供者接前文中的项目
server:
port: 9999
#配置应用的名字
spring:
application:
name: zuul-server
eureka:
client:
#register-with-eureka: true #是否将当前应用注册到注册中心, 默认true,这里可以不配置
#fetch-registry: true #当前项目是否到注册中心中拉去服务列表,默认true,这里可不一配置
service-url:
# 配置服务治理中心
defaultZone: http://localhost:7000/eureka/,http://localhost:8000/eureka/
- 添加注解:@EnableZuulProxy、@EnableEurekaClient
package com.springcloud.zuulserver;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
/**
*
* @date 2020/8/2 11:18
* @author wei.heng
*/
@SpringBootApplication
@EnableEurekaClient
@EnableZuulProxy
public class ZuulServerApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulServerApplication.class, args);
}
}
- 测试
这里可以直接通过“网关服务的地址 + 服务名称 + 接口地址”进行访问了
这与直接访问服务地址(接前文的项目)是一毛一样滴,如下:
2. 服务名路由
2.1. 修改application.yml,添加配置信息
# 配置静态路由规则
zuul:
routes:
product-provider: # 自定义节点,用于区分不同的服务(多个服务路由自定义多个节点即可)
path: /pp/** # 服务路径映射规则
serviceId: product-provider # 注册中心的服务名
或采用更简单的配置方式(亲测OK):
#zuul.routes.<服务名>=<映射地址>
zuul:
routes:
product-provider: /pp/**
2.2 启动项目进行测试
可以看到直接通过 http://localhost:9999/pp/product/1 代替了原有地址获取到了访问结果
2.3 参考文档
看以前的笔记,配置的是 service-id,发现一直报错,于是乎上官方找了找,发现改成了serviceId
官网地址:NetflixZuulStarter
示例图:
3.路由排除
3.1 添加application配置
#网关路由排除,排除某个几个服务
zuul:
product-provider:
#- product-provider #排除单个服务(订单服务)
- '*' #排除所有服务
#- xxx-provider 该属性为集合属性,指定需要排除的服务名字
routes:
product-provider.path: /pp/** #不排除产品服务
#网络路由排除,使用通配符指定排除规则
#zuul:
#ignored-patterns:
#- /**/product/** #排除路径中包含product的服务
#routes:
#product-provider.path: /product/** #不排除产品服务
了解一下吧,这块功能我就不测试了
3.2 通配符规则
通配符 | 说明 | 举例 |
---|---|---|
? | 匹配单个字符 | /product/? |
* | 匹配任意数量字符,但不支持多级目录 | /product/* |
** | 匹配任意数量字符,支持多级目录 | /product/** |
4. 自定义路由映射
4.1 官方简介
PatternServiceRouteMapper对象可以让开发者通过正则表达式来自定义服务与路由映射的生成关系。其中构造函数的第一个参数用来匹配服务名称是否符合该自定义规则的正则表达式(serviceId),而第二个参数则是定义服务名中定义的内容转换出的路径表达式(path),只要有符合第一个参数定义的serviceId,那么就会优先使用该实现构建出path,如果没有找到则使用默认路由映射需——采用完整服务名做为前缀的路径表达式。
这里是将myusers-v1这个微服务, 映射到/v1/myusers/**这个路径。
4.2 使用场景
我们在构建微服服务系统的进行业务逻辑开发的时候,为了兼容外部不同版本的客户端程序(尽量不强迫用户升级客户端),一般都会采用开闭原则来进行设计与开发。这使得系统在迭代过程中,有时候需要我们为一组互相配合的微服务定义一个版本标记来方便管理它们的版本关系,根据这个标记我们可以很容易的知道这些服务需要一起启动并配合使用。比如:userservice-v1,userservice-v2,orderservice-v1,orderservice-v2等等。默认情况下,zuul自动为服务创建的路由表达式会采用服务名作为前缀,比如针对上面的userservice-v1和userservice-v2,它会产生/userservice-v1和/userservice-v2两个路径表达式来映射,这样生成出来的表示式规则单一,不利于管理。通常的做法就是为这些不同的版本的微服务应用生成以版本号作为路由前缀定义规则的路由规则,比如/v1/userservice/。这时候,通过这样具有版本号前缀的url路径,我们就可以很同意的通过路径表达式来归类和管理这些具有版本信息的微服务了。
4.3 使用
- 添加配置
package com.springcloud.zuulserver;
import org.springframework.cloud.netflix.zuul.filters.discovery.PatternServiceRouteMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ApplicationConfig {
@Bean
public PatternServiceRouteMapper patternServiceRouteMapper() {
//定义用来匹配服务的正则表达式
String servicePattern="(?<name>^.+)-(?<version>v.+$)";
//定义用来匹配路径的正则表达式
String routePattern="${version}/${name}";
return new PatternServiceRouteMapper(servicePattern, routePattern);
}
}
- 注释掉application.yml中zuul的静态路由配置
4.4 测试
测试结果很OK,但生产中的实际使用,感受还不太明显,希望下一份工作找个springcloud架构的项目
5. 过滤器
5.1 简介
spring cloud Zuul包含了对请求的路由和过滤2个功能。路由功能负责将请求转发到具体的微服务上,而过滤器负责对请求的处理过程进行干预,是实现权限校验、服务聚合等功能的基础。
在实际运行时,路由映射和请求转发是由几个不同的过滤器完成的。每一个进入zuul的http请求都会经过一系列的过滤器处理链得到请求响应并返回给客户端。
spring cloud zuul包含4种类型的过滤器:
- pre过滤器: 在请求被路由之前调用。Zuul请求微服务之前。比如请求身份验证,选择微服务实例,记录调试信息等。
- route过滤器: 负责转发请求到微服务。原始请求在此构建,并使用Apache HttpClient或Netflix Ribbon发送原始请求。
- post过滤器: 在route和error过滤器之后被调用。可以在响应添加标准HTTP Header、收集统计信息和指标,以及将响应发送给客户端等。
- error过滤器: 在处理请求发生错误时被调用。
在Spring Cloud Zuul 中实现过滤器必须包含4 个基本特征:
- 过滤类型
- 执行顺序
- 执行条件
- 具体操作
实际上就是ZuulFilter抽象类中定义的抽象方法:
String filterType();
int filterOrder();
boolean shouldFilter();
Object run();
- filterType:该方法需要返回一个字符串来代表过滤器的类型,而这个类型就是Zuul中的4种不同生命周期的过滤器类型,如下
- pre:在请求到达路由前被调用
- route:在路由请求时被调用
- error: 处理请求时发生的错误时被调用。
- post:在route和error过滤器之后被调用,最后调用。
- filterOrder:通过int值定义过滤器执行顺序,数值越小优先级越高。
- shouldFilter:返回布尔值来判断该过滤器是否执行。
- run:过滤器的具体逻辑。可以在此确定是否拦截当前请求等。
5.2 Zuul请求的生命周期
- 首先HTTP请求到达Zuul,最先来到pre过滤器,在这里会去映射url-patern到目标地址上
- 然后将请求与找到的地址交给route类型的过滤器进行求转发,请求服务实例获取响应,
- 通过post类型过滤器对处理结果进行加工与转换等操作返回。
- error类型的过滤器在这整个请求过程中只要有异常才会触发,将异常结果交给post类型过滤器加工返回
5.3 实现
- 自定义过滤器类
package com.springcloud.zuulserver;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.netflix.zuul.exception.ZuulException;
import org.apache.commons.lang.StringUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class PermissionFilter extends ZuulFilter {
/**
* 过滤器的类型,它决定过滤器在请求的哪个生命周期中执行。
* 这里定义为pre,代表会在请求被路由之前执行。
* @return java.lang.String
* @date 2020/8/2 17:33
* @author wei.heng
*/
@Override
public String filterType() {
return "pre";
}
/**
* filter执行顺序,通过数字指定。
* 数字越大,优先级越低。
* @return int
* @date 2020/8/2 17:34
* @author wei.heng
*/
@Override
public int filterOrder() {
return 0;
}
/**
* filter 开启关闭
* 判断该过滤器是否需要被执行。这里我们直接返回了true,
* 因此该过滤器对所有请求都会生效。
* 实际运用中我们可以利用该函数来指定过滤器的有效范围。
* @return boolean
* @date 2020/8/2 17:35
* @author wei.heng
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 过滤器的具体逻辑
* @return java.lang.Object
* @date 2020/8/2 17:36
* @author wei.heng
*/
@Override
public Object run() throws ZuulException {
//获得HttpServletRequest对象
RequestContext cc = RequestContext.getCurrentContext();
HttpServletRequest request = cc.getRequest();
//获得HttpServletResponse对象
HttpServletResponse response = cc.getResponse();
//获得客户端提交的Token进行验证
String token=request.getParameter("token");
if(StringUtils.isEmpty(token) || !token.equals("123456")) {
//不对该请求进行路由,不会将请求转发到后端
cc.setSendZuulResponse(false);
//设置错误状态码
cc.setResponseStatusCode(HttpStatus.UNAUTHORIZED.value());
//设置响应体,也可以将响应的数据封装为json串进行响应
cc.setResponseBody("权限不足.....");
response.setContentType("text/html;charset=UTF-8");
//设置响应头信息
//cc.addZuulRequestHeader("Content-Type",
//MediaType.TEXT_HTML_VALUE+";charset=UTF-8");
//ZuulFilterResult
}
return null;
}
}
- 过滤器注册
@Bean
public PermissionFilter permissionFilter() {
return new PermissionFilter();
}
- 测试