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

调用链系列(2):轻调用链实现

在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以后用户将无法获取参数。但至少我们已经验证了这条路是可行的,所以已经成功了一半。希望这份技术分享能够在工作中帮到大家。

开源地址:https://github.com/uavorg/uavstack

作者:李崇

来源:宜信技术学院

调用链系列(3):如何从零开始捕获body和header的更多相关文章

  1. 调用链系列二、Zipkin 和 Brave 实现(springmvc、RestTemplate)服务调用跟踪

    Brave介绍 1.Brave简介 Brave 是用来装备 Java 程序的类库,提供了面向标准Servlet.Spring MVC.Http Client.JAX RS.Jersey.Resteas ...

  2. 调用链系列三、基于zipkin调用链封装starter实现springmvc、dubbo、restTemplate等实现全链路跟踪

    一.实现思路 1.过滤器实现思路 所有调用链数据都通过过滤器实现埋点并收集.同一条链共享一个traceId.每个节点有唯一的spanId. 2.共享传递方式 1.rpc调用:通过隐式传参.dubbo有 ...

  3. 调用链系列一、Zipkin架构介绍、Springboot集承(springmvc,HttpClient)调用链跟踪、Zipkin UI详解

    1.Zipkin是什么 Zipkin分布式跟踪系统:它可以帮助收集时间数据,解决在microservice架构下的延迟问题:它管理这些数据的收集和查找:Zipkin的设计是基于谷歌的Google Da ...

  4. Tom_No_02 Servlet向流中打印内容,之后在调用finsihResponse,调用上是先发送了body,后发送Header的解释

    上次在培训班学上网课的时候就发现了这个问题,一直没有解决,昨天又碰到了,2-3小时也未能发现点端倪,今早又仔细缕了下,让我看了他的秘密 1.Servlet向流中打印内容,之后在调用finsihResp ...

  5. .net4.0调用非托管DLL的异常捕获

    转发: 由于有些非托管的DLL内部异常未有效处理,当托管程序调用到这样的DLL时,就引起托管程序意外退出. 托管程序使用通常的捕获try……catch块不起作用.原因是.NET 4.0里新的异常处理机 ...

  6. bootstrap的table调用本列ID

    我们是用json解析数据. 后台传送data数据~ String data = JSON.toJSONString(baseInfoService.list());request.setAttribu ...

  7. 第一章 区块链系列 联盟链FISCO BCOS 底层搭建

    想了解相关区块链开发,技术提问,请加QQ群:538327407 FISCO BCOS 基础安装教程:https://fisco-bcos-documentation.readthedocs.io/zh ...

  8. java 从零开始手写 RPC (05) reflect 反射实现通用调用之服务端

    通用调用 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何 ...

  9. SQL Server 变更数据捕获(CDC)

    标签:SQL SERVER/MSSQL SERVER/数据库/DBA/字段/对象更改 概述 变更数据捕获用于捕获应用到 SQL Server 表中的插入.更新和删除活动,并以易于使用的关系格式提供这些 ...

随机推荐

  1. web前端兼容性问题总结

    1.   HTML对象获取问题 FireFox:document.getElementById("idName");ie:document.idname或者document.get ...

  2. Java学习笔记之---面向对象

    Java学习笔记之---面向对象 (一)封装 (1)封装的优点 良好的封装能够减少耦合. 类内部的结构可以自由修改. 可以对成员变量进行更精确的控制. 隐藏信息,实现细节. (2)实现封装的步骤 1. ...

  3. 【深入浅出-JVM】(8):TLAB

    概念 TLAB(Thread Local Allocation Buffer)线程本地分配缓冲区(线程私有分配区,私有分配,公共查看),占用 Eden 区(缺省 Eden 的1%),默认开启,JVM ...

  4. Java操作文件

    import java.io.File; import java.io.IOException; import java.nio.file.*; import java.nio.file.attrib ...

  5. 洛谷 P1463、POI2002、HAOI2007 反素数

    题意: 求最小的$x\in[1,N]$,使得$x$为$g(x)$最大的数 中最小的一个. 分析: 1.$x$不会有超过$10$个不同质因子.理由:$2 \times 3\times 5...\time ...

  6. 跟着大彬读源码 - Redis 5 - 对象和数据类型(上)

    相信很多人应该都知道 Redis 有五种数据类型:字符串.列表.哈希.集合和有序集合.但这五种数据类型是什么含义?Redis 的数据又是怎样存储的?今天我们一起来认识下 Redis 这五种数据结构的含 ...

  7. Socket 连接问题之大量 TIME_WAIT

    简评:最近项目就出现了大量短连接导致建立新连接超时问题,最后是通过维护长连接解决的. 代理或者服务器设备都有端口限制,如果使用 TCP 连接,连接数量达到端口限制,在这种情况下,将不能创建新的连接. ...

  8. mysql_fetch_assoc与mysql_fetch_array的区别

    mysql_fetch_assoc与mysql_fetch_array的区别? 1. mysql_fetch_assoc : mysql_fetch_assoc() 函数从结果集中取得一行作为关联数组 ...

  9. luogu题解 P3388 【【模板】割点(割顶)】

    外加定义:在一个无向图中,如果删掉点 x 后图的连通块数量增加,则称点 x 为图的割点. 外加图示 开始思路为割桥上的点为割点,后来证明的确正确. 不过可惜的是他的逆定理错了(gg了),不过数据很弱以 ...

  10. 8 NLP-自然语言处理Demo

    1 NLP(自然语言处理) 1.1相似度 相似度和距离之间关系: 1.文本相似度: 1) 语义相似.但字面不相似: 老王的个人简介 铁王人物介绍 2) 字面相似.但是语义不相似: 我吃饱饭了 我吃不饱 ...