gateway使用:
网关的作用
- 作为API接口服务请求的接入点,管理所有的接入请求
- 所有的业务服务都可以在这里被调用
- 实现安全、验证、路由、过滤、流控,缓存等策略,进行一些必要的中介处理
- 所有 API 统一管理
常见网关:
Nginx+Lua(OpenResty)、kong(基于OpenResty)、Zuul/Zuul2、Spring Cloud Gateway
(1)Kong 的性能非常不错,非常适合做流量网关,并且对于 service、route、upstream、consumer、plugins 的抽象,也是自研网关值得借鉴的。对于复杂系统,不建议业务网关用 Kong,或者更明确的说是不建议在 Java 技术栈的系统深度定制 Kong 或 OpenResty,主要是工程性方面的考虑。毕竟维护lua脚本的工作量和成本不低。
(2)pring Cloud Gateway/Zuul2 对于 Java 技术栈来说比较方便,可以依赖业务系统的一些 common jar。Lua 不方便,不光是语言的问题,更是复用基础设施的问题。另外,对于网关系统来说,性能不是差一个数量级,问题不大,多加 2 台机器就可以搞定。
(3)目前来看 Zuul2 的坑还是比较多的,因此作为java技术栈,比较建议使用 Spring Cloud Gateway 作为基础骨架。
介绍
Gateway是SpringCloud的一个全新项目,基于Spring5.0、Springboot2.0和Project Reactor等技术开发的网关,它旨在为微服务架构提供一种简单有效的统一的API路由管理方式。其作为SpringCloud生态系统中的网关,目标是替代Zuul,在SpringCloud2.0以上版本中,没有对新版本的Zuul2.0以上最新高性能版本进行集成,仍然还是使用的Zuul1.x非Reator模式的老版本。而为了提升网关的性能,Gateway是基于WebFlux框架实现的,二WebFlux框架底层则使用了高性能的Reactor模式通信框架Netty。
和zuul对比
- Zuul 1.x 是阻塞式的高并发场景下效率低下
- Gateway基于WebFlux是非阻塞式的异步框架
- 引申:Spring WebFlux 是一个异步非阻塞式 IO 模型,通过少量的容器线程就可以支撑大量的并发访问。底层使用的是 Netty 容器,这点也和传统的 SpringMVC 不一样,SpringMVC 是基于 Servlet 的。
原理图:
gateway-client客户端
handler-mapping 根据断言匹配路由
web-handler 处理过滤器链
Filter在之前可以做参数校验、权限校验、流量监控、日志输出、协议转换等,在之后可以做响应内容&响应头的吸怪、日志的输出、流量监控等非常重要的作用。
核心概念:
- Route(路由):路由是构建网关的基本模块,它由ID,目标URI,一些列的断言和过滤器组成,如果断言为true则匹配改路由
- Filter(过滤器):指的是GatewayFilter的实例,使用过滤器可以在请求被路由前或者后对请求进行修改
- Filter(过滤器):指的是GatewayFilter的实例,使用过滤器可以在请求被路由前或者后对请求进行修改
Route主要配置项包括 :id、uri、 Predicates、path
- id 路由的唯一标识,以服务名命名即可
- uri 请求地址
- predicates 主要起的作用是:基于Java8的Predicate,可以匹配Http请求中所有内容(例如请求头或者请求参数),如果请求与断言相匹配则进行路由
- Path 配置对于请求路径的匹配规则,多个可用都好隔开、
++总结:条件predicates ,拦截filter,路由uri ++
路由:
cloud:
nacos:
discovery:
server-addr: @env.cloud.nacos.server@
namespace: @env.cloud.nacos.namespace@
group: @env.cloud.nacos.group@
gateway:
discovery:
locator:
enabled: true
routes:
- id: steward
uri: lb://steward
predicates:
- Path=/auth/**,/user/**
- id: blog-sv
uri: lb://blog-sv
predicates:
- Path=/user/**
过滤器的使用
- 全局过滤器: GlobalFilter
基于全局过滤可以实现对客户端请求的处理,比如添加用户信息,以方便下游使用当前登陆的用户关键信息。
@Component
@Slf4j
public class GlobalAuthenticationFilter implements GlobalFilter, Ordered {
List<String> ignoreUrls= Arrays.asList();
@Autowired
private PemiFeignClient pemiFeignClient;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String requestUrl = exchange.getRequest().getPath().value();
log.info("请求地址:{}",requestUrl);
Date start=new Date();
//1、白名单放行
if (checkUrls(ignoreUrls,requestUrl)){
return chain.filter(exchange);
}
try {
//调用认证服务查询或者校验用户信息
AccountLoginInfoParam accountLoginInfoParam=new AccountLoginInfoParam();
accountLoginInfoParam.setLoginName("17686486497");
BaseResponse<Account> response=pemiFeignClient.getAccountByLoginName(accountLoginInfoParam);
if (!response.isSuccess() || response.getData()==null){
return invalidTokenMono(exchange);
}
response.getData();
String userInfo=JSON.toJSONString(response.getData());
log.info("当前用户信息:{}",userInfo);
//设置用户信息
String base64 = Base64.encode(userInfo);
ServerHttpRequest tokenRequest = exchange.getRequest().mutate().header(TokenConstant.TOKEN_NAME, base64).build();
ServerWebExchange exchangeWithUserInfo = exchange.mutate().request(tokenRequest).build();
Date end=new Date();
log.info("请求耗时:{}ms",end.getTime()-start.getTime());
return chain.filter(exchangeWithUserInfo);
}catch (Exception e){
e.printStackTrace();
return invalidTokenMono(exchange);
}
}
@Override
public int getOrder() {
return 0;
}
/**
* 对url进行校验匹配
*/
private boolean checkUrls(List<String> urls,String path){
AntPathMatcher pathMatcher = new AntPathMatcher();
for (String url : urls) {
if (pathMatcher.match(url,path))
return true;
}
return false;
}
/**
* 从请求头中获取Token
*/
private String getToken(ServerWebExchange exchange) {
String tokenStr = exchange.getRequest().getHeaders().getFirst("Authorization");
if (StringUtils.isBlank(tokenStr)) {
return null;
}
String token = tokenStr.split(" ")[1];
if (StringUtils.isBlank(token)) {
return null;
}
return token;
}
/**
* 无效的token
*/
private Mono<Void> invalidTokenMono(ServerWebExchange exchange) {
return buildReturnMono(RespFactory.fail(ErrCodeEnum.INVALID_TOKEN.code,ErrCodeEnum.INVALID_TOKEN.getMsg()), exchange);
}
private Mono<Void> buildReturnMono(BaseResponse baseResponse, ServerWebExchange exchange) {
ServerHttpResponse response = exchange.getResponse();
byte[] bits = JSON.toJSONString(baseResponse).getBytes(StandardCharsets.UTF_8);
DataBuffer buffer = response.bufferFactory().wrap(bits);
response.setStatusCode(HttpStatus.UNAUTHORIZED);
response.getHeaders().add("Content-Type", "application/json;charset:utf-8");
return response.writeWith(Mono.just(buffer));
}
}
遇到问题:
gateway中无法调用openfeign
Spring Cloud Gateway是基于WebFlux的,是ReactiveWeb,所以HttpMessageConverters不会自动注入,如HttpMessageConvertersAutoConfiguration源码所示。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-37a6i6lb-1656036962727)(https://img.october2.top/image.png1654594189305?e=1654597789&token=wSev1gzynuXgxcEYMfGcsli2c4R9hKbErgs0s-7Z:rrw-bOuhU6gDFgNY6MAJbt9UiSs=)]
解决方法
@SpringBootConfiguration
public class FeignConfig {
@Bean
@ConditionalOnMissingBean
public HttpMessageConverters messageConverters(ObjectProvider<HttpMessageConverter<?>> converters) {
return new HttpMessageConverters(converters.orderedStream().collect(Collectors.toList()));
}
@Bean
public Decoder feignDecoder() {
return new ResponseEntityDecoder(new SpringDecoder(feignHttpMessageConverter()));
}
public ObjectFactory<HttpMessageConverters> feignHttpMessageConverter() {
final HttpMessageConverters httpMessageConverters = new HttpMessageConverters(new GateWayMappingJackson2HttpMessageConverter());
return new ObjectFactory<HttpMessageConverters>() {
@Override
public HttpMessageConverters getObject() throws BeansException {
return httpMessageConverters;
}
};
}
public static class GateWayMappingJackson2HttpMessageConverter extends MappingJackson2HttpMessageConverter {
GateWayMappingJackson2HttpMessageConverter(){
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.valueOf(MediaType.TEXT_HTML_VALUE + "; charset=UTF-8"));
setSupportedMediaTypes(mediaTypes);
}
}
}