前端跨域

同源策略

    • 跨域:浏览器允许向服务器发送跨域请求,从而克服Ajax只能同源使用的限制。

    • 同源策略:如果两个页面的协议,域名,端口都相同,则两个页面具有相同的源。
      同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源。这是一个用于隔离潜在恶意文件的重要安全机制。

不受同源策略限制

  1. 页面中的链接,重定向以及表单提交是不会受到同源策略限制的。

  2. 跨域资源的引入是可以的。但是js不能读写加载的内容。如嵌入到页面中的<script src="..."></script>,<img>,<link>,<iframe>等。

受同源策略限制

  1. Cookie、LocalStorage 和 IndexDB 无法读取
  2. DOM和JS对象无法获得
  3. AJAX 请求不能发送

跨域解决方案

一、JSONP跨域

  • jsonp的核心原理就是:目标页面回调本地页面的方法,并带入参数
  • 服务器端实现 JSONP 接口的步骤
    1. 服务器端获取客户端发送过来的query参数,其中参数有回调函数的名字
    2. 得到的数据,拼接出一个函数调用的字符串
    3. 把上一步拼接得到的字符串,响应给客户端的 <script> 标签进行解析执行
  • jsonp的缺点:只能发送get一种请求。

1、原生JS实现

通过script标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。

<script>
    function getData(data){
        console.log(data)
    }
</script>
<script src="http://127.0.0.1:3000/web?cb=getData"></script>

后端nodejs代码
主要用来模拟服务器
携带参数必须是字符串

const express=require('express')
const router=express.Router()
router.get('/web',(req,res)=>{
    let {cb}=req.query
    console.log(req.query)
    var data = {
        name: 'xtt',
        age: 18,
        gender:'女孩子'
    }
    // 携带参数必须是字符串
    res.send(`${cb}(${JSON.stringify(data)})`)
    router.get('/que',(req,res)=>{
        res.send(`${req.query.cb}('dd')`)
    })
})
module.exports=router

2、Jquery Ajax实现

以jquery来发起jsonp请求

<script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.10.0/jquery.js"></script>
<script>
    let url = 'http://127.0.0.1:3000/que?cb=getData'
    $.ajax({
        method: 'GET',
        url,
        dataType: 'jsonp',
        success: (res) => {
            console.log(res)
        }
    })
</script>

3、Vue axios实现

handleCallback({"success": true, "user": "admin"})
this.$http = axios;
this.$http.jsonp('http://127.0.0.1:3000/que?cb=getData', {
    params: {},
    jsonp: 'handleCallback'
}).then((res) => {
    console.log(res); 
})

二、跨域资源共享

  • CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
  • 它允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。
  • CORS需要浏览器和服务器同时支持。
    • 目前,所有主流浏览器都支持该功能,IE10以下不支持。
    • 浏览器将CORS跨域请求分为:简单请求、非简单请求。

简单请求与非简单请求

简单请求

浏览器在发送跨域请求的时候,会先判断下是简单请求还是非简单请求,如果是简单请求,就先执行服务端程序,然后浏览器才会判断是否跨域。
同时满足以下的两个条件,就属于简单请求。浏览器对这两种的处理,是不一样的。

  1. 请求方式:get/post/head其中一种
  2. 请求头设置:
    • Accept
    • Accept-Language
    • Content-Type:application/x-www-form-urlencoded、multipart/form-data、text/plain( 只限于三个值中的一个)

详细描述
对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,增加一个Origin字段。

举例

  1. 发起请求

  2. 自动在头信息之中,添加一个Origin字段。

    GET /cors HTTP/1.1
    Origin: http://127.0.0.1:8080
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0...

    Origin:本次请求来自哪个域(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。

  3. 服务器判断此次请求Origin源
      • 不在许可范围内:服务器会返回一个正常的 HTTP 回应。
        • 浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段(详见下文),就知道出错了,从而抛出一个错误,被请求的异常回调函数捕获。
          注意,这种错误无法通过状态码识别,因为 HTTP 回应的状态码有可能是200。
      • 在许可范围内:服务器返回的响应,会多出几个头信息字段。
        • 有三个与 CORS 请求相关的字段,都以Access-Control-开头

    Access-Control-Allow-Origin: http://api.bob.com
    Access-Control-Allow-Credentials: true
    Access-Control-Expose-Headers: FooBar
    Content-Type: text/html; charset=utf-8
  4. Access-Control解释
    1. Access-Control-Allow-Origin:必须的

      它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。

    2. Access-Control-Allow-Credentials:可选

      布尔值,表示是否允许发送 Cookie。默认情况下,Cookie 不包括在 CORS 请求之中(为了降低 CSRF 攻击的风险。)。设为true,即表示服务器明确许可,浏览器可以把 Cookie 包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器发送 Cookie,不发送该字段即可。

    3. Access-Control-Expose-Headers:可选

      CORS 请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个服务器返回的基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('FooBar')可以返回FooBar字段的值。

       

非简单请求

对服务器提出特殊要求的请求,比如请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。

预检请求

  1. 非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为“预检”请求(preflight)。
  2. 浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 方法和头信息字段。
  3. 只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。
  4. 这是为了防止这些新增的请求,对传统的没有 CORS 支持的服务器形成压力,给服务器一个提前拒绝的机会,这样可以防止服务器收到大量DELETE和PUT请求,这些传统的表单不可能跨域发出的请求

举例

  1. 自动发出一个“预检”请求,要求服务器确认可以这样请求。下面是这个“预检”请求的 HTTP 头信息:

    OPTIONS /cors HTTP/1.1
    Origin: http://api.bob.com
    Access-Control-Request-Method: PUT
    Access-Control-Request-Headers: X-Custom-Header
    Host: api.alice.com
    Accept-Language: en-US
    Connection: keep-alive
    User-Agent: Mozilla/5.0...

    两个特殊字段:

    • Access-Control-Request-Method必须的

      用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法,上例是PUT。

    • Access-Control-Request-Headers

      该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段。

    服务器收到“预检”请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应

  2. 预检请求的回应:
    1. 服务器收到"预检"请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应。

    HTTP回应中,除了关键的是Access-Control-Allow-Origin字段,其他CORS相关字段如下:

    • Access-Control-Allow-Methods:必选

      它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。

    • Access-Control-Allow-Headers

      如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。

    • Access-Control-Allow-Credentials:可选

      该字段与简单请求时的含义相同。

    • Access-Control-Max-Age:可选

      用来指定本次预检请求的有效期,单位为秒。

       

CORS跨域

1)前端设置

let xhr;
try {
    xhr=new XMLHttpRequest();   
} catch (error) {
     xhr=new ActiveXObject('Microsoft.XMLHTTP');
}
xhr.open('post','http://localhost:3000/login',true);
xhr.setRequestHeader('content-type','application/x-www-form-urlencoded');
xhr.send('name=111&age=12');
xhr.onreadystatechange=function(){
    if(xhr.readyState==4){
        let reg=/^2\d{2}/
        if(reg.test(xhr.status)){
            console.log(JSON.parse(xhr.response))
        }
    }
}

nodejs代码
在Express中通过第3方中间件来完成cors跨域解决
使用步骤分为如下 3 步:

    1. 运行 npm install cors 安装中间件
    2. 使用 const cors = require('cors') 导入中间件
    3. 在路由之前调用 app.use(cors()) 配置中间件
const express=require('express')
const cors=require('cors')
const app=express()
app.listen(3000)
const allowHosts=[
    'http://localhost:5000',
    'http://localhost:2000'
]
app.use(cors())
app.use((req,res,next)=>{
    let hst =req.header.origin
    if(allowHosts.includes(hst)){
        next()
    }else{
        return res.send({
            code:404,
            msg:'地址不对'
        })
    }
})
app.get('/login',(req,res)=>{
    res.send('登陆')
})

三、Nginx反向代理

要点先知:

在使用nginx之前先要明确一下的几个概念:

前端项目,后端项目,以及nginx,这就是三个serve项目,它们只是互相之间交流数据;
三个项目都有自己的ip:port组合,哪怕你是在同一台服务器上启动这三个server,它们的port也是不可能有一样的;
所以,前端项目,不论访问nginx还是访问后端项目,都会产生跨域问题。 

(1)首先要安装nginx

在官网进行安装,地址是http://nginx.org/en/download.html,然后选择第二个稳定版本,下载即可。
下载完,解压后即可得到目录:
主要的配置文件在文件夹conf的nginx.conf中,想要解决跨域问题就得在此处进行配置。

(2)熟悉简单常用的nginx命令

这些命令需要在cmd端切换到 安装nginx的目录下 进行。

  1. start nginx # 启动nginx --启动的时候,有一个终端会闪退说明启动成功
  1. nginx.exe -s reload # 修改配置后需要重启才生效
  1. nginx.exe -s quit # 关闭nginx

第一步,要启动nginx,启动成功后,访问 localhost:8088,可以看到下面这个界面:

此处为什么是locahost:8088呢,这个配置在nginx.conf 中,如下:

 

 当你下载完打开后默认的配置是80端口,如果该端口被占用的话,你可以配置其他未被占用的端口,比如我这里就是8088

(3)nginx.conf文件配置
目前各个环境下的域名和端口号情况:本地项目在运行的时候,访问的地址是http:loaclhost 3000 用的是3000端口号,配置nginx使用的是8088端口号,也就是http://localhost:8088,我希望http://localhost:3000可以访问到http://test.abc.com这个域名下的接口(可以理解为要调用的一系列接口的公共前缀部分),如果没有通过nginx中转的话,访问时报跨域错误。那该如何解决呢:
1.配置nginx.conf文件
 

 

第一大方块:nginx的访问端口可以修改为没被占用的其他端口,这里设置nginx的访问路径是 http://localhost:8088


第二大方块:注意这里需要添加 proxy_pass 为本地运行的项目地址 http://localhost:3000 !!! 当访问 http://localhost:8088 的时候,location会匹配 ’ / ’ 到文件夹根目录下的index.html文件,但这里添加 proxy_pass 后,访问 http://localhost:8088 的时候会代理到 http://localhost:3000,而且你的访问路径显示的还是http://localhost:8088 ,内容是 http://localhost:3000的


第三大方块:这里location匹配的是 访问 http://localhost:8088/api/.* 路径的时候,在proxy_pass填上需要用到的外域api地址 http://test.abc.com/ ,此时就相当于访问:
http://test.abc.com/ . ,但实际上显示在你眼前的还是原来路径: http://localhost:8088/api/. ,只是你访问这个原路径的时候nginx自动帮你代理到你想要访问的api路径 。但这里有个很重要的细节需要注意: 匹配路径 /api/ 和代理路径 http://test.abc.com/ 后面的斜杠统统都不能少!!!任何一个少了都不行,不信试试,这是nginx的规则。

修改完 nginx.conf 文件后,需要重启nginx ,才会生效 !!!

2.本地项目配置和访问方式:
在本地项目中,访问接口时,以 /api/.* 这样的格式,然后运行本地项目后(需要先运行),访问 http://locallhost:8088地址,而不是http://localhost:3000

直接访问http://localhost:3000不会通过nginx中转,而访问http://localhost:8088通过nginx解决了跨域问题。

四、Node.js中间件代理

node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发,也可以通过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登录认证。

1、nodejs服务器代理

使用node + express + http-proxy-middleware搭建一个proxy服务器。

  1. 安装expresshttp-proxy-middleware

    yarn add express http-proxy-middleware
  2. 新建一个index.js
    const express = require('express');
    const { createProxyMiddleware } = require('http-proxy-middleware');
    
    const app = express();
    app.use(
        '/api',
        createProxyMiddleware({ 
            target: 'https://www.baidu.com/', // 需要跨域处理的请求地址
            changeOrigin: true,
            pathRewrite: { // 请求中去除/api
                "^/api": ""
            },
            onProxyReq: function onProxyReq(proxyReq, req, res) {
     // 我就打个log康康
                console.log('-->  ', req.method, req.baseUrl, '->', proxyReq.host + proxyReq.path);
            }
        })
    )
    
    app.listen(3001);
  3. 命令行启动一下
    node index.js

    这下我们已经有了一个本地3001端口的转发服务小帮手了, 所有发向http://localhost:3001/api的请求, 小助手都会帮我处理一下后转发给百度.

  4. 代码改动试一下
    fetch('http://localhost:3001/api')
  5. 不用上截图我就告诉你还是失败

    因为http://localhost:3000http://localhost:3001的请求依然属于跨域, 他们还不是好朋友.
    跨域的判定: 协议 + 域 + 端口
    文档: https://developer.mozilla.org/zh-CN/docs/Glossary/Origin

    下面让我们的3001小助手认可3000, 在们的index.js文件中, 添加onProxyRes等代码.

      ......
            onProxyReq: function onProxyReq(proxyReq, req, res) {
                console.log('-->  ', req.method, req.baseUrl, '->', proxyReq.host + proxyReq.path);
            },
            onProxyRes: function onProxyReq(proxyRes, req, res) {
                proxyRes.headers["Access-Control-Allow-Origin"] = "*"; // 或者直接写"http://localhost:3000"成为唯一好友
            } 
      ......
  6. 请求成功

2、http-proxy-middleware 集成到 create-react-app

create-react-appreact-scripts@0.2.3之后对http-proxy-middleware自带很好的集成
参考文档: https://create-react-app.dev/docs/proxying-api-requests-in-development
按照他的方式, 他会在我们启动的服务上(如我的localhost:3000端口)直接集成转发, 省去我们手动启3001, 以及让它和3000成为好朋友的一步.
  1. 安装
    yarn add http-proxy-middleware
  2. 在项目根目录src下新建setupProx.js文件
    tips: 修改调试setupProxy.js文件需要重启服务
    const { createProxyMiddleware } = require('http-proxy-middleware');
    
    module.exports = function(app) {
        app.use(
            '/api',
            createProxyMiddleware({
                target: 'https://www.baidu.com',
                changeOrigin: true,
                pathRewrite: { // 请求中去除/api
                    "^/api": ""
                },
                onProxyReq: function onProxyReq(proxyReq, req, res) {
                    console.log('-->  ', req.method, req.baseUrl, '->', proxyReq.host + proxyReq.path);
                },
            })
        );
    };

     

3、vue框架跨域

vue中实现开发环境的时的反向代理进行跨域解决,在项目根目录下面创建一个vue.config.js文件,写下如下代码
vue.config.js部分配置:

module.exports={
    // 指定服务器模块
    devServer:{
        // 代理
        proxy:{
            '/v1/api':{
                // 目标地址
                target:'http://localhost:3000',
                changeOrigin:true,
                pathRewrite:{
                    '/v1/api':'/api'
                }
            }
        }
    }
}

五、document.domain + iframe跨域

前提条件

这两个域名必须属于同一个一级域名!而且所用的协议,端口都要一致,否则无法利用document.domain进行跨域。
Javascript出于对安全性的考虑,而禁止两个或者多个不同域的页面进行互相操作。
而相同域的页面在相互操作的时候不会有任何问题。

比如在百度(https://www.baidu.com)页面控制台中输入:

alert(document.domain);              //"www.baidu.com"

我们也可以给document.domain属性赋值,不过是有限制的,你只能赋成当前的域名或者一级域名。
比如:

alert(document.domain = "baidu.com");     //"baidu.com"
alert(document.domain = "www.baidu.com"); //"www.baidu.com"

上面的赋值都是成功的,因为www.baidu.com是当前的域名,而baidu.com是一级域名。

但是下面的赋值就会出来"参数无效"的错误:
比如:

alert(document.domain = "qq.com");     //参数无效  报错
alert(document.domain = "www.qq.com"); //参数无效  报错

因为qq.com与baidu.com的一级域名不相同,所以会有错误出现。
这是为了防止有人恶意修改document.domain来实现跨域偷取数据。

1.比如:
baidu.com的一个网页(baidu.html)里面 利用iframe引入了一个qq.com里的一个网页(qq.html)。

这时在baidu.html里面可以看到qq.html里的内容,但是却不能利用javascript来操作它。因为这两个页面属于不同的域,在操作之前,js会检测两个页面的域是否相等,如果相等,就允许其操作,如果不相等,就会拒绝操作。
这里不可能把baidu.htmlqq.html利用JS改成同一个域的。因为它们的一级域名不相等。(强制用JS将它们改成相等的域的话会报跟上面一样的"参数无效错误。")
但如果在baidu.html里引入baidu.com里的另一个网页,是不会有这个问题的,因为域相等。

2.另一种情况,有两个子域名:
news.baidu.com(news.html)
map.baidu.com(map.html)
news.baidu.com里的一个网页(news.html)引入了map.baidu.com里的一个网页(map.html)

这时news.html里同样是不能操作map.html里面的内容的。
因为document.domain不一样,一个是news.baidu.com,另一个是map.baidu.com。
这时我们就可以通过Javascript,将两个页面的domain改成一样的,
需要在a.html里与b.html里都加入:

document.domain = “baidu.com”;

这样这两个页面就可以互相操作了。也就是实现了同一一级域名之间的"跨域"。

举例

news.baidu.com下的news.html页面:

<script>
    document.domain = 'baidu.com';
    var ifr = document.createElement('iframe');
    ifr.src = 'map.baidu.com/map.html';
    ifr.style.display = 'none';
    document.body.appendChild(ifr);
    ifr.onload = function(){
        var doc = ifr.contentDocument || ifr.contentWindow.document;
        // 这里可以操作map.baidu.com下的map.html页面
        var oUl = doc.getElementById('ul1');
        alert(oUl.innerHTML);
        ifr.onload = null;
    };
</script>

map.baidu.com下的map.html页面:

<ul id="ul1">我是map.baidu.com中的ul</ul>
<script>
    document.domain = 'baidu.com';
</script>

六、location.hash + iframe跨域

hash 属性是一个可读可写的字符串,该字符串是 URL 的锚部分(从 # 号开始的部分)。

实现原理

  • a想要与b跨域相互通信,通过中间页c来实现。
  • 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。
  • 利用location.hash传值,创建定时器,坚持hash的变化,执行相应的操作。
    下面我们来完成一个案例:

具体实现

    • A域:a.html -> B域:b.html -> A域:c.html
    • a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。
1)a.html:(http://www.baidu1.com/a.html)
<iframe id="iframe" src="http://www.baidu2.com/b.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 向b.html传hash值
    setTimeout(function() {
        iframe.src = iframe.src + '#user=admin';
    }, 1000);
    
    // 开放给同域c.html的回调方法
    function onCallback(res) {
        alert('data from c.html ---> ' + res);
    }
</script>

2)b.html:(http://www.baidu2.com/b.html)

<iframe id="iframe" src="http://www.baidu1.com/c.html" style="display:none;"></iframe>
<script>
    var iframe = document.getElementById('iframe');

    // 监听a.html传来的hash值,再传给c.html
    window.onhashchange = function () {
        iframe.src = iframe.src + location.hash;
    };
</script>

3)c.html:(http://www.baidu1.com/c.html)

<script>
    // 监听b.html传来的hash值
    window.onhashchange = function () {
        // 再通过操作同域a.html的js回调,将结果传回
        window.parent.parent.onCallback('hello: ' + location.hash.replace('#user=', ''));
    };
</script>

优缺点

  • location.hash + iframe跨域的优点:

    1. 可以解决域名完全不同的跨域
    2. 可以实现双向通讯
  • location.hash + iframe跨域的缺点:

    1. location.hash会直接暴露在URL里,并且在一些浏览器里会产生历史记录,数据安全性不高也影响用户体验
    2. 另外由于URL大小的限制,支持传递的数据量也不大。

七、window.name + iframe解决跨域

window.name属性的独特之处:只要在一个window下,无论url怎么变化,只要设置好了window.name,那么后续就一直都不会改变。同理,在iframe中,即使url在变化,iframe中的window.name也是一个固定的值,利用这个,我们就可以实现跨域了(2MB)。

举例

test1.html

<body>
  <h2>test1页面</h2>
  <iframe src="http://192.168.0.1/php_demo/test2.html" frameborder="1"></iframe>
  <script>
    var ifr = document.querySelector('iframe')
    ifr.style.display = 'none'
    var flag = 0;
    ifr.onload = function () {
        console.log('跨域获取数据', ifr.contentWindow.name);
        ifr.contentWindow.close();
    }
  </script>
</body>

test2.html

<body>
  <h2>test2页面</h2>
  <script>
    var person = {
      name: '大鹏_yp',
      age: 24,
      school: 'lngydx'
    }
    window.name = JSON.stringify(person)
  </script>
</body>

通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全操作。

八、postMessage通信跨域

PWA渐进式web应用

  • 在HTML5中新增了postMessage方法,postMessage可以实现跨文档消息传输(Cross Document Messaging)
  • 该方法可以通过绑定window的message事件来监听发送跨文档消息传输内容。
  1. 它可用于解决以下方面的问题:

    a. 页面和其打开的新窗口的数据传递
    b. 多窗口之间消息传递
    c. 页面与嵌套的iframe消息传递
    d. 上面三个场景的跨域数据传递

  2. postMessage用法:

    postMessage(data,origin)方法接受两个参数
    

    参数说明:

    • data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
    • origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

举例

    • postMessage:发送
    • onmessage:接收
      1)a.html:(http://www.baidu1.com/a.html)
      <iframe id="iframe" src="http://www.baidu2.com/b.html" style="display:none;"></iframe>
      <script>       
          var iframe = document.getElementById('iframe');
          iframe.onload = function() {
              var data = {
                  name: 'aym'
              };
              // 向domain2传送跨域数据
              iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.baidu2.com');
          };
      
          // 接受baidu2返回数据
          window.addEventListener('message', function(e) {
              alert('data from baidu2 ---> ' + e.data);
          }, false);
      </script>

      2)b.html:(http://www.baidu2.com/b.html)

      <script>
          // 接收baidu1的数据
          window.addEventListener('message', function(e) {
              alert('data from baidu1 ---> ' + e.data);
      
              var data = JSON.parse(e.data);
              if (data) {
                  data.number = 16;
      
                  // 处理后再发回baidu1
                  window.parent.postMessage(JSON.stringify(data), 'http://www.baidu1.com');
              }
          }, false);
      </script>

九、WebSocket协议跨域

  • WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯,是server push技术的一种很好的实现。
  • 原生WebSocket API使用起来不太方便,我们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。

WebSocket 如何工作

Web浏览器和服务器都必须实现 WebSockets 协议来建立和维护连接。由于 WebSockets 连接长期存在,与典型的HTTP连接不同,对服务器有重要的影响。

基于多线程或多进程的服务器无法适用于 WebSockets,因为它旨在打开连接,尽可能快地处理请求,然后关闭连接。任何实际的 WebSockets 服务器端实现都需要一个异步服务器。

案例

1)前端代码:

<div>user input:<input type="text"></div>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<script>
var socket = io('http://www.baidu2.com:8080');

// 连接成功处理
socket.on('connect', function() {
    // 监听服务端消息
    socket.on('message', function(msg) {
        console.log('data from server: ---> ' + msg); 
    });

    // 监听服务端关闭
    socket.on('disconnect', function() { 
        console.log('Server socket has closed.'); 
    });
});

document.getElementsByTagName('input')[0].onblur = function() {
    socket.send(this.value);
};
</script>

2)Nodejs socket后台:

var http = require('http');
var socket = require('socket.io');

// 启http服务
var server = http.createServer(function(req, res) {
    res.writeHead(200, {
        'Content-type': 'text/html'
    });
    res.end();
});

server.listen('8080');
console.log('Server is running at port 8080...');

// 监听socket连接
socket.listen(server).on('connection', function(client) {
    // 接收信息
    client.on('message', function(msg) {
        client.send('hello:' + msg);
        console.log('data from client: ---> ' + msg);
    });

    // 断开处理
    client.on('disconnect', function() {
        console.log('Client socket has closed.'); 
    });
});

 

posted @ 2023-02-06 15:31  marilol  阅读(232)  评论(0)    收藏  举报