前端監控中的一項重要數據,就是請求數據html
下圖描述了咱們對於請求數據一般有什麼須要 前端
那麼咱們如何才能作到對業務代碼無侵入式的數據收集呢?json
方法很明確, 就是從新原生的api,在關鍵方法上進行作一層代理小程序
下面, 咱們就從這兩個api的角度, 逐步分析如何實現api
通常封裝的方法以下promise
// 首先將須要封裝對象的原屬性保存一個副本,用於代理以後調用
let xhr_open = XMLHttpRequest.prototype.open;
let xhr_send = XMLHttpRequest.prototype.send;
// 第二步,將原對象屬性替換成代理對象
XMLHttpRequest.prototype.open = function (...args) {
// 在這裏,咱們加入本身的獲取邏輯
xhr_open.apply(this, args);
};
XMLHttpRequest.prototype.send = function (data) {
// 在這裏,咱們加入本身的獲取邏輯
xhr_send.apply(this, args);
}
複製代碼
那麼onreadystatechange事件如何處理呢瀏覽器
注意點: 該事件的監聽須要在send方法發送以前 安全
咱們能夠在封裝send方法時,加入該事件的監聽服務器
XMLHttpRequest.prototype.send = function (data) {
// 添加 readystatechange 事件
this.addEventListener('readystatechange', function () {
// 對請求結果作響應的處理
});
xhr_send.call(this, data);
};
複製代碼
封裝方法已經完成了, 那咱們該如何獲取到第一張圖 描述的信息呢?app
open方法的參數列表是固定的, 依次是method,url, async, username, password
在open代理過程當中,獲取便可
XMLHttpRequest.prototype.open = function (...args) {
this.__monitor_xhr = {
method: args[0],
url: args[1]
}
xhr_open.apply(this, args);
};
複製代碼
上面代碼中,咱們在當前xhr對象上寫入了一個新的屬性,用於保存咱們獲取到的信息。 請求body的數據,咱們在下面的分析中獲取
這時候,咱們就須要對請求結果進行處理,獲取咱們想要的數據
XMLHttpRequest.prototype.send = function (data) {
// 記錄請求開始時間,用於計算耗時
const startTime = new Date();
// 添加 readystatechange 事件
this.addEventListener('readystatechange', function () {
try {
if (this.readyState === XMLHttpRequest.DONE) {
// 請求結束時間
const endTime = new Date();
// 請求耗時
this.__monitor.duration = (endTime - startTime) ;
// 請求body
this.__monitor.req_body = data;
// 獲取response header、body等信息
}
} catch (err) {
}
}
});
xhr_send.call(this, data);
};
複製代碼
上述response header信息,能夠經過
xml.getAllResponseHeaders()
複製代碼
獲取, body信息能夠經過如下方法獲取
function getBody (xhrObject) {
var body = void 0;
// IE 11 sometimes throws when trying to access a large responses:
// https://connect.microsoft.com/IE/Feedback/Details/1053110
// gte IE10 will support responseType
try {
switch (xhrObject.responseType) {
case 'json':
case 'arraybuffer':
case 'blob':
{
body = xhrObject.response;
break;
}
case 'document':
{
body = xhrObject.responseXML;
break;
}
case 'text':
case '':
{
body = xhrObject.responseText;
break;
}
default:
{
body = '';
}
}
// When requesting binary data, IE6-9 will throw an exception
// on any attempt to access responseText (#11426)
if (!body && typeof xhrObject.responseText === "string" ) {
body = xhrObject.responseText;
}
} catch (err) {
body = 'monitor: Error accessing response.';
}
return body
}
複製代碼
fetch因爲api和xhr有很大差別, fetch返回了promise對象, 這種狀況的封裝
首先,咱們仍是須要對fetch加個代理, 方式相似xhr
// 首先保存原先的fetch 引用
let origFetch = window.fetch
window.fetch =function(fn, t) {
// 這邊執行咱們的數據收集工做
return origFetch.apply(this, args)
};
複製代碼
獲取request 和response信息
window.fetch =function(fn, t) {
var args = new Array(arguments.length);
for (var i = 0; i < args.length; ++i) {
args[i] = arguments[i];
}
var p = null;
// 因爲fetch的參數列表更靈活, 因此須要對應的處理
if (typeof Request !== 'undefined' && args[0] instanceof Request) {
p = args[0].clone().text().then(function (body) {
return utils.extendsObjects({}, pluckFetchFields(args[0]), {
body: body
});
});
} else {
p = Promise.resolve(utils.extendsObjects({}, pluckFetchFields(args[1]), {
url: '' + args[0],
body: (args[1] || {}).body
}));
}
var fetchData = {
method: '',
url: '',
status_code: null,
start_time: new Date().getTime(),
request:{
headers: {},
body: ''
},
response:{
headers: {},
body: ''
},
timeline:{
dns:0,
connect:0,
response:0,
request: 0,
duration: 0
}
};
// 此處默認加一個then,對結果進行收集處理
return origFetch.apply(this, args).then(function(response) {
fetchData.status_code = response.status;
fetchData.duration = new Date().getTime() - fetchData.start_time
fetchData.timeline.duration = fetchData.duration
p.then(function(req) {
fetchData.method = req.method
fetchData.url = req.url
utils.objectMerge(fetchData.request, {mode: req.mode, referrer: req.referrer, credentials: req.credentials, headers: req.headers, body: req.body})
var clonedText = null;
try {
clonedText = response.clone().text();
} catch (err) {
// safari has a bug where cloning can fail
clonedText = Promise.resolve('Monitor fetch error: ' + err.message);
}
clonedText.then(function(body) {
fetchData.response.body = body
fetchData.response.headers = makeObjectFromHeaders(response.headers)
// 將數據發送到服務器
_reportToServer(fetchData)
})
})
return response;
});
};
複製代碼
以上內容,咱們還缺乏了一步,那就是第一張圖中的timeline數據, 該部分將和性能數據一塊兒分析
安全, 咱們應該對涉及用戶隱私的數據進行脫敏傳輸 或 不傳輸
對於response的數據, 咱們應該有須要的截取, 而不是一股腦的傳輸,防止無效數據太多
以上內容只是對瀏覽器請求數據收集和使用作了個大概的分享,除了瀏覽器外, 還有一些,如小程序的請求獲取,會在小程序信息收集中分享