Gateway是一个API网关服务,提供了反向代理、鉴权、流量控制。熔断、日志监控等功能。
一、服务创建
- 创建项目;
- 引入依赖:
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
- 网关实现
主要有配置文件实现与Java配置类实现两种方式
二.、断言规则配置
2.1、 配置类
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* gateway配置类
*
* @Since 2021-01-26 21:43
*/
@Configuration
public class GatewayConfig {
@Bean
public RouteLocator getRouteLocator(RouteLocatorBuilder builder) {
RouteLocatorBuilder.Builder routes = builder.routes();
routes.route("route", r -> r.path("/guonei").uri("http://news.baidu.com/guonei")).build();
return routes.build();
}
}
即访问"/guonei"将跳转到"http://news.baidu.com/guonei"。
注:HTTPS的网站不行
2.2、 配置文件
spring:
cloud:
gateway:
enabled: true # 是否开启网关,默认true
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由
routes:
- id: payment_route1 # 路由id,唯一
# 路由转发后实际访问地址,lb为负载均衡(默认轮训),payment-service为微服务名,也可以为http://127.0.0.1:8000或者http://test.com等
uri: lb://payment-service
predicates: # 断言,设置匹配URL的规则
# 可以允许访问的接口路径,不配置即默认可以访问所有接口,多个以“,”英文逗号分隔,- Path在每个 - id(即每组路由规则)中只能有一个,可以写为/payment/**一次性匹配多个
- Path=/payment/getById/**,/payment/add
- After=2021-01-27T20:47:01.941+08:00[Asia/Shanghai] # 路由在该时间之后生效
- Before=2022-01-27T20:47:01.941+08:00[Asia/Shanghai] # 路由在该时间之前生效
# 路由在这段时间之内生效
- Between=2021-01-27T20:47:01.941+08:00[Asia/Shanghai],2022-01-27T21:47:01.941+08:00[Asia/Shanghai]
- Cookie=username,zhangsan # 必须带有cookie并且“username”的值与配置的“zhangsan”正则匹配成功
- Header=X-Request-Id,\d+ # 请求头必须带有"X-Request-Id"属性并且值与配置的“\d+”正则匹配成功
- Host=**.somehost.com # 必须带有"Host"并且值与配置的“**.somehost.com”正则匹配成功
- Method=GET,POST # 设置请求方式
- Query=username,\d+ # 设置参数,即带有参数username并且值与配置的“\d+”正则匹配成功
- Query=id,\d+ # 多个参数可以有多个Query,猜测(待验证):所有入Query这种一次配置一个键值对的属性,都可以有多个
# 限制可以访问网关的IP,如果网关在代理层之后要注意IP的真实性
- RemoteAddr=127.0.0.1/24,192.168.0.1/16,172.0.0.1/8,10.25.48.32
# 权重,group1为组名,3为权重值,int类型,要是有权重,uri不能是服务名,本路由权重值/同组所有路由权重值之和=分配到本路由的几率
- Weight=group1,3
时间获取:
ZonedDateTime time = ZonedDateTime.now();
测试请求样例:
curl -X GET "http://127.0.0.1:9527/payment/getById/5?username=123&id=1" --cookie "username=zhangsan" -H "X-Request-Id:123456" -H "Host:www.somehost.com"
注:注意yml格式
三. 过滤器
可以对经过路由的请求和响应做处理。
3.1. Gateway Fiter
3.1.1 操作消息头
spring:
cloud:
gateway:
enabled: true # 是否开启网关,默认true
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由
routes:
- id: payment_route1 # 路由id,唯一
# 路由转发后实际访问地址,lb为负载均衡(默认轮训),payment-service为微服务名,也可以为http://127.0.0.1:8000或者http://test.com
uri: lb://payment-service
filters: # 过滤器
- AddRequestHeader=X-Request-red, blue # 在请求头中加入X-Request-red = blue
- SetRequestHeader=X-Request-Red, Blue # 将请求消息头中的X-Request-Red的值设置(替换)为Blue
- SetRequestHeader=foo, bar-{segment} # 如果- Host: {segment}.myhost.org,可以动态获取
- RemoveRequestHeader=X-Request-Foo # 移除请求消息头中的X-Request-Foo
# 在请求头中加入X-Request-red-id = blue + URL中的入参id,注意:要使用这个,获取URL中的入参,必须把URL写在 - Path中
- AddRequestHeader=X-Request-red-id, blue-{id}
- AddResponseHeader=X-Response-color, Blue # 在响应消息头中添加
- SetResponseHeader=X-Response-Red, Blue # 将响应头中的X-Response-Red的值设置(替换)为Blue
- SetResponseHeader=foo, bar-{segment} # 如果- Host: {segment}.myhost.org,可以动态获取
- RemoveResponseHeader=X-Response-Foo # 移除响应消息头中的X-Response-Foo
- AddResponseHeader=foo, bar-{segment} # 在响应消息头中添加,值可以动态获取URL中的参数
# 删除响应消息头中“重复”的参数,多个以“空格”分隔
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin
# RETAIN_FIRST (default), RETAIN_LAST, and RETAIN_UNIQUE
- DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin, RETAIN_LAST
# 从请求消息头中取出Blue的值,赋值给X-Request-Red,如果Blue不存在,则没有影响,如果X-Request-Red不存在,则创建
- MapRequestHeader=Blue, X-Request-Red
# 改写响应消息头中Location的值,四个参数
# 第一个参数:NEVER_STRIP:版本信息不会被剥离,即使原始请求路径不包含版本
# AS_IN_REQUEST:仅当原始请求路径不包含任何版本时,才会剥离版本【默认】
# ALWAYS_STRIP:即使原始请求路径包含版本,也会剥离版本
# 第三个参数:如果提供,会替换 Response Header Location 值中的 host:port 部分;如果不提供,则会使用 Request 的 Host 作为默认值
# 第四个参数:协议会与该值匹配,如果不匹配,过滤器不回做任何操作,默认值 http|https|ftp|ftps
- RewriteLocationResponseHeader=AS_IN_REQUEST, Location, ,
- RewriteResponseHeader=X-Response-Red, , password=[^&]+, password=*** # 重写响应消息头,支持正则
# 没有值,为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host
- PreserveHostHeader
- name: SetRequestHostHeader # 在某些情况下,可能需要覆盖主机标头。在这种情况下可以用指定的值替换现有的主机头
args:
host: example.org
3.1.2 操作消息体
- 操作单个参数
spring:
cloud:
gateway:
enabled: true # 是否开启网关,默认true
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由
routes:
- id: payment_route1 # 路由id,唯一
# 路由转发后实际访问地址,lb为负载均衡(默认轮训),payment-service为微服务名,也可以为http://127.0.0.1:8000或者http://test.com
uri: lb://payment-service
filters: # 过滤器
- AddRequestParameter=color, blue # 在请求中加入参数,color为字段名,blue为
- AddRequestParameter=foo, bar-{segment} # 在请求中加入参数,值可以动态获取URL中的参数拼接
- RemoveRequestParameter=red # 移除请求参数red
- 操作整个消息体
修改请求体
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_request_obj", r -> r.host("*.rewriterequestobj.org")
.filters(f -> f.prefixPath("/httpbin")
.modifyRequestBody(String.class, Hello.class, MediaType.APPLICATION_JSON_VALUE,
(exchange, s) -> return Mono.just(new Hello(s.toUpperCase())))).uri(uri))
.build();
}
static class Hello {
String message;
public Hello() { }
public Hello(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
修改响应体
@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("rewrite_response_upper", r -> r.host("*.rewriteresponseupper.org")
.filters(f -> f.prefixPath("/httpbin")
.modifyResponseBody(String.class, String.class,
(exchange, s) -> Mono.just(s.toUpperCase()))).uri(uri))
.build();
}
3.1.3 断路器
spring:
cloud:
gateway:
enabled: true # 是否开启网关,默认true
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由
routes:
- id: payment_route1 # 路由id,唯一
# 路由转发后实际访问地址,lb为负载均衡(默认轮训),payment-service为微服务名,也可以为http://127.0.0.1:8000或者http://test.com
uri: lb://payment-service
filters: # 过滤器
- name: CircuitBreaker # 断路器
args:
name: myCircuitBreaker # 自定义断路器
fallbackUri: forward:/fallback # 转发到回退方法,该方法也不一定要在网关服务上,可以通过路由调用其他服务上的接口
statusCodes: # 设置使用断路器的状态码,可以使用带有状态代码值的整数或HttpStatus枚举的字符串表示
- 500
- "NOT_FOUND"
3.1.4 操作URL
spring:
cloud:
gateway:
enabled: true # 是否开启网关,默认true
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由
routes:
- id: payment_route1 # 路由id,唯一
# 路由转发后实际访问地址,lb为负载均衡(默认轮训),payment-service为微服务名,也可以为http://127.0.0.1:8000或者http://test.com
uri: lb://payment-service
filters: # 过滤器
# 改写路径,当请求/aaa 接口时,会改写去请求/test 接口,URL可以是正则,请注意,由于 YAML 规范,$应替换$\为
- RewritePath=/aaa, /test
- PrefixPath=/gateway # 在接口URL前面拼接上/gateway后,在转发
- RedirectTo=302, https://acme.org # 重定向,第一个参数为300系列的状态码
- SetPath=/{segment} # 如果- Path=/red/{segment},对于/red/blue的请求路径,这会在发出下游请求之前将路径设置为/blue。
- StripPrefix=2 # 从头部开始去掉2个/以及后面的值,如/name/blue/red会变为/red
3.1.5 限流
spring:
redis:
host: 192.168.10.128
port: 6379
cloud:
gateway:
enabled: true # 是否开启网关,默认true
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由
routes:
- id: payment_route1 # 路由id,唯一
# 路由转发后实际访问地址,lb为负载均衡(默认轮训),payment-service为微服务名,也可以为http://127.0.0.1:8000或者http://test.com
uri: lb://payment-service
filters: # 过滤器
- name: RequestRateLimiter # 限流
args:
redis-rate-limiter.replenishRate: 10 # 平均每秒的请求数(在requestedTokens为1的时候)
# 突发情况下允许用户在一秒内执行的最大请求数(在requestedTokens为1的时候)。这是令牌桶可以容纳的令牌数量。将此值设置为零会阻止所有请求。
redis-rate-limiter.burstCapacity: 20
# 请求花费多少令牌。这是每个请求从存储桶中获取的令牌数量,默认为 1。
redis-rate-limiter.requestedTokens: 1
key-resolver: "#{@apiKeyResolver}"
- name: RequestSize # 设置请求的大小,超过则被拒绝,默认请求大小设置为 5 MB
args:
maxSize: 5000000 # 没有单位这则是默认B,可以是1MB等
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import reactor.core.publisher.Mono;
import java.util.Objects;
/**
* 网关限流配置类
*
* @author guoli
* @data 2021-09-12 17:28
*/
@Configuration
public class RequestRateLimiterConfig {
@Bean
@Primary
KeyResolver apiKeyResolver() {
// 按URL限流,即以每秒内请求数按URL分组统计,超出限流的url请求都将返回429状态
return exchange -> Mono.just(exchange.getRequest().getPath().toString());
}
@Bean
KeyResolver userKeyResolver() {
// 按用户限流
return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getQueryParams().getFirst("user")));
}
@Bean
KeyResolver ipKeyResolver() {
// 按IP来限流
return exchange -> Mono.just(Objects.requireNonNull(exchange.getRequest().getRemoteAddress()).getHostName());
}
}
3.1.6 重试(请求失败后重新发送请求)
spring:
cloud:
gateway:
enabled: true # 是否开启网关,默认true
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由
routes:
- id: payment_route1 # 路由id,唯一
# 路由转发后实际访问地址,lb为负载均衡(默认轮训),payment-service为微服务名,也可以为http://127.0.0.1:8000或者http://test.com
uri: lb://payment-service
filters: # 过滤器
- name: Retry # 重试
args:
retries: 3 # 应该尝试的重试次数,默认3
statuses: BAD_GATEWAY # 应该重试的 HTTP 状态码,用org.springframework.http.HttpStatus表示
series: CLIENT_ERROR # 应该重试的 HTTP 状态码,用org.springframework.http.HttpStatus.Series表示,默认SERVER_ERROR
methods: GET,POST # 应该重试的 HTTP 方法,默认获取方法
exceptions: TimeoutException # 应该重试的异常,默认IOException和TimeoutException
backoff: # 为重试配置的指数退避,默认禁用,重试的间隔时间为firstBackoff乘以factor的n次方,其中n为迭代次数
firstBackoff: 10ms
maxBackoff: 50ms # 最大后退限制
factor: 2
basedOnPreviousValue: false # 如果为true,则使用(PreviousBackoff * factor)计算退避。
3.1.7 其他
spring:
cloud:
gateway:
enabled: true # 是否开启网关,默认true
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由
routes:
- id: payment_route1 # 路由id,唯一
# 路由转发后实际访问地址,lb为负载均衡(默认轮训),payment-service为微服务名,也可以为http://127.0.0.1:8000或者http://test.com
uri: lb://payment-service
filters: # 过滤器
- SaveSession # 保存 Session,在向下游服务转发请求之前强制执行 WebSession::save操作
- SetStatus=401 # 设置返回状态码,无论什么情况,都会返回401
- SetStatus=BAD_REQUEST # 可能是枚举的整数值404或字符串表示形式
- TokenRelay= # token转发,需要添加 org.springframework.boot:spring-boot-starter-oauth2-client 依赖项
3.1.8 默认过滤器
应用于所有路由
spring:
cloud:
gateway:
default-filters:
- AddResponseHeader=X-Response-Default-Red, Default-Blue
- PrefixPath=/httpbin
2. Global Fiter
3.2.1 自定义全局过滤器
可用于日志、鉴权等。
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
/**
* 路由日志过滤器
*
* @Author guoli
* @Since 2021-01-27 22:51
*/
@Component
@Slf4j
public class LogGatewayFilter implements GlobalFilter, Ordered {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 校验参数username不能为空
String username = exchange.getRequest().getQueryParams().getFirst("username");
if (StringUtils.isEmpty(username)) {
exchange.getResponse().setStatusCode(HttpStatus.NOT_ACCEPTABLE);
return exchange.getResponse().setComplete();
}
return chain.filter(exchange);
}
/**
* 加载过滤器的优先级,值越小优先级越高
*
* @return int
*/
@Override
public int getOrder() {
return 0;
}
}
3.2.2 网关指标过滤器(The Gateway Metrics Filter)
四 其他
4.1 DiscoveryClient
- 加注解
启动类加注解 @EnableDiscoveryClient 或其他发现服务的注解如 @EnableEurekaClient 。
- 配置
spring:
cloud:
gateway:
discovery:
locator:
enabled: true # 开启从注册中心动态创建路由
lower-case-service-id: true # 开启服务ID小写
- 作用
1、uri可以配置微服务名
2、向网关发送的请求可以带上目标服务的微服务名指定请求服务,gateway在下发请求之前会自动去掉微服务名。(注:必须开启微服务名小写)
4.2 打印网络日志
Java程序启动参数新增 -Dreactor.netty.http.server.accessLogEnabled=true
4.3 远程操作已部署好的网关
4.3.1 开放端点
# 远程查询端点
management:
endpoint:
gateway:
enabled: true
endpoints:
web:
exposure:
include: gateway
4.3.2通过GET请求查询
4.3.2.1 查询详细的路由信息
查询全部
curl -X GET http://gatewayIP或域名:端口/actuator/gateway/routes
查询单个
curl -X GET http://gatewayIP或域名:端口/actuator/gateway/routes/{id}
默认启用,禁用配置
spring:
cloud:
gateway:
actuator:
verbose:
enabled: false
4.3.2.2 查询全局过滤器
查询所有全局过滤器对象以及它们在过滤器链路中的执行顺序(order)(注:如果是通过 @Order 实现则查询不出,返回null,只有实现 ordered 接口才能查到)
向gateway服务发送请求:
curl -X GET http://IgatewayP或域名:端口/actuator/gateway/globalfilters
4.3.2.3 刷新路由缓存
向gateway服务发送请求:
curl -X POST http://IgatewayP或域名:端口/actuator/gateway/refresh
请求成功返回 200 没有响应正文
4.3.2.4 创建与删除特定路由
创建:
向 /gateway/routes/{id_route_to_create} 发送POST请求,入参:
{
"id": "first_route",
"predicates": [{
"name": "Path",
"args": {"_genkey_0":"/first"}
}],
"filters": [],
"uri": "https://www.uri-destination.org",
"order": 0
}
删除
向 /gateway/routes/{id_route_to_delete} 发送DELETE请求
注:创建与删除都报404