HTTP 各种协议
CORS 跨域
// server-1.js
const http = require('http')
http.createServer(function (req, res) {
console.log('request come', req.url)
const html = fs.readFileSync('test.html', 'utf8')
res.writeHead(200, {
'Content-Type': 'text/html'
})
res.end(html)
}).listen(8888)
console.log('server listening on 8888')
// server-2.js
const http = require('http')
http.createServer(function (req, res) {
console.log('request come', req.url)
res.end('hello world')
}).listen(8887)
console.log('server listening on 8887')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
const xhr = new XMLHttpRequest()
xhr.open('GET', 'http://127.0.0.1:8887/')
xhr.send()
</script>
</body>
</html>
在 8888 服务中,浏览器会出现跨域问题(服务器不会因为跨域而不响应,而是浏览器做的安全策略),解决问题如下:
// server-2.js
const http = require('http')
http.createServer(function (req, res) {
console.log('request come', req.url)
res.writeHead(200, {
//'Access-Control-Allow-Origin': '*', // 任何服务都可访问
//'Access-Control-Allow-Origin': 'http://baidu.com', // 允许百度可访问
'Access-Control-Allow-Origin': 'http://127.0.0.1:8888/'
})
res.end('hello world')
}).listen(8887)
console.log('server listening on 8887')
难道真要使用 'Access-Control-Allow-Origin': '*'
? 答案是 JSONP(图片、iframe、script[src] 等等是可以跨域的)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="http://localhost:8887/"></script>
</body>
</html>
'Access-Control-Allow-Origin': 'http://127.0.0.1:8888/'
如果有多个域呢?根据 request.url 区分然后分别在 head 设置 'Access-Control-Allow-Origin'
跨域请求的限制及预请求验证
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
fetch('http://127.0.0.1:8887/', {
methods: 'POST',
headers: {
'X-Test-Cors': '123'
}
})
</script>
</body>
</html>
以上代码浏览器会抛出错误:Request header field X-Test-Cors is not allowed by Access-Control-Allow-Headers in preflight response.
但是这个预请求(preflight
)是发送成功的(Request Methods: OPTIONS
),是浏览器做处理进行报错了。浏览器怎么判断的?是根据响应头上的 Access-Control-Allow-Headers: xxx
判断的。如果允许自定义 header 的请求发送成功,那么这里需要设置新的请求头 Access-Control-Allow-Headers: X-Test-Cors
。
CORS 预请求:
- 允许的方法:GET、POST、HEAD (除了这三个不需要,其他都要预请求进行验证)。 根据上面设置
Access-Control-Allow-Headers
使得自定义请求发送成功,那么也可以设置'Access-Control-Allow-Methods': 'PUT, POST, Delete'
使得 PUT 和 Delete 方法可以发出请求 - 允许的
Content-Type :text/plain | multipart/form-data | application/x-www-form-urlencoded
(除了这三个,其他都需要预请求进行验证)。 - 其他限制:请求头限制(设置了自定义的 header 字段),有哪些请求头不允许,这里官方文档有解释;XMLHttpRequestUpload 对象均没有注册任何事件监听器;请求中没有使用 ReadableStream 对象。 以上几点被称为复杂请求。
什么是预请求?
预请求就是复杂请求(可能对服务器数据产生副作用的 HTTP 请求方法,如 PUT 和 Delete 都会对服务器数据进行更修改,所以要先询问服务器,这也是为什么需要预请求)。
跨域请求中,浏览器自发的发起的预请求,浏览器会查询到两次请求,第一次的请求参数是 options ,以检测试实际请求是否可以被浏览器接受。 注意:Access-Control-Max-Age: 1000
表示这种允许跨域的请求在 1000 秒不需要再次预请求验证了,直接发起正式的请求就可以了。
带凭证的请求
跨域的 Ajax 请求是不会发送基于 Cookie 和 HTTP 认证信息的身份凭证,如果想要跨域的 Ajax 请求能够发送凭证信息需要在浏览器和服务器两端分别设置。
// browser
// 本质是在设置 xhr.withCredentials = true
axios.defaults.withCredentials = true
// server
res.setHeader('Access-Control-Allow-Credentials', true)
注意:当响应头设置 Access-Control-Allow-Credentials
为 true 后,Access-Control-Allow-Credentials
不能设置为 * 必须设置为具体源。
缓存 Cache-Control
参考文章 HTTP的缓存机制以及原理 - 掘金 (juejin.cn)
可缓存性
private
:私有缓存只能用于单独的用户(默认值,就是发起请求的用户),不能被代理服务器缓存;public
:共享缓存可以被多个用户使用;no-cache
:可以在本地或 proxy 缓存(如果服务器返回说可以使用本地的缓存,才可以真正使用本地缓存),需要经过服务器验证。到期
max-age=<seconds>
:缓存的过期时间;s-maxage=<seconds>
:代替max-age
,只会在代理服务器生效(如果和max-age
同时设置了,优先读取代理服务器的s-maxage
);max-stale=<seconds>
:max-age
过期了,如果返回资源有max-stale
设置,那么max-stale
是发起请求这一方主动带到头中,意思是即便缓存过期(超出max-age
)了,只要在max-stale
时间内可以使用过期的缓存,不需要到服务器请求新的资源。这个在浏览器里使用不到,一般请求和静态资源请求过程中并不会设置,只有在发起端有用,在服务端返回内容中设置它没有作用;重新验证
must-revalidata
:指定如果页面是过期(max-age
)的,则去服务器进行重新获取。proxy-revalidata
:用于缓存服务器中,指定如果页面或资源是过期(max-age
)的,则去源服务器进行重新获取。 这两个指令并不常用,就不做过多的讨论了。其他
no-store
:一切都不缓存(Firfox需要cache-control:no-cache; no-store;),永远去服务器获取新资源;no-transform
:用于 proxy 服务器,告诉 proxy 服务器不要随意改动原始内容(比如不允许压缩等等)
代码示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./script.js"></script>
</body>
</html>
// 对 script 文件缓存
const http = require('http')
http.createServer(function (req, res) {
console.log('request come', req.url)
if(request.url === '/') {
const html = fs.readFileSync('test.html', 'utf8')
res.writeHead(200, {
'Content-Type': 'text/html'
})
res.end(html)
}
if(request.url === '/script.js') {
res.writeHead(200, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=20', // 20秒
})
res.end('console.log("load script")')
// res.end('console.log("load script changed")')
}
}).listen(8888)
console.log('server listening on 8888')
如何处理 script 在缓存时间内再次更改时,请求的 script 是上次的内容?最佳方案是 对 script 文件名使用 content hash ,这样可以确保浏览的内容都是最新的。
问题:CORS 如何缓存 options 预检请求? 增加响应头 Access-Control-Max-Age
即可,这样在一定的时间内,后续请求就不会再去发 options 预检请求了。
缓存(资源)验证
参考文章 浏览器HTTP缓存机制 - 掘金 (juejin.cn)
验证头:
Last-Modified
意思是上次修改时间,配合If-Modified-Since
或If-Unmodified-Since
使用,如果某个请求返回的头有Last-Modified
,下一次这个请求会携带If-Modified-Since
或If-Unmodified-Since
访问服务器资源时,服务器会检查Last-Modified
,如果Last-Modified
的时间早于或等于If-Modified-Since
则会返回一个不带主体的304
响应,否则将重新返回资源,就是对比资源上次修改时间以验证资源是否需要更新。 If-Modified-Since 是一个请求首部字段,并且只能用在 GET 或者 HEAD 请求中。Etag 更加严格的验证,主要通过数字签名,对资源内容会产生唯一的签名,只要有任何修改,会产生新的签名。配合
If-Match
或If-None-Match
使用,对应的值就是Etag
给的值,下次请求就携带If-Match
数据对比资源的签名判断是否使用缓存。ETag
优先级比Last-Modified
高,同时存在时会以ETag
为准。
代码示例:
// 对 script 文件缓存
const http = require('http')
http.createServer(function (req, res) {
console.log('request come', req.url)
if(request.url === '/') {
const html = fs.readFileSync('test.html', 'utf8')
res.writeHead(200, {
'Content-Type': 'text/html'
})
res.end(html)
}
if(request.url === '/script.js') {
res.writeHead(200, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=3600, no-cache',
'Last-Modified': '20220312',
'Etag': 'asd32gfhs2'
})
const etag = req.headers['if-none-match']
if(etag === 'asd32gfhs2') {
res.writeHeader(304, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=3600, no-cache',
'Last-Modified': '20220312',
'Etag': 'asd32gfhs2'
})
res.end('')
} else {
res.writeHeader(200, {
'Content-Type': 'text/javascript',
'Cache-Control': 'max-age=3600, no-cache',
'Last-Modified': '20220312',
'Etag': 'asd32gfhs2'
})
res.end('console.log("load script")')
}
}
}).listen(8888)
console.log('server listening on 8888')
问题:尝试去除 no-cache
或设置成 no-store
? 前者请求从 memory cache
获取,后者没有缓存
Cookie 和 Session
- Cookie 通过服务端设置
Set-Cookie
,浏览器会进行保存,下一次请求会自动在请求头中携带 Cookie 数据,它还是以键值对的方式存在,可以设置多个。
Cookie 有哪些属性呢? max-age
和 expires
设置过期时间;Secure
只在使用 HTTPS 的时候发送;设置 HttpOnly
后 JS 无法通过 document.cookie 访问内容(考虑到安全问题,比如 CSRF
攻击)。
Cookie 使用方式:
const http = require('http')
http.createServer(function (req, res) {
console.log('request come', req.url)
if(request.url === '/') {
const html = fs.readFileSync('test.html', 'utf8')
res.writeHead(200, {
'Content-Type': 'text/html',
'Set-Cookie': ['id=121212', 'uuid=34']
})
res.end(html)
}
}).listen(8888)
console.log('server listening on 8888')
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cookie</title>
</head>
<body>
<script>
console.log(document.cookie) // id=121212
</script>
</body>
</html>
在 Network 面板看到 Response Headers
里面有两个 Set-Cookie
。
Cookie 是有过期时间的,如果没有设置过期时间,会在浏览器关闭之前一直有效,关闭浏览器再次打开访问,在 Network 面板看到并没有携带 Cookie ,刷新会出现。
res.writeHead(200, {
'Content-Type': 'text/html',
'Set-Cookie': ['id=121; max-age=10', 'uuid=34'] // 10秒
})
10 秒后,再次请求,在 Network 面板看到 Request Headers
只有Cookie: uuid=34
,没有 id=121
。 如果再次设置 HttpOnly
会怎样?
res.writeHead(200, {
'Content-Type': 'text/html',
'Set-Cookie': ['id=121; max-age=10', 'uuid=34; HttpOnly']
})
<script>
console.log(document.cookie) // id=121 没有 uuid=34
</script>
Cookie 的 domain 设置: 用于指定域可以访问 Cookie 数据,其他域不可访问。 问题:b.test.com
访问 a.test.com
的 Cookie 怎么办?那 a.test.com
访问 test.com
的 Cookie 怎么办(二级域名访问一级域名)?
// 开发环境下 设置一下 hosts
// 127.0.0.1 a.test.com
// 127.0.0.1 b.test.com
// 127.0.0.1 test.com
const host = req.headers.host
// 问题1
if(host === 'a.test.com') {
res.writeHead(200, {
'Content-Type': 'text/html',
'Set-Cookie': [
'id=121; max-age=10',
'uuid=34'
]
})
}
// 问题2
if(host === 'test.com') {
res.writeHead(200, {
'Content-Type': 'text/html',
'Set-Cookie': [
'id=121; max-age=10',
'uuid=34; domain=test.com' //二级可以访问一级
]
})
}
- Session 一般是使用 Cookie 保存 Session,把用户登录的信息存放到请求的 Cookie 中,保存到 Session 中,下一次请求时读取 Cookie 值与 Session 对比。
HTTP 长连接
HTTP 的请求建立在 TCP 连接之上,TCP 的连接分为长连接和短连接(相应的文章 TCP 长连接与短连接 - 知乎 (zhihu.com))。 打开 baidu.com 在 Network 面板看到有不少相同的 Connection ID
(复用 TCP 连接)。HTTP1.1 中连接在 TCP 连接上面发送请求是有先后顺序的,比如说有 10 个请求不可以并发地在一个 TCP 连接上发送,在这一个 TCP 连接上只能一个接着一个发送请求(串行),好在浏览器可以并发地创建 TCP 连接(Chrome 一次性并发 6 个,如果有 10 个,剩下的 4 个需要等前面 6 个中的 TCP 连接空出来使用)。 怎么知道这个连接是长连接?Connection: Keep-Alive
代码示例:
<body>
<img src="./img-1.png" alt="" />
<img src="./img-2.png" alt="" />
<img src="./img-3.png" alt="" />
<img src="./img-4.png" alt="" />
<img src="./img-5.png" alt="" />
<img src="./img-6.png" alt="" />
<img src="./img-7.png" alt="" />
</body>
const http = require('http')
http.createServer(function (req, res) {
console.log('request come', req.url)
const html = fs.readFileSync('test.html', 'utf8')
const img = fs.readFileSync('img.png', 'utf8')
if(request.url === '/') {
res.writeHead(200, {
'Content-Type': 'text/html',
})
res.end(html)
} else {
res.writeHead(200, {
'Content-Type': 'image/png',
})
res.end(img)
}
}).listen(8888)
console.log('server listening on 8888')
截图如下:
稍微调整一下:
<body>
<img src="./img-1.png" alt="" />
<img src="./img-2.png" alt="" />
<img src="./img-3.png" alt="" />
<img src="./img-4.png" alt="" />
<img src="./img-5.png" alt="" />
<img src="./img-6.png" alt="" />
<img src="./img-7.png" alt="" />
<img src="./img-10.png" alt="" />
<img src="./img-20.png" alt="" />
<img src="./img-30.png" alt="" />
<img src="./img-40.png" alt="" />
<img src="./img-50.png" alt="" />
<img src="./img-60.png" alt="" />
<img src="./img-70.png" alt="" />
<img src="./img-11.png" alt="" />
<img src="./img-21.png" alt="" />
<img src="./img-31.png" alt="" />
<img src="./img-41.png" alt="" />
<img src="./img-51.png" alt="" />
<img src="./img-61.png" alt="" />
<img src="./img-71.png" alt="" />
</body>
结果更明显,有不少连接复用之前的 TCP 连接。请求头里面 Request Headers
的 connection 是 keep-alive ;如果为 close ,则表示这个请求连接完成,TCP 连接会关闭掉。 看一下 close 的效果:
if(request.url === '/') {
res.writeHead(200, {
'Content-Type': 'text/html',
'Connection': 'close',
})
res.end(html)
} else {
res.writeHead(200, {
'Content-Type': 'image/png',
'Connection': 'close',
})
res.end(img)
}
结果是没有重复的 Connection ID
。
在 HTTP2 中,会复用信道,在一个 TCP 连接上面可以并发地发送 HTTP 请求,或许请求一个网页只需要一个 TCP 连接就够了(可以打开 google.com 查看 Connection ID
)。
数据协商
客户端发送请求给服务端,客户端会声明请求希望拿到的数据的格式和限制,服务端会根据请求头信息,来决定返回的数据。 分为两类:请求(Accept)和返回(Content)。
请求类: Accept
(指定声明想要的数据类型,根据 MIME-Type 类型决定)。 Accept-Encoding
定义以什么样的数据编码进行传输,主要限制服务端如何进行数据的压缩(压缩算法很多)。 Accept-Language
指定使用什么语言。 User-Agent
表示浏览器相关的信息,移动端和 PC 端的浏览器不一样。
返回类: Content-Type
对应 Accept
,Accept
可以设置好几种数据类型,Content-Type
从中选择真正的数据类型进行返回。 Content-Encoding
对应 Accept-Encoding
。 Content-Language
对应 Accept-Language
。
代码示例:
// 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('test.html') // 这里没有设置 utf8,读出来是 buffer,zlib.gzipSync 需要这个 buffer 格式
response.writeHead(200, {
'Content-Type': 'text/html', // 可以搜索 MIME Type
// 'X-Content-Options': 'nosniff' // 很少使用
// 'Content-Encoding': 'gzip' // 还有 deflate、br
})
// response.end(zlib.gzipSync(html))
response.end(html)
}).listen(8888)
console.log('server listening on 8888')
如何发送 Content-Type ?
<body>
<form action="/form" enctype="application/x-www-form-urlencoded">
<input type="text" name="name" />
<input type="password" name="password" />
<input type="submit" />
</form>
<form action="/form" method="POST" enctype="application/x-www-form-urlencoded">
<input type="text" name="name" />
<input type="password" name="password" />
<input type="submit" />
</form>
<form action="/form" id="form" method="POST" enctype="multipart/form-data">
<input type="text" name="name" />
<input type="password" name="password" />
<input type="file" name="file" />
<input type="submit" />
</form>
<!-- 上面的请求在浏览器里面会进行跳转,请求的具体信息看不到,所以下面脚本需要手动触发请求 -->
<script>
let form = document.getElementById('form')
form.addEventListener('submit', function(e) {
e.preventDefault()
let formData = new FormData(form)
fetch('/form', {
method: 'POST',
body: formData
})
})
</script>
</body>
使用 multipart/form-data
结果如下:
Request Headers
Content-Type: multipart/form-data; boundary=----WebKitFormBoundary39Ug3FSPIBvDYZd6
Request Payload
------WebKitFormBoundary39Ug3FSPIBvDYZd6
Content-Disposition: form-data; name="name"
sdfs
------WebKitFormBoundary39Ug3FSPIBvDYZd6
Content-Disposition: form-data; name="password"
sdfs
------WebKitFormBoundary39Ug3FSPIBvDYZd6
Content-Disposition: form-data; name="file"; filename="1536973449110.png"
Content-Type: image/png
------WebKitFormBoundary39Ug3FSPIBvDYZd6--
form[enctype] 有三种: application/x-www-form-urlencoded
:如果 form 表单没有设置 method ,那么默认发出的请求方法是 GET 请求,参数是以 query 的格式组织。如果 method 设置成 POST ,那么发出的请求头里 Content-Type: application/x-www-form-urlencoded
,请求体里面是 Form Data
其内容是 name=hello&password=123456
。
multipart/form-data
:表示请求分为多个部分(比如上传文件),发出的请求头里 Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxxxxxx
,boundary
用来分割表单提交数据的各个部分(服务端拿到表单数据后,根据这个分割字符串,进行数据分割)。
text/plain
:将文件设置为纯文本的形式,浏览器在获取到这种文件时并不会对其进行处理。
问题:text/plain
和 text/html
的区别? text/html
将文件的 Content-Type
设置为 text/html
的形式,浏览器在获取到这种文件时会自动调用 html 解析器对文件进行相应的处理。
Redirect
通过 url 访问某个路径请求资源时,发现资源不在 url 所指定的位置,这时服务器要告诉浏览器,新的资源地址,浏览器再重新请求新的 url,从而拿到资源。 若服务器指定了某个资源的地址,现在需要更换地址,不应该立刻废弃掉 url,如果废弃掉可能直接返回 404,这时应该告诉客户端新的资源地址,所以有了 Redirect 。
代码示例:
// server.js
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')
问题:Redirect 301 和 302 的区别? 302 临时跳转,每次请求仍然需要经过服务端指定跳转地址; 301 永久跳转。
使用 302 的效果:
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')
每次访问 locahost:8888
,都要经过服务端跳转,服务端通过 console.log
可以看到 /
、/new
两次请求。
使用 301 的效果:
response.writeHead(301, {
'Location': '/new'
})
访问 locahost:8888
,第一次经过服务端跳转,服务端通过 console.log
可以看到 /
、/new
两次请求;第二次经过服务端 console.log
只显示 /new
,没有再次经过服务器指定新的 Location
。
注意:使用 301 要慎重,一旦使用,服务端更改路由设置,用户如果不清理浏览器缓存,就会一直重定向。设置了 301,locahost
会从缓存中读取,并且这个缓存会保留到浏览器,当我们访问 8888 都会进行跳转。此时,就算服务端改变设置也是没有用的,浏览器还是会从缓存中读取。
CSP
全称:Content-Security-Policy
,内容安全策略。 有什么作用:限制资源获取、报告资源获取越权。
限制方式:default-src 限制全局、制定资源类型。
有哪些资源类型:connect-src img-src font-src media-src frame-src script-src manifest-src style-src ...
代码示例:
- 限制内联 script
<body>
<div>This is content</div>
<!-- 内联脚本 -->
<script>
console.log('inline js')
</script>
</body>
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',
'Content-Security-Policy': 'default-src http: https:' // 只能通过 http 或 https 的方式加载
})
response.end(html)
} else {
response.writeHead(200, {
'Content-Type': 'application/javascript'
})
response.end('console.log("loaded script")')
}
}).listen(8888)
console.log('server listening on 8888')
浏览器控制台会报错: Refused to execute inline script because it violates the following Content Security Policy directive: "default-src http: https:". Either the 'unsafe-inline' keyword, a hash ('sha256-KU4m2rqHAFwi569te1RE5P3qW1O/qJ+m+gVo66Frm4k='), or a nonce ('nonce-...') is required to enable inline execution. Note also that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.
- 限制外链加载 script
// server.js
'Content-Security-Policy': 'script-src \'self\'' // 限制外链 script 只能是本域名下的
<script src="test.js"></script> // 本域名下的可以使用
<script src="https://cdn.bootcss.com/jquery/3.3.1/core.js"></script>
可以看到报错信息 Refused to load the script 'https://cdn.bootcss.com/jquery/3.3.1/core.js' because it violates the following Content Security Policy directive: "script-src 'self'".
查看 Network
面板,发现在浏览器端就被 block 掉了,没有发送请求。
- 限制指定某个网站
'Content-Security-Policy': 'script-src \'self\' https://cdn.bootcss.com' // 限制外链 script 只能是本域名下的,允许指定域名script加载
在 Network
看到 core.js
加载成功。
- 限制 form 表单提交范围 form 不接受 default-src 的限制,可以通过 form-action来限制。 下面代码中 form 会调转到 http://baidu.com,通过 form-action限制浏览器会报错。
// server.js
'Content-Security-Policy': 'script-src \'self\'; form-action \'self\'' // 限制表单提交只能在本域下
<form action="http://baidu.com">
<button type="submit">click me</button>
</form>
有报错信息: Refused to send form data to 'http://baidu.com/' because it violates the following Content Security Policy directive: "form-action 'self'".
- 限制图片链接 通过全局限制 default-src 就可以实现。
'Content-Security-Policy': 'default-src \'self\'; form-action \'self\''
- 限制 ajax 请求 通过 connect-src 。
'Content-Security-Policy': 'connect-src \'self\'; form-action \'self\'; report-uri / report'
<script>
fetch('http://baidu.com')
</script>
- 汇报
// server.js
'Content-Security-Policy': 'default-src \'self\'; form-action \'self\'; report-uri / report'
在 Network
看到,发送的内容,是标准的 csp report 的内容。 报错信息: Refused to connect to 'http://baidu.com/' because it violates the following Content Security Policy directive: "connect-src 'self'".
- 允许加载但汇报 使用
Content-Security-Policy-Report-Only
。
// server.js
'Content-Security-Policy-Report-Only': 'default-src \'self\'; form-action \'self\'; report-uri / report'
资源会正常加载,但是汇报 Report-Only
相关的错误提醒。
- 在 html 中使用 csp 和在服务端使用效果相同,最好在服务端做。
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; form-action 'self';">
report-uri
不允许在 html 的 meta 中使用,只能在服务端通过 head 进行设置。