{"id":15025627,"url":"https://github.com/wendux/ajax-hook","last_synced_at":"2025-05-14T11:09:50.323Z","repository":{"id":37431909,"uuid":"74861972","full_name":"wendux/ajax-hook","owner":"wendux","description":"Intercepting browser's http requests which made by XMLHttpRequest.","archived":false,"fork":false,"pushed_at":"2023-09-24T07:12:58.000Z","size":325,"stargazers_count":2629,"open_issues_count":37,"forks_count":497,"subscribers_count":38,"default_branch":"master","last_synced_at":"2025-04-07T17:07:31.532Z","etag":null,"topics":["ajax","ajax-hook","hooks","xmlhttprequest-hook"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/wendux.png","metadata":{"files":{"readme":"README.md","changelog":"change-list.md","contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2016-11-27T02:10:04.000Z","updated_at":"2025-04-04T12:07:31.000Z","dependencies_parsed_at":"2024-01-13T18:15:43.987Z","dependency_job_id":null,"html_url":"https://github.com/wendux/ajax-hook","commit_stats":{"total_commits":48,"total_committers":6,"mean_commits":8.0,"dds":0.6041666666666667,"last_synced_commit":"9efea44c8b2bc27b5ec0e8c2e3a2477bdc76dab9"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wendux%2Fajax-hook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wendux%2Fajax-hook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wendux%2Fajax-hook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/wendux%2Fajax-hook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/wendux","download_url":"https://codeload.github.com/wendux/ajax-hook/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247882179,"owners_count":21012012,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ajax","ajax-hook","hooks","xmlhttprequest-hook"],"created_at":"2024-09-24T20:02:42.608Z","updated_at":"2025-04-11T04:19:45.683Z","avatar_url":"https://github.com/wendux.png","language":"JavaScript","readme":"# ajax-hook\n\n[![npm version](https://img.shields.io/npm/v/ajax-hook.svg)](https://www.npmjs.org/package/ajax-hook) [![build status](https://travis-ci.org/wendux/Ajax-hook.svg?branch=master)](https://travis-ci.org/wendux/Ajax-hook) [![license](https://img.shields.io/github/license/mashape/apistatus.svg)](https://opensource.org/licenses/mit-license.php) ![](https://img.shields.io/badge/TypeScript-support-orange.svg)   [![](https://img.shields.io/github/size/wendux/Ajax-hook/dist/ajaxhook.core.min.js.svg)](https://unpkg.com/ajax-hook@2.0.0/dist/ajaxhook.core.min.js)\n\n\n## 简介\n\najax-hook是用于拦截浏览器 XMLHttpRequest 对象的轻量库，它可以在 XMLHttpRequest 对象发起请求之前、收到响应内容之后以及发生错误时获得处理权，通过它你可以提前对请求、响应以及错误进行一些预处理。Ajax-hook具有很好的兼容性，可以在任何支持**ES5**的浏览器上运行，因为它并不依赖 ES6 特性。\n\n## 使用\n\n### 安装\n\n- CDN引入\n\n  ```html\n  \u003cscript src=\"https://unpkg.com/ajax-hook@{版本号}/dist/ajaxhook.min.js\"\u003e\u003c/script\u003e\n  ```\n\n  引入后会有一个名为\"ah\"（ajax hook）的全局对象，通过它可以调用ajax-hook的API，如`ah.proxy(hooks)`\n\n- NPM引入\n\n  ```shell\n  npm install ajax-hook\n  ```\n\n一个简单示例：\n\n```javascript\nimport { proxy } from \"ajax-hook\";\nproxy({\n    //请求发起前进入\n    onRequest: (config, handler) =\u003e {\n        console.log(config.url)\n        handler.next(config);\n    },\n    //请求发生错误时进入，比如超时；注意，不包括http状态码错误，如404仍然会认为请求成功\n    onError: (err, handler) =\u003e {\n        console.log(err.type)\n        handler.next(err)\n    },\n    //请求成功后进入\n    onResponse: (response, handler) =\u003e {\n        console.log(response.response)\n        handler.next(response)\n    }\n})\n```\n\n现在，我们便拦截了浏览器中通过`XMLHttpRequest`发起的所有网络请求！在请求发起前，会先进入`onRequest`钩子，调用`handler.next(config)` 请求继续，如果请求成功，则会进入`onResponse`钩子，如果请求发生错误，则会进入`onError` 。我们可以更改回调钩子的第一个参数来修改修改数据。\n\n[点击查看更多项目示例](https://github.com/wendux/ajax-hook/tree/master/examples)。\n\n### API介绍\n\n#### `proxy(proxyObject, [window])`\n\n拦截全局`XMLHttpRequest`\n\n\u003e 注意：proxy 是通过ES5的getter和setter特性实现的，并没有使用ES6 的Proxy对象，所以可以兼容ES5浏览器。\n\n参数：\n\n- `proxyObject`是一个对象，包含三个可选的钩子`onRequest`、`onResponse`、`onError`，我们可以直接在这三个钩子中对请求进行预处理。\n- `window`：可选参数，默认情况会使用当前窗口的`window`对象，如果要拦截iframe中的请求，可以将`iframe.contentWindow` 传入，注意，只能拦截**同源**的iframe页面（不能跨域）。\n\n返回值：\n`ProxyReturnObject`\n\nProxyReturnObject 是一个对象，包含了 `unProxy` 和 `originXhr`\n- `unProxy([window])`：取消拦截；取消后 `XMLHttpRequest` 将不会再被代理，浏览器原生`XMLHttpRequest`会恢复到全局变量空间\n- `originXhr`： 浏览器原生的 `XMLHttpRequest`\n\n\n\n### 钩子函数的签名\n\n#### Handler\n\n在钩子函数中，我们可以通过其第二个参数`handler`对象提供的方法来决定请求的后续流程，`handler`对象它有个方法：\n\n1. `next(arg)`：继续进入后续流程；如果不调用，则请求链便会暂停，这种机制可以支持在钩子中执行一些异步任务。该方法在`onResponse`钩子中等价于`resolve`，在`onError`钩子中等价于`reject`\n2. `resolve(response)`：调用后，请求后续流程会被阻断，直接返回响应数据，上层`xhr.onreadystatechange`或`xhr.onload`会被调用。\n3. `reject(err)`：调用后，请求后续流程会被阻断，直接返回错误，上层的`xhr.onerror`、`xhr.ontimeout`、`xhr.onabort`之一会被调用，具体调用哪个取决于`err.type`的值，比如我们设置`err.type`为\"timeout\"，则`xhr.ontimeout`会被调用。\n\n\u003e 关于`config`、`response`、`err`的结构定义请参考[类型定义文件](./index.d.ts)中的`XhrRequestConfig`、`XhrResponse`、`XhrError`\n\n#### 示例\n\n```javascript\nconst { unProxy, originXhr } = proxy({\n    onRequest: (config, handler) =\u003e {\n        if (config.url === 'https://aa/') {\n            handler.resolve({\n                config: config,\n                status: 200,\n                headers: {'content-type': 'text/text'},\n                response: 'hi world'\n            })\n        } else {\n            handler.next(config);\n        }\n    },\n    onError: (err, handler) =\u003e {\n        if (err.config.url === 'https://bb/') {\n            handler.resolve({\n                config: err.config,\n                status: 200,\n                headers: {'content-type': 'text/text'},\n                response: 'hi world'\n            })\n        } else {\n            handler.next(err)\n        }\n    },\n    onResponse: (response, handler) =\u003e {\n        if (response.config.url === location.href) {\n            handler.reject({\n                config: response.config,\n                type: 'error'\n            })\n        } else {\n            handler.next(response)\n        }\n    }\n})\n\n// 使用jQuery发起网络请求\nfunction testJquery(url) {\n    $.get(url).done(function (d) {\n        console.log(d)\n    }).fail(function (e) {\n        console.log('hi world')\n    })\n}\n\n//测试\ntestJquery('https://aa/');\ntestJquery('https://bb/');\ntestJquery(location.href)\n\n// 取消拦截\nunProxy();\n```\n\n运行后，控制台输出3次 \"hi world\"。\n\n\n\n## 核心API - `hook(hooks,[window])`\n\nAjax-hook在1.x版本中只提供了一个核心拦截功能的库，在1.x中，我们通过`hookAjax()` 方法（2.x中改名为`hook()`）实现了对`XMLHttpRequest`对象**具体属性、方法、回调的细粒度拦截**。而2.x中的`proxy()`方法则是基于`hook`的一个封装。\n\n### `hook(Hooks,[window])`\n\n拦截全局`XMLHttpRequest`，此方法调用后，浏览器原生的`XMLHttpRequest`将会被代理，代理对象会覆盖浏览器原生`XMLHttpRequest`，直到调用`unHook(...)`后才会取消代理。\n\n参数：\n\n- `hooks`：钩子对象，里面是XMLHttpRequest对象的回调、方法、属性的钩子函数，钩子函数会在执行`XMLHttpRequest`对象真正的回调、方法、属性访问器前执行。\n- `window`：可选参数，默认情况会使用当前窗口的`window`对象，如果要拦截iframe中的请求，可以将`iframe.contentWindow` 传入，注意，只能拦截**同源**的iframe页面（不能跨域）。\n\n返回值：\n`HookReturnObject`\n\nHookReturnObject 是一个对象，包含了 `unHook` 和 `originXhr`\n- `unHook([window])`：取消拦截；取消后 `XMLHttpRequest` 将不会再被代理，浏览器原生`XMLHttpRequest` 会恢复到全局变量空间\n- `originXhr`： 浏览器原生的 `XMLHttpRequest`\n\n\n#### 示例\n\n下面我们看一下如何使用`hook`方法来拦截`XMLHttpRequest`对象：\n\n```javascript\nimport { hook } from \"ajax-hook\"\nconst { unHook, originXhr } = hook({\n  //拦截回调\n  onreadystatechange:function(xhr,event){\n    console.log(\"onreadystatechange called: %O\")\n    //返回false表示不阻断，拦截函数执行完后会接着执行真正的xhr.onreadystatechange回调.\n    //返回true则表示阻断，拦截函数执行完后将不会执行xhr.onreadystatechange. \n    return false\n  },\n  onload:function(xhr,event){\n    console.log(\"onload called\")\n    return false\n  },\n  //拦截方法\n  open:function(args,xhr){\n    console.log(\"open called: method:%s,url:%s,async:%s\",args[0],args[1],args[2])\n    //拦截方法的返回值含义同拦截回调的返回值\n    return false\n  }\n})\n\n// 取消拦截\nunHook();\n```\n\n这样拦截就生效了，拦截的全局的`XMLHttpRequest`，所以，无论你使用的是哪种JavaScript http请求库，它们只要最终是使用`XMLHttpRequest`发起的网络请求，那么拦截都会生效。下面我们用jQuery发起一个请求：\n\n```javascript\n// 获取当前页面的源码(Chrome中测试)\n$.get().done(function(d){\n    console.log(d.substr(0,30)+\"...\")\n})\n```\n\n输出:\n\n```\n\u003e open called: method:GET,url:http://localhost:63342/Ajax-hook/demo.html,async:true\n\u003e onload called\n\u003e \u003c!DOCTYPE html\u003e\n  \u003chtml\u003e\n  \u003chead l...\n```\n\n可以看到我们的拦截已经成功。通过日志我们可以发现，在请求成功时，jQuery是回调的`onload（）`，而不是`onreadystatechange()`，由于这两个回调都会在返回响应结果时被调用，所以为了保险起见，如果你要拦截网络请求的结果，建议同时拦截`onload()`和`onreadystatechange()`，除非你清楚的知道上层库使用的具体回调。\n\n### 代理xhr对象和原生xhr对象\n\n“**原生xhr对象**”即浏览器提供的XMLHttpRequest对象实例，而“**代理xhr对象**”指代理了“原生xhr对象”的对象，用户请求都是通过“代理xhr对象”发出，而“代理xhr对象”中又会调用“原生xhr对象”发起真正的网络请求。那么如何获取“代理xhr对象”和“原生xhr对象”呢？\n\n1. XHR事件回调钩子函数（以\"on\"开头的），如`onreadystatechange`、`onload`等，他们的拦截函数的第一个参数都为\"**原生xhr对象**\" (**注意：这个和1.x版本有区别，1.x中为代理xhr对象**)。\n2. XHR方法钩子函数（如`open`、`send`等），它们的第二个参数为**原生xhr对象**。\n3. 所有回调函数钩子、方法钩子中，`this`为**代理xhr对象**\n4. 原生xhr对象和代理对象都有获取彼此的方法和属性，具体见下面示例\n\n```javascript\nhook({\n  // 参数xhr为原生xhr对象\n  onload:function(xhr, event){\n    // this 为代理xhr对象\n    // 原生xhr对象扩展了一个`getProxy()`方法，调用它可以获取代理xhr对象\n    this==xhr.getProxy() //true\n    //可以通过代理xhr对象的`xhr`属性获取原生xhr对象\n    this.xhr==xhr //true\n    console.log(\"onload called\")\n    return false\n  },\n})\n```\n\n### 注意\n\n- `XMLHttpRequest`所有回调函数(以\"on\"开头的，如`onreadystatechange`、`onload`等)，他们的拦截函数的第一个参数都为当前的`XMLHttpRequest`对象**的代理对象**，所以，你可以通过它来进行请求上下文管理。\n\n  \u003e 假设对于同一个请求，你需要在其`open`的拦截函数和`onload` 回调中共享一个变量，但由于拦截的是全局XMLHttpRequest对象，所有网络请求会无次序的走到拦截的方法中，这时你可以通过`xhr`来对应请求的上下文信息。在上述场景中，你可以在`open`拦截函数中给`xhr`设置一个属性，然后在`onload`回调中获取即可。\n\n- `XMLHttpRequest`的所有方法如`open`、`send等`，他们拦截函数的第一个参数是一个数组，数组的内容是其对应的原生方法的参数列表，第二个参数是本次请求对应的`XMLHttpRequest`对象（已代理）。返回值类型是一个布尔值，为true时会阻断对应的请求。\n\n- 对于属性拦截器，为了避免循环调用导致的栈溢出，不可以在其getter拦截器中再读取其同名属性或在其setter拦截器中在给其同名属性赋值。\n\n- 本库需要在支持ES5的浏览器环境中运行(不支持IE8)，但本库并不依赖ES6新特性。\n\n### 拦截`XMLHttpRequest`的属性\n\n除了拦截`XMLHttpRequest`回调和方法外，也可以拦截属性的**读/写**操作。比如设置超时`timeout`，读取响应内容`responseText`等。下面我们通过两个示例说明。\n\n- 假设为了避免用户设置不合理的超时时间，如，小于30ms，那么这将导致超过30ms的网络请求都将触发超时，因此，我们在底层做一个判断，确保超时时间最小为1s：\n\n  ```javascript\n  hook(\n      //需要拦截的属性名\n      timeout: {\n          //拦截写操作\n          setter: function (v, xhr) {\n              //超时最短为1s，返回值为最终值。\n              return Math.max(v, 1000);\n          }\n      }\n  )\n  ```\n\n- 假设在请求成功后，但在返回给用户之前，如果发现响应内容是JSON文本，那么我们想自动将JSON文本转为对象，要实现这个功能，有两种方方法：\n\n  - 拦截成功回调\n\n    ```javascript\n    \n    function tryParseJson1(xhr){\n        var contentType=xhr.getResponseHeader(\"content-type\")||\"\";\n        if(contentType.toLocaleLowerCase().indexOf(\"json\")!==-1){\n            xhr.responseText=JSON.parse(xhr.responseText);\n        }\n    }\n    \n    hookAjax({\n      //拦截回调\n      onreadystatechange:tryParseJson1,\n      onload:tryParseJson1\n    });\n    ```\n\n\n\n  - 拦截`responseText` 和 `response`读操作\n\n    ```javascript\n    function tryParseJson2(v,xhr){\n        var contentType=xhr.getResponseHeader(\"content-type\")||\"\";\n        if(contentType.toLocaleLowerCase().indexOf(\"json\")!==-1){\n            v=JSON.parse(v);\n            //不能在属性的getter钩子中再读取该属性，这会导致循环调用\n            //v=JSON.parse(xhr.responseText);\n        }\n        return v;\n    }\n    \n    //因为无法确定上层使用的是responseText还是response属性，为了保险起见，两个属性都拦截一下\n    hook(\n        responseText: {\n            getter: tryParseJson2\n        },\n        response: {\n            getter:tryParseJson2\n        }\n    )\n    ```\n\n### ajax-hook.core.js\n\n如果你只想使用`hook(...)`方法（不需要使用`proxy()`），我们提供了只包含`hook()`方法的核心库，你可以在[dist目录](https://github.com/wendux/Ajax-hook/tree/master/dist)找到名为`ajax-hook.core.js`的文件，直接使用它即可。\n\n\n\n## `proxy(...)` vs `hook(...)`\n\n`proxy()` 和`hook()`都可以用于拦截全局`XMLHttpRequest`。它们的区别是：`hook()`的拦截粒度细，可以具体到`XMLHttpRequest`对象的某一方法、属性、回调，但是使用起来比较麻烦，很多时候，不仅业务逻辑需要散落在各个回调当中，而且还容易出错。而`proxy()`抽象度高，并且构建了请求上下文（请求信息config在各个回调中都可以直接获取），使用起来更简单、高效。\n\n大多数情况下，我们建议使用`proxy()` 方法，除非`proxy()` 方法不能满足你的需求。\n\n\n\n## 拦截iframe\n\n显示指定iframe 的 window 对象即可，比如：\n```javascript\nvar iframeWindow = ...;\nconst { unProxy } = proxy({...},iframeWindow)\nunProxy(iframeWindow)\n//或\nconst { unHook } = hook({...},iframeWindow)\nunHook(frameWindow)      \n```\n完整示例见：[拦截iframe中的请求](https://github.com/wendux/ajax-hook/tree/master/examples/iframe)。\n\n\u003e 注意：只能拦截**同源**的iframe页面（不能跨域）。\n\n## 原理解析\n通过ES5 getter和setter特性实现对 XMLHttpRequest 对象的代理：\n![image](https://github.com/wendux/Ajax-hook/raw/master/ajaxhook.png)\n\n具体原理解析请查看：[ajax-hook 原理解析](https://juejin.cn/post/6844903470181384206)。\n\n## 最后\n\n本库在2016年首次开源，最初只是个人研究所用，源码50行左右，实现了一个Ajax拦截的核心，并非一个完整可商用的项目。自开源后，有好多人对这个黑科技比较感兴趣，于是我便写了篇介绍的博客，由于代码比较精炼，所以对于JavaScript不是很精通的同学可能看起来比较吃力，之后专门写了一篇原理解析的文章，现在已经有很多公司已经将 ajax-hook 用于线上项目中，直到我知道美团、滴滴也用到之后，笔者对此库进行了修改和扩展以增强其健壮性和实用性，现在已经达到商用的标准，本库也将进行技术支持。如果你喜欢，欢迎Star，如果有问题，欢迎提Issue， 如果你觉得对自己有用想请作者喝杯咖啡的话，请扫描下面二维码：\n\n\u003cimg  width=300 src=\"https://doc.flutterchina.club/images/pay29.jpeg\" /\u003e\n\n","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwendux%2Fajax-hook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwendux%2Fajax-hook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwendux%2Fajax-hook/lists"}