官网:https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/
注册中心使用的Eureka
1.创建模块
1.1创建模块cloud-gateway-gateway9527
1.2pom文件
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <parent> <artifactId>cloud2020</artifactId> <groupId>com.atguigu.springcloud</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>cloud-gateway-gateway9527</artifactId> <dependencies> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency> <dependency> <groupId>com.atguigu.springcloud</groupId> <artifactId>cloud-api-commons</artifactId> <version>1.0-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-hystrix</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-test --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> </project>
1.3yml文件
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:8001 #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - id: payment_routh2 #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:8001 #匹配后提供服务的路由地址 predicates: - Path=/payment/get1/** #断言,路径相匹配的进行路由 eureka: instance: hostname: cloud-gateway-service client: service-url: register-with-eureka: true fetch-registry: true defaultZone: http://eureka7001.com:7001/eureka
关于gateway的route的配置说明
id:路由的id,随便取,不要重复
uri:实际的服务提供者的地址,也就是需要路由过去的地址
predicates:断言,访问9527时,判断路径是否符合断言的规则。是,则进行路由。如:访问http://localhost:9527/payment/get/1。符合断言的匹配规则,此时进行路由,路由到http://localhost:8001/payment/get/1
1.4主启动类
package com.atguigu.springcloud; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.cloud.netflix.eureka.EnableEurekaClient; @SpringBootApplication @EnableEurekaClient public class GatewayMain9527 { public static void main(String[] args) { SpringApplication.run(GatewayMain9527.class,args); } }
1.5测试
访问http://localhost:9527/payment/get/1
2.使用编码来配置gateway路由断言
这是上面在yml文件进行配置
cloud: gateway: routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://localhost:8001 #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由
也可以不这样配置,采用编码的方式来配置
package com.atguigu.springcloud.config; import org.springframework.cloud.gateway.route.RouteLocator; import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @Classname GateWayConfig * @Description TODO * @Date 2021/5/14 0014 下午 2:39 * @Created by jcc */ @Configuration public class GateWayConfig { /* 这里的配置相当于yml里面的配置 gateway: routes: - id: path_rote_atguigu #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: http://news.baidu.com #匹配后提供服务的路由地址---这里访问的是百度的新闻网站-用外网做下测试 predicates: - Path=/guonei #断言,路径相匹配的进行路由*/ @Bean public RouteLocator customRouteLocator(RouteLocatorBuilder routeLocatorBuilder) { RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes(); routes.route("path_rote_atguigu", r -> r.path("/guonei").uri("http://news.baidu.com")).build(); return routes.build(); } //再配置一个 @Bean public RouteLocator customRouteLocator2(RouteLocatorBuilder routeLocatorBuilder) { RouteLocatorBuilder.Builder routes = routeLocatorBuilder.routes(); routes.route("path_rote_atguigu", r -> r.path("/guoji").uri("http://news.baidu.com")).build(); return routes.build(); } }
两种配置方式都可以。
3.配置动态路由
上面配置路由,服务路径都是写的http://localhost:8001。如果这个服务是集群呢,难道去配置集群中的每一个服务吗?
可以这样子来配置
加入cloud: gateway: discovery: locator: enabled: true
修改uri属性: uri: lb://cloud-payment-hystrix-service #服务在注册中心的名称。使用服务名称来代替地址
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://cloud-payment-hystrix-service #服务在注册中心的名称 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - id: payment_routh2 #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://cloud-payment-hystrix-service #服务在注册中心的名称 predicates: - Path=/payment/get1/** #断言,路径相匹配的进行路由 eureka: instance: hostname: cloud-gateway-service client: service-url: register-with-eureka: true fetch-registry: true defaultZone: http://eureka7001.com:7001/eureka
4.断言
4.1断言工厂
官方提供了11种
4.1.1The After Route Predicate Factory
- After=2017-01-20T17:42:47.789-07:00[America/Denver] 表示在某个时间之后开始生效(这里是美国时间,可有使用国内时间)
spring: cloud: gateway: routes: - id: after_route uri: https://example.org predicates: - After=2017-01-20T17:42:47.789-07:00[America/Denver]
4.1.2The Before Route Predicate Factory
- Before=2017-01-20T17:42:47.789-07:00[America/Denver] 表示在某个时间之前有效
spring: cloud: gateway: routes: - id: before_route uri: https://example.org predicates: - Before=2017-01-20T17:42:47.789-07:00[America/Denver]
4.1.3 The Between Route Predicate Factory
- Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver] 在这段时间内有效
spring: cloud: gateway: routes: - id: between_route uri: https://example.org predicates: - Between=2017-01-20T17:42:47.789-07:00[America/Denver], 2017-01-21T17:42:47.789-07:00[America/Denver]
4.1.4 The Cookie Route Predicate Factory
- Cookie=chocolate, ch.p 必须携带cookie,名称为chocolate,值和正则表达式ch.p匹配
spring: cloud: gateway: routes: - id: cookie_route uri: https://example.org predicates: - Cookie=chocolate, ch.p
4.1.5 The Header Route Predicate Factory
- Header=X-Request-Id, \d+ 请求具有名为X-Request-Id,
其值与\d+
正则表达式匹配的标头
spring: cloud: gateway: routes: - id: header_route uri: https://example.org predicates: - Header=X-Request-Id, \d+
4.1.6The Host Route Predicate Factory
- Host=**.somehost.org,**.anotherhost.org 请求的Host
标头值为www.somehost.org
或beta.somehost.org
或,则此路由匹配www.anotherhost.org
spring: cloud: gateway: routes: - id: host_route uri: https://example.org predicates: - Host=**.somehost.org,**.anotherhost.org
4.1.7 The Method Route Predicate Factory
- Method=GET,POST 请求方式为GET 或者 POST
spring: cloud: gateway: routes: - id: method_route uri: https://example.org predicates: - Method=GET,POST
4.1.8The Path Route Predicate Factory
- Path=/red/{segment},/blue/{segment} 路径匹配
spring: cloud: gateway: routes: - id: host_route uri: https://example.org predicates: - Path=/red/{segment},/blue/{segment}
4.1.9 The Query Route Predicate Factory
- Query=green 请求包含green
查询参数
spring: cloud: gateway: routes: - id: query_route uri: https://example.org predicates: - Query=green
4.1.10The RemoteAddr Route Predicate Factory
- RemoteAddr=192.168.1.1/24 请求方的ip匹配 192.168.1.1 到 192.168.1.24
spring: cloud: gateway: routes: - id: remoteaddr_route uri: https://example.org predicates: - RemoteAddr=192.168.1.1/24
4.1.11The Weight Route Predicate Factory
- Weight=group1, 2 将大约80%的流量转发到weighthigh.org,将大约20%的流量转发到weightlow.org。相当于负载均衡的配置
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
4.2组合配置
- Path=/payment/get/** #断言,路径相匹配的进行路由
- AFTER=2022-01-20T17:42:47.485-08:00[Asia/Shanghai] #断言 时间在2022-01-20 17:42:47 后生效
在在2022-01-20 17:42:47之后,路径匹配符合 /payment/get/**的请求断言判定成功
routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://cloud-payment-hystrix-service #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - AFTER=2017-01-20T17:42:47.485-08:00[Asia/Shanghai]
5. Filter(转https://blog.csdn.net/forezp/article/details/85057268)
5.1filter基本
当我们有很多个服务时,比如下图中的user-service、goods-service、sales-service等服务,客户端请求各个服务的Api时,每个服务都需要做相同的事情,比如鉴权、限流、日志输出等。
对于这样重复的工作,有没有办法做的更好,答案是肯定的。在微服务的上一层加一个全局的权限控制、限流、日志输出的Api Gatewat服务,然后再将请求转发到具体的业务服务层。这个Api Gateway服务就是起到一个服务边界的作用,外接的请求访问系统,必须先通过网关层。
5.2生命周期
Spring Cloud Gateway同zuul类似,有“pre”(请求前)和“post”(请求后)两种方式的filter。客户端的请求先经过“pre”类型的filter,然后将请求转发到具体的业务服务,比如上图中的user-service,收到业务服务的响应之后,再经过“post”类型的filter处理,最后返回响应到客户端
与zuul不同的是,filter除了分为“pre”和“post”两种方式的filter外,在Spring Cloud Gateway中,filter从作用范围可分为另外两种,一种是针对于单个路由的gateway filter,它在配置文件中的写法同predict类似;另外一种是针对于所有路由的global gateway filer。现在从作用范围划分的维度来讲解这两种filter
5.3Gateway Filter
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#gatewayfilter-factories
5.3.1介绍
过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。过滤器可以限定作用在某些特定请求路径上。 Spring Cloud Gateway包含许多内置的GatewayFilter工厂。
GatewayFilter工厂是在配置文件application.yml中配置,遵循了约定大于配置的思想,只需要在配置文件配置GatewayFilter Factory的名称,而不需要写全部的类名,比如AddRequestHeaderGatewayFilterFactory只需要在配置文件中写AddRequestHeader,而不是全部类名。在配置文件中配置的GatewayFilter Factory最终都会相应的过滤器工厂类处理。
Spring Cloud Gateway 内置的过滤器工厂一览表如下
现在挑几个常见的过滤器工厂来讲解,每一个过滤器工厂在官方文档都给出了详细的使用案例,如果不清楚的还可以在org.springframework.cloud.gateway.filter.factory看每一个过滤器工厂的源码。
5.3.2截个filter介绍
1)AddRequestHeader GatewayFilter Factory
创建工程,gateway依赖如下:
<dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-gateway</artifactId> </dependency>
在工程的配置文件中,加入以下的配置
filters:
- AddRequestHeader=X-Request-red, blue
完整配置
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://cloud-payment-hystrix-service #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] filters: - AddRequestHeader=X-Request-red, blue - id: payment_routh2 #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://cloud-payment-hystrix-service #匹配后提供服务的路由地址 predicates: - Path=/payment/get1/** #断言,路径相匹配的进行路由 - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] filters: - AddRequestHeader=X-Request-red, blue eureka: instance: hostname: cloud-gateway-service client: service-url: register-with-eureka: true fetch-registry: true defaultZone: http://eureka7001.com:7001/eureka
配置了一个filter为AddRequestHeaderGatewayFilterFactory(约定写成AddRequestHeader),AddRequestHeader过滤器工厂会在请求头加上一对请求头,名称为X-Request-Foo,值为Bar。为了验证AddRequestHeaderGatewayFilterFactory是怎么样工作的,查看它的源码,AddRequestHeaderGatewayFilterFactory的源码如下:
public class AddRequestHeaderGatewayFilterFactory extends AbstractNameValueGatewayFilterFactory { @Override public GatewayFilter apply(NameValueConfig config) { return (exchange, chain) -> { ServerHttpRequest request = exchange.getRequest().mutate() .header(config.getName(), config.getValue()) .build(); return chain.filter(exchange.mutate().request(request).build()); }; } }
由上面的代码可知,根据旧的ServerHttpRequest创建新的 ServerHttpRequest ,在新的ServerHttpRequest加了一个请求头,然后创建新的 ServerWebExchange ,提交过滤器链继续过滤
测试访问
在服务提供方代码修改
@GetMapping(value = "/payment/get1/{id}") public String create(@PathVariable("id") Long id,HttpServletRequest request){ String header = request.getHeader("X-Request-red"); System.out.println(header); return paymentService.paymentInfo_OK(id); }
访问
服务提供方控制台打印blue,说明请求头中X-Request-red:blue成功添加
跟AddRequestHeader过滤器工厂类似的还有AddResponseHeader过滤器工厂,在此就不再重复
2)RewritePath GatewayFilter Factory
在Nginx服务启中有一个非常强大的功能就是重写路径,Spring Cloud Gateway默认也提供了这样的功能,这个功能是Zuul没有的。在配置文件中加上以下的配置
spring: cloud: gateway: routes: - id: rewritepath_route uri: https://blog.csdn.net predicates: - Path=/foo/** filters: - RewritePath=/foo/(?<segment>.*), /$\{segment}
上面的配置中,所有的/foo/**开始的路径都会命中配置的router,并执行过滤器的逻辑,在本案例中配置了RewritePath过滤器工厂,此工厂将/foo/(?.*)重写为{segment},然后转发到https://blog.csdn.net。比如在网页上请求localhost:8081/foo/forezp,此时会将请求转发到https://blog.csdn.net/forezp的页面,比如在网页上请求localhost:8081/foo/forezp/1,页面显示404,就是因为不存在https://blog.csdn.net/forezp/1这个页面
5.4自定义GatewayFilter过滤器
https://cloud.spring.io/spring-cloud-static/spring-cloud-gateway/2.2.1.RELEASE/reference/html/#global-filters
Spring Cloud Gateway内置了多种强大的过滤器工厂,能够满足很多场景的需求,那么能不能自定义自己的过滤器呢,当然是可以的。
1)在spring Cloud Gateway中,过滤器需要实现GatewayFilter和Ordered2个接口。写一个RequestTimeGatewayFilter ,代码如下
package com.atguigu.springcloud.filter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilterChain; import org.springframework.core.Ordered; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; /** * @Classname RequestTimeFilter * @Description TODO * @Date 2021/5/17 0017 下午 2:28 * @Created by jcc */ @Component public class RequestTimeGatewayFilter implements GatewayFilter, Ordered { private static final Log log = LogFactory.getLog(GatewayFilter.class); private static final String REQUEST_TIME_BEGIN = "requestTimeBegin"; @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { exchange.getAttributes().put(REQUEST_TIME_BEGIN, System.currentTimeMillis()); return chain.filter(exchange).then( Mono.fromRunnable(() -> { Long startTime = exchange.getAttribute(REQUEST_TIME_BEGIN); if (startTime != null) { log.info("myfilter: " + exchange.getRequest().getURI().getRawPath() + ": " + (System.currentTimeMillis() - startTime) + "ms"); } }) ); } @Override public int getOrder() { return 0; } }
在上面的代码中,Ordered中的int getOrder()方法是来给过滤器设定优先级别的,值越大则优先级越低。还有有一个filterI(exchange,chain)方法,在该方法中,先记录了请求的开始时间,并保存在ServerWebExchange中,此处是一个“pre”类型的过滤器,然后再chain.filter的内部类中的run()方法中相当于"post"过滤器,在此处打印了请求所消耗的时间。
2)把这个过滤器加入到过滤工厂,并且添加到Spring容器中
package com.atguigu.springcloud.config; import com.atguigu.springcloud.filter.RequestTimeGatewayFilter; import org.springframework.cloud.gateway.filter.GatewayFilter; import org.springframework.cloud.gateway.filter.factory.AbstractGatewayFilterFactory; import org.springframework.context.annotation.Configuration; import org.springframework.stereotype.Component; /** * @Classname FilterConfig * @Description TODO * @Date 2021/5/17 0017 下午 2:29 * @Created by jcc */ @Component public class RequestTimeGatewayFilterFactory extends AbstractGatewayFilterFactory<Object> { @Override public GatewayFilter apply(Object config) { return new RequestTimeGatewayFilter(); } }
查看GatewayFilterFactory的源码,可以发现GatewayFilterfactory的层级如下:
过滤器工厂的顶级接口是GatewayFilterFactory,有2个两个较接近具体实现的抽象类,分别为AbstractGatewayFilterFactory和AbstractNameValueGatewayFilterFactory,这2个类前者接收一个参数,比如它的实现类RedirectToGatewayFilterFactory;后者接收2个参数,比如它的实现类AddRequestHeaderGatewayFilterFactory类。现在需要将请求的日志打印出来,需要使用一个参数,这时可以参照RedirectToGatewayFilterFactory的写法
3)yml中配置
filters:- RequestTime
完整配置
server: port: 9527 spring: application: name: cloud-gateway cloud: gateway: discovery: locator: enabled: true routes: - id: payment_routh #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://cloud-payment-hystrix-service #匹配后提供服务的路由地址 predicates: - Path=/payment/get/** #断言,路径相匹配的进行路由 - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] filters: - AddRequestHeader=X-Request-red, blue - RequestTime - id: payment_routh2 #路由的ID,没有固定规则但要求唯一,建议配合服务名 uri: lb://cloud-payment-hystrix-service #匹配后提供服务的路由地址 predicates: - Path=/payment/get1/** #断言,路径相匹配的进行路由 - After=2020-03-08T10:59:34.102+08:00[Asia/Shanghai] filters: - AddRequestHeader=X-Request-red, blue - RequestTime eureka: instance: hostname: cloud-gateway-service client: service-url: register-with-eureka: true fetch-registry: true defaultZone: http://eureka7001.com:7001/eureka
测试 :
控制台打印:
5.5 global filter
Spring Cloud Gateway根据作用范围划分为GatewayFilter和GlobalFilter,二者区别如下:
GatewayFilter : 需要通过spring.cloud.routes.filters 配置在具体路由下,只作用在当前路由上或通过spring.cloud.default-filters配置在全局,作用在所有路由上
GlobalFilter : 全局过滤器,不需要在配置文件中配置,作用在所有的路由上,最终通过GatewayFilterAdapter包装成GatewayFilterChain可识别的过滤器,它为请求业务以及路由的URI转换为真实业务服务的请求地址的核心过滤器,不需要配置,系统初始化时加载,并作用在每个路由上。
Spring Cloud Gateway框架内置的GlobalFilter如下:
上图中每一个GlobalFilter都作用在每一个router上,能够满足大多数的需求。但是如果遇到业务上的定制,可能需要编写满足自己需求的GlobalFilter。
5.6自定义GlobalFilter
在下面的案例中将讲述如何编写自己GlobalFilter,该GlobalFilter会校验请求中是否包含了请求参数“token”,如何不包含请求参数“token”则不转发路由,否则执行正常的逻辑。代码如下:
public class TokenFilter implements GlobalFilter, Ordered { Logger logger=LoggerFactory.getLogger( TokenFilter.class ); @Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token = exchange.getRequest().getQueryParams().getFirst("token"); if (token == null || token.isEmpty()) { logger.info( "token is empty..." ); exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } @Override public int getOrder() { return -100; } }
注入spring
@Configuration public class FilterConfig { @Bean public TokenFilter tokenFilter(){ return new TokenFilter(); } }
测试访问
由于没有带token,被拦截了,控制台打印