大家都都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去用。
这样的架构,会存在着诸多的问题:
上面的这些问题可以借助API网关来解决。
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户说提供统一服务,一些与业务本身功能天关的公共逻辑可以在这里实现。诸如认证、鉴权、监控、路由转发等等,添加上API网关之后,系统的架构图变成了如下所示:
网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。
Sping Cloud cGateway是Sping Clou官方推出的第二代网关框架,定位于取代NeificZul。
相比Zul来说,Spring Cloud Gateway提供更优秀的性能,更强大的有功能。
Spring Cloud Gateway是由WebFlux + Netty + Reactor实现的响应式的API网关。它不能在传统的servlet容器中工作,也不能构建成war包。
Sping Cloud Gateway旨在为微服务架构提供一种简单且有效的API路由的管理方式,并基于Fiter的方式提供网关的基本功能,例如说安全认证、监控、限流等等。
官网文档: https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
路由(route)
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Filter组成。如果断言为真,则说明请求的URL和配置的路由匹配。
断言(predicates)
Java3中的断言函数,SpringCloud Gateway中的断言函数类型是Sping5.0框架中的SeverieExctange。断言函数允许开发者去定义匹酷t request中的任何信息,比加如请求头和参数等。
过滤器(Filter)
SpringCloud Gateway中的filter分为Gateway Filler和Global Filter。Filter可以对请求和响应进行处理
Spring Cloud Gateway的工作原理跟Zuul的差不多,最大的区别就是Gateway的Filter只有pre和post 两种。
客户端向Spring Cloud Gateway发出请求,如果请求与网关程序定义的路由匹配,则该请求就会被发送到网关Web处理程序,此时处理程序运行特定的请求过滤器链。过滤器之间用虚线分开的原因是过滤器可能会在发送代理请求的前后执行逻辑。所有pre过滤器逻辑先执行,然后执行代理请求;代理请求完成后,执行post过滤器逻辑。
注意:会和spring-webmvc的依赖冲突,需要排除spring-webmvc
<dependencies>
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-starter-gatewayartifactId>
dependency>
dependencies>
server:
port: 8088
spring:
application:
name: api-gateway
# gateway的配置
cloud:
gateway:
routes:
- id: order_route #路由的唯一标识,路由到order
uri: http://localhost:8020 #需要转发的地址
#断言规则 用于路由规则的匹配
predicates:
- Path=//
# http://localhost:8088/order-serve/order/add 路由到↓
# http://localhost:8020/order-serve/order/add
filters:
- StripPrefix=1 # 转发之前,去掉第一次的路径
# http://localhost:8020/order/add
#- id: stock_route
访问:http://localhost:8088/order-serve/order/add
<dependency>
<groupId>com.alibaba.cloudgroupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
dependency>
server:
port: 8088
spring:
application:
name: api-gateway
# gateway的配置
cloud:
gateway:
routes:
- id: order_route #路由的唯一标识,路由到order
uri: lb://order-service # 需要转发的地址 lb指的是从nacos中按照名称获取微服务,并遵循负载均衡策略 order-service服务名
#断言规则 用于路由规则的匹配
predicates:
- Path=/order-serve/**
# http://localhost:8088/order-serve/order/add 路由到↓
# http://localhost:8020/order-serve/order/add
filters:
- StripPrefix=1 # 转发之前,去掉第一次的路径
# http://localhost:8020/order/add
#- id: stock_route
# 配置Nacos
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
重新运行并访问:
http://localhost:8088/order-service/order/add
设置约定集成nacos
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
# gateway的配置
gateway:
discovery:
locator:
enabled: true #是否启动自动识别nacos服务
#配置Nacos
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
重新启动项目
访问服务名对应的地址:http://localhost:8088/order-service/order/add
重新启动项目
访问:http://localhost:8088/order/add
http://localhost:8088/order/add
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories
作用:当请求gateway的时候, 使用断言对请求进行匹配, 如果匹配成功就路由转发,如果匹配失败就返回404
类型:内置,自定义
SpringCloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。具体如下:
此类型的断言根据时间做判断,主要有三个:
AfterRoutePredicateFactory:接收一个日期参数, 判断请求日期是否晚于指定日期
BeforeRoutePredicateFactory:接收- 个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内
- After=2023-10-19T09:07:00.660+08:00[Asia/Shanghai]
- RemoteAddr=192.168.1.1/24
-Cookie=chocolate, ch.
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。判断请求Header是否具有给定名称且值与正则表达式匹配。
- Header=X-Request-Id, \d+
重新启动项目
利用API工具发送请求
把Headers当中的请求参数取消重新发起请求,会报错
HostRoutePredicateFactory:接收一个参数, 主机名模式。判断请求的Host是否满足匹配规则。
-Host=**.testhost.org
MethodRoutePredicateFactory:接收一个参数, 判断请求类型是否跟指定的类型匹配。
重新启动测试
通过GET发送请求
通过POST发起请求
- Query=name
重新启动发起请求,没有设置name参数报错
设置name参数
设置指定参数
- Query=name,xushu|zhuge
设置name的参数只能是xushu或者zhuge否则断言失败
发送请求报错
将参数改为xushu请求成功,改为zhuge也请求成功
WeightRoutePredicateFactory:接收-个[组名 权重],然后对于同-一个组内的路由按照权重转发
routes:
-id: weight_ route1
uri: host1
predicates:
-Path=/ product/**
-Weight=group3,1
-id: weight_ route2
uri: host2
predicates:
- Path=/ product/**
-Weight= group3, 9
自定义路由断言工厂需要继承AbstractRoutePredicateFactory类,重写apply方法的逻辑。
在apply方法中可以通过exchange. getRequest()倒ServerHttpRequest对象,从而可以获取到请求的参数、请求方式、请求头等信息。
1、必须是Spring组件bean
2、类必须加上RoutePredicateFactory作为结尾
3、必须继承AbstractRoutePredicateFactory
4、必须声明静态的内部类 声明属性来接受配置文件当中配置的断言信息
5、需要结合shortcutFieldOrder进行绑定
6、通过apply方法进行逻辑判断 true 就是匹配成功 false就是匹配失败
注意:命名需要以RoutePredicateFactory结尾
@Component
public class CheckAuthRoutePredicateFactory
extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(CheckAuthRoutePredicateFactory.Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}
@Override
public Predicate<ServerWebExchange> apply(CheckAuthRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
@Override
public boolean test(ServerWebExchange exchange) {
if(config.getName().equals("xushu")){
return true;
}
return false;
}
};
}
// 用于接收配置文件中 断言的信息
@Validated
public static class Config {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}
重新启动项目
访问:http://localhost:8088/order/add
修改对应的断言名称
查询启动
http://localhost:8088/order/add
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayilter-factories
filters:
- AddRequestHeader=X-Request-color,red
@RequestMapping("/header")
public String header(@RequestHeader("X-Request-color") String color){
return color;
}
http://localhost:8088/order/header
- AddRequestParameter=color,blue
@RequestMapping("/parameter")
public String parameter(@RequestParam("color") String color){
return color;
}
http://localhost:8088/order/parameter
servlet:
context-path: /mall-order
- PrefixPath=/mall-order #添加前缀对应微服务需要配置context-path
重新启动两个项目
访问:http://localhost:8088/order/add
现在访问:http://localhost:8020/mall-order/order/add
然而访问8082必须携带前缀
http://localhost:8020/mall-order/order/add
- RedirectTo=302, https://www.baidu.com
访问:http://localhost:8088/order/add
重定向到了百度
- SetStatus= 404
访问:http://localhost:8088/order/add
虽然访问成功了,但是返回的状态码为404
继承AbstractNameValueGatewayFilterFactory且我们的自定义名称必须要以GatewayFilterFactory结尾并交给spring管理。
创建CheckAuthGatewayFilterFactory
/***
*/
@Component
public class CheckAuthGatewayFilterFactory
extends AbstractGatewayFilterFactory<CheckAuthGatewayFilterFactory.Config> {
public CheckAuthGatewayFilterFactory() {
super(Config.class);
}
@Override
public List<String> shortcutFieldOrder() {
return Arrays.asList("value");
}
@Override
public GatewayFilter apply(Config config) {
return new GatewayFilter() {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String name=exchange.getRequest().getQueryParams().getFirst("name");
if(StringUtils.isNotBlank(name)){
if(config.getValue().equals(name)){
return chain.filter(exchange);
}
else {
// 返回404
exchange.getResponse().setStatusCode(HttpStatus.NOT_FOUND);
return exchange.getResponse().setComplete();
}
}
// 正常请求
return chain.filter(exchange);
}
};
}
public static class Config {
private String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
}
- CheckAuth=xushu
局部:针对某个路由,需要在路由中进行配置
全局:针对所有路由请求,一旦定义就会投入使用
GlobalFilter接口和GatewayFilter有一样的接口定义, 只不过,GlobalFilter 会作用于所有路由。
@Component
public class LogFilter implements GlobalFilter {
Logger log= LoggerFactory.getLogger(this.getClass());
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
log.info(exchange.getRequest().getPath().value());
return chain.filter(exchange);
}
}
重新启动:http://localhost:8088/order/add?name=xushu
要启用Reactor Netty访问日志,请设置
-Dreactor.netty.http.server.accessLogEnabled=true
它必须是Java系统属性,而不是Spring Boot属性。
您可以将日志记录系统配置为具有单独的访问日志文件。以下示例创建一个Logback配置:
<appender name=" accessLog" class="ch.qos.logback.core.FileAppender">
<file>access_log.logfile>
<encoder>
<pattern>%msg%npattern>
encoder>
appender>
<appender name=" async" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="accessLog" />
appender>
<logger name="reactor.netty.http.server.AccessLog" level="INFO" additivity="false">
<appender-ref ref=" async"/>
logger>
通过ym|配置的方式
https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#cors-configuration
globalcors:
cors-configurations:
'[/**]': # 允许跨域访问的资源
allowedOrigins: "*" #跨域允许的来源 例如:www.smsm.com
allowedMethods:
- GET
- POST
- PUT
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="http://apps.bdimg.com/libs/jquery/1.9.1/jquery.min.js">script>
head>
<body>
<div >
<table border="1">
<thead>
<tr>
<th>idth>
<th>usernameth>
<th>ageth>
tr>
thead>
<tbody id="userlist">
tbody>
table>
div>
<input type="button" value="用户列表" onclick="getData()">
<script>
function getData() {
$.get('http://localhost:8088/order/add',function(data){
alert(data)
});
}
script>
body>
html>
allowedOrigins: "localhost:8088" #跨域允许的来源 例如:www.smsm.com
再次访问访问网页(抛出跨域异常)
设置*运行所有的来源访问
Spring自带的跨域方式
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*"); //允许运行method
config.addAllowedOrigin("*"); //允许的来源
config.addAllowedHeader("*"); //允许的请求头参数
// 允许访问的资源
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**",config);
return new CorsWebFilter(source);
}
}