文章目录
- 1 微服务API网关Gateway
- 1.1 网关
- 1.1.1 简介
- 1.1.2 相比于Zuul
- 1.2 Spring Cloud Gateway介绍
- 1.3 Gateway特性
- 1.4 Gateway工作流程
- 1.5 Gateway核心概念
- 1.5.1 路由
- 1.5.1.1 定义
- 1.4.1.2 动态路由
- 1.4.2 断言
- 1.4.2.1 默认断言
- 1.4.2.2 自定义Predicate
- 1.4.3 过滤器
- 1.4.3.1 默认过滤器
- 1.4.3.2 局部过滤器
- 1.4.3.3 全局过滤器
- 1.4.3.4 自定义过滤器
- 1.4.3.4.1 GatewayFilter
- 1.4.3.4.2 GlobalFilter
- 1.4.3.4.3 AbstractGatewayFilterFactory
- 1.4.3.5 过滤路由过滤器的执行顺序
- 1.6 实际操作
- 1.6.1 pom.xml
- 1.6.2 启动类
- 1.6.3 配置文件 application.yml
- 1.7 跨域处理
- 1.7.1 跨域的概念和原理
- 1.7.2 跨域常见解决方案
- 1.7.3 gateway中如何解决跨域问题
- 1.7.3.1 配置文件
- 1.7.3.2 编码
1 微服务API网关Gateway
1.1 网关
1.1.1 简介
如果没有网关,难道不行吗?功能上是可以的,我们直接调用提供的接口就可以了。那为什么还需要网关?
因为网关的作用不仅仅是转发请求而已。我们可以试想一下,如果需要做一个请求认证功能,我们可以接入到 API
服务中。但是倘若后续又有服务需要接入,我们又需要重复接入。这样我们不仅代码要重复编写,而且后期也不利于维护。
由于接入网关后,网关将转发请求。所以在这一层做请求认证,天然合适。这样这需要编写一次代码,在这一层过滤完毕,再转发给下面的 API。所以 API 网关的通常作用是完成一些通用的功能,如请求认证,请求记录,请求限流,黑白名单判断等。
API网关
是一个服务器,是系统的唯一入口。
API网关
方式的核心要点是,所有的客户端和消费端都通过统一的网关接入微服务,在网关层处理所有的非业务功能。通常,网关提供REST/HTTP的访问API。
总结主要作用是:
- 反向代理(请求的转发)
- 路由和负载均衡
- 身份认证和权限控制
- 对请求限流
1.1.2 相比于Zuul
SpringCloudGateway
基于Spring5
中提供的WebFlux
,是一种响应式编程的实现,性能更加优越。
Zuul
的实现方式比较老式,基于Servlet
的实现,它是一种阻塞式
编程,在高并发下性能性能不佳。
1.2 Spring Cloud Gateway介绍
Spring Cloud Gateway
是 Spring Cloud
的新一代API网关,基于WebFlux
框架实现,它旨在为微服务架构提供一种简单而有效的统一的API路由管理方式。
Spring Cloud Gateway
作为Spring Cloud
生态系统中的网关,目标是替代Netflix ZUUL
,具有更好的性能、更强的扩展性、以及更丰富的功能特性,其不仅提供统一的路由方式,并且基于Filter
链的方式提供了网关基本的功能,例如:安全,监控/埋点,限流等。
1.3 Gateway特性
Spring Cloud Gateway
特性:
- 基于Spring Framework 5, Project Reactor和Spring Boot 2.0
- 动态路由:能够匹配任何请求属性
- 可以对路由指定 Predicate 和 Filter
- 集成
Hystrix
断路器 - 集成
Spring Cloud DiscoveryClient
服务发现功能 - 易于编写的Predicate和Filter
- 请求限流
- 支持路径重写
1.4 Gateway工作流程
客户端向 Spring Cloud Gateway
发出请求,然后在Gateway Handler Mapping
中找到与请求相匹配的路由,将其发送到 Gateway Web Handler
。Handler
再通过指定的过滤器链来对请求进行过滤处理,最后发送到我们实际的服务执行业务逻辑,然后返回。
过滤器链被虚线分隔,是因为过滤器既可以在转发请求前拦截请求,也可以在请求处理之后对响应进行拦截处理。
1.5 Gateway核心概念
1.5.1 路由
1.5.1.1 定义
路由(Route
)是网关最基础的部分,路由信息由一个ID
,一个目标URI
,一组断言
和过滤器
组成。
路由断言Predicate
用于匹配请求,过滤器 Filter
用于修改请求和响应。如果断言为true
,则说明请求URI
和配置匹配,则执行路由。
spring:cloud:gateway:# 定义多个路由routes:# 一个路由route的id- id: path_route# 该路由转发的目标URIuri: https://example.org# 路由条件集合predicates:- Path=/test/**# 过滤器集合filters:- AddRequestHeader=X-Request-Id, 1024- AddRequestParameter=color, red
1.4.1.2 动态路由
网关接收外部请求,按照一定的规则,将请求转发给其他服务或者应用。如果站在服务调用的角度,网关就扮演着服务消费者的角色,此时,如果再来看看服务调用的目标URI配置,就会很自然的发现一个问题,服务提供者调用的地址是写死的,即网关没有动态的发现服务,这就涉及到了服务的自动发现问题,以及发现服务后,所涉及到的服务调用的负载均衡的问题。
可以通过Nacos
或者Eureka
注册中心动态发现服务,通过Ribbon
进行服务调用的负载均衡。同样,Gateway
也可以整合Nacos
或者Eureka
,Ribbon
从而实现动态路由的功能。
想要使用动态路由的功能,首先要整合注册中心,这里以Nacos为例
pom依赖
<!--SpringCloud ailibaba nacos -->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
配置文件
spring:application:name: cloud-gatewaycloud:nacos:discovery:server-addr: localhost:8848gateway:routes:#路由的ID,没有固定规则但要求唯一,建议配合服务名- id: config_route#匹配后提供服务的路由地址, 这里lb之后,跟的是要调用的服务名称uri: lb://nacos-provider-8002# 断言,路径相匹配的条件predicates:- Path=/routeconfig/rest/**
此时,当id为config_route
的路由规则匹配某个请求后,在调用该请求对应的服务时,就会从nacos注册中心自动发现服务,并在服务调用的时候实现负载均衡。
1.4.2 断言
断言(Predicate
)参考Java8
中的断言 Predicate
,用于实现请求匹配逻辑,例如匹配路径、请求头、请求参数等。请求与断言匹配则执行该路由。
在Gateway
中,有一些的内置Predicate Factory
,有了这些Pridicate Factory
,在运行时,Gateway
会自动根据需要创建其对应的 Pridicate
对象测试路由条件。
1.4.2.1 默认断言
Gateway提供的断言有:Path
路由断言,After
路由断言,Cookie
路由断言,Header
路由断言 ,Host
路由断言 ,Method
路由断言等
名称 | 说明 | 示例 |
---|---|---|
After | 某个时间点之后的请求 | - After=2023-08-01T14:31:20.123-07:00[Asia/Shanghai] |
Before | 某个时间点之前的请求 | - Before=2023-08-01T14:31:20.123+08:00[Asia/Shanghai] |
Between | 某个时间点之中的请求 | - Before=2023-08-01T14:31:20.123+08:00[Asia/Shanghai],2023-08-05T14:31:20.123+08:00[Asia/Shanghai] |
Cookie | 请求必须包含某些cookie | - Cookie=test |
Header | 请求必须包含某些header | - Header=asd,cas |
Host | 请求必须包含某个host(域名) | - Host=baidu.com,jd.com |
Method | 请求必须是指定方式 | - Method=GET,POST |
Path | 请求路径必须符合指定规则 | - Path=/user/{params},/card/** |
Query | 请求参数必须包含指定参数 | - Query=name,jack |
RemoteAddr | 请求者的ip必须是指定范围 | - RemoteAddr=192.168.1.1、24 |
Weight | 权重处理 | - Weight=50 |
Path
路由断言 Factory
: 根据请求路径匹配的路由条件工厂
spring:cloud:gateway:routes:- id: path_routeuri: https://example.orgpredicates:# 如果可以匹配的PathPattern有多个,则每个路径模式以,分开- Path=/red/{segment},/blue/{segment}
After
路由断言 Factory
: 在指定日期时间之后发生的请求都将被匹配
spring:cloud:gateway:routes:- id: after_routeuri: https://example.orgpredicates:- After=2017-01-20T17:42:47.789-07:00[America/Denver]
Cookie
路由断言 Factory
: Cookie
路由断言 Factory
有两个参数,cookie名称和正则表达式。请求包含此cookie名称且正则表达式为真的将会被匹配。
spring:cloud:gateway:routes:- id: cookie_routeuri: https://example.orgpredicates:- Cookie=chocolate, ch.p
Header
路由断言 Factory
: Header
路由断言 Factory
有两个参数,header
名称和正则表达式。请求包含此header名称且正则表达式为真的将会被匹配。
spring:cloud:gateway:routes:- id: header_routeuri: https://example.orgpredicates:- Header=X-Request-Id, \d+
Host
路由断言 Factory
: Host
路由断言 Factory
包括一个参数:host name
列表。使用Ant路径匹配规则, .
作为分隔符。
spring:cloud:gateway:routes:- id: host_routeuri: https://example.orgpredicates:- Host=**.somehost.org,**.anotherhost.org
Method
路由断言 Factory
: Method
路由断言 Factory
只包含一个参数:需要匹配的HTTP请求方式
spring:cloud:gateway:routes:- id: method_routeuri: https://example.orgpredicates:- Method=GET
1.4.2.2 自定义Predicate
可以自定义Predicate
来实现复杂的路由匹配规则
实现自定义 Predicate 工厂
通过HostRoutePredicateFactory创建Predicate进行路由判断
@Component
public class MyHostRoutePredicateFactory extends AbstractRoutePredicateFactory<MyHostRoutePredicateFactory.Config> {public MyHostRoutePredicateFactory() {// Config 类作为 Predicate 的配置参数类super(Config.class);}public static class Config {// 路由匹配规则private String hostName;public String getHostName() {return hostName;}public void setHostName(String hostName) {this.hostName = hostName; }}// 生成一个 Predicate 实例@Overridepublic Predicate<ServerWebExchange> apply(Config config) {// 实现匹配逻辑return exchange -> {// 根据config实现匹配判断 String host = exchange.getRequest().getURI().getHost();// 匹配配置中的域名return host.equals(config.getHostName());};}
}
使用
RouteLocator locator = new RouteLocatorBuilder(router).routes().route("test_route", r -> r.path("/test").filters(f -> f.filter(new MyHostRoutePredicateFactory.Config("www.test.com"))).uri("http://localhost:8080")).build();
1.4.3 过滤器
过滤器(Filter
) 指的是Spring
框架中GatewayFilter
的实例,使用过滤器,可以在请求被路由前后对请求进行修改
1.4.3.1 默认过滤器
配置文件中添加过滤器 filters
filters:- AddRequestHeader=name,zs #请求头添加name:zs- RemoveRequestHeader=name #移除请求中一个请求头- AddRequestParameter=color,blue #请求参数添加color:blue- AddResponseHeader=phone,973345344 #响应头添加phone:973345344- RemoveResponseHeader=phone,973345344 #响应头移除一个响应头phone- PrefixPath=/mypath #添加路径前缀/mypath- StripPrefix=n #删除路径前缀n个- RequestRateLimiter=n #限制请求流量
Spring Cloud Gateway
内置的多种过滤器类,例如:
AddRequestHeader GatewayFilter
:在请求头中添加参数PrefixPath GatewayFilter
:请求路径前缀Hystrix GatewayFilter
:断路器RateLimit GatewayFilter
:限流Retry GatewayFilter
:重试
1.4.3.2 局部过滤器
spring:cloud:gateway:routes:- id: user-service uri: lb://userservice predicates: - Path=/user/** filters: # 过滤器配置- AddRequestHeader=token, test # 添加请求头
上述过滤器的含义:
- 给所有进入userservice的请求添加一个请求头。
- 请求头的key为token,value为test。
- 由于当前前过滤器写在微服务的userservice路由下,因此仅仅对访问微服务userservice的请求有效。
1.4.3.3 全局过滤器
spring:cloud:gateway:routes:- id: user-service uri: lb://userservice predicates: - Path=/user/**default-filters: # 默认过滤器配置- AddRequestHeader=token, test # 添加请求头
default-filters
的配置和 routes
平级。
只要配置在 default-filters
下面的过滤器,会对 routes
配置的所有路由都生效。
1.4.3.4 自定义过滤器
1.4.3.4.1 GatewayFilter
可以通过实现GatewayFilter
和Ordered
接口自定义Filter
来实现请求处理逻辑:
@Component
public class TokenFilter implements GatewayFilter, Ordered {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//请求处理逻辑log.info("请求路径:"+ exchange.getRequest().getPath());ServerHttpRequest request = exchange.getRequest();MultiValueMap<String, HttpCookie> cookies = request.getCookies();List<HttpCookie> tokens = cookies.get("access_token");if (tokens == null || tokens.size() == 0) {throw new RuntimeException("少了cookie!");}return chain.filter(exchange);}@Overridepublic int getOrder() {return 0; }
}
当有多个过滤器时,Order
的值决定了过滤器的执行顺序。
数值越大优先级越低, 负的越多, 优先级越高
设置Order
的值有两种方式:
- 实现
Ordered
接口,并且重写getOrder
方法 - 使用
@Order
注解
1.4.3.4.2 GlobalFilter
public class MyFilter implements GlobalFilter {@Overridepublic Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {//1.获取请求参数 //1.这里的request并不是servlet中的request //2.返回值是一个多键的map集合、也就是说这个map集合的键可以重复MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();// 2.获取userName参数String userName = params.getFirst("userName");// 3.校验if ("root".equals(userName)) {// 放行return chain.filter(exchange);}// 4.拦截// 4.1.禁止访问,设置状态码exchange.getResponse().setStatusCode(500);// 4.2.结束处理return exchange.getResponse().setComplete();}
}
1.4.3.4.3 AbstractGatewayFilterFactory
通过继承AbstractGatewayFilterFactory
来实现
public class DemoGatewayFilterFactory extends AbstractGatewayFilterFactory<DemoGatewayFilterFactory.Config> {@Overridepublic GatewayFilter apply(Config config) {return null;}//Config 静态内部类,负责指定网关的参数static class Config{private String arg1;}
}
注意
:DemoGatewayFilterFactory
的命名方式:由Demo + GatewayFilterFactory
组成,
其中Demo是自己起名字,GatewayFilterFactory
是固定的。
范型中的Config
是我们待会要用到的静态内部类,用于声明过滤器中传递的参数
在Spring Cloud Gateway
中,自定义的过滤器会在全局过滤器链中生效,不需要显式配置在路由配置中。这是因为AbstractGatewayFilterFactory
类已经实现了GatewayFilterFactory
接口,并且通过Spring
的自动装配机制将其注册到全局过滤器链中,并且可以在任何路由上生效。
需要注意的是,在全局过滤器链中的顺序是根据Spring Bean
加载顺序决定的。可以通过设置@Order
注解或实现Ordered
接口来控制自定义过滤器在全局过滤器链中的顺序。
1.4.3.5 过滤路由过滤器的执行顺序
由上面可知 SpringCloudGateWay
中,有三种过滤器:
- 默认过滤器
default-filters
- 只对具体某个路由生效的局部过滤器
filters
- 使用
java
代码编写的自定义全局过滤器GlobalFilter
过滤器的执行顺序为:默认过滤器 → 当前路由过滤器 → 自定义全局过滤器
1.6 实际操作
1.6.1 pom.xml
<dependency><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
1.6.2 启动类
@SpringBootApplication
@EnableEurekaClient
public class GatewayApplication {public static void main(String[] args) {SpringApplication.run(GatewayApplication.class, args);}
}
1.6.3 配置文件 application.yml
spring: application:name: cloud-gateway cloud:gateway:routes:# 路由的ID,没有固定规则但要求唯一,建议配合服务名- id: config_route# 匹配后提供服务的路由地址uri: http://ityouknow.com# 断言,路径相匹配的条件predicates:- Path=/routeconfig/rest/**- id: header_routeuri: http://ityouknow.compredicates:- Header=X-Request-Id, \d+
1.7 跨域处理
1.7.1 跨域的概念和原理
跨域:请求位置和被请求位置不同源就会发生跨域。
这里的不同源包括两个点:
域名不同:www.baidu.com 和 www.taobao.com。(IP不同也是相同道理)
端口不同:127.0.0.1:8080和127.0.0.1:8081。
而浏览器又会禁止请求的发起者与服务端发生跨域AJAX请求。
如果发生了跨域请求,服务器端是能够正常响应的,但是响应的结果会被浏览器拦截。
1.7.2 跨域常见解决方案
使用CORS
方式。
CORS
是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing
)。
它允许浏览器向跨源服务器,发出XMLHttpRequest
请求,从而克服了AJAX
只能同源使用的限制。
1.7.3 gateway中如何解决跨域问题
1.7.3.1 配置文件
配置application.yml文件:
spring:cloud:gateway:globalcors: # 全局的跨域配置add-to-simple-url-handler-mapping: true # 解决options请求被拦截问题# options请求 就是一种询问服务器是否浏览器可以跨域的请求# 如果每次跨域都有询问服务器是否浏览器可以跨域对性能也是损耗# 可以配置本次跨域检测的有效期maxAge# 在maxAge设置的时间范围内,不去询问,统统允许跨域corsConfigurations:'[/**]':allowedOrigins: # 允许哪些网站的跨域请求 - "http://localhost:8090"allowedMethods: # 允许的跨域ajax的请求方式- "GET"- "POST"- "DELETE"- "PUT"- "OPTIONS"allowedHeaders: "*" # 允许在请求中携带的头信息allowCredentials: true # 允许在请求中携带cookiemaxAge: 360000 # 本次跨域检测的有效期(单位毫秒)# 有效期内,跨域请求不会一直发option请求去增大服务器压力
1.7.3.2 编码
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.web.cors.reactive.CorsUtils;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;@Configuration
public class CorsConfig {private static final String MAX_AGE = "18000L";@Beanpublic WebFilter corsFilter() {return (ServerWebExchange ctx, WebFilterChain chain) -> {ServerHttpRequest request = ctx.getRequest();// 使用SpringMvc自带的跨域检测工具类判断当前请求是否跨域if (!CorsUtils.isCorsRequest(request)) {return chain.filter(ctx);}HttpHeaders requestHeaders = request.getHeaders(); // 获取请求头ServerHttpResponse response = ctx.getResponse(); // 获取响应对象HttpMethod requestMethod = requestHeaders.getAccessControlRequestMethod(); // 获取请求方式对象HttpHeaders headers = response.getHeaders(); // 获取响应头headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, requestHeaders.getOrigin()); // 把请求头中的请求源(协议+ip+端口)添加到响应头中(相当于yml中的allowedOrigins)headers.addAll(HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, requestHeaders.getAccessControlRequestHeaders());if (requestMethod != null) {headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, requestMethod.name()); // 允许被响应的方法(GET/POST等,相当于yml中的allowedMethods)}headers.add(HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true"); // 允许在请求中携带cookie(相当于yml中的allowCredentials)headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*"); // 允许在请求中携带的头信息(相当于yml中的allowedHeaders)headers.add(HttpHeaders.ACCESS_CONTROL_MAX_AGE, MAX_AGE); // 本次跨域检测的有效期(单位毫秒,相当于yml中的maxAge)if (request.getMethod() == HttpMethod.OPTIONS) { // 直接给option请求反回结果response.setStatusCode(HttpStatus.OK);return Mono.empty();}return chain.filter(ctx); // 不是option请求则放行};}}
参考连接:
https://mp.weixin.qq.com/s/LY66FPCajHkzXJUFlfSNYg
https://blog.csdn.net/h1774733219/article/details/124384527
https://blog.csdn.net/qq_46203643/article/details/127150590
https://mp.weixin.qq.com/s/Y6M1GzrIOBOz6fM12b0uPw