由于每一个微服务的地址都有可能发生变化,无法直接对外公布这些服务地址,基于安全以及高内聚低耦合等设计,我们有必要将内部系统和外部系统做一个切割。需要一个专门用来处理外部请求的组件,就是服务网关。API网关的的定义类似于面向对象设计模式中的Facade模式,像是整个微服务架构系统的门面一样,所有的外部客户端访问都需要经过它来调度和过滤。API网关需要实现请求路由,负载均衡,校验过滤,请求转发时的熔断机制,服务的聚合等功能。
Spring Cloud 中,API网关主要有两种实现方案:Zuul和Spring Cloud Gateway。Spring Cloud Zuul是基于Neflix Zuul实现的API网关组件。Zuul 具备的功能:
Zuul 中的功能基本上都是基于过滤器来实现,它的过滤器有几种不同的类型,分别是:
沿用之前文章所创建的Eureka服务和Provider服务。Spring Cloud框架学习-注册中心Spring Cloud Eureka
新创建名为Zuul的Spring Boot项目,使用Spring Boot版本2.2.4,pom.xml添加如下依赖:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<parent>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-parentartifactId>
<version>2.2.6.RELEASEversion>
<relativePath/>
parent>
<groupId>top.javahai.spring.cloudgroupId>
<artifactId>zuulartifactId>
<version>0.0.1-SNAPSHOTversion>
<name>zuulname>
<description>Demo project for Spring Bootdescription>
<properties>
<java.version>1.8java.version>
<spring-cloud.version>Hoxton.SR6spring-cloud.version>
properties>
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-eureka-clientartifactId>
dependency>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-netflix-zuulartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-testartifactId>
<scope>testscope>
dependency>
dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>${spring-cloud.version}version>
<type>pomtype>
<scope>importscope>
dependency>
dependencies>
dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
project>
在启动类上添加注解@EnableZuulProxy开启Zuul的API网关代理:
@SpringBootApplication
@EnableZuulProxy
public class ZuulApplication {
public static void main(String[] args) {
SpringApplication.run(ZuulApplication.class, args);
}
}
在application.properties中配置当前Zuul服务的基础信息
#服务名
spring.application.name=zuul
#服务端口号
server.port=2020
#注册中心url地址
eureka.client.service-url.defaultZone=http://localhost:1111/eureka
启动注册中心,provider服务和zuul服务,在注册中心管理界面查看服务是否启动成功
然后请求地址:http://localhost:2020/v1/provider/hello,通过Zuul的代理可以访问到provider服务。
在application.properties中配置路由规则
zuul.routes.provider.path=/provider-test/**
zuul.routes.provider.service-id=provider
通过上面的配置定义了满足/provider-test/**这个匹配规则的请求,将会转发到provider服务上处理。
上面配置路由规则的两行配置还可以简化成一行配置
zuul.routes.provider=/provider-test/**
通过以下配置可以实现满足规则的请求路由转发到具体的URL上。其中api-test-url为路由名,一组path和url映射关系的路由名必须相同。
zuul.routes.api-test-url.path=/api-test/**
zuul.routes.api-test-url.url=http://localhost:8080/
每个客户端用户请求微服务应用提供的接口时,它们的访问权限往往都有一定的限制,系统不会将所有的微服务接口都对它们开放。这就需要要网关中统一实现请求的权限校验。在Zuul中我们只需要继承ZuulFilter抽象类并实现它定义的4个抽象方法就可以完成对请求的拦截和过滤了。
下面实现一个简单的Zuul过滤器,实现在请求过滤之前检查HttpServletRequest中是否有username和password参数且是否满足要求。如满足则进行路由,否则就拒绝访问,返回401错误。
//注册到Spring容器中
@Component
public class PermissionFilter extends ZuulFilter {
/**
* 过滤器类型,它决定过滤器在请求的哪个生命周期中执行。
* 权限判断一般是pre,pre代表在请求被路由之前执行
* @return
*/
@Override
public String filterType() {
return "pre";
}
/**
* 过滤器的执行顺序。当请求一个阶段存在多个过滤器时,需要根据该方法返回的值来依次执行
* @return
*/
@Override
public int filterOrder() {
return 0;
}
/**
* 判断该过滤器是否需要执行。可以通过该方法来指定过滤器的有效范围
* @return
*/
@Override
public boolean shouldFilter() {
return true;
}
/**
* 核心过滤逻辑
* @return
* @throws ZuulException
*/
@Override
public Object run() throws ZuulException {
RequestContext ctx = RequestContext.getCurrentContext();
//获取当前请求
HttpServletRequest request = ctx.getRequest();
String username = request.getParameter("username");
String password = request.getParameter("password");
//如果请求条件不满足的话,直接从这里给出响应
if (!"admin".equals(username) || !"123".equals(password)) {
//设置不进行路由
ctx.setSendZuulResponse(false);
ctx.setResponseStatusCode(401);
ctx.addZuulResponseHeader("content-type","text/html;charset=utf-8");
ctx.setResponseBody("非法访问");
}
return null;
}
}
重启Zuul接下来发送请求就必须带上username 和 password 参数,否则请求不通过。
带上username 和 password 参数发送请求
默认情况下,Zuul 注册到 Eureka 上之后,Eureka 上的所有注册服务都会被自动代理。如果不想给某
一个服务做代理,可以忽略该服务,配置如下:
zuul.ignored-services=provider
上面这个配置表示忽略 provider 服务,此时就不会自动代理 provider 服务了
忽略某一类地址:
zuul.ignored-patterns=/**/hello/**
这个表示请求路径中如果包含 hello,则不做代理
我们可以通过zuul.prefix参数全局地为路由规则增加前缀信息,给路由添加前缀的配置示例:
zuul.prefix=/v1
路径匹配的表达式采用了Ant风格定义,Ant风格的路径表达式一共有三种通配符。分别是:
通配符 | 说明 |
---|---|
? | 匹配任意单个字符 |
* | 匹配任意数量的字符,不支持多级目录(例如/user-service/*无法匹配/user-service/a/b) |
** | 匹配任意数量的字符,支持多级目录 |
#设置API网关中路由转发请求的hystrixCommand执行超时时间,单位为毫秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=
#设置路由转发请求的时候,创建请求连接的超时时间
ribbon.ConnectTimeOut=
#设置路由转发请求的超时时间
ribbon.ReadTimeout=
参考:
1.《Spring Cloud微服务实战》