如何 0 成本启动全员 AI 技能提升?戳> 了解详情
写点什么

调用链系列三:解读 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:16932

评论

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

华为18级工程师三年心血终成趣谈网络协议文档(附大牛讲解)

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

阿里巴巴首发:Java核心框架指导手册1小时点击量破千万!

Java 编程 面试 程序人生 Alibaba

WorkPlus高端制造业移动数字化平台解决方案—华晨宝马

BeeWorks

即时通讯 移动办公平台 移动数字化底座 移动数字化基座 企业即时通讯平台

吐血整理!金九银十必问的1000道Java面试题及答案

Java 编程 程序员 架构 面试

浅析智慧交通有哪些应用场景?

一只数据鲸鱼

数据可视化 智慧城市 智慧交通 城市交通

cocoapods 的主模块如何判断子模块有没有被加载?

fuyoufang

ios swift 8月日更

关于飞书的告警通知,这里有个更好的办法

睿象云

运维 告警 运维平台 智能告警

ipfs挖矿合法吗?ipfs挖矿靠谱吗?

区块链 IPFS ipfs挖矿 ipfs矿机 filecoin挖矿

如何对接口参数的描述进行集中管理

CodeNongXiaoW

大前端 测试 后端 接口工具

图数据库在百度汉语中的应用

百度Geek说

数据库 后端

模块一作业

陈家豪

架构实战营

云原生多云容器编排平台karmada上手指南

谐云

云原生 开源技术

来!看排名一年上升16位的ClickHouse,如何在京东落地实践

京东科技开发者

数据库 Clickhouse

如何基于分布式KV研发一款消息中间件

Java 编程 面试 后端 中间件

通过明道云实现培训机构客户管理

明道云

打开vscode好像打开了原神?vscode原神背景推荐,比博燃

CodeNongXiaoW

vscode vscode背景 原神

阿里资深架构师终于把微服务架构与实践第2版PDF分享出来了

公众号_愿天堂没有BUG

Java 编程 程序员 架构 面试

量化交易炒币机器人系统搭建

量化系统19942438797

机器人 量化交易

交易所智能炒币机器人开发||量化交易炒币机器人系统搭建

Geek_23f0c3

量化交易机器人系统开发 炒币机器人

模块一作业

当归

微信业务架构 | 架构实战营

樊江。

架构实战营

Spring 配置加载

樊江。

Spring Framework

TCP协议认知篇

邱学喆

TCP协议 拥塞避免算法 慢启动算法 坚持定时器 TCP状图切换

高可用 | Xenon 实现 MySQL 高可用架构 部署篇

RadonDB

MySQL 数据库 Xenon RadonDB

如何从内部保障企业数据安全?用IT运维审计系统可以吗?

行云管家

网络安全 数据安全 堡垒机 IT运维 运维审计

计算机网络常用知识总结

Java 架构 后端 网络 计算机

技术分析| 实时音视频通讯中的流媒体是怎样传输的

anyRTC开发者

音视频 WebRTC 流媒体 流媒体传输

关于C++中“不能返回对象引用”的思考

她的男人是程序员

WebRTC中的RefCountedObject解析

她的男人是程序员

👊 【Spring技术原理】异步编程机制以及功能分析讲解

码界西柚

spring springboot 异步编程 8月日更

CERT和CWE之间有什么联系?

鉴释

安全编码规范 cwe cert

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