GateWay—网关介绍
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能天关的公共逻辑可以在这里实现,诸如认证、签权、监控、路由转发等等。添加上API网关之后,系统的架构图变成了如下所示:
优点:
整体微服务结构图,网关Gateway的位置
GateWay介绍
网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。
Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,定位于取代NetflexZuul 1.0。相比Zuul来说,Spring Clout Gateway)提供更优秀的性能,更强大的有功能
Spring Cloud Gateway是由WebFlux + Netty + Reactor实现的响应式的APl网关。它不能在传统的 servlet容器中工作,也不能构建成war包.
Spring Clout Gateway旨在为微服务架构提供一种简单且有效的API路由的管理方式,并基于Filter的方式提供网关的基本功能,例如说安全认证、监控、限流等等。
功能特征
- 基于Spring Framework 5,Project Reactor和 Spring Boot 2.0进行构建;
- 动态路由:能够匹配任何请求属性;
- 支持路径重写;
- 集成 Spring Cloud服务发现功能(Nacos、Eruka) ;
- 可集成流控降级功能(Sentinel、Hystrix) ;
- 可以对路由指定易于编写的 Predicate (断言)和Filter(过滤器);
核心概念:
- 路由
路由是网关中最基础的部分,路由信息包括一个ID、一个目的URI、一组断言工厂、一组Fiter组成。如果断言为真,则说明请求的URL和配置的路由匹配.
- 断言(predicates)
Java8中的断言函数,SprigCloud Gateway中的断言函数类型是Sping5 o框架中的SererWebExchange。断言函数允许开发者去定义匹配Http request中的任何信息,比如请求头和参数等。
- 过滤器(Fllter)
SpringCloud Gateway中的filter分为Gateway Filler和Global Filter。Filter可以对请求和响应进行处理。
GateWay—初体验
1.创建新的子项目gateway 引入依赖
<!-- gateway的依赖 springcloud开发 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
2.编写配置文件
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
#gateway的配置
gateway:
#路由配置
routes:
- id: order_route #路由的唯一标识,路由到order
uri: http://localhost:8020 #需要转发的地址
#断言规则 用于路由规则的匹配
predicates:
- Path=/order-serv/**
# 请求 http://localhost:8088/order-serv/order/add路由到
# http://localhost:8020/order-serv/order/add
filters:
- StripPrefix=1 #转发之前去掉第一层路径
# http://localhost:8020/order/add
3.主启动类启动
@SpringBootApplication
public class GatewayApplication {
public static void main(String[] args) {
SpringApplication.run(GatewayApplication.class,args);
}
}
访问之前启动 order-nacos 和 stock-nacos模块
order-nacos模块 controller代码
@RestController
@RequestMapping("/order")
public class OrderController {
@Autowired
RestTemplate restTemplate;
@RequestMapping("/add")
public String add(){
System.out.println("下单成功!");
String msg = restTemplate.getForObject("http://stock-service/stock/reduct", String.class);
return "hello world"+msg;
}
}
stock-nacos模块 controller代码
@RestController
@RequestMapping("/stock")
public class StockController {
@Value("${server.port}")
String port;
@RequestMapping("/reduct")
public String reduct(){
System.out.println("扣减库存");
return "扣减库存"+port;
}
}
三个模块全部启动
测试访问 http://localhost:8088/order-serv/order/add
页面显示 hello world扣减库存8021
GateWay整合nacos
现在在配置文件中写死了转发路径的地址,前面我们已经分析过地址写死带来的问题,接下来我们从注册中心获取此地址。
集成Nacos
1.再引入依赖
<!-- nacos服务注册发现 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.编写yml配置文件
server:
port: 8088
spring:
application:
name: api-gateway
cloud:
#gateway的配置
gateway:
#路由配置
routes:
- id: order_route #路由的唯一标识,路由到order
uri: lb://order-service #需要转发的地址 lb 使用nacos本地负载均衡策略 order-service 服务名
predicates:
- Path=/order-serv/**
filters:
- StripPrefix=1
#配置Nacos
nacos:
discovery:
server-addr: 127.0.0.1:8848
username: nacos
password: nacos
测试访问 http://localhost:8088/order-serv/order/add
页面显示 hello world扣减库存8021
简写形式
- 比较少用
- 自己去配置断言和过滤器比较好
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 用的是服务名
页面显示 hello world扣减库存8021
GateWay—内置路由断言工厂
作用 : 当请求gateway的时候,使用断言对请求进行匹配,如果匹配成功就路由转发,如果匹配失败就返回404
类型 内置 、自定义
- 基于Datetime类型的断言工厂
此类型的断言根据时间做判断,主要有三个:
AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期
BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期
BetweenRoutePredicateFactory:接收两个日期参数,判断请求日期是否在指定时间段内
predicates:
- Path=/order-serv/**
- After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]
- 基于远程地址的断言工厂
RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中
- RemoteAddr=192.168.1.1/24
- 基于Cookie的断言工厂
CookieRoutePredicateFactory: 接收两个参数,cookie名字和一个正则表达式。判断请求cookie是否具有给定名称且值与正则表达式匹配。
- Cookie=chocolate,ch.
- 基于Header的断言工厂
HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。判断请求Header是否员有给定名称且值与正则表达式匹配.
- Header=X-Request-Id,\d+
- 基于Host的断言工厂
HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则
- Host=**.testhost.org
- 基于Method请求方法的断言工厂
MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。
- Method=GET
- 基于Path请求路径的断言工厂
PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则,
- Path=/foo/{segment}
- 基于Query请求参数的断言工厂
- Query=name,xushu|zhuge
#要有name参数 而且要等于xushu或者zhuge
GateWay—自定义路由断言工厂
自定义路由断言工厂需要继承AbstractRoutePredicateFactory
类,重写 apply 方法的逻辑。在apply方法中可以通过 exchange.getRequest()拿到ServerhttptRequest对象,从而可以获取到请求的参数、请求方式、请求头等信息.
1.必须spring组件 bean
2.类必须加上RoutePredicateFactory
作为结尾
3.必须继承AbstractRoutePredicateFactory
4.必须声明静态内部类 声明属性来接受 配置文件中的对应的断言的信息
5.需要结合shortcutFieldOrder
进行绑定
6.通过apply进行逻辑判断 true就是匹配成功 false匹配失败
写个自定义参数叫CheckAuth 值必须是xushu才可以访问
package com.tian.predicates;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import javax.validation.constraints.NotEmpty;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.function.Predicate;
@Component
public class CheckAuthRoutePredicateFactory extends AbstractRoutePredicateFactory<CheckAuthRoutePredicateFactory.Config> {
public CheckAuthRoutePredicateFactory() {
super(CheckAuthRoutePredicateFactory.Config.class);
}
public List<String> shortcutFieldOrder() {
return Arrays.asList("name");
}
public Predicate<ServerWebExchange> apply(CheckAuthRoutePredicateFactory.Config config) {
return new GatewayPredicate() {
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;
}
}
}
配置文件
predicates:
#- Path=/order-serv/**
- Path=/order/**
- CheckAuth=xushu
#- CheckAuth=xushu2
访问 http://localhost:8088/order/add xushu就可以正确访问,xushu2页面就会报404
GateWay—局部(内置、自定义)过滤器
添加请求头
#gateway模块配置文件
spring:
cloud:
gateway:
routes:
- id: order_route
uri: http://localhost:8020
filters:
- AddRequestHeader=X-Request-color,red #添加请求头
order-nacos模块controller
@RequestMapping("/header")
public String header(@RequestHeader("X-Request-color") String color){
return color;
访问 http://localhost:8088/order/header 页面显示 red
自定义过滤器工厂
- 和断言工厂差不多
配置文件
filters:
- CheckAuth=fox,男
访问输出CheckAuthGatewayFilterFactory===fox: 男
GateWay—全局过滤器
局部过滤器和全同过滤器区别:
局部:局部针对某个路由,需要在路由中进行配置
全局:针对所有路由请求,你定义就会投入使用
自定义全局过滤器
@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);
}
}
GateWay—请求日记记录&跨域处理
要启用Reactor Netty 访问日志,请设置
-Dreactor.netty.http.server.accessLogEnabled=true.
重启 控制台就会打印输出日志
它必须是Java系统属性,而不是 Spring Boot属性。
跨域问题
配置文件application.yml
#跨域配置
spring:
cloud:
gateway:
globalcors:
cors-configurations:
'[/**]': #允许跨域访问的资源
allowedOrigins: "https://docs.spring.io" #跨域允许来源
allowedMethods:
- GET
- POST
配置类配置
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsWebFilter;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import org.springframework.web.util.pattern.PathPatternParser;
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsWebFilter(){
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*"); //允许的method
config.addAllowedOrigin("*"); //允许的来源
config.addAllowedHeader("*"); //允许的请求头参数
// 运行访问的资源
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**",config);
return new CorsWebFilter(source);
}
}
GateWay—整合Sentinel流控降级
api-gateway模块
1.添加依赖
<!-- sentinel整合gateway -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-sentinel-gateway</artifactId>
</dependency>
<!-- sentinel的依赖 -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
2.配置文件
spring:
cloud:
#配置sentinel
sentinel:
transport:
dashboard: 127.0.0.1:8888
3.启动sentinel 控制台配置规则
4.访问
http://localhost:8088/order/add
现象 前两次正常页面显示 第三次页面显示
Blocked by Sentinel: ParamFlowException
GateWay—整合Sentinel流控降级详细配置
API管理
降级
处理方法(在降级规则那配置)
自定义异常
- 让页面显示自定义异常信息
package com.tian.config;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserter;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.PostConstruct;
import java.util.HashMap;
@Configuration
public class GatewayConfig {
@PostConstruct
public void init(){
BlockRequestHandler blockRequestHandler=new BlockRequestHandler() {
@Override
public Mono<ServerResponse> handleRequest(ServerWebExchange serverWebExchange, Throwable throwable) {
HashMap<String, String> map = new HashMap<>();
map.put("code",HttpStatus.TOO_MANY_REQUESTS.toString());
map.put("message","限流了");
//自定义异常处理
return ServerResponse.status(HttpStatus.OK)
.contentType(MediaType.APPLICATION_JSON)
.body(BodyInserters.fromValue(map));
}
};
GatewayCallbackManager.setBlockHandler(blockRequestHandler);
}
}
或 yml配置
spring:
cloud:
sentinel:
scg:
fallback:
mode: response
response-body: "{code:'',message:''}"