/*
Ajax-hook是一种比较实用的技术,它可以自定义XMLHttpRequest中的方法和属性,
覆盖全局的XMLHttpRequest。当网站在使用Ajax请求动态渲染时,开发者可以对其中的
请求参数和响应数据进行任意修改和展示,有些类似前面讲过的本地覆盖,区别是这里
会去调用原本的XMLHttpRequest。
*/
//2.4.1 Ajax-hook源码分析
!function (ob){
ob.hookAjax=function(funs){
window._ahrealxhr=window._ahrealxhr||XMLHttpRequest;
XMLHttpRequest=function(){
/*
查看Ajax-hook的核心代码,发现它会将浏览器window对象传入,并先获取全局的真实XMLHttpRequest,
同时对XMLHttpRequest对象中的API接口进行遍历,其中的方法和属性会采取不同的策略进行处理。
如果遍历到的API接口类型是function,就会用hookfun方法处理,
属性会用Object.defineProperty方法进行定义,代码如下:
*/
this.xhr=new window._ahrealxhr;
for (var attr in this.xhr) {//遍历XHR接口(可枚举)
var type="";
try{
type=typeof this.xhr[attr]//获取接口类型
}catch(e){}
if (type==="function"){ //方法
this[attr]=hookfun(attr);
}else{ //属性
Object.defineProperty(this,attr,{
//get:在获取定义的属性时,会调用这里定义的函数。它不传入任何参数,但是会传入this对象。
//该函数的返回值会被用作属性的值。
get:getFactory(attr),
//set:在定义的属性被修改时,会调用这里定义的函数。该方法会接受被定义的新值作为参数,
//同时也会传入this对象。
set:setFactory(attr)
})
}
}
};
/*
Ajax-hook获取属性时调用getFactory函数,返回值使用了三元运算符。this.hasOwnProperty方法会返回一个布尔值,
用来表明this对象自身属性中是否具有指定的属性。
*/
function getFactory(attr){
return function(){
//判断this对象自身属性中是否具有指定的属性
return this.hasOwnProperty(attr+"_")?this[attr+"_"]:this.xhr[attr];
//这里通常返回以on开头的属性对应的,用户自定义的方法调用
}
}
/*
Ajax-hook运行周期内,修改属性会调用setFactory函数,它会接受被定义的新值作为参数,这里用f来指代。
如果属性以on开头,就会在开发者自定义的func中进行匹配并调用,开发者自定义的水星返回为true表示进行阻断,
自定义的函数执行完毕后不会再执行XHR原生函数,反之会继续执行XHR原先的回调。
*/
function setFactory(attr){
return function(f){
var xhr=this.xhr;
var that=this;
if (attr.indexOf("on")!=0){
//Ajax-hook在遍历的属性后加上了下划线,在setFactory中对不以on开头的属性会进行下划线添加的新映射值
this[attr+"_"]=f;
return;
}
if (funs[attr]){
xhr[attr]=function(){
funs[attr](that)||f.apply(xhr,arguments);
}
}else{
xhr[attr]=f;
}
}
}
//在对XMLHttpRequest中原生的方法进行处理时,会使用如下hookfun方法。
/*
它首先会把当前遍历来的方法中的参数赋值到args中,之后会获取开发者自定义的XMLHttpRequest方法,
如果能够获取到开发者自定义的方法,就会对当前方法进行覆盖,并将参数传入调用:如果没有获取到,
说明开发者没有重写这个方法,会继续调用原生方法。
处理XMLHttpRequest属性时,主要使用Object.defineProperty,这个方法的作用是直接在一个对象上
定义一个新的属性,或者修改一个已经存在的属性,代码如下:
Object.defineProperty(obj,prop,descriptor)
*/
function hookfun(fun){
return function(){
var args=[].slice.call(arguments);
if (funs[fun] && funs[fun].call(this,args,this.xhr)){
return;
}
return this.xhr[fun].apply(this.xhr,args);
}
}
return window._ahrealxhr;
}
ob.unHookAjax=function(){
if (window._ahrealxhr) XMLHttpRequest=window._ahrealxhr;
window._ahrealxhr=undefined;
}
}(window);
//2.4.2 Ajax-hook拦截
/*
在使用Ajax技术的网页上,使用Ajax-hook进行代码调式会让开发者处理起来游刃有余。
XMLHttpRequest中的方法和属性都可以进行自由操作,所以能做的事也非常多,如可以
直接对网页的Ajax请求中的参数进行打印,也可以在发送Ajax请求的时候进行断点调式,
或直接将返回的响应数据进行展示,代码如下:
*/
hookAjax({
//检测到状态码变化,打印输出响应体
onreadystatechange:function(xhr){
if(xhr.status==200){
console.log("response:\n",xhr.response)
//console.log("response")
}
return false;//不进行阻断
},
//在打开Ajax请求的时候进行操作
open:function(arg){
console.log("open called:\nmethod:%s,\nurl:%s,\nasync:%s\n",arg[0],arg[1],arg[2])
//console.log("open")
},
//在发送Ajax请求的时候进行操作
send:function(arg){
debugger;//设置断点
console.log("send called:",arg)
},
//在设置请求头部的时候进行操作
setRequestHeader:function(arg){
console.log("setRequestHearder:",arg)
},
})
/*
百度图片(https://image.baidu.com/)的加载使用了Ajax技术,可用来做测试。将以上hook代码注入到网页中,
或直接输入Console控制中,查看Console面板的输出:
Console面板会直接输出请求图片的Ajax接口,并且打印设置的headers头部,
还展示了响应体返回的JSON图片数据。
*/
