Yapi插件 cross-request的理解以及修改

3,687 阅读4分钟

最新又重新看了一遍cross-requset的代码,因为毕竟平台做了二次开发,cross-request 在接口运行上起到了至关重要的作用,所以还是需要了解它的作用,也方便后续的排查问题以及解决。

原理

重新花时间缕了下cross-request的整个流程 大体如下:

未命名文件 (1).png

我们需要解决几个疑惑

1. 为什么需要一个index.js进行脚本注入,暴露出一个crossRequest的方法出来。

如果对chrome插件有一定了解的话一定知道, content-script 与实际的页面只是共享dom内容,而不能够共享js的变量以及方法的。所以这里就有一个比较麻烦的问题了,真正的页面点击运行的时候,如何才能够将具体的请求的数据给到content-script呢? 所以就需要用到inject-script了。 注入的脚本能够做到共享js变量的方式来解决这个事情。

2. 解决完变量共享的问题, 但是问题又来了。怎么将数据给到content-script呢。

因为我们第一个问题说过了,content-script 与实际的页面只是共享dom内容,所以传递数据就是通过修改dom的数据,来进行数据的传递了。所以上图的流程里面id为y-request的div就是起着这样子一个作用。

3. background.js的作用又是做什么呢?为什么不能通过content-script直接进行http的请求

这里就涉及到了chrome 浏览器安全的限制了。在chrome 72的版本以后就限制了不允许content-script进行跨域的请求。而我们的接口运行肯定是需要进行跨域的操作的。所以只能够将所有的信息再次传递给到background由它来完成我们的接口执行,最后再把执行的结果传递回来。

修改

从上面的流程图我们会发现一个问题,index.js 在收到接口请求发给content-script后就会不停的轮询去读取dom的变化来确定是否已经完成了接口请求,而response.js(content-script)同样, 它更是启动后就在循环的读取dom里面一个status的状态值来判断是否需要获取接口数据进行请求。

这种方式的实现还是有点麻烦了。其实应该可以通过通知,订阅类似于这种模式来解决这个问题。所以我们下来可以考虑改造下cross-request

所以这里我们需要先普及下一个内容 关于

element.dispatchEvent()

对于标准浏览器,其提供了可供元素触发自定义事件的方法:element.dispatchEvent().。 不过,在使用该方法之前,我们还需要做其他两件事,即创建和初始化。

document.createEvent()
event.initEvent()
element.dispatchEvent()
举个例子:

var dom = document.querySelector('#id')
document.addEventListener('alert', function (event) {
  console.log(event)
}, false);
 
// 创建
var evt = document.createEvent("HTMLEvents");
// 初始化
evt.initEvent("alert", false, false);
 
// 触发, 即弹出文字
dom.dispatchEvent(evt);

1. createEvent()

createEvent()方法返回新创建的Event对象,支持一个参数,表示事件类型,具体见下表:

image

2. initEvent()

initEvent()方法用于初始化通过DocumentEvent接口创建的Event的值。 支持三个参数:initEvent(eventName, canBubble, preventDefault)

分别表示:

  • 事件名称
  • 是否可以冒泡
  • 是否阻止事件的默认操作

3. dispatchEvent()

dispatchEvent()就是触发执行了,dom.dispatchEvent(eventObject)参数eventObject表示事件对象,是createEvent()方法返回的创建的Event对象。

所以以下就是具体的代码修改的内容

index.js

 var customEvent = document.createEvent('Event');
    customEvent.initEvent('myCustomEvent', true, true);
    function fireCustomEvent(data) {
        hiddenDiv = document.getElementById(container);
        hiddenDiv.innerText = data
        hiddenDiv.dispatchEvent(customEvent);
    }

    function run(req) {
        if (!req) return;
        if (typeof req === 'string') req = { url: req }
        var newId = getid();
        data = {
            id: newId,
            res: null,
            req: req
        }
        data = encode(data);
        yRequestMap[newId] = {
            id: newId,
            status: INITSTATUS,
            success: function (res, header, data) {
                if (typeof req.success === 'function') {
                    req.success(res, header, data);
                }
            },
            error: function (error, header, data) {
                if (typeof req.error === 'function') {
                    req.error(error, header, data)
                }
            }
        }
        fireCustomEvent(data);
    }


    yRequestDom.addEventListener('reponseEvent', function () {
        var text = yRequestDom.innerText;
        if (text) {
            var data = decode(yRequestDom.innerText);
            var id = data.id;
            var res = data.res;
            if (res.status === 200) {
                yRequestMap[id].success(res.body, res.header, data);
            } else {
                yRequestMap[id].error(res.statusText, res.header, data);
            }
        } else {
        }
    });

    win.crossRequest = run;
    if (typeof define == 'function' && define.amd) {
        define('crossRequest', [], function () {
            return run;
        });
    }

resopnse.js

var customEvent = document.createEvent('Event');
customEvent.initEvent('reponseEvent', true, true);

function responseCallback(res, dom, data) {
    var id = data.id;
    var headers = handleHeader(res.headers);
    data.runTime = new Date().getTime() - data.runTime;
    data.res = {
        id: id,
        status: res.status,
        statusText: res.statusText,
        header: headers,
        body: res.body
    }
    dom.innerText = encode(data);
    dom.dispatchEvent(customEvent);
    
}


function sendAjaxByBack(id, req, successFn, errorFn) {
    successFns[id] = successFn;
    errorFns[id] = errorFn;
    connect.postMessage({
        id: id,
        req: req
    });
}

connect.onMessage.addListener(function (msg) {
    var id = msg.id;
    var res = msg.res;
    res.status === 200 ?
        successFns[id](res) :
        errorFns[id](res);
    delete successFns[id];
    delete errorFns[id];
});

//因注入 index.js ,需要等到 indexScript 初始化完成后执行
var findDom = setInterval(function () {
    try {
        yRequestDom = document.getElementById(container);
        if (yRequestDom) {
            clearInterval(findDom)
            yRequestDom.addEventListener('myCustomEvent', function () {
                var data = yRequestDom.innerText;
                console.log('收到自定义事件消息:', decode(data));
                data = decode(data);
                var req = data.req;
                req.url = req.url || '';
                data.runTime = new Date().getTime();

                sendAjaxByBack(data.id, req, function (res) {
                    responseCallback(res, yRequestDom, data);
                }, function (err) {
                    responseCallback(err, yRequestDom, data);
                })
            });
        }

    } catch (e) {
        clearInterval(findDom)
        console.error(e)
    }
}, 100)

以上就是修改的内容,只是把定时任务给去除 替换成了事件监听的方式来完成这个工作了。 具体代码见: cross-request