前端工程化与质量保障:请求监控

107 阅读12分钟

前端页面的展示和提交的数据都依赖于接口,因此请求的性能和稳定性是前端页面中至关重要的一环。很多人认为接口属于后端服务,应该由后端建立监控系统。实际上,前端作为流量入口,能够更有效地采集接口信息。如果由后端统计请求,就需要在服务端区分页面来源、用户身份等信息,还要记录请求之间的时间差,会增加服务器负担;而前端统计则可以在用户本地进行计算,既不占用服务器资源,又保证了不同用户之间请求的隔离性。

通过建立前端接口监控系统,可以监控并记录所有接口请求的返回状态和结果,当接口报错时,能够及时定位线上问题产生的原因。还可以分析接口的平均耗时、成功率等信息,对应用进行优化,提升系统的质量。

1. 请求采集

请求监控需要对接口请求进行采集,采集的信息一般包括路径、方法、入参、响应、状态码、时间戳、是否取消等,可以根据实际情况定义请求采集的数据结构。

请求监控通常采取请求方法劫持的方式,包括 XMLHttpRequestFetch API拦截,下面详细介绍。

1.1 XMLHttpRequest 拦截器

现代浏览器都支持 XMLHttpRequest API,我们可以实现一个 XhrInterceptor 类对 XMLHttpRequest 进行拦截,从而拦截请求信息。具体的实现方式有很多种,比如重写原型、继承、使用 Proxy 代理等。下面给出简单的模拟实现:

class XhrInterceptor {
    constructor(config) {
        this.config = config;
        // 保留原始的 XMLHttpRequest 对象
        this.xhr = window.XMLHttpRequest;
        this.init();
    }

    init() {
        const originXhr = this.xhr
        const _this = this;
        // 覆盖 XMLHttpRequest 对象
        window.XMLHttpRequest = new Proxy(originXhr, {
            construct: function(target, args) {
                const xhr = new target(...args);
                for(const key in originXhr) {
                    if (typeof originXhr[key] === 'function') {
                        // 复写方法
                        _this.overrideMethod(xhr, key);
                    } else {
                        // 复写属性
                    }
                }
                return xhr;
            }
        });
    }

    overrideMethod(xhr, method) {
        const originXhr = this.xhr
        xhr[method] = (...args) => {
            // 执行方法本体
            const res = originXhr[method].apply(originXhr, args);
            // 采集请求信息
            this.setRecord(method, args);
            // 返回执行结果
            return res;
        }
    }

    setRecord(method, args) {
        // 根据方法收集不同的信息
        // 1. open::收集url、method、query
        // 2. setRequestHeader:收集请求头
        // 3. send:收集body、发送时间戳
        // 4. onreadystatechange:readyState=4表示响应已完成,收集响应头、响应数据、响应时间戳
        // 5. abort:记录取消状态,canceled=true
        
        // 4和5代表请求已完成,此时调用this.config.callback(record)执行采集回调。
    }

    // 取消拦截,恢复原始对象
    unset() {
        window.XMLHttpRequest = this.xhr
    }
}

1.2 Fetch 拦截器

fetch 请求也可以使用 Proxy 代理实现拦截进行请求采集:

const fetchProxy = new Proxy(window.fetch, {
    apply: function (target, thisArg, args) {
        const [url, options] = args;
        console.log(`Fetching: ${url}`, options);
        return target(url, options)
           .then(response => {
                console.log(`Response received from ${url}`, response);
                return response;
            })
           .catch(error => {
                console.error(`Fetch error for ${url}:`, error);
                throw error;
            });
    }
});
window.fetch = fetchProxy;

还有另一种方式,就是强制使用 fetch polyfill,此时 fetch 内部使用的就是 XMLHttpRequest,这样就可以用一套 XhrInterceptor 同时拦截 XMLHttpRequestfetch 请求了。

2. 请求类型

请求监控需要关注的请求类型大致有以下几种:

  • 高并发请求:高并发请求是指一定时间内发送了过多请求,即请求并发数过多。这会导致请求拥塞,影响页面的性能。监控高并发请求可以帮助开发人员发现页面中存在的性能和质量问题,提前进行优化和修复。监控措施很简单,只需将一定时间范围内的请求收集起来,并统计请求数量,当请求数量超过一定阈值时上报。

  • 重复请求:重复请求是指相同的请求在短时间内被多次发送。这种情况可能是由于用户的多次点击、网络抖动导致客户端重试,或者是逻辑错误等。监控重复请求有助于找出异常的客户端行为或系统中的错误逻辑。比如,因业务需要反复调用不同条件的查询导致的请求次数过多,这种情况就需要考虑让后端提供批量查询接口,从而提升页面性能以及降低QPS。监控方法跟高并发请求类似,只需将一定时间范围内的请求收集起来,并统计每个请求的数量,当请求数量超过一定阈值时上报。

  • HTTP状态码异常:HTTP状态码用于标识请求响应的状态,可以将4XX5XX的状态码标记为异常,进行采集上报。

  • 被取消的请求:被取消的请求可分为两类,分别是手动取消自动取消。可以在请求采集时使用canceled 标记被取消的请求,然后进行自定义上报。

    • 手动取消:当执行耗时请求操作时,可以通过手动取消请求来释放资源。XMLHttpRequest 请求可以通过 abort 方法取消,fetch 请求可以通过 AbortController 取消。
    • 自动取消:通常由于请求被挂起时间过长而被触发,即请求超时。这时候请求会被异常结束,导致请求后面的逻辑无法正常执行。
  • 业务异常的请求:业务异常是指请求没有按照预期完成功能流程,包括预期以内的、有处理措施的业务异常,以及非预期的错误的业务异常。可以根据后端接口自定义的业务状态码进行自定义处理。

  • 高延迟请求:即请求的响应时间过长。高延迟会影响用户体验,用户会感觉页面加载缓慢或操作响应迟钝。监控高延迟请求可以通过计算请求发起和响应之间的时间差,当时间差超过设定的阈值时,将其标记为高延迟请求。

3. 爬虫请求

3.1 爬虫技术

互联网诞生后网页数量迅速增加,搜索引擎为了给用户提供全面的搜索结果,需要一种方式来遍历整个网络。爬虫技术应运而生。爬虫从一个已知的网页链接开始,读取该网页的内容,提取其中的文本信息、超链接等关键元素,然后沿着超链接不断发现新的网页。

在互联网生态中,爬虫技术应该在合理合法的范围内获取信息。

  • 爬虫的大量访问可能会对网站服务器造成巨大的负担,造成网站性能下降甚至瘫痪。
  • 网站通常包含各种敏感信息以及受版权保护的信息,如用户数据、未公开的商业策略、内部文档等。

为此,互联网界制定了 Robots 协议 。Robots 协议提供了一个统一的标准和沟通机制,让网站和数据收集者之间能够有明确的规则,搜索引擎等也能够在尊重网站所有者意愿的基础上,更好地为用户提供服务。

Robots 协议通常是位于网站根目录下的一个纯文本的为 “robots.txt”,它包含了一系列规则,告知爬虫哪些页面或目录可以访问,哪些不可以访问。比如:

User-agent: *
crawl-delay: 1
Disallow: /private/
Allow: /public/
  • User-agent:表示该规则适用于哪种爬虫,* 是通配符,表示适用于所有爬虫;
  • crawl-delay:表示允许爬取的时间间隔,单位为秒。用于限制爬虫频率;
  • Disallow:表示禁止访问的目录或页面,这里表示禁止爬虫访问 /private 目录;
  • Allow:表示允许访问的部分,这里表示允许访问 /public 目录。

Robots 协议并不是一个规范,并不能保证所有爬虫都遵守协议。根据爬虫是否遵守Robots 协议,可以分为善意爬虫恶意爬虫

  • 善意爬虫:可以为网站引流,增加曝光率。为了让善意爬虫更好地阅读网站信息,开发人员还会进行SEO优化。
  • 恶意爬虫:可能会对网站信息进行恶意窃取,造成信息泄露,还会增加服务器负担。

因此,网站需要采取各种手段阻止恶意爬虫。

3.2 爬虫识别

开发人员可以通过UA识别不同爬虫的身份信息,但是UA信息是可以伪造的,所以通过UA没法有效识别恶意爬虫,需要通过行为来识别恶意爬虫。

识别恶意爬虫的技术手段有很多,核心思路围绕诱捕行为分析展开。

  • 诱捕:又称为蜜罐技术,它是网络安全中的一种入侵诱饵,目的是引诱恶意方前来攻击,从而收集到相关的证据和信息,用于防御。例如,网站可以添加一个不显示的链接,正常用户不会访问这个链接,而恶意爬虫会扫描到这个链接并进行访问,这样就能捕获到恶意爬虫的身份信息;为了避免误伤善意爬虫,可以把这个链接添加到在 Robots 协议的Disallow中。

  • 行为分析:通过观察访问者在网站上的行为模式来判断是否为恶意爬虫,包括对访问频率、访问路径、停留时间、请求资源类型等多个方面的分析。例如,某个访问者在10s内快速访问了100个商品详情页,这显然不符合正常用户的访问规律,大概率是恶意爬虫。又或者某个访问者集中访问一些敏感信息页面、隐藏的网站配置文件,这样的异常行为也可判定为恶意爬虫。

常见的爬虫识别技术有:

  • 特征识别技术:通过设备指纹来区分真实用户和机器流量。设备指纹是指通过收集设备的各种特征信息来唯一标识该设备的技术,包括设备的操作系统、浏览器类型及版本等多维度的终端环境信息,这些信息组合起来就构成了一个区别于其他设备的指纹,具有较高的准确性。不过,设备指纹的收集也可能会涉及用户隐私问题。

  • 基于 IP 识别:建立 IP 黑名单是一种常见的方法。如果发现某个 IP 地址曾经被用于恶意爬虫活动,就将其加入黑名单。同时,也可以建立白名单,只允许来自白名单中的 IP 地址访问敏感区域。值得注意的是,使用代理服务器等技术可能会干扰 IP 地理位置分析的准确性。

  • 验证码和人机验证技术:通过要求用户(包括爬虫)输入验证码或进行人机验证操作来区分人类和机器。这是利用了人类的行为习惯和视觉感知能力,恶意爬虫很难模拟这些复杂的行为来通过验证。

  • 基于 AI 的行为分析:利用AI技术分析批量业务请求和真实用户形成的会话数据,判断访问数据是否符合正常访问特征。

3.3 爬虫防御

恶意爬虫会导致两个恶劣影响,即网站核心内容泄漏,以及服务器负担增加。因此,需要对恶意爬虫进行有效防御,保障网站的安全和稳定。本节介绍4种恶意爬虫防御措施。

首先,可以从请求的层面进行恶意爬虫防御:

  • 请求限流:以真实用户的操作频率为标准,为请求设定一个阈值,规定单个用户在指定时间范围内能被响应的请求数量,超出阈值的请求就不响应。它可以降低恶意爬虫的抓取频率,降低服务器的负载量。
  • 拒绝访问:将利用蜜罐技术和行为分析捕获的恶意爬虫的IP地址加入黑名单,禁止其访问网站。它直接杜绝了恶意爬虫抓取内容的可能性。

从请求层面对恶意爬虫进行防御有可能误伤真实用户,所以部分网站选择了下面的手段来防御恶意爬虫:

  • 信息加密:对网页上展示的关键内容进行加密,避免关键信息泄露。加密的内容能够在页面上正常显示,不影响正常用户浏览,但是恶意爬虫爬取时,扫描到的是加密后的字符串。它的原理是利用CSS中的font-family进行字体自定义,将普通字符(如英文字母、数字、标点符号等)映射为特殊的编码。页面上能被扫描到的字符串的是特殊编码,经过自定义字体映射后用户看到的是正常展示的字符。这种方式需要借助自定义字体,有一定的维护成本和页面性能负担。

  • 校验拦截:在访问页面内容前加一层校验,比如限制用户必须处于登录状态、请求必须携带自定义报头用于校验等。这种方式应用范围最广,如果限制用户必须处于登录状态,那么针对异常访问的账户,只需对账户进行封杀,比封杀 IP 地址更加准确有效。

恶意爬虫的手段层出不穷,防御措施也五花八门,两边都在不断变化迭代。开发人员需要根据实际情况灵活调整防御措施。此外,很多云服务提供商也提供了防爬虫的增值服务。