spring cloud gateway源码分析,一个请求进来的默认处理流程

1.前言

spring cloud gateway的基本组成和作用就不细赘述,此篇适合对此有一定了解的人阅读。
spring cloud gateway版本: Hoxton.SR1

spring cloud gateway的配置使用yml配置:

server:port: 9527y#根据微服务名称进行动态路由的配置
spring:application:name: cloud-gatewaycloud:gateway:discovery:locator:enabled: true                     #开启从注册中心动态创建路由的功能,利用微服务名称进行路由routes:                - id: config-clienturi: lb://config-clientpredicates:- Path=/config/**filters:- RewritePath=/config/?(?<segment>.*),/config/v1/$\{segment}

2. 流程图

在这里插入图片描述
先看一张官网文档给的图,此图大概描述了请求的处理原理,各个组件大致的位置。

3.源码剖析

在这里插入图片描述
http底层处理是基于netty,netty是一个高性能异步事件驱动的通讯框架,对于netty的处理流程可以查阅其源码。netty读取完数据经过pipeline管道处理后,最终调用到reactor.netty.http.server.HttpServerHandle#onStateChange方法。然后经过层层方法调用到核心类org.springframework.web.reactive.DispatcherHandler#handle

	public Mono<Void> handle(ServerWebExchange exchange) {if (this.handlerMappings == null) {//没有合适的handler返回失败return createNotFoundError();}return Flux.fromIterable(this.handlerMappings)//mapping.getHandler是关键方法,根据handlerMapping找到对应的handler.concatMap(mapping -> mapping.getHandler(exchange)).next().switchIfEmpty(createNotFoundError())//invokeHandler是关键方法,调用处理逻辑.flatMap(handler -> invokeHandler(exchange, handler))//处理结果,写出.flatMap(result -> handleResult(exchange, result));}private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {if (this.handlerAdapters != null) {for (HandlerAdapter handlerAdapter : this.handlerAdapters) {if (handlerAdapter.supports(handler)) {//查找合适的handlerAdapter处理,默认会调用到SimpleHandlerAdapter#handlereturn handlerAdapter.handle(exchange, handler);}}}return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));}private Mono<Void> handleResult(ServerWebExchange exchange, HandlerResult result) {return getResultHandler(result).handleResult(exchange, result).checkpoint("Handler " + result.getHandler() + " [DispatcherHandler]").onErrorResume(ex ->result.applyExceptionHandler(ex).flatMap(exResult -> {String text = "Exception handler " + exResult.getHandler() +", error=\"" + ex.getMessage() + "\" [DispatcherHandler]";return getResultHandler(exResult).handleResult(exchange, exResult).checkpoint(text);}));}private HandlerResultHandler getResultHandler(HandlerResult handlerResult) {if (this.resultHandlers != null) {for (HandlerResultHandler resultHandler : this.resultHandlers) {if (resultHandler.supports(handlerResult)) {return resultHandler;}}}throw new IllegalStateException("No HandlerResultHandler for " + handlerResult.getReturnValue());}

handlerMappings的注入类看下图,最后通过RoutePredicateHandlerMapping找到合适的处理类。handlerMappings中的其他几种Mapping方式,是别的策略或者配置时会用到,可以思考是怎么用的。

在这里插入图片描述

先来看看mapping.getHandler的处理逻辑,默认会调用到org.springframework.web.reactive.handler.AbstractHandlerMapping#getHandler

	@Overridepublic Mono<Object> getHandler(ServerWebExchange exchange) {//getHandlerInternal,根据exchange真正去查找合适的处理handler,根据上面解释,//getHandlerInternal调用到RoutePredicateHandlerMapping类中去return getHandlerInternal(exchange).map(handler -> {if (logger.isDebugEnabled()) {logger.debug(exchange.getLogPrefix() + "Mapped to " + handler);}//跨域处理if (hasCorsConfigurationSource(handler)) {ServerHttpRequest request = exchange.getRequest();CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(exchange) : null);CorsConfiguration handlerConfig = getCorsConfiguration(handler, exchange);config = (config != null ? config.combine(handlerConfig) : handlerConfig);if (!this.corsProcessor.process(config, exchange) || CorsUtils.isPreFlightRequest(request)) {return REQUEST_HANDLED_HANDLER;}}return handler;});}

现在调用到了org.springframework.cloud.gateway.handler.RoutePredicateHandlerMapping#getHandlerInternal方法中,这里一个关键点就来了。Predicates断言,是路由配置的关键,根据predicates的结果,满足的话就会转发请求到对应的Router配置的uri上。

	@Overrideprotected Mono<?> getHandlerInternal(ServerWebExchange exchange) {// don't handle requests on management port if set and different than server portif (this.managementPortType == DIFFERENT && this.managementPort != null&& exchange.getRequest().getURI().getPort() == this.managementPort) {return Mono.empty();}exchange.getAttributes().put(GATEWAY_HANDLER_MAPPER_ATTR, getSimpleName());//lookupRoute(exchange)去查找满足断言条件的路由Routerreturn lookupRoute(exchange)// 满足的router会被组装到exchange中,然后返回webHandler.flatMap((Function<Route, Mono<?>>) r -> {exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);if (logger.isDebugEnabled()) {logger.debug("Mapping [" + getExchangeDesc(exchange) + "] to " + r);}exchange.getAttributes().put(GATEWAY_ROUTE_ATTR, r);return Mono.just(webHandler);}).switchIfEmpty(Mono.empty().then(Mono.fromRunnable(() -> {exchange.getAttributes().remove(GATEWAY_PREDICATE_ROUTE_ATTR);if (logger.isTraceEnabled()) {logger.trace("No RouteDefinition found for ["+ getExchangeDesc(exchange) + "]");}})));}protected Mono<Route> lookupRoute(ServerWebExchange exchange) {return this.routeLocator.getRoutes()// 遍历所有的Router,此行r.getPredicate().apply(exchange)是验证是否满足断言要求,// 满足的Router会被返回.concatMap(route -> Mono.just(route).filterWhen(r -> {// add the current route we are testingexchange.getAttributes().put(GATEWAY_PREDICATE_ROUTE_ATTR, r.getId());return r.getPredicate().apply(exchange);})// instead of immediately stopping main flux due to error, log and// swallow it.doOnError(e -> logger.error("Error applying predicate for route: " + route.getId(),e)).onErrorResume(e -> Mono.empty()))// .defaultIfEmpty() put a static Route not found// or .switchIfEmpty()// .switchIfEmpty(Mono.<Route>empty().log("noroute")).next()// TODO: error handling.map(route -> {if (logger.isDebugEnabled()) {logger.debug("Route matched: " + route.getId());}validateRoute(route, exchange);return route;});/** TODO: trace logging if (logger.isTraceEnabled()) {* logger.trace("RouteDefinition did not match: " + routeDefinition.getId()); }*/}

apply调用进入org.springframework.cloud.gateway.handler.AsyncPredicate.DefaultAsyncPredicate#apply方法,查看delegate.test(t)调用的实现类,可以发现所有断言的调用,此处根据我们配置的断言规则调用对应的断言,返回Boolean。
在这里插入图片描述
通过断言拿到对应的handler后回到DispatcherHandler#handle方法接下来调用invokeHandler(exchange, handler)

//这个handler 是 FilteringWehHandler
private Mono<HandlerResult> invokeHandler(ServerWebExchange exchange, Object handler) {if (this.handlerAdapters != null) {for (HandlerAdapter handlerAdapter : this.handlerAdapters) {if (handlerAdapter.supports(handler)) {//使用SimpleHandlerAdapter来处理return handlerAdapter.handle(exchange, handler);}}}return Mono.error(new IllegalStateException("No HandlerAdapter: " + handler));}

然后调用org.springframework.web.reactive.result.SimpleHandlerAdapter#handle

	@Overridepublic Mono<HandlerResult> handle(ServerWebExchange exchange, Object handler) {WebHandler webHandler = (WebHandler) handler;//handler是FilteringWehHandler,所以调用到FilteringWehHandler.handleMono<Void> mono = webHandler.handle(exchange);return mono.then(Mono.empty());}

org.springframework.cloud.gateway.handler.FilteringWebHandler#handle

	@Overridepublic Mono<Void> handle(ServerWebExchange exchange) {Route route = exchange.getRequiredAttribute(GATEWAY_ROUTE_ATTR);//取出之前匹配的Router,取出filters,如果配置了的话List<GatewayFilter> gatewayFilters = route.getFilters();//GatewayFilter和globalFilter合并,并按order排序List<GatewayFilter> combined = new ArrayList<>(this.globalFilters);combined.addAll(gatewayFilters);// TODO: needed or cached?AnnotationAwareOrderComparator.sort(combined);if (logger.isDebugEnabled()) {logger.debug("Sorted gatewayFilterFactories: " + combined);}//进入过滤链调用filtersreturn new DefaultGatewayFilterChain(combined).filter(exchange);}@Overridepublic Mono<Void> filter(ServerWebExchange exchange) {return Mono.defer(() -> {if (this.index < filters.size()) {GatewayFilter filter = filters.get(this.index);//index 每次+1,设置到chain中,传递到下一次filter,下一次filter时取就是next的filterDefaultGatewayFilterChain chain = new DefaultGatewayFilterChain(this,this.index + 1);//filter链执行return filter.filter(exchange, chain);}else {return Mono.empty(); // complete}});}

filter链很重要,是spring cloud gateway的扩展点,可以做扩展逻辑,比如权限校验,登录认证,日志等。默认情况下的filter链如下,需要关注一下LoadBalancerClientFilter和NettyRoutingFilter
在这里插入图片描述

org.springframework.cloud.gateway.filter.LoadBalancerClientFilter#filter

	@Override@SuppressWarnings("Duplicates")public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {URI url = exchange.getAttribute(GATEWAY_REQUEST_URL_ATTR);//使用协议 http还是lbString schemePrefix = exchange.getAttribute(GATEWAY_SCHEME_PREFIX_ATTR);if (url == null|| (!"lb".equals(url.getScheme()) && !"lb".equals(schemePrefix))) {return chain.filter(exchange);}// 保存原始请求urladdOriginalRequestUrl(exchange, url);if (log.isTraceEnabled()) {log.trace("LoadBalancerClientFilter url before: " + url);}//根据注册中心的信息,使用负载均衡算法,找一个可用的服务final ServiceInstance instance = choose(exchange);if (instance == null) {throw NotFoundException.create(properties.isUse404(),"Unable to find instance for " + url.getHost());}URI uri = exchange.getRequest().getURI();// if the `lb:<scheme>` mechanism was used, use `<scheme>` as the default,// if the loadbalancer doesn't provide one.String overrideScheme = instance.isSecure() ? "https" : "http";if (schemePrefix != null) {overrideScheme = url.getScheme();}//替换成真实服务器的地址,后续调用使用URI requestUrl = loadBalancer.reconstructURI(new DelegatingServiceInstance(instance, overrideScheme), uri);if (log.isTraceEnabled()) {log.trace("LoadBalancerClientFilter url chosen: " + requestUrl);}exchange.getAttributes().put(GATEWAY_REQUEST_URL_ATTR, requestUrl);return chain.filter(exchange);}

org.springframework.cloud.gateway.filter.NettyRoutingFilter#filter 处理http和https请求的发送

	@Override@SuppressWarnings("Duplicates")public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {URI requestUrl = exchange.getRequiredAttribute(GATEWAY_REQUEST_URL_ATTR);String scheme = requestUrl.getScheme();if (isAlreadyRouted(exchange)|| (!"http".equals(scheme) && !"https".equals(scheme))) {return chain.filter(exchange);}setAlreadyRouted(exchange);ServerHttpRequest request = exchange.getRequest();final HttpMethod method = HttpMethod.valueOf(request.getMethodValue());final String url = requestUrl.toASCIIString();HttpHeaders filtered = filterRequest(getHeadersFilters(), exchange);final DefaultHttpHeaders httpHeaders = new DefaultHttpHeaders();filtered.forEach(httpHeaders::set);boolean preserveHost = exchange.getAttributeOrDefault(PRESERVE_HOST_HEADER_ATTRIBUTE, false);Route route = exchange.getAttribute(GATEWAY_ROUTE_ATTR);//发送请求Flux<HttpClientResponse> responseFlux = httpClientWithTimeoutFrom(route).headers(headers -> {headers.add(httpHeaders);// Will either be set below, or later by Nettyheaders.remove(HttpHeaders.HOST);if (preserveHost) {String host = request.getHeaders().getFirst(HttpHeaders.HOST);headers.add(HttpHeaders.HOST, host);}}).request(method).uri(url).send((req, nettyOutbound) -> {if (log.isTraceEnabled()) {nettyOutbound.withConnection(connection -> log.trace("outbound route: "+ connection.channel().id().asShortText()+ ", inbound: " + exchange.getLogPrefix()));}return nettyOutbound.send(request.getBody().map(dataBuffer -> ((NettyDataBuffer) dataBuffer).getNativeBuffer()));}).responseConnection((res, connection) -> {// Defer committing the response until all route filters have run// Put client response as ServerWebExchange attribute and write// response later NettyWriteResponseFilterexchange.getAttributes().put(CLIENT_RESPONSE_ATTR, res);exchange.getAttributes().put(CLIENT_RESPONSE_CONN_ATTR, connection);ServerHttpResponse response = exchange.getResponse();// put headers and status so filters can modify the responseHttpHeaders headers = new HttpHeaders();res.responseHeaders().forEach(entry -> headers.add(entry.getKey(), entry.getValue()));String contentTypeValue = headers.getFirst(HttpHeaders.CONTENT_TYPE);if (StringUtils.hasLength(contentTypeValue)) {exchange.getAttributes().put(ORIGINAL_RESPONSE_CONTENT_TYPE_ATTR,contentTypeValue);}setResponseStatus(res, response);// make sure headers filters run after setting status so it is// available in responseHttpHeaders filteredResponseHeaders = HttpHeadersFilter.filter(getHeadersFilters(), headers, exchange, Type.RESPONSE);if (!filteredResponseHeaders.containsKey(HttpHeaders.TRANSFER_ENCODING)&& filteredResponseHeaders.containsKey(HttpHeaders.CONTENT_LENGTH)) {// It is not valid to have both the transfer-encoding header and// the content-length header.// Remove the transfer-encoding header in the response if the// content-length header is present.response.getHeaders().remove(HttpHeaders.TRANSFER_ENCODING);}exchange.getAttributes().put(CLIENT_RESPONSE_HEADER_NAMES,filteredResponseHeaders.keySet());response.getHeaders().putAll(filteredResponseHeaders);return Mono.just(res);});Duration responseTimeout = getResponseTimeout(route);if (responseTimeout != null) {responseFlux = responseFlux.timeout(responseTimeout, Mono.error(new TimeoutException("Response took longer than timeout: " + responseTimeout))).onErrorMap(TimeoutException.class,th -> new ResponseStatusException(HttpStatus.GATEWAY_TIMEOUT,th.getMessage(), th));}return responseFlux.then(chain.filter(exchange));}

以上,就是请求进来的处理过程。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/189288.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

人机协同

人机协同是指人和机器之间进行合作和协同工作的方式&#xff0c;人机协同是人工智能技术发展的一个重要方向&#xff0c;通过人机协同的方式&#xff0c;可以充分利用机器的智能和人的智慧&#xff0c;共同实现更高效、更智能的工作和生活方式。人机协同可以应用于各种领域和场…

Docker的常用基本命令(基础命令)

文章目录 1. Docker简介2. Docker环境安装Linux安装 3. 配置镜像加速4. Docker镜像常用命令列出镜像列表搜索镜像下载镜像查看镜像版本删除镜像构建镜像推送镜像 5. Docker容器常用命令新建并启动容器列出容器停止容器启动容器进入容器删除容器&#xff08;慎用&#xff09;查看…

基于Spring MVC的前后端交互案例及应用分层的实现

目录 分析程序报错的步骤 案例 一.加法计算器 二.实现用户登录 1.登录接口 2.获取用户的登录信息 三.留言板 1.接口定义 2.完成后端代码 3.测试后端代码 四.图书管理系统 1.定义接口 2.后端代码 3.测试后端代码 4.前端交互代码 应用分层 1.三层架构 分析程序报…

QT Creator 保存(Ctrl+S)时,会将Tab制表符转换为空格

今天在写makefile文件时&#xff0c;发现QT Creator 保存(CtrlS)时&#xff0c;会将Tab制表符转换为空格&#xff0c;之前没有发现&#xff0c;略坑&#xff0c;官网上也有说明&#xff0c;点这里 简单来说&#xff0c;解决办法如下 依次点击&#xff1a;Tools ->Options-&g…

JPA数据源Oracle异常记录

代码执行异常 ObjectOptimisticLockingFailureException org.springframework.orm.ObjectOptimisticLockingFailureException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1; nested exception is org.hibernate.StaleSta…

Linux 文件管理

内容概述 1 文件系统目录结构 存放的是内存中正在运行的系统状态信息&#xff0c;数据不在硬盘而是在内存中 echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all1.1 文件系统的目录结构 Linux 操作系统 ext / xfs 文件系统是区分大小写&#xff0c;大小写敏感 Linux的文件…

MySQL导出ER图为图片或PDF

目录 1、Navicat 生成ER图 1、选择数据库&#xff0c;逆向数据库到模型 2、查看ER图 3、导出ER图 2、使用MySQL官方工具&#xff1a;MySQL Workbench 1、首先连接MySQL数据库 2、点击Database&#xff0c;选择Reverse Engineer 3、填写数据库信息&#xff0c;点Next …

【鸿蒙应用ArkTS开发系列】-自定义底部菜单列表弹窗

文章目录 前言创建Demo工程创建dialog 文件夹创建ListMenu 接口创建自定义弹窗 ListMenuDialog使用自定义弹窗 打包测试效果演示默认效果菜单带图标效果设置文本颜色效果不同文本颜色效果无标题效果 前言 上一篇文章中我们实现了选择图片、选择文件、拍照的功能 。 链接在这里…

每日一练2023.12.1——输出GPLT【PTA】

题目链接&#xff1a;L1-023 输出GPLT 题目要求&#xff1a; 给定一个长度不超过10000的、仅由英文字母构成的字符串。请将字符重新调整顺序&#xff0c;按GPLTGPLT....这样的顺序输出&#xff0c;并忽略其它字符。当然&#xff0c;四种字符&#xff08;不区分大小写&#x…

对小程序的初了解

WXML和HTML的区别 标签名称不同 HTML&#xff1a;div、a、span、img WXML&#xff1a;view、text、image、navigator 属性节点不同 <a href"#">超链接</a> <navigator url"/pages/home/home"></navigator> 提供了类似vue的…

计算机视觉(OpenCV+TensorFlow)

计算机视觉&#xff08;OpenCVTensorFlow&#xff09; 文章目录 计算机视觉&#xff08;OpenCVTensorFlow&#xff09;前言7. 图像直方图绘制直方图绘制直方图有两种方式&#xff1a; 掩膜 8. 直方图均衡化直方图均衡化的介绍直方图均衡化的步骤自适应直方图均衡化 9. 图像转换…

SAP_ABAP_编程基础_数据集_创建并填充摘录数据集 / 处理摘录数据集

SAP ABAP 顾问&#xff08;开发工程师&#xff09;能力模型_Terry谈企业数字化的博客-CSDN博客文章浏览阅读494次。目标&#xff1a;基于对SAP abap 顾问能力模型的梳理&#xff0c;给一年左右经验的abaper 快速成长为三年经验提供超级燃料&#xff01;https://blog.csdn.net/j…

webshell之自建漏洞免杀

今天主要讲解&#xff0c;如何利用通用漏洞来进行命令执行&#xff0c;从而达到免杀效果 常规反序列化免杀 这种方式就相当于直接触发提供一个反序列化漏洞入口&#xff0c;但是能否被利用&#xff0c;还是在于服务端本身是否存在反序列化漏洞&#xff0c;下面给了一个例子&a…

基于机器学习的笔记本电脑导购系统

目 录 中英文摘要 第一章 概述 1 1.1 课题开发背景及意义 1 1.2 课题研究现状 2 1.3 课题主要研究内容 3 第二章 需求分析 4 2.1 功能需求分析 4 2.2 可行性分析 10 2.3 流程分析 11 2.4 数据流图 13 2.5 性能需求分析 15 第三章 开发技术及工具 16 3.1 系统开发模式技术 16 3…

《凤凰项目》读书笔记

文章目录 一、书名和作者二、书籍概览2.1 主要论点和结构2.2 目标读者和应用场景 三、核心观点与主题3.1 DevOps的核心原则与文化变革3.2 持续交付与自动化3.3 变更管理与风险控制3.4 关键绩效指标与持续改进 四、亮点与启发4.1 最有影响的观点4.2 对个人专业发展的启示 五、批…

【Linux--进程控制】

目录 一、进程等待1.1进程等待方法1.2获取子进程status 二、进程替换2.1单进程版本--最简单得程序替换2.2 进程替换得原理2.3 多进程版本--验证各种程序替换接口2.4 总结 一、进程等待 1.1进程等待方法 问题1&#xff1a;进程等待是什么&#xff1f; 通过系统调用wait/waitpi…

Java 定时任务

Java 定时任务 为什么需要定时任务&#xff1f; 我们来看一下几个非常常见的业务场景&#xff1a; 某系统凌晨 1 点要进行数据备份。某电商平台&#xff0c;用户下单半个小时未支付的情况下需要自动取消订单。某媒体聚合平台&#xff0c;每 10 分钟动态抓取某某网站的数据为…

五、关闭三台虚拟机的防火墙和Selinux

目录 1、关闭每台虚拟机的防火墙 2、关闭每台虚拟机的Selinux 2.1 什么是SELinux

使用SpringBoot和ZXing实现二维码生成与解析

一、ZXing简介 ZXing是一个开源的&#xff0c;用Java实现的多种格式的1D/2D条码图像处理库。它包含了用于解析多种格式的1D/2D条形码的工具类&#xff0c;目标是能够对QR编码&#xff0c;Data Matrix, UPC的1D条形码进行解码。在二维码编制上&#xff0c;ZXing巧妙地利用构成计…

系列十四、SpringBoot的jar包可以直接运行原理分析

一、普通jar包运行 vs SpringBoot jar包运行 1.1、普通jar包运行 general-test-1.0-SNAPSHOT.jar是位于D盘的一个普通的jar包&#xff0c;是idea中一个普通的maven项目通过package打包生成&#xff0c;为了方便测试我把它拷贝到D盘了。 java -jar general-test-1.0-SNAPSHOT.j…