Springboot如何利用攔截器攔截請求信息收集到日志詳解
1、需求
最近在工作中遇到的一個(gè)需求,將請求中的客戶端類型、操作系統(tǒng)類型、ip、port、請求方式、URI以及請求參數(shù)值收集到日志中,網(wǎng)上找資料說用攔截器攔截所有請求然后收集信息,于是就開始了操作:
2、問題
試了之后發(fā)現(xiàn)當(dāng)請求方式為POST,前端發(fā)送數(shù)據(jù)json時(shí)只能用request.getReader()流獲取,自信滿滿從流中獲取之后發(fā)現(xiàn)請求之后報(bào)錯(cuò):
getInputStream() has already been called for this request...
于是網(wǎng)上找答案,發(fā)現(xiàn)是ServletRequest的getReader()和getInputStream()兩個(gè)方法只能被調(diào)用一次,而且不能兩個(gè)都調(diào)用。那么如果Filter中調(diào)用了一次,在Controller里面就不能再調(diào)用了。
然后又開始找解決方法,說既然ServletInputStream不支持重新讀寫,就把流讀出來后用容器存儲(chǔ)起來,后面就可以多次利用了。
于是繼承 HttpServletRequestWrapper類(http請求包裝器,其基于裝飾者模式實(shí)現(xiàn)了HttpServletRequest界面)并實(shí)現(xiàn)想要重新定義的方法以達(dá)到包裝原生HttpServletRequest對象。還需要在過濾器里將原生的HttpServletRequest對象替換成我們的RequestWrapper對象。
測試發(fā)現(xiàn)POST請求參數(shù)值可以在攔截器類中獲取到了,本以為大功告成,又發(fā)現(xiàn)GET請求不好使了,開始報(bào)錯(cuò)Stream closed,一頓操作發(fā)現(xiàn)需要在過濾器進(jìn)行判斷,如果是POST請求走自己的繼承的HttpServletRequestWrapper類請求,否則走普通的請求。終于成功!突然舒服了。
2、獲取
1)導(dǎo)入依賴為了獲取客戶端類型、操作系統(tǒng)類型、ip、port
<dependency> <groupId>eu.bitwalker</groupId> <artifactId>UserAgentUtils</artifactId> <version>1.21</version> </dependency>
2)封裝獲取body字符串的工具類
package com.btrc.access.util; import javax.servlet.http.HttpServletRequest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.nio.charset.Charset; public class RequestUtil { public static String getBodyString(HttpServletRequest request) { StringBuilder sb = new StringBuilder(); try ( InputStream inputStream = request.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, Charset.forName("UTF-8"))) ) { String line; while ((line = reader.readLine()) != null) { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } return sb.toString(); } }
3)攔截器類
package com.btrc.access.filter; import com.btrc.access.util.RequestUtil; import eu.bitwalker.useragentutils.UserAgent; import org.apache.commons.lang.StringUtils; import org.springframework.http.HttpMethod; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * 請求攔截器:攔截請求目的是將請求的信息收集到日志 */ public class RequestInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("user-agent")); //客戶端類型 String clientType = userAgent.getOperatingSystem().getDeviceType().getName(); //客戶端操作系統(tǒng)類型 String osType = userAgent.getOperatingSystem().getName(); //客戶端ip String clientIp = request.getRemoteAddr(); //客戶端port int clientPort = request.getRemotePort(); //請求方式 String requestMethod = request.getMethod(); //客戶端請求URI String requestURI = request.getRequestURI(); //客戶端請求參數(shù)值 String requestParam; //如果請求是POST獲取body字符串,否則GET的話用request.getQueryString()獲取參數(shù)值 if(StringUtils.equalsIgnoreCase(HttpMethod.POST.name(), requestMethod)){ requestParam = RequestUtil.getBodyString(request); }else{ requestParam = request.getQueryString(); } //客戶端整體請求信息 StringBuilder clientInfo = new StringBuilder(); clientInfo.append("客戶端信息:[類型:").append(clientType) .append(", 操作系統(tǒng)類型:").append(osType) .append(", ip:").append(clientIp) .append(", port:").append(clientPort) .append(", 請求方式:").append(requestMethod) .append(", URI:").append(requestURI) .append(", 請求參數(shù)值:").append(requestParam.replaceAll("\\s*", "")) .append("]"); //***這里的clientInfo就是所有信息了,請根據(jù)自己的日志框架進(jìn)行收集*** System.out.println(clientInfo); //返回ture才會(huì)繼續(xù)執(zhí)行,否則一直攔截住 return true; } }
4)繼承 HttpServletRequestWrapper類
package com.btrc.access.filter; import com.btrc.access.util.RequestUtil; import javax.servlet.ReadListener; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import java.io.*; import java.nio.charset.Charset; public class AccessRequestWrapper extends HttpServletRequestWrapper { private final byte[] body; public AccessRequestWrapper(HttpServletRequest request) { super(request); body = RequestUtil.getBodyString(request).getBytes(Charset.forName("UTF-8")); } @Override public BufferedReader getReader() throws IOException { return new BufferedReader(new InputStreamReader(getInputStream())); } @Override public ServletInputStream getInputStream() throws IOException { final ByteArrayInputStream bais = new ByteArrayInputStream(body); return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } @Override public boolean isFinished() { return false; } @Override public boolean isReady() { return false; } @Override public void setReadListener(ReadListener readListener) { } }; } }
5)過濾器類
package com.btrc.access.filter; import org.apache.commons.lang.StringUtils; import org.springframework.http.HttpMethod; import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; public class AccessFilter implements Filter { @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; //如果是POST走自己的繼承的HttpServletRequestWrapper類請求,否則走正常的請求 if(StringUtils.equalsIgnoreCase(HttpMethod.POST.name(), request.getMethod())){ //一定要在判斷中new對象,否則還會(huì)出現(xiàn)Stream closed問題 filterChain.doFilter(new AccessRequestWrapper(request),servletResponse); }else{ filterChain.doFilter(servletRequest,servletResponse); } } @Override public void destroy() { } }
6)攔截器過濾器配置類
package com.btrc.access.config; import com.btrc.access.filter.AccessFilter; import com.btrc.access.filter.RequestInterceptor; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import javax.servlet.Filter; /** * 攔截器過濾器配置類 */ @Configuration public class WebMvcConfig implements WebMvcConfigurer { @Bean public FilterRegistrationBean httpServletRequestReplacedFilter() { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(new AccessFilter()); // /* 是全部的請求攔截,和Interceptor的攔截地址/**區(qū)別開 registration.addUrlPatterns("/*"); registration.setName("accessRequestFilter"); registration.setOrder(1); return registration; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RequestInterceptor()).addPathPatterns("/**"); } }
總結(jié)
到此這篇關(guān)于Springboot如何利用攔截器攔截請求信息收集到日志的文章就介紹到這了,更多相關(guān)Springboot攔截請求信息到日志內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaEE實(shí)現(xiàn)前后臺(tái)交互的文件上傳與下載
這篇文章主要介紹了JavaEE實(shí)現(xiàn)前后臺(tái)交互的文件上傳與下載,分享相關(guān)技術(shù),實(shí)現(xiàn)文件上傳下載功能,需要的朋友可以參考下2015-11-11Java中使用Preconditions來檢查傳入?yún)?shù)介紹
這篇文章主要介紹了Java中使用Preconditions來檢查傳入?yún)?shù)介紹,本文只是作為一個(gè)簡單的用法介紹,需要的朋友可以參考下2015-06-06Java實(shí)現(xiàn)線程的暫停和恢復(fù)的示例詳解
這幾天的項(xiàng)目中,客戶給了個(gè)需求,希望我可以開啟一個(gè)任務(wù),想什么時(shí)候暫停就什么時(shí)候暫停,想什么時(shí)候開始就什么時(shí)候開始,所以本文小編給大家介紹了Java實(shí)現(xiàn)線程的暫停和恢復(fù)的示例,需要的朋友可以參考下2023-11-11淺談synchronized加鎖this和class的區(qū)別
synchronized 是 Java 語言中處理并發(fā)問題的一種常用手段,本文主要介紹了synchronized加鎖this和class的區(qū)別,具有一定的參考價(jià)值,感興趣的可以了解一下2021-11-11Java實(shí)現(xiàn)線程按序交替執(zhí)行的方法詳解
這篇文章主要為大家詳細(xì)介紹了Java如何實(shí)現(xiàn)線程按序交替執(zhí)行,文中的示例代碼講解詳細(xì),對我們了解線程有一定幫助,需要的可以參考一下2022-10-10java實(shí)現(xiàn)一個(gè)簡單的Web服務(wù)器實(shí)例解析
這篇文章主要介紹了java實(shí)現(xiàn)一個(gè)簡單的Web服務(wù)器實(shí)例解析,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02http中g(shù)et請求與post請求區(qū)別及如何選擇
這篇文章主要介紹了http中g(shù)et請求與post請求在應(yīng)用中應(yīng)該如何選擇,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-09-09