├── README.md ├── go.tar ├── go └── middleware │ ├── Stdout.class │ └── allow-url.txt ├── gor ├── gor_v0.14.1_mac.tar.gz ├── gor_v0.14.1_x64.tar.gz ├── pom.xml └── src └── go ├── CompareHttpLog.java ├── JsonContentCompare.java ├── ReqRespEntity.java ├── middleware ├── Stdout.java └── allow-url.txt └── urlJsonIgnorePath.properties /README.md: -------------------------------------------------------------------------------- 1 | 项目概述 2 | ==== 3 | 本项目功能是将某台机器的某个端口的流量回放到指定的机器上,支持同步\异步方式 4 | 5 | 6 | gor工具原理 7 | ------- 8 | 9 | 基于github上的一个Go语言写的开源流量复制工具gor的扩展 10 | * gor项目地址:https://github.com/buger/gor 11 | * 到 https://github.com/buger/gor/releases 下载最新版本 12 | 分为mac和linux,按自己平台选择,或者选择本项目里的两个tar包 13 | * 解压后是一个名叫gor的文件,使用方式如下 14 | 1. 在本机8080端口部署一个web应用 15 | 2. 在另外一台机器比如192.168.11.22的8080端口部署同样的应用 16 | 3. 在本机有sudo权限的用户下执行如下命令 17 | sudo ./gor --input-raw :8080 --output-http http://192.168.11.22:8080 18 | 即可将当前机器的8080端口流量同步回放到http://192.168.11.22:8080端口 19 | 所以访问本机8080的应用就可以看到同时在http://192.168.11.22:8080也有请求过去了 20 | * gor工具所有的命令参数说明可以看执行sudo ./gor -h 命令查看 21 | 22 | 23 | 基于gor工具的扩展 24 | ---------- 25 | 添加了一个java的class文件,go.middleware.Stdout,该功能支持在配置文件中用添加url表达式,用于只将指定的URL复制出来 26 | * 源码见src目录下 27 | * 执行的命令是 28 | ./gor --input-raw-track-response --input-raw :8082 --middleware "java go.middleware.Stdout" --output-file 29 | 即加一个参数: --middleware "java go.middleware.Stdout" 30 | * 注意,要将该文件路径放置到与gor文件同级目录下的gor/middleware/下面 31 | * 生产上执行的命令如下 32 | nohup ./gor --input-raw-track-response --input-raw :8082 --middleware "java go.middleware.Stdout" --output-file gor-online-%Y-%m-%d-%H.log --output-file-append >dev/null & 33 | 将请求保存到文件中用于异步回放 34 | 35 | 36 | 文件结果比对 37 | ------ 38 | CompareHttpLog文件用于将两个文件结果进行比对,支持json格式结果中只比较部分json节点 39 | -------------------------------------------------------------------------------- /go.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niweicumt/copyflow/a203fcf226640a3b1ef5b72a61f3cfb0dd7ce563/go.tar -------------------------------------------------------------------------------- /go/middleware/Stdout.class: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niweicumt/copyflow/a203fcf226640a3b1ef5b72a61f3cfb0dd7ce563/go/middleware/Stdout.class -------------------------------------------------------------------------------- /go/middleware/allow-url.txt: -------------------------------------------------------------------------------- 1 | .*confirm.*,.*generate.* -------------------------------------------------------------------------------- /gor: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niweicumt/copyflow/a203fcf226640a3b1ef5b72a61f3cfb0dd7ce563/gor -------------------------------------------------------------------------------- /gor_v0.14.1_mac.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niweicumt/copyflow/a203fcf226640a3b1ef5b72a61f3cfb0dd7ce563/gor_v0.14.1_mac.tar.gz -------------------------------------------------------------------------------- /gor_v0.14.1_x64.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/niweicumt/copyflow/a203fcf226640a3b1ef5b72a61f3cfb0dd7ce563/gor_v0.14.1_x64.tar.gz -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | com.edol.gor 6 | copyflow 7 | SNAPSHOT 8 | 9 | 10 | 11 | org.apache.maven.plugins 12 | maven-compiler-plugin 13 | 14 | 1.8 15 | 1.8 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | com.google.guava 24 | guava 25 | 18.0 26 | 27 | 28 | org.json 29 | json 30 | 20140107 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/go/CompareHttpLog.java: -------------------------------------------------------------------------------- 1 | package go; 2 | 3 | import com.google.common.base.Charsets; 4 | import com.google.common.base.Splitter; 5 | import com.google.common.collect.Iterables; 6 | import com.google.common.collect.Lists; 7 | import com.google.common.io.Files; 8 | import com.google.common.io.LineProcessor; 9 | 10 | import java.io.File; 11 | import java.io.IOException; 12 | import java.util.*; 13 | 14 | /** 15 | * Created by niwei on 16/7/20. 16 | */ 17 | public class CompareHttpLog { 18 | 19 | private static final String lineSeparator = System.getProperty("line.separator"); 20 | 21 | /** 22 | * HTTP解析时用到的分割器 23 | */ 24 | private static final Splitter httpParseSplitter = Splitter.on(" "); 25 | 26 | /** 27 | * properties文件解析时用到的分割器 28 | */ 29 | private static final Splitter propertiesParseSplitter = Splitter.on(","); 30 | 31 | private String leftFileName; 32 | 33 | private String rightFileName; 34 | 35 | private File resultFile; 36 | 37 | private Map urlJsonIgnorePath = new HashMap<>(); 38 | 39 | /** 40 | * @param leftFileName 比较文件路径 41 | * @param rightFileName 被比较文件路径 42 | * @param resultFileName 输出的结果文件路径 43 | * @param urlConfigurePropertyFile 用于文件响应内容比较时的URL黑名单配置 44 | * @throws IOException 45 | */ 46 | public CompareHttpLog(String leftFileName, String rightFileName, String resultFileName, String urlConfigurePropertyFile) throws IOException { 47 | this.leftFileName = leftFileName; 48 | this.rightFileName = rightFileName; 49 | 50 | /** 51 | * 比较结果文件处理 52 | */ 53 | this.resultFile = new File(resultFileName); 54 | Files.write("Http compare result", resultFile, Charsets.UTF_8); 55 | Files.append(lineSeparator, resultFile, Charsets.UTF_8); 56 | 57 | Properties properties = new Properties(); 58 | properties.load(Class.class.getClassLoader().getSystemResourceAsStream(urlConfigurePropertyFile)); 59 | for (String url : properties.stringPropertyNames()) { 60 | urlJsonIgnorePath.put(url, properties.getProperty(url)); 61 | } 62 | } 63 | 64 | /** 65 | * 将内容追加入文件 66 | * 67 | * @param contents 68 | * @throws IOException 69 | */ 70 | private void append(final String contents) throws IOException { 71 | Files.append(contents, resultFile, Charsets.UTF_8); 72 | Files.append(lineSeparator, resultFile, Charsets.UTF_8); 73 | } 74 | 75 | /** 76 | * 将文件解析成对象列表 77 | * 78 | * @param fileName 79 | * @return 80 | * @throws Exception 81 | */ 82 | private List parseFile(String fileName) throws Exception { 83 | List readLines = Files.readLines(new File(fileName), Charsets.UTF_8, new LineProcessor>() { 84 | private List result = Lists.newArrayList(); 85 | 86 | /** 87 | * 每一个请求响应解析结果所保存的对象 88 | */ 89 | private ReqRespEntity entity; 90 | 91 | /** 92 | * 标记解析请求响应的次数值,初始从1开始,每次碰到三个特殊的unicde字符开始的行自增1, 93 | * 这样可以根据奇数偶数来判断当前是在处理请求还是在处理响应的内容 94 | */ 95 | private int processReqResp = 1; 96 | 97 | /** 98 | * 标记当前处理的是从实体开始的第几行(每个实体以三个特殊的unicde字符开始) 99 | */ 100 | private int lineNumber = 0; 101 | 102 | /** 103 | * 标志是否在处理请求体,用于保存POST请求的参数信息 104 | * 根据HTTP协议描述: 105 | * 请求消息和响应消息都是由开始行,消息报头(可选),空行(只有CRLF的行),消息正文(可选)组成 106 | * 107 | */ 108 | private boolean isRequestBody = false; 109 | 110 | /** 111 | * 标志是否在处理响应体, 112 | * 根据HTTP协议描述: 113 | * 请求消息和响应消息都是由开始行,消息报头(可选),空行(只有CRLF的行),消息正文(可选)组成 114 | * 115 | */ 116 | private boolean isResponseBody = false; 117 | 118 | /** 119 | * 标记解析响应的Chunked编码的内容值,初始从0开始 120 | * 这样可以根据奇数偶数来判断当前是在处理Chunked头还是Chunked的内容 121 | */ 122 | private int processChunkedContent = 0; 123 | 124 | public boolean processLine(String line) throws IOException { 125 | if (line.startsWith("\uD83D\uDC35\uD83D\uDE48\uD83D\uDE49")) { 126 | processReqResp++;//自增1 127 | lineNumber = 0;//lineNumber值复位为0 128 | } else { 129 | lineNumber++; 130 | 131 | if ((processReqResp & 1) != 0) {//在处理请求 132 | if (lineNumber == 1) {//第一行,获取到标志ID 133 | entity = new ReqRespEntity(); 134 | entity.setId(Iterables.get(httpParseSplitter.split(line), 1));//第二项表示标志ID 135 | result.add(entity); 136 | isRequestBody = false; 137 | isResponseBody = false; 138 | processChunkedContent = 0; 139 | } else if (lineNumber == 2) {//第二行,请求URL 140 | entity.getRequestUrl().append(Iterables.get(httpParseSplitter.split(line), 1));//第二项表示请求URL 141 | } else if (line.equals("")) { 142 | isRequestBody = true; 143 | } else if (isRequestBody) { 144 | entity.getRequestUrl().append("?").append(line); 145 | } 146 | } else {//在处理响应 147 | if (line.equals("")) { 148 | isResponseBody = true; 149 | } else if (line.startsWith("Transfer-Encoding")) { 150 | entity.setResponseTransferEncoding(Iterables.get(httpParseSplitter.split(line), 1));//第二项表示Transfer-Encoding的内容 151 | } else if (line.startsWith("Content-Type")) { 152 | entity.setResponseContentType(Iterables.get(httpParseSplitter.split(line), 1));//第二项表示Content-Type的内容 153 | if (entity.getResponseContentType().startsWith("application/json")) { 154 | entity.setResponseJson(true);//表示响应内容为json格式 155 | } 156 | } else if (line.startsWith("Content-Length")) { 157 | entity.setResponseContentLength(Iterables.get(httpParseSplitter.split(line), 1));//第二项表示Content-Length的内容 158 | } else if (isResponseBody) { 159 | if (entity.getResponseTransferEncoding() != null) {//表示响应的内容是用Chunked编码 160 | processChunkedContent++; 161 | if ((processChunkedContent & 1) == 0) {//在处理Chunked内容 162 | entity.getResponseContent().append(line); 163 | } 164 | } 165 | if (entity.getResponseContentLength() != null) {//表示响应的内容是用正常编码 166 | entity.getResponseContent().append(line); 167 | } 168 | } 169 | } 170 | } 171 | 172 | 173 | return true; 174 | } 175 | 176 | public List getResult() { 177 | return result; 178 | } 179 | }); 180 | 181 | return readLines; 182 | } 183 | 184 | /** 185 | * 比较内容是否相同 186 | * 187 | * @param leftContent 左边的内容 188 | * @param rightContent 被比较的右边的内容 189 | * @param isJson 内容是否是json 190 | * @param jsonIgnorePath json内容中需要忽略比较的json路径 191 | * @return 192 | */ 193 | private boolean isContentSame(String leftContent, String rightContent, boolean isJson, String jsonIgnorePath) { 194 | boolean result; 195 | 196 | if (isJson && (jsonIgnorePath != null)) { 197 | Set ignorePathSet = new HashSet<>(); 198 | 199 | propertiesParseSplitter.split(jsonIgnorePath).forEach(ignorePath -> { 200 | ignorePathSet.add(ignorePath); 201 | }); 202 | 203 | JsonContentCompare jsonContentCompare = new JsonContentCompare(leftContent, rightContent, ignorePathSet); 204 | result = jsonContentCompare.compare(); 205 | } else { 206 | result = leftContent.equals(rightContent); 207 | } 208 | 209 | return result; 210 | } 211 | 212 | /** 213 | * 比较HTTP日志文件 214 | * 215 | * @throws Exception 216 | */ 217 | public void compare() throws Exception { 218 | List leftList = parseFile(leftFileName); 219 | append("left file 总行数:" + leftList.size()); 220 | 221 | List rightList = parseFile(rightFileName); 222 | append("right file 总行数:" + rightList.size()); 223 | 224 | Set urlSet = urlJsonIgnorePath.keySet(); 225 | 226 | for (int i = 0; i < leftList.size(); i++) { 227 | ReqRespEntity leftEntity = leftList.get(i); 228 | String leftUrl = leftEntity.getRequestUrl().toString(); 229 | 230 | ReqRespEntity rightEntity = null; 231 | 232 | for (int j = 0; j < rightList.size(); j++) { 233 | if (leftUrl.equals(rightList.get(j).getRequestUrl().toString())) { 234 | rightEntity = rightList.get(j); 235 | break; 236 | } 237 | } 238 | if (rightEntity == null) { 239 | continue; 240 | } 241 | 242 | String rightUrl = rightEntity.getRequestUrl().toString(); 243 | 244 | /** 245 | * 获取配置文件中与当前URL匹配的配置项 246 | */ 247 | String jsonIgnorePath = null; 248 | for (String urlRegular : urlSet) { 249 | if (leftUrl.matches(urlRegular)) { 250 | jsonIgnorePath = urlJsonIgnorePath.get(urlRegular); 251 | break; 252 | } 253 | } 254 | 255 | String leftResponseContent = leftEntity.getResponseContent().toString(); 256 | String rightResponseContent = rightEntity.getResponseContent().toString(); 257 | 258 | /** 259 | * 将响应报文比较后不同的响应内容记录到结果文件 260 | */ 261 | if (!isContentSame(leftResponseContent, rightResponseContent, leftEntity.isResponseJson(), jsonIgnorePath)) { 262 | append("==================================="); 263 | append(leftUrl); 264 | append(""); 265 | append(rightUrl); 266 | append(""); 267 | append("left 响应内容 :"); 268 | append(leftEntity.getResponseContent().toString()); 269 | append(""); 270 | append("right 响应内容 :"); 271 | append(rightEntity.getResponseContent().toString()); 272 | append("==================================="); 273 | append(""); 274 | append(""); 275 | append(""); 276 | } 277 | } 278 | } 279 | 280 | public static void main(String[] args) throws Exception { 281 | CompareHttpLog compareHttpLog = new CompareHttpLog("/Users/niwei/Downloads/gor-test-2016-07-22-19.log", 282 | "/Users/niwei/Downloads/gor-online-2016-07-22-19.log", 283 | "/Users/niwei/Downloads/compare-2016-07-22.log", 284 | "go/urlJsonIgnorePath.properties"); 285 | 286 | compareHttpLog.compare(); 287 | 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /src/go/JsonContentCompare.java: -------------------------------------------------------------------------------- 1 | package go; 2 | 3 | import org.json.JSONArray; 4 | import org.json.JSONObject; 5 | 6 | import java.util.*; 7 | 8 | /** 9 | * json内容比较工具类 10 | *

11 | * Created by niwei on 16/7/21. 12 | */ 13 | public class JsonContentCompare { 14 | private static final String PATH_HEAD = "JSONHEAD";//json路径头定义 15 | private static final String PATH_SPLITTER = "#";//json路径分隔符定义,选择正则表达式中不用的,避免解析正则表达式的时候处理麻烦 16 | 17 | private String json;//原始json字符串 18 | private String compareJson;//被比较的json字符串 19 | private List jsonPathList;//保存原始json的多有解析过的value的路径 20 | private Set ignorePahtSet;//比较路径黑名单,用于忽略某些路径值的比较 21 | 22 | public JsonContentCompare(String json, String compareJson, Set ignorePahtSet) { 23 | this.json = json; 24 | this.compareJson = compareJson; 25 | this.jsonPathList = new ArrayList(); 26 | if (ignorePahtSet == null) { 27 | ignorePahtSet = new HashSet<>(); 28 | } 29 | this.ignorePahtSet = ignorePahtSet; 30 | } 31 | 32 | public boolean compare() { 33 | boolean result = false; 34 | 35 | if (json == null || json.trim().equals("") || compareJson == null || compareJson.trim().equals("")) { 36 | return result; 37 | } 38 | 39 | JSONObject jsonObj = new JSONObject(json); 40 | JSONObject compareJsonObj = new JSONObject(compareJson); 41 | String jsonPath = PATH_HEAD; 42 | if ((jsonObj != JSONObject.NULL) && (compareJsonObj != JSONObject.NULL)) { 43 | result = compareObject(jsonObj, compareJsonObj, jsonPath); 44 | } 45 | 46 | return result; 47 | } 48 | 49 | /** 50 | * json的路径是否包含在ignorePahtSet中 51 | * 52 | * @param jsonPath 53 | * @return 54 | */ 55 | private boolean isInIgnorePathJudge(String jsonPath) { 56 | boolean result = false; 57 | for (String path : ignorePahtSet) { 58 | if (jsonPath.matches(path)) { 59 | result = true; 60 | break; 61 | } 62 | } 63 | return result; 64 | } 65 | 66 | /** 67 | * json对象比较 68 | * 69 | * @param jsonObj json对象 70 | * @param compareJsonObj 与json对象比较的json对象 71 | * @param jsonPath json对象在原始json中所处的路径 72 | * @return 比较结果 73 | */ 74 | private boolean compareObject(JSONObject jsonObj, JSONObject compareJsonObj, String jsonPath) { 75 | boolean result = true; 76 | 77 | Iterator jsonKeys = jsonObj.keys(); 78 | String parentPath = jsonPath; 79 | while (jsonKeys.hasNext()) { 80 | String jsonKey = jsonKeys.next(); 81 | jsonPath = parentPath + PATH_SPLITTER + jsonKey; 82 | if (isInIgnorePathJudge(jsonPath)) { 83 | //如果包含在ignorePahtSet中则忽略本次比较 84 | continue; 85 | } 86 | 87 | if (!compareJsonObj.has(jsonKey)) { 88 | //如果被比较的json中没有该key直接返回false 89 | result = false; 90 | break; 91 | } 92 | Object compareJsonValue = compareJsonObj.get(jsonKey); 93 | Object jsonValue = jsonObj.get(jsonKey); 94 | 95 | //json的value通用比较方法 96 | result = compareJsonValue(jsonValue, compareJsonValue, jsonPath); 97 | 98 | if (!result) { 99 | //result等于false,直接跳出循环,不再继续比较 100 | break; 101 | } 102 | } 103 | 104 | return result; 105 | } 106 | 107 | /** 108 | * json数组比较 109 | * 110 | * @param jsonArr json数组 111 | * @param compareJsonArr 与json数组比较的json数组 112 | * @param jsonPath json数组在原始json中所处的路径 113 | * @return 比较结果 114 | */ 115 | private boolean compareArray(JSONArray jsonArr, JSONArray compareJsonArr, String jsonPath) { 116 | boolean result = true; 117 | 118 | String parentPath = jsonPath; 119 | for (int i = 0; i < jsonArr.length(); i++) { 120 | jsonPath = parentPath + "[" + i + "]"; 121 | 122 | Object jsonValue = jsonArr.get(i); 123 | Object compareJsonValue = compareJsonArr.get(i); 124 | result = compareJsonValue(jsonValue, compareJsonValue, jsonPath); 125 | 126 | if (!result) { 127 | //result等于false,直接跳出循环,不再继续比较 128 | break; 129 | } 130 | } 131 | return result; 132 | } 133 | 134 | /** 135 | * 普通json值比较 136 | * 137 | * @param jsonValue json值 138 | * @param compareJsonValue 与json值比较的json值 139 | * @param jsonPath json值在原始json中所处的路径 140 | * @return 141 | */ 142 | private boolean comparePlain(Object jsonValue, Object compareJsonValue, String jsonPath) { 143 | boolean result = true; 144 | 145 | if (isInIgnorePathJudge(jsonPath)) { 146 | return result; 147 | } 148 | 149 | // 直接比较字符串内容 150 | if (jsonValue != null && !jsonValue.equals(compareJsonValue)) { 151 | result = false; 152 | } 153 | return result; 154 | } 155 | 156 | /** 157 | * 比较json的value,需要根据实际json类型执行不同的解析路径 158 | * 159 | * @param jsonValue 原始json解析后的value 160 | * @param compareJsonValue 原始被比较json解析后的value 161 | * @param jsonPath jsonValue在原始json中所处的路径 162 | * @return 163 | */ 164 | private boolean compareJsonValue(Object jsonValue, Object compareJsonValue, String jsonPath) { 165 | boolean result = true; 166 | 167 | if (isInIgnorePathJudge(jsonPath)) { 168 | return result; 169 | } 170 | 171 | if (jsonValue instanceof JSONArray) { 172 | if (!(compareJsonValue instanceof JSONArray)) { 173 | //如果两个json对象类型不一样,直接返回false 174 | result = false; 175 | } else { 176 | result = compareArray((JSONArray) jsonValue, (JSONArray) compareJsonValue, jsonPath); 177 | } 178 | } else if (jsonValue instanceof JSONObject) { 179 | if (!(compareJsonValue instanceof JSONObject)) { 180 | //如果两个json对象类型不一样,直接返回false 181 | result = false; 182 | } else { 183 | result = compareObject((JSONObject) jsonValue, (JSONObject) compareJsonValue, jsonPath); 184 | } 185 | } else { 186 | result = comparePlain(jsonValue, compareJsonValue, jsonPath); 187 | } 188 | this.jsonPathList.add(jsonPath); 189 | return result; 190 | } 191 | 192 | public List getJsonPathList() { 193 | return jsonPathList; 194 | } 195 | 196 | public static void main(String[] args) throws Exception { 197 | String jsonStr1 = "{\"result\":\"ok\",\"msg\":\"操作成功\",\"code\":200,\"data\":{\"LOCATION_THIRD\":{\"promotionId\":0,\"linkUrl\":\"\",\"promotionType\":null,\"showImgUrl\":\"http://static2.8dol.com/homeAds/TOP/618zq.jpg\",\"homeShowType\":\"INVITE\",\"typeName\":\"邀请好友\",\"timeDiff\":0,\"secondKillType\":null},\"LOCATION_FIRST\":{\"promotionId\":102,\"linkUrl\":\"http://t.cn/RqFwUhW\",\"promotionType\":\"SECOND_KILL\",\"showImgUrl\":\"http://static2.8dol.com/homeAds/TOP/622ms.jpg\",\"homeShowType\":\"WEB_SITE\",\"typeName\":\"秒杀\",\"timeDiff\":26632127,\"secondKillType\":\"END\"},\"LOCATION_SECOND\":{\"promotionId\":6,\"linkUrl\":\"\",\"promotionType\":\"FULL_DISCOUNT\",\"showImgUrl\":\"http://static2.8dol.com/homeAds/TOP/622tg.jpg\",\"homeShowType\":\"PROMOTION\",\"typeName\":\"团购批发\",\"timeDiff\":0,\"secondKillType\":null}},\"rescode\":200}"; 198 | 199 | String jsonStr2 = "{\"result\":\"ok\",\"msg\":\"操作成功\",\"code\":200,\"data\":{\"LOCATION_SECOND\":{\"promotionId\":102,\"linkUrl\":\"http://t.cn/RqFwUhW\",\"promotionType\":\"SECOND_KILL\",\"showImgUrl\":\"http://static2.8dol.com/homeAds/secondkill.jpg\",\"homeShowType\":\"PROMOTION\",\"typeName\":\"秒杀\",\"timeDiff\":26631850,\"secondKillType\":\"END\"},\"LOCATION_FIRST\":{\"promotionId\":0,\"linkUrl\":\"\",\"promotionType\":null,\"showImgUrl\":\"http://static2.8dol.com/homeAds/first.jpg\",\"homeShowType\":\"DAY\",\"typeName\":\"默认广告位1显示\",\"timeDiff\":0,\"secondKillType\":null},\"LOCATION_THIRD\":{\"promotionId\":0,\"linkUrl\":\"\",\"promotionType\":null,\"showImgUrl\":\"http://static2.8dol.com/homeAds/third.jpg\",\"homeShowType\":\"INVITE\",\"typeName\":\"默认广告位2显示\",\"timeDiff\":0,\"secondKillType\":null}},\"rescode\":200}"; 200 | 201 | Set blackPathSet = new HashSet<>(); 202 | blackPathSet.add(PATH_HEAD + PATH_SPLITTER + ".*LOCATION_SECOND.*"); 203 | blackPathSet.add(PATH_HEAD + PATH_SPLITTER + ".*LOCATION_THIRD.*"); 204 | blackPathSet.add(PATH_HEAD + PATH_SPLITTER + ".*LOCATION_FIRST.*"); 205 | 206 | JsonContentCompare jsonContentCompare = new JsonContentCompare(jsonStr1, jsonStr2, blackPathSet); 207 | 208 | System.out.println("对象比较结果:" + jsonContentCompare.compare()); 209 | 210 | System.out.println("对象比较路径:"); 211 | 212 | jsonContentCompare.getJsonPathList().forEach(path -> { 213 | System.out.println(path); 214 | }); 215 | 216 | /*String jsonStr1 = "{\"result\":\"ok\",\"msg\":\"登录成功\",\"code\":200,\"data\":{\"username\":\"18625150155\",\"mobile\":\"18625150155\",\"email\":\"\",\"status\":1,\"head_ico\":\"\",\"lock_reason\":\"\",\"open_id_app\":\"\",\"union_id\":\"\",\"verify_code\":\"Toalzwd-gppx-CUEL2WFYDLFPKXIZ54URGAR6IM-xo\",\"bind_mobile\":1,\"isBind8Dol\":1,\"user_id\":\"a4b55123100c8fcb733e7ded03465967b58017b3\",\"is_Vip\":false,\"vip_expire_time\":\"\"},\"rescode\":200,\"timestamp\":1469009437228}"; 217 | 218 | String jsonStr2 = "{\"result\":\"ok\",\"msg\":\"操作成功\",\"code\":200,\"data\":[],\"rescode\":200}"; 219 | 220 | Set blackPathSet = new HashSet<>(); 221 | blackPathSet.add(".*#msg"); 222 | blackPathSet.add(".*#data"); 223 | blackPathSet.add(".*#timestamp"); 224 | 225 | 226 | JsonContentCompare jsonContentCompare = new JsonContentCompare(jsonStr1, jsonStr2, blackPathSet); 227 | 228 | System.out.println("对象比较结果:" + jsonContentCompare.compare()); 229 | 230 | System.out.println("对象比较路径:"); 231 | 232 | jsonContentCompare.getJsonPathList().forEach(path -> { 233 | System.out.println(path); 234 | });*/ 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /src/go/ReqRespEntity.java: -------------------------------------------------------------------------------- 1 | package go; 2 | 3 | /** 4 | * Created by niwei on 16/7/20. 5 | */ 6 | public class ReqRespEntity { 7 | private String id; 8 | 9 | //请求URL,包含了GET请求URL和POST请求的URL及请求体 10 | private StringBuilder requestUrl = new StringBuilder(); 11 | 12 | private String responseContentType; 13 | private String responseTransferEncoding; 14 | private boolean isResponseJson = false; 15 | private String responseContentLength; 16 | private StringBuilder responseContent = new StringBuilder(); 17 | 18 | public String getId() { 19 | return id; 20 | } 21 | 22 | public void setId(String id) { 23 | this.id = id; 24 | } 25 | 26 | public StringBuilder getRequestUrl() { 27 | return requestUrl; 28 | } 29 | 30 | public void setRequestUrl(StringBuilder requestUrl) { 31 | this.requestUrl = requestUrl; 32 | } 33 | 34 | public String getResponseContentType() { 35 | return responseContentType; 36 | } 37 | 38 | public void setResponseContentType(String responseContentType) { 39 | this.responseContentType = responseContentType; 40 | } 41 | 42 | public String getResponseTransferEncoding() { 43 | return responseTransferEncoding; 44 | } 45 | 46 | public void setResponseTransferEncoding(String responseTransferEncoding) { 47 | this.responseTransferEncoding = responseTransferEncoding; 48 | } 49 | 50 | public boolean isResponseJson() { 51 | return isResponseJson; 52 | } 53 | 54 | public void setResponseJson(boolean responseJson) { 55 | isResponseJson = responseJson; 56 | } 57 | 58 | public String getResponseContentLength() { 59 | return responseContentLength; 60 | } 61 | 62 | public void setResponseContentLength(String responseContentLength) { 63 | this.responseContentLength = responseContentLength; 64 | } 65 | 66 | public StringBuilder getResponseContent() { 67 | return responseContent; 68 | } 69 | 70 | public void setResponseContent(StringBuilder responseContent) { 71 | this.responseContent = responseContent; 72 | } 73 | 74 | @Override 75 | public String toString() { 76 | return "ReqRespEntity{" + 77 | "id='" + id + '\'' + 78 | ", requestUrl=" + requestUrl + 79 | ", responseContentType='" + responseContentType + '\'' + 80 | ", responseTransferEncoding='" + responseTransferEncoding + '\'' + 81 | ", isResponseJson=" + isResponseJson + 82 | ", responseContentLength='" + responseContentLength + '\'' + 83 | ", responseContent=" + responseContent + 84 | '}'; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/go/middleware/Stdout.java: -------------------------------------------------------------------------------- 1 | package go.middleware; 2 | 3 | import javax.xml.bind.DatatypeConverter; 4 | import java.io.BufferedInputStream; 5 | import java.io.BufferedReader; 6 | import java.io.InputStreamReader; 7 | import java.util.HashSet; 8 | import java.util.Set; 9 | 10 | /** 11 | * Gor中间件Java版本,增强的功能有: 12 | * 13 | * 1.在请求体中注入参数GorRequestId,用于请求回放时的原始请求比对 14 | * 2.支持根据url配置过滤请求和响应的输出 15 | *

16 | * Created by niwei on 16/7/22. 17 | */ 18 | public class Stdout { 19 | private static final String SPLITTER_HEADER_BODY_SPLITTER = "\r\n\r\n"; 20 | private static final String SPLITTER_HEAD_FIRST_LINE = "\n"; 21 | private static final String SPLITTER_HEADER_ITEM = " "; 22 | /** 23 | * payload type, possible values: 1 - request, 2 - original response, 3 - replayed response 24 | */ 25 | private static final String PAYLOAD_TYPE_REQUEST = "1"; 26 | private static final String PAYLOAD_TYPE_ORIGINAL_RESPONSE = "2"; 27 | 28 | /** 29 | * 定义新增加的requestId参数名称 30 | */ 31 | private static String INJECT_TO_REQUEST_ENTITY_REQUEST_ID = "GorRequestId"; 32 | 33 | /** 34 | * 定义需要输出的请求和响应的requestId 35 | */ 36 | private static Set recordRequestIds = new HashSet<>(); 37 | 38 | /** 39 | * convert hex to string 40 | * 41 | * @param hexStr 42 | * @return 43 | * @throws Exception 44 | */ 45 | public static String hexDecode(String hexStr) throws Exception { 46 | byte[] decodedHex = DatatypeConverter.parseHexBinary(hexStr); 47 | String decodedString = new String(decodedHex, "UTF-8"); 48 | 49 | return decodedString; 50 | } 51 | 52 | /** 53 | * convert string to hex 54 | * 55 | * @param str 56 | * @return 57 | * @throws Exception 58 | */ 59 | public static String encodeHex(String str) throws Exception { 60 | if (str == null) { 61 | return null; 62 | } 63 | byte[] strBytes = str.getBytes(); 64 | String encodeString = DatatypeConverter.printHexBinary(strBytes); 65 | 66 | return encodeString; 67 | } 68 | 69 | private static String getRequestHeader(String key, String value) { 70 | StringBuilder result = new StringBuilder(SPLITTER_HEAD_FIRST_LINE); 71 | 72 | result.append(key).append(":").append(SPLITTER_HEADER_ITEM).append(value); 73 | 74 | return result.toString(); 75 | } 76 | 77 | /** 78 | * gor原始内容增强 79 | * 80 | * @param content 原始的gor工具输出的内容 81 | * @param allowUrlRegular 允许记录文件的url正则表达式 82 | * @return 增强后输出的内容 83 | */ 84 | public static String enhanceContent(String content, String allowUrlRegular) { 85 | if ((allowUrlRegular == null) || (allowUrlRegular.trim().equals(""))){ 86 | allowUrlRegular = "*"; 87 | } 88 | 89 | String result = content; 90 | 91 | /** 92 | * get first line content 93 | */ 94 | String[] lines = content.split(SPLITTER_HEAD_FIRST_LINE); 95 | if (lines == null || lines.length <= 1) { 96 | return result; 97 | } 98 | String firstLine = lines[0]; 99 | String secondLine = lines[1]; 100 | 101 | String[] firstLineItems = firstLine.split(SPLITTER_HEADER_ITEM); 102 | if (firstLineItems.length != 3) { 103 | return result; 104 | } else { 105 | String payloadType = firstLineItems[0]; 106 | String requestId = firstLineItems[1]; 107 | 108 | if (PAYLOAD_TYPE_REQUEST.equals(payloadType)) { 109 | String[] secondLineItems = secondLine.split(SPLITTER_HEADER_ITEM); 110 | String url = secondLineItems[1]; 111 | String uri = url; 112 | int urlIndex = url.indexOf("?"); 113 | if (urlIndex > 0) { 114 | uri = url.substring(0, urlIndex); 115 | } 116 | 117 | String requestIdPair = INJECT_TO_REQUEST_ENTITY_REQUEST_ID + "=" + requestId + "&"; 118 | result = content.replaceFirst(SPLITTER_HEADER_BODY_SPLITTER, SPLITTER_HEADER_BODY_SPLITTER + requestIdPair); 119 | 120 | boolean isMatch = false; 121 | String[] allowUrls = allowUrlRegular.split(","); 122 | for (String allowUrl : allowUrls) { 123 | if (uri.matches(allowUrl)){ 124 | recordRequestIds.add(requestId); 125 | isMatch = true; 126 | break; 127 | } 128 | } 129 | if(!isMatch){ 130 | //URL不能匹配上的则不输出到文件 131 | result = ""; 132 | } 133 | 134 | } else if (PAYLOAD_TYPE_ORIGINAL_RESPONSE.equals(payloadType)) { 135 | if (recordRequestIds.contains(requestId)) { 136 | recordRequestIds.remove(requestId); 137 | } else {//不再recordRequestIds记录中则不输出到文件 138 | result = ""; 139 | } 140 | } 141 | } 142 | 143 | return result; 144 | } 145 | 146 | /** 147 | * java go.GorEnhance 148 | * 149 | * @param args 150 | * @throws Exception 151 | */ 152 | public static void main(String[] args) throws Exception { 153 | 154 | String line; 155 | StringBuilder allowUrlRegular = new StringBuilder(); 156 | int bytesRead = 0; 157 | byte[] buffer = new byte[1024]; 158 | 159 | try (BufferedInputStream bufferedInput = new BufferedInputStream(Class.class.getClassLoader().getSystemResourceAsStream("go/middleware/allow-url.txt"))) { 160 | while ((bytesRead = bufferedInput.read(buffer)) != -1) { 161 | allowUrlRegular.append(new String(buffer, 0, bytesRead)); 162 | } 163 | } 164 | 165 | BufferedReader stdin = new BufferedReader(new InputStreamReader( 166 | System.in)); 167 | while ((line = stdin.readLine()) != null) { 168 | System.out.println(encodeHex(enhanceContent(hexDecode(line), allowUrlRegular.toString()))); 169 | } 170 | 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/go/middleware/allow-url.txt: -------------------------------------------------------------------------------- 1 | .*/order/confirmOrder.*,.*/order/generate.*,.*/order/customziationGenerate.* -------------------------------------------------------------------------------- /src/go/urlJsonIgnorePath.properties: -------------------------------------------------------------------------------- 1 | .*8dolLogin.*=.*#data,.*#msg,.*#timestamp 2 | .*generateToken.*=.*#data 3 | 4 | 5 | --------------------------------------------------------------------------------