目录
- 网关简介
- 网关⼯作过程
- 网关基本概念
- 常见网关的区别
- Gateway网关实战
- 父工程
- 子工程demo
- 子工程gateway
- 启动两个子工程
网关简介
Spring Cloud GateWay是Spring Cloud的⼀个全新项⽬,⽬标是取代Netflix Zuul,它基于Spring5.0 SpringBoot2.0 WebFlux(基于⾼性能的Reactor模式响应式通信框架Netty,异步⾮阻塞模型)等技术开发,性能⾼于Zuul,官⽅测试,GateWay是Zuul的1.6倍,旨在为微服务架构提供⼀种简单有效的统⼀的API路由管理⽅式。
Spring Cloud GateWay不仅提供统⼀的路由⽅式(反向代理)并且基于 Filter(定义过滤器对请求过滤,完成⼀些功能) 链的⽅式提供了⽹关基本的功能,例如:鉴权、流量控制、熔断、路径重写、⽇志监控等。
网关在架构中的位置,可以看到是请求进来由网关路由分配找到需要请求的服务,其中Nginx是用来做网关高可用的。
Spring Cloud GateWay天⽣就是异步⾮阻塞的,基于Reactor模型;
⼀个请求—>⽹关根据⼀定的条件匹配—匹配成功之后可以将请求转发到指定的服务地址;⽽在这个过程中,我们可以进⾏⼀些⽐较具体的控制(限流、⽇志、⿊⽩名单)
- 路由(route): ⽹关最基础的部分,也是⽹关⽐较基础的⼯作单元。路由由⼀个ID、⼀个⽬标URL(最终路由到的地址)、⼀系列的断⾔(匹配条件判断)和Filter过滤器(精细化控制)组成。如果断⾔为true,则匹配该路由。
- 断⾔(predicates):参考了Java8中的断⾔java.util.function.Predicate,开发⼈员可以匹配Http请求中的所有内容(包括请求头、请求参数等)(类似于nginx中的location匹配⼀样),如果断⾔与请求相匹配则路由。
- 过滤器(filter):⼀个标准的Spring webFilter,使⽤过滤器,可以在请求之前 或者之后执⾏业务逻辑。
Predicates断⾔就是我们的匹配条件,⽽Filter就可以理解为⼀个⽆所不能的拦截器,有了这两个元素,结合⽬标URL,就可以实现⼀个具体的路由转发。
Spring Cloud GateWay 帮我们内置了很多 Predicates功能,实现了各种路由匹配规则(通过 Header、请求参数等作为条件)匹配到对应的路由。
一般都会使用请求路径正则匹配
代码语言:javascript复制spring:
cloud:
gateway:
routes: # 路由可以有多个
- id: service-xxx-router # 我们⾃定义的路由 ID,保持唯⼀
uri: lb://server-name
predicates: #路由条件
- Path=/xx/xxxx/**
网关⼯作过程
客户端向Spring Cloud GateWay发出请求,然后在GateWay Handler Mapping中找到与请求相匹配的路由,将其发送到GateWay Web Handler;Handler再通过指定的过滤器链来将请求发送到我们实际的服务执⾏业务逻辑,然后返回。过滤器之间⽤虚线分开是因为过滤器可能会在发送代理请求之前(pre)或者之后(post)执⾏业务逻辑。
Filter在“pre”类型过滤器中可以做参数校验、权限校验、流量监控、⽇志输出、协议转换等,在“post”类型的过滤器中可以做响应内容、响应头的修改、⽇志的输出、流量监控等。
从过滤器⽣命周期(影响时机点)的⻆度来说,主要有两个pre和post:
从过滤器类型的⻆度 ,Spring Cloud GateWay的过滤器分为GateWayFilter和GlobalFilter两种。
一般情况下GlobalFilter全局过滤器是程序员使⽤⽐较多的过滤器;
可以用来自定义一些黑名单校验等
⾃定义GateWay全局过滤器时,我们实现Global Filter接⼝即可,通过全局过滤器可以实现⿊⽩名单、限流等功能。
代码语言:javascript复制@Slf4j
@Component
public class BlackListFilter implements GlobalFilter, Ordered {
private static List<String> blackList = new ArrayList<>();
static {
blackList.add("0:0:0:0:0:0:0:1"); // 模拟本机地址
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 思路:获取客户端ip,判断是否在⿊名单中,在的话就拒绝访问,不在的话就放⾏
// 从上下⽂中取出request和response对象
ServerHttpRequest request = exchange.getRequest();
ServerHttpResponse response = exchange.getResponse();
// 从request对象中获取客户端ip
String clientIp = request.getRemoteAddress().getHostString();
// 拿着clientIp去⿊名单中查询,存在的话就决绝访问
if (blackList.contains(clientIp)) {
// 决绝访问,返回
response.setStatusCode(HttpStatus.UNAUTHORIZED); // 状态码
log.debug("=====>IP:" clientIp " 在⿊名单中,将被拒绝访 问!");
String data = "请求被禁止!";
DataBuffer wrap = response.bufferFactory().wrap(data.getBytes());
return response.writeWith(Mono.just(wrap));
}
// 合法请求,放⾏,执⾏后续的过滤器
return chain.filter(exchange);
}
@Override
public int getOrder() {
return 0; //越小优先级越高
}
}
官方全局过滤器使用规范例子
代码语言:javascript复制@RestController
@SpringBootApplication
public class DemogatewayApplication {
@RequestMapping("/hystrixfallback")
public String hystrixfallback() {
return "This is a fallback";
}
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
//@formatter:off
return builder.routes()
.route("path_route", r -> r.path("/get")
.uri("http://httpbin.org"))
.route("host_route", r -> r.host("*.myhost.org")
.uri("http://httpbin.org"))
.route("rewrite_route", r -> r.host("*.rewrite.org")
.filters(f -> f.rewritePath("/foo/(?<segment>.*)",
"/${segment}"))
.uri("http://httpbin.org"))
.route("hystrix_route", r -> r.host("*.hystrix.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd")))
.uri("http://httpbin.org"))
.route("hystrix_fallback_route", r -> r.host("*.hystrixfallback.org")
.filters(f -> f.hystrix(c -> c.setName("slowcmd").setFallbackUri("forward:/hystrixfallback")))
.uri("http://httpbin.org"))
.route("limit_route", r -> r
.host("*.limited.org").and().path("/anything/**")
.filters(f -> f.requestRateLimiter(c -> c.setRateLimiter(redisRateLimiter())))
.uri("http://httpbin.org"))
.route("websocket_route", r -> r.path("/echo")
.uri("ws://localhost:9000"))
.build();
//@formatter:on
}
@Bean
RedisRateLimiter redisRateLimiter() {
return new RedisRateLimiter(1, 2);
}
@Bean
SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http) throws Exception {
return http.httpBasic().and()
.csrf().disable()
.authorizeExchange()
.pathMatchers("/anything/**").authenticated()
.anyExchange().permitAll()
.and()
.build();
}
@Bean
public MapReactiveUserDetailsService reactiveUserDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build();
return new MapReactiveUserDetailsService(user);
}
public static void main(String[] args) {
SpringApplication.run(DemogatewayApplication.class, args);
}
}
当然也可以单独做过滤器,比如,黑白名单过滤器,限流,认证,以及token校验等过滤器
代码语言:javascript复制import org.apache.commons.chain.Command;
import org.apache.commons.chain.impl.ChainBase;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 责任链模式
* 接口限流->黑名单->白名单->数字签名验证,解码body数据->检查api信息及api参数->远程调用对应的接口->调用结果加密->返回
*/
@Configuration
public class ChainConfig {
@Bean
Command interfaceFlowLimitHandler() {
return new InterfaceFlowLimitHandler();
}
// @Bean
// Command blackIpListCheckHandler() {
// return new BlackIpListCheckHandler();
// }
@Bean
Command whiteIpListCheckHandler() {
return new WhiteIpListCheckHandler();
}
@Bean
Command signatureCheckHandler() {
return new SignatureCheckHandler();
}
@Bean
Command apiInfoCheckHandler() {
return new ApiInfoCheckHandler();
}
@Bean
Command invokeInterfaceHandler() {
return new InvokeInterfaceHandler();
}
@Bean
Command encodeResponseHandler() {
return new EncodeResponseHandler();
}
@Bean
ChainBase chains() {
ChainBase chainBase = new ChainBase();
chainBase.addCommand(interfaceFlowLimitHandler());
chainBase.addCommand(blackIpListCheckHandler());
chainBase.addCommand(whiteIpListCheckHandler());
chainBase.addCommand(signatureCheckHandler());
chainBase.addCommand(apiInfoCheckHandler());
chainBase.addCommand(invokeInterfaceHandler());
chainBase.addCommand(encodeResponseHandler());
return chainBase;
}
}
每个bean类实现Command接口。命令封装要执行的处理工作单元,其目的是检查或修改由上下文表示的事务的状态。单个命令可以被组装成一个链,这允许它们完成所需的处理,或者将进一步的处理委托给链中的下一个命令。命令实现应该以线程安全的方式设计,适合包含在可能由不同线程同时处理的多个链中。 一般来说,这意味着命令类不应该维护实例变量中的状态信息。相反,应该通过对传递给execute()命令的上下文属性的适当修改来维护状态信息。 命令实现通常在上下文实例中检索和存储状态信息,这些信息作为参数传递给execute()方法.
代码语言:javascript复制//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.apache.commons.chain;
public interface Command {
boolean CONTINUE_PROCESSING = false;
boolean PROCESSING_COMPLETE = true;
boolean execute(Context var1) throws Exception;
}
网关基本概念
路由
代码语言:javascript复制spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: demo
uri: lb://demo
predicates:
- Path=/rest-web/**
filters:
- StripPrefix=1
Java配置:
代表将 ip地址为10.1.1.1的访问转发到 down。。。。
断言 predicates: 下面加个 -可以添加多种断言
- 过滤器
路由过滤器允许以某种方式修改传入的HTTP请求或传出的HTTP响应。路由过滤器适用于特定路由。Spring Cloud Gateway包括许多内置的xGatewayFilter工厂。
配置:
代码语言:javascript复制spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: demo
uri: lb://demo
predicates:
- Path=/rest-web/**
filters:
- StripPrefix=1 # 将url前缀去掉比如ip,port,http等
- AddRequestHeader=X-Request-red, blue
此清单将X-Request-red:blue标头添加到所有匹配请求的下游请求的标头中。
AddRequestHeader了解用于匹配路径或主机的URI变量。URI变量可以在值中使用,并在运行时扩展。
java config
形式使用ModifyRequestBody过滤器过滤器在网关向下游发送请求主体之前对其进行修改。
尝试通过代码的形式将转发的请求添加头信息:
代码语言:javascript复制@Bean
public RouteLocator routes(RouteLocatorBuilder builder) {
return builder.routes()
.route("route1", r -> r.path("/payment/nacos/header/**")
.filters(f->f.addRequestHeader("json", "123"))
.uri("lb://nacos-payment-provider")).build();
}
常见网关的区别
常见的有Zuul、Gateway、Nginx
- zuul 是Netflix的,早期在微服务中使用较广泛,是基于servlet实现的,阻塞式的api,不支持长连接。 只能同步,不支持异步。 不依赖spring-webflux,可以扩展至其他微服务框架。 内部没有实现限流、负载均衡,其负载均衡的实现是采用 Ribbon Eureka 来实现本地负载均衡。 代码简单,注释多,易理解。
- Gateway 是springcloud自己研制的微服务网关,是基于Spring5构建,,能够实现响应式非阻塞式的Api,支持长连接。 支持异步。功能更强大,内部实现了限流、负载均衡等,扩展性也更强。Spring Cloud Gateway明确的区分了Router 和Filter,并且一个很大的特点是内置了非常多的开箱即用功能,并且都可以通过 SpringBoot 配置或者手工编码链式调用来使用。依赖于spring-webflux,仅适合于Spring Cloud套件。代码复杂,注释少。
- nginx C语言编写,采用服务器实现负载均衡,高性能的HTTP和反向代理web服务器。 Nginx适合于服务器端负载均衡,Zuul和gateway 是本地负载均衡,适合微服务中实现网关。Spring Cloud Gateway 天然适合Spring Cloud 生态。
Gateway网关实战
首先创建父类工程和两个子工程:demo和gateway
父工程
父类pom文件定义版本
代码语言:javascript复制 <modules>
<module>gateway</module>
<module>dubbo-api</module>
<module>rest-web</module>
</modules>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.3.7.RELEASE</spring-boot.version>
<spring-cloud-alibaba.version>2.2.2.RELEASE</spring-cloud-alibaba.version>
<spring-cloud.version>Hoxton.SR9</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
子工程demo
pom.xml
代码语言:javascript复制<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--nacos 配置中心-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.9</version>
</dependency>
</dependencies>
配置文件application.yml
代码语言:javascript复制server:
port: 8000
spring:
application:
name: demo
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
config:
server-addr: 127.0.0.1:8848
file-extension: yaml
简单的controller路由
代码语言:javascript复制package com.demo.restweb;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@SpringBootApplication
@RestController
@EnableDiscoveryClient
public class RestWebApplication {
public static void main(String[] args) {
SpringApplication.run(RestWebApplication.class, args);
}
@GetMapping("/demo")
public String demo(String name) {
System.out.println("ssss " name);
return "hello, " name "i am demo";
}
}
子工程gateway
pom.xml
代码语言:javascript复制<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.csp</groupId>
<artifactId>sentinel-datasource-nacos</artifactId>
</dependency>
</dependencys>
配置文件application.yml
代码语言:javascript复制spring:
application:
name: gateway
cloud:
gateway:
routes:
- id: demo
uri: lb://demo
predicates:
- Path=/rest-web/**
filters:
- StripPrefix=1
nacos:
discovery:
server-addr: 127.0.0.1:8848
enabled: true
启动两个子工程
访问地址:http://127.0.0.1:8085/rest-web/demo?name=Java技术债务