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前后端分离中,后端拦截器拦截后,前端没有对应的返回码可以判断
项目登录流程如下 用户进入前端登录界面,输入账号密码等,输入完成之后前端发送请求到后端(拦截器不会拦截登录请求),后端验证账号密码等成功之后生成Token并存储到数据库,数据库中包含该Token过期时 ...
- Java过滤器处理Ajax请求,Java拦截器处理Ajax请求,拦截器Ajax请求
Java过滤器处理Ajax请求,Java拦截器处理Ajax请求,拦截器Ajax请求 >>>>>>>>>>>>>>&g ...
- spring mvc 通过拦截器记录请求数据和响应数据
spring mvc 能过拦截器记录请求数据记录有很多种方式,主要有以下三种: 1:过滤器 2:HandlerInterceptor拦截器 3:Aspect接口控制器 但是就我个人所知要记录返回的数据 ...
- Struts2 拦截器—拦截action
对于拦截器的基本使用这里我就懒得打字了,我这里就讲下如何用 Struts2 拦截器 拦截action.这是我个人的想法,如果有什么不对的,或者你们有什么更好的方法.请多多留言! 拦截器的默认拦截的方法 ...
- 防止SpringMVC拦截器拦截js等静态资源文件
SpringMVC提供<mvc:resources>来设置静态资源,但是增加该设置如果采用通配符的方式增加拦截器的话仍然会被拦截器拦截,可采用如下方案进行解决: 方案一.拦截器中增加针对静 ...
- EF Core3.0+ 通过拦截器实现读写分离与SQL日志记录
前言 本文主要是讲解EF Core3.0+ 通过拦截器实现读写分离与SQL日志记录 注意拦截器只有EF Core3.0+ 支持,2.1请考虑上下文工厂的形式实现. 说点题外话.. 一晃又大半年没更新技 ...
- springboot springmvc拦截器 拦截POST、PUT、DELETE请求参数和响应数据,并记录操作日志
1.操作日志实体类 @Document(collection = "operation_log") @Getter @Setter @ToString public class O ...
- Structs2 中拦截器获取请求参数
前言 环境:window 10,JDK 1.7,Tomcat 7 测试代码 package com.szxy.interceptor; import java.util.Map; import jav ...
- SpringBoot 拦截器 && 拦截之后返回前台自定义格式
1.加入 阿里的 json jar包 <!--json jar相关jar包--> <dependency> <groupId>com.fasterxml.jacks ...
随机推荐
- TS基础应用 & Hook中的TS
说在前面 本文难度偏中下,涉及到的点大多为如何在项目中合理应用ts,小部分会涉及一些原理,受众面较广,有无TS基础均可放心食用. **>>>> 阅完本文,您可能会收获到< ...
- ABP Framework V4.4 RC 新增功能介绍
目录 新增功能概述 启动模板删除 EntityFrameworkCore.DbMigrations 项目 CMS-Kit 动态菜单管理 Razor引擎对文本模板的支持 DbContext/Entiti ...
- keycloak~账号密码认证和授权码认证
用户名密码登录 POST /auth/realms/demo/protocol/openid-connect/token 请求体 x-www-form-urlencoded grant_type:pa ...
- 【Quartz】Quartz存储与持久化-基于quartz.properties的配置
林炳文Evankaka原创作品.转载请注明出处http://blog.csdn.net/evankaka 一. Quartz存储与持久化 Quartz提供两种基本作业存储类型.第一种类型叫做RAM ...
- linux 生成密钥
p.p1 { margin: 0; font: 16px "Helvetica Neue" } span.s1 { font: 16px ".PingFang SC&qu ...
- Window server 2016 搭建Java Web环境
系统下载 下载种子(迅雷下载): ed2k://|file|cn_windows_server_2016_updated_feb_2018_x64_dvd_11636703.iso|629426585 ...
- mongodb,redis,mysql的区别和具体应用场景(转)
一.MySQL 关系型数据库. 在不同的引擎上有不同 的存储方式. 查询语句是使用传统的sql语句,拥有较为成熟的体系,成熟度很高. 开源数据库的份额在不断增加,mysql的份额页在持续增长. 缺点就 ...
- 如何为HttpServer服务器配置PHP FastCGI,从而让HttpServer具备动态交互能力
一.软件准备 1.下载HttpServer :HttpServer.zip HttpServer是一款windows平台下基于IOCP模型的轻量级.高并发.高性能web服务器(参见文章). 2.下载W ...
- 【Azure 环境】Azure通知中心(Notification Hub)使用百度推送平台解说
问题描述 在通知中心的页面中显示支持BaiDu,介绍一下支持的是百度(Baidu)的什么吗?Azure的这个功能在国内使用的时候是否可以保证国内安卓手机的信息送达率? 问题解答 通知中心的页面中的Ba ...
- 【012】JavaSE面试题(十二):多线程(2)
第一期:Java面试 - 100题,梳理各大网站优秀面试题.大家可以跟着我一起来刷刷Java理论知识 [012] - JavaSE面试题(十二):多线程(2) 第1问:多线程的创建方式? 方式一:继承 ...