Gateway核心概念
路由(Route)
是gateway中最基本的组件之一,表示一个具体的路由信息载体,由ID、目标URI、Predicate集合、Filter集合组成。主要定义了下面的几个信息:
- id,路由标识符,区别于其他 Route。
- uri,路由指向的目的地 uri,即客户端请求最终被转发到的微服务。
- order,用于多个 Route 之间的排序,数值越小排序越靠前,匹配优先级越高。
- predicate,断言的作用是进行条件判断,只有断言都返回真,才会真正的执行路由。
- fifilter,过滤器用于修改请求和响应信息。
断言(Predicate)
是Java8中引入的函数式接口,提供了断言的功能,它可以匹配Http请求中的任何内容。只有断言都返回真,才会真正的执行路由。
过滤器(Filter)
过滤器就是在请求的传递过程中,为请求提供前置和后置的过滤
执行流程大体如下:
Gateway Client向Gateway Server发送请求
请求首先会被HttpWebHandlerAdapter进行提取组装成网关上下文
然后网关的上下文会传递到DispatcherHandler,它负责将请求分发给RoutePredicateHandlerMapping
RoutePredicateHandlerMapping负责路由查找,并根据路由断言判断路由是否可用
如果过断言成功,由FilteringWebHandler创建过滤器链并调用
请求会一次经过PreFilter–微服务-PostFilter的方法,最终返回响应
测试项目
一、 自定义一个断言工厂,匹配规则为请求参数中count和money字段都存在,且两者值的乘积小于1000大于0。
创建网关项目访问微服务
创建一个父项目,pom文件中添加常见的依赖版本。
<!-- 统一管理jar包版本 --> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <junit.version>4.12</junit.version> <log4j.version>1.2.17</log4j.version> <lombok.version>1.16.18</lombok.version> <mysql.version>8.0.15</mysql.version> <druid.version>1.1.16</druid.version> <mybatis.spring.boot.version>1.3.0</mybatis.spring.boot.version> </properties> <dependencyManagement> <dependencies> <!--spring boot 2.2.2--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>2.2.2.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <!--spring cloud Hoxton.SR1--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR1</version> <type>pom</type> <scope>import</scope> </dependency> <!--spring cloud alibaba 2.1.1.RELEASE--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.1.1.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.70</version> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>${mybatis.spring.boot.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>${junit.version}</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>${log4j.version}</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <version>${lombok.version}</version> <optional>true</optional> </dependency> </dependencies> </dependencyManagement>创建Gateway5001模块,pom文件中引入gateway依赖
注意:gateway使用的netty+webflux实现,不要加入web依赖(不要引用webmvc),否则初始化会报错 ,需要加入webflux依赖。
<dependencies> <!--gateway--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>创建主类
@SpringBootApplication public class GatewayMain5001 { public static void main(String[] args) { SpringApplication.run(GatewayMain5001.class,args); } }添加配置文件
server: port: 5001 spring: application: name: springcloud-gateway cloud: gateway: routes: - id: order_route #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:8001 #匹配后提供服务的路由地址 predicates: #匹配后提供服务的路由地址 - Path=/gateway/order # 断言,路径相匹配的进行路由 filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改 - StripPrefix=1 # 转发之前去掉1层路径新建一个Order8001模块,引入springbootweb起步依赖,新建controller类用于测试
@RestController public class Mycontroller { @RequestMapping("/order") public String order(){ return "访问到了/order资源"; } }
6.启动两个项目进行测试,浏览器访问localhost:5001/gateway/order地址,成功访问到了Order8001服务

自定义断言工厂
在Gateway5001网关配置中的断言添加- Count=0,1000
spring: application: name: springcloud-gateway cloud: gateway: routes: - id: order_route #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:8001 #匹配后提供服务的路由地址 predicates: #匹配后提供服务的路由地址 - Path=/gateway/order # 断言,路径相匹配的进行路由 - Count=0,1000 #自定义断言工厂 filters: # 过滤器,请求在传递过程中可以通过过滤器对其进行一定的修改 - StripPrefix=1 # 转发之前去掉1层路径自定义一个断言工厂类,继承AbstracRoutePredicateFactory类,实现断言方法
注意断言工厂类的名称要在路由配置中的自定义断言名称+RoutePredicateFactory。如在这里就应该为CountRoutePredicateFactory
@Component
public class CountRoutePredicateFactory extends AbstractRoutePredicateFactory<CountRoutePredicateFactory.Config> {
//构造函数
public CountRoutePredicateFactory() {
super(CountRoutePredicateFactory.Config.class);
}
//读取配置文件中的参数值,并赋值到配置类的属性上
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("min", "max");
}
//断言逻辑
@Override
public Predicate<ServerWebExchange> apply(CountRoutePredicateFactory.Config config) {
return new Predicate<ServerWebExchange>() {
@Override
public boolean test(ServerWebExchange ex) {
String money = ex.getRequest().getQueryParams().getFirst("money");
String count = ex.getRequest().getQueryParams().getFirst("count");
if (!StringUtils.isEmpty(count) && !StringUtils.isEmpty(money)) {
int res = Integer.parseInt(money) * Integer.parseInt(count);
return res > 0 && res < 1000;
}
return false;
}
};
}
//配置类,接受配置文件中的对应参数
@Data
@NoArgsConstructor
public static class Config {
private int min;
private int max;
}
}
在网页上输入localhost:5001/gateway/order?money=1&count=1000 因为money*count=1000,不符合自定义断言规则,故无法访问

输入localhost:5001/gateway/order?money=1&count=999,成功访问,测试自定义断言工厂成功

二、自定义全局过滤器解决跨域问题
什么是跨域
出于浏览器的同源策略限制。浏览器从一个域名的网页去请求另一个域名的资源时,域名、端口、协议任一不同,都是跨域。此时浏览器会阻止读取后端传来的数据。
在前后端分离的模式下,前后端的域名是不一致的,此时就会发生跨域访问问题。下面列出在gateway中解决跨域访问问题两种方法。
在gateway端增加CorsFilter过滤器
@Component
public class CorsWebFilter implements WebFilter {
private static final String ALL = "*";
private static final String MAX_AGE = "18000L";
@Override
public Mono<Void> filter(ServerWebExchange ctx, WebFilterChain chain) {
ServerHttpRequest request = ctx.getRequest();
String path = request.getPath().value();
ServerHttpResponse response = ctx.getResponse();
if ("/favicon.ico".equals(path)) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
if (!CorsUtils.isCorsRequest(request)) {
return chain.filter(ctx);
}
HttpHeaders requestHeaders = request.getHeaders();
HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod();
HttpHeaders headers = response.getHeaders();
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin());
headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());
if (requestMethod != null) {
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name());
}
headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, ALL);
headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE);
if (request.getMethod() == HttpMethod.OPTIONS) {
response.setStatusCode(HttpStatus.OK);
return Mono.empty();
}
return chain.filter(ctx);
}
}
修改Gataway配置文件
CorsFilter已经在Gateway里了,不需要写代码实现,直接修改配置文件即可
spring:
application:
name: springcloud-gateway
cloud:
gateway:
globalcors:
corsConfigurations:
'[/**]':
allowedOrigins: "*"
allowedMethods: "*"
allowedHeaders: "*"