目录
6. 统一资源定位器 Uniform Resource Locators
9. 缓存Cache-Control: (哈希值用于刷新浏览器缓存)
12. Content Security Policy (内容安全策略)
1. 了解部分有意义的头部信息
头部信息 | |
Catch-Control:max-age=100 | 静态资源缓存100s |
Content-Type,Content-Encoding 等 | 用来约束数据类型 |
Cookie | 保持会话信息 |
CORS | 实现跨域并保持安全性限制 |
2. 页面如何到达浏览器并展现给用户
Redirect(跳转)--> App catch(应用缓存)-->DNS(DNS查找)-->TCP (创建TCP链接)-->Request(发送请求)
-->Response(接受响应)
1. redirect : 对于浏览器已经记录过的301请求,直接跳转,属于纯客户端的行为。
2. cache: 根据资源是否有设置cache control ,然后判断缓存。如果超时则要重新请求缓存。
3. DNS查询:当通过浏览器请求一个web页面,浏览器会创建一个线程去处理这个请求,随后开始远程dns查找,远程dns服务器会将输入的URL对应的IP地址返回给浏览器。(DNS域名系统)
4. TCP连接: 三次握手 (HTTP请求是在TCP连接上发送的,一个TCP连接可以发送多个HTTP请求)
5. 发送请求: 数据经过代理服务器或代理服务器缓存直接读取。浏览器通过连接发送一个HTTP GET请求到Web服务端。
6. 接受响应。Web服务端找到请求的资源,然后在HTTP响应中将其返回,状态200表示响应正常。
常用状态码:
- 200: 服务端成功响应
- 301: 永久重定向
- 302: 临时重定向
- 403: 请求被拒绝
- 404: 服务端找不到请求的资源
- 500: 处理请求时出错
- 503: 服务不可用
- 504: 网关超时
最后,浏览器收到页面HTML,就开始解析并渲染页面内容
3. 网络协议分层
经典的五层网路模型:
物理层:定义物理设备如何传输数据(电脑硬件,网卡端口,光缆...)。
数据链路层: 在通信的实体间建立数据链路连接。
网络层:为数据在节点之间传输创建逻辑链路。
传输层: 向用户提供可靠的端对端(end-to-end)服务。更多情况下实用TCP/IP协议传输数据。
定义如何传输数据,传输数据的方式。
应用层:为应用软件提供了很多的服务。构建于TCP协议之上。
4. HTTP/1.1
持久连接:一个连接中在发送的请求后不关闭
pipeline: 可以通过声明,在同一个连接里发送多个请求
增加host:可以在同一台物理服务器跑多个不同的web服务
HTTP 2 : 所有数据以二进制传输,同一个连接里发送多个请求不再需要按照顺序,头信息压缩以及推送等提高效率的功能。
推送功能(请求html的同时,可以将html中需要引用到的JavaScript文件和css文件推送到客户端,实现html和css js文件的发送顺序是闭型的,提高整体的传输效率)
5. 三次握手
三次:为了验证服务端和客户端收发功能正常,防止服务端开启无用的连接。(网络服务器延迟等)
浏览器通过与远程web服务端的三次握手来建立一个TCP/IP请求。这个握手由浏览器与远程服务端之间的SYN(标志位),SYN-ACK,ACK消息组成。(SYN:同步序列编号)
图1 TCP 三次握手
6. 统一资源定位器 Uniform Resource Locators
scheme://host.domain:port/path/filename
说明:
- scheme - 定义因特网服务的类型。最常见的类型是 http
- host - 定义域主机(http 的默认主机是 www)
- domain - 定义因特网域名,比如 runoob.com
- :port - 定义主机上的端口号(http 的默认端口号是 80)
- path - 定义服务器上的路径(如果省略,则文档必须位于网站的根目录中)。
- filename - 定义文档/资源的名称
7. 创建一个最简单的web服务
server.js文件下创建如下内容:
const http = require('http')
http.createServer((request, response) => {
console.log('request', request.url)
response.end('123')
}).listen(8888)
console.log('server listening on 8888')
启动: node server.js
浏览器打开:localhost:8888, 查看内容
利用curl命令查看http报文信息
$ curl -v www.baidu.com
能得到报文头和header的内容:
* Rebuilt URL to: www.baidu.com/
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0* Trying 14.215.177.39...
* TCP_NODELAY set
* Connected to www.baidu.com (14.215.177.39) port 80 (#0)
> GET / HTTP/1.1
> Host: www.baidu.com
> User-Agent: curl/7.57.0
> Accept: */*
>
< HTTP/1.1 200 OK
< Accept-Ranges: bytes
< Cache-Control: private, no-cache, no-store, proxy-revalidate, no-transform
< Connection: Keep-Alive
< Content-Length: 2381
< Content-Type: text/html
< Date: Fri, 17 Aug 2018 05:07:47 GMT
< Etag: "588604cf-94d"
< Last-Modified: Mon, 23 Jan 2017 13:27:43 GMT
< Pragma: no-cache
< Server: bfe/1.0.8.18
< Set-Cookie: BDORZ=27315; max-age=86400; domain=.baidu.com; path=/
<
{ [1048 bytes data]
100 2381 100 2381 0 0 2381 0 0:00:01 --:--:-- 0:00:01 74406
8. 跨域请求
新建server.js: 创建请求
//在8888端口中请求了一个8887端口的内容
const http = require('http')
const fs = require('fs')
http.createServer((request, response) => { //创建请求
console.log('request', request.url)
const html = fs.readFileSync('test.html', 'utf8'); //同步读取html内容
response.writeHead(200, {
'Content-Type': 'text/html'
})
response.end(html) //响应内容
}).listen(8888)
console.log('server listening on 8888')
server2.js
const http = require('http')
http.createServer((request, response) => {
console.log('request', request.url)
// 允许跨域请求 写返回的状态
response.writeHead(200, {
'Access-Control-Allow-Origin': '*',
})
response.end('123')
}).listen(8887)
console.log('server listening on 8887')
test.html 中向8887端口发送请求:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8887/')
xhr.send()
</script>
</body>
</html>
发送预请求:(跨域限制:保证服务端的安全)
预请求 | |
---|---|
允许的方法 | GRT、POST、HEAD |
允许Content-Type | text/plain、multipart/formdata、 application/x-www-form-urlencoded |
通过options请求获得服务器的认可
server.js:
//相当于在8888端口中请求了一个8887端口的内容
const http = require('http')
const fs = require('fs')
http.createServer((request, response) => { //创建请求
console.log('request', request.url)
const html = fs.readFileSync('test.html', 'utf8'); //同步读取html内容
response.writeHead(200, {
'Content-Type': 'text/html'
})
response.end(html) //响应内容
}).listen(8888)
console.log('server listening on 8888')
server2.js:
const http = require('http')
http.createServer((request, response) => {
console.log('request', request.url)
// 允许跨域请求 写返回的状态
response.writeHead(200, {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'X-Test-Cors', //允许自定义的头
'Access-Control-Allow-Methods': 'PUT, POST, GET',
'Access-Control-Max-Age': '1000', //1000ms内不用再次发送预请求
})
response.end('123')
}).listen(8887)
console.log('server listening on 8887')
Access-Control-Max-Age 这个响应首部表示 preflight request (预检请求)的返回结果(即 Access-Control-Allow-Methods 和Access-Control-Allow-Headers 提供的信息) 可以被缓存多久。
9. 缓存Cache-Control: (哈希值用于刷新浏览器缓存)
可缓存性 | |
---|---|
public | 任何地方都可以对返回的内容缓存 |
private | 只有发起请求的地方可以 |
no-cache | 通过服务器端验证后才能使用本地缓存 |
到期 | |
max-age=<seconds> | 缓存还有多过期。 可以将max-age设置为0, 从而让每次访问时缓存都进行刷新。 |
max-stale=<seconds> | 可以使用多久的过期缓存 |
重新验证 | |
must-revalidata | 如果要提供过期的数据给客户端,则必须向服务器验证数据的新鲜度 |
缓存验证:
缓存验证 | |
---|---|
Last-Modified | '123' 标记此文件在服务器端最后被修改的时间 |
Etag | '777' 用于标示URL对象是否改变 |
no-store: 本地和代理服务器都忽略缓存,直接请求数据,下一次请求是200。
no-catch:通过服务器端验证后才能使用本地缓存,下一次请求是304。
使用:
- max-age: 缓存时间
if (request.url === '/script.js') {
response.writeHead(200, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=20',// 客户端缓存,20s内不会重新请求服务端获取资源
})
response.end('console.log("script loaded")')
}
问题:会导致在缓存时间内无法及时更新到客户端
解决方案:通常在实际生产中会将max-age设置比较长(一年),在项目构建的时候,会在将打包的资源加上哈希码(内容有变化,则哈希码也发生变化),那么客户端发起的请求就是一个新的请求,达到更新缓存的目的。
验证资源能否使用缓存:
- Last-Modified:上次修改时间,配合If-Modified-Since使用。(对比上一次修改时间,验证资源是否需要更新)
服务端在设置Last-Modified之后,浏览器在下一次请求的时候会在请求头中带上If-Modified-Since。
if (request.url === '/script.js') {
// const etag = request.headers['if-none-match']
response.writeHead(200, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=2000000, no-cache',
'Last-Modified': '123',
'Etag': '777'
})
response.end('console.log("script loaded twice")')
}
- Etag:通过数据签名验证,配合If-Match使用。(对比资源的签名判断是否需要缓存)
Etag与Catch-Control的区别:
- Catch-Control:max-age=30; 在缓存时间内,直接不发送请求
- Etag:发送请求有响应,但不下载响应体。
http1.1中,一个tcp连接最多并发6个http请求(谷歌浏览器中)。
http2 中能够在一个tcp连接中发送多个http请求。
10. Cookie和Session
Cookie:1. 通过Set-Cookie设置 2. 下次请求会自动带上 3.键值对,可以设置多个
属性:
Cookie属性 | |
---|---|
max-age和expires | 设置过期时间 |
Secure | 只在https的时候发送 |
HttpOnly | 无法通过document.cookie访问,保证用户安全 |
服务端server.js:
const http = require('http')
const fs = require('fs')
http.createServer((request, response) => { //创建请求
console.log('request', request.url)
if (request.url === '/') {
const html = fs.readFileSync('test.html', 'utf8'); //同步读取html内容
response.writeHead(200, {
'Content-Type': 'text/html',
'Set-Cookie': ['abc=123; max-age=2','id=456'], //可以以数组方式传递多个cookie 前面一个字段的有效时间为两秒
// 2s后abc=123这个数据就过期,request headers中就不会有这个信息出现
})
response.end(html) //响应内容
}
}).listen(8888)
console.log('server listening on 8888')
客户端test.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<div>content</div>
<script src="/script.js">
console.log(document.cookie)
</script>
</body>
</html>
11. 数据协商
客户端请求时会声明希望拿到的数据格式和限制,服务端根据请求头返回不同的数据
客户端请求 | |
---|---|
Accept | 声明想要的数据类型 [主类型]/[子类型] ,如text/html,image/jpg |
Accept-Encoding | 声明进行传输的编码方式 主要是数据压缩的算法,如gzip, deflate, br |
Accept-Language | 声明希望返回信息的语言 如zh-CN,zh;q=0.9(q表权重,0~1) |
Usrer-Agent | 声明浏览器和操作系统的相关信息 |
服务端返回 | |
---|---|
Content-Type | 声明返回的数据格式, 如'X-Content-Type-Options':'nosniff',可阻止浏览器自行猜测返回数据类型而引发的安全问题。 |
Content-Encoding | 声明返回的编码方式,即数据压缩 |
Content-Language | 声明返回的语言 |
表单发送到服务器时的三种编码方式:
enctype(编码方式) | |
---|---|
application/x-www-form-urlencoded | 默认的编码方式。但是在用文本的传输和MP3等大型文件的时候,使用这种编码就显得 效率低下。 |
multipart/form-data | 指定传输数据为二进制类型,比如图片、mp3、文件 |
text/plain | 纯文体的传输。空格转换为 “+” 加号,但不对特殊字符编码 |
将表单的enctype(编码方式)定义为"multipart/form-data":适用于上传文件的方式
<form method="POST" enctype="multipart/form-data">
<input type="text" name="name">
<input type="password" name="psd">
<input type="submit">
</form>
提交表单之后:能够在浏览器请求头中得到如下信息:
Request Header:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryCoZcoYObU8Ubn5U2
boundary后的字符串用于分割表单中的每一个部分。
------WebKitFormBoundaryCoZcoYObU8Ubn5U2
Content-Disposition: form-data; name="name"
8月5
------WebKitFormBoundaryCoZcoYObU8Ubn5U2
Content-Disposition: form-data; name="psd"
sss
------WebKitFormBoundaryCoZcoYObU8Ubn5U2--
控制台中的connection ID 代表TCP连接的ID,可以用来区分是否用的同一个连接。
connection: close, //没有重复利用TCP连接,每次TCP请求发送完就关闭了
(HTTP请求是在TCP连接上发送的,一个TCP连接可以发送多个HTTP请求)
12. Content Security Policy (内容安全策略)
1. 限制资源获取 2. 报告资源获取越权
CSP 大大增强了网页的安全性。
两种方法启动CSP:
1. 一种是通过 HTTP 头信息的Content-Security-Policy
的字段
Content-Security-Policy: script-src 'self'; object-src 'none';
2. 通过网页的<meta>
标签。
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'">
( 脚本:只信任当前域名 <object>
标签:不信任任何URL,即不加载任何资源)
只允许通过http,https方式加载src资源:
'Content-Security-Policy': 'default-src http: https:'
只能根据本域名下的JavaScript内容进行加载:
'Content-Security-Policy': 'default-src self'
13. nginx设置代理缓存
# 代理缓存: 有一个用户请求成功之后,其他用户都可以直接使用该缓存
# keys_zone:缓存空间(shared memory zone)的名称和大小
proxy_cache_path cache levels=1:2 keys_zone=my_cache:10m;
server {
listen 80;
# listen [::]:80 default_server;
server_name test.com;
# return 302 https://$server_name$request_uri;
location / {
proxy_cache my_cache;
proxy_pass http://127.0.0.1:8888;
proxy_set_header Host $host;
}
}
14. nginx缓存验证头信息
if (request.url === '/data') {
response.writeHead(200, {
'Cache-Control': 'max-age=2, s-maxage=20, private',
'Vary': 'X-Test-Cache' // 验证头信息一样才进行缓存,场景: 判断不同的设备进行不一样的缓存
})
wait(2).then(() => response.end('success'))
}
15. https
加密: 使用公钥加密传输信息(就算信息被截取也无法获取内容),再通过私钥进行解密
16. nginx部署https服务
- 生成证书
openssl req -x509 -newkey rsa:2048 -nodes -sha256 -keyout localhost-privkey.pem -out localhost-cert.pem
mac 电脑不能一路回车下去,空字符会导致一些证书无法正常生成的问题。error, no objects specified in config file
2. nginx 配置
路径: /usr/local/etc/nginx/nginx.conf
server {
# https默认使用端口 ,开启https加密算法ssl
listen 443 ssl;
server_name test.com;
ssl_certificate_key ./certs/localhost-privkey.pem;
ssl_certificate ./certs/localhost-cert.pem;
location / {
proxy_pass http://127.0.0.1:8888;
}
}
3. hosts配置
路径:/etc/hosts
127.0.0.1 localhost
127.0.0.1 test.com
访问:https://test.com,会提示不是安全的链接,就证明配置成功(因为浏览器所需要的https证书要经过认证的,而不是本地直接生成使用)。
- 配置http的情况下直接跳转https。server_name即test.com,request_uri即访问的url
server {
listen 80;
listen [::]:80 default_server;
server_name test.com;
return 302 https://$server_name$request_uri;
}
17. http2
- HTTP/1.1
- 纯文本形式的报文
- 引入了持久链接,即TCP默认不关闭,可以被多个请求复用
- 引入pipeline管道机制,一个TCP连接,可以同时发送多个请求
- 管道化要求服务端必须按照请求发送的顺序返回响应,会导致队头阻塞,可以使用HTTP2的多路复用解决
- HTTP/2
- 头部压缩
- HTTP/2 采用二进制传输数据,协议解析起来更高效
- 多路复用
- 分帧传输:在一个tcp连接上并发发送多个请求
- Server Push 服务器推送
- nginx 配置
# 使用http2需要在https的条件下
server {
listen 443 http2;
server_name test.com;
# server push
http2_push_preload on;
...
}
2. 服务端配置
http.createServer(function (request, response) {
response.writeHead(200, {
'Content-Type': 'text/html',
'Connection': 'keep-alive',
// 绝对路径;图片类型;需要服务端推送
'Link': '</test.jpg>; as=image; rel=preload'
})
response.end(html)
})
3. 终端测试http服务
-k: 忽略证书问题。 使用nginx代理服务器能够自动判断浏览器支持的http类型。