记录--让URL地址都变成了"ooooooooo"

Posted 林恒

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了记录--让URL地址都变成了"ooooooooo"相关的知识,希望对你有一定的参考价值。

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

发现一个很有创意的小工具网站,如封面图所示功能很简单,就是将一个URL地址转换为都是 ooooooooo 的样子,通过转换后的地址访问可以转换回到原始地址,简单流程如下图所示。转换的逻辑有点像短链平台一样,只不过这个是将你的URL地址变的很长长长长,但是看着都是 ooooooooo,很好奇是如何实现的,所以查阅了源码,本文解读其核心实现逻辑,很有趣且巧妙的实现了这个功能。

前置知识点

在正式开始前,先了解一些需要学习的知识点。因为涉及到两个地址其实也就是字符串之间的转换,会用到一些编码和解码的能力。

将字符转为utf8数组,转换后的每个字符都有一个特定的唯一数值,比如 http 转换后的 utf8 格式数组即是 [104, 116, 116, 112]

    toUTF8Array(str) 
        var utf8 = [];
        for (var i = 0; i < str.length; i++) 
            var charcode = str.charCodeAt(i);
            if (charcode < 0x80) utf8.push(charcode);
            else if (charcode < 0x800) 
                utf8.push(0xc0 | (charcode >> 6),
                    0x80 | (charcode & 0x3f));
            
            else if (charcode < 0xd800 || charcode >= 0xe000) 
                utf8.push(0xe0 | (charcode >> 12),
                    0x80 | ((charcode >> 6) & 0x3f),
                    0x80 | (charcode & 0x3f));
            
            else 
                i++;
                charcode = ((charcode & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff)
                utf8.push(0xf0 | (charcode >> 18),
                    0x80 | ((charcode >> 12) & 0x3f),
                    0x80 | ((charcode >> 6) & 0x3f),
                    0x80 | (charcode & 0x3f));
            
        
        console.log(utf8, \'utf8\');
        return utf8;
    

上面是编码,对应下面的则是解码,将utf8数组转换为字符串,比如 [99, 111, 109] 转换后的 utf8 格式数组即是 com

    Utf8ArrayToStr(array) 
        var out, i, len, c;
        var char2, char3;

        out = "";
        len = array.length;
        i = 0;
        while (i < len) 
            c = array[i++];
            switch (c >> 4) 
                case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
                    // 0xxxxxxx
                    out += String.fromCharCode(c);
                    break;
                case 12: case 13:
                    // 110x xxxx   10xx xxxx
                    char2 = array[i++];
                    out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
                    break;
                case 14:
                    // 1110 xxxx  10xx xxxx  10xx xxxx
                    char2 = array[i++];
                    char3 = array[i++];
                    out += String.fromCharCode(((c & 0x0F) << 12) |
                        ((char2 & 0x3F) << 6) |
                        ((char3 & 0x3F) << 0));
                    break;
            
        

        return out;
    

将 Number 对象以 4 进制的形式表示为字符串,toString 用的比较多,但是里面传入参数的场景比较少,这个参数 radix 是一个可选的参数,用于指定转换的进制数,范围为 2 ~ 36,如果未传入该参数,则默认使用 10 进制。

n.toString(4)

在字符串左侧填充指定字符,直到字符串达到指定长度。基本语法为 str.padStart(targetLength [, padString])

  • targetLength:必需,指定期望字符串的最小长度,如果当前字符串小于这个长度,则会在左侧使用 padString 进行填充,直到字符串达到指定长度。
  • padString:可选,指定用于填充字符串的字符,默认为 " "(空格)。
str.padStart(4, \'0\')

URL 编码/解码

下面正式开始URL编码的逻辑,核心的逻辑如下:

  • 转换为utf8数组
  • 转换为4进制并左侧补0到4位数
  • 分割转换为字符串数组
  • 映射到o的不同形式
  • 再次拼接为字符串,即转换完成后的URL
// 获取utf8数组
let unversioned = this.toUTF8Array(url)
    // 转换为base 4字符串
    // padstart非常重要!否则会丢失前导0
    .map(n => n.toString(4).padStart(4, "0"))
    // 转换为字符数组
    .join("").split("")
    // 映射到o的不同形式
    .map(x => this.enc[parseInt(x)])
    // 连接成单个字符串
    .join("")
上面有两个关键点解释一下,首先映射到o的不同形式这个是什么意思呢?其实转换后的o并不是一种“o”,而是4种,只不过我们肉眼看到的效果很像,通过 encodeURI 转换后的字符可以看出来。
encodeURI(\'o-ο-о-ᴏ\')
// o-%CE%BF-%D0%BE-%E1%B4%8F

这里其实也解释了为什么上面为什么是转换为4进制和左侧补0到四位数。因为上面代码定义的 this.enc 如下,因为总共只有四种“o”,4进制只会产生0,1,2,3,这样就可以将转换后的utf8字符一一对应上这几种特殊的“o”。

enc = ["o", "ο", "о", "ᴏ"] 

最后的效果举例转换 http 这个字符:

  • 转换为utf8数组:[ 104, 116, 116, 112 ]
  • 转换为4进制并左侧补0到4位数:[\'1220\', \'1310\', \'1310\', \'1300\']
  • 分割转换为字符串数组:[\'1\', \'2\', \'2\', \'0\', \'1\', \'3\', \'1\', \'0\', \'1\', \'3\', \'1\', \'0\', \'1\', \'3\', \'0\', \'0\']
  • 映射到o的不同形式:[ \'ο\', \'о\', \'о\', \'o\', \'ο\', \'ᴏ\', \'ο\', \'o\', \'ο\', \'ᴏ\', \'ο\', \'o\', \'ο\', \'ᴏ\', \'o\', \'o\' ]
  • 再次拼接为字符串,即转换完成后的URL:οооoοᴏοoοᴏοoοᴏoo

到此整个转换编码的过程就结束了,看完后是不是觉得设计的很不错,编码完后就是解码,解码就是将上面的过程倒序来一遍,恢复到最原始的URL地址。这里要注意一点的是每次解析4个字符且parseInt以4进制的方式进行解析。

// 获取url的base 4字符串表示
let b4str = ooo.split("").map(x => this.dec[x]).join("")

let utf8arr = []
// 每次解析4个字符
// 记住添加前导0的填充
for (let i = 0; i < b4str.length; i += 4)
    utf8arr.push(parseInt(b4str.substring(i, i + 4), 4))
// 返回解码后的字符串
return this.Utf8ArrayToStr(utf8arr) 

到此就核心实现代码就分享结束了,看完是不是感觉并没有很复杂,基于此设计或许可以延伸出其他的字符效果,有兴趣的也可以试试看。将转码后的地址分享给你的朋友们一定会带来不一样的惊喜。

下面是我转换的一个AI小工具地址,点击看看效果吧~

ooooooooooooooooooooooo.ooo/ooooοооoοᴏο…

本文转载于:

https://juejin.cn/post/7225573912670191677

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

你的域名是如何变成 IP 地址的?

可能大家都知道或者被问过一个问题,那就是很经典的「从浏览器输入 URL 再到页面展示,都发生了什么」。这个问题虽然简单,但是真的能够从回答的各种细节上看出不同人之间的水平差距。

这篇文章主要是聊一聊输入 URL 之后的第一步——域名解析

域名就类似于 www.google.com,而通过 ping 命令,就可以查询到对应域名的 IP 地址了。

那为什么又要有域名,又要有 IP 呢?

域名、IP 共存

首先还是解释一下,为什么会出现现在这种域名、IP 地址共存的情况。主要有两个点:

  1. 提升用户体验
  2. 提高运行效率

分别解释一下,IP 地址长度为 32 位,平时用十进制来表示的话,就长这样——192.168.1.0 ,但是想象一下,如果我们要访问某个网站需要让我们输入这么一长串数字,体验肯定相当差,首先记忆这么长串数字对很多人来说就很痛苦,更何况我们常用的网站肯定不止一个。

除此之外,如果你给其他人推广你的网站,你吧啦吧啦说了一大堆,然后来个「如果你感兴趣,请访问我们的网站 192.168.1.0」,然后就没有然后了。

这也是为啥现在仍然在使用域名,方便人脑去记忆。

那为啥还需要 IP 地址呢?因为 IPv4 中的 IP 地址只需要 4 个字节,而用字符串表示的域名最少也需要几十个字节,长的甚至达到几百字节,而这会大大的增加底层路由器的负担。

这也是为啥 IP 地址仍然在被使用。人来使用域名,而路由器层则使用 IP 地址,就跟我们书写的是我们能认识的字符,而最终计算机认识的是一堆二进制一样。

DNS 解析

知道了这个背景之后,我们就可以来看看「域名」是如果变成「IP 地址」的。

首先我们知道,会往 DNS 服务器发送请求,那问题就来了,浏览器怎么知道 DNS 服务器的地址是啥?

答案是提前配置好的。当然这不是唯一的方式,DNS 也有可能通过 DHCP(Dynamic Host Configuration Protocol) 动态分配的。

例如,MacOS 中的 DNS 配置就长下面这样。

当然,你也可以通过命令行来查看、修改,地址在 /etc/resolv.conf

有了 DNS 服务器,那么你可能会觉得,接下来的事情就很简单了:

我给你传个域名,你返给我对应的 IP 地址即可。那问题来了,现在互联网中有数万台的 DNS 服务器,我怎么知道数据在哪台服务器上?难道要一台一台的遍历请求这数万台服务器吗?

我相信你肯定没有感知到在浏览器中输入域名到页面展示会花费那么久,这也说明肯定不是一台一台服务器进行遍历的。

域名的组成

要了解 DNS 是如何对其进行优化的,我们需要先知道域名的组成部分。看到这,很可能你会这么想:

实际上,域名是有由不同的组成的,每个 . 隔开的部分就是一个

这里举个例子,假设我们分析的域名为 www.google.com ,从我们平时写快递的收货地址的惯性思维来看,这个域的各个部分大小可能是这样的:

但是实际上并不是这样,而是:

你甚至发现,最大的还是个 . 。其实完整的域名应该是 www.google.com.. 代表根域,因为根域对于所有的域名来说,意义都一样,所以平时我们都把最后的点给省略了。

每个域都有自己的专属名词:

当然,我们知道还可以针对二级域名再划分子域名,类似于 mail.google.com

所以看到这,你应该能够理解域名是由层次的这个概念了,我再举个比较的通俗的例子。

DNS 的分层

了解完域名的分层之后,DNS 是如何优化域名解析的问题就迎刃而解了,那就是——分层。

DNS 服务器会将域名的数据分布式的存储在各个 DNS 服务器上,但是同一个域的数据,会存储在同一台 DNS 服务器上,同一台 DNS 服务器可以存储多个域的数据。

这么说可能会有些抽象,一图胜千言,其实就是这样:

有了对数据的分层,那么查询数据就会很有节奏感

查询域名数据

一图胜千言,有了分层的机制,整个的查询过程就会长这样:

首先会去配置的 DNS 服务器中查询,这个其实一般都是本地或者内网中的 DNS 服务器。如果没有找到,就会去问根域要,说哥们,我这里需要 www.google.com 的 IP 地址。

根域一看,我这里没有啊,但是我知道 com 域的 DNS 服务器地址,他可能知道。

然后 com 域的 DNS 服务器一看,www.google.com 的 IP 地址我也不知道,但是我知道 google.com 域的 DNS 服务器的地址,他可能知道,你再去问问他。

就这样一路问下去,最终就能够找到 www.google.com 所对应的 IP 地址了。

根域名服务器

看了上面的流程,可能你还是会有点疑问。因为去找 DNS 服务器查询 IP 地址时,初始的 DNS 的服务器的 IP 地址是走的本地计算机的配置的。那在分层查询时,我怎么知道有哪些根服务器?以及我怎么知道这些根服务器的 IP 地址是啥?

答案是内置

我们的设备,或者说所有能上网的设备都会内置根服务器的列表。总共有 13 台根 DNS 服务器,分别是[a-m].root-servers.net ,这些根服务器的地址根本不需要查询就能直接获取。

当然,稍微想想也知道,13 台服务器是很难扛住全球互联网用户的请求的,实际上对于这 13 台服务器有很多的镜像服务器

眼见为实

说了这么多虚的概念,接下来我们通过 dig 命令来实际操作一下。

可以看到,在 QUESTION SECTION 下的完整域名是 www.google.com. 是带了根域的,那后面的这个 INA 又是啥意思呢?

这是因为,在向 DNS 服务器查询请求的时候,需要三个参数,分别是:

  1. 域名(例如 www.google.com)
  2. 网络类型(Class 设计之初,考虑到了多种网络并存的场景,但是目前实际上只有一种网络——互联网,所以该参数的值一直都会为 —— IN
  3. 类型(例如 A 表示 IP 地址,而 MX 则表示邮件服务器的地址)

而在 ANSWER SECTION 中,则是 DNS 服务的响应结果,上图中显示了总有 6 条 DNS 记录,并且在后面返回了其对应的 IP 地址。

而其中的 69 则是 TTL,单位是秒,代表了在 69 之内都不用再次发送请求了。

最下面则是统计的信息,本次 DNS 查询所话费的时间,以及请求的 DNS 服务器的地址和端口。这个服务器地址是我们本机配置的 DNS 服务器的地址。

眼尖的可能发现了,上图中根本没有设计到对根服务器的请求。这是因为这个命令把这部分给省略掉了,我们可能通过加上 +trace 命令行参数来查看详细的分级查询过程

这次我们以 www.36kr.com 来作为例子。

可以看到,上图中列出了所有的根域名服务器,然后去找 com 域要,然后再找 36kr.com 域去要,最终是拿到了 www.36kr.com 的 IP 地址。

缓存机制

当然,如果每次都从根服务器开始往下找,明显是不合理的,因为域名和 IP 地址的对应关系本来变动的就不频繁,所以 DNS 服务器是都会将结果缓存的。

并且,在下图中:

我只写了一个 DNS 服务器中有同级别的域信息,但是实际上不同层级的域信息可能存在于同一个 DNS 服务器,举个例子,com 域和 google.com 域可能在同一台机器上。

但是,这个缓存是有有效期的。如果在这个有效期内 DNS 数据发生了变化,那么缓存中的数据就会不正确,此时需要手动的将 DNS 删除。

以上是关于记录--让URL地址都变成了"ooooooooo"的主要内容,如果未能解决你的问题,请参考以下文章

你的域名是如何变成 IP 地址的?

你的域名是如何变成 IP 地址的?

记录生活,活的更多

Exacl怎么快递将地址变成省市区?

如何在javascript中让一个链接变成灰色,不可点击?

JAVA如何将本地图片变成网络URL 再把URL传给前台