背景介绍
从毕业到工作都一年多了,在这一年多的时间里,我发现有的后台程序员连 URL 的查询参数都不知道,再加上最近使用 node 和 python 连接加密的 mongodb 时也遇到了点问题,就决定对我所知道的 URL 方面知识做一个总结。
何为URL
名词解释
URL 是 Uniform Resource Location 的缩写,译为“统一资源定位符”。通俗地说, URL 是 Internet 上用来描述信息资源的字符串,主要用在各种 www 客户程序和服务器程序上。采用URL可以用一种统一的格式来描述各种信息资源,包括文件、服务器的地址和目录等。
顺便也提一下 URI 吧:
Web上可用的每种资源 - HTML文档、图像、视频片段、程序等 - 由一个通过通 用资源标志符(Universal Resource Identifier, 简称"URI")进行定位。
URI一般由三部分组成:
- 访问资源的命名机制。
- 存放资源的主机名。
- 资源自身的名称,由路径表示。
URL 是 URI 的子集,但是平时的开发中我们只需要了解 URL 就可以了。
URL格式
以 http://test.com:8080/example/index.html
为例进行说明。
URL的格式由下列三部分组成:
- 第一部分是协议(或称为服务方式),本例中为
HTTP 协议
。 - 第二部分是存有该资源的主机IP地址或域名(有时也包括端口号),本例中为
test.com:8080
。平时看到的都是域名,之后客户端会通过DNS(域名系统) 查询域名对应的 IP,然后根据IP和端口号进行服务器的连接。稍后会对此进行详细说明。 - 第三部分是主机资源的具体地址,如目录和文件名等,本例中为
/example/index.html
。
URL 的语法
通用语法
<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
- scheme: 协议,常见的有 http(80),https(443),mailto,ftp(21),rtsp,rtspu,file。
- user:用户名。
- password: 密码。
- host:主机。
- port: 端口。
- params: 参数。通常为
key=value
。 - query:查询参数或查询字符串。
- frag: 片段(在浏览器中会被解析为
window.location.hash
)。
这只是通用语法,大多数的 URL 都只遵循了一部分而已,并不是每种URL都会有上面的所有信息。
从上面的语法我们可以看出至少 :/@;?# 都是敏感字符,所以在其他参数中不能包含这些字符,如果含有敏感字符或者特殊字符,就需要使用对应的转义字符,否则可能会发生不可预料的结果。 在开发过程中,容易在查询参数中含有 敏感字符,所以查询字符串的值一般都需要使用 encodeURIComponent
进行编码。
http(s)中的语法
http://test.com:8080/user/index.html?id=1&nickName=test#/list
在浏览器中被解析后的格式为:
{
protocol: 'http:', // 协议
host: 'test.com:8080', // 主机名或域名,带端口号
hostname: 'test.com', // 主机名或域名,不带端口号
port: '', // 端口号,http默认80,https默认443
path: '/user/index.html', // 路径
query: '?id=1&nickName=test', // 查询字符串
hash: '#list', //片段或者哈希,
}
复制代码
注意: #后面的东西都不会从客户端传到服务器。
在地址栏中改变 # 后的字符串时,页面并不会刷新,但是会出发 hashchange 事件,很多前端路由的 hash 模式就是根据这种特性实现的。而改变出了frag(hash)
之外的东西都会导致浏览器刷新,因为此时相当于向服务器发出了一个新的请求。
ftp中的语法
文件传输协议,可以用来从服务器下载或上传文件。
基本格式:
ftp://<user>:<password>@<host>:<port>/<path>;<params>
示例:
ftp://ftpuser:123456.com@test.com:21/path/example
file中的语法
该协议常见于本地文件于浏览器中打开的场景,也可能是网络文件系统或者其他一些文件共享系统。这个我就不细说了,感觉没啥可说的。
mongodb协议
常见于后台程序连接 mongodb 数据库。虽然我们常见的包都是用对象格式来连接数据库的,但是最后都会被转化为字符串形式的。
基本格式:
mongodb://<user>:<password>@<host>:<port>/<path>?<query>
示例:
mongodb://test:123456@127.0.0.1:27017/novel?authSource=admin
该字符串表示以 mongodb 协议,用户名为test,密码为123456,数据库dbName为novel,验证的数据库为admin,连接至127.0.0.1主机的27017端口。
注意:如mongodb服务器没有开启加密,需要去掉查询参数,否则会连接失败。若开启了加密,authSource的值必须和前方的user和password相对应。,否则用户验证页无法连接。 我用的thinkjs,一开始没有配置 authSource,给我报的错误是连接超时,当时还纳闷远程的服务器再慢也不至于吧???后来调整了超时时间,发现还是超时,而且一直在尝试重新连接,就想到了可能是验证失败了,这个纠结了不少时间。
在浏览器中输入URL后,执行的全部过程
整个流程如下:
- 域名解析;
- 发起TCP的3次握手;
- 建立TCP连接后发起http请求;
- 服务器响应htp请求;
- 浏览器解析htm代码,并请求html代码中的资源(如js、css、图片等);
- 断开TCP连接;
- 浏览器对页面进行渲染呈现给用户。
其实,域名解析这个过程要是细说的话还是有点复杂的,总之有时候也是蛮耗时的,毕竟从 解析 这个用词我们就能看出它一定是需要时间的?通过DNS会将域名解析为 IP,之后会根据 IP 和 端口连接服务器。
如果我们直接用IP访问服务器,就可以节省一部分时间,但还是不建议大家这么做,因为如果更换了服务器的话,域名可以解析到另一个IP,可以在浏览器端保留相应的数据,而IP就不行了。还有就是,域名的可访问性和可读性可比IP强多了。
动态服务器和静态服务器
上面的大部分都只适用于静态服务器,如果是动态服务器的话,它会有自己的一套解析规则,但是大致上也是相同的,最大的区别可能还是动态服务器的动态路由吧(前端路由现在也支持动态路由了)。
下面主要就说一下动态服务器相对于静态服务器的特殊点:
动态路由
比如定义一个获取某个人的信息的接口,它的路径为:{ path: '/user/:id' }
当访问 /user/1?name=test
时,该接口将被框架解析为:
{
params: {
id: 1,
},
query: {
name: 'test',
}
}
复制代码
如果是静态路由的话,只能使用 /user?id=1&name=test
这种方式来传递参数了,从这可以看出动态路由在一定程度上还是比静态路由有一点优势的;而且动态路由看上去更优雅。
但是如果后台对路由的定义不好,前端传过去的参数为空的话,动态路由就会变成 /user/
,访问到的接口就不是 /user/:id
这个路由,而是 /user/
这个接口了,后台差找不到该路由,直接就返回 404 或者自定义的错误了。
路由重写
我接触过的后台框架有 ThinkPHP 和 thinkjs, 这两个还是比较相似的,它们都有一个特性就是能够进行路由重写,比如在定义好的路由之后加上后缀,如果设置 ext: '.html'
,那么访问 /user/1.html
时将被解析为 /user/:id
,在那些前后端还未分离的公司,这个应该是主要的页面输出方式了(记得不知道从哪看到的这样好像有利于SEO优化?)。
所以以后在看到 http://test.com/user/1.html
这样的 URL的时候,就不能再单纯的认为它一定指向服务器的某个静态文件,它现在也可能是经过模板渲染之后的一个响应。
说了点动态服务器的,那就再说一点静态服务器的吧,因为我遇到的一个后台,在前端上传文件后,直接把文件存储到linux系统的根目录,然后在域名后面把文件的路径拼上去,还纳闷怎么就是访问不到呢?
web服务器的根目录
常用的静态服务器是 nginx,设置一下静态文件的压缩啦,缓存啦,代理啦,识别设备进行跳转,图片裁剪之类的,都是so easy,简直就是我们大前端的标配嘛。
流行一点的web服务器主要是 nginx, apache,tomcat,后两个主要还是用于搭配后台使用,不直接向外暴露接口的。这些静态服务器都会有一个配置用于设置 web 服务器的根目录,那么这个根目录的作用是什么呢?就是控制客户端能访问到的顶级目录。 比如根目录是 www,是不能访问 www 目录以外的其他文件的,只能访问 www 的子目录的各文件。
参考链接
- 名词解释URL和URI
- 《HTTP权威指南》
- 在浏览器中输入URL后,执行的全部过程