JAVA—Spring—SpringCloud—gateway

本文涉及的产品
传统型负载均衡 CLB,每月750个小时 15LCU
应用型负载均衡 ALB,每月750个小时 15LCU
网络型负载均衡 NLB,每月750个小时 15LCU
简介: 1. 什么是gateway官网解释: 该项目提供了一个在Spring生态系统之上构建的API网关,包括:Spring 5,Spring Boot 2和Project Reactor。Spring Cloud Gateway旨在提供一种简单而有效的方法来路由到API,并为它们提供跨领域的关注,例如:安全性,监视/指标和弹性。

为什么使用gateway


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


2.提供路由分发功能


2. 工作原理图

image.png

3. 简单使用


3.1 引入jar包


     

     

           org.springframework.cloud

           spring-cloud-starter-gateway

     

     

     

           com.alibaba.cloud

           spring-cloud-starter-alibaba-nacos-discovery

     

     

           com.alibaba.cloud

           spring-cloud-starter-alibaba-nacos-config

     


     

     

           org.springframework.boot

           spring-boot-starter-actuator

     


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 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 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 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 filter(ServerWebExchange exchange, GatewayFilterChain chain) {

           log.info("111111111111111111");

           return chain.filter(exchange);

       }


       @Override

       public int getOrder() {

           return 0;

       }

   }

}


5.源码分析


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

image.png

  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 getRouteDefinitions() {

       //这里调用的GatewayProperties中的getRoutes方法,也就是读取了配置文件中的路由信息

       return Flux.fromIterable(this.properties.getRoutes());

   }

}


5.1.2.1.2 RouteDefinitionRouteLocator


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


   private Route convertToRoute(RouteDefinition routeDefinition) {

       AsyncPredicate predicate = this.combinePredicates(routeDefinition);

       List 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 delegates;


   public CompositeRouteDefinitionLocator(Flux delegates) {

       this.delegates = delegates;

   }


   public Flux 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 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 routes = Collections.synchronizedMap(new LinkedHashMap());


   public InMemoryRouteDefinitionRepository() {

   }


   public Mono save(Mono route) {

       return route.flatMap((r) -> {

           this.routes.put(r.getId(), r);

           return Mono.empty();

       });

   }


   public Mono delete(Mono 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 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 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 initPredicates() {

       ArrayList 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 initFilters() {

       ArrayList definitions = new ArrayList();

       FilterDefinition filter = new FilterDefinition();

       filter.setName(NameUtils.normalizeFilterFactoryName(RewritePathGatewayFilterFactory.class));

       String regex = "'/' + serviceId + '/(?.*)'";

       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 mappingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);

    //转换成List

       ArrayList mappings = new ArrayList(mappingBeans.values());

    //排序(Order接口)

       AnnotationAwareOrderComparator.sort(mappings);

    //将转换后的Mappings转换为一个不能修改,只读的List

       this.handlerMappings = Collections.unmodifiableList(mappings);

    //获取所有HandlerAdapter的实现类

       Map adapterBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);

    //转换为list

       this.handlerAdapters = new ArrayList(adapterBeans.values());

    //排序

       AnnotationAwareOrderComparator.sort(this.handlerAdapters);

    //获取所有HandlerResultHandler的实现类

       Map beans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerResultHandler.class, true, false);

    //转为list

       this.resultHandlers = new ArrayList(beans.values());

    //排序

       AnnotationAwareOrderComparator.sort(this.resultHandlers);

   }


//请求的处理方法

   public Mono 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 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 globalFilters) {

   this.globalFilters = loadFilters(globalFilters);

}


//处理全局过滤器

private static List loadFilters(List 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 handle(ServerWebExchange exchange) {

   //获取exchange上下文中的route路由信息

   Route route = (Route)exchange.getRequiredAttribute(ServerWebExchangeUtils.GATEWAY_ROUTE_ATTR);

   //获取路由中的过滤器

   List gatewayFilters = route.getFilters();

   //全局过滤器

   List 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 filters;


       public DefaultGatewayFilterChain(List filters) {

           this.filters = filters;

           this.index = 0;

       }


       private DefaultGatewayFilterChain(FilteringWebHandler.DefaultGatewayFilterChain parent, int index) {

           this.filters = parent.getFilters();

           this.index = index;

       }


       public List getFilters() {

           return this.filters;

       }


       public Mono 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 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 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 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);

       }

   }

image.png



相关实践学习
SLB负载均衡实践
本场景通过使用阿里云负载均衡 SLB 以及对负载均衡 SLB 后端服务器 ECS 的权重进行修改,快速解决服务器响应速度慢的问题
负载均衡入门与产品使用指南
负载均衡(Server Load Balancer)是对多台云服务器进行流量分发的负载均衡服务,可以通过流量分发扩展应用系统对外的服务能力,通过消除单点故障提升应用系统的可用性。 本课程主要介绍负载均衡的相关技术以及阿里云负载均衡产品的使用方法。
目录
相关文章
|
21天前
|
设计模式 Java 关系型数据库
【Java笔记+踩坑汇总】Java基础+JavaWeb+SSM+SpringBoot+SpringCloud+瑞吉外卖/谷粒商城/学成在线+设计模式+面试题汇总+性能调优/架构设计+源码解析
本文是“Java学习路线”专栏的导航文章,目标是为Java初学者和初中高级工程师提供一套完整的Java学习路线。
176 37
|
21天前
|
缓存 前端开发 Java
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
Soring Boot的起步依赖、启动流程、自动装配、常用的注解、Spring MVC的执行流程、对MVC的理解、RestFull风格、为什么service层要写接口、MyBatis的缓存机制、$和#有什么区别、resultType和resultMap区别、cookie和session的区别是什么?session的工作原理
【Java面试题汇总】Spring,SpringBoot,SpringMVC,Mybatis,JavaWeb篇(2023版)
|
21天前
|
缓存 Java 数据库
【Java面试题汇总】Spring篇(2023版)
IoC、DI、aop、事务、为什么不建议@Transactional、事务传播级别、@Autowired和@Resource注解的区别、BeanFactory和FactoryBean的区别、Bean的作用域,以及默认的作用域、Bean的生命周期、循环依赖、三级缓存、
【Java面试题汇总】Spring篇(2023版)
|
11天前
|
人工智能 开发框架 Java
重磅发布!AI 驱动的 Java 开发框架:Spring AI Alibaba
随着生成式 AI 的快速发展,基于 AI 开发框架构建 AI 应用的诉求迅速增长,涌现出了包括 LangChain、LlamaIndex 等开发框架,但大部分框架只提供了 Python 语言的实现。但这些开发框架对于国内习惯了 Spring 开发范式的 Java 开发者而言,并非十分友好和丝滑。因此,我们基于 Spring AI 发布并快速演进 Spring AI Alibaba,通过提供一种方便的 API 抽象,帮助 Java 开发者简化 AI 应用的开发。同时,提供了完整的开源配套,包括可观测、网关、消息队列、配置中心等。
557 8
|
22天前
|
Java 数据库连接 API
【Java笔记+踩坑】Spring Data JPA
从常用注解、实体类和各层编写方法入手,详细介绍JPA框架在增删改查等方面的基本用法,以及填充用户名日期、分页查询等高级用法。
【Java笔记+踩坑】Spring Data JPA
|
22天前
|
Java 数据库连接 数据格式
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
IOC/DI配置管理DruidDataSource和properties、核心容器的创建、获取bean的方式、spring注解开发、注解开发管理第三方bean、Spring整合Mybatis和Junit
【Java笔记+踩坑】Spring基础2——IOC,DI注解开发、整合Mybatis,Junit
|
2月前
|
Java 前端开发 Apache
Apache Wicket与Spring MVC等Java Web框架大PK,究竟谁才是你的最佳拍档?点击揭秘!
【8月更文挑战第31天】在Java Web开发领域,众多框架各具特色。Apache Wicket以组件化开发和易用性脱颖而出,提高了代码的可维护性和可读性。相比之下,Spring MVC拥有强大的生态系统,但学习曲线较陡;JSF与Java EE紧密集成,但在性能和灵活性上略逊一筹;Struts2虽成熟,但在RESTful API支持上不足。选择框架时还需考虑社区支持和文档完善程度。希望本文能帮助开发者找到最适合自己的框架。
31 0
|
22天前
|
负载均衡 Java Nacos
SpringCloud基础2——Nacos配置、Feign、Gateway
nacos配置管理、Feign远程调用、Gateway服务网关
SpringCloud基础2——Nacos配置、Feign、Gateway
|
7天前
|
Java 开发者 Spring
Spring Cloud Gateway 中,过滤器的分类有哪些?
Spring Cloud Gateway 中,过滤器的分类有哪些?
15 3
|
8天前
|
负载均衡 Java 网络架构
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
实现微服务网关:Zuul与Spring Cloud Gateway的比较分析
21 5
下一篇
无影云桌面