06月12, 2020

解决网页中调用客户端提供的 HTTP 服务报错问题

网页中有时候需要一些特殊能力,但网页本身又没有提供。如果公司旗下有覆盖率很高的客户端,那就可以利用该端提供一些特殊的能力供网页使用。如果覆盖率很高的客户端就是浏览器,那只要注入一些接口就可以了(如:360浏览器给一些域名下注入了特殊的功能)。如果是是其他的客户端,要想提供能力给网页调用的话,只能走 HTTP 协议了。

比如 QQ 的网页登录就借助了 QQ 客户端实现快速登录(DEMO)。具体方案为:QQ 客户端在本机提供了一个 HTTP 服务(域名解析到 127.0.0.1),网页通过 jsonp 的方式调用该接口获取对应的信息实现快速登录。

端口的问题

由于客户端在本机创建 HTTP 服务时在后台偷偷进行的,所以无法锁定一个固定的端口(因为改端口有可能被其他地方实用),但也不能是一个随机的端口(这样网页中就不知道怎么调用了)。所以一般会选择多个端口,客户端创建的时候会依次测试这些端口,网页中调用的时候也会依次测试调用能否成功。比如 QQ 登录会利用下面5个端口:

没有该客户端导致控制台下报错的问题

如果当前环境已经安装了该客户端,那么本机下就会有该 HTTP 服务,网页中调用的时候也就不会报错。但是若当前环境没有安装该客户端(网页中并不知道),那么调用的时候就会出现报错,并在控制台下体现出来,如同上图所示。

有这个错误一方面看着很不爽,另一方面用户发现后有不安全感(以为会收集一些用户隐私)。那怎么解决这个问题呢?下面列举 2 个方案

客户端劫持

现在的做法是把域名解析到 127.0.0.1,改变的做法是域名正常解析,客户端对这个域名请求进行劫持到本机,然后返回对应的数据。如果没有安装该客户端,就会请求远程的服务,这样并不会报错。

隐藏控制台下的报错

请求不存在的 HTTP 服务后,会报 net::ERR_CONNECTION_REFUSED 错误,并在控制台下体现出来。视图改写 console.error 方法并不能隐藏该错误。通过研究发现,将 HTTP 请求放在 Worker 中进行不会报这个错误,如:

const str = `
    fetch('http://localhost:8360/?callback=werwe&t=1591759622832').then(data => {
        console.log(data)
    }).catch(err => {
        console.log(err, 'error')
    })`;
const blob = new Blob([str], {type: 'text/javascript'});
const url = URL.createObjectURL(blob);
const worker = new Worker(url);

通过这个方式后,会发现在控制台下并没有 net::ERR_CONNECTION_REFUSED 的错误。当然这个方案也有一些缺点:

  • 由于是通过 fetch 请求的,所以要把 jsonp 接口改为 ajax 接口,同时要设置 CORS 解决跨域的问题;
  • 这个方案只能在 Chrome 下有效,Firefox/Safari 等浏览器还是有错误。不知道是不是 Chrome 的 bug,如果是 bug 的话,不排除后续修复的可能;
  • Worker 下请求丢失了 Referrer,本机 HTTP 服务不能做 Referrer 控制了,可能会有一些安全风险;
  • 虽然控制台下没有错误信息,但 network 面板里还是会看到错误的 HTTP 请求。

策略优化

除了想办法不显示错误信息,也可以通过一些策略优化,减少调用次数,降低显示错误的频率。如:

  • 如果某个系统下没有该客户端,那么可以完全不调用;
  • 成功/失败后将结果缓存起来,一段时间内不请求。

本文链接:http://www.welefen.com/post/how-to-resolve-http-service-error-created-by-app.html

-- EOF --

Comments