1. 什么是API网关
API网关:是一个服务器,是系统的唯一入口。同时也可以实现服务的路由、负载均衡、鉴权、限流、熔断等功能。
API网关出现的原因是微服务架构的出现,不同的微服务一般会有不同的网络地址,而外部客户端可能需要调用多个服务的接口才能完成一个业务需求,如果让客户端直接与各个微服务通信,会有以下的问题:
客户端请求多个微服务,各个服务ip不一样,增加客户端复杂度。
存在跨域,在一定场景下处理相对复杂。
认证复杂,每个服务都需要独立认证。
与微服务耦合太强,微服务变更,客户端需要变更
2. 使用API网关的好处
所有的外部请求都会先经过API 网关这一层。也就是说,API 的实现方面更多的考虑业务逻辑,而安全、性能、监控可以交由 API 网关来做,这样既提高业务灵活性又不缺安全性。
- 易于监控
- 统一认证
- 减少客户端与微服务交互,解耦接口依赖
3. 常用网关
Nginx+lua
Zuul Zuul是一种提供动态路由、监视、弹性、安全性等功能的边缘服务。Zuul是Netflix出品的一个基于JVM路由和服务端的负载均衡器。
SpringCoud Gateway Spring Cloud GateWay是Spring Cloud的⼀个全新项⽬【SpringCloud公司开发的】,⽬标是取代Netflix Zuul。
3. gateWay入门使用
3.-1 基本概念
-
断言:用于进行条件判断,只有断言都返回真,才会真正的执行路由。
-
路由:路由是构建网关的基本模块,它由ID,目标URI,断言Predicates集合,过滤器Filters集合组成,如果断言为true,则匹配该路由。
-
过滤器:可以在请求被路由前后修改请求和响应的内容。基于过滤器可以实现:安全,监控,限流等问题。
3.0 创建一个服务
ip配置为80端口,当访问这个服务,就是访问我们的getWay。
# 服务端口
server.port=80
# 服务名
spring.application.name=service-gateway
3.1 请求转发的实现
导入依赖
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
添加配置文件
spring:
application:
name: springcloud-gateway
cloud:
nacos:
discovery:
server-addr: localhost:8848 # 当有了注册中心时,网关也是一个服务,所以需要注册到注册中心去。
gateway:
discovery:
locator:
enabled: true # 让gateway从nacos中获取服务信息
routes: # routes 是路由,一个GateWay可以包含多个route
- id: to-user # 路由的id,必须唯一。用于区别其他Route。
uri: lb://springcloud-consumer # 路由指向的目的地 uri。 lb 动态路由,因为有了注册中心,uri就不是是写,而是写它的服务实例名,lb代表负载均衡。
order: 1 # 路由的优先级,数字越小代表路由的优先级越高。
predicates: # predicates 是断言,一个ateWay可以包含多个predicate
- Path=/user/** # 断言规则,用于进行条件判断,只有断言都返回真,才会真正的执行路由。
#filters: # 过滤器,用于在请求的传递过程中,对请求和响应做一些手脚
路由介绍:
Route:主要由 路由id、目标uri、断言集合和过滤器集合组成。
- 路由标识,要求唯一,名称任意(默认值 uuid,一般不用,需要自定义)
- uri:请求最终被转发到的目标地址
- order: 路由优先级,数字越小,优先级越高
- predicates:断言数组,即判断条件,如果返回值是boolean,则转发请求到 uri 属性指定的服务中
- filters:过滤器数组,在请求传递过程中,对请求做一些修改
断言介绍:
Spring Cloud Gateway包括许多内置的路由断言工厂。所有这些断言都与HTTP请求的不同属性匹配。您可以将多个路由断言工厂与逻辑 and 语句结合使用。
Spring Cloud Gateway 中的断言命名都是有规范的,格式:“xxx + RoutePredicateFactory”,比如权重断言 WeightRoutePredicateFactory,那么配置时直接取前面的 “Weight”。
也可以自定义断言工厂,用到的时候百度吧。
过滤器介绍:
过滤器按区域
划分,可分为全局,局部两种过滤器。
- 局部GatewayFilter:会应用到单个路由上
- 全局GlobalFilter:会应用到所有路由上
过滤器按作用点
划分,可分为Pre(前置),Post(后置)两种过滤器。
- Pre:在请求转发到后端微服务之前执行。
- Post:在请求执行完成之后执行。
Spring Cloud Gateway 中的过滤器命名都是有规范的,格式: "xxx + “GatewayFilterFactory”,比如PrefixPathGatewayFilterFactory,那么配置时直接取前面的 “PrefixPath”。
在Spring Cloud Gateway中内置了很多局部Filter。
3.2 介绍用得比较多的内置局部过滤器——Path路径过滤器
Path相关过滤器可以实现URL重写,通过重写URL可以实现隐藏真实路径提高安全性。
Path相关过滤器采用路径正则表达式参数和替换参数,使用Java正则表达式来灵活地重写请求路径。
Gateway过滤器-自定义局部过滤器
自定义(局部)网关过滤器:https://www.bilibili.com/video/BV1R7411774f?p=42&spm_id_from=pageDriver
因为Gateway提供了很多Gateway内置网关(局部)过滤器,所以一般很少使用Gateway自定义网关过滤器。
在Spring Cloud Gateway中内置了很多全局Filter。
全局:会应用到所有的路由上。通过全局过滤器可以实现对权限的统一校验,安全性验证等功能。
多个 GlobalFilter 可以通过 @Order 或者 getOrder() 方法指定执行顺序,order值越小,执行的优先级越高。
Gateway过滤器-自定义全局过滤器:
常见需求:用户需要登录了才放行。
1. 当客户端第一次请求服务时,服务端对用户进行信息认证(登录)
2. 认证通过,将用户信息进行加密形成token,返回给客户端,作为登录凭证
3. 以后每次请求,客户端都携带认证的token
4. 服务端对token进行解密,判断是否有效
// 自定义类实现 GlobalFilter , Ordered 接口,加上@Component注解即可;
@Component
public class MyCustomerGlobalFilter implements GlobalFilter ,Ordered {
// 参数1:是一个响应交互的契约。提供对HTTP请求和响应的访问,并公开额外的服务器端处理相关属性和特性,如请求属性。
// 参数2:用于承载请求相关的属性和请求体,Spring Cloud Gateway中底层使用Netty处理网络请求。
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 判断token,并验证账号密码
String token = request.getQueryParams().getFirst("token"); // 获取请求对象中参数名为 token 的值,获取第一个参数的值。
if (StringUtils.isBlank(token)) {
System.out.println("鉴权失败");
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
return response.setComplete();
}
if(token.equale("abc")){
// 放行
return chain.filter(exchange);
}
}
// 该方法用于声明该过滤器执行的优先级
@Override
public int getOrder() { // 返回值越低,表示过滤器执行的优先级越高。
return 0;
}
}
4. 跨域问题的解决
方法一:配置类方式
@Configuration
public class CorsConfig {
@Bean
public CorsWebFilter corsFilter() {
CorsConfiguration config = new CorsConfiguration();
config.addAllowedMethod("*");
config.addAllowedOrigin("*");
config.addAllowedHeader("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
source.registerCorsConfiguration("/**", config);
return new CorsWebFilter(source);
}
}
方法二:配置文件方式
spring:
cloud:
gateway:
globalcors: # 全局的跨域处理
add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题
corsConfigurations:
'[/**]':
allowedOrigins: # 允许哪些网站的跨域请求 # 需要 注意的是在springboot2.4之前的版本是使用allowed-origins: "*",在springboot2.4之后的版本是 allowed-origin-patterns: "*"。
- "http://localhost:8090"
- "http://www.leyou.com"
allowedMethods: # 允许的跨域ajax的请求方式
- "GET"
- "POST"
- "DELETE"
- "PUT"
- "OPTIONS"
allowedHeaders: "*" # 允许在请求中携带的头信息
allowCredentials: true # 是否允许携带cookie
maxAge: 360000 # 这次跨域检测的有效期
5. Nacos+Spring Cloud Gateway动态路由配置
将网关的一系列配置写到项目的配置文件中,一旦路由策略发生改变必须要重启项目,这样维护成本很高,特别是服务网关作为系统的中心点,一旦重启出现问题,影响面将是十分巨大的。
因此,我们将网关的配置存放到配置中心中,这样由配置中心统一管理,一旦路由发生改变,只需要在配置中心修改即可,降低风险且实时失效。
https://www.cnblogs.com/jian0110/p/12862569.html
6. 自定义全局异常处理器
来源:https://blog.csdn.net/a745233700/article/details/122917167
一旦路由的微服务下线或者失联了,Spring Cloud Gateway直接返回了一个错误页面,如下图:
显然这种异常信息不友好,前后端分离架构中必须定制返回的异常信息。传统的Spring Boot 服务中都是使用 @ControllerAdvice 来包装全局异常处理的,但是由于服务下线,请求并没有到达。
因此必须在网关中也要定制一层全局异常处理,这样才能更加友好的和客户端交互。
pring Cloud Gateway提供了多种全局处理的方式,今天只介绍其中一种方式,实现还算比较优雅:
直接创建一个类 GlobalErrorExceptionHandler,实现 ErrorWebExceptionHandler,重写其中的 handle 方法,代码如下:
/**
* 用于网关的全局异常处理
* @Order(-1):优先级一定要比ResponseStatusExceptionHandler低
*/
@Slf4j
@Order(-1)
@Component
@RequiredArgsConstructor
public class GlobalErrorExceptionHandler implements ErrorWebExceptionHandler {
private final ObjectMapper objectMapper;
@SuppressWarnings({"rawtypes", "unchecked", "NullableProblems"})
@Override
public Mono<Void> handle(ServerWebExchange exchange, Throwable ex) {
ServerHttpResponse response = exchange.getResponse();
if (response.isCommitted()) {
return Mono.error(ex);
}
// JOSN格式返回
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
if (ex instanceof ResponseStatusException) {
response.setStatusCode(((ResponseStatusException) ex).getStatus());
}
return response.writeWith(Mono.fromSupplier(() -> {
DataBufferFactory bufferFactory = response.bufferFactory();
try {
//todo 返回响应结果,根据业务需求,自己定制
CommonResponse resultMsg = new CommonResponse("500",ex.getMessage(),null);
return bufferFactory.wrap(objectMapper.writeValueAsBytes(resultMsg));
}
catch (JsonProcessingException e) {
log.error("Error writing response", ex);
return bufferFactory.wrap(new byte[0]);
}
}));
}
}
6. gateway整合sentinel
https://www.bilibili.com/video/BV1pF41147Aa?p=60&vd_source=61b6fb4e547748656e36b17ee95125fb
https://blog.csdn.net/a745233700/article/details/122917160