chrome插件之网络模块

1. 背景

本文将介绍 chrome 扩展(chrome插件)webRequest模块的内容,介绍其对于实际应用中的使用场景,以及如何使用的简单引导,适用于对 chrome 扩展开发不熟悉或者对webRequest模块不熟悉的朋友。

本文中的内容,绝大部分是对官方文档的翻译,其次就是笔者在 Chrome 扩展的开发过程中,对该模块的理解。如果想了解更多信息,请阅读官方文档,本文仅起抛砖引玉的作用。

chrome 一直在更新版本,新版本发布时,模块的使用方法、参数等或许会发生变化,如果在使用过程中,部分 api 出现使用错误的情况,请读者及时查看最新的官方文档。

2. 应用场景

  • 嗅探分析网络请求
  • 屏蔽广告
  • 禁用跨域
  • 反向代理调试线上项目
  • 修改请求头

3. 知识背景

3.1 权限相关

要使用网络请求 API,必须要在扩展程序的清单文件中声明 “webRequest” 权限,以及需要访问网络请求的所有主机的主机权限。如果需要以阻塞方式使用网络请求 API,还需要另外请求 “webRequestBlocking” 权限。

3.2 请求的生命周期

网络请求 API 定义了一系列事件,在一次网络请求的生命周期内产生,可以使用这些事件监控和分析流量。某些同步事件还允许使用者截获、阻止或者修改请求。

请求的生命周期

  • onBeforeRequest(可以为同步)

    当请求即将发出时产生。这一事件在 TCP 连接建立前发送,可以用来取消或重定向请求。

  • onBeforeSendHeaders(可以为同步)

    当请求即将发出并且初始标头已经准备好时产生。在发送 HTTP 请求前,一旦请求标头可用就产生这一事件。这一事件可能在与服务器的 TCP 连接建立后产生,但是确保在发送任何 HTTP 数据前产生。这一事件是为了使扩展程序能够添加、修改和删除请求标头。onBeforeSendHeaders 事件将传递给所有订阅者,所以不同的订阅者都可以尝试修改请求。有关具体如何处理的细节,请参见实现细节部分。这一事件可以用来取消请求。

  • onSendHeaders

    当所有扩展程序已经修改完请求标头并且展现最终版本时产生。当请求即将发送至服务器的前一刻产生(前面 onBeforeSendHeaders 事件处理函数作出的修改可以在这一事件产生时体现)。这一事件在标头发送至网络前触发,仅用于提供信息,并且以异步方式处理,不允许修改或取消请求。

  • onHeadersReceived(可以为同步)

    每当接收到 HTTP(S) 响应标头时产生。由于重定向以及认证请求,对于每次请求这一事件可以多次产生。这一事件是为了使扩展程序能够添加、修改和删除响应标头,例如传入的 Set-Cookie 标头。缓存指示是在该事件触发前处理的,所以修改 Cache-Control 之类的标头不会影响浏览器的缓存。它还允许重定向请求。

  • onAuthRequired(可以为同步)

    当请求需要用户认证时产生。当接收到认证失败时产生。事件处理函数有三种选择:提供认证凭据、取消请求并显示错误页面、不采取任何行动。如果提供了错误的用户凭据,可能会为同一请求重复调用事件处理函数。这一事件可以同步处理,提供认证凭据。注意,扩展程序提供的凭据可能无效,注意不要重复提供无效凭据,陷入无限循环。

  • onBeforeRedirect

    当重定向即将执行时产生,重定向可以由 HTTP 响应代码或扩展程序触发。这一事件仅用于提供信息,并以异步方式处理,不允许修改或取消请求。

  • onResponseStarted

    当接收到响应正文的第一个字节时产生。对于 HTTP 请求,这意味着状态行和响应标头已经可用。这一事件仅用于提供信息,并以异步方式处理,不允许修改或取消请求。

  • onCompleted

    当请求成功处理后产生。

  • onErrorOccurred

    当请求不能成功处理时产生。

关于“同步事件”的含义将在下文中描述

3.3 备注

3.3.1 最终事件

网络请求 API 保证对于每一个请求,onCompleted 或 onErrorOccurred 是最终产生的事件,除了如下例外:如果请求重定向至data://URL,onBeforeRedirect 将是最后报告的事件。

3.3.2 特殊 request header

当前不提供给 onBeforeSendHeaders 事件的标头列表,这一列表不保证是完整的或者不会变化:

  • Authorization
  • Cache-Control
  • Connection
  • Content-Length
  • Host
  • If-Modified-Since
  • If-None-Match
  • If-Range
  • Partial-Data
  • Pragma
  • Proxy-Authorization
  • Proxy-Connection
  • Transfer-Encoding

3.3.3 支持哪些协议

只有扩展程序具有相应的主机权限时,网络请求 API 才会暴露相关的请求。此外,只能访问下列协议的请求:http://、https://、ftp://、file:// 或 chrome-extension://。 此外,某些请求的 URL 即使使用了以上某种协议也会被隐藏。例如,chrome-extension://other_extension_id,其中 other_extension_id 不是处理该请求的扩展程序标识符。

3.3.4 关于 CORS

从 Chrome79 开始,请求标头修改会影响跨源资源共享(CORS)检查。如果为跨源请求修改的标头不符合条件,将导致发送 CORS 预检请求,以询问服务器是否可以接受这些标头。如果真的需要修改标头以违反 CORS 协议,那么需要在 opt_extraInfoSpec 中指定“extraHeaders”。另一方面,响应标头的修改并不能欺骗 CORS 检查。如果需要欺骗 CORS 协议,还需要为响应修改指定“extraHeaders”。

从 Chrome79 开始,web 请求 API 在默认情况下不会拦截 CORS 飞前请求和响应。如果在 opt_extraInfoSpec 中为请求 URL 指定了“extraHeaders”的侦听器,则扩展可以看到请求 URL 的 CORS 预检请求。

3.4 注册事件监听器

enum ResourceType {
  main_frame: 'main_frame',
  sub_frame: 'sub_frame',
  stylesheet: 'stylesheet',
  script: 'script',
  image: 'image',
  font: 'font',
  object: 'object',
  xmlhttprequest: 'xmlhttprequest',
  ping: 'ping',
  csp_report: 'csp_report',
  media: 'media',
  websocket: 'websocket',
  other: 'other',
}

interface RequestFilter {
  tabId: number
  types: ResourceType[]
  urls: string[]
  windowId: number
}

interface Header {
  name: string
  value?: string
  binaryValue?: any
}

interface Credential {
  username: string
  password: string
}

interface BlockingResponse {
  // 仅用于 onBeforeRequest
  cancel: Boolean
  // 仅用于 onBeforeRequest 和 onHeadersReceived
  redirectUrl: string
  // 仅用于 onBeforeSendHeaders,如:[ { name: 'pragma', value: 'no-cache' }]
  requestHeaders: Header[]
  // 仅用于 onHeadersReceived,如:[ { name: 'content-type', value: 'text/html' }]
  responseHeaders: Header[]
  // 仅用于 onAuthRequired
  authCredentials: Credential
}

enum Option {
  blocking: 'blocking',
  extraHeaders: 'extraHeaders',
  requestBody: 'requestBody',
  requestHeaders: 'requestHeaders',
  responseHeaders: 'responseHeaders',
}

const callback = details => {
  this.callback(details, 'onBeforeRequest')
  const response: BlockingResponse = {
    cancel: true
  }
  return response
}
const filter: RequestFilter = { urls: ['<all_urls>'] }
// 详情请看官方文档
const options: Option[] = ['blocking']

chrome.webRequest.onBeforeRequest.addListener(callback, filter, options)
  • callback

    即事件触发时的回调,该回调默认传入一个参数(details),details 就是请求的详情。

  • filter

    Object 类型,限制事件回调 callback 触发的过滤器。filter 有四个属性可以指定,分别为 ①urls(包含指定 url 的数组)、②types(请求的类型,共 8 种)、③tabId(标签页 id)、④windowId(窗口 id)。

  • options

    包含"blocking",意味着要求请求同步,基于此才可以修改消息头。

4. 如何编写代码

4.1 声明权限

{
  // ....
  "permissions": [
    // 声明模块权限
    "webRequest",
    // 声明模块权限
    "webRequestBlocking",
    // 声明主机权限
    "<all_urls>"
    // ....
  ]
  // ....
}

4.2 DEMO

4.2.1 入门 DEMO

const callback = details => {}
const filter = { urls: ['<all_urls>'] }
const extraInfoSpec = []

chrome.webRequest.onBeforeRequest.addListener(callback, filter, extraInfoSpec)

每一个 addListener() 调用必须传递回调函数,作为第一个参数。将向这一回调函数传递包含当前 URL 请求详情的字典数据,字典数据中的信息取决于具体事件类型以及 extraInfoSpec 的内容。

webRequest.RequestFilter 类型的 filter 参数允许通过不同的方式限制为哪些请求产生事件:

  • urls

    URL 匹配表达式,例如 😕/www.google.com/foobar。

  • types

    请求类型,例如 “main_frame”(为顶层框架加载的文档)、“sub_frame”(为内嵌框架加载的文档)和 “image”(网站上的图片)。请参见 webRequest.RequestFilter。

  • tabId

    某个标签页的 id。

  • windowId

    某个窗口的 id。

extraInfoSpec 取决于所监听的事件类型,可以在 extraInfoSpec 中指定字符串,获取有关请求的附加信息。这样是为了仅在明确请求时才提供请求数据的有关详情。

4.2.2 阻塞请求的 DEMO

chrome.webRequest.onBeforeRequest.addListener(
  function (details) {
    return { cancel: details.url.includes('https://www.baidu.com') }
  },
  { urls: ['<all_urls>'] },
  ['blocking']
)
chrome.webRequest.onBeforeRequest.addListener(
  function (details) {
    return { cancel: true }
  },
  { urls: ['*://www.baidu.com/*'] },
  ['blocking']
)

示例二的效率更高,因为不发送至 www.baidu.com 的请求不必传递给扩展程序。

4.2.3 禁用跨域的 DEMO

笔者只是介绍如何禁用跨域,若不是非常清楚该行为的副作用,请不要添加该行为。

class WebRequestUtils {
  static getHeaderValueByName (headers, name) {
    for (let i = 0; i < headers.length; i++) {
      const header = headers[i]
      if (header.name.toLowerCase() === name.toLowerCase()) {
        return header.value
      }
    }
    return ''
  }

  static modifyHeaderValue (headers, name, value) {
    for (let i = 0; i < headers.length; i++) {
      const header = headers[i]
      if (header.name.toLowerCase() === name) {
        headers.splice(i, 1)
      }
    }
    headers.push({ name, value })
  }
}
const callback = async details => {
  let { initiator, responseHeaders, url } = details
  const originalOrigin = WebRequestUtils.getHeaderValueByName(
    responseHeaders,
    'Access-Control-Allow-Origin'
  )
  if (
    initiator &&
    !initiator.startsWith(originalOrigin) &&
    originalOrigin !== '*'
  ) {
    console.log('%cCORS', 'color: red;', `pageUrl: ${initiator} url: ${url}`)
    WebRequestUtils.modifyHeaderValue(
      responseHeaders,
      'Access-Control-Allow-Origin',
      '*'
    )
    WebRequestUtils.modifyHeaderValue(
      responseHeaders,
      'Access-Control-Allow-Credentials',
      'false'
    )
    WebRequestUtils.modifyHeaderValue(
      responseHeaders,
      'Access-Control-Allow-Methods',
      '*'
    )
    WebRequestUtils.modifyHeaderValue(
      responseHeaders,
      'Access-Control-Allow-Headers',
      '*'
    )
  }
  return { responseHeaders }
}
chrome.webRequest.onHeadersReceived.addListener(
  callback,
  { urls: ['<all_urls>'] },
  ['blocking', 'responseHeaders', 'extraHeaders']
)

4.3 同步事件

如果可选的 extraInfoSpec 数组包含 ‘blocking’ 字符串(仅允许用于特定事件,具体请查看上文中的生命周期),回调函数将以同步方式处理。这意味着请求将阻塞,直到回调函数返回。在这一种情况下,回调函数可以返回 webRequest.BlockingResponse 对象,确定这一请求下一步的生命周期。取决于当前上下文,这一响应允许取消或重定向某个请求(OnBeforeRequest),取消请求或修改标头(onBeforeSendHeaders),或者提供认证凭据(onAuthRequired)。

5. webRequest实现细节

5.1 冲突的解决

在网络请求 API 的当前实现中,如果至少一个扩展程序要求取消请求,则请求将被取消。如果一个扩展程序取消了一个请求,所有扩展程序都会收到 onErrorOccurred 事件的通知。一次只有一个扩展程序允许重定向请求或者修改标头。如果多于一个扩展程序尝试修改请求,最近安装的扩展程序具有优先级,而忽略所有其他扩展程序。如果扩展程序修改或重定向的要求被忽略,也不会收到通知。

5.2 缓存

Chrome 浏览器使用两种缓存:磁盘缓存和十分快速的内存缓存。内存缓存的生命周期与渲染器进程(大致与每个标签页对应)的生命周期相关,通过内存缓存响应的请求对网络请求 API 不可见。如果请求处理函数更改了它的行为(例如根据哪些请求被阻止而作出的行为),简单的页面刷新不一定能够体现这一更改的行为。要确保行为更改生效,请调用handlerBehaviorChanged()来清洗内存缓存。然而不要经常调用,清洗缓存是一项开销非常大的操作。

5.3 时间戳

网络请求事件的 timeStamp 属性仅保证内部的一致性,将两个事件的时间相比较会得到它们之间正确的时间差,但是与扩展程序内的当前时间(例如通过 (new Date()).getTime())比较则可能会导致不可预料的结果。(关于这一点,建议直接阅读官方文档)

5.4 错误处理

如果使用无效的参数注册事件,会引发 JavaScript 错误,事件处理程序也不会注册。如果事件处理的过程中产生错误,或者事件处理程序返回无效的 BlockingResponse 对象,扩展程序的控制台中会记录错误消息,同时忽略对应请求的处理程序。

6. 局限

6.1 无法修改响应数据

出于安全考虑,只允许监听请求、修改消息头,不允许直接修改响应数据。

7. API

笔者只在此处列出部分 API,详细的 API 文档请查看官方资料。

7.1 RequestFilter

描述应用于网络请求事件的过滤器对象。

属性类型描述
urlsarray of stringURL 或 URL 匹配表达式列表,不匹配任何 URL 的请求会被过滤出去。
types(可选)array of enum of “main_frame”, “sub_frame”, “stylesheet”, “script”, “image”, “object”, “xmlhttprequest”, or “other”请求类型的列表,不匹配任何一种类型的请求会被过滤出去。
tabId(可选)integer标签页的 id
windowId(可选)integer窗口的 id

7.2 HttpHeaders

包含 HTTP 标头的数组,每一项标头都通过词典表示,包含 name 属性,以及 value 或 binaryValue 中的某一属性。

7.3 BlockingResponse

用于在 extraInfoSpec 参数中指定 “blocking” 的事件处理函数的返回值,允许事件处理函数修改网络请求。

属性类型描述
cancel(可选)boolean如果为 true,则取消请求。在 onBeforeRequest 事件中使用,用来阻止请求的发送。
redirectUrl(可选)string仅用于 onBeforeRequest 和 onHeadersReceived 事件的返回值。如果设置了该属性,就会阻止原始请求的发送/完成,并重定向至指定的 URL。允许重定向至非 HTTP 协议的 URL,例如 data:。重定向操作产生的重定向通常使用原来的请求方法,以下情况例外:如果在 onHeadersReceived 阶段产生了重定向,重定向请求将使用 GET 方法发出。
requestHeaders(可选)HttpHeaders仅用于 onBeforeSendHeaders 事件的返回值。如果设置了这一属性,请求将改用这些标头发出。
responseHeaders(可选)HttpHeaders仅用于 onHeadersReceived 事件的返回值。如果设置了这一属性,则假定服务器返回了这些响应标头。为了限制冲突数目(对于每一个请求,只有一个扩展程序可以修改 responseHeaders),只有有当确实需要修改标头时才应该返回 responseHeaders。
authCredentials(可选)object仅用于 onAuthRequired 事件的返回值。如果设置了这一属性,发出的请求将使用提供的凭据,数据结构:{username: ‘’, password: ‘’}

7.4 handlerBehaviorChanged

chrome.webRequest.handlerBehaviorChanged(() => {})
当网络请求处理函数的行为发生更改时,为了避免缓存导致的不正确处理,需要调用这一函数。调用这一函数的开销比较大,不要经常调用。

8. 资源

8.1 DEMO项目

DEMO包含该模块全部事件的监听方法,解压之后,直接安装到chrome浏览器,即可使用插件,下载地址

8.2 插件开发文档

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿祥_csdn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值