学习以下知识点:
- 1.跨越
- 2.CORS预请求
- 3.缓存
- 4.cookie
- 5.长链接
- 6.重定向
- 7.数据协商
- 8.CSP
跨域
看下面代码
1.server.js
// 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('test.html', 'utf8')
response.writeHead(200, {
'Content-Type': 'text/html'
})
response.end(html)
}).listen(8888)
console.log('server listening on 8888')
2.server2.js
// server2.js
const http = require('http')
http.createServer(function (request, response) {
console.log('request come', request.url)
response.end('123')
}).listen(8887)
console.log('server listening on 8887')
3.test.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>
<!-- cors1 -->
<script>
var xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8887/')
xhr.send()
</script>
</html>
从上面代码中可以看出,server.js中去读取test.html中内容,跑在8888端口
在test.html中,向8887端口发送http请求。
打开控制台看到:
这就是产生了跨域。
解决跨域
通过cors解决跨域,在server2.js中添加下面内容 Access-Control-Allow-Origin 头
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(8887)
console.log('server listening on 8887')
'Access-Control-Allow-Origin': '*', 代表允许所有第三方请求。但是实际中不会,不安全。
重新打开控制台,不会出现报错了。
我们也可以通过jsonp来解决跨域问题。
修改下test.html 将请求放到script标签中的src属性中。这是因为浏览器允许script link这些标签跨域。
<!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 src='http://127.0.0.1:8887/'>
</script>
</html>
CORS预请求
- 1.跨域的时候,默认允许的方法只有get post head请求。其他请求会有预请求。
- 2.Content-Type中除了text/plain ,multipart/form-data, application/x-www-form-urlencoded ,其他都会有预请求。
- 3.header头限制
我们自定义下header,修改下test.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>
<!-- cors1 -->
<!-- <script>
fetch('http://localhost:8887', {
method: 'POST',
headers: {
'X-Test-Cors': '123'
}
})
</script> -->
</html>
打开控制台如下报错:
意思是自定义header,不允许跨域。这也就是CORS预请求。
可以看请求,代码中发送的是POST请求,但是图中发送的是options请求
可以通过在服务端中添加对应的header头:
// server2.js
const http = require('http')
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', // 添加自定义头
})
response.end('123')
}).listen(8887)
console.log('server listening on 8887')
重新运行,看下浏览器。此时发送了两个请求,先是options请求,再是post请求。控制台也不会报错了。
下面再试下请求方式的预请求。
我们修改下test.html中的请求方式,将POST改为PUT。重新允许,看下浏览器,控制台报错,只发送了options请求。
我们可以通过在服务端添加允许的请求方式头
// server2.js
const http = require('http')
http.createServer(function (request, response) {
console.log('request come', request.url)
response.writeHead(200, {
'Access-Control-Allow-Origin': 'http://127.0.0.1:8888',
'Access-Control-Allow-Headers': 'X-Test-Cors',
'Access-Control-Allow-Methods': 'POST, PUT, DELETE',
})
response.end('123')
}).listen(8887)
console.log('server listening on 8887')
重新运行。此时再看下浏览器。控制台不会报错。先有options请求,再有put请求。
下面我们再添加一个max-age头,如下:
const http = require('http')
http.createServer(function (request, response) {
console.log('request come', request.url)
response.writeHead(200, {
'Access-Control-Allow-Origin': 'http://127.0.0.1:8888',
'Access-Control-Allow-Headers': 'X-Test-Cors',
'Access-Control-Allow-Methods': 'POST, PUT, DELETE',
'Access-Control-Max-Age': '1000'
})
response.end('123')
}).listen(8887)
console.log('server listening on 8887')
这个头代表,开始会先触发options请求,再触发put请求。1s过后,只有put请求。也就是告诉浏览器这个请求不用再预请求了。
缓存
可缓存性:Cache-Control
- 1.public:整个http请求节点都可以缓存,包括代理缓存
- 2.private:只有发起的浏览器才可以缓存
- 3.no-cache:可以在本地缓存,但是需要服务器认证
- 4.no-store:任何节点都不可缓存
到期:
- max-age = 表示缓存多少s过期
- s-max-age = 可以代替max-age 但是只在代理服务器上才生效。在代理服务器,两个同时存在,会读取s-max-age
下面看下max-age示例:
test.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 src="/script.js"></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'
})
response.end(html)
}
if (request.url === '/script.js') {
response.end('console.log("script loaded")')
}
}).listen(8888)
console.log('server listening on 8888')
运行node server.js。打开控制台,可以看到:打印了 script loaded
我们在server.js中加入Cache-Control : max-age = 20 来缓存
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=20'
})
response.end('console.log("script loaded")')
}
}).listen(8888)
console.log('server listening on 8888')
第一次刷新:看到size为228B,时间是3ms
过了20s后,再次刷新,可以看到这次是从memory cache中读取,而且时间是0ms
接下来,我看下打印的内容:
1.在不加入cacae-controle情况下,打印如下:
2.加入cache-controle,20s内,打印如下:
3.修改打印的内容,因为设置的max-age为20s,缓存时间过去了,所以打印新内容,打印如下:
4.我们将max-age改为200,将内容改回原先内容,重新运行打印如下:说明还是从缓存读取
实际工作中,cache-control是客户端缓存,一般将max-age设置很长时间。但是项目内容改变了,因为缓存没有更新。此时一般会使用时间戳或者内容hash来更新项目内容。
下面看下缓存的流程:
验证能否使用缓存的头:
1.Last-Modified
2.ETag
Last-Modified:
1.上次修改时间
2.配合If-Modified-Since使用
3.对比上次修改时间来验证资源是否需要更新
ETag:
1.数据签名
2.内容改变,数据签名改变,对比两个数据签名是否一样,从而判断是否缓存
3.配合If-Match使用
下面通过代码演示一下:
test.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 src="/script.js"></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'
})
response.end(html)
}
if (request.url === '/script.js') {
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")')
}
}).listen(8888)
console.log('server listening on 8888')
代码中设置了Cache-Control: max-age=2000000, no-cache , 'Last-Modified': '123', 'Etag': '777'
第一次刷新页面看下,在response header中返回:
当第二次刷新的时候,request header就会分别带上对应的header,来判断是否命中缓存
可以看到,Etag和If-None-Match一样,Last-Modified 和If-Modified-Since 一样。
但是,第二次刷新还是请求了服务器,返回200
此时,可以通过在服务端判断Etag来确定是否可以读取缓存
修改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'
})
response.end(html)
}
if (request.url === '/script.js') {
const etag = request.headers['if-none-match']
if (etag === '777') {
response.writeHead(304, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=2000000, no-cache',
'Last-Modified': '123',
'Etag': '777'
})
response.end()
} else {
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")')
}
}
}).listen(8888)
console.log('server listening on 8888')
此时重新运行,如下:
代码中当命中Etag策略的时候,服务端没有返回内容,浏览器没有去请求服务器,而是返回304,内容确返回了缓存中的内容。
现在把server.js中的no-cache去掉,重新运行。
控制台打印如下:
当再次刷新:是从缓存中读取内容。
现在将no-cache改为no-store,打印如下:
当再次刷新:并没有从缓存中读取。这也就是no-cache和no-store的区别。
cookie
- 1.通过Set-Cookie设置
- 2.下次请求就会带上
- 3.max-age和expires设置过期时间
- 4.Secure只在https的时候发送
- 5.HttpOnly 无法通过 document.cookie访问
下面代码演示下:
test.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>
<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')
在服务端通过Set-Cookie设置了id=123,当首次访问的时候,可以看到response header中显示:
同时控制台,也能打印出cookie
当第二次访问的时候,request header 就会自动带上cookie,如下:
也可以设置多个cookie,修改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','abc=456']
})
response.end(html)
}
}).listen(8888)
console.log('server listening on 8888')
重新运行,可以看到:设置了两个cookie
同样第二次访问的时候,request header自动带上所有cookie,多个cookie以分号隔开。
通过max-age给cookie设置过期时间,修改代码如:给id=123,设置过期时间为2s
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; max-age=2', 'abc=456']
})
response.end(html)
}
}).listen(8888)
console.log('server listening on 8888')
重新运行,第一次打开如下:
在2s内,重新刷新浏览器打开:此时有id=123
在2s后,重新刷新浏览器打开:此时只有abc=456,没有id=123了
我们知道,不同域名,cookie不能共享。但是可以做到二级域名共享cookie。使用domain。
在abc=456 这个cookie设置domain,表示二级域名是test.com,可以共享cookie
'Set-Cookie': ['id=123; max-age=2', 'abc=456;domain=test.com']
长链接
浏览器发送一个http请求,http1.0会直接关闭tcp链接。现在发送http请求,和服务端协商是否关闭TCP链接,
使用connection:keep-alive 来保持tcp不关闭。现在浏览器默认是keep-alive
代码演示:
test.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>
<img src="/test1.jpg" alt="">
<img src="/test2.jpg" alt="">
<img src="/test3.jpg" alt="">
<img src="/test4.jpg" alt="">
<img src="/test5.jpg" alt="">
<img src="/test6.jpg" alt="">
<img src="/test7.jpg" alt="">
<img src="/test11.jpg" alt="">
<img src="/test12.jpg" alt="">
<img src="/test13.jpg" alt="">
<img src="/test14.jpg" alt="">
<img src="/test15.jpg" alt="">
<img src="/test16.jpg" alt="">
<img src="/test17.jpg" alt="">
<img src="/test111.jpg" alt="">
<img src="/test112.jpg" alt="">
<img src="/test113.jpg" alt="">
<img src="/test114.jpg" alt="">
<img src="/test115.jpg" alt="">
<img src="/test116.jpg" alt="">
<img src="/test117.jpg" alt="">
</body>
</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('test.html', 'utf8')
const img = fs.readFileSync('test.jpg')
if (request.url === '/') {
response.writeHead(200, {
'Content-Type': 'text/html',
})
response.end(html)
} else {
response.writeHead(200, {
'Content-Type': 'image/jpg',
'Connection': 'keep-alive' // or close
})
response.end(img)
}
}).listen(8888)
console.log('server listening on 8888')
首先把浏览器设置如下:
刷新浏览器:可以看到前两个connection ID一样,与网络环境也有关系。
将connection设置为close,即可关闭长链接。
重定向
301:永久重定向
302:临时重定向
通过location头设置重定向
代码演示:
const http = require('http')
http.createServer(function (request, response) {
console.log('request come', request.url)
if (request.url === '/') {
response.writeHead(302, { // or 301
'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')
打开浏览器可以看到: 先访问根路径,通过location,重定向到/new路径
再看下301和302,
设置为302,第一次访问浏览器,看到先访问根路径,再访问/new
刷新浏览器,同样是先访问根路径,再访问/new
设置为301,首次访问,先访问根路径,再访问/new
再次刷新,访问,直接访问/new路径
数据协商
Request header:
- Accept:请求什么类型内容
- Accept-Encoding:压缩方式
- Accept-Language:语言
- User-Agent:用户浏览器信息
Response Header:
- Content:返回的内容
- Content-Type:类型
- Content-Encoding:压缩方式
- Content-Language:语言
CSP
developer.mozilla.org/zh-CN/docs/…
以下内容代码放到了github:github.com/liangchaofe…
下载代码,可以直接运行测试。