输入域名到显示网页的网络过程
5层网络模型介绍
低三层
物理层主要作用是定义物理设备如何传输数据
数据链路层在通信的实体间建立数据链路连接
网络层为数据在结点之间传输创建逻辑链路
传输层:
两个协议 TCPIP 和 UDP ,向用户提供可靠的端到端(End-to-End)服务。
建立起了自己电脑到百度服务器(举例)的链接,它们两端如何去传输数据,它的传输数据的方式 ,都是在这层定义。
传输层向高层屏蔽了下层数据通信的细节。
应用层:
为应用软件提供了服务 http / ftp 是构建于TCP 协议之上,屏蔽网络传输相关细节。
HTTP协议的发展历史
1、弄清楚一个概念,HTTP请求与TCP请求不是一个概念,在同一个TCP请求可以发送多个HTTP请求,以前的协议版本不能这么做,但是现在HTTP1.1.1里面可以这么做,而且在HTTP2里面是会更大的去优化相关的一些东西,来提高HTTP传输效率以及服务器的性能。
2、TCP连接对应多个HTTP请求,而一个HTTP请求肯定在某一个TCP连接里面去定义发送的。
第一个版本 HTTP / 0.9
只有一个命令 GET
没有HEADER 等描述数据的信息
服务器发送完毕就关闭
第二个版本 HTTP / 1.0
增加了很多命令
增加了status code 和 header
多字符集支持、多部分发送、权限、缓存 等等
第三个版本 HTTP / 1.1
持久链接
pipleine
增加了 host 和其他一些命令 (在同一个物理服务器可以同时跑很多服务)
第四个版本 HTTP / 2.0
所以数据都是以二进制传输
同一个链接里面发送多个请求不在需要按照顺序来
头信息压缩以及推送等提高效率的功能
HTTP2
1、所有数据以二进制传输
同一个连接里面发送多个请求不再需要按照顺序来(可以同时返回数据)
头信息压缩以及推送等提高效率的功能:
头信息压缩:在HTTP1发送和返回请求,http头都是必须完整发送并返回,带宽量大。
2、推送:http请求只能是发送然后响应返回内容,客户端永远是主动方,服务端是被动方。http2有了推送,服务端可以主动发起数据传输。
如:web页面里面有css,img,js等文件,它们都是连接的形式,这里就有顺序的问题,解析文本之后才能发送对应的链接请求,http2有了推送功能,在请求的同时,可以主动把这个页面的css,img,js等文件推送到客户端,这样发送顺序是并行的,不是串行的,性能高出许多。
HTTPS
安全版本的HTTP。
HTTP的三次握手
在客户端和服务器端之间进行http请求的发送和返回的过程当中,需要创建TCP connection,因为http不存在连接这样的概念,它只有请求和响应,里面都是数据包,需要经过传输的通道,这个通道就在TCP里面创建了从客户端发起到客户端接收的这样的一个连接,这个连接可以一直保持,http请求就是在这个连接的基础上发送的。
在TCP上面可以发送多个http请求,在不同http版本里,这个模式是不一样的。
http1.0:TCP连接是在一个http请求创建的时候,就去创建这个TCP链接,链接创建完了之后,请求发送过去,服务器响应了之后,这个TCP连接就关闭了。
http1.1:TCP链接可以通过某种方式去声明链接一直保持,就是在第一个请求发送之后,TCP链接没有关闭,第二个请求来了之后,还可以在这个没有关闭的链接上进行发送。
http2:的TCP链接上面的http请求时可以并发的,如果同一个用户对同一个服务器发起一个网页请求的时候,只需要一个TCP连接。
长链接好处:
- TCP链接在创建的过程中要经历三次握手这么一个消耗,代表着有三次网络传输:客户端发送-服务端返回-客户端再发送-创建TCP链接,这个时候才可以发送HTTP请求,所以如果我们把TCP链接保持,那么第二个http请求就没有三次握手的开销。
TCP三次握手
TCP握手过程
1.客户端发起一个我要连接的数据包请求给服务器,里面会有一个SYN的标志位,标志这是一个创建请求的数据包
2.服务端接收到数据包后知道有一个客户要和它建立链接了,然后会开启一个TCP socket 端口,开启之后返回数据给客户端,数据包含 SYN标志位,ACK= X+1,Seq=Y
3.客户端拿到数据包后意味着服务器端允许创建TCP连接,然后发送数据包 ACK = Y+1,Seq=Z
为什么要三次握手创建TCP连接?
为了防止服务端开始无用的链接,网络传输是有延时的,传输过程中防止丢包,造成重复创建链接,资源浪费,所以设置三次握手。为了规避网络传输延时。
三次握手确认客户端发起到服务器返回的过程,就是为了规避这些在网络传输过程中延时导致的一些服务器开销的问题。
URI-URL和URN
user:pass@ 代表访问资源的身份使用用户名和密码
host.com 用来定位资源的服务器在互联网中的位置(可以是IP 也可以是 域名)
:80 端口,每台物理服务器可以跑很多软件的web服务,端口就是监听物理服务器上面某个具体的web服务
/path 路由,web 服务器里面的内容可以通过路由进行定位
?query=string 搜索参数
hash 查找文档的某个片段
HTTP报文格式
method :
GET:获取数据
POST:创建数据
PUT:更新数据
DELETE : 删除数据
http方法:用来定义对于资源的操作
http code :定义服务器对请求的处理结果
100-199代表操作要持续进行
200-299代表成功
300-399需要重定向
400-499代表请求有问题
500-599服务器错误
CORS跨域请求的限制与解决
请求已经发送了,内容也已经返回了,只不过浏览器在解析了内容之后,发现这是不允许的,所以帮你拦截了,这其实是浏览器提供的功能。
跨域 : 在这个网页发送一个请求,如ajax请求,都必须在同域的,如果跨域,需要服务器同意,才能够接收到返回内容。
jsonp到底做了什么?
建立script标签,src放置请求地址,即可。因为浏览器允许link,img,script标签上的路径加载内容,并不在乎服务器是否设置了跨域的头,jsonp的原理其实就是在script上加载了一个连接,这个连接去访问了服务器的某一个请求并返回内容。
设置请求头:
response.writeHead(200,{
'Access-Control-Allow-Origin':'*'
})
'Access-Control-Allow-Origin'的值:
'Access-Control-Allow-Origin':'*
- '*' : 任何服务,任何域名都可以访问服务器。的做法 :
更安全'Access-Control-Allow-Origin':'http://baidu.com'
,只允许指定的域名请求。
缓存头Cache-Control的含义和使用
自定义的头在跨域请求时不被允许的
fetch('http://localhost:8887/',{
method:'POST',
headers:{
// 自定义头
'X-Test-Cors':'123'
}
})
CORS预请求限制
跨域时默认允许的方法(不需要预请求) :
- GET
- HEAD
- POST
除了以上三种,都会先发送预请求
允许Content-Type - text/plain
- multipart/form-data
- application/x-www-form-urlendoded
其他限制
- 请求头限制
- XMLHttpRequestUpload对象均没有注册任何事件监听器
- 请求中没有使用ReadableStream对象
什么是预请求
浏览器根据Response Headers判断请求是否允许
解决:设置允许访问的自定义请求头
http.createServer(function(request,response){
console.log('request come',request.url)
response.writeHead(200,{
'Access-Control-Allow-Origin':'*',
// 设置允许访问的自定义请求头
'Access-Control-Allow-Headers':'X-Test-Cor'
})
response.end('123')
}).listen(8887)
浏览器跨域请求之预请求操作:Request Method:OPTIONS,首先发送这个请求,服务端可以根据不同的method进行不同的操作,允许接下来实际发送的请求。通过这个OPTION来获得服务端允许的任何请求,然后再实际发送设置的请求。
做跨域文件上传的时候,浏览器会自动发起一个 OPTIONS 方法到服务器。普通的 ajax 请求,也不会发起这个请求,只有当 ajax 请求绑定了 upload 的事件并且跨域的时候,就会自动发起这个请求。用于探测后续真正需要发起的跨域 POST 请求对于服务器来说是否是安全可接受的,因为跨域提交数据对于服务器来说可能存在很大的安全问题。
概括:允许跨域的请求 : get , post,options
发送的请求跨域了,浏览器会自动发送一个options预请求,询问后端支持的跨域请求方法。后端需要设置允许请求的方法。
router.options('/upload', function* (){
this.set('Access-Control-Allow-Method', 'POST');
this.set('Access-Control-Allow-Origin', 'http://xxx.com');
this.status = 204;
});
204 :告知客户端表示该响应成功了,但是该响应并没有返回任何响应体,options预响应。如果状态码为 200,还得携带多余的响应体,在这种场景下是完全多余的,只会浪费流量。
浏览器为什么设置请求限制?
因为浏览器希望在网页进行跨域请求操作的时候是保证服务端的安全的,不允许任何随便进行跨域,不允许随便的方法进行跨域,以防数据被恶意篡改。所以提供这些限制之后,就可以进行一些非常有利的判断。
http.createServer(function(request,response){
console.log('request come',request.url)
response.writeHead(200,{
// 设置允许跨域的访问地址
'Access-Control-Allow-Origin':'*',
// 设置允许访问的自定义请求头
'Access-Control-Allow-Headers':'X-Test-Cors',
// 设置允许跨域的methods
'Access-Control-Allow-Methods':'POST,Put,Delete',
// 允许以上三个方式进行跨域的最长时间,1000秒内不需要发送预请求验证了
'Access-Control-Max-Age':'1000'
})
response.end('123')
}).listen(8887)
Redirect
通过url去访问一个路径的时候,这个资源可能已经不在这个url指定的位置了,服务器要告诉浏览器这个资源具体的位置,然后浏览器再去重新请求这个指定的位置。所以如果资源换了位置,不应该马上废弃url,应该告知客户端资源正确的位置。
CORS跨域限制以及预请求验证
自定义的头在跨域请求时不被允许的
fetch('http://localhost:8887/',{
method:'POST',
headers:{
// 自定义头
'X-Test-Cors':'123'
}
})
CORS预请求限制
跨域时默认允许的方法 :
- GET
- HEAD
- POST
允许Content-Type
- text/plain
- multipart/form-data
- application/x-www-form-urlendoded
其他限制
- 请求头限制
- XMLHttpRequestUpload对象均没有注册任何事件监听器
- 请求中没有使用ReadableStream对象
什么是预请求
浏览器根据Response Headers判断请求是否允许
解决:设置允许访问的自定义请求头
http.createServer(function(request,response){
console.log('request come',request.url)
response.writeHead(200,{
'Access-Control-Allow-Origin':'*',
// 设置允许访问的自定义请求头
'Access-Control-Allow-Headers':'X-Test-Cor'
})
response.end('123')
}).listen(8887)
浏览器跨域请求之预请求操作:Request Method:OPTIONS,首先发送这个请求,服务端可以根据不同的method进行不同的操作,允许接下来实际发送的请求。通过这个OPTION来获得服务端允许的任何请求,然后再实际发送设置的请求。
浏览器为什么设置请求限制?
因为浏览器希望在网页进行跨域请求操作的时候是保证服务端的安全的,不允许任何随便进行跨域,不允许随便的方法进行跨域,以防数据被恶意篡改。所以提供这些限制之后,就可以进行一些非常有利的判断。
http.createServer(function(request,response){
console.log('request come',request.url)
response.writeHead(200,{
// 设置允许跨域的访问地址
'Access-Control-Allow-Origin':'*',
// 设置允许访问的自定义请求头
'Access-Control-Allow-Headers':'X-Test-Cors',
// 设置允许跨域的methods
'Access-Control-Allow-Methods':'POST,Put,Delete',
// 允许以上三个方式进行跨域的最长时间,1000秒内不需要发送预请求验证了
'Access-Control-Max-Age':'1000'
})
response.end('123')
}).listen(8887)
缓存头Cache-Control的含义和使用
特性:
以下这些头只是限制性的,声明性的作用,没有强制约束力。只是为代理服务器设置了这些头,要求按照规范去做,但是完全可以不按照这个规范做。
可缓存性
- public
在http请求返回的过程当中,在Cache-Control设置了public值,代表这个http请求返回的内容所经过的任何路径当中,包括一些中间的http代理服务器以及我们发出请求的客户端浏览器,都可以进行对返回内容的缓存操作:就是把这份数据存在本地,下次直接读这个缓存,不需要到返回这个内容的服务器上面重新进行操作返回内容。可缓存性是指哪些地方可以执行这些缓存。 - private
只要发起请求的浏览器才可以缓存 - no-cache
任何节点都不可以缓存。可以在本地服务器缓存,每次发起请求都需要去服务器验证,如果服务器说可以使用缓存,才能使用缓存。也就是说需要经过服务器验证的。
到期
- max-age=<seconds>
缓存有效期 - s-maxage=<seconds>
代替上面的max-age,但是只有在代理服务器里面才会生效 - max-stale=<seconds>
max-stale:浏览器用不到,浏览器并不会主动去设置这个头,只有在发起端设置是有用的,服务端返回的内容中设置没有用。发起请求方,主动带的头,在max-age过期之后,如果我们返回的资源中有这个max-stale设置,还可以使用过期的缓存,而不需要去服务器请求新的内容。
重新验证
- must-revalidate
设置了max-age,如果缓存已经过期了,必须去原服务端发送这个请求,重新获取数据,来验证内容是否真的过期了,而不能直接使用本地缓存。 - proxy-revalidate
用在指定缓存服务器,在过期的时候必须去原服务器重新请求一遍,而不能直接使用本地缓存。
其他
- no-store
本地和代理服务器不可以存储这个缓存,永远要去服务器拿新的body的内容。 - no-transform
不允许代理服务器不要改动返回的内容
浏览器中用到的
可缓存性 :
- public
- private
- no-cahe
到期
- max-age=<seconds>
- s-maxage=<seconds>
- max-stale=<seconds>
重新验证
- must-revalidate
- 设置请求文件缓存时间
'Cache-Control': 'max-age=20'
http.createServer(function (request, response) {
console.log('request come', request.url)
if (request.url === '/') {
const html = fs.readFileSync('test.html', 'utf8')
response.writeHead(200, {
'Content-Type': 'text/html'
})
response.end(html)
}
if (request.url === '/script.js') {
response.writeHead(200, {
'Content-Type': 'text/javascript',
// 设置到期时间
'Cache-Control': 'max-age=20'
})
response.end('console.log("script loaded")')
}
}).listen(8888)
问题 : 这时如果改变了服务器返回的结果,刷新,发现返回的还是之前的结果,并不是最新的。这是因为服务器端更新了之后,客户端还是请求的缓存的资源,这样想要更新一个应用的时候,客户端根本触及不到了,一般max-ag可能会设置一年。
解决:在构建流程的时候,把打包完成的JS文件名根据内容的hash结果,加上一串hash码,这串hash码是因为根据打包完成的js以及其他静态资源的文件内容进行性的hash计算,所以如果这些静态文件内容没有变,hash码就不变,反应到web页面上就是url没有变,那么就可以使用静态缓存;而如果你的内容有变,hash码就会变化,嵌入在html的url路径就有变化,有了变化之后发起的请求就是一个新的静态资源请求而不是之前缓存的请求。这样就可以达到缓存的目的。
缓存验证:Last-Modified和Etag的使用
资源验证
如果catche-Ctrol设置了no-cache(任何节点都不可以缓存。可以在本地服务器缓存,每次发起请求都需要去服务器验证,如果服务器说可以使用缓存,才能使用缓存。也就是说需要经过服务器验证的。)
设置了Catche-Ctrol的请求过程如下:
数据如何验证?
1、验证头
-
Last-Modified
上次修改时间。给资源设置了上一次是什么时候被修改的。
配合If-Modified-Since或者If-Unmodified-Since使用。
验证是否可以使用缓存的过程:如果我们请求了一个资源,如果返回的head里面有以上两个头信息,指定了时间,下次在请求的时候就会带上这两个传过来的值到服务器,通常浏览器都是用If-Modified-Since,服务器就可以通过读取head里面的If-Modified-Since的值,然后对比资源存在的地方,对比上次修改时间,如果发现这两个时间是一样的,代表这个资源还没有被修改,服务器就告诉浏览器直接使用缓存。对比上次修改时间以验证资源是否需要更新。 -
Etag
一个更加严格的验证。- 数据签名
一个资源对它的内容会产生一个唯一的签名,如果这个资源进行了修改,这个签名就会变成新的,这样前后的签名就不一样。
典型做法:对内容进行hash计算(就像打包文件一样,对内容计算就会得到一个唯一值)。用来标记这个资源。 - 配合if-Match或者If-Non-Macth使用
下次请求就会带上这两个值,就是服务端返回的Etag值。 - 对比资源的签名判断是否使用缓存
- 数据签名
if (request.url === '/script.js') {
response.writeHead(200, {
'Content-Type': 'text/javascript',
// 设置到期时间 , 每次发起请求都需要去服务器验证
'Cache-Control': 'max-age=20000000000,no-cache',
//Last-Modified
'Last-Modified' : '123',
// Etag
'Etag' : 777
})
这个时候依旧返回了内容,因为内容并没有修改,所以不需要服务器返回response,做法如下:对比Etag
// 如果两次结果相同,返回缓存结果为空
const etag = request.headers['if-none-match']
if(etag === '777'){
response.writeHead(304, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=20000000000,no-cache',
'Last-Modified' : '123',
'Etag' : 777
})
// 设置请求结果为空
response.end('')
}else{
response.writeHead(200, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=20000000000,no-cache',
'Last-Modified' : '123',
'Etag' : 777
})
// 否则就是内容修改了,返回最新结果
response.end('console.log("script loaded twice")')
}
cookie&&session
什么是cookie?
通过服务器返回数据后,通过:Set-Cookie设置保存在浏览器里的内容。浏览器在保存了这个内容之后,下一次在同域里的请求当中带上这个Cookie。这样就可以实现在这次访问网站的会话中,可以通过Cookie一直在传输的内容来保证我们返回的数据是这个用户的。
cookie属性
- max-age 和 expires 设置过期时间
- Secure 只在https的时候发送
- httpOnly无法通过js访问,浏览器中还是有的。
安全性,比如csrs攻击,会通过在网页注入脚本,或通过url来引导用户去给攻击者的服务器发送用户自己的,这样他就能拿到这个这个cookie,从而访问我们网站中保存的用户的数据。禁止重要的数据通过JS访问,是保护用户数据安全重要的一步。
cookie时效
- 如果没有设置时间,浏览器关闭失效。
- 'Set-cookie': ['id=123; max-age=30', 'name=lin'] : id=123->
30s后失效
设置test.com以及test.com的所有二级域名享受到cookie
session:
Cookie保存session,经常做的就是把用户登录之后的ID,或者session的key设置到cookie里面。能够保证定位到用户,就是session实现的方案。
HTTP长连接
http请求时在TCP的连接上发送的,TCP的连接分为长链接和短连接。
长链接: HTTP请求在发送的时候,要先去创建一个TCP连接,然后在这个连接上把http请求发送并且接收完返回。这个时候,因为一次http请求已经结束了,浏览器和服务端就会商量,是否要把TCP连接关闭,如果不关闭,这个TCP连接会一直开着,一直消耗,但是如果下次再有请求,可以直接在TCP连接上发送,那么就不需要经过三次握手了。如果直接关闭,就意味着下次请求又要重新创建一个连接,这个时候就会有网络延迟的开销。
请求之后就关闭的好处 : 减少客户端和服务端高并发的连接数。实际中网站并发量会比较大,如果一直重新创建链接,会导致这个创建过程过多。
数据协商
客户端发送请求到服务端的时候,客户端会声明需要拿到的数据格式以及数据相关的限制,服务端会根据这些判断,可能会有不同类型的数据的返回。服务端会根据客户端发送过来的这些头信息来确定到底要返回什么样的数据。
分类
- 请求
Accept:声明想要的数据- Accept:指定数据类型
- Accept-Encoding : 代表数据是以什么编码方式来进行传输,主要用来限制服务端如何进行数据压缩。
- Accept-Language : 世界语言不通,不同地方期望展示的不同。
- User-Agent : 判断返回的是PC还是M端页面
- 返回
Content- Content-Type
- Content-Encoding
- Content-Language
csp
xss : 通过某些方法在网站里注入一些脚本,导致页面出现问题,甚至窃取用户信息。很可能就是通过网站提供的富文本编辑器之类的工具,插入script内容。
限制方法:在服务器返回的头里面写入'Content-Security-Policy'。
- 只能通过http: https加载,不能直接写在html
- 如果通过外链加载的JS文件,指定域名可以加载,只能本域名下的JS文件可以加载
//限制default-src,只能通过http: https加载,不能直接写在html
'Content-Security-Policy':'default-src http: https'
可以写在meta标签
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'self';">
http明文传输:
互联网中的每一层,如果有人拦截,解析数据包并读取就可以获取相关信息。
https:
私钥:解密公钥加密过的内容,中间截取的人无法解密
公钥:放在互联网上,所有人都可以拿到的加密字符串
两者的主要区别就在于握手阶段。