Spring Cloud组件-网关Gateway

Spring Cloud组件-网关Gateway

官方文档地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-starter

在微服务架构中,一个系统会被拆分成多个微服务,每个业务都要做鉴权、限流、权限校验、跨域等逻辑,前端客户端当然不能分别去请求多个服务这时就需要借助API网关来解决。所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控、路由转发等等。

一、Spring Cloud Gateway快速开始

1. Gateway单独使用

1.1 引入依赖

新建spring cloud项目模块中引入gateway依赖

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
1.2 配置文件
server:
  port: 8820
spring:
  application:
    name: api-gateway
  cloud:
    # gateway配置
    gateway:
      # 路由规则
      routes:
        - id: order_route # 路由的唯一标识
          uri: http://localhost:8880  # 转发到的地址
          predicates:
            - Path=/my-server/** # 断言路径中是否存在
          filters:
            - StripPrefix=1   # 转发之前去掉第1层路径

这样就可以通过访问http://localhost:8820/my-server/来实现访问http://localhost:8880/

2. 集成Nacos使用Gateway

2.1 引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.2 配置文件
server:
  port: 8820
spring:
  application:
    name: api-gateway
  cloud:
  	nacos:
      server-addr: 127.0.0.1:8848
      discovery:
        username: nacos
        password: nacos
    # gateway配置
    gateway:
      # 路由规则
      routes:
        - id: order_route # 路由的唯一标识
          uri: lb://MY-SERVICE  # 转发到的地址,lb表示使用nacos中的本地负载均衡策略
          predicates:
            - Path=/my-server/** # 断言路径中是否存在
          filters:
            - StripPrefix=1   # 转发之前去掉第1层路径

将之前的spring.cloud.gateway.routes[0].uri属性值改为nacos中注册的服务名即可。

还有一种简写方式,启用 DiscoveryClient 网关集成的标志(以服务名作为断言和过滤),默认为false。

spring.cloud.gateway.discovery.locator.enabled=true

开启之后访问网关地址需加上路由目标服务的服务名,例如8080的网关去路由名为my-server的8081服务,请求地址为:http://ip:8080/my-server/,路由地址为:http://ip:8081/

二、Gateway中配置详解

路由规则通过GatewayProperties类中routes属性,即spring.cloud.gateway.routes属性配置,该属性为RouteDefinition的集合。RouteDefinition属性如下:

private String id;
private List<PredicateDefinition> predicates = new ArrayList<>();
private List<FilterDefinition> filters = new ArrayList<>();
private URI uri;
private Map<String, Object> metadata = new HashMap<>();
private int order = 0;

其中id为不同路由的惟一标识,uri为路由转发到的地址,predicates为断言,filters为过滤器。

1. 路由断言工厂

Spring Cloud Gateway包括许多内置的断言工厂,所有这些断言都与HTTP请求的不同属性匹配。如果匹配成功就路由转发,如果匹配失败就返回404。

spring.cloud.gateway.routes[0].predicates

断言工厂都继承于AbstractRoutePredicateFactory类。

AbstractRoutePredicateFactory

1.1 基于时间类型路由的断言工厂

时间类型为ZoneDateTime.now(),主要有三种:

  • AfterRoutePredicateFactory:接收一个日期参数,判断请求日期是否晚于指定日期

    predicates:
    	- After=2017-01-20T17:42:47.789-07:00[America/Denver]
    
  • BeforeRoutePredicateFactory:接收一个日期参数,判断请求日期是否早于指定日期

    predicates:
    	- Before=2017-01-20T17:42:47.789-07:00[America/Denver]
    
  • BetweenRoutePredicateFactory:接收一个日期参数,判断请求日期是否在指定日期之间

    predicates:
    	- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
    
1.2 基于Cookie路由断言工厂

CookieRoutePredicateFactory:接收两个参数,cookie 名字和一个正则表达式,判断请求 cookie是否具有给定名称且值与正则表达式匹配。

predicates:
	- Cookie=chocolate, ch.p
1.3 Header路由断言工厂

HeaderRoutePredicateFactory:接收两个参数,标题名称和正则表达式。 判断请求Header是否具有给定名称且值与正则表达式匹配。

predicates:
	- Header=X-Request-Id, \d+
1.4 Host路由断言工厂

HostRoutePredicateFactory:接收一个参数,主机名模式。判断请求的Host是否满足匹配规则。多个逗号隔开。

predicates:
	- Host=**.somehost.org,**.anotherhost.org
1.5 Method路由断言工厂

MethodRoutePredicateFactory:接收一个参数,判断请求类型是否跟指定的类型匹配。

predicates:
	- Method=GET,POST
1.6 Path路由断言工厂

PathRoutePredicateFactory:接收一个参数,判断请求的URI部分是否满足路径规则。

predicates:
	- Path=/red/{segment},/blue/{segment}
Map<String, String> uriVariables = ServerWebExchangeUtils.getPathPredicateVariables(exchange);

String segment = uriVariables.get("segment");
1.7 Query路由断言工厂

QueryRoutePredicateFactory:接收两个参数,请求param和可选参数正则表达式, 判断请求参数是否具有给定名称且值与正则表达式匹配。

predicates:
	- Query=color,green
1.8 远程添加路由断言工厂

RemoteAddrRoutePredicateFactory:接收一个IP地址段,判断请求主机地址是否在地址段中。

predicates:
	- RemoteAddr=192.168.1.1/24

更多断言工厂详情前往官方地址https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gateway-request-predicates-factories

1.9 自定义断言工厂

自定义路由断言工厂需要继承 AbstractRoutePredicateFactory 类,重写 apply 方法的逻辑。在 apply 方法中可以通过
exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。

自定断言工厂需注意:

  • 该类必须成为spring组件的bean
  • 该类的结尾必须是RoutePredicateFactory,配置文件中写法为该类去除结尾,如:自定义XxxRoutePredicateFactory类,配置文件中为- Xxx=...
  • 必须继承AbstractRoutePredicateFactory 类
  • 必须声明静态内部类,用该静态内部类来接收配置文件中对应的断言信息
  • 通过shortcutFieldOrder()方法进行绑定内部类属性
  • 通过apply方法进行逻辑判断,true为匹配成功,false为匹配失败

示例:

自定义断言工厂MyRoutePredicateFactory.java

package com.xc.config.predicates;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.handler.predicate.AbstractRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.GatewayPredicate;
import org.springframework.cloud.gateway.handler.predicate.RoutePredicateFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.server.ServerWebExchange;
import java.util.function.Predicate;
import java.util.*;

/**
 * 自定义断言工厂
 * @author wyp
 */
@Component
public class MyRoutePredicateFactory extends AbstractRoutePredicateFactory<MyRoutePredicateFactory.Config> {

    private static final Log log = LogFactory.getLog(RoutePredicateFactory.class);

    public MyRoutePredicateFactory() {
        super(MyRoutePredicateFactory.Config.class);
    }

    @Override
    public Predicate<ServerWebExchange> apply(final MyRoutePredicateFactory.Config config) {
        return (GatewayPredicate) serverWebExchange -> {
            log.info("自定义断言工厂-" + config.getName());
            return "xiaochen".equals(config.getName());
        };
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Collections.singletonList("name");
    }

    @Validated
    public static class Config {
        private String name;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
    }
}

配置中只有配置My=后的值为xiaochen才能正常访问,否则为404。

predicates:
  - My=xiaochen

2. 网关过滤器工厂

Gateway 内置了很多的过滤器工厂,我们通过一些过滤器工厂可以进行一些业务逻辑处理器,比如添加剔除响应头,添加去除参数等等。

spring.cloud.gateway.routes[0].filters

过滤器工厂都继承于AbstractGatewayFilterFactory类。

AbstractGatewayFilterFactory

过滤器工厂作用参数
AddRequestHeader为原始请求添加HeaderHeader的名称及值
AddRequestParameter为原始请求添加请求参数参数名称及值
AddResponseHeader为原始响应添加HeaderHeader的名称及值
DedupeResponseHeader剔除响应头中重复的值需要去重的Header名称及去重策略
Hystrix为路由引入Hystrix的断路器保护HystrixCommand的名称
FallbackHeaders为fallbackUri的请求头中添加具体的异常信息Header的名称
PrefixPath为原始请求路径添加前缀前缀路径
PreserveHostHeader为请求添加一个preserveHostHeader=true的属性,路由过滤器会检查该属性以决定是否要发送原始的Host
RequestRateLimiter用于对请求限流,限流算法为令牌桶keyResolver、rateLimiter、statusCode、denyEmptyKey、emptyKeyStatus
RedirectTo将原始请求重定向到指定的URLhttp状态码及重定向的url
RemoveHopByHopHeadersFilter为原始请求删除IETF组织规定的一系列Header默认就会启用,可以通过配置指定仅删除哪些Header
RemoveRequestHeader为原始请求删除某个HeaderHeader名称
RemoveResponseHeader为原始响应删除某个HeaderHeader名称
RewritePath重写原始的请求路径原始路径正则表达式以及重写后路径的正则表达式
RewriteResponseHeader重写原始响应中的某个HeaderHeader名称,值的正则表达式,重写后的值
SaveSession在转发请求之前,强制执行WebSession::save操作
secureHeaders为原始响应添加一系列起安全作用的响应头无,支持修改这些安全响应头的值
SetPath修改原始的请求路径修改后的路径
SetResponseHeader修改原始响应中某个Header的值Header名称,修改后的值
SetStatus修改原始响应的状态码HTTP 状态码,可以是数字,也可以是字符串
StripPrefix用于截断原始请求的路径使用数字表示要截断的路径的数量
Retry针对不同的响应进行重试retries、statuses、methods、series
RequestSize设置允许接收最大请求包的大小。如果请求包大小超过设置的值,则返回 413 Payload Too Large请求包大小,单位为字节,默认值为5M
ModifyRequestBody在转发请求之前修改原始请求体内容修改后的请求体内容
ModifyResponseBody修改原始响应体的内容修改后的响应体内容
Default为所有路由添加过滤器过滤器工厂名称及值

更多详情请前往官方地址:https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/#gatewayfilter-factories

2.1 添加请求头

AddRequestHeaderGatewayFilterFactory:接收两个参数,name和value。 请求时自动携带请求头name和value。

filters:
	- AddRequestHeader=X-Request-red,blue
@RequestMapping("/gateway")
public String gateway(@RequestHeader("X-Request-red") String header) {
    System.out.println("X-Request-red:" + header);
    return "X-Request-red:" + header;
}

请求/gateway接口发现输出了“X-Request-red:blue”,说明请求中自动携带了该请求头。

2.2 添加请求参数

AddRequestParameterGatewayFilterFactory:接收两个参数,name和value。 请求时自动携带请求参数value和name。

filters:
	- AddRequestParameter=color,blue
@RequestMapping("/gateway")
public String gateway1(@RequestParam("color") String color) {
    System.out.println("color:" + color);
    return "color:" + color;
}

请求/gateway接口发现输出了“color:blue”,说明请求中自动携带了请求参数name于value。

2.3 自定义过滤器工厂

自定义路由断言工厂需要继承 AbstractGatewayFilterFactory类,重写 apply 方法的逻辑。在 apply 方法中可以通过
exchange.getRequest() 拿到 ServerHttpRequest 对象,从而可以获取到请求的参数、请求方式、请求头等信息。

自定断言工厂需注意:

  • 该类必须成为spring组件的bean
  • 该类的结尾必须是GatewayFilterFactory,配置文件中写法为该类去除结尾,如:自定义XxxGatewayFilterFactory类,配置文件中为- Xxx=...
  • 必须继承AbstractGatewayFilterFactory 类
  • 必须声明静态内部类,用该静态内部类来接收配置文件中对应的断言信息
  • 通过shortcutFieldOrder()方法进行绑定内部类属性
  • 通过apply方法操作后返回chain.filter(exchange)

示例:

自定义断言工厂MyGatewayFilterFactory.java

package com.xc.config.filters;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.cloud.gateway.filter.factory.GatewayFilterFactory;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

/**
 * 自定义过滤器
 * @author wyp
 */
@Component
public class MyGatewayFilterFactory extends AbstractGatewayFilterFactory<MyGatewayFilterFactory.Config> {

    private static final Log log = LogFactory.getLog(GatewayFilterFactory.class);

    public MyGatewayFilterFactory() {
        super(MyGatewayFilterFactory.Config.class);
    }

    @Override
    public List<String> shortcutFieldOrder() {
        return Arrays.asList("name","value");
    }

    @Override
    public GatewayFilter apply(Config config) {
        return (exchange, chain) -> {
            log.info("自定义过滤器 name = " + config.getName());
            log.info("自定义过滤器 value = " + config.getValue());
            return chain.filter(exchange);
        };
    }

    public static class Config {
        private String name;
        private String value;
        public String getValue() {
            return value;
        }
        public void setValue(String value) {
            this.value = value;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
}

当配置中配置如下配置后,

filters:
  - My=color,red

请求接口后,控制台会输出日志:

自定义过滤器 name = color
自定义过滤器 value = red

3. 全局过滤器

全局过滤器GlobalFilter 接口和 GatewayFilter 有一样的接口定义,只不过, GlobalFilter 会作用于所有路由。

局部:局部针对某个路由, 需要在路由中进行配置

全局:针对所有路由请求, 一旦定义就会投入使用

GlobalFilter

3.1 自定义全局过滤器
package com.xc.config.global;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 自定义全局过滤器
 * @author wyp
 */
@Component
public class LogFilter implements GlobalFilter {

    private static final Log log = LogFactory.getLog(LogFilter.class);

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        log.info(request.getPath());
        return chain.filter(exchange);
    }
}

任意请求进来都会打印请求路径。

4. Gateway跨域配置

4.1 配置文件的方式
spring:
  cloud:
    gateway:
      globalcors:
        cors-configurations:
          '[/**]':  # 允许跨域访问的资源
            allowedOrigins: "*" # 跨域允许的来源
            allowedMethods:   # 允许的请求方式
              - GET
              - POST
4.2 配置类的方式
package com.xc.config;

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;

/**
 * @author wyp
 */
@Configuration
public class CorsConfig {
    @Bean
    public CorsWebFilter corsWebFilter() {
        CorsConfiguration config = new CorsConfiguration();
        // 允许的请求头
        config.addAllowedHeader("*");
        // 允许的HTTP方法
        config.addAllowedMethod("*");
        // 允许的来源
        config.addAllowedOrigin("*");

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(new PathPatternParser());
        // 允许访问的资源
        source.registerCorsConfiguration("/**",config);

        return new CorsWebFilter(source);
    }
}

三、路由配置

在spring cloud gateway中配置uri有三种方式,如下:

1. websocket配置方式

spring:
  cloud:
    gateway:
      routes:
        - id: my-service
          uri: ws://localhost:9090/
          predicates:
            - Path=/api/**

2. http地址配置方式

spring:
  cloud:
    gateway:
      routes:
        - id: my-service
          uri: http://localhost:9090/
          predicates:
            - Path=/api/**

3. 注册中心配置方式

spring:
  cloud:
    gateway:
      routes:
        - id: my-service
          uri: lb://my-service
          predicates:
            - Path=/api/**

四、Gateway整合Sentinel

官方文档地址:https://github.com/alibaba/Sentinel/wiki/网关限流

网关层的限流可以简单地针对不同路由进行限流,也可针对业务的接口进行限流,或者根据接口的特征分组限流。

1. 简单实现

1.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>
1.2 配置sentinel控制台
spring:
	cloud:
		sentinel:
			transport:
				dashboard: 127.0.0.1:8858	# sentinel控制台地址
1.3 配置规则

可以直接使用控制台的方式配置,也可以通过代码实现规则配置。

2. Gateway整合的Sentinel控制台介绍

控制台如下图,与原先Sentinel有所区别,多了API 管理面板,流控规则的配置也有些变化。

在这里插入图片描述

2.1 流控规则

在这里插入图片描述

  • 当API类型为Route ID 时,API名称为路由规则中路由的唯一id值,即spring.cloud.gateway.routes[n].id的值。

  • 当API类型为API 分组时,需要在API 管理面板中新增API 分组,如下图:
    在这里插入图片描述

    这样可以设置具体的请求路径进行流控降级,之后可在流控规则里选择API 类型为API 分组,然后进行设置。

  • 针对请求属性选中后,如下图:
    在这里插入图片描述

    • Client IP针对的是IP地址
    • Remote Host针对的是远程主机名
    • Header针对的是请求头
    • URL 参数针对的是URL 参数
    • Cookie针对的是Cookie 名称

    这些有点像断言工厂中的那些。

3. 自定义流控降级异常方式

3.1 配置文件的方式
spring:
  cloud:
    sentinel:
      scg:
        fallback:
          mode: response
          response-body: '{"code":429,"msg":"限流"}'
3.2 配置类的方式
package com.xc.config;

import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandler;
import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.GatewayCallbackManager;
import com.alibaba.csp.sentinel.slots.block.degrade.DegradeException;
import com.alibaba.csp.sentinel.slots.block.flow.FlowException;
import com.alibaba.csp.sentinel.slots.block.flow.param.ParamFlowException;
import com.alibaba.csp.sentinel.slots.system.SystemBlockException;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.server.ServerResponse;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.Map;

/**
 * @author wyp
 */
@Configuration
public class FlowDegradeConfig {
    @PostConstruct
    public void init() {
        BlockRequestHandler blockRequestHandler = (exchange, t) -> {

            Map<String, Object> map = new HashMap<>();
            if (t instanceof FlowException) {
                map.put("code", HttpStatus.TOO_MANY_REQUESTS.value());
                map.put("msg", "流控规则-FlowException");
            }else if (t instanceof ParamFlowException) {
                map.put("code", HttpStatus.TOO_MANY_REQUESTS.value());
                map.put("msg", "流控规则-ParamFlowException");
            }else if (t instanceof DegradeException) {
                map.put("code", HttpStatus.TOO_MANY_REQUESTS.value());
                map.put("msg", "降级规则-DegradeException");
            }else if (t instanceof SystemBlockException) {
                map.put("code", HttpStatus.TOO_MANY_REQUESTS.value());
                map.put("msg", "系统规则-SystemBlockException");
            }

            return ServerResponse.status(HttpStatus.OK)
                    .contentType(MediaType.APPLICATION_JSON)
                    .body(BodyInserters.fromValue(map));
        };
        GatewayCallbackManager.setBlockHandler(blockRequestHandler);
    }
}

4. 代码的方式设置流控规则

与单独sentinel的设置流控规则方法类似,只是将流控规则FlowRule换成GatewayFlowRule,加载规则的FlowRuleManager换成GatewayRuleManager。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小辰~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值