流量回放系统的设计与实现--流量回放模块

2615 篇文章 26 订阅
2195 篇文章 14 订阅

架构设计

流程回放功能数据流转示意图

图片

 实现效果

录制:

图片

回放:

图片

功能设计

流量回放模块主要包含的功能是:

  • 任务信息列表

  • 创建回放任务

  • 修改任务信息

  • 执行任务

  • 回放对比结果

  • 查看任务详情信息

  • 流量用例列表

  • 用例详情、编辑

问题与解决方案

1队列中很多流量,如何获取指定的部分流量?

解决方案:通过对mitmproxy的二次开发,我们增加了根据指定列表id的dump接口。前端在列表选择的流量id,直接调用暴露的api接口,遍历获取到的id,自定义过滤获取指定的流量文件,同时解析流量数据并入库保存。此时的流量文件保存路径也会入库,以便于后续回放任务指定流量文件。

2回放对比时,部分请求流量很大,对比起来很慢如何处理?

解决方案:通过全量对比返回数据的形式是不可取的,特别是流量数据大时,很容易造成前端界面的卡死。这里我们通过对返回数据建立hash值的方式,直接对比2次的hash值来获得回放的对比结果,大大提高数据对比模块的性能。

3回放时如何让新的流量与老的录制流量进行关联?

解决方案:此种基于mitmproxy自身流量文件的回放方式,回放时的request流量序列id是不变的,由于在生成流量用例时已经将老的流量返回值入库保存,后续回放时通过中间插件解析并根据id也对新的response流量进行保存,通过唯一的id进行了关联。

4流量回放时如何自定义去修改请求?

解决方案:这个功能平台目前没有开发实现,但是前期已经调研尝试过,可以过开发中间件脚本对流量进行自定义的编辑处理。

核心代码设计

前段对比模块

这里采用的是 react-diff-viewer 库,组件化剥离了数据对比模块。

// DiffView.js
import React, { Component } from 'react';
import ReactDiffViewer, { DiffMethod } from 'react-diff-viewer';

import styles from './index.less';

export default class Page extends Component {
  render() {
    const { leftTitle, rightTitle, srcResponse, dstResponse } = this.props;
    return (
      <div className={styles.diffContainer}>
        <ReactDiffViewer
          leftTitle={leftTitle}
          rightTitle={rightTitle}
          oldValue={JSON.stringify(srcResponse, null, 2)}
          newValue={JSON.stringify(dstResponse, null, 2)}
          splitView
          // showDiffOnly={false}
          compareMethod={DiffMethod.JSON}
          // renderContent={this.highlightSyntax}
        />
      </div>
    )
  }
}

mitmproxy中的自定义dump接口

这个实际是在mitmweb模式下才会提供的一个接口,通过忽略跨域,暴露出来给前端调用。

# mitmproxy/tools/web/app.py
class FlowDumpByIdHandler(RequestHandler):
    def get(self):
        self.set_header("Content-Disposition", "attachment; filename=flows")
        self.set_header("Content-Type", "application/octet-stream")

        save_ids = self.json

        bio = BytesIO()
        fw = io.FlowWriter(bio)
        for f in self.view:
            if save_ids['saveIds']:
                if f.id in save_ids['saveIds']:
                    fw.add(f)

        self.write(bio.getvalue())
        bio.close()

获取流量response的中间件

核心的是返回值解析部分,这里也可以不做解析就用原生的json,但是最后入库的数据量会很大,影响后续的界面展示性能,可以看到目前主要是解析了xml和json数据,应该能覆盖常见的类型了,对于后续遇到的其它形式的返回值可以自定义扩展解析。

# getReplayResponse.py
import mitmproxy.http
from bs4 import BeautifulSoup
from mitmproxy import ctx
import logging, json, typing, requests, hashlib

PARSE_ERROR = object()

def parse_json(s: bytes) -> typing.Any:
    try:
        return json.loads(s.decode('utf-8'))
    except ValueError:
        return PARSE_ERROR

class MappingAddonConfig:
    HTML_PARSER = "html.parser"

class GetReplayResponse:
    def __init__(self):
        self.serverDomain = "{appHost}"
        self.taskId = "{taskId}"
        self.logger = logging.getLogger(self.__class__.__name__)

    def uploadToServer(self, requestId, responseData, replayRespHash):
        url = self.serverDomain + '/api/replayManage/getResponseFromProxy'
        data = {{
            'taskId': self.taskId,
            'requestId': requestId,
            'responseData': responseData,
            'replayRespHash': replayRespHash,
        }}
        headers = {{'Content-Type': 'application/json'}}
        res = requests.post(url, data=json.dumps(data), headers=headers)
        resp = res.json()
        if resp['code'] != 0:
            ctx.log.info("upload [ %s ] flow failed " % requestId)

    def response(self, flow: mitmproxy.http.HTTPFlow) -> None:
        """If a response is received, check if we should replace some content. """
        try:
            requestId = flow.id
            res = flow.response
            if res is not None:
                encoding = res.headers.get("content-encoding", "utf-8")
                content_type = res.headers.get("content-type", "text/html")
                replayRespHash = hashlib.sha256(res.raw_content).hexdigest()
                responseData = ''
                if "text/html" in content_type and encoding == "utf-8":
                    content = BeautifulSoup(res.content, MappingAddonConfig.HTML_PARSER)
                    responseData = content.encode(encoding)
                elif "json" in content_type:
                    data = parse_json(res.content)
                    if data is not PARSE_ERROR:
                        responseData = data
                    else:
                        self.logger.warning(
                            f"PARSE_ERROR content type '{{content_type}}'")
                else:
                    self.logger.warning(f"Unsupported content type '{{content_type}}' or content encoding '{{encoding}}'")
                self.uploadToServer(requestId, responseData, replayRespHash)
        except KeyError:
            pass


addons = [
    GetReplayResponse()
]

本系统采用前后端分离架构,本文仅就 流量回放 模块的设计与实现,做分析讲解,其它模块将在后续文章中拆分讲述。

最后:下方这份完整的软件测试视频教程已经整理上传完成,需要的朋友们可以自行领取【保证100%免费】

软件测试面试文档

我们学习必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有字节大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Dubbo实现流量录制与的原理是利用Dubbo提供的拦截器机制,在调用前和调用后对请求和响应进行拦截和处理,将请求和响应的内容保存到磁盘中,然后在时读取磁盘中保存的请求和响应内容,再次发送给服务提供者,达到重请求的目的。 具体来说,Dubbo实现流量录制和的步骤如下: 1.在Dubbo服务提供者和消费者中配置流量拦截器。Dubbo提供了Filter机制,可以通过实现Filter接口来实现拦截器,拦截器可以在调用前后对请求和响应进行处理。 2.在拦截器中将请求和响应内容保存到磁盘中。可以使用类似于录制日志的方式将请求和响应内容以特定的格式保存到文件中,例如JSON格式或者二进制格式。 3.在Dubbo服务消费者中配置流量拦截器。在拦截器中读取磁盘中保存的请求和响应内容,并重新发送给服务提供者。 4.在服务消费者中配置Dubbo的路由规则,将请求路由到拦截器。 5.启动流量服务,监听指定的端口,等待服务消费者发送请求。 6.服务消费者发送请求到拦截器,拦截器读取磁盘中的请求和响应内容,并将请求发送给服务提供者。 7.服务提供者接收到请求并处理,将响应发送给拦截器。 8.拦截器将服务提供者的响应返给服务消费者,完成请求的。 通过以上步骤,Dubbo实现流量录制和的功能。在实际应用中,可以通过流量来验证服务的正确性和稳定性,提高服务的可靠性和安全性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值