免费下载案例集|20+数字化领先企业人才培养实践经验 了解详情
写点什么

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

评论

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

爬虫代理ip和代理ip池确保用户隐私和数据安全的问题

Geek_bf375d

代理IP 代理IP设置 跨境电商 IP 路由 #HTTP

CnosDB 在最近新发布的 2.4.0 版本中增加对时空函数的支持。

CnosDB

开源 时序数据库 CnosDB

轻量应用服务器购买清单,双11看华为云这篇文章就够了

轶天下事

数字化专精特新战略,华为云耀云服务器L实例全面赋能企业

平平无奇爱好科技

Databend 开源周报第 119 期

Databend

挑战与机遇并存,华为云跨境电商新手入局必读指南来了

YG科技

未来之选:为什么向量数据库是您的数据管理利器

熬夜磕代码、

数据库

都快2024年了还在用传统服务器?试试华为云这款中小企业“上云”利器吧!

轶天下事

诚意满满 颠覆认知丨华为云这款轻量应用服务总能带来惊喜

平平无奇爱好科技

影响HTTP代理IP的稳定性的相关因素

Geek_bf375d

IP 代理IP 免费代理ip 跨境电商 #HTTP

万物智联的数字底座上,LightBeeOS筑起金融安全的春巢

脑极体

AI 金融

如何在 Python 中执行 MySQL 结果限制和分页查询

小万哥

Python 程序员 软件 后端 开发

保持http代理服务器的方法

Geek_bf375d

IP HTTP 跨境电子商务 跨境电商 #HTTP

投资机构Janus Capital Group为Rola-IP品牌融资700万美元

Geek_bf375d

IP 代理IP 免费代理ip 跨境电商 #HTTP

Linux 源码包安装

芯动大师

阿里云、腾讯云之后,华为云耀云服务器L实例凭实力成为“卷”死对手

轶天下事

职场“内卷”利器?华为云这款轻量应用服务器助力开发者效率翻倍

平平无奇爱好科技

深谙数字化奥义,华为云耀云服务器L实例助企业行稳致远.

平平无奇爱好科技

使用Amazon WAF有效保护托管Web应用

Hanson

轻量应用服务器看花眼?华为云耀云服务器L实例“闭眼”选准没错

轶天下事

推翻企业ERP管理“三座大山”,华为云耀云服务器L实例言出必行

平平无奇爱好科技

为什么要使用动态IP代理爬取

Geek_bf375d

IP HTTP 跨境电子商务 代理IP 跨境电商

爬虫IP使用后遇到问题如何解决

Geek_bf375d

IP 跨境贸易 代理IP 免费代理ip #HTTP

深入理解 Django 信号机制

K8sCat

django signal

快get2023年跨境电商出海指南,华为云教你把握快速捞金机遇

YG科技

小程序开发还犯迷糊?快戳这份华为云实用技巧效率翻倍

YG科技

代理服务IP的选择上有什么技巧

Geek_bf375d

IP HTTP 代理IP 免费代理ip 跨境电商

云上应用技术架构-LNMP应用

深蓝

当“996”开发模式都没用了,华为云这款轻量应用服务器才是终极答案

YG科技

小程序游戏风口有点“堵”?华为云耀云服务器L实例为企业疏难解困

YG科技

还在烧钱买量?华为云这款轻量应用服务器让跨境电商生意不难做

轶天下事

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