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 ...
随机推荐
- Linux中curl的用法
一.简介:在Linux中curl是一个利用URL规则在命令行下工作的文件传输工具,是一款强大的http命令行工具.支持文件的上传和下载,是综合传输工具. 二.语法:curl [option] [url ...
- Python调用shell命令常用方法
Python调用shell指令 方法一.使用os模块的system方法:os.system(cmd),其返回值是shell指令运行后返回的状态码,int类型,0表示shell指令成功执行,256表示未 ...
- linux:mysql
数据库介绍 mysql与php是黄金搭档(LAMP.LNMP) 常见的数据库 Oracle.Sql server.Access.Mariadb.Ds2等 安装 安装前确保计算机时间准确 源码包安装 优 ...
- hugo + nginx 搭建博客记录
作为一个萌新Gopher,经常逛网站能看到那种极简的博客,引入眼帘的不是花里胡哨的图片和样式,而是黑白搭配,简简单单的文章标题,这种风格很吸引我.正好看到煎鱼佬也在用这种风格的博客,于是卸载了我的wo ...
- 关于SOA和AOP
SOA:面向服务的架构(SOA)是一个组件模型,它将应用程序的不同功能单元(称为服务)通过这些服务之间定义良好的接口和契约联系起来.C/S端框架有WPF,服务端应用程序有WCF.asp.net web ...
- Java基础00-形参和返回值22
1. 形参和返回值 1.1 类名作为形参和返回值 1.2 抽象类名作为形参和返回值 代码示例: 方法的形参是抽象类名 抽象动物类:定义了一个抽象的eat方法 动物的操作类:创建一个useAnimal方 ...
- 雪花算法(SnowFlake)Java实现
分布式id生成算法的有很多种,Twitter的SnowFlake就是其中经典的一种. 算法原理 SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图: 1bit,不用,因为二 ...
- [刘阳Java]_BeanNameViewResolver视图解析器_第8讲
BeanNameViewResolver:这个视图解析器跟XmlViewResolver有点类似,也是通过把返回的逻辑视图名称去匹配定义好的视图bean对象.它要求视图bean对象都定义在Spring ...
- SLAM基础算法(1):卡尔曼滤波
对于一个正在运动中的小车来说,如何准确的知道它所处的位置? 理论家说:我可以通过牛顿公式来计算! 实践家说:给它装个GPS不就得了! 好吧,你们说的听上去都很有道理,但我们到底该相信谁? 现实情况是: ...
- 你有没有乱用“leader”,担当是个好东西
PS:此文为个人认知,不足处请多多批评. 近期在一线leader(经理)身上发现了几个case,然后又回想起前几年自己做的一些傻事,可能都属于明面上leader不会说什么,但私下会有情绪的类型: Ca ...