前端初学者都应该知道得跨域通信方式

611 阅读19分钟

跨域是一个我们经常遇到的问题,特别是当服务有两个域名的时候,如果你的页面是在 a.test.com,然后需要向 b.test.com 请求数据,这个时候就存在跨域的问题了。如果你直接请求,你就会发现浏览器就直接报错了,这个时候就要了解什么是 同源策略 了。

 

什么是同源策略及其限制

概念

同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互,这是一个用于隔离潜在恶意文件的关键安全机制。

什么是源

协议、域名与端口。这三者任何一个不一样的话,就算是不同源,就会产生跨域的问题。

什么是限制

不是一个源的文档,就没有权限去操作另一个源的文档。

Cookie、LocalStorage 和 IndexDB无法读取。

Dom无法获得

Ajax请求不能发送

为什么要有同源限制

举个简单的例子,我们登陆是需要带上 cookie 的,比如我们登录了一个银行网站,那么我们的浏览器中则会带上相应的 cookie,下次我们这个银行网站的时候,如果这个 cookie 还存在的话,就可以不需要登录了;

如果浏览器没有同源策略,那么一些不法分子可以拿到我们浏览器的 cookie 从而进我们的银行账号取钱,这是非常可怕的事情。

这就是浏览器要有同源策略的原因。

常见的跨域场景

URL                                      说明                    是否允许通信
http://www.domain.com/a.js
http://www.domain.com/b.js         同一域名,不同文件或路径           允许
http://www.domain.com/lab/c.js

http://www.domain.com:8000/a.js
http://www.domain.com/b.js         同一域名,不同端口                不允许
 
http://www.domain.com/a.js
https://www.domain.com/b.js        同一域名,不同协议                不允许
 
http://www.domain.com/a.js
http://192.168.4.12/b.js           域名和域名对应相同ip              不允许
 
http://www.domain.com/a.js
http://x.domain.com/b.js           主域相同,子域不同                不允许
http://domain.com/c.js
 
http://www.domain1.com/a.js
http://www.domain2.com/b.js        不同域名                         不允许

 

跨域通信的几种方式

因为浏览器存在跨域这个问题,但是我们平时在开发过程中,有很多场景需要用到跨域通信,比如前端和后端的接口源不是同一个的时候,这个时候我们就需要进行跨域通信了。

  • jsonp
  • CORS:跨域资源共享
  • postMessageHTML5 提供的 api
  • Hash:不同源可以访问 hash
  • window.name:不同的页面(甚至不同域名)加载后依旧存在
  • document.domain:主域相同的情况
  • websocketHTML5 提供的新的协议
  • webpack:等构建构建工具中提供的跨域解决办法
  • nginx:这个属于后端的,笔者在本文就不做详细介绍了,大家可以自行找相关资料,其实也蛮简单的

接下来我们来详细讲一下这几种解决跨域问题的通信方式,所有代码都会放在这个仓库中:跨域通信 示例代码

JSONP

通常为了减轻 web 服务器的负载,我们把 jscssimg 等静态资源分离到另一台独立域名的服务器上,在html 页面中再通过相应的标签从不同域名下加载静态资源,而被浏览器允许。

基于此原理,我们可以通过动态创建 script,再请求一个带参网址实现跨域通信。

案例代码

我们新建 jsonp 文件夹,并新建三个文件

  • jsonp.js:封装了一个简易的 jsonp 方法,jsonp 其实就是新建一个 script 文件,因为我们上面也讲到了 script 是不会跨域的,接着将需要请求的链接放在 scriptsrc 中,同时在 window 上挂一个全局 callback 方法,后端返回的时候会将你要的数据放在这个 callback 的参数中,这个时候你就可以得到这些数据了。
function jsonp ({
  url,
  params,
  cb
}) {
  return new Promise(function(resolve, reject) {
    let script = document.createElement('script');
    // 全局挂一个方法
    window[cb] = function(data) {
      resolve(data);
      document.body.removeChild(script);
    }
    // 将 cb 放在 params 中
    params = { ...params, cb};
    let arrs = [];
    // 将 params 中的 key 拼成类似 a=1 形式 
    for(let key in params) {
      arrs.push(`${key}=${params[key]}`);
    }
    // 开始拼 url,目标结果 a=1&b=2&c=3
    script.src = `${url}?${arrs.join('&')}`;
    document.body.appendChild(script);
  })
}
  • server.js:起一个服务,我们使用 express 来起一个 Node.js 服务,接下去其他项目的例子笔者也会使用类似于下面这个 js 来启动服务,如果是增加方法我会特别标出:
let express = require('express');
let app = express();

// 以当前目录作为服务器静态目录
// 我们可以通过 localhost:3000/index.html 访问 html 文件
app.use(express.static(__dirname)); 

app.get('/jsonp', function(req, res) {
  // 准备放回给前端的数据
  const data = {
    data: '我是 jsonp 的 data',
  }

  // 获取前端传的 cb 方法
  const { cb } = req.query;

  // 设置输出的 html 格式为 utf-8
  res.writeHead(200,{'Content-Type':'text/html;charset=utf-8'})

  if (cb) {
    // 如果有 cb 参数,就讲 data 返回给前端
    res.end(`${cb}(${JSON.stringify(data)})`);
  } else {
    // 如果没有 cb,就返回错误
    res.end('未传 callback 参数');
  }
  
})

app.listen(3000, () => {
  console.log('服务器已经在3000端口启动');
});
  • index.html:jsonp 例子 html 文件,发 jsonp 的请求
...
<body>
  <h1>Jsonp 的 demo</h1>
  <script src="./jsonp.js"></script>
  <script>
    jsonp({
      url: 'http://localhost:3000/jsonp', 
      params: {
        key: '1',
      },
      cb: 'showData',
    }).then((data) => {
      console.log('data', data);
    })
  </script>
</body>
...

我们在根目录下运行一下 node ./jsonp/server.js,访问 localhost:3000

可以看到在页面中显示了 index.html 的内容,同时在控制台输出了相应的 data

在 jquery 中使用:

jquery 中已经帮我们封装好了,直接使用即可,但需要先引入 jquery,可以使用 $.ajax 规定 dataType,具体笔者也不演示了,使用如下:

// ...
$.ajax({
  url: 'http://localhost:3000/jsonp',
  type: 'get',
  dataType: 'jsonp',  // 请求方式为jsonp
  success:function(ret){
    console.log(ret);
  }
});

一些说明

使用起来很简单,兼容性好,能兼容一些远古浏览器。但是他只支持 GET 请求,同时会有安全的问题,可能会被 xss 注入。

 

CORS

CORS 是一个 W3C 标准,全称是"跨域资源共享"(Cross-origin resource sharing)。它允许浏览器向跨源发送 XMLHttpRequest,主要在后端服务器中增加一些相关配置即可,但有的时候需要前端进行一些配合,接下来我会讲几个常用的配置:

案例代码

我们新建 cors 文件夹,并新建四个文件,一个 html 文件,两个服务器文件,一个自己的服务器文件,一个目标服务器文件,用来我们做请求使用;一个简单封装的 ajax.js 文件:

  • ajax.js

这里写了一个简单的 ajax,具体就不细讲了,大家想深入研究可以参考:Ajax 知识体系大梳理

function ajax(
  methods,
  url
) {
  // 新建一个 xhr 实例
  // 在这里没有兼容 IE6, IE5,
  // IE6,IE5 可以使用 newActiveXObject("Microsoft.XMLHTTP");  
  const xhr = new XMLHttpRequest();

  // 初始化一个请求
  // methods:请求类型
  // url:链接
  // async:是否异步,true (异步); false(同步)
  xhr.open(methods, url, true);

  // 只要 readyState 属性发生变化
  // 状态变化的监听函数
  xhr.onreadystatechange = function() {
    if (xhr.readyState === 4) {
      // 返回成功的时候,输出相应的 response
      if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
        console.log(xhr.response);
      }
    }
  }  

  xhr.send();
}
  • 两个 server 文件
server1.js

这个文件和 jsonpserver.js 一样,在 3000 端口开启一个服务器,让我们的 index.html 能够在 localhost:3000/index.html 访问。

server2.js

这个文件是目标服务器文件,端口号 4000,用来我们发送 ajax 请求,这里我们增加了一个 getData 方法:

// ...
app.get('/getData', function(req, res) {
  const data = {
    data: '我是 cors 的 data',
  }

  res.end(JSON.stringify(data));  
})
// ...
  • index.html

html 文件用来发送 ajax 请求:

...
<body>
  <h1>Cors demo</h1>
  <script src="./ajax.js"></script>
  <script>
    ajax('GET', 'http://localhost:4000/getData');
  </script>
</body>
...

接下来我们启动两个服务,访问一下 localhost:3000/index.html

发现有一个报错:

这时我们就需要在后端配置 CORS 了。

配置 CORS

我们可以修改一下 server2.js,增加 Access-Control-Allow-Origin 这个头,一般来说我们改成 *,就能解决这个问题了,但是在项目中,我们可能需要设置一个白名单,规定哪些源可以访问,我们可以做如下配置:

let whiteList = ['http://localhost:3000']

// exprss 的一个中间件,所有的路由都会走一下
app.all('*', function (req, res, next) {
  // 获取请求的源
  let origin = req.headers.origin;

  if (whiteList.includes(origin)) {
    // 设置哪个源访问
    res.setHeader('Access-Control-Allow-Origin', origin);
  } else {
    // 不在白名单中 返回
    res.end('你不在白名单中');  
  }
  // 继续执行
  next();
});

这个时候我们重新运行 server2.js,我们就能拿到相应的值了:

更多配置

我们再来介绍几个常用的配置:

Access-Control-Allow-Headers

如果用户想要在头部设置一些值,则在后端需要定义这个头,它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段。如果不定义会报如下错误:

如果我们想要在 header 中增加 name=darrell,我们在 ajax.js 中新增如下代码:

//...
xhr.open(methods, url, true);
// ...
xhr.setRequestHeader('name', 'darrell'); // 增加一个头
// ...
xhr.send();

servers.js 增加:

// ...
app.all('*', function (req, res, next) {
  // ...
  if (whiteList.includes(origin)) {
    // 设置哪个源访问
    res.setHeader('Access-Control-Allow-Origin', origin);
    // 允许前端携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name,key');
  }
  // ...
});
Access-Control-Allow-Methods

它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。如果我的请求方法为 put,不设置次字段会报错:

server.js 中增加此头:

app.all('*', function (req, res, next) {
  // ...
  if (whiteList.includes(origin)) {
    // 设置哪个源访问
    res.setHeader('Access-Control-Allow-Origin', origin);
    // 允许前端携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name,key');
    // 允许哪个方法访问
    res.header("Access-Control-Allow-Methods","PUT");
  }
  // ...
});
Access-Control-Allow-Credentials

它的值是一个布尔值,表示是否允许发送 Cookie。默认情况下,Cookie 不包括在 CORS 请求之中。设为 true,即表示服务器明确许可,Cookie 可以包含在请求中,一起发给服务器。

如果在浏览器端设置了允许携带 cookiexhr.withCredentials = true;,那么必须要在后端设置这个头,不然会如下错:

server.js 中增加此头:

app.all('*', function (req, res, next) {
  // ...
  if (whiteList.includes(origin)) {
    // 设置哪个源访问
    res.setHeader('Access-Control-Allow-Origin', origin);
    // 允许前端携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name,key');
    // 允许哪个方法访问
    res.header("Access-Control-Allow-Methods","PUT");
    // 允许携带 cookie
    res.header("Access-Control-Allow-Credentials", true);
  }
  // ...
});
Access-Control-Expose-Headers

CORS 请求时,XMLHttpRequest对象的 getResponseHeader() 方法只能拿到6个基本字段:Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma。如果想拿到其他字段,就必须在 Access-Control-Expose-Headers 里面指定。

比如我要在 ajaxresponse 中取返回头中的 name

// ...
if (xhr.readyState === 4) {
  if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
    console.log(xhr.response);
    console.log(xhr.getResponseHeader('name'));
  }
}
// ...

同时在 getData 这个接口写一个 name 上去:

app.get('/getData', function(req, res) {
  // ...
  res.setHeader('name', 'maolei');
  // ... 
})

如果后端没有设置次头,则会有如下报错:

server.js 中增加此头:

app.all('*', function (req, res, next) {
  // ...
  if (whiteList.includes(origin)) {
    // 设置哪个源访问
    res.setHeader('Access-Control-Allow-Origin', origin);
    // 允许前端携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name,key');
    // 允许哪个方法访问
    res.header("Access-Control-Allow-Methods","PUT");
    // 允许携带 cookie
    res.header("Access-Control-Allow-Credentials", true);
    // 允许前端获取哪个头
    res.header("Access-Control-Expose-Headers", 'name');
  }
  // ...
});
Access-Control-Max-Age

用来指定本次预检请求的有效期,单位为秒。预检请求指的是浏览器针对于复杂请求的时候,会在正式通信之前,增加一次HTTP查询请求(option),称为 预检请求。我们可以设置这个请求的有效期:

app.all('*', function (req, res, next) {
  // ...
  if (whiteList.includes(origin)) {
    // 设置哪个源访问
    res.setHeader('Access-Control-Allow-Origin', origin);
    // 允许前端携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers', 'name,key');
    // 允许哪个方法访问
    res.header("Access-Control-Allow-Methods","PUT");
    // 允许携带 cookie
    res.header("Access-Control-Allow-Credentials", true);
    // 允许前端获取哪个头
    res.header("Access-Control-Expose-Headers", 'name');
    // 用来指定本次预检请求的有效期,单位为秒
    res.header("Access-Control-Max-Age","6");
  }
  // ...
});

更多的请求,笔者在这里就不讲了,大家可以参考阮一峰老师的 跨域资源共享 CORS 详解

一些说明

CORS 请求是比较安全的,而且支持所有类型的 HTTP 请求,配置起来也相对比较方便;开发者可以使用普通的 XMLHttpRequest 发起请求和获得数据,比起 JSONP 有更好的错误处理;而且在绝大多数现代浏览器都已经支持了 CORS,如果业务中不需要兼容远古浏览器的,就可以放心的使用 CORS 了。

 

postMessage

postMessageHTML5 中的API,且是为数不多可以跨域操作的 window 属性之一,它可用于解决以下方面的问题

  • 页面和其打开的新窗口的数据传递,可以通过 opener 来进行通行。
  • 多窗口之间消息传递
  • 页面与嵌套的 iframe 消息传递
  • 上面三个场景的跨域数据传递

这里我们就来举一个 iframe 嵌套的例子:

案例代码

我们新建 postmessage 文件夹,并新建四个文件,两个不同源 html 文件,两个服务器文件,一个自己的服务器文件,一个目标服务器文件,用来我们做请求使用;

  • 两个 html

a.html 中新建一个 iframe,其 srchttp://localhost:4000/b.html

<!-- a.html -->
...
<body>
  <h1>postmessage A 页面</h1>
  <iframe
    src="http://localhost:4000/b.html"
    id="frame"
    frameborder="10"
    onload="load()"
  ></iframe>
</body>
...

<!-- b.html -->
...
<body>
  <h1>postmessage B 页面</h1>
</body>
...
  • 两个 server 文件

两个 server 文件和 CORS 一样,只不过都是开启两个简单的服务而已。

需求

我么想要从 a 页面发送一些信息给 b 页面,同时 b 在收到消息后在回信给 a 页面。

我们可以修改 a.html,补充 iframeload 函数,我们在 a 页面发送我们定义好的信息给 b 页面,同时监听 b 页面回发过来的信息,具体配置可参考 postMessge 官方文档

...
<script>
  function load() {
    let frame = document.getElementById("frame");
    var iwindow = frame.contentWindow;

    // 发送 postMessage 信息
    // 第一个:要传的数据
    // 第二个:指定源
    iwindow.postMessage({
      type: 'a',
      data: "我是 a 发的消息",
    }, "http://localhost:4000")
  }
	
  // 监听 b 页面发过来的信息
  window.addEventListener('message', function(event){
    // 拿到数据,判断是不是从 b 发过来的
    // 如果不是 直接 return
    const { type, data } =  event.data;
    if (!event.data || type !== 'b') {
      return;
    }
    console.log(data);
  })
</script>
...

b 中监听 a 发过来的信息:

...
<script>
  window.addEventListener('message', function(event){
    console.log(event.data.data);

    event.source.postMessage({
      type: 'b',
      data: "我是 b 发的消息",
    }, event.origin);
  })
</script>
...

我们开启两个服务,在页面中打开 localhost:3000/a.html,可以看到信息打印了出来:

 

Hash

不同域之间可以通过 window.location.hash 进行传递相应的信息,在同域名直接可以直接通过 js 访问来设置:

案例代码

我们新建 hash 文件夹,并新建五个文件,三个 html 文件,两个同源 html,一个不同源 html,两个服务器文件,一个自己的服务器文件,一个目标服务器文件,用来我们做请求使用,服务器文件和之前一样,就不列举了。

  • 三个 html 文件

两个同源 htmlab 文件,在 3000 端口下,另外一个 c4000 端口下:

<!-- a.html -->
...
<body>
  <!-- 路径后的 hash 可以用来通信 -->
  <!-- a 访问 c -->
  <h1>window-hash a.html</h1>
  <iframe
    src="http://localhost:4000/c.html#hashname=darrell"
    id="frame"
    frameborder="10"
  ></iframe>
</body>
...

<!-- b.html -->
...
<body>
  <h1>window-hash b.html</h1>
</body>
...

<!-- c.html -->
...
<body>
  <h1>window-hash c.html</h1>
</body>
...

需求:

完成 ac 页面的通信过程,在 a.html 中的 iframec.html 发送 #hashname=darrellc 取到之后在发送相关的信息给 aa 在进行相应的处理。

其实很简单,我们在 c 得到 a 发过来的信息之后在新建 iframe 指向一个和a 同源的 b,作为一个中转站,因为 ab 同源,接着就可以直接通过 js 访问了,我们修改一下 html 代码:

<!-- a.html -->
...
<body>
  <!-- 路径后的 hash 可以用来通信 -->
  <!-- a 访问 c -->
  <h1>window-hash a.html</h1>
  <iframe
    src="http://localhost:4000/c.html#hashname=darrell"
    id="frame"
    frameborder="10"
  ></iframe>
  <script>
    // 监听 b 修改 hashchange
    window.onhashchange = function() {
      console.log('a 收到', location.hash);
    }
  </script>
</body>
...

<!-- b.html -->
...
<body>
  <h1>window-hash b.html</h1>
  <script>
    // 收到 c 发过来的 hash
    console.log('b 收到', location.hash);
    window.parent.parent.location.hash = window.location.hash;
  </script>
</body>
...

<!-- c.html -->
...
<body>
  <h1>window-hash c.html</h1>
  <script>
    // 收到 a 发过来的 hash
    console.log('c 收到', window.location.hash);

    let iframe = document.createElement('iframe');
    iframe.src = 'http://localhost:3000/b.html#name=c';
    document.body.appendChild(iframe);
  </script>
</body>
...

我们开启两个服务,在页面中打开 localhost:3000/a.html,可以看到信息打印了出来:

 

window.name

window.name 有一个特性,就是 name 值在不同的页面(甚至不同域名)加载后依旧存在。因此我们也可以使用其帮助我们解决跨域问题。

案例代码

我们新建 name 文件夹,并新建五个文件,代码和 hash 的文件代码一样。

<!-- a.html -->
...
<body>
  <h1>window-name a.html</h1>
  <iframe
    src="http://localhost:4000/c.html#hashname=darrell"
    id="frame"
    frameborder="10"
  ></iframe>
</body>
...

<!-- b.html -->
...
<body>
  <h1>window-name b.html</h1>
</body>
...

<!-- c.html -->
...
<body>
  <h1>window-name c.html</h1>
  <script>
  	window.name = '我是来自 c 的信息';
  </script>
</body>
...

需求也类似,我需要在 a 获取到 c 这个页面进行定义的 window.name,其实很简单我们只要通过 b 这个中间页面进行数据传递就行了,当我们加载完成 c 页面这个 iframe 以后,再重新将 src 赋值为 http://localhost:3000/b.html,利用 name 的特性,这个时候 a 页面就可以通过 window.name 拿到 c 中的 name 了,我们只需要修改 a.html 文件就可以了:

<!-- a.html -->
...
<body>
  <h1>window-name a.html</h1>
  <iframe
    src="http://localhost:4000/c.html"
    id="frame"
    frameborder="10"
    onload="load()"
  ></iframe>
  <script>
    let first = true;

    function load() {
      let frame = document.getElementById("frame");
      let iwindow = frame.contentWindow;

      if (first) {
        
        frame.src = 'http://localhost:3000/b.html';
        first = false;
      } else {
        console.log(iwindow.name);
      }
    }
  </script>
</body>
...

我们开启两个服务,在页面中打开 localhost:3000/a.html,可以看到信息打印了出来:

 

document.domain

在两个域名是同一个主域的时候,如果需要进行跨域通信,我们可以将两个页面都通过 js 强制设置document.domain 为基础主域,这就实现了同域。

案例代码

我们新建 domain 文件夹,并新建四个文件,两个不同源的 html,两个服务文件和 name 相同:

我们配置一下系统的 host 文件,将 127.0.0.1 配置出两个虚拟域名:a.example.cnb.example.cn

这样我们就能使用 a.example.cn:3000/a.htmlb.example.cn:4000/b.html 来访问两个 html 文件了,

因为两个域名的主域相同,我们可以设置 document-domain=example.cn 来实现:

<!--a.html-->
...
<body>
  <!-- 配置 host -->
  <!-- http://a.example.cn:3000/a.html -->
  <h1>document-domain a.html</h1>
  <iframe
    src="http://b.example.cn:3000/b.html"
    id="frame"
    frameborder="10"
    onload="load()"
  ></iframe>
  <script>
    document.domain = "example.cn";
    function load() {
      let frame = document.getElementById("frame");
      let iwindow = frame.contentWindow;
      console.log(iwindow.name);
    }
  </script>
</body>
...

<!--b.html-->
...
<body>
  <h1>document-domain b.html</h1>
  <script>
    document.domain = "example.cn";
    var name = 'b 定义的 name';
  </script>
</body>
...

我们开启两个服务,在页面中打开 http://a.example.cn/:3000/a.html,可以看到信息打印了出来:

一些说明

需要注意的是此方案只能在主域相同,子域不同的跨域应用场景。其他情况下不能使用。

 

webSocket

WebSocketHTML5 一种新的协议。它实现了浏览器与服务器全双工通信,同时允许跨域通讯。

我们先用原生的 WebSocket来举个例子,之后再使用 Socket.io 来举个例子:

原生 WebSocket

我们新建 websocket 文件夹,新建 socket.htmlserver.js 文件:

  • socket.html

我们创建一个 socket 实例,并向服务端发送一些消息,同时监听服务端发送给客户端的消息。

...
<body>
  <h1>Socket demo</h1>
  <script>
    let socket = new WebSocket("ws://localhost:3000");

    socket.onopen = function() {
      socket.send('浏览器发出的内容');
    }

    socket.onmessage = function(e) {
      console.log(e.data);
    }

  </script>
</body>
...
  • server.js

我们安装一个依赖 ws,用来处理 websocket,接着起一个 websocket 服务器,监听客户端的信息,同时发送相应的信息出到客户端。

let express = require('express');
let WebSocket = require('ws');
let wss = new WebSocket.Server({ port: 3000 });

wss.on('connection', function(ws) {
  ws.on('message', function(data) {
    console.log(data);

    ws.send('服务器发的内容');
  })
})

我们开启一下服务,这个时候我们可以直接打开 file 协议的 socket.html,我们可以在控制台看到服务器发送过来的信息,同时也可以在服务端开到客户端发送过来的信息:

 

sockit.io

因为原生 WebSocket API 不是特别好用,兼容性也不好,我们可以使用 Socket.io,它很好地封装了 webSocket 接口,提供了更简单、灵活的接口。同时对不支持 Websocket 的浏览器做了向下兼容。

我们这个例子参考了官方的一个 简易聊天室,同时我们需要安装一下 socket.io

npm install socket.io
html 代码
<!-- 引入 socketIO.html -->
...
<body>
  <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>Send</button>
    </form>
  </ul>

  <!-- 引入 socket.io 和 jquery 的 cdn -->
  <script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
  <script src="https://code.jquery.com/jquery-1.11.1.js"></script>

  <script>
    $(function () {
      //建立一个socket
      var socket = io();
      $('form').submit(function(){
        //发送socket到服务器。
        socket.emit('chat message', $('#m').val());
        $('#m').val('');
        return false;
      });
      socket.on('chat message', function(msg){
        //接受服务器传回来的数据,新建一个li标签
        $('#messages').append($('<li>').text(msg));
        window.scrollTo(0, document.body.scrollHeight);
      });
    });
  </script>
</body>
...
serverIO.js
var app = require('express')();
var http = require('http').createServer(app);
var io = require('socket.io')(http);
var port = process.env.PORT || 3000;

app.get('/', function(req, res){
  res.sendFile(__dirname + '/socketIO.html');
});

io.on('connection', function(socket){
  // 监听socket是否连接,如果连接的话,就发送数据,chat-message
  socket.on('chat message', function(msg){
    io.emit('chat message', msg);
  });

  // 监听socket是否断开,断开时执行相应的方法
  socket.on('disconnect', function(){
    console.log('user disconnected');
  }); 
});

http.listen(port, function(){
  console.log('listening on *:' + port);
});

我们启动一下服务:node ./websocket/serverIO.js:我们可以在两个页面互发消息,如下图:

 

webpack 中的 devServer

如果你在你的项目中使用了 webpack,那么 webpackdevServer 中提供了相应的配置参数,你只要在其中进行配置即可:

module.exports = {
  ...
  devServer: {
    // 代理
    proxy: {
      '/movie/': {
      	target: 'https://douban.uieee.com/v2',
        // 是否可以允许跨域
      	changeOrigin: true,
      	headers: {
          host:'www.example.org',
          cookie: 'isLogin=1' // 判断是否登录的 cookie 信息
        }
      }
    }
  }
};

更多的关于 webpackdevServer 的配置,大家可以参考笔者写的 webpack-dev-server 高级配置,在这里就不细讲了。

 

一些说明

文章参考了 前端常见跨域解决方案(全),同时加入了自己的思考和代码示例,希望大家看完之后,能对跨域通信的解决办法了然于胸。

 

相关资料

 

示例代码