JVM-Sandbox 提供字节码插桩功能,用来改变目标程序运行时的代码,通过插入字节码增强程序中的功能; JVM-Sandbox-Repeater 是基于 JVM-Sandbox 的字节码插桩实现了流量录制和回放功能。
1、git clone 下源码 https://github.com/alibaba/jvm-sandbox-repeater.git
2、repeater 数据库准备
录制的数据需要进行保存,所以需要数据库,同时官网提供了前端页面展示。
查看 repeater-console-start
项目下的 application.properties
可以知道需要新建数据库 repeater, 数据库表结构文件在 repeater-console-dal
中的资源目录下
3、查看程序的启动方式
程序的运行方法包含在三个文件中分别是:bootstrap.sh
, install-local.sh
, package.sh
bin 目录下,首先 package.sh
,其作用是创建目标文件目录 /target/repeater
, 并将相关 Jar 包和配置文件放入。
bin 目录下,其次查看 install-local.sh
,其作用是下载 sandbox 程序包并解压到用户目录, 创建主目录下的 .sandbox-module
目录,并将target/repeater
下的文件拷贝到 .sandbox-module
, 可以手动执行 shell 中的命令也可以运行 install-local.sh 文件。
bin 目录下,bootstrap.sh
是运行 jvm-sanbox-repeater 程序的命令, 可以单独执行此 shell 文件便可运行 repeater 程序
${JAVA_HOME}/bin/java -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000 \
-javaagent:${HOME}/sandbox/lib/sandbox-agent.jar=server.port=8820\;server.ip=0.0.0.0 \
-Dapp.name=repeater \
-Dapp.env=daily \
-jar ${HOME}/.sandbox-module/repeater-bootstrap.jar
> ${JAVA_HOME}/bin/java
>
> 指定Java程序所在位置
> -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=8000
>
> 远程Debug 的设置,如不用可以省略
> -javaagent:${HOME}/sandbox/lib/sandbox-agent.jar=server.port=8820\;server.ip=0.0.0.0 \
>
> 以 permain 的方式指定代理,启动时进行插桩,具体详见 instanceation , jvm 提供了两种入口方式分别是 premain 和 agentmain
> -Dapp.name=repeater \
> -Dapp.env=daily \
>
> 这是传递的两个参数
> -jar ${HOME}/.sandbox-module/repeater-bootstrap.jar
>
> 启动 Java 程序
录制应用请求时响应中的中文存储乱码
经查询响应数据流转,已经确定问题出现在官方提供的插件 http-plugin 代码中
如果没有设置字符编码,默认会使用 ISO-8859-1
然而这个字符编码是不支持中文的,所以会出现乱码,setResponse
的时候指定字符编码或者 wtm.copier.setCharacterEncoding("UTF-8");
或者使用其他方式,参考类 API 文档即可。
当想设置抓取全部流量的时候就把他放开了,没想到出了问题。
config.setHttpEntrancePatterns(Lists.newArrayList("([\\s\\S]*)"));
reapater 当抓取到请求之后,就会调用自己本身的保存接口 http://127.0.0.1:8001/facade/api/record/save
将记录保存起来,因为录制时将白名单放开了,所以就会不断的录制自己的 http://127.0.0.1:8001/facade/api/record/save
, 接口,又因为实际上他将每次的请求体录制下来的同时进行了序列化(序列化后请求体变大)交给 save
接口保存 , 导致请求体越来越大,而且增长速度飞快。很快就会造成数据库表很大,JAVA 堆栈溢出等问题。
用 repeater 前端接口查看记录时,会发现显示的时间有点特殊,我们正常的时间显示是 2022-01-01 12:01:12
, 但是在前端界面显示的时候会显示为 2022-01-01 12:01:12.0
, 经过查看是 java.sql.Timestamp
的 toString()
就是这么写的,最后的 0 表示纳秒。
想按 2022-01-01 12:01:12
这种形式展示,只需要重新定义一个自己的 Date 类,重写 toString()
方法,在通过反射的方式将时间转换为自己自定义的对象即可。
public class BeantimeUtil {
private static String pattern = "yyyy-MM-dd HH:mm:ss";
public static <T> void Restore(T t) {
Class clazz = t.getClass();
handleRestore(clazz, t);
}
public static <T> void handleRestore(Class clazz, T t){
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
field.setAccessible(true);
if (field.getAnnotatedType().getType().getTypeName().equals(Date.class.getName())) {
SimpleDateFormat dateFormat = new SimpleDateFormat(pattern);
try {
if (Optional.ofNullable(field.get(t)).isPresent()) {
String date = dateFormat.format(field.get(t));
Date date_obj = dateFormat.parse(date, new ParsePosition(0));
field.set(t, new CustomDate(date_obj.getTime()));
}
} catch (IllegalAccessException e) {
// lgnore
e.printStackTrace();
}
}
}
Class superclass = clazz.getSuperclass();
if (superclass.getName().equals("java.lang.Object") || Modifier.isAbstract(superclass.getModifiers())){
return;
}
handleRestore(superclass, t);
}
}
public class CustomDate extends Date {
public CustomDate(long time) {
this.time = time;
}
private long time;
@Override
public String toString() {
long date = time;
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return dateFormat.format(new Date(date));
}
}