摘要:拓展閱讀調(diào)用鏈系列解讀中的貪吃蛇調(diào)用鏈系列輕調(diào)用鏈實(shí)現(xiàn)在中,協(xié)議的請(qǐng)求響應(yīng)模型是由規(guī)范容器如實(shí)現(xiàn)的。在這篇文章中,我會(huì)向大家具體介紹如何從零開(kāi)始捕獲和。配置以后,我們就可以從的方法中獲取到和后文簡(jiǎn)稱和了。三獲取和獲取的方式大體相同。
拓展閱讀:調(diào)用鏈系列(1):解讀UAVStack中的貪吃蛇
調(diào)用鏈系列(2):輕調(diào)用鏈實(shí)現(xiàn)
在Java中,HTTP協(xié)議的請(qǐng)求/響應(yīng)模型是由Servlet規(guī)范+Servlet容器(如Tomcat)實(shí)現(xiàn)的。換句話說(shuō),在類Tomcat容器中,一次完整的HTTP請(qǐng)求都是通過(guò)實(shí)現(xiàn)Servlet規(guī)范完成的;Spring、Jesery 等技術(shù)棧也是在Servlet規(guī)范基礎(chǔ)上封裝的。因此我們可以借助底層的Servlet規(guī)范來(lái)獲取Java技術(shù)棧中HTTP的body和header,即通過(guò)攔截用戶自定義實(shí)現(xiàn)的HttpServlet類中的HttpServletRequest和HttpServletResponse,獲取HTTP的body和header。
通過(guò)閱讀前幾篇文章大家知道,調(diào)用鏈模型和架構(gòu)都是依托UAVStack的中間件增強(qiáng)框架技術(shù)實(shí)現(xiàn)的。在這篇文章中,我會(huì)向大家具體介紹如何從零開(kāi)始捕獲body和header。
一、攔截http請(qǐng)求想要在盡可能少改動(dòng)代碼的前提下從請(qǐng)求中提取body和header,必須對(duì)進(jìn)入容器的請(qǐng)求進(jìn)行統(tǒng)一攔截,否則就需要在所有HttpServlet實(shí)現(xiàn)類中嵌入代碼。這里要再次感謝Servlet規(guī)范制定者為我們提供的filter機(jī)制。
根據(jù)Servlet規(guī)范,filter是一個(gè)可重用的代碼段,可以轉(zhuǎn)換HTTP requests、responses和header信息的內(nèi)容。過(guò)濾器一般不會(huì)為一個(gè)request創(chuàng)建一個(gè)響應(yīng),而是會(huì)修改或適配一個(gè)request和response。filter主要提供四種攔截方式:
REQUEST:直接訪問(wèn)目標(biāo)資源時(shí)執(zhí)行過(guò)濾器。包括:在地址欄中直接訪問(wèn)、表單提交、超鏈接、重定向,只要在地址欄中可以看到目標(biāo)資源的路徑,就是REQUEST;
FORWARD:轉(zhuǎn)發(fā)訪問(wèn)執(zhí)行過(guò)濾器。包括RequestDispatcher#forward()方法、< jsp:forward>標(biāo)簽都是轉(zhuǎn)發(fā)訪問(wèn);
INCLUDE:包含訪問(wèn)執(zhí)行過(guò)濾器。包括RequestDispatcher#include()方法、< jsp:include>標(biāo)簽都是包含訪問(wèn);
ERROR:當(dāng)目標(biāo)資源在web.xml中配置為< error-page>中時(shí),并且真的出現(xiàn)了異常,轉(zhuǎn)發(fā)到目標(biāo)資源時(shí),會(huì)執(zhí)行過(guò)濾器。
這里我們只需使用REQUEST模式。配置filter以后,我們就可以從filter的doFilter方法中獲取到HttpServletRequest和HttpServletResponse(后文簡(jiǎn)稱request和response)了。
二、獲取header上文中我們已經(jīng)通過(guò)filter機(jī)制獲取了request和response。打開(kāi)對(duì)應(yīng)源碼實(shí)現(xiàn)我們可以發(fā)現(xiàn)如下API:
規(guī)范中已經(jīng)為我們提供API直接獲取header,通過(guò)組合使用getHeaderNames()和getHeader(String name)方法我們可以輕松獲取到request和response中的header。
三、獲取bodyrequest和response獲取body的方式大體相同。此處我們先以request為例,后文會(huì)對(duì)不同之處進(jìn)行適配。
從request的API中可以發(fā)現(xiàn),body在Java中是以ServletInputStream形式存儲(chǔ)的,并且ServletInputStream是繼承的InputStream。若直接讀取,用戶獲取到的body將為空(因?yàn)镮nputStream只能被讀取一次,除非把指針回執(zhí))。這里我們就需要借助Servlet的wrapper機(jī)制了。
四、Servlet中的wrapper這里簡(jiǎn)單介紹一下requestWrapper和responseWrapper。wrapper是一種裝飾模式,在Servlet規(guī)范中通過(guò)繼承HttpServletResponseWrapper和HttpServletRequestWrapper實(shí)現(xiàn),相當(dāng)于為request和response進(jìn)行了一次套殼,類似于Java中的代理,這樣所有操作request和response的動(dòng)作都會(huì)經(jīng)過(guò)我們的自定義wrapper,使重復(fù)獲取request和response中的body成為可能。
五、編寫(xiě)自己的wrapper我們以request為例,解釋如何編寫(xiě)自定義wrapper。打開(kāi)servlet-api源碼可見(jiàn)HttpServletRequestWrapper繼承了ServletRequestWrapper并且實(shí)現(xiàn)了HttpServletRequest接口。
ServletRequestWrapper已經(jīng)幫我們實(shí)現(xiàn)了大部分的方法。
我們只需要將關(guān)心的幾個(gè)方法覆寫(xiě)即可,如:getInputStream和getReader等。
當(dāng)用戶嘗試調(diào)用getReader或getInputStream時(shí),我們將之替換為自己的流,并且額外提供一個(gè)getContent()方法,將提前從StringBuilder或byte[]中讀取到的body內(nèi)容進(jìn)行提取。
編寫(xiě)完自定義wrapper以后,我們就可以將其放入我們上文定義好的filter中,并將原request進(jìn)行包裝替換,進(jìn)而將用戶的request都變成我們的requestWrapper。
六、優(yōu)化提取邏輯上文的方法相當(dāng)于是將包含body的inputStream提前進(jìn)行一次讀取,將其存儲(chǔ)在中間byte[]或StringBuilder當(dāng)中,當(dāng)用戶在調(diào)用getInputStream時(shí),將byte[]或StringBuilder轉(zhuǎn)成inputStream返給用戶。如果用戶根本不關(guān)心本次http請(qǐng)求的body,即用戶根本沒(méi)有使用此次請(qǐng)求的body,那我們將其提前讀取出來(lái)相當(dāng)于做了一次無(wú)用功(浪費(fèi)了寶貴的CPU時(shí)間和內(nèi)存資源)。如何保證只有在用戶使用時(shí)才讀取inputStream,并且當(dāng)用戶或后續(xù)邏輯多次獲取body時(shí)都只讀一次是我們優(yōu)化的目標(biāo)。
答案還是繼續(xù)從源碼中尋找。既然我們的數(shù)據(jù)在inputStream中,那我們可以跟進(jìn)源碼,看看inputStream是如何被讀取到的。在Servlet規(guī)范中,inputStream被封裝成了ServletInputStream,而ServletInputStream又提供了一個(gè)readLine方法。仔細(xì)觀察可以發(fā)現(xiàn),他們都是調(diào)用了inputStream中的read方法,如下圖:
既然read方法是統(tǒng)一入口,是否只需要自定義實(shí)現(xiàn)一個(gè)ServletInputStream并覆寫(xiě)其中的read()方法就能修改所有讀取方式了呢?答案是肯定的。只要在用戶調(diào)用read方法時(shí),悄悄復(fù)制一份我們關(guān)心的內(nèi)容,就能保證只有在用戶使用body時(shí)才讀取inputStream。
下一個(gè)問(wèn)題就是如何保證在用戶多次調(diào)用read時(shí)只讀取一次inputStream。這里需要借助一個(gè)AtomicBoolean標(biāo)志:當(dāng)已經(jīng)進(jìn)行了一次完整讀取后,將其置為true;否則為false。最終效果如下:
七、舉一反三這里我們使用Servlet規(guī)范中的filter和wrapper機(jī)制來(lái)獲取進(jìn)入我們?nèi)萜鳎═omcat)中所有Http請(qǐng)求的body和header。這個(gè)能力在實(shí)際生產(chǎn)中還能進(jìn)一步拓展,如:傳輸某些敏感數(shù)據(jù)時(shí),在Client端進(jìn)行加密,然后在Server端統(tǒng)一解密,并格式化Client端上送的數(shù)據(jù)格式等。
讀完本文,大家應(yīng)該能夠在不影響原代碼的前提下,通過(guò)簡(jiǎn)單代碼獲取進(jìn)入容器的所有Http請(qǐng)求的body和header。不過(guò)對(duì)于特殊技術(shù)棧,還需要進(jìn)行適配。如果項(xiàng)目中使用了Jersey且使用application/x-www-form-urlencoded形式傳遞參數(shù)等信息,而服務(wù)端沒(méi)有使用@FormParam注解來(lái)獲取參數(shù),那么獲取body以后用戶將無(wú)法獲取參數(shù)。但至少我們已經(jīng)驗(yàn)證了這條路是可行的,所以已經(jīng)成功了一半。希望這份技術(shù)分享能夠在工作中幫到大家。
開(kāi)源地址:https://github.com/uavorg/uav...
作者:李崇
來(lái)源:宜信技術(shù)學(xué)院
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/76144.html
摘要:前言在從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的框架七實(shí)現(xiàn)中實(shí)現(xiàn)了框架的的功能,不過(guò)最后指出代碼的邏輯不是很好,在這一章節(jié)就將這一部分代碼進(jìn)行優(yōu)化。 前言 在從零開(kāi)始實(shí)現(xiàn)一個(gè)簡(jiǎn)易的Java MVC框架(七)--實(shí)現(xiàn)MVC中實(shí)現(xiàn)了doodle框架的MVC的功能,不過(guò)最后指出代碼的邏輯不是很好,在這一章節(jié)就將這一部分代碼進(jìn)行優(yōu)化。 優(yōu)化的目標(biāo)是1.去除DispatcherServlet請(qǐng)求分發(fā)器中的http邏...
摘要:實(shí)現(xiàn)的四大模塊上文簡(jiǎn)述了源碼的大體框架結(jié)構(gòu),接下來(lái)我們來(lái)實(shí)現(xiàn)一個(gè)的框架,筆者認(rèn)為理解和實(shí)現(xiàn)一個(gè)框架需要實(shí)現(xiàn)四個(gè)大模塊,分別是封裝創(chuàng)建類構(gòu)造函數(shù)構(gòu)造對(duì)象中間件機(jī)制和剝洋蔥模型的實(shí)現(xiàn)錯(cuò)誤捕獲和錯(cuò)誤處理下面我們就逐一分析和實(shí)現(xiàn)。 什么是koa框架? ? ? ? ?koa是一個(gè)基于node實(shí)現(xiàn)的一個(gè)新的web框架,它是由express框架的原班人馬打造的。它的特點(diǎn)是優(yōu)雅、簡(jiǎn)潔、表達(dá)力強(qiáng)、自由度...
摘要:實(shí)現(xiàn)的四大模塊上文簡(jiǎn)述了源碼的大體框架結(jié)構(gòu),接下來(lái)我們來(lái)實(shí)現(xiàn)一個(gè)的框架,筆者認(rèn)為理解和實(shí)現(xiàn)一個(gè)框架需要實(shí)現(xiàn)四個(gè)大模塊,分別是封裝創(chuàng)建類構(gòu)造函數(shù)構(gòu)造對(duì)象中間件機(jī)制和剝洋蔥模型的實(shí)現(xiàn)錯(cuò)誤捕獲和錯(cuò)誤處理下面我們就逐一分析和實(shí)現(xiàn)。 什么是koa框架? ? ? ? ?koa是一個(gè)基于node實(shí)現(xiàn)的一個(gè)新的web框架,它是由express框架的原班人馬打造的。它的特點(diǎn)是優(yōu)雅、簡(jiǎn)潔、表達(dá)力強(qiáng)、自由度...
摘要:捕捉錯(cuò)誤確保捕獲運(yùn)行路由處理程序和中間件時(shí)發(fā)生的所有錯(cuò)誤非常重要。對(duì)和的調(diào)用表明當(dāng)前處理程序已完成并處于什么狀態(tài),將跳過(guò)鏈中的所有剩余處理程序,除了那些設(shè)置為處理上述錯(cuò)誤的處理程序。 錯(cuò)誤處理 錯(cuò)誤處理是指Express如何捕獲和處理同步和異步發(fā)生的錯(cuò)誤,Express附帶一個(gè)默認(rèn)的錯(cuò)誤處理程序,因此你無(wú)需編寫(xiě)自己的錯(cuò)誤處理程序即可開(kāi)始使用。 捕捉錯(cuò)誤 確保Express捕獲運(yùn)行路由處...
閱讀 706·2023-04-25 18:37
閱讀 2852·2021-10-12 10:12
閱讀 8526·2021-09-22 15:07
閱讀 616·2019-08-30 15:55
閱讀 3230·2019-08-30 15:44
閱讀 2252·2019-08-30 15:44
閱讀 1685·2019-08-30 13:03
閱讀 1616·2019-08-30 12:55