1.1导学开始
数据内容传输都是通过http协议的。
需要理解http
http很重要
提升自身价值
简单的例子:浏览器中输入网页,ajax数据,img加载图片
Cache-Control?
缓存的验证
深入到TCP
Nginx使用
准备:http这个概念
1.2内容介绍
2.1五层网络模型介绍
一、经典五层模型
1、应用层:为应用软件提供了很多服务,构建于tcp协议之上,屏蔽网络传输相关细节。
(1)http,ftp…
2、传输层:向用户提供可靠的端到端(end-to-end)服务
(1)如tcp,udp
(2)传输层向高层屏蔽了下层数据通信的细节
————底三层————
3、网络层:为数据在结点之间传输创建逻辑链路。
4、数据链路层:在通信的实体间建立数据链路连接。
5、物理层:定义物理设备如何传输数据。
(1)如电脑硬件,网卡端口,网线,光缆。
2.2HTTP协议的发展历史
HTTP/0.9
只有一个Get命令
没有Header等描述数据的信息
服务器发送完毕,就关闭TCP连接
HTTP/1.0
增加了很多命令
增加了status code和header
多字符集支持、多部分发送、权限、缓存等
HTTP/1.1
持久连接
pipeline
增加host和其他一些命令
HTTP2
所有数据以二进制传输
同一个连接里面发送多个请求不再需要按照顺序来
头信息压缩以及推送等提高效率的功能
2.3HTTP的三次握手
一、tcp connection
1、http只有请求和响应的概念,没有连接的概念,靠tcp来连接请求。
2、一个tcp连接可以发送多个http请求。1.0请求发送就会就断开了,1.1保持连接,减少Tcp开销,只要开一个TCP连接。
3、三次握手
(1)防止服务器开启一些无用的连接
二、wireshark抓包:网络层、tcp层、http层都可以抓到
charles是抓到http层
2.4 URI、URL、URN
1、Uniform Resource Identifier 统一资源标识符
2、用来唯一标识互联网上的信息资源
3、包含URL、URN
二、URL
1、Uniform Resource Locator 统一资源定位符
2、http://user:pass@host.com:80/path?query=string#hash
3、此类格式的都叫做url,比如ftp协议、 file
protocol :// hostname[:port] / path / [;parameters][?query]#fragment
protocol(协议)
指定使用的传输协议,下表列出 protocol 属性的有效方案名称。 最常用的是HTTP协议,它也是目前WWW中应用最广的协议。
file 资源是本地计算机上的文件。格式file:///,注意后边应是三个斜杠。
ftp 通过 FTP访问资源。格式 FTP://
http 通过 HTTP 访问该资源。 格式 HTTP://
https 通过安全的 HTTPS 访问该资源。 格式 HTTPS://
hostname(主机名)
是指存放资源的服务器的域名系统(DNS) 主机名或 IP 地址。有时,在主机名前也可以包含连接到服务器所需的用户名和密码(格式:username:password@hostname)。
port(端口号)
整数,可选,省略时使用方案的默认端口,各种传输协议都有默认的端口号,如http的默认端口为80。如果输入时省略,则使用默认端口号。有时候出于安全或其他考虑,可以在服务器上对端口进行重定义,即采用非标准端口号,此时,URL中就不能省略端口号这一项。
path(路径)
由零或多个“/”符号隔开的字符串,一般用来表示主机上的一个目录或文件地址。
parameters(参数)
这是用于指定特殊参数的可选项。
query(查询)
可选,用于给动态网页(如使用CGI、ISAPI、PHP/JSP/ASP/ASP。NET等技术制作的网页)传递参数,可有多个参数,用“&”符号隔开,每个参数的名和值用“=”符号隔开。
fragment(信息片断)
字符串,用于指定网络资源中的片断。例如一个网页中有多个名词解释,可使用fragment直接定位到某一名词解释
三、URN
1、Uniform Resource Name,永久统一资源定位符
2、在资源移动之后还能被找到
3、目前还没有非常成熟的使用方案。
2.5http报文格式
报文主要分为起始行、首部、主体。
起始行
在请求报文中,起始行包含了HTTP的Method、路由地址和HTTP协议版本,在响应报文中则是HTTP协议版本和Status Code如上图
首部:后续详细讲
主体
HTTP请求中返回的内容。
HTTP方法
用来定义对资源的操作
常用有GET、POST等
从定义上讲有各自的语义
HTTP CODE,定义服务器对请求的处理结果
各个区间的CODE有各自的语义,
如100-199之间代表这个操作需要持续的进行,
200-299之间代表操作是成功的,
300-399之间代表重定向,
400-499之间代表发送的请求有问题,
500-599之间代表服务器出现了错误。
好的HTTP服务可以通过CODE判断结果
2.6 实现一个最简单的http服务(nodejs)
const http = require('http')
http.createServer(function (request, response) {
console.log('request come', request.url)
response.end('123')
}).listen('2333')
console.log('server listening on 2333')
3.1认识http客户端
一、浏览器是最常用的http客户端
二、查看http请求, terminal
1、curl baidu.com。curl只是发送请求,并返回,不会对页面进行渲染
2、curl www.baidu.com
3、curl -v www.baidu.com
可以看到dns解析后的ip地址。
3.2跨域请求的限制与解决
跨域:指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器施加的安全限制。
所谓同源是指,协议、域名、端口均相同,不明白没关系,举个栗子:
http://www.123.com/index.html 调用 http://www.123.com/server.php (非跨域)
http://www.123.com/index.html 调用 http://www.456.com/server.php (主域名不同:123/456,跨域)
http://abc.123.com/index.html 调用 http://def.123.com/server.php (子域名不同:abc/def,跨域)
http://www.123.com:8080/index.html 调用 http://www.123.com:8081/server.php (端口不同:8080/8081,跨域)
http://www.123.com/index.html 调用 https://www.123.com/server.php (协议不同:http/https,跨域)
请注意:localhost和127.0.0.1虽然都指向本机,但也属于跨域
那么该如何解决跨域问题呢
JSONP使用方式
<script src="需要请求的地址">
</script>
但是要注意JSONP只支持GET请求,不支持POST请求。
添加请求头
还是用Node环境进行测试,在当前目录下有server.js,server2.js,index.html。
贴上server.js(请求方)的代码
const http = require('http')
const fs = require('fs')
http.createServer(function (request, response) {
console.log('request come', request.url)
const html = fs.readFileSync('index.html', 'utf8')
response.writeHead(200, {
'Content-Type': 'text/html'
})
response.end(html)
}).listen('2333')
console.log('server listening on 2333')
server2.js(响应方)代码
const http = require('http')
http.createServer(function (request, response) {
console.log('request come', request.url)
response.writeHead(200, {
'Access-Control-Allow-Origin': '*'
})
response.end('123')
}).listen('2334')
console.log('server listening on 2334')
index.html的代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
</body>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:2334/')
xhr.send()
</script>
</html>
新建两个终端分别运行node server.js,node server2.js,打开localhost:2333,可以看到,我们当前从不同端口请求时,是属于跨域请求的,但是并没有跨域的报错,原因是因为我们在响应方里添加了请求头'Access-Control-Allow-Origin': '*',如果去掉了这个请求头的话,则会报错
代理:
例如 www.123.com/index.html需要调用www.456.com/server.php,可以写一个接口www.123.com/server.php,由这个接口在后端去调用www.456.com/server.php并拿到返回值,然后再返回给index.html,这就是一个代理的模式。相当于绕过了浏览器端,自然就不存在跨域问题
3.3CORS跨域限制以及预请求验证
1、首先要说一下CORS跨域的限制,CORS允许的方法只有
GET HEAD POST
2、CORS允许的Content-Type只有
text/plain
multipart/form-data
application/x-www-form-urlencoded`
其他限制:请求头限制、XMLHttpRequestUpload对象均没有注册任何事件监听器、请求中没有使用ReadableStream对象
假如我们使用fetch预请求,并需要带一个自定义的请求头如:X-Test-Cors,那么必须要在服务端中定义好Access-Control-Allow-Headers: X-Test-Cors,否则则会报错。
同理,如果我们需要使用其他自定义的方法的话,我们同样需要在服务端中定义Access-Control-Allow-Methods: Post, Put, Delete, xxxxxxx
3.4缓存头Cache-Control
1、可缓存性
public:代表http请求经过的任何地方(代理服务器等等)都可以进行缓存。
private:代表只有发起请求的浏览器才可以进行缓存
no-cache:任何节点都不可以进行缓存
2、到期
缓存到期时间:max-age = <seconds>(秒)
代理服务器使用:s-maxage = <seconds>(秒)
指示客户机可以接收超出max-age时间的响应消息,max-stale在请求设置中有效,在响应设置中无效:`max-stale =
3、重新验证
must-revalidate:如果过期,则必须更新
proxy-revalidate
和must-revalidate差不多,是给缓存服务器使用的。
4、其他
no-store
no-cache从字面意义上很容易误解为不缓存,但是no-cache代表不缓存过期的资源,缓存会向服务器进行有效处理确认之后处理资源,更确切的说,no-cache应该是:do-not-serve-from-cache-without-revalidation
而no-store才是真正的不进行缓存
使用no-cache的目的就是为了防止从缓存中获取过期的资源。
no-transform
如果第三方网站不希望页面转码,可在页面中添加此协议,当用户进入时,会直接跳转至原网页
具体实践:Cache-Control缓存到期时间
const http = require('http')
const fs = require('fs')
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=200'
})
response.end('console.log("script loaded")')
}
}).listen('8888')
console.log('server listening on 8888')
加入了Cache-Control': 'max-age=200'之后,浏览器(客户端)即可对静态资源进行缓存,效果如下图所示。过了200秒后,缓存失效,
3.5缓存验证Last-Modified和Etag
一、Last-Modified
1、上次修改时间,配合If-Modified-Since或者If-Unmodified-Since使用。
2、对比上次修改时间以验证资源是否需要更新。
二、Etag
1、数据签名
2、最常见的:对资源进行hash计算
3、对比资源的签名判断是否使用缓存
server.js
const http = require('http')
const fs = require('fs')
http.createServer(function (request, response) {
console.log('request come --- ', request.url)
const etag = request.headers['if-none-match'];
if (etag === '777') {
response.writeHead(304, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=200000, no-cache',
'Last-Modified': '123',
'Etag': '777'
})
response.writeHead()
} else {
response.writeHead(200, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=200000, no-cache',
'Last-Modified': '123',
'Etag': '777'
})
response.end('console.log("scripte loaded twice")')
}
}).listen('8888')
console.log('server listening on 8888')
index.html
3.6 cookie和session
1、Cookie
通过Set-Cookie设置
下次请求会自动带上
键值对,可以设置多个
2、Cookie属性
max-age和expires设置过期时间
Secure只在https的时候发送
HttpOnly无法通过document.cookie访问
测试cookie返回的方式:
首先新建文件夹,然后新建server.js、index.html。
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>content</div>
</body>
<script>
console.log(document.cookie);
</script>
</html>
server.js
const http = require('http')
const fs = require('fs')
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',
'Set-Cookie': 'id=123'
})
response.end(html)
}
}).listen('8888')
console.log('server listening on 8888')
1、我们通过在请求头中加入'Set-Cookie': 'id=123',来将Cookie写入到浏览器,随后我们在index.html中使用console.log(document.cookie);来打印当前的cookie。
2、我们也可以在chrome开发者工具中的Application中可以查找到当前的cookie
3、Cookie !== Session,Cookie可以是Session的一种实现方式,但是Cookie和Session不是一一对应的,Session有许多别的实现。
4、cookie如果没有设置过期时间,浏览器关闭,就过期了
5、chrome工具,hostAdmin,可以改host,域名映射。
6、同一个主域名下,二级域名可以共享cookie
7、常用:使用cookie来保存session
8、只要是能定位到这个用户,就是一种session的实现方案
3.7HTTP长连接
在HTTP/1.0中默认使用短连接。也就是说,客户端和服务器每进行一次HTTP操作,就建立一次连接,任务结束就中断连接。当客户端浏览器访问的某个HTML或其他类型的Web页中包含有其他的Web资源(如JavaScript文件、图像文件、CSS文件等),每遇到这样一个Web资源,浏览器就会重新建立一个HTTP会话。
而从HTTP/1.1起,默认使用长连接,用以保持连接特性。使用长连接的HTTP协议,会在响应头加入这行代码:
Connection:keep-alive
在使用长连接的情况下,当一个网页打开完成后,客户端和服务器之间用于传输HTTP数据的TCP连接不会关闭,客户端再次访问这个服务器时,会继续使用这一条已经建立的连接。Keep-Alive不会永久保持连接,它有一个保持时间,可以在不同的服务器软件(如Apache)中设定这个时间。实现长连接需要客户端和服务端都支持长连接。
HTTP协议的长连接和短连接,实质上是TCP协议的长连接和短连接。
那么,长连接的优劣是什么?
由上可以看出,长连接可以省去较多的TCP建立和关闭的操作,减少浪费,节约时间。对于频繁请求资源的客户来说,较适用长连接。不过这里存在一个问题,存活功能的探测周期太长,还有就是它只是探测TCP连接的存活,属于比较斯文的做法,遇到恶意的连接时,保活功能就不够使了。在长连接的应用场景下,client端一般不会主动关闭它们之间的连接,Client与server之间的连接如果一直不关闭的话,会存在一个问题,随着客户端连接越来越多,server早晚有扛不住的时候,这时候server端需要采取一些策略,如关闭一些长时间没有读写事件发生的连接,这样可 以避免一些恶意连接导致server端服务受损;如果条件再允许就可以以客户端机器为颗粒度,限制每个客户端的最大长连接数,这样可以完全避免某个蛋疼的客户端连累后端服务。
短连接对于服务器来说管理较为简单,存在的连接都是有用的连接,不需要额外的控制手段。但如果客户请求频繁,将在TCP的建立和关闭操作上浪费时间和带宽。
长连接和短连接的产生在于client和server采取的关闭策略,具体的应用场景采用具体的策略,没有十全十美的选择,只有合适的选择。
接下来在node环境中测试下chrome长连接的并发数限制
我们新建一个文件夹,里面新建三个文件,分别是:server.js、test.html、guo.jpg
test.html
const http = require('http')
const fs = require('fs')
http.createServer(function (request, response) {
console.log('request come --- ', request.url)
const html = fs.readFileSync('test.html', 'utf8')
const img = fs.readFileSync('guo.jpg')
if (request.url === '/') {
response.writeHead(200, {
'Content-Type': 'text/html',
// 默认状态keep-alive 创建一个tcp链接 可以并发多个http请求
// 'Connection': 'keep-alive',
// 关闭之后
// 'Connection': 'close'
})
response.end(html)
} else {
response.writeHead(200, {
'Content-Type': 'image/jpg',
'Connection': 'close'
})
response.end(img)
}
}).listen('8888')
console.log('server listening on 8888')
test.index.html
<body>
<!-- 用多张地址不一样的图片 -->
<div><img src="../guo.jpg" alt=""></div>
<div><img src="../guo2.jpg" alt=""></div>
<div><img src="../guo3.jpg" alt=""></div>
<div><img src="../guo4.jpg" alt=""></div>
<div><img src="../guo5.jpg" alt=""></div>
<div><img src="../guo6.jpg" alt=""></div>
<div><img src="../guo7.jpg" alt=""></div>
<div><img src="../guo8.jpg" alt=""></div>
<div><img src="../guo9.jpg" alt=""></div>
<div><img src="../guo10.jpg" alt=""></div>
<div><img src="../guo11.jpg" alt=""></div>
<div><img src="../guo12.jpg" alt=""></div>
<div><img src="../guo13.jpg" alt=""></div>
<div><img src="../guo14.jpg" alt=""></div>
<div><img src="../guo15.jpg" alt=""></div>
<div><img src="../guo16.jpg" alt=""></div>
</body>
打开chrome的network可以发现,由于并发数的限制,最后一张图片得等前面的几个连接执行完毕,才能加载,因此有了很长的waterfull时间
3.8 数据协商
客户端请求时会声明希望拿到的数据格式和限制,服务端根据请求头返回不同的数据
1. Accept
声明想要的数据类型,[主类型]/[子类型] ,如text/html,image/jpg
2、Accept-Encoding
声明进行传输的编码方式,主要是数据压缩的算法,如gzip, deflate, br
3、Accept-Lanuage
声明希望返回信息的语言,如zh-CN,zh;q=0.9(q表权重,0~1)
4、User-Agent
声明浏览器和操作系统的相关信息
2. Content
1、Content-Type
声明返回的数据格式,如’X-Content-Type-Options’:‘nosniff’,可阻止浏览器自行猜测返回数据类型而引发的安全问题。
不同的文件后缀对应不同的Content-Type
2、Content-Encoding
声明返回的编码方式,即数据压缩
3、Content-Lanuage
声明返回的语言
这里先实践下在请求头中声明Content-Type和Content-Encoding。
先上代码,server.js:
const http = require('http')
const fs = require('fs')
const zlib = require('zlib')
http.createServer(function (request, response) {
console.log('request come', request.url)
const html = fs.readFileSync('index.html')
response.writeHead(200, {
'Content-Type': 'text/html',
'Content-Encoding': 'gzip' //声明以gzip的方式传输
})
response.end(zlib.gzipSync(html))
// response.end(html)
}).listen('2333')
console.log('server listening on 2333')
上述代码在请求头重声明了Content-Type和Content-Encoding,那么Content-Type我们肯定很好理解,他告诉了浏览器我们文本的格式是什么。
接下来我们先看下在运行node server.js启动之后,Network的截图。红框中上面的数字是文件在传输过程中加上HTTP传输信息后的体积,下面的数字则是body中内容的体积。那么在Content-Type声明gzip压缩方式,开启gzip压缩后,传输时的体积会比body的体积更小,从而加快传输的速度。
3.9redirect
重定向
通过 url 访问某个路径请求资源时,发现资源不在 url 所指定的位置,这时服务器要告诉浏览器,新的资源地址,浏览器再重新请求新的 url,从而拿到资源。
若服务器指定了某个资源的地址,现在需要更换地址,不应该立刻废弃掉 url,如果废弃掉可能直接返回 404,这时应该告诉客户端新的资源地址。
node server.js启动,发现url路径自动带了/new。
//server.js
const http = require('http')
const fs = require('fs')
http.createServer(function (request, response) {
console.log('request come', request.url)
if (request.url === '/') {
response.writeHead(302, {
'Location': '/new'
})
response.end()
} else if (request.url === '/new') {
response.writeHead(200, {
'Content-Type': 'text/html'
})
response.end(`<div>this is new Content</div>`)
}
}).listen('2333')
console.log('server listening on 2333')
可以看到根据定义的302临时重定向,页面自动的加载到了/new路由下的内容。
但如果我们需要永久重定向,把status code 改成301即可。
Redirect 301 和 302 的区别
302 临时跳转,每次请求仍然需要经过服务端指定跳转地址
301 永久跳转
302的情况
每次访问 locahost:8888,都要经过服务端跳转,服务端通过 console.log 可以看到 / → /new 两次请求。
const http = require('http')
http.createServer(function (request, response) {
console.log('request come', request.url)
if (request.url === '/') {
response.writeHead(302, {
'Location': '/new'
})
response.end()
}
if (request.url === '/new') {
response.writeHead(200, {
'Content-Type': 'text/html',
})
response.end('<div>this is content</div>')
}
}).listen(8888)
console.log('server listening on 8888')
301 的情况
访问 locahost:8888,第一次经过服务端跳转,服务端通过 console.log 可以看到 / /new 两次请求;第二次 服务端 console.log 只显示 /new ,没有再次经过服务器指定新的 Location。
response.writeHead(301, {
'Location': '/new'
})
注意:使用 301 要慎重,一旦使用,服务端更改路由设置,用户如果不清理浏览器缓存,就会一直重定向。
设置了 301,locahost 会从缓存中读取,并且这个缓存会保留到浏览器,当我们访问 8888 都会进行跳转。此时,就算服务端改变设置也是没有用的,浏览器还是会从缓存中读取。
3.10 内容安全策略( CSP )
Content-Security-Policy 的作用:
使用白名单的方式告诉客户端(浏览器)允许加载和不允许加载的资源。
向服务器举报这种强贴牛皮鲜广告的行为,以便做出更加针对性的措施予以绝杀。
限制方式:
default-src 限制全局
指定资源类型
connect-src、manifest-src、img-src、font-src/media-src、frame-src、style-src、script-src…
更详细的文档请移步MDN的:内容安全策略( CSP )
- http/https 协议
1.0 协议缺陷:
无法复用链接,完成即断开,重新慢启动和 TCP 3次握手
head of line blocking: 线头阻塞,导致请求之间互相影响
1.1 改进:
长连接(默认 keep-alive),复用
host 字段指定对应的虚拟站点
新增功能:
断点续传
身份认证
状态管理
cache 缓存
Cache-Control
Expires
Last-Modified
Etag
2.0:
多路复用
二进制分帧层: 应用层和传输层之间
首部压缩
服务端推送
https: 较为安全的网络传输协议
证书(公钥)
SSL 加密
端口 443
TCP:
三次握手
四次挥手
滑动窗口: 流量控制
拥塞处理
慢开始
拥塞避免
快速重传
快速恢复
缓存策略: 可分为 强缓存 和 协商缓存
Cache-Control/Expires: 浏览器判断缓存是否过期,未过期时,直接使用强缓存,Cache-Control的 max-age 优先级高于 Expires
当缓存已经过期时,使用协商缓存
唯一标识方案: Etag(response 携带) & If-None-Match(request携带,上一次返回的 Etag): 服务器判断资源是否被修改,
最后一次修改时间: Last-Modified(response) & If-Modified-Since (request,上一次返回的Last-Modified)
如果一致,则直接返回 304 通知浏览器使用缓存
如不一致,则服务端返回新的资源
Last-Modified 缺点:
周期性修改,但内容未变时,会导致缓存失效
最小粒度只到 s, s 以内的改动无法检测到
Etag 的优先级高于 Last-Modified