网关
通过服务网关统一向外系统提供 REST API 的过程中, 除了具备服务路由, 均衡负载功能之外, 它还具备了权限控制等功能. Spring Cloud Netflix 中的 Zuul 就担任了这样的一个角色, 为微服务架构提供了前门保护的作用, 同时将权限控制这些较重的非业务逻辑内容迁移到服务路由层面, 使得服务集群主体能够具备更高的可复用性和可测试性.
下面我们通过实例例子来使用一下 Zuul 来作为服务的路有功能.
准备
在使用 Zuul 之前, 我们先构建一个服务注册中心, 以及两个简单的服务, 比如: 我构建了一个 service-A, 一个 service-B. 然后启动 eureka-server 和这两个服务. 通过访问 eureka-server, 我们可以看到 service-A 和 service-B 已经注册到了服务中心.
如果您还不熟悉如何构建服务中心和注册服务, 请先阅读 1--SpringCloud 的服务注册与发现 Eureka
创建网关项目
首先创建一个简单的 spring-boot 项目 zuul-getway
pom 文件如下:
- <parent>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-parent</artifactId>
- <version>1.3.5.RELEASE</version>
- <relativePath/> <!-- lookup parent from repository -->
- </parent>
- <dependencies>
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-test</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-eureka-server</artifactId>
- </dependency>
- <!-- 网关依赖 -->
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-starter-zuul</artifactId>
- </dependency>
- </dependencies>
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.springframework.cloud</groupId>
- <artifactId>spring-cloud-dependencies</artifactId>
- <version>Brixton.RELEASE</version>
- <type>pom</type>
- <scope>import</scope>
- </dependency>
- </dependencies>
- </dependencyManagement>
应用主类使用 @EnableZuulProxy 注解开启 Zuul
- @EnableZuulProxy
- @SpringCloudApplication
- public class Application {
- public static void main(String[] args) {
- new SpringApplicationBuilder(Application.class).web(true).run(args);
- }
- @Bean
- public AccessFilter accessFilter() {
- return new AccessFilter();
- }
- }
这里用了 @SpringCloudApplication 注解, 之前没有提过, 通过源码我们看到, 它整合了 @SpringBootApplication,@EnableDiscoveryClient,@EnableCircuitBreaker, 主要目的还是简化配置.
application.properties 配置文件
- spring.application.name=API-gateway
- server.port=5555
服务路由
通过服务路由的功能, 我们在对外提供服务的时候, 只需要通过暴露 Zuul 中配置的调用地址就可以让调用方统一的来访问我们的服务, 而不需要了解具体提供服务的主机信息了.
在 Zuul 中提供了两种映射方式:
通过 url 直接映射, 我们可以如下配置:
- zuul.routes.API-a-url.path=/API-a-url/**
- zuul.routes.API-a-url.url=http://localhost:2222/
- 该配置, 定义了, 所有到 Zuul 的中规则为:/API-a-url/** 的访问都映射到 http://localhost:2222 / 上, 也就是说当我们访问 http://localhost:4444/API-a-url/add?a=1&b=2 的时候, Zuul 会将该请求路由到: http://localhost:2222/add?a=1&b=2 上.
- 其中, 配置属性 zuul.routes.API-a-url.path 中的 API-a-url 部分为路由的名字, 可以任意定义, 但是一组映射关系的 path 和 url 要相同, 下面讲 serviceId 时候也是如此.
- 通过 url 映射的方式对于 Zuul 来说, 并不是特别友好, Zuul 需要知道我们所有为服务的地址, 才能完成所有的映射配置. 而实际上, 我们在实现微服务架构时, 服务名与服务实例地址的关系在 eureka server 中已经存在了, 所以只需要将 Zuul 注册到 eureka server 上去发现其他服务, 我们就可以实现对 serviceId 的映射. 例如, 我们可以如下配置: 完整 application.properties 配置文件
- spring.application.name=API-gateway
- # 启动端口
- server.port=4444
- # 服务中心地址
- eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/
- zuul.routes.API-a.path=/API-a/**
- zuul.routes.API-a.serviceId=service-A
- zuul.routes.API-b.path=/API-b/**
- zuul.routes.API-b.serviceId=service-B
- 针对我们在准备工作中实现的两个微服务 service-A 和 service-B, 定义了两个路由 API-a 和 API-b 来分别映射. 另外为了让 Zuul 能发现 service-A 和 service-B, 也加入了 eureka 的配置.
- 接下来, 我们将 eureka-server,service-A,service-B 以及这里用 Zuul 实现的服务网关启动起来, 在 eureka-server 的控制页面中, 我们可以看到分别注册了 service-A,service-B 以及 API-gateway
- 尝试通过服务网关来访问 service-A 和 service-B, 根据配置的映射关系, 分别访问下面的 url
- http://localhost:4444/API-a/add?a=1&b=2
- : 通过 serviceId 映射访问 service-A 中的 add 服务
- http://localhost:4444/API-b/add?a=1&b=2
- : 通过 serviceId 映射访问 service-B 中的 add 服务
- http://localhost:4444/API-a-url/add?a=1&b=2
- : 通过 url 映射访问 service-A 中的 add 服务 (本示例并没有使用 url 映射, 可以在配置文件里面去掉 serviceId 映射配置, 添加上 url 映射)
- 推荐使用 serviceId 的映射方式, 除了对 Zuul 维护上更加友好之外, serviceId 映射方式还支持了断路器, 对于服务故障的情况下, 可以有效的防止故障蔓延到服务网关上而影响整个系统的对外服务
- 服务过滤
- 在完成了服务路由之后, 我们对外开放服务还需要一些安全措施来保护客户端只能访问它应该访问到的资源. 所以我们需要利用 Zuul 的过滤器来实现我们对外服务的安全控制.
- 在服务网关中定义过滤器只需要继承 ZuulFilter 抽象类实现其定义的四个抽象函数就可对请求进行拦截与过滤.
- 比如下面的例子, 定义了一个 Zuul 过滤器, 实现了在请求被路由之前检查请求中是否有 accessToken 参数, 若有就进行路由, 若没有就拒绝访问, 返回 401 Unauthorized 错误.
- package com;
- import javax.servlet.http.HttpServletRequest;
- import com.netflix.zuul.ZuulFilter;
- import com.netflix.zuul.context.RequestContext;
- public class AccessFilter extends ZuulFilter{
- public Object run() {// 过滤器的具体逻辑
- // TODO Auto-generated method stub
- RequestContext ctx = RequestContext.getCurrentContext();
- HttpServletRequest request = ctx.getRequest();
- Object accessToken = request.getParameter("accessToken");
- if(accessToken == null) {
- ctx.setSendZuulResponse(false);// 不进行路由
- ctx.setResponseStatusCode(401);
- return null;
- }
- return null;
- }
- public boolean shouldFilter() {// 返回一个 boolean 类型来判断该过滤器是否要执行, 所以通过此函数可实现过滤器的开关
- // TODO Auto-generated method stub
- return true;//true 总的是执行
- }
- @Override
- public int filterOrder() {// 过滤的执行顺序
- // TODO Auto-generated method stub
- return 0;
- }
- /* (non-Javadoc)
- * @see com.netflix.zuul.ZuulFilter#filterType()
- *
- pre: 可以在请求被路由之前调用
- routing: 在路由请求时候被调用
- post: 在 routing 和 error 过滤器之后被调用
- error: 处理请求时发生错误时被调用
- */
- @Override
- public String filterType() {// 过滤器的执行时间
- // TODO Auto-generated method stub
- return "pre";
- }
- }
自定义过滤器的实现, 需要继承 ZuulFilter, 需要重写实现下面四个方法:
filterType: 返回一个字符串代表过滤器的类型, 在 zuul 中定义了四种不同生命周期的过滤器类型, 具体如下:
pre: 可以在请求被路由之前调用
routing: 在路由请求时候被调用
post: 在 routing 和 error 过滤器之后被调用
error: 处理请求时发生错误时被调用
filterOrder: 通过 int 值来定义过滤器的执行顺序
shouldFilter: 返回一个 boolean 类型来判断该过滤器是否要执行, 所以通过此函数可实现过滤器的开关. 在上例中, 我们直接返回 true, 所以该过滤器总是生效.
run: 过滤器的具体逻辑. 需要注意, 这里我们通过
ctx.setSendZuulResponse(false)
令 zuul 过滤该请求, 不对其进行路由, 然后通过
ctx.setResponseStatusCode(401)
设置了其返回的错误码, 当然我们也可以进一步优化我们的返回, 比如, 通过
ctx.setResponseBody(body)
对返回 body 内容进行编辑等.
http://localhost:4444/API-a/add?a=1&b=2 没有进行路由
http://localhost:4444/API-a/add?a=1&b=2&accessToken 进行路由
获取源码: 3--SpringCloud 网关 zuul https://gitee.com/cengjiang/springcloud_learning
来源: https://www.cnblogs.com/GH0522/p/9896697.html