自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

通過(guò)覆蓋原型屬性攔截 XMLHttpRequest 響應(yīng)

開(kāi)發(fā) 前端
在這篇文章中我修改了文本數(shù)據(jù),因?yàn)檫@種修改更常見(jiàn)且結(jié)果容易可視化,但同樣的方法應(yīng)該也適用于blob或任何類型的響應(yīng)數(shù)據(jù)。當(dāng)然modifyTextResponse()應(yīng)該替換為合適的函數(shù)。

在JavaScript中有兩種發(fā)起HTTP請(qǐng)求的API - 現(xiàn)代的fetch()和傳統(tǒng)的XMLHttpRequest。它們功能完全相同,只是語(yǔ)法不同。XMLHttpRequest使用回調(diào)處理響應(yīng),而fetch()返回更方便使用的Promise。

XMLHttpRequest是發(fā)起HTTP請(qǐng)求的主流API。在新項(xiàng)目中使用傳統(tǒng)的XMLHttpRequest是沒(méi)有意義的。

另一方面,將現(xiàn)有可運(yùn)行的基于XMLHttpRequest的代碼升級(jí)到fetch()并不會(huì)帶來(lái)顯著好處。那些經(jīng)過(guò)多年開(kāi)發(fā)、擁有大量代碼庫(kù)的成功網(wǎng)站,沒(méi)有理由在代碼中用fetch()替換XMLHttpRequest。將他們可運(yùn)行的代碼升級(jí)到fetch()只會(huì)帶來(lái)bug和風(fēng)險(xiǎn)。

我檢查了我所知道的一些流行網(wǎng)站的網(wǎng)絡(luò)活動(dòng)。google、youtube、gmail、bing、linkedin、tiktok、instagram、facebook主要依賴XMLHttpRequest,也使用一些fetch()。reddit、quora則不使用XMLHttpRequest。

為什么要重寫(xiě)XMLHttpRequest中的response

首先,在前端開(kāi)發(fā)和調(diào)試過(guò)程中,在網(wǎng)頁(yè)接收到HTTP響應(yīng)之前修改響應(yīng)是一個(gè)有用的技術(shù)。通過(guò)重寫(xiě)XMLHttpRequest,可以在不改變后端的情況下記錄、偽造或調(diào)整響應(yīng)體。

這種技術(shù)也可用于網(wǎng)頁(yè)爬蟲(chóng),并且使一些瀏覽器擴(kuò)展的功能得以實(shí)現(xiàn)。

但是如果網(wǎng)頁(yè)需要多次重新加載,比如在開(kāi)發(fā)或調(diào)試期間,最好不要在控制臺(tái)執(zhí)行修改響應(yīng)數(shù)據(jù)的腳本,而是作為瀏覽器擴(kuò)展的content script自動(dòng)執(zhí)行。當(dāng)腳本作為content script注入時(shí),可以方便地由小型可重用模塊組成。

攔截HTTP響應(yīng)數(shù)據(jù)的示例

如上所述,許多流行網(wǎng)站都使用XMLHttpRequest發(fā)起HTTP請(qǐng)求。在這個(gè)實(shí)驗(yàn)中我使用知名且信譽(yù)良好的Facebook。。Facebook的初始HTML是在服務(wù)器端渲染的,所以不能通過(guò)修改XMLHttpRequest響應(yīng)來(lái)修改它。但是之后逐漸加載的內(nèi)容可以在被網(wǎng)頁(yè)訪問(wèn)之前進(jìn)行修改:

  • words mushroom、fungus或fungi被替換為字符串 ??????REPLACED??????
  • jpg圖片的URL被替換為一個(gè)蘑菇圖片的URL

HTTP響應(yīng)中的文本修改是通過(guò)以下函數(shù)完成的:

var RE = /"[^"]+\.jpg?[^"]+"/gi;
var REPLACEMENT = '"https://scontent-zrh1-1.xx.fbcdn.net/v/t39.30808-6/272917062_10157959892971991_7437132751388296237_n.jpg?_nc_cat=100&ccb=1-7&_nc_sid=127cfc&_nc_ohc=g7Qun1RfEvgQ7kNvgFVEOv6&_nc_ht=scontent-zrh1-1.xx&_nc_gid=AmiHBSQhbkAppb0buDWHP2N&oh=00_AYAYpDPV90lNRXvX2-bftFkUPHcqQJYVBmsE8BZnyNvqmg&oe=66EB3BAE"'
    .replaceAll('/', '\\/');

var RE2 = /mushroom|fungus|fungi/gi;
var REPLACEMENT2 = '??????REPLACED??????';

function modifyTextResponse(val) {
    if (typeof val === 'string')
        return val.replaceAll(RE, REPLACEMENT).replaceAll(RE2, REPLACEMENT2);
    return val;
}

下面的示例腳本使用了這個(gè)modifyTextResponse(val)函數(shù)。

這個(gè)蘑菇圖片很好看但URL很長(zhǎng)且難看。Facebook頁(yè)面的內(nèi)容安全策略(CSP)阻止從其他來(lái)源加載圖片。我本可以使用反CSP瀏覽器擴(kuò)展來(lái)放寬CSP,但為了簡(jiǎn)單起見(jiàn),我遵守了CSP并使用了Facebook托管的圖片。

在視頻中,腳本在頁(yè)面加載時(shí)自動(dòng)作為content script注入。

{
  "name": "XMLHttpRequest",
  "version": "1.0",
  "manifest_version": 3,
  "description": "XMLHttpRequest",
  "permissions": [
    "scripting"
  ],
  "action": {},
  "icons": {
    "128": "icon.png"
  },
  "content_scripts": [
    {
      "matches": [
        "https://*.facebook.com/*"
      ],
      "run_at": "document_start",
      "js": [
        "main.js"
      ],
      "world":"MAIN"
    }
  ]
}

腳本必須注入到頁(yè)面上下文中,即MAIN world。重寫(xiě)原生JavaScript方法是MAIN world的主要用例,否則這個(gè)world沒(méi)有擴(kuò)展API,用處不大。

訪問(wèn)XMLHttpRequest響應(yīng)數(shù)據(jù)的唯一方式

XMLHttpRequest中有幾個(gè)提供訪問(wèn)響應(yīng)數(shù)據(jù)的屬性:

  • response
  • responseText
  • responseXML

這些屬性都是getter函數(shù)。要重寫(xiě)任何類型的響應(yīng),只需要重寫(xiě)response getter就夠了。responseText和responseXML似乎只是通過(guò)轉(zhuǎn)換response的值來(lái)工作。

但是需要了解什么時(shí)候進(jìn)行重寫(xiě)。有兩個(gè)合理的選擇:

  • readystatechange事件監(jiān)聽(tīng)器
  • open()方法

XMLHttpRequest的所有可能事件

我們看看HTTP請(qǐng)求期間發(fā)生的所有事件。

<script src="api.js" type="module"></script>

<button type="button" id="btn">Send</button>

腳本為所有以on開(kāi)頭的XMLHttpRequest屬性添加監(jiān)聽(tīng)器:

// api.js

const url = "https://data.cdc.gov/api/views/95ax-ymtc/rows.json";

function onEvent(e) {
    console.log(e.type.padEnd(16, ' '), this.readyState, this.response.length, e.loaded);
}

function request() {
    const xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.send();

    for (let k in xhr)
        if (k.startsWith('on'))
            xhr[k] = onEvent;
}

btn.addEventListener("click", request);

這個(gè)腳本請(qǐng)求一些公開(kāi)可用的數(shù)據(jù)。

你可以看到,readystatechange監(jiān)聽(tīng)器可能被多次調(diào)用,甚至可以訪問(wèn)未完全加載的數(shù)據(jù)。某些網(wǎng)站可能不會(huì)等待readyState===4就立即使用不完整的數(shù)據(jù)。

下面的代碼通過(guò)在XMLHttpRequest對(duì)象中創(chuàng)建新的response屬性來(lái)重寫(xiě)prototype中的response getter:

if (!oldXHROpen)
    var oldXHROpen = XMLHttpRequest.prototype.open;

XMLHttpRequest.prototype.open = function () {
    let oldOnreadystatechange = this.onreadystatechange;

    this.onreadystatechange = function () {
        if (this.readyState === XMLHttpRequest.DONE) {
             const txt = this.responseText;

            if (txt) {
                Object.defineProperty(this, 'response', { writable: true });
                this.response = modifyTextResponse(txt);
            }
        }

        if (oldOnreadystatechange)
            return oldOnreadystatechange.apply(this, arguments);
    };

    return oldXHROpen.apply(this, arguments);
}

這種方法適用于許多網(wǎng)站,但在Facebook上會(huì)產(chǎn)生異常效果 - 關(guān)鍵詞沒(méi)有被替換,而且頁(yè)面很快就會(huì)崩潰。這可能是因?yàn)镕acebook頁(yè)面在數(shù)據(jù)完全加載之前就使用了數(shù)據(jù)。因此,創(chuàng)建一個(gè)從原生getter讀取數(shù)據(jù)的getter而不是屬性是至關(guān)重要的。新的getter應(yīng)該在所有可能的事件回調(diào)中都能正常工作。定義getter的唯一可能位置是open()方法。

代理getter轉(zhuǎn)換繼承g(shù)etter返回的值

這段不會(huì)出錯(cuò)的代碼定義了一個(gè)response getter函數(shù),它首先通過(guò)在this對(duì)象上調(diào)用prototype中的response getter獲取真實(shí)的響應(yīng)值,然后返回用當(dāng)前響應(yīng)值調(diào)用modifyTextResponse()產(chǎn)生的值:

function defineProxyGetter(obj, property, func) {
    Object.defineProperty(obj, property, {
        get() {
            const val = Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, property).get.call(obj);
            return func(val);
        }
    });
}

if (!oldXHROpen)
    var oldXHROpen = XMLHttpRequest.prototype.open;

XMLHttpRequest.prototype.open = function () {
    defineProxyGetter(this, 'response', modifyTextResponse);

    return oldXHROpen.apply(this, arguments);
}

在這篇文章中我修改了文本數(shù)據(jù),因?yàn)檫@種修改更常見(jiàn)且結(jié)果容易可視化,但同樣的方法應(yīng)該也適用于blob或任何類型的響應(yīng)數(shù)據(jù)。當(dāng)然modifyTextResponse()應(yīng)該替換為合適的函數(shù)。

責(zé)任編輯:武曉燕 來(lái)源: 大遷世界
相關(guān)推薦

2012-11-08 10:40:47

JavaScript原型鏈

2022-03-15 16:35:22

ARM

2011-11-25 17:24:20

跨平臺(tái)開(kāi)發(fā)移動(dòng)開(kāi)發(fā)

2017-09-11 17:52:35

APP

2020-02-20 14:00:15

JavaScript原型原型鏈

2009-04-14 13:36:50

AJAXXMLHttpRequ基礎(chǔ)

2009-01-16 10:43:00

Web開(kāi)發(fā)AJAX

2020-09-10 07:04:30

JSJavaScript 原型鏈

2017-04-07 11:15:49

原型鏈原型Javascript

2019-02-27 16:00:48

JS原型原型鏈對(duì)象

2018-12-24 17:52:39

身份認(rèn)證桌面登錄安全

2024-08-09 12:44:45

JavaScript原型鏈鏈條

2024-04-08 08:18:35

f2JavaScripHTTP

2022-02-11 09:38:14

Java技巧反射

2020-10-20 08:35:34

JS基礎(chǔ)進(jìn)階

2022-05-26 09:20:01

JavaScript原型原型鏈

2011-08-31 14:48:33

JavaScript

2012-01-12 11:05:05

響應(yīng)式Web設(shè)計(jì)

2022-05-26 23:14:26

原型原型鏈JS繼承

2013-11-29 11:01:44

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)