源码解析-url状态检测神器ping-url

5,368

前言

ping-url是我最近开源的一个小工具,这篇文章也是专门写它设计理念的科普文。
为什么会做这个ping-url开源工具呢?
起因是:本小哥在某天接到一个特殊的需求,要用前端的方式判断任意一个url,是否可以正常访问
这么简短的需求,通常背后都有个大坑:)

先捊下思路,要实现这个功能,必须具备以下2点:

  • 正常发起url请求
  • 监听请求状态

有了思路,就开始撸起袖子加油干!

一、判断url是否可访问

由于浏览器的安全机制——同源策略的存在,要实现任意这个要求确实有点难。

同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的重要安全机制。

所以,为了实现任意url可以正常访问,第一个要解决的问题就是:跨域

1.1 解决同源限制

前端老鸟很快就会联想到JSONP。其原理其实是利用script可跨源访问的特性。
依据这个,可以做个拓展,找出所有可跨源访问的html标签:

  • <script>
  • <img>
  • <link>
  • <video>
  • <audio>
  • <audio>
  • <iframe>

1.2 如何判断访问性

从可跨源访问的html标签中,筛选出能支持onerroronload事件标签,则可以依靠标签很好地完成功能的开发。
这里说明下:

  • onerror事件的作用
    如果跨源标签请求的资源,和本身能解析的文件格式不一样,就会报error事件。
    而要检测的url,通常都是html
    所以onerror事件可以用于监听发起请求,到接收到反馈error所花费的时间。这样,就可以直接算出网络访问的延时。
    但是,很遗憾,准确率并不是100%。
    因为有一种情况是:url本身就是死链。
    用死链发起http请求后,会得到failed的状态。这种情况下onerror也是会触发的。

  • 为什么需要onload事件?
    onload事件的触发时机是资源已下载完成。
    只要触发这个事件,则证明url不是死链。
    这样,就可以帮onerror排除意外情况,让准确率达到100%!

基于以上两点硬性要求,对标签进行过滤后如下:

  • <script>
  • <img>
  • <link>

其中<iframe>的硬伤是:只要服务器设置X-Frame-Options消息头,就直接废了。所以也被排除掉。

X-Frame-Options是一个HTTP标头(header),用来告诉浏览器这个网页是否可以放在iFrame内。

1.3 存在的安全隐患

<script>虽然可以满足需求,但是有一个很致命的问题:存在被XSS攻击的可能。
如果url对应的资源是可自执行的js函数,则完全有可能被利用干坏事。
<img>标签因为只能触发onerror,所以也被排除。

1.4 解析代码

最后只有<link>标签可以使用。
由于解析方式是CSS,所以不存在攻击的可能性。
以下是实现代码:

function getStatus(url: string){
    return new Promise((resolve, reject) => {
        let link = document.createElement('link');
        link.rel="stylesheet";
        link.type="text/css"
        link.href = url;

        link.onload = function(){
            resolve(true);
        }
        link.onerror = function(){
            resolve(false);
        }

        document.body.appendChild(link);
    })
}
  • 通过生成<link>节点,并加入资源地址url
  • 添加监听事件onloadonerror
  • 加入body中,发起请求

需要注意的是,一定要声明reltype,否则是触发不了绑定的事件的。

二、计算网络延时

由于CSS的跨域需要一个设置正确的Content-Type 消息头,所以还是存在很小概率的风险。
因此,计算网络延时这块,ping-url还是用最保守的<img>

2.1 解析代码

function getLoadTime(url: string){
    return new Promise(resolve => {
        let img = document.createElement('img');
        img.style.display = "none";
        img.src=`${url}/?v=${Math.random()}`;
        const timeStart = new Date();

        img.onerror = function(){
            const timeEnd = new Date();
            resolve(timeEnd.getTime() - timeStart.getTime());
        }
        img.onload = function(){
            const timeEnd = new Date();
            resolve(timeEnd.getTime() - timeStart.getTime());
        }

        document.body.appendChild(img);
    });
}
  • 生成<img>节点,加入资源请求url,并将样式设置为display:none,避免对页面产生影响
  • 记录开始时间timeStart
  • 加入监听事件onerroronload
  • 加入body中,发起请求
  • 事件触发后,计算延迟时间

这里有个小细节,url后要加上随机数v=***。这样可以避免缓存的情况。

三、开源共享

虽然这只是个小项目,但是挺实用的。所以利用空闲时间,将其封装成npm包,发布到npmjs.com上。
源码也同步到GitHub上,可以点击访问ping-url
如果对你有帮助的话,可以打赏个Star。

参考

[1] 华佗诊断分析系统
[2] 详解script标签
[3] 不要再问我跨域的问题了
[4] <link>:外部资源链接元素
[5] 跨源网络访问


喜欢我文章的朋友,可以通过以下方式关注我: