HarmonyOS开发者限时福利来啦!最高10w+现金激励等你拿~ 了解详情
写点什么

调用链系列三:解读 UAVStack 中的调用链技术

  • 2020-02-11
  • 本文字数:2394 字

    阅读完需:约 8 分钟

调用链系列三:解读UAVStack中的调用链技术

在 Java 中,HTTP 协议的请求/响应模型是由 Servlet 规范+Servlet 容器(如 Tomcat)实现的。换句话说,在类 Tomcat 容器中,一次完整的 HTTP 请求都是通过实现 Servlet 规范完成的;Spring、Jesery 等技术栈也是在 Servlet 规范基础上封装的。因此我们可以借助底层的 Servlet 规范来获取 Java 技术栈中 HTTP 的 body 和 header,即通过拦截用户自定义实现的 HttpServlet 类中的 HttpServletRequest 和 HttpServletResponse,获取 HTTP 的 body 和 header。


通过阅读前几篇文章大家知道,调用链模型和架构都是依托 UAVStack 的中间件增强框架技术实现的。在这篇文章中,我会向大家具体介绍如何从零开始捕获 body 和 header。

拦截 http 请求

想要在尽可能少改动代码的前提下从请求中提取 body 和 header,必须对进入容器的请求进行统一拦截,否则就需要在所有 HttpServlet 实现类中嵌入代码。这里要再次感谢 Servlet 规范制定者为我们提供的 filter 机制。


根据 Servlet 规范,filter 是一个可重用的代码段,可以转换 HTTP requests、responses 和 header 信息的内容。过滤器一般不会为一个 request 创建一个响应,而是会修改或适配一个 request 和 response。filter 主要提供四种拦截方式:


  • REQUEST:直接访问目标资源时执行过滤器。包括:在地址栏中直接访问、表单提交、超链接、重定向,只要在地址栏中可以看到目标资源的路径,就是 REQUEST;

  • FORWARD:转发访问执行过滤器。包括 RequestDispatcher#forward()方法、< jsp:forward>标签都是转发访问;

  • INCLUDE:包含访问执行过滤器。包括 RequestDispatcher#include()方法、< jsp:include>标签都是包含访问;

  • ERROR:当目标资源在 web.xml 中配置为< error-page>中时,并且真的出现了异常,转发到目标资源时,会执行过滤器。


这里我们只需使用 REQUEST 模式。配置 filter 以后,我们就可以从 filter 的 doFilter 方法中获取到 HttpServletRequest 和 HttpServletResponse(后文简称 request 和 response)了。

获取 header

上文中我们已经通过 filter 机制获取了 request 和 response。打开对应源码实现我们可以发现如下 API:


1551237143561038990.png


规范中已经为我们提供 API 直接获取 header,通过组合使用 getHeaderNames()和 getHeader(String name)方法我们可以轻松获取到 request 和 response 中的 header。

获取 body

request 和 response 获取 body 的方式大体相同。此处我们先以 request 为例,后文会对不同之处进行适配。


从 request 的 API 中可以发现,body 在 Java 中是以 ServletInputStream 形式存储的,并且 ServletInputStream 是继承的 InputStream。若直接读取,用户获取到的 body 将为空(因为 InputStream 只能被读取一次,除非把指针回执)。这里我们就需要借助 Servlet 的 wrapper 机制了。

Servlet 中的 wrapper

这里简单介绍一下 requestWrapper 和 responseWrapper。wrapper 是一种装饰模式,在 Servlet 规范中通过继承 HttpServletResponseWrapper 和 HttpServletRequestWrapper 实现,相当于为 request 和 response 进行了一次套壳,类似于 Java 中的代理,这样所有操作 request 和 response 的动作都会经过我们的自定义 wrapper,使重复获取 request 和 response 中的 body 成为可能。

编写自己的 wrapper

我们以 request 为例,解释如何编写自定义 wrapper。打开 servlet-api 源码可见 HttpServletRequestWrapper 继承了 ServletRequestWrapper 并且实现了 HttpServletRequest 接口。


1551237151328071800.png


ServletRequestWrapper 已经帮我们实现了大部分的方法。


1551237162163017474.jpg


我们只需要将关心的几个方法覆写即可,如:getInputStream 和 getReader 等。


1551237169911062083.jpg


当用户尝试调用 getReader 或 getInputStream 时,我们将之替换为自己的流,并且额外提供一个 getContent()方法,将提前从 StringBuilder 或 byte[]中读取到的 body 内容进行提取。


编写完自定义 wrapper 以后,我们就可以将其放入我们上文定义好的 filter 中,并将原 request 进行包装替换,进而将用户的 request 都变成我们的 requestWrapper。

优化提取逻辑

上文的方法相当于是将包含 body 的 inputStream 提前进行一次读取,将其存储在中间 byte[]或 StringBuilder 当中,当用户在调用 getInputStream 时,将 byte[]或 StringBuilder 转成 inputStream 返给用户。如果用户根本不关心本次 http 请求的 body,即用户根本没有使用此次请求的 body,那我们将其提前读取出来相当于做了一次无用功(浪费了宝贵的 CPU 时间和内存资源)。如何保证只有在用户使用时才读取 inputStream,并且当用户或后续逻辑多次获取 body 时都只读一次是我们优化的目标。


答案还是继续从源码中寻找。既然我们的数据在 inputStream 中,那我们可以跟进源码,看看 inputStream 是如何被读取到的。在 Servlet 规范中,inputStream 被封装成了 ServletInputStream,而 ServletInputStream 又提供了一个 readLine 方法。仔细观察可以发现,他们都是调用了 inputStream 中的 read 方法,如下图:


1551237176632045190.png


既然 read 方法是统一入口,是否只需要自定义实现一个 ServletInputStream 并覆写其中的 read()方法就能修改所有读取方式了呢?答案是肯定的。只要在用户调用 read 方法时,悄悄复制一份我们关心的内容,就能保证只有在用户使用 body 时才读取 inputStream。


下一个问题就是如何保证在用户多次调用 read 时只读取一次 inputStream。这里需要借助一个 AtomicBoolean 标志:当已经进行了一次完整读取后,将其置为 true;否则为 false。最终效果如下:


1551237183193085247.png


举一反三


这里我们使用 Servlet 规范中的 filter 和 wrapper 机制来获取进入我们容器(Tomcat)中所有 Http 请求的 body 和 header。这个能力在实际生产中还能进一步拓展,如:传输某些敏感数据时,在 Client 端进行加密,然后在 Server 端统一解密,并格式化 Client 端上送的数据格式等。


本文转载自宜信技术学院。


原文链接:http://college.creditease.cn/detail/218


2020-02-11 20:16795

评论

发布
暂无评论
发现更多内容

微信多开 WechatTweak for Mac(微信多开、消息防撤回工具)

理理

制造企业为什么必须数字化转型?

万界星空科技

数字化转型 数字化 智能工厂 mes 万界星空科技

win版Driver Talent Pro (驱动人生) v8.1.11.46 特别版

iMac小白

人工智能文生图介绍

测试人

人工智能 软件测试 AIGC

一文带你理解透MyBatis源码

华为云开发者联盟

Java JVM mybatis 华为云 华为云开发者联盟

MatrixOne→MatrixOS:矩阵起源的创业史即将用“AI Infra”和“AI Platform”书写新章程

MatrixOrigin

数据库 AI 云原生

炫酷JavaScript文本时钟

南城FE

JavaScript 前端

阿里云入选Gartner「边缘分发平台市场指南」代表厂商

MasterInTech

CDN 阿里云; 边缘云

全球八大云厂商,谁的RDS MySQL性能最强?

NineData

腾讯云 AWS 华为云 云厂商 RDS MySQL性能测试

win版IDimager Photo Supreme 2024(图片管理软件) v2024.1激活版

iMac小白

实力!云起无垠晋级“第九届安全创客汇”年度10强

云起无垠

推动AI“产业实用”的腾讯云,不可小视

ToB行业头条

Redis Desktop Manager for Mac(Redis桌面管理工具)v2022.5.0中文激活版

理理

Eudic欧路词典 for Mac 专业的翻译软件 Eudic欧路词典下载安装

理理

疯狂裁!大厂保利润的手段,中小企业学得来吗?

ToB行业头条

Navicat for MySQL Mac v16.3.4汉化版 数据库管理开发工具

理理

Vectorworks 2023 mac v2023 SP2激活版 3D建筑设计

理理

AI 倒贴钱也不好用,是因为没有「操作系统」

MatrixOrigin

数据库 AI 算力

win版Syncovery Premium(备份数据和同步工具) v10.14.13特别版

iMac小白

win版jprofiler(Java性能分析工具) v14.0.3 特别版

iMac小白

Prompt Tuning:大模型微调实战

百度开发者中心

人工智能 深度学习 大模型

人工智能文生图技术介绍

霍格沃兹测试开发学社

AI时代下的智能商品计划管理

第七在线

合约交易系统设计与开发

区块链开发团队DappNetWork

做软件测试需要懂代码吗?

禅道项目管理

软件测试 测试人员 测试面试

win版WonderFox HD Video Converter Factory Pro(高清视频转换软件) v27.5 便携版

iMac小白

win版Radiant Photo(照片编辑美化软件)v1.3.1特别版

iMac小白

BELLE-开源中文对话大模型

百度开发者中心

#人工智能 #大模型

什么是文生图?

测吧(北京)科技有限公司

测试

调用链系列三:解读UAVStack中的调用链技术_区块链_李崇_InfoQ精选文章