1. Request.mode
Request 接口的 mode 只读属性包含请求的模式(例如:cors 、 no-cors 、 cors-with-forced-preflight 、 same-origin 或 navigate 。)这用于确定跨域请求是否能得到有效的响应,以及响应的哪些属性是可读的。
属性值 | 作用 |
---|
same-origin | 如果使用此模式向另外一个源发送请求,显而易见,结果会是一个错误。你可以设置该模式以确保请求总是向当前的源发起的。 | no-cors | 保证请求对应的 method 只有 HEAD,GET 或 POST 方法,并且请求的 headers 只能有简单请求头,时保证了在跨域时不会发生安全和隐私泄露的问题。 | cors | 允许跨域请求,例如访问第三方供应商提供的各种 API。 | navigate | 表示这是一个浏览器的页面切换请求(request)。 navigate请求仅在浏览器切换页面时创建,该请求应该返回HTML。 |
当一个 Request 对象以 Request.Request 方式创建,该Request 的 mode 的值为 cors 。 然而,除了以 Request.Request 创建的请求,模式通常为 no-cors 。例如,对与嵌入资源发起的请求,除非存在 crossorigin 属性,即对于 <link> 、 <script> (除了和模块一起使用之外)、 <img>、 <audio>、 <video>、 <object>、 <embed>还有 <iframe> 元素,在大多数情况下是使用 no-cors 模式。
也就是说,之所以JSONP可以通过<script> 标签来实现跨域,就是因为<script> 标签发起的请求使用了mode:'no-cors' 。
server.js: 监听8888端口,返回一个index.html,index.html向8887端口发起请求。
const http = require('http')
const fs = require('fs')
http.createServer(function (request, response){
console.log('request url: ', request.url)
const html = fs.readFileSync('index.html', 'utf-8')
response.writeHead(200, {
'Content-Type': 'text/html'
})
response.end(html)
}).listen(8888)
console.log('server running on 8888')
index.html: 向8887端口发起跨域请求
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>content</div>
</body>
<script>
fetch('http://localhost:8887', {
method: 'POST',
mode: 'no-cors'
})
</script>
</html>
server2.js: 监听8887端口
const http = require('http')
http.createServer(function (request,response){
console.log('request url: ', request.url)
response.writeHead(200, {
})
response.end('123')
}).listen(8887)
console.log('server running on 8887')
注意:我们并没有在8887端口设置’Access-Control-Allow-Origin’: ‘http://127.0.0.1:8888’,而是在index.html发起请求时设置了mode: ‘no-cors’。 请求成功返回数据:
2. CORS跨域限制
为了发起PUT,DELETE等非简单请求,甚至自定义请求头,如何正确的跨域呢? 这时候就不能简单的使用mode:'no-cors' 来应对了。
2.1 发起PUT请求
我们修改server2.js,使来自8888端口的请求能正常返回数据。
response.writeHead(200, {
'Access-Control-Allow-Origin': 'http://127.0.0.1:8888'
})
修改index.html,发起PUT请求:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div>content</div>
</body>
<script>
fetch('http://127.0.0.1:8887', {
method: 'POST',
mode: 'no-cors'
})
</script>
</html>
控制台报错: 值得我们注意的是,网络中不止一个put请求被客户端发送到8887端口: 我们明明是向127.0.0.1:8887 发送了PUT请求,为什么这里多出来了一个OPTIONS 请求呢,后文中会有详细的解释。 根据控制台的报错信息,我们修改server2.js:
response.writeHead(200, {
'Access-Control-Allow-Origin': 'http://127.0.0.1:8888',
'Access-Control-Allow-Methods': 'PUT'
})
然后重新访问,控制台就没有报错信息了,并且成功拿到了8887端口返回的数据,但是,此时的网络还是存在一个OPTIONS 的请求。
2.2 自定义表头
修改index.html:
<script>
fetch('http://127.0.0.1:8887', {
method: 'POST',
headers: {
'My-Custom-Header': 'Test'
}
})
</script>
重新访问,控制台报错: 这时候,我们由于设置了自定义表头,我们还需要在8887响应头中设置Access-Control-Allow-Headers 修改server2.js:
response.writeHead(200, {
'Access-Control-Allow-Origin': 'http://127.0.0.1:8888',
'Access-Control-Allow-Headers': 'My-Custom-Header'
})
重新访问,控制台不在报错,同样的,这时候网络中也增加了一个OPTIONS 的请求。
3. 预请求验证
这里的预请求验证就是前文中我们发现的OPTIONS 请求。 什么时候会发送OPTIONS请求呢? 当一个请求(记做R)跨域并且不是简单请求的时候,浏览器会先向请求目标发送一个OPTIONS请求,通过OPTIONS请求判断是否发送请求R。 预请求验证可以避免跨域请求对服务器数据产生不可预料的影响,使跨域请求更加安全。 怎样的请求才是简单请求呢? 请求方法: 只能是一下三种之一
GET HEAD POST
Content-Type: 只能是以下三种之一
test/plain multipart/form-data application/x-www-form-urlencoded
每当发送一个非简单请求时,浏览器都会发送一个OPTIONS 请求,如果我们需要频繁的发起类似请求,那么就会增加我们的网络负担,所以,我们可以修改server2.js:
response.writeHead(200, {
'Access-Control-Allow-Origin': 'http://127.0.0.1:8888',
'Access-Control-Allow-Headers': 'My-Custom-Header',
'Access-Control-Max-Age': 1000
})
当我们第一次发起请求时,浏览器会发送OPTIONS请求: 在之后的'Access-Control-Max-Age' 时间内,浏览器都不需要再次发送OPTIONS请求。
|