本地代理无需Webpack,用Chrome扩展实现浏览器代理

5,387 阅读3分钟

这是我参与更文挑战的第5天,活动详情查看: 更文挑战

本地前端代码尝试与后端接口连调时,我们常用像Webpack dev server之类的工具配置代理:

// webpack.config.js
module.exports = {
  //...
  devServer: {
    proxy: {
      '/api': 'http://localhost:3000',
    },
  },
};

 每次改配置得重启一下才能生效。实际上无需如此麻烦,用Chrome调试的话,只需利用Chrome提供的代理接口就可以轻松配置修改代理立即生效!

Chrome插件市场上有一大把这样的插件,如Proxy SwitchyOmega

unnamed.jpeg

自己做一个也行对简单,我们只需要:

  • manifest.json
  • 监听触发代理的background.js
  • 一个配置用的页面

在v2扩展中可以实现代理效果的接口有两个:

  1. proxy接口
  2. webRequest接口

 各有千秋,使用场景叶略不同。让我们分别看两种方法怎么做:

使用proxy接口

proxy接口相比webRequest并不灵活,只能指定代理的目标服务器地址(域名或IP。但它的性能比较好。

manifest.json配置

{
  "name": "PowerProxy",
  "description": "",
  "manifest_version": 2,
  "version": "1.0.0",
  "permissions": [
    "storage",
    "proxy",
    "background",
    "http://localhost/*"
  ],
  "icons": {
    "32": "32.png",
    "64": "64.png",
    "128": "128.png"
  },
  "browser_action": {
    "default_icon": "32.png",
    "default_title": "PowerProxy"
  },
  "background": {
    "scripts": ["background.js"],
    "persistent": true
  }
}

以上除了名字描述和版本之类基础信息之外,声明以下几项权限:

  • storage ——存储代理配置
  • proxy——配置代理
  • background——处理代理请求
  • http://localhost/* ——域名限制,可改成*://*/*匹配所有域名

代理配置页面

这里我们用一个粗糙的纯HTML举例,创建表单填写代理配置的JSON:

<!-- index.html -->
<html>
  <head></head>
  <body>
    <form id="form" action="#" onsubmit="javascript:save()">
      <textarea name="rules" rows="30"></textarea>
      <p id="buttons">
        <input type="submit" value="Save" />
      </p>
    </form>
    <script type="text/javascript">
      var form = document.getElementById('form')
      var rulesField = document.getElementById('id')
      chrome.storage.local.get(['rules'], ({ rules }) => {
        form.rules.value = JSON.stringify(rules)
      })

      function save(e) {
        chrome.storage.local.set(
          {
            rules: form.rules.value,
          },
          () => null
        )
        return false
      }
    </script>
  </body>
</html>

这里我们利用Chrome的local storage来存储配置,等会background.js可以从中读取配置来进行相应的操作。在这里我存储为文本类型,也可以选择直接存储JS对象类型。

期望的配置JSON格式如下:

[{
  "url": "http://localhost:8000/api/a",
  "proxy": "http://www.example.com"
}]

 并且我们从上到下匹配,即第一条配置的优先级最高。

background.js监听触发代理

创建配置页面之后,我们还需要一个入口可以打开这个页面。这里我利用Chrome的browser action,即右上角地址栏边的小图标作为入口,在background.js监听点击事件打开页面:

chrome.browserAction.onClicked.addListener(() => {
  chrome.tabs.create({
    url: chrome.runtime.getURL('index.html'), 
    active: true 
 	}, 
	() => null
});

然后根据我们的配置JSON调整浏览器代理的设置:

chrome.storage.onChanged.addListener((changes) => {
  const { newValue: rules } = changes.rules;
  var config = {
    mode: "pac_script",
    pacScript: {
      data: `
const rules = ${rules};
function FindProxyForURL(url, host) {
	const rule = rules.find(rule => url.startsWith(rule.url));
	if (rule) {
		return 'PROXY ${rule.proxy}';
	}
	return 'DIRECT';
}
`
    }
  };
  chrome.proxy.settings.set(
    { value: config, scope: 'regular' },
    function() {}
  );
});

使用webRequest接口重定向

webRequest的性能比较差,但它非常灵活,更容易使用。

manifest.json配置

{
  "name": "PowerProxy",
  "description": "",
  "manifest_version": 2,
  "version": "1.0.0",
  "permissions": [
    "storage",
    "webRequest",
    "webRequestBlocking",
    "background",
    "http://localhost/*"
  ],
  "icons": {
    "32": "32.png",
    "64": "64.png",
    "128": "128.png"
  },
  "browser_action": {
    "default_icon": "32.png",
    "default_title": "PowerProxy"
  },
  "background": {
    "scripts": ["background.js"],
    "persistent": true
  }
}

以上除了名字描述和版本之类基础信息之外,声明以下几项权限:

  • storage ——存储代理配置
  • webRequestwebRequestBlocking——配置代理
  • background——处理代理请求
  • http://localhost/* ——域名限制,可改成*://*/*匹配所有域名

代理配置页面

这部分与用proxy接口完全相同。

background.js监听触发代理

background.js中除了处理browserAction之外,我们还需要监听chrome.webRequest.onBeforeRequest事件:

let rules = [];
chrome.storage.local.get(['rules'], (data) => {
  rules = JSON.parse(data.rules) || [];
});
chrome.storage.onChanged.addListener((changes) => {
  const { newValue } = JSON.parse(changes.rules);
  rules = newValue;
});

chrome.webRequest.onBeforeRequest.addListener(
  ({ url }) => {
    const rule = rules.find(rule => url.startsWith(rule.url));
    if (rule) {
      return {
       redirectUrl: url.replace(rule.url, rule.proxy)
      };
    }
  },
  {
    urls: ['<all_urls>'],
    types: ['xmlhttprequest'],
  },
  ['blocking']
)

由于这个事件的处理是同步的,但我们读取storage的操作却是异步的,所以这里做了个额外的操作把代理的配置事先读好放到一个变量中以便后续操作。

小结

一个简单的Chrome扩展完成了。喜欢的话帮我点个【赞】吧!

往期推荐: