1、goreplay流量回放

  • 目的

在实际项目中,会有大量的回归测试工作,通常会使用自动化代码的手段来实现回归,但是对于一个庞大的系统来说,通过自动化脚本的方式来实现回归测试,又显得很费时费力。并且如果有定期将线上数据同步到测试环境的需求的话,流量回放就显得很必要了。当然,流量回放并不能替代自动化,对于存在上下文关联的接口,流量回放的处理上就显得很复杂。

  • 流量回放

其实流量回放主要是对某个端口的流量进行转发和存储,这边用的是goreplay,mitmproxy也可以做到流量的抓取和回放。

流量回放有三种使用方式:

  • 服务->to->服务,即端到端
  • 服务->to->文件,即流量存储
  • 文件->to->服务,即流量文件回放到服务
  • 常用配置示例
  • service_to_file.yml
bin:
  - "/opt/crawl/project/flow_replay/gor"
input:
  - "--input-raw :18080" 
  - "--input-raw-track-response"
  - "--prettify-http"
middle:
  - 
output:
  - "--output-file ./data/requests-%Y-%m-%d-%H.gor"
  - "--output-file-size-limit 100kb"
  - "--output-file-queue-limit 60"
  • file_to_service.yml
bin:
  - "/opt/crawl/project/flow_replay/gor"
input:
  - "--input-file ./data/'requests-2024-03-18-14_7.gor'"
middle:
  - "--middleware /opt/crawl/project/flow_replay/middle/main.js"
output:
  - "--output-http='http://localhost:18080'"
  - "--output-http-track-response"
  - "--prettify-http"
  • 释义

gorplay的配置主要分为三个部分,分别是input、output和middleware

  • input:定义需要捕获请求的一些必要条件
# 定义监听端口
--input-raw :18080 
# 定义是否要捕获请求响应,只有添加了这个配置,回放时才会触发response事件
--input-raw-track-response
# 定义是否要美化响应,这个配置是作者修复bug新加的配置,必须要有,
# 如果没有会造成乱码
--prettify-http
# 定义存储请求的文件位置
# 可以使用*模糊匹配,批量回放相关文件,如requests-2024-03-18-*.gor
--input-file ./data/'requests-2024-03-18-14_7.gor'
# 定义缓冲区大小,单位为b
--input-raw-buffer-size 10485760

注:不论是从端口读取请求或者从文件读取请求,则必然会触发request事件

  • output:定义输出请求的一些必要参数
# 定义存储请求文件的位置、格式及命名
--output-file ./data/requests-%Y-%m-%d-%H.gor
# 追踪流量回放响应,只有存在这个配置,回放成功后才会触发replay事件
--output-http-track-response
# 定义存储请求文件的最大size
--output-file-size-limit 100kb
# 定义单个存储文件中存储请求的数量
--output-file-queue-limit 20
# 同input
--prettify-http
# 定义请求丢弃比例,分别为绝对值和百分比
# 每秒请求超过绝对值的数字,则丢弃
# 只回放百分比的请求数量提升速度
--output-http='http://localhost:18080|10'
--output-http='http://localhost:18080|10%'
  • 中间件
  • 中间件流程示意图

  • 中间件常用Api
init - 初始化
httpPath - 获取请求URL,不包含请求的域名: gor.httpPath(req.http)
httpMethod - 获取请求方法:gor.httpMethod(req.http).
setHttpPath - 更新URL: req.http = gor.setHttpPath(req.http, newPath)
httpPathParam - 获取URL上的参数: gor.httpPathParam(req.http, queryParam)
setHttpPathParam - 设置URL参数: req.http = gor.setHttpPathParam(req.http, queryParam, value)
httpStatus - 响应状态码:gor.httpStatus(rep.http)
httpHeaders - 获取所有请求头: gor.httpHeaders(req.http)
httpHeader - 获取指定请求头: gor.httpHeader(req.http, "Content-Length")
setHttpHeader - 设置请求头, returns modified payload: req.http = gor.setHttpHeader(req.http, "X-Replayed", "1")
httpBody - 获取请求体: gor.httpBody(req.http)
setHttpBody - 设置请求体,需要注意Content-Length的大小: req.http = gor.setHttpBody(req.http, Buffer.from('hello!')).
httpBodyParam - 获取post请求param: gor.httpBodyParam(req.http, param)
setHttpBodyParam - 设置post请求的param: req.http = gor.setHttpBodyParam(req.http, param, value)
httpCookie - 获取cookie: gor.httpCookie(req.http, "SESSSION_ID")
setHttpCookie - 设置cookie, returns modified payload: req.http = gor.setHttpCookie(req.http, "iam", "cuckoo")
deleteHttpCookie - 删除cookie, returns modified payload: req.http = gor.deleteHttpCookie(req.http, "iam")
  • 事件

事件只存在于中间件中,goreplay接收到某个端口的请求后,会把request和response压缩成gzip,通过data.http字段转发给中间件,这样会触发request事件和response事件,当回放完成后,会触发replay事件

  • 中间件代码示例
#!/usr/bin/node
const gor = require("goreplay_middleware");
const NodeRSA = require('node-rsa');
const axios = require('axios')
const {user, baseUrl, publicKey} = require('./defalut')
const https = require('https');
const jsonpath = require('jsonpath');

/**
 * @description 对公钥进行加密
 * @param publicKey
 * @param {string} data 需要加密的数据
 * @returns {string} 加密后的数据
 */
function encrypt(publicKey, data) {
    let buffer = Buffer.from(data);
    let key = new NodeRSA(publicKey);
    key.setOptions({encryptionScheme: 'pkcs1'});
    return key.encrypt(buffer, 'base64', 'base64');
}


/**
 *
 * @param str
 * @param color:string, red, green, yellow, blue, and white if the color is the other value
 * @return {string}
 */
function addStringColor(str, color) {
    switch (color) {
        case "red":
            color = "31";
            break;
        case "green":
            color = "32";
            break;
        case "yellow":
            color = "33";
            break;
        case "blue":
            color = "34";
            break;
        case "pink":
            color = "35";
            break;
        default:
            color = "37";
            break;
    }
    return "\x1b[" + color + "m" + str + "\x1b[0m";
}


/**
 * @description send login request and extract sessionId
 * @param baseUrl{string} request url
 * @returns {string} return sessionId
 */

async function getCookie(baseUrl) {
    const data = {
        userName: user["username"],
        password: encrypt(publicKey, user["password"])
    };
    const url = baseUrl.concat('api/auth/doLogin');
    try {
        const response = await axios.post(url, data, {
            httpsAgent: new https.Agent({rejectUnauthorized: false}),
            headers: {'Content-Type': 'application/json; charset=utf-8'}
        });
        return response.headers['set-cookie'][0].match('(?<=JSESSIONID=)[a-f0-9-]+')[0];
    } catch (error) {
        console.error(error);
    }
}

(async () => {
    let cookie = await getCookie(baseUrl);
    gor.init();
    gor.on('request', function (request) {
        //request.http = gor.deleteHttpCookie(request.http, 'JSESSIONID')
        request.http = gor.setHttpCookie(request.http, 'JSESSIONID', cookie)
        gor.on("replay", request.ID, function (replay) {
            try {
                let obj = JSON.parse(gor.httpBody(replay.http).toString('utf8'))
                if (jsonpath.query(obj, '$..code')[0] === '0000') {
                    console.error(addStringColor(`√\t${gor.httpMethod(request.http)}\t${gor.httpPath(request.http)}`, 'green'))
                } else {
                    console.error(addStringColor(`x\t${gor.httpMethod(request.http)}\t${gor.httpPath(request.http)}\n ${gor.httpBody(replay.http)} `, 'red'))
                }
            } catch (e) {
                console.error(addStringColor(`\t${gor.httpMethod(request.http)}\t${gor.httpPath(request.http)}\n ${e}`, 'red'))
            }
            return replay;
        })
        return request
    })
})()
  • 效果展示

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/480420.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java代码基础算法练习-递归求数-2024.03.22

任务描述&#xff1a; 利用递归函数调用方式&#xff0c;将所输入的5个字符&#xff0c;以相反顺序打印出来。 任务要求&#xff1a; 代码示例&#xff1a; package march0317_0331;import java.util.Scanner;/*** m240322类&#xff0c;提供了一个反转输入字符串前5个字符的…

linux之sed编辑器指令练习

目录 一、sed编辑器 二、sed使用案例 1.1 s命令&#xff08;substitute替换&#xff09; 一、sed编辑器 sed编辑器比交互式编辑器快的多&#xff0c;可以简化数据处理任务,sed编辑器并不会修改文件&#xff0c;只会将修改后的数据&#xff0c;输出。 二、sed使用案例 首先…

ts js vue 验证文件 MD5 值 spark-md5

ts js vue 验证文件 MD5 值 spark-md5 如何在前端中验证要上传的文件的 md5 值 一、安装 spark-md5 插件 需要用到 spark-md5 这个插件 官方 github&#xff1a;https://github.com/satazor/js-spark-md5/tree/master yarn add spark-md5 // 或 npm i spark-md5使用的时候引…

JavaWeb的MVC设计模式

JavaWeb的MVC设计模式学习笔记 JSP Model1 在JSP Model1架构中&#xff0c;JSP页面既充当了视图&#xff08;View&#xff09;的角色&#xff0c;又包含了处理业务逻辑和数据处理的代码&#xff0c;承担了Controller和Model的责任。这种架构简单直接&#xff0c;适用于小型项…

使用阿里CICD流水线打包Vue项目到阿里的docker镜像私仓,并自动部署到服务器启动服务

文章目录 使用阿里CICD流水线打包Vue项目到阿里的docker镜像私仓&#xff0c;并自动部署到服务器启动服务1、功能实现原理大家可以看我之前的两篇文章2、打包vue项目和打包咱们的Java项目过程差不多相同&#xff0c;大家可以看着上面的Java打包过程进行实验&#xff0c;下面是v…

MySQL数据库:索引

一、索引&#xff1a; 1. 索引的概念&#xff1a; 索引是一个排序的列表&#xff0c;在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址。 使用索引后可以不用扫描全表来定位某行的数据&#xff0c;而是先通过索引表找到该行数据对应的物理地址然后访问相应的数据…

框架结构静力分析matlab有限元编程【Matlab源码+PPT讲义】| 梁单元 | 弯矩图 |坐标变换

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

第1篇:Mysql数据库表结构导出字段到Excel(一个sheet中)

package com.xx.util;import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.sql.*; import java.io.*;public class DatabaseToExcel {public static void main(String[] args) throws Exception {// 数据库连接配置String u…

RuoYi 自定义字典列表页面编码翻译

“字典数据”单独维护&#xff0c;而不是使用系统自带的字典表&#xff0c;应该如何使用这样的字典信息呢&#xff1f; 系统字典的使用&#xff0c;请参考&#xff1a; 《RuoYi列表页面字典翻译的实现》 https://blog.csdn.net/lxyoucan/article/details/136877238 需求说明…

【C++】1597. 买文具

问题&#xff1a;1597. 买文具 类型&#xff1a;基本运算、整数运算 题目描述&#xff1a; 花花去文具店买了 1 支笔和 1块橡皮&#xff0c;已知笔 x 元/ 支&#xff0c;橡皮 y元 / 块&#xff0c;花花付给了老板 n 元&#xff0c;请问老板应该找给花花多少钱&#xff1f; 输…

数字孪生底层技术框架

数字孪生是一种将现实世界中的物理实体、过程或系统数字化并映射到计算机模型中的方法。它在数学建模与仿真方面具有重要作用&#xff0c;为了实现数字孪生&#xff0c;以下是一些底层技术框架和方法&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业…

现代游戏引擎架构

一、并行编程 1.1 为什么需要并行编程 游戏的渲染计算对算力要求很高&#xff0c;所以我们需要把操作系统的资源利用到极致。 但是摩尔定律已经不在适用了&#xff0c;硬件的发展目前已经达到瓶颈。所以我们需要通过数量来提高计算效率。 1.2 并行编程基础 进程与线程&#…

关于在vue中有时候表格的位置不对是怎么个情况

今天在写代码的时候多了一个<div>标签&#xff0c;导致表格的位置大小不对 <template><div><tr><td><input type"checkbox" checked"true" /></td><td>xxxxx</td><td><button class"…

virtualBox虚拟机的Ubuntu系统下vscode 的标题栏无法显示,打开文件对话框显示不正常。

遇到问题&#xff1a;vscode 的标题栏无法显示&#xff0c;打开文件对话框显示不正常。打开文件对话框显示闪烁无法打开文件。如下图所示 解决办法&#xff1a; &#xff08;1&#xff09;Ctrl Shift P 输入 Preferences: Configure Runtime Arguments 搜“"disable-h…

AI智能分析网关V4在非煤矿山安全生产视频智能监管场景中的应用

近年来&#xff0c;全国非煤矿山&#xff08;&#xff08;含金属非金属矿山、尾矿库&#xff0c;以及矿泉水等其他矿山&#xff09;安全生产工作取得明显成效&#xff0c;但安全基础仍然薄弱&#xff0c;事故总量仍然较大&#xff0c;重特大事故尚未得到根本遏制&#xff0c;安…

基于ArcGIS的2015-2020辽宁省土地利用变化分析

数据准备 栅格转面 运行ArcToolbox&#xff0c;打开【转换工具】&#xff0c;选择【从栅格转出】里面的【栅格转面工具】&#xff0c;调出面板进行参数设置。输入栅格选择裁剪的2015年中国土地利用遥感监测数据&#xff08;…

JAVA 8 新特性 Lamdba表达式(二)

一、Lamdba的语法 (参数类型1 参数1,参数类型2 参数2,…) -> { 方法体 }; Lambda表达式就是一个匿名函数&#xff0c;不关注方法名&#xff0c;只关注参数和方法体。 Lambda表达式组成三要素&#xff1a;括号&#xff08;&#xff09;&#xff0c;箭头->&#xff0c;代码…

递归的个人总结

递归函数&#xff08;递去、回归&#xff09;是函数不断的调用自己&#xff1b; 可以按照如下来理解&#xff1a;func1中调用func2&#xff0c;func2中调用func3; func3函数返回了&#xff0c;继续执行func2中的语句&#xff1b;func2执行完了&#xff0c;继续执行func1之后的…

MySQL 中的索引

MySQL 中的索引 一、索引的创建和删除1.主键会自动添加索引2.unique 约束的字段自动添加索引3.给指定的字段添加索引4.删除指定索引5.查询表上的索引 二、索引的分类三、MySQL索引采用了B树数据结构1.B树的经典面试题 四、其他索引及相关调优1.Hash索引2.聚集索引和非聚集索引3…

专业130+总分410+西南交通大学924信号与系统考研经验西南交大电子信息通信工程,真题,大纲,参考书。

初试分数出来&#xff0c;专业课924信号与系统130&#xff0c;总分410&#xff0c;整体上发挥正常&#xff0c;但是还有遗憾&#xff0c;其实自己可以做的更好&#xff0c;总结一下经验&#xff0c;希望对大家有所帮助。专业课&#xff1a;&#xff08;130&#xff09; 西南交…