1、URL构成
平时我们大多数见到的一个URL(Uniform Resource Locator,统一资源定位器)都是这个样子,大体由协议(Protocol)、主机名(Hostname)、域名(Domain Name)、端口(Port)、路由(Path/Router)、参数(Query/Parameter)组成。接下来对各个部分进行简单的介绍。
1.1、Protocol
这里指的是上图中的https://,我们现如今见的比较多的协议ftp:// 、ssh:// 、http// 、thunder:// 。这些协议对数据、报文的打包、拆包过程规则做了确立和解释 。如ftp则表示通过这个协议,可以访问服务器的文件传输服务器,ssh可以连接服务器的22端口建立远程的登录链接,访问和使用服务器,而http则默认访问80端口使用服务器的web服务。thunder则是迅雷对http协议的封装,访问该链接进行下载文件。
总之,协议头标识了访问具体资源的规则。当然,也可以设计一个协议,例如 abc://,这叫作自定义协议。
1.2、Hostname
hostname 称为主机名,在本例的 URL 中,www就是该域名的主机名,平时我们也见过例如mp3.baidu.com、news.baidu.com。这些不一样的部分也都是百度域名下的主机名,不通过的主机名对应着不同的web服务,也就意味着访问具有不同的主机名的域名进入不同的页面。在这里,区别于网上大多数贴中所说将主机名与域名混为一谈,重点强调主机名与域名不是一个东西,更与一级域名二级域名毫无关系。
1.3、Domain Name
首先,域名是IP的补充,IP是一个由四组数字以点区隔的字符串,例如192.168.1.1。域名的出现时为了解决IP地址不便于记忆的问题,域名相对IP地址更便于记忆,并且域名与IP地址有相互映射的关系,又因为映射关系复杂产生出了默认映射,唯一映射,高低域名服务器的映射优先级等等,这些不在本文的讨论范围中,不详细阐述。
域名又根据不同域名服务器产生分级服务器,例如com是一级域名,而com.cn是基于cn的二级域名,那么可根据该理论推出如果有个域名叫做baidu.com.cn则可称为该域名是一个基于cn的三级域名,这也就验证了上述网上大多数介绍将主机名和域名混为一谈的原因了,因为按此逻辑推下去,www.baidu.com 就是基于com的三级域名。从严格意义上来讲,主机名对应的是一台服务器,而域名则是这一系列服务器的出处IP的映射。
1.4、Port
在上图中,并没有出现端口的显示,而该URL的完整显示应该是www.baidu.com:443/s?wd=vue。 这里的:443就是端口号。
之前我们说过,协议规定了数据、报文的装包、拆包的过程和解释,同时协议也规定了,访问该协议所对应的默认端口号。 80端口默认对应了http的web服务 443端口默认对应了https(在HTTP的基础上通过传输加密和身份认证保证了传输过程的安全性) ftp默认对应着20和21端口 ssh默认对应22端口 Telnet对应23端口 SMTP对应25端口 DNS对应的是53端口 Windows下的远程访问对应的是3389端口。 这些端口都是我们平时生活工作中经常会碰到的端口号。
1.5、Path/Router
据网上大多资料会将这部分称作路径或路由,而最官方的说法应该是路由,路由: 达到目的地所经过的路径。这样的解释更为的合理一些。
路由一般情况下对应该服务下的真实路径或目录,以根(‘/’,root)开始的树状结构,而在路由下的叶子节点则对应着某个可访问的文件(不绝对)。路由若以访问路径区分可以使用绝对路径和相对路径进行区分,若以服务器的功能性需求上分,又可根据一定需求分为hash路由和历史路由,这里只简单区分,后文再围绕着这两部分区分详细阐述。
在前后端中,也会将路由中的某些字段使用Restful风格进行带参,可使编程从语义上更便于理解,例如http://localhost:8080/petMng/detail/101 可理解成,宠物管理中的101号宠物的详情。
1.6、Query/Parameter
?wd=vue,这部分一般被我们称作参数,也可以叫作查询。
传统意义上来讲它的目的是在 URL 中带上一些信息传给服务器,而现在在前后端服务器分离的工程化项目中,它的目的是带一些参数跳转下一个页面或者向服务器发请求是携带Param参数。这些参数以Key/Value的方式出现,以?开头,以&区隔,一般适用于get请求。
2、虚拟主机
虚拟主机或称共享主机,又称虚拟服务器,是指在一个服务器上,通过基于端口、IP、主机名、路由方式同时运行多个服务,而互相之间又相对独立,隔离,可进行分别配置,节约性能,可通过单主机或集群的方式进行实现。
2.1、基于端口
基于端口的虚拟主机则是将多个服务绑定于不同的端口号,例如有A、B两个web服务,分别需要挂载到www.baidu.com上,则可以定义两个端口,A使用默认的80端口;B使用8080端口,则A、B对应的域名是www.baidu.com:80、www.baidu.com:8080。通过该方法可以实现两个服务之间的绝对隔离。具体实现方式可以在百度中搜索。基于端口的方式优点是方便部署,不需要对服务进行配置,只需要在安装的时候选择端口即可。缺点是不方便做搜索引擎优化(SEO,Search Engine Optimization),一般使用到该方式的场景是开发环境,因为在开发环境中经常需要启用多个服务,To C的场景不太适合使用该方式,因为用户一般不会去记具体的端口号(可通过网关或代理的方式转发到默认端口)。
2.2、基于IP
一般情况服务器可以绑定多个IP地址,那么每一个出口IP地址都可以绑定一个服务。该方法的优点是端口有若干套,不用担心端口冲突问题;缺点是如果在公网环境下,IP会有一定的价格成本,维护也是多套的。
2.3、基于主机名
基于主机的方式可能是最多的选择,因为符合域名产生的初衷,利用便于记忆的方式去处理域名与IP的映射和主机名与服务的映射。例如mp3.baidu.com就可以直观的看出是百度的音乐平台;news.baidu.com就是百度新闻。域名与IP进行映射,端口使用默认端口也方便SEO,主机名的不同也可以对应不同的服务。适用于To C的场景,缺点是维护成本略高,需要对配置文件进行配置。
2.4、基于路由
基于路由的方式则是理由路由的规则,将不同的一级路由映射不同的服务,常用于不同的web服务,通过默认的端口进行服务目录树的展示,进入不同的一级路由则显示不同的服务。缺点时,在开发时也需要用到路由,容易导致路由的冲突,一般情况下会制定大量的约定或者说是协议去避免这些路由,后来hash路由的流行也让这个问题得以解决,hash路由自身的问题后文再说。常用于内网环境或者To B的业务场景。
3、路由的定义
不同场景下的路由具有不同的含义,在计算机网络,或者说是数据通信中,路由指的是数据包到达目的主机可能会经过的拓扑图。在计算机硬件中,路由常指我们生活中的路由器设备。而在前后端开发的过程中,我们常常会将路由和路径、目录的概念进行绑定。
那么我们处于前后端开发的立场下将路由与路径和目录的概念进行绑定后,将路由也分为shell脚本下的路由、资源引用下的路由、URL中的路由。共同的理解是到达这个资源需要走过的路径,这三种使用方式几乎是一样的。
4、路由的模式
4.1、相对路由和绝对路由
相对路径是相对于绝对路径的,绝对路径是指的文件或者目标距离根'/'的路径。例如我想访问linux下admin目录下的.bashrc文件,则按照绝对路径的写法是
cat /home/admin/.bashrc
而这样的写法对于用户来说相对复杂了,比如admin用户就会想,我以我自己的身份登录,为什么还要写这么复杂的路径才能访问我自己文件夹下的文件,于是基于用户目录的~相对路径就出现了,于是当admin登录后访问.bashrc文件则变成了
cat ~/.bashrc
当然了,这种方式仅限于shell,并不适用于URL路由。
而当路径过深后,例如/home/admin/document/workplace/git/web-temp-admin/dist/dev/src/lib/style/。 这时候用户已经在这个目录下了,用户的需求是访问这个目录中的lib下的js文件夹下的a文件,如果按照绝对路径的写法则是
cat /home/admin/document/workplace/git/web-temp-admin/dist/dev/src/lib/js/a
虽然可以用CV解决,但是也过于的麻烦。所以相对路径解决了这个问题。相对路径只需要如下写法
cat ../js/a
相对路径适用于绝大多数场合,也可以通过相对路径于URL中,例如pd.dev.wyyt.cc/8_Jinrong/和pd.dev.wyyt.cc/8_Jinrong/.…是一样的都可以访问到同一目录下。
有时候我们会遇到这样的场景,例如当前用户处于/home/admin/document/workplace/git/web-temp-admin/dist/dev/src/lib/style/下,现在想查看git目录下的web-test-user项目下的package.json文件,如果通过相对路径和绝对路径写法如下
# 利用相对路径
cat ../../../../../web-test-user/package.json
# 利用绝对路径
cat /home/admin/document/workplace/git/web-test-user/package.json
这两种书写就显得很不人性,这如果是单次使用还好,如果这是一个批处理脚本,下次维护该脚本时,这里就很难理解。在shell中,linux系统定义‘ ~ ’是代表用户目录,‘ - ’是上次一次访问的目录等等,在很多场景下,我们都是通过映射来进行代替某段路径,例如在Vue工程中,我们经常会将/src映射或者说是设定一个别名‘ @ ’,这样之后再访问文件就可以以@开头,如果经常用到例如/src/lib/assets目录,也可以将这个目录设定别名'@assets'。
4.2、历史路由和Hash路由
绝对路径和相对路径是按照书写形式进行分类,而在功能上所达到的结果是一样的。历史路由和Hash路由则是从功能上进行区分。从严格意义上来讲,历史路由的出现要比Hash路由晚的多,最开始的路由不区分历史路由还是Hash路由,因为路由从根本上来讲就是目录或者说是路径。后来为了不同的需求需要所以才逐渐分为两类路由。
4.2.1、历史路由定义
在有历史路由前,hash模式就已经存在了,那时候的hash模式还仅仅作为锚点的存在。那为什么又需要历史路由的出现呢?如果将路由模式定义为hash模式,那么锚点将失去本身的作用,虽然也有兼容方案,但是监听#后的部分作为锚点肯定是不行了的。然后是hash的传参是基于url的,如果传递比较复杂的数据,会导致url变得很长,阅读起来不方便且url的体积会增加,URL的长度限制为16,384个字符。而历史模式既可以在URL里方参数,也可以将数据放入state中进行传输,虽然也有限制,是640k,也就可以存储320,000个字符,比URL传参大一倍。在这里声明,不建议利用URL或者是state带这么大的数据,如果数据过大,建议存入状态管理机或localstorage或sessionstorage中进行跨页面的数据交互。
如果不想要很丑的 hash,我们可以用路由的 history 模式 —— 引用自 VueRouter文档
4.2.2、历史路由API
// 等同于浏览器的后退按钮,进行后退操作,在history栈中向栈底移动一格指针
window.history.back();
// 等同于浏览器的前进按钮,进行前进操作,在history栈中向栈顶移动一格指针
window.history.forward();
// 向栈底或栈顶移动num格指针,若为负数则向栈底移动,若为正数则向栈顶移动
window.history.go(num: number);
// 获取历史栈的长度
let numberOfEntries: number = window.history.length;
// 切换到指定path,并向栈顶放入一条数据
history.pushState(stateObj: object, title: string, path: string);
// 重定向到指定path,并将栈顶的数据替换为该数据
history.replaceState(stateObj: object, title: string, path: string);
4.2.3、历史路由的优缺点
优点:历史路由与我们目录结构或者路径一致,便于理解,便于SEO,对html5中的history API完全兼容适用 缺点:每一次的路径切换都会与web服务器做一个交互,发出请求,容易造成路径冲突。特别是在传统前后端没有分离的架构中,经常后端定义的路由和前端定义的路由冲突,或访问资源不存在的错误,我们又需要在服务端定义一个访问不到静态资源的静态html。兼容性差,历史路由的概念最早的兼容IE版本是IE10,而hash路由可兼容到IE8。
4.2.4、Hash路由的定义
hash路由中的hash并不是散列、杂凑函数算法,而是我们很早开始使用的锚点,以‘ # ’开始的部分,例如
从#开始的部分都可以被我们称为hash路由,hash路由最开始的定义是作为锚点使用,那时候和css中的#是一样的含义,后面跟id,这样可以跳转到指定dom元素。由于 hash 值变化不会导致浏览器向服务器发出请求,而且 hash 改变会触发 hashchange 事件,浏览器的进后退也能对其进行控制,所以人们在 html5 的 history 出现前,基本都是使用 hash 来实现前端路由的。
4.2.5、Hash路由的优缺点
优点:浏览器兼容性较好,可以兼容到IE8,页面间的跳转不会与服务器发生交互。 缺点:不方便做SEO,服务端无法准确捕获路由的信息,对于需要锚点功能的需求会与当前路由机制发生冲突,需要通过其他方式来实现锚点,对于需要重定向的操作,后段无法获取url全部内容,导致后台无法得到url数据,典型的场景就是微信公众号的oauth验证。
5、History栈
栈是一种数据结构,只允许一端进和出,分为栈顶、栈底和指针,指针指向当前所处的数据,可以通过指针获取当前数据中的state和path,而title基本上浏览器不做处理,可以根据需求进行操作。
之前的History API中的go()方法就是移动指针到指定的数据处,back()和forward()就是利用go()方法的一种实例。通过pushState()将在栈顶添加一条数据,通过replaceState()则将入参数据替换掉栈顶数据。
6、总结
由于历史路由的出现时间在Hash路由之后,所以历史路由模式兼容了hash路由,也就意味着history api也可以在hash场景下使用,只是不同的是,历史路由的pushState和replaceState都是从' / '开始进行替换的,如果说当前项目部署于服务器的一级目录中,跳转会失败。例如,当前的目录在www.cnblogs.com/vir1/user/, 并且我们的服务器部署在www.cnblogs.com/vir1/ 这个目录下。这个时候我们需要通过pushState切换到www.cnblogs.com/vir1/doddgu… 那么利用history api进行pushState时的写法是
history.pushState(null,'','/vir1/doddgu/p/crank')
而我们的理想状态的写法是history.pushState(null,'','/doddgu/p/crank')。再如果我们在本地开发,内网环境,测试环境和生产环境不同的时候,这个虚拟主机的目录可能是不确定的,那么我们就不可能将这个部分写死,所以极其的不方便(当然了,也可以事先约束好,在什么环境下都保持一致,这个问题也可以解决)。
使用Hash路由的时候,可以通过onhashchange事件来监听hash值的改变从而修改#标志位以后的部分实现页面跳转,不会影响#标志位前的路由,则可以实现利用相对路径来切换页面。当然了,缺点也很明显,SEO不方便,所以该方式适用于To B业务,不太适用于To C业务。