当前位置:首页 > 服务端 > gateway(源码分析)

gateway(源码分析)

2022年11月05日 23:27:42服务端22

gateway

1. 什么是gateway

官网解释: 该项目提供了一个在Spring生态系统之上构建的API网关,包括:Spring 5,Spring Boot 2和Project Reactor。Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注,例如:安全性,监视/指标和弹性。

为什么使用gateway

1.网关作为请求的入口,可以提供限流,权限校验等一系列功能

2.提供路由分发功能

2. 工作原理图

gateway(源码分析) _ JavaClub全栈架构师技术笔记

3. 简单使用

3.1 引入jar包

<dependencies>
        <!-- gateway 依赖 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!--nacos服务发现 动态配置-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
        </dependency>

        <!--端点检查-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
</dependencies>

3.2 配置文件

spring.application.name=gateway
spring.cloud.nacos.config.namespace=dev
spring.cloud.nacos.config.group=GATEWAY_GROUP
spring.cloud.nacos.config.file-extension=yaml
spring.cloud.nacos.config.server-addr=zy.nacos.com:8848
# gateway默认从注册中心给所有的服务建立一个默认的路由 默认false
spring.cloud.gateway.discovery.locator.enabled=true

3.2.3 动态路由

3.2.1 增加配置bean 用来监听nacos的配置文件修改

package com.zy.more.config;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.Executor;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.event.RefreshRoutesEvent;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Service;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.nacos.api.NacosFactory;
import com.alibaba.nacos.api.config.ConfigService;
import com.alibaba.nacos.api.config.listener.Listener;

import reactor.core.publisher.Mono;

/**
 * 动态路由,可以通过获取Bean才做该类,提供增删改查已经发布功能
 * @author wunaozai
 * @Date 2020-03-16
 */
@Service
public class DynamicRouteConfig implements ApplicationEventPublisherAware {
     

    private static final Logger log = LoggerFactory.getLogger(DynamicRouteConfig.class);

    @Autowired
    private RouteDefinitionWriter routedefinitionWriter;

    private ApplicationEventPublisher publisher;

    private String dataId = "gateway-router.properties";
    private String group = "DEFAULT_GROUP";
    @Value("${spring.cloud.nacos.config.server-addr}")
    private String serverAddr;
    @Value("${spring.cloud.nacos.config.namespace}")
    private String namespace;

    private static final List<String> ROUTE_LIST = new ArrayList<>();

    @PostConstruct
    public void dynamicRouteByNacosListener() {
     
        try {
     
            Properties prop = new Properties();
            prop.put("serverAddr", serverAddr);
            prop.put("namespace", namespace);
            ConfigService config = NacosFactory.createConfigService(prop);
            String content = config.getConfig(dataId, group, 5000);
            publisher(content);
            config.addListener(dataId, group, new Listener() {
     
                @Override
                public void receiveConfigInfo(String config) {
     
                    publisher(config);
                }
                @Override
                public Executor getExecutor() {
     
                    return null;
                }
            });
        } catch (Exception e) {
     
            e.printStackTrace();
        }
    }

    /**
     * 增加路由
     * @param def
     * @return
     */
    public Boolean addRoute(RouteDefinition def) {
     
        try {
     
            routedefinitionWriter.save(Mono.just(def)).subscribe();
            ROUTE_LIST.add(def.getId());
        } catch (Exception e) {
     
            e.printStackTrace();
        }
        return true;
    }
    /**
     * 删除路由
     * @return
     */
    public Boolean clearRoute() {
     
        for(String id: ROUTE_LIST) {
     
            routedefinitionWriter.delete(Mono.just(id)).subscribe();
        }
        ROUTE_LIST.clear();
        return false;
    }
    /**
     * 发布路由
     */
    private void publisher(String config) {
     
        clearRoute();
        try {
     
            log.info("重新更新动态路由");
            List<RouteDefinition> gateway = JSONObject.parseArray(config, RouteDefinition.class);
            for(RouteDefinition route: gateway) {
     
                addRoute(route);
            }
            publisher.publishEvent(new RefreshRoutesEvent(this.routedefinitionWriter));
        } catch (Exception e) {
     
            e.printStackTrace();
        }

    }

    @Override
    public void setApplicationEventPublisher(ApplicationEventPublisher app) {
     
        publisher = app;
    }

}

3.2.2 nacos增加json配置文件 gateway-router.properties

[{
     
    "id": "order",
    "order": 1,
    "predicates": [{
     
        "args": {
     
            "pattern": "/order/**"
        },
        "name": "Path"
    }],
    "uri": "lb://order"
},{
     
    "id": "product",
    "order": 2,
    "predicates": [{
     
        "args": {
     
            "pattern": "/product/**"
        },
        "name": "Path"
    }],
    "uri": "lb://product"
},{
     
    "id": "baidu",
    "order": 3,
    "predicates": [{
     
        "args": {
     
            "pattern": "/baidu/**"
        },
        "name": "Path"
    }],
    "uri": "http://www.baidu.com"
}]

这样就能在在不停止gateway服务的情况下,动态的修改gateway路由信息

4. 配置说明

gateway分为三大部分

route: 路由

predicate: 断言

filter: 过滤器

4.1 简介

spring:
  cloud:
    gateway:
      routes: #路由属性
      - id: after_route #id 唯一标识
        uri: https://example.org #当配置到此路由后跳转路径
        predicates: #断言 也就是判断条件 如果符合此条件,就跳转此路径
        - Cookie=mycookie,mycookievalue #条件 predicates默认有11种
        filters: #过滤器
        - AddRequestHeader=X-Request-red, blue #过滤器也有31种 这个过滤器作用是经过的请求都会加上一个请求头

4.2 十一种predicates Factory

4.2.1 After

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - After=2017-01-20T17:42:47.789-07:00[America/Denver]

表示在2017-01-20T17:42:47.789-07:00[America/Denver]这个时间之后的所有请求都会被匹配到这个路由

4.2.2 Before

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - Before=2017-01-20T17:42:47.789-07:00[America/Denver]

表示在2017-01-20T17:42:47.789-07:00[America/Denver]这个时间之前的所有请求都会被匹配到这个路由

4.2.3 Between

spring:
  cloud:
    gateway:
      routes:
      - id: after_route
        uri: https://example.org
        predicates:
        - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-20T17:42:47.789-07:00[America/Denver]

表示在两个时间之间的所有请求都会被匹配到这个路由,第二个时间必须大于第一个时间

上面这三个predicate factory 中的时间都必须是dateTime(java8中的ZoneDateTime)

4.2.4 Cookie

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://example.org
        predicates:
        - Cookie=chocolate, ch.p

表示请求中有一个cookie name是chocolate,值可以匹配到正则表达式[ch.p] 才会匹配到这个路由

4.2.5 Header

spring:
  cloud:
    gateway:
      routes:
      - id: cookie_route
        uri: https://example.org
        predicates:
         - Header=X-Request-Id, \d+

表示请求中有一个Headername是X-Request-Id,值可以匹配到正则表达式[\d+] 才会匹配到这个路由

4.2.6 Host

spring:
  cloud:
    gateway:
      routes:
      - id: host_route
        uri: https://example.org
        predicates:
        - Host=**.somehost.org,**.anotherhost.org

表示请求的host主机名如果是* *.somehost.org, **.anotherhost.org,则匹配此路由

4.2.7 Method

spring:
  cloud:
    gateway:
      routes:
      - id: method_route
        uri: https://example.org
        predicates:
        - Method=GET,POST

请求如果是HTTP Get/Post请求则匹配此路由

4.2.8 Path

spring:
  cloud:
    gateway:
      routes:
      - id: path_route
        uri: https://example.org
        predicates:
        - Path=/red/{
     segment},/blue/{
     segment}

请求路径中能匹配到/red/{segment},/blue/{segment} 则匹配此路由

4.2.9 Query

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=green

请求中包含参数名为green,则会匹配此路由

spring:
  cloud:
    gateway:
      routes:
      - id: query_route
        uri: https://example.org
        predicates:
        - Query=red, gree.

同理,不光可以配置名字,如果两个参数,则第一个参数为参数名,第二个参数为参数值的正则表达式

4.2.10 RemoteAddr

spring:
  cloud:
    gateway:
      routes:
      - id: remoteaddr_route
        uri: https://example.org
        predicates:
        - RemoteAddr=192.168.1.1/24

如果请求中的remoteAddr匹配192.168.1.1/24,则匹配此路由

4.2.11 Weight

spring:
  cloud:
    gateway:
      routes:
      - id: weight_high
        uri: https://weighthigh.org
        predicates:
        - Weight=group1, 8
      - id: weight_low
        uri: https://weightlow.org
        predicates:
        - Weight=group1, 2

权重配置,20%的请求会被发送至weight_low,80%的请求会被发送至weight_heigh

4.2.12 总结

  1. 当一个路由中存在多个predicate时,所有的条件都必须满足才能匹配此路由
  2. 当一个请求满足多个路由的predicate的条件时,只有第一个满足条件的路由会生效

4.3 过滤器

4.3.1 AddRequestHeader

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_header_route
        uri: https://example.org
        filters:
        - AddRequestHeader=X-Request-red, blue

顾名思义,把所有匹配到此路由的请求的请求头都添加一个Header X-Request-red:blue

4.3.2 AddRequestParameter

spring:
  cloud:
    gateway:
      routes:
      - id: add_request_parameter_route
        uri: https://example.org
        filters:
        - AddRequestParameter=red, blue

顾名思义,把所有匹配到此路由的请求中都添加一个参数param red:blue

4.3.3 AddResponseHeader

spring:
  cloud:
    gateway:
      routes:
      - id: add_response_header_route
        uri: https://example.org
        filters:
        - AddResponseHeader=X-Response-Red, Blue

把所有到此路由的请求的Response中增加响应头 X-Response-Red:Blue

4.3.4 DedupeResponseHeader

spring:
  cloud:
    gateway:
      routes:
      - id: dedupe_response_header_route
        uri: https://example.org
        filters:
        - DedupeResponseHeader=Access-Control-Allow-Credentials Access-Control-Allow-Origin

去重,去除经过此路由的请求中的响应头的Access-Control-Allow-Credentials 和 Access-Control-Allow-Origin的重复值

…官网有三十多种,官网地址…

https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.3.RELEASE/reference/html/#the-addrequestheader-gatewayfilter-factory

4.4 自定义过滤器

4.4.1 自定义全局过滤器

全局过滤器不需要在配置文件中配置,因为所有的请求都要经过全局过滤器

一个简单的全局过滤器

package com.zy.more.filter;

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.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author: zhangyao
 * @create:2020-07-16 14:38
 **/
@Component
@Slf4j
public class AuthorGlobalFilter implements GlobalFilter, Ordered {
     
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
     
        log.info("我的自定义全局过滤器启动");
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() {
     
        return 0;
    }
}
	

4.4.2 自定义过滤器

定义一个过滤器,并在配置文件中指定某个路由使用

  1. 定义一个实现了GatewayFilter接口的类
  2. 把这个类注入进GatewayFIlterFactory配置中
package com.zy.more.filter;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * @author: zhangyao
 * @create:2020-07-16 15:12
 **/
@Component
@Slf4j
public class MySingleGatewayFilterFactory extends AbstractGatewayFilterFactory {
     

    @Override
    public GatewayFilter apply(Object config) {
     
        return new MyTestGatewayFilter();
    }


    public class MyTestGatewayFilter implements GatewayFilter, Ordered {
     

        @Override
        public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
     
            log.info("111111111111111111");
            return chain.filter(exchange);
        }

        @Override
        public int getOrder() {
     
            return 0;
        }
    }
}

5.源码分析

在分析源码之前,猜测一下gateway的运行原理

gateway(源码分析) _ JavaClub全栈架构师技术笔记

  1. 请求先进入 Gateway Handler mappeing 中解析出对应的路由 Route 和 Predicates
  2. 再进入 Gateway Web Handler 中转发到不同的服务
  3. 在发送到不同的服务之前和之后都有对应的过滤器来对请求进行处理

以上都是猜测,进行源码分析

5.1 入口(初始化自动配置类)

查看gateway core包的Spring.factories文件

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
#用来验证项目中是否存在DispatcherServlet(SpringMvc使用,webFlux中不能存在) 是否不存在DispatcherHandler(WebFlux使用)
org.springframework.cloud.gateway.config.GatewayClassPathWarningAutoConfiguration,\
#核心配置类 配置gateway Route Filter 等相关bean
org.springframework.cloud.gateway.config.GatewayAutoConfiguration,\
#负载均衡配置
org.springframework.cloud.gateway.config.GatewayLoadBalancerClientAutoConfiguration,\
org.springframework.cloud.gateway.config.GatewayMetricsAutoConfiguration,\
#缓存相关配置
org.springframework.cloud.gateway.config.GatewayRedisAutoConfiguration,\
#服务发现相关配置 gateway支持从注册中心查询服务并生成对应路由
org.springframework.cloud.gateway.discovery.GatewayDiscoveryClientAutoConfiguration

org.springframework.boot.env.EnvironmentPostProcessor=\
org.springframework.cloud.gateway.config.GatewayEnvironmentPostProcessor

5.1.1 GatewayClassPathWarningAutoConfiguration

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.cloud.gateway.config;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass;
import org.springframework.context.annotation.Configuration;

@Configuration
@AutoConfigureBefore({
     GatewayAutoConfiguration.class})
public class GatewayClassPathWarningAutoConfiguration {
     
    private static final Log log = LogFactory.getLog(GatewayClassPathWarningAutoConfiguration.class);
    private static final String BORDER = "\n\n**********************************************************\n\n";

    public GatewayClassPathWarningAutoConfiguration() {
     
    }

    @Configuration
    @ConditionalOnMissingClass({
     "org.springframework.web.reactive.DispatcherHandler"})
    protected static class WebfluxMissingFromClasspathConfiguration {
     
        public WebfluxMissingFromClasspathConfiguration() {
     
            GatewayClassPathWarningAutoConfiguration.log.warn("\n\n**********************************************************\n\nSpring Webflux is missing from the classpath, which is required for Spring Cloud Gateway at this time. Please add spring-boot-starter-webflux dependency.\n\n**********************************************************\n\n");
        }
    }

    @Configuration
    @ConditionalOnClass(
        name = {
     "org.springframework.web.servlet.DispatcherServlet"}
    )
    protected static class SpringMvcFoundOnClasspathConfiguration {
     
        public SpringMvcFoundOnClasspathConfiguration() {
     
            GatewayClassPathWarningAutoConfiguration.log.warn("\n\n**********************************************************\n\nSpring MVC found on classpath, which is incompatible with Spring Cloud Gateway at this time. Please remove spring-boot-starter-web dependency.\n\n**********************************************************\n\n");
        }
    }
}

在项目启动时监测是否有 org.springframework.web.servlet.DispatcherServlet 这个类是SpringMvc使用,不能在Gateway中使用,因为Gateway是基于WebFlux的,同理,项目中必须有 org.springframework.web.reactive.DispatcherHandler

5.1.2 GatewayAutoConfiguration

gateway核心配置类,在此配置类中注入了Gateway相关的路由,过滤器相关bean

5.1.2.1 Route的加载过程
5.1.2.1.1 PropertiesRouteDefinitionLocator

从配置文件中获取路由信息

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.cloud.gateway.config;

import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionLocator;
import reactor.core.publisher.Flux;

public class PropertiesRouteDefinitionLocator implements RouteDefinitionLocator {
     
    private final GatewayProperties properties;

    public PropertiesRouteDefinitionLocator(GatewayProperties properties) {
     
        this.properties = properties;
    }

    public Flux<RouteDefinition> getRouteDefinitions() {
     
        //这里调用的GatewayProperties中的getRoutes方法,也就是读取了配置文件中的路由信息
        return Flux.fromIterable(this.properties.getRoutes());
    }
}

5.1.2.1.2 RouteDefinitionRouteLocator

路由定义类(RouteDefinition) 转换为路由类(Route)

    private Route convertToRoute(RouteDefinition routeDefinition) {
     
        AsyncPredicate<ServerWebExchange> predicate = this.combinePredicates(routeDefinition);
        List<GatewayFilter> gatewayFilters = this.getFilters(routeDefinition);
        return ((AsyncBuilder)Route.async(routeDefinition).asyncPredicate(predicate).replaceFilters(gatewayFilters)).build();
    }
5.1.2.1.3 CompositeRouteDefinitionLocator

这个类是对RouteDefinitionLocator的包装,就是从 PropertiesRouteDefinitionLocator(配置文件中读取) InMemoryRouteDefinitionRepository(内存中读取) CachingRouteDefinitionLocator(缓存中读取)

DiscoveryClientRouteDefinitionLocator(注册中心服务发现中获取路由)

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.cloud.gateway.route;

import reactor.core.publisher.Flux;

public class CompositeRouteDefinitionLocator implements RouteDefinitionLocator {
     
    private final Flux<RouteDefinitionLocator> delegates;

    public CompositeRouteDefinitionLocator(Flux<RouteDefinitionLocator> delegates) {
     
        this.delegates = delegates;
    }

    public Flux<RouteDefinition> getRouteDefinitions() {
     
        //调用RouteDefinitionLocator的各个实现类的getRouteDefinitions获取路由定义
        return this.delegates.flatMap(RouteDefinitionLocator::getRouteDefinitions);
    }
}

5.1.2.1.4 CachingRouteLocator

是对RouteLocator的封装,直接从内存中读取路由信息

  public CachingRouteLocator(RouteLocator delegate) {
     
        this.delegate = delegate;
        this.routes = CacheFlux.lookup(this.cache, "routes", Route.class).onCacheMissResume(() -> {
     
            return this.delegate.getRoutes().sort(AnnotationAwareOrderComparator.INSTANCE);
        });
    }

    public Flux<Route> getRoutes() {
     
        return this.routes;
    }
5.1.2.1.5 InMemoryRouteDefinitionRepository

默认可以从内存中获取路由,或者可以自己实现 RouteDefinitionRepository,从数据库等媒介中读取路由信息

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.cloud.gateway.route;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import org.springframework.cloud.gateway.support.NotFoundException;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public class InMemoryRouteDefinitionRepository implements RouteDefinitionRepository {
     
    private final Map<String, RouteDefinition> routes = Collections.synchronizedMap(new LinkedHashMap());

    public InMemoryRouteDefinitionRepository() {
     
    }

    public Mono<Void> save(Mono<RouteDefinition> route) {
     
        return route.flatMap((r) -> {
     
            this.routes.put(r.getId(), r);
            return Mono.empty();
        });
    }

    public Mono<Void> delete(Mono<String> routeId) {
     
        return routeId.flatMap((id) -> {
     
            if (this.routes.containsKey(id)) {
     
                this.routes.remove(id);
                return Mono.empty();
            } else {
     
                return Mono.defer(() -> {
     
                    return Mono.error(new NotFoundException("RouteDefinition not found: " + routeId));
                });
            }
        });
    }

    public Flux<RouteDefinition> getRouteDefinitions() {
     
        return Flux.fromIterable(this.routes.values());
    }
}

5.1.3 GatewayLoadBalancerClientAutoConfiguration

负载均衡过滤器配置

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.cloud.gateway.config;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.loadbalancer.LoadBalancerClient;
import org.springframework.cloud.gateway.filter.LoadBalancerClientFilter;
import org.springframework.cloud.netflix.ribbon.RibbonAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;

@Configuration
@ConditionalOnClass({
     LoadBalancerClient.class, RibbonAutoConfiguration.class, DispatcherHandler.class})
@AutoConfigureAfter({
     RibbonAutoConfiguration.class})
@EnableConfigurationProperties({
     LoadBalancerProperties.class})
public class GatewayLoadBalancerClientAutoConfiguration {
     
    public GatewayLoadBalancerClientAutoConfiguration() {
     
    }

    @Bean
    @ConditionalOnBean({
     LoadBalancerClient.class})
    @ConditionalOnMissingBean({
     LoadBalancerClientFilter.class})
    public LoadBalancerClientFilter loadBalancerClientFilter(LoadBalancerClient client, LoadBalancerProperties properties) {
     
        return new LoadBalancerClientFilter(client, properties);
    }
}

注入LoadBalancerClientFilter 过滤器,引入RibbonLoadBalanceClient (gateway也默认引入了Ribbon)

点进去看下 LoadBalancerClientFilter 的作用

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
     
        URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
        if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {
     
            ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
            log.trace("LoadBalancerClientFilter url before: " + url);
            ServiceInstance instance = this.choose(exchange);
            if (instance == null) {
     
                String msg = "Unable to find instance for " + url.getHost();
                if (this.properties.isUse404()) {
     
                    throw new LoadBalancerClientFilter.FourOFourNotFoundException(msg);
                } else {
     
                    throw new NotFoundException(msg);
                }
            } else {
     
                URI uri = exchange.getRequest().getURI();
                String overrideScheme = instance.isSecure() ? "https" : "http";
                if (schemePrefix != null) {
     
                    overrideScheme = url.getScheme();
                }

                URI requestUrl = this.loadBalancer.reconstructURI(new LoadBalancerClientFilter.DelegatingServiceInstance(instance, overrideScheme), uri);
                log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
                return chain.filter(exchange);
            }
        } else {
     
            return chain.filter(exchange);
        }
    }

这个过滤器的作用就是判断路由的uri是不是包含 lb:开头

如果是,就使用ribbon负载均衡去转发请求

5.1.4 GatewayMetricsAutoConfiguration

注入监控相关filter

5.1.5 GatewayRedisAutoConfiguration

缓存注入

5.1.6 GatewayDiscoveryClientAutoConfiguration

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.springframework.cloud.gateway.discovery;

import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.client.discovery.DiscoveryClient;
import org.springframework.cloud.client.discovery.composite.CompositeDiscoveryClientAutoConfiguration;
import org.springframework.cloud.gateway.config.GatewayAutoConfiguration;
import org.springframework.cloud.gateway.filter.FilterDefinition;
import org.springframework.cloud.gateway.filter.factory.RewritePathGatewayFilterFactory;
import org.springframework.cloud.gateway.handler.predicate.PathRoutePredicateFactory;
import org.springframework.cloud.gateway.handler.predicate.PredicateDefinition;
import org.springframework.cloud.gateway.support.NameUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.DispatcherHandler;

@Configuration
@ConditionalOnProperty(
    name = {
     "spring.cloud.gateway.enabled"},
    matchIfMissing = true
)
@AutoConfigureBefore({
     GatewayAutoConfiguration.class})
@AutoConfigureAfter({
     CompositeDiscoveryClientAutoConfiguration.class})
@ConditionalOnClass({
     DispatcherHandler.class, DiscoveryClient.class})
@EnableConfigurationProperties
public class GatewayDiscoveryClientAutoConfiguration {
     
    public GatewayDiscoveryClientAutoConfiguration() {
     
    }

    @Bean
    @ConditionalOnBean({
     DiscoveryClient.class})
    @ConditionalOnProperty(
        name = {
     "spring.cloud.gateway.discovery.locator.enabled"}
    )
    public DiscoveryClientRouteDefinitionLocator discoveryClientRouteDefinitionLocator(DiscoveryClient discoveryClient, DiscoveryLocatorProperties properties) {
     
        return new DiscoveryClientRouteDefinitionLocator(discoveryClient, properties);
    }

    @Bean
    public DiscoveryLocatorProperties discoveryLocatorProperties() {
     
        DiscoveryLocatorProperties properties = new DiscoveryLocatorProperties();
        properties.setPredicates(initPredicates());
        properties.setFilters(initFilters());
        return properties;
    }

    public static List<PredicateDefinition> initPredicates() {
     
        ArrayList<PredicateDefinition> definitions = new ArrayList();
        PredicateDefinition predicate = new PredicateDefinition();
        predicate.setName(NameUtils.normalizeRoutePredicateName(PathRoutePredicateFactory.class));
        predicate.addArg("pattern", "'/'+serviceId+'/**'");
        definitions.add(predicate);
        return definitions;
    }

    public static List<FilterDefinition> initFilters() {
     
        ArrayList<FilterDefinition> definitions = new ArrayList();
        FilterDefinition filter = new FilterDefinition();
        filter.setName(NameUtils.normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));
        String regex = "'/' + serviceId + '/(?<remaining>.*)'";
        String replacement = "'/${remaining}'";
        filter.addArg("regexp", regex);
        filter.addArg("replacement", replacement);
        definitions.add(filter);
        return definitions;
    }
}

当接入有注册中心时,并在配置文件中开启 spring.cloud.gateway.enabled 后,gateway会从注册中心中查询所有服务,并创建对应的路由,默认断言是 PathRoutePredicateFactory “pattern”, “’/’+serviceId+’/**’”

5.2 请求处理

5.2.1 DispatcherHandler 请求分发

服务启动,首先进入 DispatcherHandler (类似springmvc的DispatcherServlet) 的 initStrategies 方法,初始化所有的HandlerMapping放入上下文

当有请求进入网关时,同样也是进入DispatcherHandler,DispatcherHandler执行handle方法

 protected void initStrategies(ApplicationContext context) {
     
     	//获取所有HandlerMapping接口的实现类
        Map<String, HandlerMapping> mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
     	//转换成List
        ArrayList<HandlerMapping> mappings = new ArrayList(mappingBeans.values());
     	//排序(Order接口)
        AnnotationAwareOrderComparator.sort(mappings);
     	//将转换后的Mappings转换为一个不能修改,只读的List
        this.handlerMappings = Collections.unmodifiableList(mappings);
     	//获取所有HandlerAdapter的实现类
        Map<String, HandlerAdapter> adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
     	//转换为list
        this.handlerAdapters = new ArrayList(adapterBeans.values());
     	//排序
        AnnotationAwareOrderComparator.sort(this.handlerAdapters);
     	//获取所有HandlerResultHandler的实现类
        Map<String, HandlerResultHandler> beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerResultHandler.class, true, false);
     	//转为list
        this.resultHandlers = new ArrayList(beans.values());
     	//排序
        AnnotationAwareOrderComparator.sort(this.resultHandlers);
    }

	//请求的处理方法
    public Mono<Void> handle(ServerWebExchange exchange) {
     
        //如果初始化的方法里handlerMappings不为空,就执行handlerMapping的getHandler方法
        return this.handlerMappings == null ? this.createNotFoundError() : Flux.fromIterable(this.handlerMappings).concatMap((mapping) -> {
     
            return mapping.getHandler(exchange);
        }).next().switchIfEmpty(this.createNotFoundError()).flatMap((handler) -> {
     
            return this.invokeHandler(exchange, handler);
        }).flatMap((result) -> {
     
            return this.handleResult(exchange, result);
        });
    }

进入handlerMapping实现类AbstractHandlerMappinggetHandler方法

这个方法是用来获取所有的handler的

public Mono<Object> getHandler(ServerWebExchange exchange) {
     
    	//函数式编程获取getHandlerInternal
        return this.getHandlerInternal(exchange).map((handler) -> {
     
            if (this.logger.isDebugEnabled()) {
     
                this.logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);
            }

            if (CorsUtils.isCorsRequest(exchange.getRequest())) {
     
                CorsConfiguration configA = this.corsConfigurationSource.getCorsConfiguration(exchange);
                CorsConfiguration configB = this.getCorsConfiguration(handler, exchange);
                CorsConfiguration config = configA != null ? configA.combine(configB) : configB;
                if (!this.getCorsProcessor().process(config, exchange) || CorsUtils.isPreFlightRequest(exchange.getRequest())) {
     
                    return REQUEST_HANDLED_HANDLER;
                }
            }

            return handler;
        });
    }
//抽象方法 由每个继承类定义具体的实现
protected abstract Mono<?> getHandlerInternal(ServerWebExchange var1);

5.2.2 RoutePredicateHandlerMapping 获取路由

再看 getHandlerInternal 方法

进入AbstractHandlerMapping的实现类 RoutePredicateHandlerMapping

这个类是通过断言来过滤符合条件的路由,并转发到处理类

protected Mono<?> getHandlerInternal(ServerWebExchange exchange) {
     
    //判断请求的端口是否是健康检查端口
        if (this.managmentPort != null && exchange.getRequest().getURI().getPort() == this.managmentPort) {
     
            return Mono.empty();
        } else {
     
            //把RoutePredicateHandlerMapping这个handlerMaping放入exchange上下文中
            exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_HANDLER_MAPPER_ATTR, this.getSimpleName());
            //lookupRoute过滤出符合处理请求的路由
            return this.lookupRoute(exchange).flatMap((r) -> {
     
                exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
                if (this.logger.isDebugEnabled()) {
     
                    this.logger.debug("Mapping [" + this.getExchangeDesc(exchange) + "] to " + r);
                }
				//把符合条件的路由放入exchange上下文中
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR, r);
                //交给webHandler处理
                return Mono.just(this.webHandler);
            }).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {
     
                exchange.getAttributes().remove(ServerWebExchangeUtils.GATEWAY_PREDICATE_ROUTE_ATTR);
                if (this.logger.isTraceEnabled()) {
     
                    this.logger.trace("No RouteDefinition found for [" + this.getExchangeDesc(exchange) + "]");
                }

            })));
        }
    }

5.2.3 FilteringWebHandler 过滤

上文中获取到路由之后,就需要对请求进行过滤处理

除了全局过滤器外,还要获取路由中定义的网关过滤器

//构造函数中对全局过滤器进行处理
public FilteringWebHandler(List<GlobalFilter> globalFilters) {
     
    this.globalFilters = loadFilters(globalFilters);
}

//处理全局过滤器
private static List<GatewayFilter> loadFilters(List<GlobalFilter> filters) {
     
    //对GlobalFilter进行包装 如果实现了Ordered接口(有排序),就包装成OrderedGatewayFilter 如果没有实现排序接口,就用gatewayFilter包装
    return (List)filters.stream().map((filter) -> {
     
        FilteringWebHandler.GatewayFilterAdapter gatewayFilter = new FilteringWebHandler.GatewayFilterAdapter(filter);
        if (filter instanceof Ordered) {
     
            int order = ((Ordered)filter).getOrder();
            return new OrderedGatewayFilter(gatewayFilter, order);
        } else {
     
            return gatewayFilter;
        }
    }).collect(Collectors.toList());
}

//过滤链处理
public Mono<Void> handle(ServerWebExchange exchange) {
     
    //获取exchange上下文中的route路由信息
    Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);
    //获取路由中的过滤器
    List<GatewayFilter> gatewayFilters = route.getFilters();
    //全局过滤器
    List<GatewayFilter> combined = new ArrayList(this.globalFilters);
    //全局过滤器加上路由中的过滤器
    combined.addAll(gatewayFilters);
    //排序
    AnnotationAwareOrderComparator.sort(combined);
    if (logger.isDebugEnabled()) {
     
        logger.debug("Sorted gatewayFilterFactories: " + combined);
    }

    //递归调用此类中的内部类DefaultGatewayFilterChain过滤器链进行过滤处理
    return (new FilteringWebHandler.DefaultGatewayFilterChain(combined)).filter(exchange);
}


递归调用

private static class DefaultGatewayFilterChain implements GatewayFilterChain {
     
        private final int index;
        private final List<GatewayFilter> filters;

        public DefaultGatewayFilterChain(List<GatewayFilter> filters) {
     
            this.filters = filters;
            this.index = 0;
        }

        private DefaultGatewayFilterChain(FilteringWebHandler.DefaultGatewayFilterChain parent, int index) {
     
            this.filters = parent.getFilters();
            this.index = index;
        }

        public List<GatewayFilter> getFilters() {
     
            return this.filters;
        }

        public Mono<Void> filter(ServerWebExchange exchange) {
     
            return Mono.defer(() -> {
     
                if (this.index < this.filters.size()) {
     
                    GatewayFilter filter = (GatewayFilter)this.filters.get(this.index);
                    FilteringWebHandler.DefaultGatewayFilterChain chain = new FilteringWebHandler.DefaultGatewayFilterChain(this, this.index + 1);
                    return filter.filter(exchange, chain);
                } else {
     
                    return Mono.empty();
                }
            });
        }
    }

5.2.4 转发请求

转发请求由两个全局过滤器实现

NettyRoutingFilterLoadBalancerClientFilter

5.2.4.1 LoadBalancerClientFilter

如果路由的uri是lb开头,就会进入负载均衡从注册中心获取地址

否则进入下一个过滤器

public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
     
        URI url = (URI)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        String schemePrefix = (String)exchange.getAttribute(ServerWebExchangeUtils.GATEWAY_SCHEME_PREFIX_ATTR);
        if (url != null && ("lb".equals(url.getScheme()) || "lb".equals(schemePrefix))) {
     
            ServerWebExchangeUtils.addOriginalRequestUrl(exchange, url);
            log.trace("LoadBalancerClientFilter url before: " + url);
            ServiceInstance instance = this.choose(exchange);
            if (instance == null) {
     
                String msg = "Unable to find instance for " + url.getHost();
                if (this.properties.isUse404()) {
     
                    throw new LoadBalancerClientFilter.FourOFourNotFoundException(msg);
                } else {
     
                    throw new NotFoundException(msg);
                }
            } else {
     
                URI uri = exchange.getRequest().getURI();
                String overrideScheme = instance.isSecure() ? "https" : "http";
                if (schemePrefix != null) {
     
                    overrideScheme = url.getScheme();
                }

                URI requestUrl = this.loadBalancer.reconstructURI(new LoadBalancerClientFilter.DelegatingServiceInstance(instance, overrideScheme), uri);
                log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);
                exchange.getAttributes().put(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR, requestUrl);
                return chain.filter(exchange);
            }
        } else {
     
            return chain.filter(exchange);
        }
    }
5.2.4.2 NettyRoutingFilter

如果是http开头或者https开头就通过这个过滤器来转发请求

 public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
     
        URI requestUrl = (URI)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_REQUEST_URL_ATTR);
        String scheme = requestUrl.getScheme();
        if (!ServerWebExchangeUtils.isAlreadyRouted(exchange) && ("http".equals(scheme) || "https".equals(scheme))) {
     
            ServerWebExchangeUtils.setAlreadyRouted(exchange);
            ServerHttpRequest request = exchange.getRequest();
            HttpMethod method = HttpMethod.valueOf(request.getMethodValue());
            String url = requestUrl.toString();
            HttpHeaders filtered = HttpHeadersFilter.filterRequest((List)this.headersFilters.getIfAvailable(), exchange);
            DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();
            filtered.forEach(httpHeaders::set);
            String transferEncoding = request.getHeaders().getFirst("Transfer-Encoding");
            boolean chunkedTransfer = "chunked".equalsIgnoreCase(transferEncoding);
            boolean preserveHost = (Boolean)exchange.getAttributeOrDefault(ServerWebExchangeUtils.PRESERVE_HOST_HEADER_ATTRIBUTE, false);
            Flux<HttpClientResponse> responseFlux = ((RequestSender)this.httpClient.chunkedTransfer(chunkedTransfer).request(method).uri(url)).send((req, nettyOutbound) -> {
     
                req.headers(httpHeaders);
                if (preserveHost) {
     
                    String host = request.getHeaders().getFirst("Host");
                    req.header("Host", host);
                }

                return nettyOutbound.options(SendOptions::flushOnEach).send(request.getBody().map((dataBuffer) -> {
     
                    return ((NettyDataBuffer)dataBuffer).getNativeBuffer();
                }));
            }).responseConnection((res, connection) -> {
     
                ServerHttpResponse response = exchange.getResponse();
                HttpHeaders headers = new HttpHeaders();
                res.responseHeaders().forEach((entry) -> {
     
                    headers.add((String)entry.getKey(), (String)entry.getValue());
                });
                String contentTypeValue = headers.getFirst("Content-Type");
                if (StringUtils.hasLength(contentTypeValue)) {
     
                    exchange.getAttributes().put("original_response_content_type", contentTypeValue);
                }

                HttpStatus status = HttpStatus.resolve(res.status().code());
                if (status != null) {
     
                    response.setStatusCode(status);
                } else {
     
                    if (!(response instanceof AbstractServerHttpResponse)) {
     
                        throw new IllegalStateException("Unable to set status code on response: " + res.status().code() + ", " + response.getClass());
                    }

                    ((AbstractServerHttpResponse)response).setStatusCodeValue(res.status().code());
                }

                HttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter((List)this.headersFilters.getIfAvailable(), headers, exchange, Type.RESPONSE);
                if (!filteredResponseHeaders.containsKey("Transfer-Encoding") && filteredResponseHeaders.containsKey("Content-Length")) {
     
                    response.getHeaders().remove("Transfer-Encoding");
                }

                exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_HEADER_NAMES, filteredResponseHeaders.keySet());
                response.getHeaders().putAll(filteredResponseHeaders);
                exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_ATTR, res);
                exchange.getAttributes().put(ServerWebExchangeUtils.CLIENT_RESPONSE_CONN_ATTR, connection);
                return Mono.just(res);
            });
            if (this.properties.getResponseTimeout() != null) {
     
                responseFlux = responseFlux.timeout(this.properties.getResponseTimeout(), Mono.error(new TimeoutException("Response took longer than timeout: " + this.properties.getResponseTimeout()))).onErrorMap(TimeoutException.class, (th) -> {
     
                    return new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT, (String)null, th);
                });
            }

            return responseFlux.then(chain.filter(exchange));
        } else {
     
            return chain.filter(exchange);
        }
    }

6.思维导图

gateway(源码分析) _ JavaClub全栈架构师技术笔记

作者:zhang.yao
来源链接:https://blog.csdn.net/white_bird_shit/article/details/107476650

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/server/68082.html

标签: Gateway
分享给朋友:

“gateway(源码分析)” 的相关文章

SpringCloud Gateway入门

本文是介绍一下SpringCloud Gateway简单路由转发使用。 SpringCloud Gateway简介 SpringCloud是基于Spring Framework 5,Project Reactor和Spring Boot 2.0构建,目标是...

体验SpringCloud Gateway

体验SpringCloud Gateway

Spring Cloud Gateway是Spring Cloud技术栈中的网关服务,本文实战构建一个SpringCloud环境,并开发一个SpringCloud Gateway应用,快速体验网关服务; 环境信息 操作系统:win10(64位) JDK:1...

SpringCloud + Consul服务注册中心 + gateway网关

  1  启动Consul 2  创建springcloud-consul项目及三个子模块        2.1 数据模块consul-producer      ...

跟我学SpringCloud | 第十二篇:Spring Cloud Gateway初探

跟我学SpringCloud | 第十二篇:Spring Cloud Gateway初探

SpringCloud系列教程 | 第十二篇:Spring Cloud Gateway初探 Springboot: 2.1.6.RELEASE SpringCloud: Greenwich.SR1 如无特殊说明,本系列教程...

SpringCloud Gateway跨域配置

Springboot版本:2.1.8.RELEASE SpringCloud版本:Greenwich.SR2 yml配置: spring: cloud: gateway: globalcors: cors-config...

SpringCloud系列之网关(Gateway)应用篇

SpringCloud系列之网关(Gateway)应用篇

@ 目录 前言 项目版本 网关访问 鉴权配置 限流配置 前言 由于项目采用了微服务架构,业务功能都在相应各自的模块中,每个业务模块都是以独立的项目运行着,对外提供各自的服务接口...

微服务网关springcloud gateway整合sentinel自定义异常

异常包括限流异常和熔断降级异常。 1. 定义配置类方式 package com.ftsafe.config; import com.alibaba.csp.sentinel.adapter.gateway.sc.callback.BlockRequestHandle...

最新版本SpringCloud Gateway网关入门(一)

最新版本SpringCloud Gateway网关入门(一)

文章目录 背景 SpringCloud Gateway 简介 Gateway入门搭建 1. 创建一个SpringBoot...

GateWay路由网关的概述与入门详解

GateWay路由网关的概述与入门详解

一、概述简介 官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/ 1.是什么? Cloud全家桶中...

Springcloud gateway (史上最全)

Springcloud gateway (史上最全)

文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 语雀版 | 总目录 码云版| 总目录 博客园版 为您奉上珍贵的学习资源 : 免费赠送 :《尼恩Java面试宝典》持续更新+ 史上最全 + 面试必备 2000页+ 面试必备...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。