同源策略(Same Origin Policy)
浏览器出于安全考虑,只允许本域下的接口交互。是浏览器的安全机制。
本域:
- 同协议:都是http或https
- 同域名:都是http://xxx.com/a 或者http://xxx.com/b
- 同端口:都是80的端口
例如:xxx.com/a/b.js 与 xxx.com/index.html 同源
非同源情况下
- Ajax不能发送请求
- DOM无法获得
- Cookie,LocalStorage无法获得
跨域的方法
JSONP
前提:在HTML标签里,一些标签比如script、img这样的获取资源的标签是没有跨域限制的。
JSONP是通过动态创建
script来实现跨域。JSONP 需要对应接口的后端的配合才能实现
- 优点:兼容性好
- 缺点:
只支持get方式请求
实例
<body>
<button class="btn">点击获取</button>
<div class="show"></div>
<script>
var btn = document.querySelector('.btn')
//点击创建一个script,利用script中的src不受同源策略约束来跨域获取数据
btn.addEventListener('click',function (){
script = document.createElement('script')
//将src设为目标请求,插入到DOM中,发送请求到服务器,服务器接送该请求并返回数据
//通过参数getData获取回调函数名showData,将数据作为参数调用返回
script.src = 'http://localhost:8080/getData?callback=showData'
document.body.appendChild(script)
})
function showData(data){
var show = document.querySelector('.show')
show.textContent = data
}
</script>
</body>
server.js-----node server.js
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true)
switch (pathObj.pathname) {
case '/getData':
var data = [
'hello world'
]
res.setHeader('Content-Type','text/json; charset=utf-8')
if(pathObj.query.callback){
res.end(pathObj.query.callback + '(' + JSON.stringify(data) + ')')
}else{
res.end(JSON.stringify(data))
}
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
CORS
跨资源共享,对Ajax功能的扩展
简单请求
- 请求方法满足
- GET
- POST
- HEAD
- 必选字段
- Access-Control-Allow-Origin
- 请求时允许的字段
- Access-Control-Allow-Origin
- 可选字段
- Access-Control-Allow-Credentials
- 表示是否允许发送Cookie
- 允许发送Cookie时,Access-Control-Allow-Origin不能设置为星号
- 在Ajax中设置
xhr.withCreadentials = true
- Access-Control-Allow-Credentials
index.html
<body>
<button class="btn">show</button>
<div class="show"></div>
<script>
var btn = document.querySelector('.btn')
// 点击按钮会向后台发起请求获取数据,请求达到后端时会解析callback获取字符串showData
btn.addEventListener('click',function() {
var xhr = new XMLHttpRequest()
//请求的url与访问的url不同域
xhr.open('GET', 'http://127.0.0.1:8080/getData', true)
xhr.send()
xhr.onload = function(){
showData(JSON.parse(xhr.responseText))
}
})
// 回调函数
function showData(data) {
var show = document.querySelector('.show')
show.textContent = data
}
</script>
</body>
server.js
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true)
switch (pathObj.pathname) {
case '/getData':
var data = [
'hello world'
]
//关键添加一个Access-Control-Allow-Origin,即允许来自http://localhost:8080的访问
res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
//
res.end(JSON.stringify(data))
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
非简单请求
对服务器有特殊要求的请求
- PUT/DELETE
- Content-type字段类型application/json
非简单请求会在正式通信前增加一次预检请求
- 浏览器先询问服务器是否许可,得到肯定答复再发送XMLHttp请求,否则就报错
- 预检请求方法是OPTIONS,表示这个请求是用来询问的
- 必选字段
- Access-Control-Request-Method
- GET, POST, PUT
- Access-Control-Request-Headers
- Access-Control-Request-Method
- 预检请求回应必选字段
- Access-Control-Allow-Method
- Access-Control-Allow-Headers
优点:支持所有类型的Http请求
index.html
<body>
<button class="btn">show</button>
<div class="show"></div>
<script>
var btn = document.querySelector('.btn')
btn.addEventListener('click',function() {
var xhr = new XMLHttpRequest()
//请求方式为PUT
xhr.open('PUT', 'http://127.0.0.1:8080/getData', true)
xhr.setRequestHeader('X-Custom-Header', 'value')
//
xhr.send()
xhr.onload = function(){
showData(JSON.parse(xhr.responseText))
}
})
// 回调函数
function showData(data) {
var show = document.querySelector('.show')
show.textContent = data
}
</script>
</body>
server.js
var http = require('http')
var fs = require('fs')
var path = require('path')
var url = require('url')
http.createServer(function(req, res){
var pathObj = url.parse(req.url, true)
switch (pathObj.pathname) {
case '/getData':
var data = [
'hello world'
]
//关键添加一个Access-Control-Allow-Origin,即允许来自http://localhost:8080的访问
res.setHeader('Access-Control-Allow-Origin','http://localhost:8080')
res.setHeader('Access-Control-Allow-Methods','GET, POST, PUT')
res.setHeader('Access-Control-Allow-Headers',' X-Custom-Header')
res.end(JSON.stringify(data))
break;
default:
fs.readFile(path.join(__dirname, pathObj.pathname), function(e, data){
if(e){
res.writeHead(404, 'not found')
res.end('<h1>404 Not Found</h1>')
}else{
res.end(data)
}
})
}
}).listen(8080)
降域
当两个子域不同,主域相同,适合利用降域来跨域。
实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
如 a.juejin.com:8080、b.juejin.com:8080。
- 在两个文件中加入
document.domain = 'juejin.com'
- 通过a.html文件创建一个iframe,去控制iframe的window进行交互
- 在hosts添加127.0.0.1 a.juejin.com、127.0.0.1 b.juejin.com
- 用http-server监听,输入a.juejin.com:8080/a.html 或者 b.juejin.com/b.html 实现跨域
<!--a.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>
<style>
.ct {
width: 910px;
margin: auto;
}
.main {
float: left;
width: 450px;
height: 300px;
border: 1px solid #ccc;
}
.main input {
margin: 20px;
width: 200px;
}
.iframe {
float: right;
}
iframe {
width: 450px;
height: 300px;
border: 1px dashed #ccc;
}
</style>
</head>
<body>
<div class="ct">
<h1>使用降域实现跨域</h1>
<div class="main">
<input type="text" placeholder="跨域成功">
</div>
<iframe src="http://b.juejin.com:8080/b.html" frameborder="0"></iframe>
</div>
<script>
document.querySelector('.main input').addEventListener('input', function () {
console.log(this.value);
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = 'juejin.com'
</script>
</body>
</html>
<!--b.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>
<style>
html,
body {
margin: 0;
}
input {
margin: 20px;
width: 200px;
}
</style>
</head>
<body>
<input id="input" type="text" placeholder="跨域成功">
<script>
document.querySelector('#input').addEventListener('input', function () {
window.parent.document.querySelector('input').value = this.value
})
document.domain = "juejin.com"
</script>
</body>
</html>
postMessage
没有主域限制
postMessage(data,origin)