是一个很迷惑的点,所以查证后记录于此
URI/URL
URI(Uniform Resource Identifier),中文名称是统一资源标识符,使用它就能够唯一地标记互联网上资源。
URI 另一个更常用的表现形式是 URL(Uniform Resource Locator), 统一资源定位符,也就是我们俗称的“网址”,它实际上是 URI 的一个子集,不过因为这两者几乎是相同的,差异不大,所以通常不会做严格的区分。
但需要注意的是,URI 不仅能够标记万维网的资源,也可以标记其他的,如邮件系统、本地文件系统等任意资源。而“资源”既可以是存在磁盘上的静态文本、页面数据,也可以是由 Java、PHP 提供的动态服务。
例如:windows系统中,你将一个图片拖入浏览器打开,也会得到这个图片的URI。
file:///C:/Users/%E7%94%98%E8%B6%85/Desktop/working/h5/img/bg.png
一个websocket协议的URI
ws://newtest/zhouheiya.cn:1010/
URI = scheme + :// + user:passwd@ + host:port + path + query + #fragment
URI = 协议 + :// + 身份信息 + 主机名:端口号 + 资源路径 + 查询参数 + 片段标识符
具有安全隐患的user:passwd@
主机名之前的身份信息“user:passwd@”,表示登录主机时的用户名和密码,但现在已经不推荐使用这种形式了(RFC7230),因为它把敏感信息以明文形式暴露出来,存在严重的安全隐患。
fragment
查询参数后的片段标识符“#fragment”,它是 URI 所定位的资源内部的一个“锚点”或者说是“标签”,浏览器可以在获取资源后直接跳转到它指示的位置。
但片段标识符仅能由浏览器这样的客户端使用,服务器是看不到的。原因是,浏览器永远不会把带“#fragment”的 URI 发送给服务器,服务器也永远不会用这种方式去处理资源的片段。
例如: 我们在浏览器的地址栏输入:https://www.baidu.com/?wd=1#test
打开浏览器的devtools查看发出的第一个请求的Referer:https://www.baidu.com/?wd=1 ,#test会被完全忽略掉。
是不是联想到了什么?对vue熟悉的都知道vue-router默认使用的是hash路由:localhost:8080/#/xxx
fragment实现的单页面路由
JavaScript 提供了 location.hash
来操作当前 URI 的 fragment,同时提供了 Hashchange
事件监听 fragment 的变化。
修改 location.hash 值,触发 hashchange
事件,JS 处理对应的逻辑,改变页面 UI 实现页面的跳转,并在浏览器中产生历史记录。
比如地址栏: https://blog.csdn.net/#/testHash
location.hash = “#/testHash”
通过caniuse的查询,hashchange event
目前的兼容性表现较好,主流浏览器都支持。
实现单页面路由还会使用 H5 为 history 新增的两个 API: History.pushState() , Histroy. replaceState ()
。
具体代码实现可以参考 vue-router hash 模式下的实现 https://github.com/vuejs/vue-router/blob/dev/src/history/hash.js。
Vue的默认路由方式是hash,如何修改?
1.实例化规则为“history”模式
const router = new VueRouter({
mode: ‘history’,
routes // (缩写)相当于 routes: routes
})2.nginx 相应的配置
location / {
try_files uriuriuri/ /index.html;
}https://router.vuejs.org/zh/guide/essentials/history-mode.html中介绍了多种后端配置的例子,根据文档配置就好了
然而, fragment 会被 Google 搜索引擎忽略掉,因此对于用 hash 模式前端路由的应用的 SEO 来说是很不友好的。不过 Google 给了一个方案,就是在 #
紧跟一个 !
,这样Google 搜索引擎就会将这个 URI 进行转换,如 https://domain/index.html#!L18
转换后就成为了 https://domain/index.html?_escaped_fragment_=L18
。这样搜索引擎就会携带上 URI’s fragment 直接去访问这个 URI,开发者可以利用这个 trick 优化网站的 SEO。
fragment实现的HTML锚点
在 HTML 中比较常见的一个应用 —— 页面内定位。
在页面中通过设置标签的 id 属性来定义锚点,从而实现锚点定位。实际上锚点定位的实现正是依赖了fragment 的特性 3。如这个 URI https://domain/index.html#L18
,假设返回的文件类型是 text/html,则浏览器会读取 URI’s fragment,然后在页面中寻找 L18 这个锚点,并将页面滚动到该锚点的位置。
因此我们当点击 <a href="#top">top</a>
时,实际上处理过程是 URI 的 hash 发生变化,然后浏览器读取新的 fragment,并寻找 DOM 中是否存在对应的锚点,将该锚点显示到页面中。
在 MIME Type 为 HTML 或 XML 时,如https://domain/index.html#
这个 URI 中是空的 fragment,则浏览器默认显示页面的最顶端。
这意味着我们可以通过一个<a href="#">返回顶部</a>
来简洁实现回到顶部的功能。
URI 对应的资源类型不同,浏览器对该 URI’s fragment 的处理方式就不一样,具体不同类型的处理方式可以参阅:[https://en.wikipedia.org/wiki/Fragment_identifier#Examples] 。
URL = scheme + :// + host + port +path + filename + query + #fragment
http:
1 | http://www.aspxfans.com:8080/news/index.asp?boardID=5&ID=24618&page=1#name |
其中端口,路径,文件名,传参不是必须部分,如下:
1 | https://www.baidu.com |
我们不必刻意去区分 URI / URL / URN 。在 [RFC3986]上已经明确说明这个点:
Future specifications and related documentation should
use the general term “URI” rather than the more restrictive terms
“URL” and “URN”.
服务端与客户端的不同
我们在浏览器地址栏输入www.baidu.com,在dev工具中看到request header中,有一个view source
,点开后看到的就是发给服务端。仔细对比一下,会发现,协议名和主机名都不见了,只剩下了后面的部分。
这是因为协议名和主机名已经分别出现在了请求行的版本号和请求头的 Host 字段里,没有必要再重复。
我们可以断定的是,客户端和服务器看到的 URI 是不一样的。客户端看到的必须是完整的 URI,使用特定的协议去连接特定的主机,而服务器看到的只是报文请求行里被删除了协议名和主机名的 URI。
在web服务器中,例如配置nginx,它的 location、rewrite 等指令操作的 URI 其实指的是真正 URI 里的 path 和后续的部分,省略掉了path之前的协议名和主机名。
URI的编码
在日常的开发中,我们经常会遇到如下的场景:
你找同事要一份资料,同事给你了一个网址:https://www.baidu.com/s?wd=%E4%BA%94%E6%9C%88%E5%A4%A9%E6%BC%94%E5%94%B1%E4%BC%9A&rsv_spt=1。
你可能会马上懵一下,wd=%E4%BA%94%E6%9C%88%E5%A4%A9%E6%BC%94%E5%94%B1%E4%BC%9A。
这是什么鬼东西?你问同事,同事给你截图表明浏览器里面的是正常的中文。
浏览器掩盖了URI“丑陋”的一面,这个丑陋其实涉及编码。
URI引入的编码机制是:ASCII 码 + ASCII 码以外的字符集和特殊字符做转义。
URI 转义的规则有点“简单粗暴”,直接把非 ASCII 码或特殊字符转换成十六进制字节值,然后前面再加上一个“%”。例如,空格被转义成“%20”,“?”被转义成“%3F”。而中文、日文等则通常使用 UTF-8 编码后再转义,例如“银河”会被转义成“%E9%93%B6%E6%B2%B3”。
现在多用:encodeURI
其原理是把字符(unicode)编码成utf-8,utf-8是用1-4个字节表示的,所以每个字节转换成16进制并在前面用百分号(%)连接,最后并把每个字节转换的结果连接起来。