为什么需要网关呢?
我们知道我们要进入一个服务本身, 很明显我们没有特别好的办法, 直接输入 IP 地址 + 端口号, 我们知道这样的做法很糟糕的, 这样的做法大有问题, 首先暴露了我们实体机器的 IP 地址, 别人一看你的 IP 地址就知道服务部署在哪里, 让别人很方便的进行攻击操作.
第二, 我们这么多服务, 我们是不是要挨个调用它呀, 我们这里假设做了个权限认证, 我们每一个客户访问的都是跑在不同机器上的不同的 JVM 上的服务程序, 我们每一个服务都需要一个服务认证, 这样做烦不烦呀, 明显是很烦的.
那么我们这时候面临着这两个极其重要的问题, 这时我们就需要一个办法解决它们. 首先, 我们看 IP 地址的暴露和 IP 地址写死后带来的单点问题, 我是不是对这么服务本身我也要动态的维护它服务的列表呀, 我需要调用这服务本身, 是不是也要一个负载均衡一样的玩意,
还有关于 IP 地址暴露的玩意, 我是不是需要做一个代理呀, 像 Nginx 的反向代理一样的东西, 还有这玩意上部署公共的模块, 比如所有入口的权限校验的东西. 因此我们现在需要 Zuul API 网关. 它就解决了上面的问题, 你想调用某个服务, 它会给你映射, 把你服务的 IP 地址映射成
某个路径, 你输入该路径, 它匹配到了, 它就去替你访问这个服务, 它会有个请求转发的过程, 像 Nginx 一样, 服务机器的具体实例, 它不会直接去访问 IP, 它会去 Eureka 注册中心拿到服务的实例 ID, 即服务的名字. 我再次使用客户端的负载均衡 ribbon 访问其中服务实例中的一台.
API 网关主要为了服务本身对外的调用该怎么调用来解决的, 还有解决权限校验的问题, 你可以在这里整合调用一系列过滤器的, 例如整合 shiro,springsecurity 之类的东西.
Zuul 可以通过加载动态过滤机制, 从而实现以下各项功能:
1. 验证与安全保障: 识别面向各类资源的验证要求并拒绝那些与要求不符的请求.
2. 审查与监控: 在边缘位置追踪有意义数据及统计结果, 从而为我们带来准确的生产状态结论.
3. 动态路由: 以动态方式根据需要将请求路由至不同后端集群处.
4. 压力测试: 逐渐增加指向集群的负载流量, 从而计算性能水平.
5. 负载分配: 为每一种负载类型分配对应容量, 并弃用超出限定值的请求.
6. 静态响应处理: 在边缘位置直接建立部分响应, 从而避免其流入内部集群.
7. 多区域弹性: 跨越 AWS 区域进行请求路由, 旨在实现 ELB 使用多样化并保证边缘位置与使用者尽可能接近.
接着下来进行实战小 Demo
第一步, 在原来的工程下, 新建一个 Zuul 模块, 引入依赖, 代码如下:
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-eureka</artifactId>
- <version>1.3.5.RELEASE</version>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-zuul</artifactId>
- <version>1.3.5.RELEASE</version>
- </dependency>
接着在启动类上打上 @EnableZuulProxy 注解, 代码如下:
- server:
- port: 5000
- spring:
- application:
- name: api-geteway
- zuul:
- routes:
- # 标识你服务的名字, 这里可以自己定义, 一般方便和规范来讲还是跟自己服务的名字一样
- hello-service:
- # 服务映射的路径, 通过这路径就可以从外部访问你的服务了, 目的是为了不爆露你机器的 IP, 面向服务的路由了, 给你选一个可用的出来,
- # 这里 zuul 是自动依赖 hystrix,ribbon 的, 不是面向单机
path: /hello-service/**
# 这里一定要是你 Eureka 注册中心的服务的名称, 是所以这里配置 serviceId 因为跟 eureka 结合了, 如果单独使用 zuul, 那么就必须写自己机器的 IP 了,
# 如 url:http://localhost:8080/ 这样的不好就是写死 IP 了, 万一这 IP 挂了, 这高可用性, 服务注册那套东西就用不起来了
serviceId: hello-service
eureka:
# 客户端
client:
# 注册中心地址
service-url:
defaultZone: http://localhost:8888/eureka/,http://localhost:8889/eureka/
接着启动先前文章中的注册中心和两个 hello-service 服务提供者, 接着我们运行, 看一下它的请求转发功能, 看他有没有轮询进入两个服务,
输入 localhost:5000/hello-service/hello, 如下:
接着再刷新一遍:
可以看到 zuul 进行了请求分发了. 它是根据你的服务名字 hello-servie 来映射到具体的机器上, 这不就是一个反向代理的功能吗?
zuul 还能进行请求过滤, 那么我们进行一下 token 校验来演示一下, 首先我们需要先新建一个 TokenFilter 类来继承 ZuulFilter 这个类, 实现它的四个接口, 代码如下:
package hjc.zuul;
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import javax.servlet.http.HttpServletRequest;
/**
* Created by cong on 2018/5/18.
*/
- public class TokenFilter extends ZuulFilter {
- // 四种类型: pre,routing,error,post
- //pre: 主要用在路由映射的阶段是寻找路由映射表的
- //routing: 具体的路由转发过滤器是在 routing 路由器, 具体的请求转发的时候会调用
- //error: 一旦前面的过滤器出错了, 会调用 error 过滤器.
- //post: 当 routing,error 运行完后才会调用该过滤器, 是在最后阶段的
- @Override
- public String filterType() {
- return "pre";
- }
- // 自定义过滤器执行的顺序, 数值越大越靠后执行, 越小就越先执行
- @Override
- public int filterOrder() {
- return 0;
- }
- // 控制过滤器生效不生效, 可以在里面写一串逻辑来控制
- @Override
- public boolean shouldFilter() {
- return true;
- }
- // 执行过滤逻辑
- @Override
- public Object run() {
- RequestContext context = RequestContext.getCurrentContext();
- HttpServletRequest request = context.getRequest();
- String token = request.getParameter("token");
- if (token == null){
- context.setSendZuulResponse(false);
- context.setResponseStatusCode(401);
- context.setResponseBody("unAuthrized");
- return null;
- }
- return null;
- }
- }
filterType: 返回一个字符串代表过滤器的类型, 在 zuul 中定义了四种不同生命周期的过滤器类型, 具体如下:
1.pre: 可以在请求被路由之前调用, 用在路由映射的阶段是寻找路由映射表的
2.route: 在路由请求时候被调用, 具体的路由转发过滤器是在 routing 路由器具体的请求转发的时候会调用
3.error: 处理请求时发生错误时被调用
4.post: 当 routing,error 运行完后才会调用该过滤器, 是在最后阶段的
这里声明一下 zuul 过滤器执行网络请求发生的异常, 过滤器里面是不能直接将 try-catch 捕捉的异常抛出给页面的. 应用程序抛出的异常是可以返回出的需解决办法就是在 catch 里面用 context.set() 方法返回给页面. 如下:
try{
业务逻辑......
- }catch(Exception e){
- RequestContext context = RequestContext.getCurrentContext();
- context.set("error.status_code",401);
- context.set("error.exception",e);
- context.set("error.message","sfdfsdf");
- }
接着, 你还需要把这个过滤器加入 spring 中, 让 spring 管理, 代码如下:
- package hjc;
- import hjc.zuul.TokenFilter;
- import org.springframework.boot.SpringApplication;
- import org.springframework.boot.autoconfigure.SpringBootApplication;
- import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
- import org.springframework.context.annotation.Bean;
- @SpringBootApplication
- @EnableZuulProxy
- public class ZuulApplication {
- public static void main(String[] args) {
- SpringApplication.run(ZuulApplication.class, args);
- }
- // 将过滤器交给 Spring 管理
- @Bean
- public TokenFilter tokenFilter(){
- return new TokenFilter();
- }
- }
接着, 让我们启动启动类, 先进行不带 token 的访问, 如下:
可以看到, 返回一个没权限的信息, 这里要说一下, Token 一般都是放在请求头中的, 这里我们只是为了演示才没那么干,
接着将 token 带上再去访问, 如下:
可以看到这是已经将我们的请求放过去了.
这里我还要讲一下什么是默认路由, 将 zuul 的配置删除路由配置, 如下:
- server:
- port: 5000
- spring:
- application:
- name: api-geteway
- eureka:
- # 客户端
- client:
- # 注册中心地址
- service-url:
- defaultZone: http://localhost:8888/eureka/,http://localhost:8889/eureka/
接着, 重启继续访问, 如下:
,
可以看到, 还是能继续访问, 我们什么都没配, 居然还能访问, 那是因为, 这里默认用你的服务名字 hello-service 自动声明了.
那么, 如果说我不想让它帮我自动声明, 我要我自己定义, 那么可以在 yml 配置文件中使用 zuu.ignored-services 就可以把自己像过滤的过滤, 如下:"
- zuul:
- # 如果 ignored-services:* 表示所有的默认路由都失效了, 要自己一个个配, 没人会那么操蛋, 除非遇到奇葩业务
- ignored-services:
接着我们再说一下映射规则, 比方说
- zuul:
- routes:
- # 标识你服务的名字, 这里可以自己定义, 一般方便和规范来讲还是跟自己服务的名字一样
- hello-service:
- # 服务映射的路径, 通过这路径就可以从外部访问你的服务了, 目的是为了不爆露你机器的 IP, 面向服务的路由了, 给你选一个可用的出来,
- # 这里 zuul 是自动依赖 hystrix,ribbon 的, 不是面向单机
- path: /hello-service/**
- # 这里一定要是你 Eureka 注册中心的服务的名称, 是所以这里配置 serviceId 因为跟 eureka 结合了, 如果单独使用 zuul, 那么就必须写自己机器的 IP 了,
- # 如 url:http://localhost:8080/ 这样的不好就是写死 IP 了, 万一这 IP 挂了, 这高可用性, 服务注册那套东西就用不起来了
- serviceId: hello-service
- zuul:
- routes:
- hello-service:
- path: /hello-service/ext/**
- serviceId: hello-service
- 这里的两个 zuul 配置映射路径都有 / hello-service/, 可以看到 / hello-service/** 是包括 / hello-service/ext/** 的, 这两个路径进行匹配的时候是不是有冲突呀, 怎么处理呢? 谁先匹配呢?
- 这里是 yml 中定义的顺序来匹配的. 如果是 application.properties 格式的配置文件, 它这个顺序是不能保证的, yml 格式的配置文件是有顺序的, 可以保证, 这里要注意下一下.
- 如果我们想定义一下匹配规则怎么办呢? 那么我们就需要在启动类中定义一个 bean, 这个类就是决定你的路由的, 如下:
- 这里就不演示了, 需要用到的时候自己再去慢慢查找资料吧.
- 还有就是 ignored-patterns:, 如下:
- zuul:
- routes:
- # 标识你服务的名字, 这里可以自己定义, 一般方便和规范来讲还是跟自己服务的名字一样
- hello-service:
- # 服务映射的路径, 通过这路径就可以从外部访问你的服务了, 目的是为了不爆露你机器的 IP, 面向服务的路由了, 给你选一个可用的出来,
- # 这里 zuul 是自动依赖 hystrix,ribbon 的, 不是面向单机
- path: /hello-service/**
- # 这里一定要是你 Eureka 注册中心的服务的名称, 是所以这里配置 serviceId 因为跟 eureka 结合了, 如果单独使用 zuul, 那么就必须写自己机器的 IP 了,
- # 如 url:http://localhost:8080/ 这样的不好就是写死 IP 了, 万一这 IP 挂了, 这高可用性, 服务注册那套东西就用不起来了
- serviceId: hello-service
- ignored-patterns: /hello/**
- ignored-patterns: 表示屏蔽掉 / hello/** 的路径, 就算你 / hello-service/hello/** 也不行, 照样屏蔽. 这个配置我们可以进一步细化, 比如说我不想给 / hello 接口路由, 那我们可以按照上面方式配置
- 如果我们还想配置一个服务的前缀该怎么办? 代码如下:
- zuul:
- routes:
- # 标识你服务的名字, 这里可以自己定义, 一般方便和规范来讲还是跟自己服务的名字一样
- hello-service:
- # 服务映射的路径, 通过这路径就可以从外部访问你的服务了, 目的是为了不爆露你机器的 IP, 面向服务的路由了, 给你选一个可用的出来,
- # 这里 zuul 是自动依赖 hystrix,ribbon 的, 不是面向单机
- path: /hello-service/**
- # 这里一定要是你 Eureka 注册中心的服务的名称, 是所以这里配置 serviceId 因为跟 eureka 结合了, 如果单独使用 zuul, 那么就必须写自己机器的 IP 了,
- # 如 url:http://localhost:8080/ 这样的不好就是写死 IP 了, 万一这 IP 挂了, 这高可用性, 服务注册那套东西就用不起来了
- serviceId: hello-service
- prefix: /api/**
- 可以看到那么你访问的服务都必须要加 / api / 前缀, 例如 / api/hello-service/**
- 如果我们还想进行一个路径访问就跳转到我的本地, 那该怎么办呢?
- 我希望用户在访问 / local 时能够自动跳转到这个方法上来处理, 那么此时我们需要用到 Zuul 的本地跳转, 配置方式如下:
- zuul:
- prefix: /api
- ignored-patterns: /**/hello/**
- routes:
- local:
- path: /hello-service/**
- url: forward:/local
我们常用的一些, 对接 springsecurity, 或者是一些第三方组件, 它们会获取你的一些 cookie 信息, 那么 Zuul 网关为了安全起见, 把你的 cookie 信息都给干掉了, 这个是没办法去搞 cookie 的. 它是默认干掉的.
这里 Zuul 提供了 zuul.sensitive-headers 来给你搞这些 cookie,header, 这些信息不要进行过滤. 控制你的敏感信息.
默认情况下, 敏感的头信息无法经过 API 网关进行传递, 我们可以通过如下配置使之可以传递:
- zuul:
- routes:
- hello-service:
- path: /hello-service/**
- serviceId: hello-service
sensitive-headers: cookie,header 之类额东西
还可以配合 Hystrix 的一些详细配置一起使用, 前面也讲过了. 这里就不说了
来源: https://www.cnblogs.com/huangjuncong/p/9060984.html