1、jsonp
jsonp解决跨域问题的本质:<script>
标签可以请求不同域名下的资源,即<script>
请求不受浏览器同源策略影响。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>jsonp</title>
</head>
<body>
</body>
</html>
<script>
var script = document.createElement('script');
script.setAttribute("type","text/javascript");
script.src = 'http://127.0.0.1:8888/?callback=foo';
document.body.appendChild(script);
function foo(data) {
console.log(`methods: ${data.methods}, result: ${data.result}`);
// 请求完后删除添加到页面上的script标签
var body = document.body
body.removeChild(script)
};
</script>
服务端:
const http = require('http')
const urlTool = require('url')
var server = http.createServer((req, res) => {
var params = urlTool.parse(req.url, true)
var data = {'methods': 'jsonp', 'result': 'success'}
res.writeHead(200, {
'Content-Type': 'text/plain'
})
if (params.query && params.query.callback) {
// 服务器端应当在JSON数据前加上回调函数名,以便完成一个有效的JSONP请求
var str = `${params.query.callback}(${JSON.stringify(data)})`
}
res.end(str);
})
server.listen(8888)
console.log('Server running at http://127.0.0.1:8888/')
2、CORS
跨域资源共享(CORS) 是一种机制,它使用额外的HTTP头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。
在进行非简单请求的时候,浏览器会先发送一次OPTION请求来“预检”(preflight)该请求是否被允许,请求头中会通过Access-Control-Request-Method
,Access-Control-Request-Headers
来告诉服务器我需要用到的方法和字段,服务器通过返回的头部信息中的Access-Control-Allow-Origin
,Access-Control-Allow-Method
来告诉浏览器该跨域请求是否被允许。当确认允许跨域之后,以后再发送该请求,就会省去预检处理,之间当作简单请求来操作。
简单请求与非简单请求的区别
* 请求方式:HEAD,GET,POST
* 请求头信息:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type 对应的值是以下三个中的任意一个
application/x-www-form-urlencoded
multipart/form-data
text/plain
只有同时满足以上两个条件时,才是简单请求,否则为非简单请求
3、Nginx代理
3.1、正向代理和反向代理的概念
无论是正向代理,还是反向代理,说到底,就是代理模式的衍生版本罢了。我们都学习过代理设计模式,都知道代理模式中有代理角色和被代理角色,为什么这么说,因为这两个角色对于我们理解正向和反向代理非常重要,下面会讲到。
下面我将引入这样一个场景,很多时候我们上网的网速特别慢,或者说由于fanqiang问题导致我们无法访问到国外的网站,通常这些情况我们会通过给浏览器配置一个网速快的、可以fanqiang的代理ip及端口号来解决我们的问题,那么配置好之后,大概的请求流程如下图所示:
我们首先请求代理服务器,然后代理服务器帮我们去快速访问国外的网站,对于这种代理方式,我们就称之为正向代理。请记住,上面说到代理模式的两个角色中,我们当前的角色为 被代理者,也就是浏览器这个角色。更重要的是,正向代理的本质是我们去请求外部的资源,如果以生产者、消费者模式来区分,我们属于消费者。
总结:
- 1、正向代理,我们的角色是 被代理者
- 2、正向代理,我们不对外提供服务,反而是对外消费服务,属于消费者
反向代理,很显然,就是和正向代理相反,如果说正向代理是男,那么反向代理就是女了,亲,此处不再纠结其他情况!下面我用一副图片解释下反向代理:
看完上面的图片,请你想象一下这么一个场景,假设你现在是某公司技术总监,你们公司需要对外提供一套web服务,那么你打算怎么做呢?
答案是可以通过反向代理来完成。通常你们公司拥有自己的IDC机房,机房通讯通常采用局域网交换机,internet网用户请求是无法直接访问到局域网内的web服务的,因此这个时候,你需要一台反向代理服务器来接收internet web请求,然后将请求分发到局域网中的不同主机上进行处理,处理完成之后再做出响应。因此,反向代理大概就是这么一个场景。请记住,反向代理中,我们的角色是 局域网 web服务。
总结:
- 1、反向代理,我们的角色是 局域网 web服务
- 2、反向代理,我们对外提供服务,属于服务提供者
3.2、nginx反向代理实现跨域
3.2.1、前端配置nginx方向代理实现跨域
场景:假如前端页面url
为http://127.0.0.1:5500
需要向后端http://127.0.0.1:3000
发起请求(http://127.0.0.1:3000/index)
,由于浏览器的同源策略,该请求跨域了。前端可以配置nginx
方向代理,将前端服务http://127.0.0.1:5500
代理到http://127.0.0.1:3003
,前端请求由http://127.0.0.1:3000/xxx
改成http://127.0.0.1:3003/xxx
,后端服务http://127.0.0.1:3000
代理到http://127.0.0.1:3003
,这样前后端交互就同源了。核心思想就是将前后端服务代理到同一域下
// http://127.0.0.1:5500页面
<!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>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>
<button id="getOK">发送请求OK(客户端解决跨域问题)</button>
<button id="getNO">发送请求NO(客户端解决跨域问题)</button>
<script>
$(document).ready(function () {
$('#getOK').click(function () {
$.ajax({
url:'http://127.0.0.1:3003/ok',
success:function(res) {
console.log("success",res)
},
error:function(err) {
console.log('fail',err)
}
})
})
$('#getNO').click(function () {
$.ajax({
url:'http://127.0.0.1:3003/no',
success:function(res) {
console.log("success",res)
},
error:function(err) {
console.log('fail',err)
}
})
})
})
</script>
</body>
</html>
// http://127.0.0.1:3000后端
const express = require('express')
const cookieParser = require('cookie-parser')
var app = express()
var router = express.Router()
router.get('/ok',function (req,res) {
res.json({
code:200,
msg:"isOK"
})
})
router.get('/ok/son',function (req,res) {
res.json({
code:200,
msg:"isOKSon"
})
})
router.get('/ok2',function (req,res) {
res.json({
code:200,
msg:"isOK2"
})
})
router.get('/no',function (req,res) {
res.json({
code:200,
msg:"isNO"
})
})
router.get('/no/son',function (req,res) {
res.json({
code:200,
msg:"isNOSON"
})
})
router.get('/no/son2',function (req,res) {
res.json({
code:200,
msg:"isNOSON2"
})
})
app.use(router)
app.use(cookieParser)
app.listen(3000,function () {
console.log('listen in 3000')
})
// nginx反向代理
server {
listen 3003;
server_name 127.0.0.1; // 不能加上http:// ,否则会报server name "\*" has suspicious symbols
location / {
proxy_pass http://127.0.0.1:5500;
}
location /no {
proxy_pass http://127.0.0.1:3000;
}
location /ok/ {
proxy_pass http://127.0.0.1:3000;
}
}
效果:
3.2.2、后端配置nginx方向代理实现跨域
思想:跟前端配置nginx方向代理有所不同,后端nginx方向代理实现跨域只需要代理后端服务,并在代理服务器上配置响应头属性允许前端访问代理服务器。
// http://127.0.0.1:5500 前端页面
<!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>
<script src="https://code.jquery.com/jquery-3.3.1.min.js"></script>
</head>
<body>
<button id="get">发送请求(服务端解决跨域问题)</button>
<script>
$(document).ready(function () {
$('#get').click(function () {
$.ajax({
url:'http://127.0.0.1:3002/ok',
// 带cookies的请求
xhrFields:{
withCredentials:true
},
success:function(res) {
console.log("success",res)
},
error:function(err) {
console.log('fail',err)
}
})
})
})
</script>
</body>
</html>
// http://127.0.0.1:3000 后端
const express = require('express')
const cookieParser = require('cookie-parser')
var app = express()
var router = express.Router()
router.get('/ok',function (req,res) {
res.json({
code:200,
msg:"isOK"
})
})
router.get('/ok/son',function (req,res) {
res.json({
code:200,
msg:"isOKSon"
})
})
router.get('/ok2',function (req,res) {
res.json({
code:200,
msg:"isOK2"
})
})
router.get('/no',function (req,res) {
res.json({
code:200,
msg:"isNO"
})
})
router.get('/no/son',function (req,res) {
res.json({
code:200,
msg:"isNOSON"
})
})
router.get('/no/son2',function (req,res) {
res.json({
code:200,
msg:"isNOSON2"
})
})
app.use(router)
app.use(cookieParser)
app.listen(3000,function () {
console.log('listen in 3000')
})
// 后端nginx方向代理
server {
listen 3002;
server_name 127.0.0.1;
location /ok {
proxy_pass http://127.0.0.1:3000;
# 指定允许跨域的方法,*代表所有
add_header Access-Control-Allow-Methods *;
# 预检命令的缓存,如果不缓存每次会发送两次请求
add_header Access-Control-Max-Age 3600;
# 带cookie请求需要加上这个字段,并设置为true
add_header Access-Control-Allow-Credentials true;
# 表示允许这个域跨域调用(客户端发送请求的域名和端口)
# $http_origin动态获取请求客户端请求的域 不用*的原因是带cookie的请求不支持*号
add_header Access-Control-Allow-Origin $http_origin;
# 表示请求头的字段 动态获取
add_header Access-Control-Allow-Headers
$http_access_control_request_headers;
# OPTIONS预检命令,预检命令通过时才发送请求
# 检查请求的类型是不是预检命令
if ($request_method = OPTIONS){
return 200;
}
}
}
实际上不是非简单请求的且不带cookie只需2个字段即可解决跨域 add\_header Access-Control-Allow-Methods \*;
add\_header Access-Control-Allow-Origin $http\_origin;
效果:
window下常用nginx命令:
查看nginx进程tasklist /fi "imagename eq nginx.exe"
杀掉nginx进程taskkill /f /pid 16900 /pid 19012
启动nginxstart nginx
4、postMessage
场景1:在a页面里打开了另一个不同源的页面b,你想要让a和b两个页面互相通信。比如,a要访问b的LocalStorage。
场景2:你的a页面里的iframe
的src是不同源的b页面,你想要让a和b两个页面互相通信。比如,a要访问b的LocalStorage。
解决方案:HTML5y引入了一个全新的API,跨文档通信 API(Cross-document messaging)。这个API为window对象新增了一个window.postMessage
方法,允许跨窗口通信,不论这两个窗口是否同源。a就可以把它的LocalStorage,发送给b,b也可以把自己的LocalStorage发给a。
window.postMessage(message, targetOrigin, [transfer]),有三个参数:
message是向目标窗口发送的数据;
targetOrigin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI(或者说是发送消息的目标域名);
transfer可选参数,是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权。
另外消息的接收方必须有监听事件,否则发送消息时就会报错。The target origin provided ('http://localhost:8080') does not match the recipient window's origin ('http://localhost:63343').
window.addEventListener("message",onmessage);
onmessage接收到的message事件包含三个属性:
data:从其他 window 中传递过来的数据。
origin:调用 postMessage 时消息发送方窗口的 origin 。请注意,这个origin不能保证是该窗口的当前或未来origin,因为postMessage被调用后可能被导航到不同的位置。
source:对发送消息的窗口对象的引用; 您可以使用此来在具有不同origin的两个窗口之间建立双向通信。
案例
使用http-server模拟不同源的两个页面
// http://127.0.0.1:8080/A.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button onclick="send()">向弹窗页发送信息</button>
</body>
</html>
<script>
var popup;
window.onload = function () {
popup = window.open('http://127.0.0.1:8081/C.html', '', 'width=500,height=500')
}
function send() {
popup.postMessage('Hello World!', 'http://127.0.0.1:8081/')
}
</script>
// http://127.0.0.1:8081/C.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
</body>
</html>
<script>
window.onload = function () {
console.log('recieve')
window.addEventListener("message", onmessage);
}
function onmessage(event) {
if (event.origin == "http://127.0.0.1:8080") { // http://localhost:8080是发送方a的域名
document.getElementById('app').innerText = event.data
}
}
</script>
5、document.domain
6、window.name
场景1:现在浏览器的一个标签页里打开http://www.damonare.cn/a.html
页面,你通过location.href=http://baidu.com/b.html
,在同一个浏览器标签页里打开了不同域名下的页面。这时候这两个页面你可以使用window.name
来传递参数。因为window.name
指的是浏览器窗口的名字,只要浏览器窗口相同,那么无论在哪个网页里访问值都是一样的。
场景2:你的http://www.damonare.cn/a.html
页面里使用<iframe>
调用另一个http://baidu.com/b.html
页面。这时候你想在a页面里获取b页面里的dom,然后进行操作。然后你会发现你不能获得b的dom。同样会因为不同源而报错,和上面提到的不同之处就是两个页面的一级域名也不相同。这时候document.domain
就解决不了了。
解决方案:浏览器窗口有window.name
属性。这个属性的最大特点是,无论是否同源,只要在同一个窗口里,前一个网页设置了这个属性,后一个网页可以读取它。。比如你在b页面里设定window.name="hello"
,你再返回到a页面,在a页面里访问window.name
,可以得到hello
。
这种方法的优点是,window.name容量很大,可以放置非常长的字符串;缺点是必须监听子窗口window.name属性的变化,影响网页性能。
// http://127.0.0.1:8080
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p></p>
<iframe src="http://127.0.0.1:8081/demo10.html" frameborder="0"></iframe>
</body>
</html>
<script>
var ifr = document.getElementsByTagName('iframe')[0]
ifr.onload = function() {
document.getElementsByTagName('p')[0].innerText = ifr.contentWindow.name
}
</script>
// http://127.0.0.1:8081
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script>
window.name="hello, world!"
</script>
</body>
</html>
上面执行结果会报错,因为不同源
解决方法:
<script>
var ifr = document.getElementsByTagName('iframe')[0]
ifr.onload = function() {
ifr.onload = function(){
document.getElementsByTagName('p')[0].innerText = ifr.contentWindow.name
}
ifr.src = 'about:blank';
}
</script>
参考:
https://segmentfault.com/a/11...
https://segmentfault.com/a/11...
https://juejin.im/entry/5b3a2...
https://juejin.im/post/5b67b3...
https://juejin.im/post/5815f4...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。