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