Electron中,可以通过WebRequest监听webview中请求的各个阶段,并且获取或修改请求头和响应头。
但却没有提供获取请求内容的方法。那么如何获取请求内容呢?
首先,我们可以通过比较暴力的方法,就是在webRequest.onCompleted时,重新利用如http模块,对url再发送一次请求,从而获取请求内容。
但这个方法也有弊端,那就是,某些ajax请求,是不能重复的,尤其是POST请求。
比如一个点赞功能,第一次请求服务端会返回成功。第二次再请求,服务端就会报错了。
那么如何做才能直接获取到ajax请求的内容呢?
接下来,我教大家一种方法,就是
利用webview的preload功能,劫持原生XHRHttpRequest类,来实现在Electron中获取ajax请求内容
原理是,Electron的webview标签,有个preload属性,可以在页面加载之前,注入指定的js脚本,并且这个js脚本是一定集成了node环境的。
那么我们就可以在这个脚本中,将原生的XHRHttpRequest类替换掉。每当页面中通过XHRHttpRequest发送ajax请求是,我们就可以先拿到内容,再返回给页面了。废话不多说,直接上代码
代码基于ES6
const READY_STATE_CHANGE = 'readystatechange';
let gHandlerList = [],//截获请求的处理函数列表
gIsInited = false;//是否已经初始化
let T_RSC_HANDLERS = Symbol('readyStateChangeHandler');
let initProxy = function(){
if(gIsInited)return;
gIsInited = true;
//这里先缓存一份原生的XMLHttpRequest类
let winXMLHttpRequest = window.XMLHttpRequest;
//用于替换原生XMLHttpRequest的类,继承自XMLHttpRequest
let ProxyXHR = class extends winXMLHttpRequest{
constructor(){
super(...arguments);
//readystatechange
//数组中第0个为页面中调用xhr.onreadystatechange的回调函数
//其他的为页面中调用addEventListener('readystatechange')时的回调函数
this[T_RSC_HANDLERS] = [null];
//调用原生XMLHttpRequest的addEventListener,添加对readystatechange事件的监听
super.addEventListener(READY_STATE_CHANGE,async ()=>{
if(this.readyState == 4 && gHandlerList.length){//只有4的时候会回调proxyHandler
try{
//调用注册的handler
await gHandlerList.map(proxyHandler => proxyHandler.call(this,this));
}
catch(e){
//TODO 这里可以替换为其他的错误处理逻辑
console.error(e);
}
}
//调用页面中注册的回调函数,保证页面中逻辑正常
this[T_RSC_HANDLERS].forEach(handler => handler && handler.apply(this,arguments));
});
}
/**
* 重写addEventListener函数,对readystatechange事件做特殊处理
*/
addEventListener(type,handler){
if(type == READY_STATE_CHANGE){
this[T_RSC_HANDLERS].push(handler);
}
else{
return super.addEventListener(...arguments);
}
}
/**
* 重写removeEventListener函数,对readystatechange事件做特殊处理
*/
removeEventListener(type,handler){
if(type == READY_STATE_CHANGE){
this[T_RSC_HANDLERS] = this[T_RSC_HANDLERS].filter(i => i!== handler);
}
else{
return super.removeEventListener(...arguments);
}
}
/**
* 重写onreadystatechange属性的setter
*/
set onreadystatechange(val){
this[T_RSC_HANDLERS][0] = val;
}
/**
* 重写onreadystatechange属性的getter
*/
get onreadystatechange(){
return this[T_RSC_HANDLERS][0] || null;
}
}
//覆盖原生的XMLHttpRequest
window.XMLHttpRequest = ProxyXHR;
}
/**
* 增加一个handler
* 当xhr.readyState == 4时,回调handler,handler中,可以通过xhr.responseText获取请求返回内容
* @param {function} handler function(xhr){}
*/
let addHandler = function(handler){
initProxy();
gHandlerList.push(handler);
}
/**
* 移除指定的handler
* @param {function} handler 调用addHandler时添加的handler
*/
let removeHandler = function(handler){
gHandlerList = gHandlerList.filter(h => h!== handler);
}
module.exports.addHandler = addHandler;
module.exports.removeHandler = removeHandler;
剩下的业务代码就简单了,获取到xhr后,简单处理,通过ipcRenderer.sendToHost即可将xhr内容发送到BrowserWindow中
/**
* preload.js
*/
const xhrProxy = require('./xhr_proxy.js');
const {ipcRenderer} = require('electron');
xhrProxy.addHandler(function(xhr){
let data = {};
//TODO 具体业务代码
//通过ipcRenderer.sendToHost即可将xhr内容发送到BrowserWindow中
ipcRenderer.sendToHost('channel',data);
});