Springboot如何利用攔截器攔截請(qǐng)求信息收集到日志詳解
1、需求
最近在工作中遇到的一個(gè)需求,將請(qǐng)求中的客戶端類型、操作系統(tǒng)類型、ip、port、請(qǐng)求方式、URI以及請(qǐng)求參數(shù)值收集到日志中,網(wǎng)上找資料說用攔截器攔截所有請(qǐng)求然后收集信息,于是就開始了操作:
2、問題
試了之后發(fā)現(xiàn)當(dāng)請(qǐng)求方式為POST,前端發(fā)送數(shù)據(jù)json時(shí)只能用request.getReader()流獲取,自信滿滿從流中獲取之后發(fā)現(xiàn)請(qǐng)求之后報(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請(qǐng)求包裝器,其基于裝飾者模式實(shí)現(xiàn)了HttpServletRequest界面)并實(shí)現(xiàn)想要重新定義的方法以達(dá)到包裝原生HttpServletRequest對(duì)象。還需要在過濾器里將原生的HttpServletRequest對(duì)象替換成我們的RequestWrapper對(duì)象。
測(cè)試發(fā)現(xiàn)POST請(qǐng)求參數(shù)值可以在攔截器類中獲取到了,本以為大功告成,又發(fā)現(xiàn)GET請(qǐng)求不好使了,開始報(bào)錯(cuò)Stream closed,一頓操作發(fā)現(xiàn)需要在過濾器進(jìn)行判斷,如果是POST請(qǐng)求走自己的繼承的HttpServletRequestWrapper類請(qǐng)求,否則走普通的請(qǐng)求。終于成功!突然舒服了。
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;
/**
* 請(qǐng)求攔截器:攔截請(qǐng)求目的是將請(qǐng)求的信息收集到日志
*/
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();
//請(qǐng)求方式
String requestMethod = request.getMethod();
//客戶端請(qǐng)求URI
String requestURI = request.getRequestURI();
//客戶端請(qǐng)求參數(shù)值
String requestParam;
//如果請(qǐng)求是POST獲取body字符串,否則GET的話用request.getQueryString()獲取參數(shù)值
if(StringUtils.equalsIgnoreCase(HttpMethod.POST.name(), requestMethod)){
requestParam = RequestUtil.getBodyString(request);
}else{
requestParam = request.getQueryString();
}
//客戶端整體請(qǐng)求信息
StringBuilder clientInfo = new StringBuilder();
clientInfo.append("客戶端信息:[類型:").append(clientType)
.append(", 操作系統(tǒng)類型:").append(osType)
.append(", ip:").append(clientIp)
.append(", port:").append(clientPort)
.append(", 請(qǐng)求方式:").append(requestMethod)
.append(", URI:").append(requestURI)
.append(", 請(qǐng)求參數(shù)值:").append(requestParam.replaceAll("\\s*", ""))
.append("]");
//***這里的clientInfo就是所有信息了,請(qǐng)根據(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類請(qǐng)求,否則走正常的請(qǐng)求
if(StringUtils.equalsIgnoreCase(HttpMethod.POST.name(), request.getMethod())){
//一定要在判斷中new對(duì)象,否則還會(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());
// /* 是全部的請(qǐng)求攔截,和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如何利用攔截器攔截請(qǐng)求信息收集到日志的文章就介紹到這了,更多相關(guān)Springboot攔截請(qǐng)求信息到日志內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(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-11
Java中使用Preconditions來檢查傳入?yún)?shù)介紹
這篇文章主要介紹了Java中使用Preconditions來檢查傳入?yún)?shù)介紹,本文只是作為一個(gè)簡單的用法介紹,需要的朋友可以參考下2015-06-06
Java實(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-11
Java實(shí)現(xiàn)線程按序交替執(zhí)行的方法詳解
這篇文章主要為大家詳細(xì)介紹了Java如何實(shí)現(xiàn)線程按序交替執(zhí)行,文中的示例代碼講解詳細(xì),對(duì)我們了解線程有一定幫助,需要的可以參考一下2022-10-10
java實(shí)現(xiàn)一個(gè)簡單的Web服務(wù)器實(shí)例解析
這篇文章主要介紹了java實(shí)現(xiàn)一個(gè)簡單的Web服務(wù)器實(shí)例解析,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02
http中g(shù)et請(qǐng)求與post請(qǐng)求區(qū)別及如何選擇
這篇文章主要介紹了http中g(shù)et請(qǐng)求與post請(qǐng)求在應(yīng)用中應(yīng)該如何選擇,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2021-09-09

