插一嘴
- 个人理解请求先进网关,网关根据url转发给某个服务,之后请求进负债均衡器,负债均衡器将请求转发给该服务的具体某个实例
-
依赖
Gateway 自己使用了 netty 实现了 Web 服务,此处『不需要引入 Spring Web』,如果引入了,反而还会报冲突错误,无法启动。
<dependency> <!-- 会自动引入 Ribbon 依赖。Gateway 整合了 Ribbon 。--><groupId>org.springframework.cloud</groupId><artifactId>spring-cloud-starter-gateway</artifactId></dependency>
配置路由(断言)
Gateway可以通过配置文件进行配置,也可以通过bean方式配置
- 路由转发的路径不会变,变得只会是ip和端口(即变得只是服务),如原始请求是
127.0.0.1:8080/api1,我们对api1进行监听然后转发给127.0.0.1:8082请求地址变为127.0.0.1/8082/api1 路由配置除了根据路径规则配置,还可以通过cookies,headers,方法名,请求时间等进行路由,总之非常自由,详见链接
配置文件方式配置
gateway转发得url有三种形式:
- websocket(ws):
uri: ws://localhost:9000 - http:
uri: http://localhost:8130 - 注册中心服务名(lb):
uri: lb://brilliance-consumer```properties下面的规则是: 请求/163 路径时,Gateway 将会跳转到163.com,因为163.com/163路径不存在,163.com进行了处理:跳转到首页
spring.application.name=gateway-server #这个跟编码方式不知道怎么对应 spring.cloud.gateway.routes[0].id=163_route #路由名称 spring.cloud.gateway.routes[0].uri=http://www.163.com #转发路径 spring.cloud.gateway.routes[0].predicates[0]=Path=/163 #监听的路径,支持表达式,如/XXX/**
- websocket(ws):
//更多配置规则
spring.cloud.gateway.routes[1].id=
```yamlspring:application:name: gateway-servercloud:gateway:routes:- id: 163_routeuri: http://www.163.compredicates:- Path=/163
bean编码方式配置
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return //下面的路由规则是: 输入/jd,gateway 会将请求导向到 http://www.jd.com builder.routes().route(r>r.path("/jd").uri("http://www.jd.com").id("jd_route")).
build();
}
与服务注册发现组件结合
使用如下配置启用服务注册发现组件结合。然后路由规则得url使用
**lb**形式spring.cloud.gateway.discovery.locator.lowerCaseServiceId=true spring.cloud.gateway.discovery.locator.enabled=true跨域配置
推荐使用nginx的方式
spring: cloud: gateway: globalcors: corsConfigurations: '[/**]': allowedOriginPatterns: "*" allowed-methods: "*" allowed-headers: "*" allow-credentials: true exposedHeaders: "Content-Disposition,Content-Type,Cache-Control"
断言Predicate
- Spring Cloud Gateway 是由很多的路由断言工厂组成。当 HTTP Request 请求进入 Spring Cloud Gateway 的时候,网关中的路由断言工厂就会根据配置的路由规则,对 HTTP Request 请求进行断言匹配。匹配成功则进行下一步处理,否则,断言失败直接返回错误信息。
- 早期得断言配置都通过
**@Bean**配置,后面才推出了配置文件配置配置方法 - 断言即路由的实现方式
- 早期得断言配置都通过
java8 中的断言函数。Spring Cloud Gateway 中的断言函数输入类型是 Spring 5.0 框架中的
ServerWebExchange。Spring Cloud Gateway 中的断言函数允许开发者去定义匹配来自 Http Request 中的任何信息,比如请求头和参数等。RewritePath过滤器
过滤器得作用是可以修改url。如:将
/xxx/yyy/zzz/hello修改为/hello再进行传递。过滤器分为2种类型:
在路由规则后添加过滤器规则,即过滤器是配合路由断言一起使用。配置上面看起来就是在路由配置得基础上增加了
**filters**路由配置的表达式:
- 命名分组:
(?<name>正则表达式)与普通分组一样的功能,并且将匹配的子字符串捕获到一个组名称或者编号名称中。在获得匹配结果时,可通过分组名进行获取。 - 引用捕获文本:
${name}将名称为 name 的命名分组所匹配到的文本内容替换到此处。$\{segment}:将前面捕获到 segment 中的文本置换到此处,注意,\ 的出现是由于避免 YAML 认为这是一个变量而使用的转义字符。spring.cloud.gateway.routes[0].id=demo_route spring.cloud.gateway.routes[0].uri=http://localhost:8080 spring.cloud.gateway.routes[0].predicates[0]=Path=/xxx-service/** spring.cloud.gateway.routes[0].filters[0]=RewritePath=/xxx-service/(?<segment>.*), /${segment}配置普通过滤器
- 命名分组:
普通过滤器就是需要自己指定过滤哪个路由,全局过滤器则对所有路由生效。
- 主要普通过滤器一般要设置优先级高于全局过滤器,否则可能失效
- 定义好过滤器,
**.routes.filters=过滤器类名**即可走自定义过滤器
- 定义好过滤器,
- 过滤器继承
AbstractGatewayFilterFactory使用@Component标注,具体的看代码,我们可以自定义实现 我们可以定义一个
**ServerHttpRequestDecorator**并重写其方法修改请求头,见**ruoyi-xss过滤器**```java @Component public class BlackListUrlFilter extends AbstractGatewayFilterFactory{ @Override public GatewayFilter apply(Config config) { return (exchange, chain) -> { // 结束过滤写入返回信息,还有重载方法可以写入状态码 return ServletUtils.webFluxResponseWriter(exchange.getResponse(), "请求地址不允许访问"); //继续执行过滤 return chain.filter(exchange); };}
public BlackListUrlFilter() {
super(Config.class);}
public static class Config {
// 可以定义一些自定义配置,如黑名单列表}
}
<a name="LoG3j"></a>
## 定义全局Filter
- **自定义全局过滤器要实现 **`**GlobalFilter**`** 接口。全局过滤器不需要指定对哪个路由生效,它对所有路由都生效。**
- **还可以单独对过滤器方法定义为bean实现全局过滤器**
- **全局过滤器可以配置多个,他们会自动被gateway使用到。**
- **可以在每个全局过滤器头部添加**`**@Order**`
- **过滤器在路由转发之前进行并生效(过滤器也可以始终放行) 如使用全局过滤器实现认证与鉴权**
```java
public class XxxGlobalFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
// 逻辑代码 ...
if (...) {
// 符合条件就继续走下一个过滤器,直到走到路由转发处为止,如果不符合,则不进行路由转发
return chain.filtergyiokvqi(exchange);
//return chain.filter(exchange); //还有一种继续执行过滤的写法是这个,不清楚区别
} else {
// 否则流程终止,拒绝路由。
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
return exchange.getResponse().setComplete();
};
}
}
@Bean
public GlobalFilter xxxGlobalFilter() {
return new XxxGlobalFilter();
}
@Bean
@Order(-100) // 注解可以去控制全局过滤器的先后顺序。值越小,优先级越高。
public GlobalFilter xxxGlobalFilter {
return ...;
}
//也可以使用@Component配置,同时实现2个接口,更加方便
public class XxxGlobalFilter implements GlobalFilter,Ordered {
@Override
public Mono<Void> filter(){...}
@Override
public int getOrder() {
return 0;
}
}
获得/修改请求信息
- 可以通过网关作所有微服务的统一检验,鉴权的中心
- 百度得知:微服务架构下检验一般2种形式:
- 每个微服务自己进行校验
- 统一由网关进行校验:网关校验无法做到精细的控制,并且性能比服务里校验更差。但是更简单,如果服务与模块不是很多时,可以采用网关校验。服务与模块多需要频繁检验时推荐服务内部进行,可以抽取出一个公共的服务进行校验
- 有的观点认为网关仅仅应该用作转发,不应该用于进行校验等业务功能
- Spring Cloud Gateway(读取、修改 Request Body详解)
ServerHttpRequest是一个类似HttpServletRequest的请求信息,ServerWebExchange源码解读 ```java ServerHttpRequest request = exchange.getRequest();
log.info(“{}”, request.getMethod()); //获得请求类型 log.info(“{}”, request.getURI());//获取url log.info(“{}”, request.getPath()); log.info(“{}”, request.getQueryParams()); // 获取Get请求时参数
//解析请求头
HttpHeaders headers = request.getHeaders();
for (Map.Entry
//简写为:
request.getHeaders().keySet().forEach(key -> {
log.info("{}: {}", key, request.getHeaders().get(key))
});
// 添加请求头 request.mutate().header(“age”, “20”).build();
<a name="Zocri"></a>
## 返回JSON格式信息
- **未通过过滤器,过滤器返回的失败信息是以**`**http**`**的错误码形式返回的,如**`**4xx**``**5xx**`**的格式**。如果我们需要指定自定义的信息规则,如`成功时返回200`,失败时返回自定义错误码和错误信息...
```java
String jsonStr = "{\"status\":\"-1\", \"msg\":\"error\"}";
byte[] bytes = jsonStr.getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = exchange.getResponse().bufferFactory().wrap(bytes);
return exchange.getResponse().writeWith(Flux.just(buffer));
过滤器返回后执行增强代码
- 过滤器除了在请求执行前进行是否继续路由的检验,还可以在请求执行并返回结果时也执行一些代码
- 要在返回后继续执行代码,在
chain.filter(exchange)后继续执行.then()@Override public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) { long before = System.currentTimeMillis(); log.info("现在是目标微服务【执行前】"); return chain.filter(exchange) .then(Mono.fromRunnable(() -> { long after = System.currentTimeMillis(); log.info("现在是目标微服务【执行后】"); System.out.println("请求执行耗时: " + (after - before) / 1000.0 + " 秒"); })); }统一异常处理
- 要在返回后继续执行代码,在
