拓展阅读:宜信开源|调用链系列(1):解读UAVStack中的贪吃蛇

调用链系列(二):解读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:

规范中已经为我们提供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接口。

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

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

当用户尝试调用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方法,如下图:

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

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

七、举一反三

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

读完本文,大家应该能够在不影响原代码的前提下,通过简单代码获取进入容器的所有Http请求的body和header。不过对于特殊技术栈,还需要进行适配。如果项目中使用了Jersey且使用application/x-www-form-urlencoded形式传递参数等信息,而服务端没有使用@FormParam注解来获取参数,那么获取body以后用户将无法获取参数。但至少我们已经验证了这条路是可行的,所以已经成功了一半。希望这份技术分享能够在工作中帮到大家。

作者:李崇

来源:宜信技术学院

request中的内容存储_宜信开源|调用链系列(3):解读UAVStack中的调用链技术...相关推荐

  1. pb 判断sql 是否合法_宜信技术|《SQL优化最佳实践》作者带你重新了解SQL

    一.SQL :一种熟悉又陌生的编程语言 这里有几个关键词:"熟悉"."陌生"."编程语言". 说它"熟悉",是因为它是D ...

  2. 使用PHPExcel将Excel中的内容存储到数据库

    PHPExcel将.xlsx文件中的内容存储到MySQL数据库 一.PHPExcel下载 PHPExcel下载可以参照我之前发过的文章--Composer安装与PHPWord的下载与使用 查找版本命令 ...

  3. 宜信开源|微服务任务调度平台SIA-TASK入手实践

    引言 最近宜信开源微服务任务调度平台SIA-TASK,SIA-TASK属于分布式的任务调度平台,使用起来简单方便,非常容易入手,部署搭建好SIA-TASK任务调度平台之后,编写TASK后配置JOB进行 ...

  4. pandas将dataframe中的内容为列表(list)的数据列裂变、拆分为多个新的数据列实战:拆分为多个新的数据列(并指定新数据列的名称)、数据列中的列表(不等长)拆分为多个新的数据列产生NaN

    pandas将dataframe中的内容为列表(list)的数据列裂变.拆分为多个新的数据列实战:拆分为多个新的数据列(并指定新数据列的名称).数据列中的列表(不等长)拆分为多个新的数据列(产生NaN ...

  5. 编写程序,在文件file1.dat中存入字符串“good morning”,然后将file1.dat中的内容输出到屏幕上,并复制到文件file2.dat中

    <程序设计基础-c语言>杨莉 刘鸿翔 ISBN-978-7-03-032903-5 p257 习题8 7.编写程序,在文件file1.dat中存入字符串"good morning ...

  6. 如何控制在一个软件中特殊的字符比如#都显示为红色呢?该字符是作为标签中的内容出现的,可能出现在JLABEL,JCheckBox,JCombox的标签中的,

    如何控制在一个软件中特殊的字符比如#都显示为红色呢?该字符是作为标签中的内容出现的,可能出现在JLABEL,JCheckBox,JCombox的标签中的, 1)在资源文件中该如何配置, 2)如果是硬编 ...

  7. 论文阅读:基于区块链的一个车联网轻量级安全V2V通信特点:利用无线网络传输在V2V通信中的信道特性,生成特殊的LF(链路指纹)用于标识每个信道,区块链技术用于生成区块

    论文阅读:基于区块链的一个车联网轻量级安全V2V通信特点:利用无线网络传输在V2V通信中的信道特性,生成特殊的LF(链路指纹)用于标识每个信道,区块链技术用于生成区块. 系统模型: 汽车使用MICAz ...

  8. 宜信开源|数据库审核软件Themis的规则解析与部署攻略

    一.介绍 Themis是宜信公司DBA团队开发的一款数据库审核产品,可帮助DBA.开发人员快速发现数据库质量问题,提升工作效率.其名称源自希腊神话中的正义与法律女神.项目取此名称,寓意此平台对数据库质 ...

  9. 宜信开源|详解PaaS平台LAIN的功能和架构

    [技术沙龙002期]数据中台:宜信敏捷数据中台建设实践|宜信技术沙龙 将于5月23日晚8点线上直播,点击报名 LAIN是宜信公司大数据创新中心开发的开源PaaS平台.在金融的场景下,LAIN 是为解放 ...

最新文章

  1. LabVIEW实现CRC校验
  2. 234. Palindrome Linked List 回文链表
  3. 洛谷P1373 小a和uim之大逃离 动态规划
  4. AngularJS JetBrains WebStorm简介
  5. (详解)11年真题:求两个序列合并后的的中位数
  6. 【论文阅读】Single- and Cross-Modality Near Duplicate Image PairsDetection via Spatial Transformer Compar
  7. 前端激荡三十年(一本最详细的编年史册)
  8. 区块链项目数据存储系统分析
  9. 一、PR的初始重要设置
  10. vs2012报“nuget 基础连接已经关闭:发送时发生错误”的问题
  11. manjaro安装tim
  12. 【C++】类的6个默认成员函数详解
  13. LinuxQQ3.0体验和下载方式
  14. linux中的ubiq命令用途,Linux3剑客习题及解答
  15. Ubuntu卸载python(慎重)
  16. SRCNN超分辨率Pytorch实现,代码逐行讲解,附源码
  17. PostgreSQL 常用命令实战
  18. sklearn代码查询(学习笔记)
  19. VUE+antv/x6实现拖拽自定义流程图 X6FlowChart
  20. 魔法少女 计蒜客 - T1551(动态规划)

热门文章

  1. pdfh5.js 基于pdf.js和jQuery,web/h5/移动端PDF预览手势缩放插件。
  2. 2022-2028年中国工业大数据行业深度调研及投资前景预测报告
  3. 计算机名称改变之后,HOUDINI Server 连接不上的解决办法
  4. 0709 C语言常见误区----------函数指针问题
  5. Hadoop学习笔记一 简要介绍
  6. shell监控java接口服务_Linux系统下Java通过shell脚本监控重启服务
  7. 人脸服务器如何与门禁系统对接,人脸识别门禁系统终端设备接口说明
  8. 用python设计学生管理系统_python+tkinter实现学生管理系统
  9. 嵌入式linux alsa,嵌入式Linux下ALSA音频架构ALSA-lib移植与编译心得
  10. 模拟器不全屏_iOS 14实测GBA游戏模拟器下载