Springboot如何利用攔截器攔截請求信息收集到日志詳解
1、需求
最近在工作中遇到的一個需求,將請求中的客戶端類型、操作系統類型、ip、port、請求方式、URI以及請求參數值收集到日志中,網上找資料說用攔截器攔截所有請求然後收集信息,於是就開始瞭操作:
2、問題
試瞭之後發現當請求方式為POST,前端發送數據json時隻能用request.getReader()流獲取,自信滿滿從流中獲取之後發現請求之後報錯:
getInputStream() has already been called for this request...
於是網上找答案,發現是ServletRequest的getReader()和getInputStream()兩個方法隻能被調用一次,而且不能兩個都調用。那麼如果Filter中調用瞭一次,在Controller裡面就不能再調用瞭。
然後又開始找解決方法,說既然ServletInputStream不支持重新讀寫,就把流讀出來後用容器存儲起來,後面就可以多次利用瞭。
於是繼承 HttpServletRequestWrapper類(http請求包裝器,其基於裝飾者模式實現瞭HttpServletRequest界面)並實現想要重新定義的方法以達到包裝原生HttpServletRequest對象。還需要在過濾器裡將原生的HttpServletRequest對象替換成我們的RequestWrapper對象。
測試發現POST請求參數值可以在攔截器類中獲取到瞭,本以為大功告成,又發現GET請求不好使瞭,開始報錯Stream closed,一頓操作發現需要在過濾器進行判斷,如果是POST請求走自己的繼承的HttpServletRequestWrapper類請求,否則走普通的請求。終於成功!突然舒服瞭。
2、獲取
1)導入依賴為瞭獲取客戶端類型、操作系統類型、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(); //客戶端操作系統類型 String osType = userAgent.getOperatingSystem().getName(); //客戶端ip String clientIp = request.getRemoteAddr(); //客戶端port int clientPort = request.getRemotePort(); //請求方式 String requestMethod = request.getMethod(); //客戶端請求URI String requestURI = request.getRequestURI(); //客戶端請求參數值 String requestParam; //如果請求是POST獲取body字符串,否則GET的話用request.getQueryString()獲取參數值 if(StringUtils.equalsIgnoreCase(HttpMethod.POST.name(), requestMethod)){ requestParam = RequestUtil.getBodyString(request); }else{ requestParam = request.getQueryString(); } //客戶端整體請求信息 StringBuilder clientInfo = new StringBuilder(); clientInfo.append("客戶端信息:[類型:").append(clientType) .append(", 操作系統類型:").append(osType) .append(", ip:").append(clientIp) .append(", port:").append(clientPort) .append(", 請求方式:").append(requestMethod) .append(", URI:").append(requestURI) .append(", 請求參數值:").append(requestParam.replaceAll("\\s*", "")) .append("]"); //***這裡的clientInfo就是所有信息瞭,請根據自己的日志框架進行收集*** System.out.println(clientInfo); //返回ture才會繼續執行,否則一直攔截住 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對象,否則還會出現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的攔截地址/**區別開 registration.addUrlPatterns("/*"); registration.setName("accessRequestFilter"); registration.setOrder(1); return registration; } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RequestInterceptor()).addPathPatterns("/**"); } }
總結
到此這篇關於Springboot如何利用攔截器攔截請求信息收集到日志的文章就介紹到這瞭,更多相關Springboot攔截請求信息到日志內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- SpringBoot 請求參數忽略大小寫的實例
- CorsFilter 過濾器解決跨域的處理
- SpringBoot中使用Servlet三大組件的方法(Servlet、Filter、Listener)
- 在攔截器中讀取request參數,解決在controller中無法二次讀取的問題
- Java解決前端數據處理及亂碼問題