头条老大哥耐心询问: 从输入url到页面展示,你都知道点啥?

5,201 阅读6分钟

正文

当你看到这个问题时候,会不会眼含泪水, 对这道题目爱的深沉呢。我背了好久的题目终于被人问到了,但是现实总是这么的残酷,我曾经被问到的时候,大概的场景是这样子的:

面试官: 你能具体的聊聊从你输入url到页面展示发生了哪些事情吗?

面试者: 我对这个块东西还是有比较深刻的理解的。

面试官: 我知道你也说不全,知道啥说啥吧。

面试者: 您怎么知道我说不全呢?

面试者: 首先, 浏览器会对输入的url进行解析

然后, 开始了一场灵魂拷问:

为什么需要对URL进行编码,顺便说一下URI和URL

这里是我们需要对问题进行一下简单的拆分:

URI和URL

首先我们先来下个定义:

URL: RFC1738 统一资源定位符( Uniform Resource Locator)

URN: RFC2141 统一资源名称( Uniform Resource Name)

URI: RFC1630 统一资源标识符 ( Uniform Resource Identifier)

其实从这么官方的语言中,我其实根本不理解都做了什么?我就看了看官方的解释

URL: 俗称网址,是因特网上标准的资源的地址。期望提供可以查找到资源的方法

URN: 期望为资源提供持久的、位置无关的标识方式。并允许简单地将多个命名空间映射到单个URN命名空间

URI: 用于区分资源, 是 URL 和 URN 的超集, 用于取代 URL 和 URN 的概念

640px-URI_Euler_Diagram_no_lone_URIs.svg.png

URI 或者 URL 你可以具体说一下

在这里我会先对URI进行一下解释, 然后会分步去解析每一部分。

Resource 资源
  • Resource 可以是图片,文档,资讯,也可以是不能通过网络访问的实体,还可以是一些抽象的概念。

  • 一个资源可以有多个 URI

Identifier 标识符
  • 标识符 就是用来将当前的资源和其他的资源区分开的名称
Uniform 统一
  • 允许不同的种类的资源在同一上下文中出现
  • 对不同种类的资源标识符可以使用同一种语义进行解读
  • 引入新的标识符时,不会对已有的标识符造成影响
  • 允许同一资源标识符在不同的Internet规模下的上下文中出现

好了,你来说一下 URI 的组成部分。

URI 的组成部分

image.png

scheme: 方案名或协议名, 表示资源应该用哪种方式来访问

://: 在 scheme之后,必须是三个特定的字符://

user:passwd@: 身份信息

host:port: 主机名和端口号

path: 标记资源所在的位置

query: URI的查询参数

fragment: 片段标识符

似乎,有点意思了,但是我可能太详细的也不知道了啊。

你能不能把相关了解到的说一说?

scheme: 最常见的就是http,表示使用HTTP协议。还有https,表示经过加密、安全的HTTPS协议。还有一些不常见的协议ftpnewsfile等。当浏览器看到URIscheme的时候,就会去按照对应的协议进行之后的解析,如果URIscheme都没有,其实就不会后续一系列工作。

://: 这三个字符的作用就是为了把 scheme 和 后面的部分分隔开。可能由于历史原因,这个设计也只能被我们所接受。

user:passwd@: 最初的设计是为了登陆主机时的发送用户名和密码,但是现在已经不推荐这种方式, 因为以明文的方式暴露出来,有严重的安全隐患。

host:port: 主机名可以是IP地址域名,但是必须要有,否则浏览器就会找不到服务器。端口是可以省略的,浏览器等客户端会依据scheme使用默认的端口号, 列如:HTTP默认80HTTPS默认443

path: 这里是采用了类似文件系统目录 路径的方式,原因是在早期的互联网用多的是UNIX系统,所以就采用 UNIX/的风格。

query: 用一个?开始但是不包含?,表示对资源附加的额外的要求。这似乎是一个很形象的比喻,很明显的表达了查询的动作。当然,对于query会有自己的一套格式,是多个 key=value的字符串,然后用&连接。这样做的目的就是为了让浏览器和服务器可以按照这个格式去把查询参数解析成字典或者数组

fragment: 它是一个锚点或者标签用来定位URI的内部资源,浏览器可以在获取资源之后直接跳转到所指向的位置。我个人觉得这个设计很妙,但是呢,fragment仅可以被浏览器所使用,服务端是看不到的。

那就先聊到这里,你还没回到另一个问题呢?下面我们来回答另外一个问题。

为什么要对ULR进行编码呢?

首先,我们需要知道的是在URI中只能用ASCII码,当然可以用其他方式可能会带来有更多的问题,这里我们不做深究。

  • 假设在传输数据的过程中,存在用作分隔符的保留字符怎么办?

举个例子🌰:

https://www.baidu.com/s?wd=?#! 不可以正常解析

? 分隔符; #fragment的分隔符

image.png

https://www.baidu.com/s?wd= 掘 金 可以正常解析

image.png

https://www.baidu.com/s?wd= 掘'> 金 不可以正常解析

image.png

  • 对可能歧义性的数据编码

    • 不在ASCII范围内的编码

    • ASCII 中不可显示的字符

    • URI 中规定的保留字符

    • 不安全的字符() 如空格、引号、尖括号

URI 百分号编码方式
  • pct-encoded = "%" HEXDIG HEXDIG
  • 对于 HEXDIG 十六进制中的字母,大小写等价

其实, URI的转义规则有点简单粗暴,直接把非ASCII码特殊字符转换为十六进制字节值,然后前面还加了一个%.

  • 非 ASCII 码字符(例如中文):建议先 UTF8 编码,再 US-ASCII 编码

  • 对 URI 合法字符,编码与不编码是等价的

image.png - www.baidu.com/s?wd=%55%52…

image.png

下面会对一些比较难理解的部分做一些解析:

ASCII:

保留字符:

reserved = gen-delims / sub-delims 
gen-delims = ":" / "/" / "?" / "#" / "[" / "]" / "@" 
sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="

gen-delims 字符集用来表示URI component之间的分隔符,考虑到component会有不同的subcomponents组成,因此还需要sub-delims来定义subcomponents之间的分隔符。

非保留字符:

unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" 
ALPHA = a-z / A-Z 
DIGIT = 0-9

可能还需要大家去学习一下 ABNF 链接

如何对URI进行编码?

我了解到历史上有三种,解码和编码的方式:

  • escape / unescape

  • encodeURI / decodeURI

  • encodeURIComponent / decodeURIComponent

因为 escape已经在标准中删除所以就不做介绍了。

encodeURI()这里我主要会看一下特殊的情况:

  • encodeURI会替换所有的字符,但不包括以下字符,即使它们具有适当的UTF-8转义序列

image.png

  • encodeURI自身无法产生 能适用于HTTP GET POST 请求的URI
  • 对于XMLHTTPReuests, 因为 "&", "+", 和 "=" 不会被编码,然而在 GET 和 POST 请求中它们是特殊字符。然而 encodeURIComponent 可以。

encodeURIComponent 的特殊情况:

  • encodeURIComponent 转义除了如下所示外的所有字符

不转义的字符:
    A-Z a-z 0-9 - _ . ! ~ * ' ( )
  • 对于 application/x-www-form-urlencoded(POST) 这种数据方式, 空格需要被替换成 '+', 通常使用 encodeURIComponent 的时候还会把 "%20" 替换为 "+"。

大家可以参照下面的代码去实际的体会一下 两者的不同之处:

var set1 = ";,/?:@&=+$";  // 保留字符
var set2 = "-_.!~*'()";   // 不转义字符
var set3 = "#";           // 数字标志
var set4 = "ABC abc 123"; // 字母数字字符和空格

console.log(encodeURI(set1)); // ;,/?:@&=+$
console.log(encodeURI(set2)); // -_.!~*'()
console.log(encodeURI(set3)); // #
console.log(encodeURI(set4)); // ABC%20abc%20123 (the space gets encoded as %20)

console.log(encodeURIComponent(set1)); // %3B%2C%2F%3F%3A%40%26%3D%2B%24
console.log(encodeURIComponent(set2)); // -_.!~*'()
console.log(encodeURIComponent(set3)); // %23
console.log(encodeURIComponent(set4)); // ABC%20abc%20123 (the space gets encoded as %20)

可能这道题刚开始,我都有点想放弃的感觉的了;哈哈,或许这就是知识的魅力吧,之后会陆续更新,有问题我们可以一起交流。

最后还会留几个问题,希望可以和大家一起去寻找答案:

  • 你了解 相对URI 吗?
  • 你了解到 URI 有几种表现形式?

参考链接:

程序员都应该知道的URI,一文帮你全面了解

透视HTTP协议

Web 协议详解与抓包实战

为什么要进行URL编码