网关(Gateway)又称网间连接器、协议转换器。默认网关在网络层以上实现网络互连,是最复杂的网络互连设备,仅用于两个高层协议不同的网络互连。网关的结构也和路由器类似,不同的是互连层。网关既可以用于广域网互连,也可以用于局域网互连。
大家都知道在微服务架构中,一个系统会被拆分为很多个微服务。那么作为客户端(pc androud ios 平板)要如何去调用这么多的微服务呢?如果没有网关的存在,我们只能在客户端记录每个微服务的地址,然后分别去调用。
这样的架构,会存在着诸多的问题:
上面的这些问题可以借助API网关来解决。
所谓的API网关,就是指系统的统一入口,它封装了应用程序的内部结构,为客户端提供统一服 务,一些与业务本身功能无关的公共逻辑可以在这里实现,诸如认证、鉴权、监控(黑白名单)、路由转发等等。 添加上API网关之后,系统的架构图变成了如下所示:

在业界比较流行的网关,有下面这些:
Ngnix+lua
使用nginx的反向代理和负载均衡可实现对api服务器的负载均衡及高可用
lua是一种脚本语言,可以来编写一些简单的逻辑, nginx支持lua脚本
Kong
基于Nginx+Lua开发,性能高,稳定,有多个可用的插件(限流、鉴权等等)可以开箱即用。 问题:
只支持Http协议;二次开发,自由扩展困难;提供管理API,缺乏更易用的管控、配置方式。
Zuul 1.0(慢 servlet 2.0 ) zuul2.0 没出来。
Netflix开源的网关,功能丰富,使用JAVA开发,易于二次开发 问题:缺乏管控,无法动态配
置;依赖组件较多;处理Http请求依赖的是Web容器,性能不如Nginx
Spring Cloud Gateway
Spring公司为了替换Zuul而开发的网关服务,将在下面具体介绍。
注意:SpringCloud alibaba技术栈中并没有提供自己的网关,我们可以采用Spring Cloud Gateway来做网关
Spring Cloud Gateway是Spring公司基于Spring 5.0,Spring Boot 2.0 和 Project Reactor 等术开发的网关,它旨在为微服务架构提供一种简单有效的统一的 API 路由管理方式。它的目标是替代 Netflix Zuul,其不仅提供统一的路由方式,并且基于 Filter 链的方式提供了网关基本的功能,例如:安全,监控和限流。
优点:
缺点:
gateway内置了服务器 netty服务器。

- <dependencies>
-
- <dependency>
- <groupId>org.springframework.cloudgroupId>
- <artifactId>spring-cloud-starter-gatewayartifactId>
- dependency>
- dependencies>
- server:
- port: 7000
- # 服务名称
- spring:
- application:
- name: springcloud-gateway
-
- # 路由转发 List<RouteDefinition> routes
- cloud:
- gateway:
- routes:
- - id: springcloud-product
- # 路由转发的真实地址
- uri: http://localhost:8081
- # predicates:当满足断言时,才会转发到真实的uri地址
- predicates:
- - Path=/product/**
- @SpringBootApplication
- public class GatewayApp {
- public static void main(String[] args) {
- SpringApplication.run(GatewayApp.class,args);
- }
- }
- <dependency>
- <groupId>com.alibaba.cloudgroupId>
- <artifactId>spring-cloud-starter-alibaba-nacos-discoveryartifactId>
- dependency>
- server:
- port: 7000
- # 服务名称
- spring:
- application:
- name: springcloud-gateway
-
- # 路由转发 List<RouteDefinition> routes
- cloud:
- gateway:
- routes:
- - id: springcloud-product
- # 路由转发的真实地址
- # uri: http://localhost:8081
- # lb://微服务的名称 默认采用ribbon负载均衡
- uri: lb://springcloud-product
- # predicates:当满足断言时,才会转发到真实的uri地址
- predicates:
- - Path=/product/**
- - id: springcloud-order
- uri: lb://springcloud-order
- predicates:
- - Path=/order/**
- # 指定注册中心的地址
- nacos:
- discovery:
- server-addr: localhost:81
- register-enabled: false

- server:
- port: 7000
- # 服务名称
- spring:
- application:
- name: springcloud-gateway
-
- # 路由转发 List<RouteDefinition> routes
- cloud:
- gateway:
- # 手动配置路由
- # routes:
- # - id: springcloud-product
- # # 路由转发的真实地址
- # # uri: http://localhost:8081
- # # lb://微服务的名称 默认采用ribbon负载均衡
- # uri: lb://springcloud-product
- # # predicates:当满足断言时,才会转发到真实的uri地址
- # predicates:
- # - Path=/product/**
- #
- # - id: springcloud-order
- # uri: lb://springcloud-order
- # predicates:
- # - Path=/order/**
- # 指定注册中心的地址
- # nacos:
- # discovery:
- # server-addr: localhost:81
- # register-enabled: false
-
- # 自动路由 是否开启gateway的定位发现功能
- discovery:
- locator:
- enabled: true
- # 指定注册中心地址
- nacos:
- server-addr: localhost:81
SpringCloud Gateway 包括许多内置的断言工厂,所有这些断言都与 HTTP 请求的不同属性匹配体如下:l * 基于 **Datetime** 类型的断言工厂 *此类型的断言根据时间做判断,主要有三个:AfterRoutePredicateFactory : 接收一个日期参数,判断请求日期是否晚于指定日期BeforeRoutePredicateFactory : 接收一个日期参数,判断请求日期是否早于指定日期 BetweenRoutePredicateFactory : 接收两个日期参数,判断请求日 期是否在指定时间段内-After=2019-12-31T23:59:59.789+08:00[Asia/Shanghai]l * 基于远程地址的断言工厂 **RemoteAddrRoutePredicateFactory** : *接收一个 IP 地址段,判断请求主机地址是否在地址段中-RemoteAddr=192.168.1.1/24l * 基于 **Cookie** 的断言工厂 *CookieRoutePredicateFactory :接收两个参数, cookie 名字和一个 正则表达式。 判断请求cookie 是否具有给定名称且值与正则表达式匹配。-Cookie=chocolate, ch.l * 基于 **Header** 的断言工厂 *HeaderRoutePredicateFactory :接收两个参数,标题名称和正则表达式。 判断请求 Header 是否 具有给定名称且值与正则表达式匹配。 key value-Header=X-Request-Id, \d+l * 基于 **Host** 的断言工厂 *HostRoutePredicateFactory :接收一个参数,主机名模式。判断请求的 Host 是否满足匹配规则。-Host=**.testhost.orgl * 基于 **Method** 请求方法的断言工厂 *MethodRoutePredicateFactory :接收一个参数,判断请求类型是否跟指定的类型匹配。-Method=GETl * 基于 **Path** 请求路径的断言工厂 * PathRoutePredicateFactory :接收一个参数,判断请求的 URI 部分是否满足路径规则。-Path=/foo/{segment} 基于 Query 请求参数的断言工厂QueryRoutePredicateFactory :接收两个参数,请求 param 和正则表达式, 判断请求参数是否具 有给定名称且值与正则表达式匹配。-Query=baz, ba.l * 基于路由权重的断言工厂 *WeightRoutePredicateFactory :接收一个 [ 组名 , 权重 ], 然后对于同一个组内的路由按照权重转发routes:-id: weight_route1 uri: host1 predicates:-Path=/product/**-Weight=group3, 1-id: weight_route2 uri: host2 predicates:-Path=/product/**-Weight= group3, 9
局部过滤器是针对单个路由的过滤器。
在SpringCloud Gateway中内置了很多不同类型的网关路由过滤器。


配置文件
- server:
- port: 7000
- # 服务名称
- spring:
- application:
- name: springcloud-gateway
-
- # 路由转发 List
routes - cloud:
- gateway:
- # 手动配置路由
- routes:
- - id: springcloud-product
- # 路由转发的真实地址
- # uri: http://localhost:8081
- # lb://微服务的名称 默认采用ribbon负载均衡
- uri: lb://springcloud-product
- # predicates:当满足断言时,才会转发到真实的uri地址,多个断言之间and的关系
- predicates:
- - Path=/product/**
- # 只允许get方式的请求
- - Method=GET
- - RemoteAddr=192.168.1.34,127.0.0.1
- - id: springcloud-order
- uri: lb://springcloud-order
- predicates:
- - Path=/order/**
- filters:
- - SetStatus=250
- # 指定注册中心的地址
- nacos:
- discovery:
- server-addr: localhost:81
- register-enabled: false
测试

自定义局部过滤器写一个配置类继承AbstractGatewayFilterFactory类
第一步,在配置文件中,添加一个Log的过滤器配置

第二步,自定义一个过滤器工厂,实现方法
- @Component
- public class LogGatewayFilterFactory extends AbstractGatewayFilterFactory
{ -
- public LogGatewayFilterFactory() {
- super(LogGatewayFilterFactory.Config.class);
- }
-
- public List
shortcutFieldOrder() { - return Arrays.asList("consoleLog", "cacheLog");
- }
-
-
- @Override
- public GatewayFilter apply(Config config) {
- return (exchange, chain) -> {
- if (config.cacheLog) {
- System.out.println("开启缓存日志");
- }
- if (config.consoleLog) {
- System.out.println("开启控制台日志");
- }
- return chain.filter(exchange);
- };
- }
- @Data
- public static class Config {
- private Boolean consoleLog;
- private Boolean cacheLog;
-
- public Config() {
- }
- }
- }
测试

- anon:
- url:
- - '/product/getOne/1'
- - '/sso/login'
- @Component
- @ConfigurationProperties(prefix = "anon")
- public class Anon {
- List url;
-
- public List getUrl() {
- return url;
- }
-
- public void setUrl(List url) {
- this.url = url;
- }
- }
- @Component
- public class LoginFilter implements GlobalFilter, Ordered {
- @Autowired
- private Anon anon;
- @Override
- public Mono
filter(ServerWebExchange exchange, GatewayFilterChain chain) { - ServerHttpRequest request = exchange.getRequest();
- ServerHttpResponse response = exchange.getResponse();
- //1.获取请求
- String path = request.getPath().toString();
- //2.判断路径是否为放行路径
- if (anon.getUrl().contains(path)){
- return chain.filter(exchange);
- }
- //3.判断是否携带token
- String token = request.getHeaders().getFirst("token");
- if (StringUtils.hasText(token)&&"admin".equals(token)){
- return chain.filter(exchange);
- }
- //4.返回一个json数据
- //4.1 设置状态码
- response.setStatusCode(HttpStatus.UNAUTHORIZED);
- //4.2 封装返回的数据
- CommonResult commonResult = new CommonResult(5001, "未登录", null);
- byte[] bytes = JSON.toJSONString(commonResult).getBytes(StandardCharsets.UTF_8);
- //4.3 调用bufferFactory方法,生成DataBuffer对象
- DataBuffer dataBuffer = response.bufferFactory().wrap(bytes);
- //5.调用Mono中的just方法,返回要写给前端的JSON数据
- return response.writeWith(Mono.just(dataBuffer));
- }
-
- //优先级 返回的值越小优先级越高
- @Override
- public int getOrder() {
- return 0;
- }
- }
(4)使用postman测试


第一种:在网关服务里添加跨域配置类
- @Configuration
- public class CorsConfig {
- @Bean
- public CorsWebFilter corsFilter() {
- CorsConfiguration config = new CorsConfiguration();
- config.setAllowCredentials(true); // 是否允许携带cookie
- config.addAllowedOrigin("*"); // 可接受的域,是一个具体域名或者*(代表任意域名)
- config.addAllowedHeader("*"); // 允许携带的头
- config.addAllowedMethod("*"); // 允许访问的方式
-
- // 基于Url的跨域配置
- UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
- // 针对所有的请求Url,采用上面的Config配置
- source.registerCorsConfiguration("/**", config);
-
- return new CorsWebFilter(source);
- }
- }
第二种:在配置文件添加以下内容
- spring:
- cloud:
- gateway:
- globalcors:
- cors-configurations:
- '[/**]':
- allowedOrigins: "*"
- allowedHeaders: "*"
- allowedMethods: "*"
- default-filters:
- - DedupeResponseHeader=Vary Access-Control-Allow-Origin Access-Control-Allow-Credentials, RETAIN_FIRST
以上两种方法选择其一即可
测试:
