# 跨域

# 1. 同源策略

# 1.1 背景

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

同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个IP地址,也非同源。

# 1.2 限制范围

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

# 1.3 跨域的三个条件

协议、域名、端口,只要有一个不同就是跨域

# 1.4 跨域的场景

  • 域名不同:www.a.com 和 www.b.com
  • 域名相同,端口不同:www.a.com:8080 和 www.a.com:8081
  • 协议不同:http://www.a.com 和 https://www.a.com
  • 主域相同,子域不同:a.b.com 和 c.b.com

# 1.5 跨域规则表

URL 说明 是否允许通信
http://www.a.com/a.js
http://www.a.com/b.js
同一域名下 允许
http://www.a.com/lab/a.js
http://www.a.com/script/b.js
同一域名下不同文件夹 允许
http://www.a.com:8000/a.js
http://www.a.com/b.js
同一域名,不同端口 不允许
http://www.a.com/a.js
https://www.a.com/b.js
同一域名,不同协议 不允许
http://www.a.com/a.js
http://74.125.136.136/b.js
域名和域名对应IP 不允许
http://www.a.com/a.js
http://script.a.com/b.js
主域相同,子域不同 不允许
http://www.a.com/a.js
http://www.b.com/b.js
不同域名 不允许

# 1.6 跨域场景分类

跨域一共有以下三种情况:

  • 跨域写操作(Cross-origin writes)一般是被允许的。例如链接(links),重定向以及表单提交。
  • 跨域嵌入(Cross-origin embedding)一般是被允许的。例如图片、脚本、iframe等。
  • 跨域读操作(Cross-origin reads)一般是不被允许的,但常可以通过内嵌代理来实现。例如,你可以读取嵌入图片的高度和宽度,调用内嵌脚本的方法,或者使用iframe嵌入。

cookie是服务器写在浏览器上的一小段数据,浏览器在访问服务器时会自动带上cookie。cookie是同源策略的产物,只有同源的请求才会带上cookie。

对于cookie来说,两个页面只要域名相同,即使端口号不同,也是可以共享cookie的。

document.cookie = "name=liam";
1

如果想在子域名之间共享cookie,可以设置cookie的domain属性:

document.cookie = "name=liam; domain=.a.com";
1

Cookie的同源策略不区分协议与端口

# 2. JSONP

JSONP(JSON with Padding)是数据格式JSON的一种"使用模式",可以让网页从别的域名(网站)获取资料,即跨域读取数据。 它是利用script标签没有跨域限制的漏洞,网页可以得到从其他来源动态产生的JSON数据。JSONP请求一定需要对方的服务器做支持才可以。

# 2.1 原理

  1. 声明一个回调函数,其函数名(如show)当作参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)
  2. 创建一个script标签,把跨域的API数据接口地址赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show)
  3. 服务器接收到请求后,需要进行特殊处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:show('data'),最后把该字符串返回给客户端
  4. 客户端接收到返回的字符串(show('data')),浏览器会把它解析成JS代码并执行,这样就能拿到数据了

# 2.2 示例

<script type="text/javascript">
  function callback (data){
    //处理获得的json数据
    console.log(data)
  }
</script>
<script src="http://www.node.huoyuhao.net/crossDomain?callback=callback"></script>
1
2
3
4
5
6
7

因为是当做一个js文件来引入的,所以 http://www.node.huoyuhao.net 返回的必须是一个能执行的js文件,所以这个页面的node代码可能是这样的:

const express = require('express');
const app = express();

app.get('/crossDomain', (req, res) => {
  const { query } = req;
  const reqData = { name: 'liam' };
  console.log(query);
  if (query.callback) {
    const str = `${query.callback }(${ JSON.stringify(reqData) })`; // jsonp
    res.end(str);
  } else {
    res.end(JSON.stringify(reqData));
  }
});

app.listen(3000, () => {
  console.log('App started on port 3000');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

最终输出结果为:callback({ name: "liam" })

# 2.3 优缺点

  • 优点
    • 它不像XMLHttpRequest对象实现的Ajax请求那样受到同源策略的限制;
    • 它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;
    • 并且在请求完毕后可以通过调用callback的方式回传结果。
  • 缺点
    • 它只支持GET请求而不支持POST等其它类型的HTTP请求;
    • 它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题。

示例网址 (opens new window)

# 3. document.domain + iframe

该方式只能用于主域名相同,子域名不同的跨域应用场景。

# 3.1 实现原理

两个页面都通过js强制设置document.domain为基础主域,就实现了同域。

浏览器有一个同源策略,其限制之一是不能通过ajax的方法去请求不同源中的文档。第二个限制是浏览器中不同域的框架之间是不能进行js的交互操作的。

不同的框架之间是可以获取window对象的,但却无法获取相应的属性和方法。比如,有一个页面,它的地址是http://www.test.huoyuhao.net/a.html,在这个页面里面有一个iframe,它的src是http://www.node.huoyuhao.net/b.html, 很显然,这个页面与它里面的iframe框架是不同域的,所以我们是无法通过在页面中书写js代码来获取iframe中的东西的。

这个时候,document.domain就可以派上用场了,我们只要把 http://www.test.huoyuhao.net/a.htmlhttp://www.node.huoyuhao.net/b.html 这两个页面的document.domain都设成相同的域名就可以了。但要注意的是,document.domain的设置是有限制的,我们只能把document.domain设置成自身或更高一级的父域,且主域必须相同。

  • 在页面 http://www.test.huoyuhao.net/a.html 中设置document.domain:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>document.domain + iframe跨域</title>
  <script type="text/javascript" src = "https://cdn.staticfile.org/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
  <div>A页面</div>
  <iframe style = "display: none" id = "iframe" src="http://www.node.huoyuhao.net/static/crossDomain/02/b.html" frameborder="0" onload="iframeLoad()"></iframe>
  <script type="text/javascript">
    $(function () {
      try {
        document.domain = "huoyuhao.net"
      } catch (e) {}
    })
    function iframeLoad (data) {
      var jq = document.getElementById('iframe').contentWindow.$
      jq.get("http://www.node.huoyuhao.net/crossDomain2", function(data){
        console.log(data)
      })
    }
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  • 在页面 http://child.a.com/b.html 中代码:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>document.domain + iframe跨域</title>
  <script type="text/javascript" src = "https://cdn.staticfile.org/jquery/3.5.1/jquery.min.js"></script>
</head>
<body>
  <div>B页面</div>
  <script type="text/javascript">
    document.domain = "huoyuhao.net"
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

最终输出结果为:{ name: "liam" }

# 3.2 优缺点

  • 优点
    • 解决了主域相同的跨域请求
  • 缺点
    • 比如一个站点受到攻击后,另一个站点会因此引起安全漏洞;
    • 若一个页面中引入多个 iframe,想要操作所有的 iframe 则需要设置相同的 domain;
    • 只支持主域相同的跨域请求,而且所用的协议,端口都要一致。

示例网址 (opens new window)

# 4. location.hash + iframe跨域

# 4.1 实现原理

a欲与b跨域相互通信,通过中间页c来实现。 三个页面,不同域之间利用iframe的location.hash传值,相同域之间直接js访问来通信。

# 4.2 示例

a.html -> B域:b.html -> A域:c.html,a与b不同域只能通过hash值单向通信,b与c也不同域也只能单向通信,但c与a同域,所以c可通过parent.parent访问a页面所有对象。

  • 在页面 http://www.test.huoyuhao.net/a.html 中代码:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>document.hash + iframe跨域</title>
</head>
<body>
  <div>A页面</div>
  <iframe style = "display: none" id = "iframe" src="http://www.node.huoyuhao.net/static/crossDomain/03/b.html" frameborder="0"></iframe>
  <script type="text/javascript">
    var iframe = document.getElementById('iframe')
    // 向b.html传hash值
    setTimeout(function() {
      iframe.src = iframe.src + '#name=liam'
    }, 1000)
    // 开放给同域c.html的回调方法
    function onCallback(res) {
      console.log('A回调函数:' + res)
    }
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • 在页面 http://www.node.huoyuhao.net/b.html 中代码:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>document.hash + iframe跨域</title>
</head>
<body>
  <div>B页面</div>
  <iframe style = "display: none" id = "iframe" src="http://www.test.huoyuhao.net/static/crossDomain/03/c.html" frameborder="0"></iframe>
  <script type="text/javascript">
    var iframe = document.getElementById('iframe')
    // 监听a.html传来的hash值,再传给c.html
    window.onhashchange = function () {
      console.log('B页面Hash值:' + location.hash)
      iframe.src = iframe.src + location.hash
    }
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • 在页面 http://www.test.huoyuhao.net/c.html 中代码:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>document.hash + iframe跨域</title>  <title>Document</title>
</head>
<body>
  <div>C页面</div>
  <script type="text/javascript">
    // 监听b.html传来的hash值
    window.onhashchange = function () {
      // 再通过操作同域a.html的js回调,将结果传回
      console.log('C页面Hash值:' + location.hash)
      window.parent.parent.onCallback('hello: ' + location.hash.replace('#name=', ''))
    }
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

最终输出结果为:

  • B页面Hash值:#name=liam
  • C页面Hash值:#name=liam
  • A回调函数:hello: liam

# 4.3 document.hash优缺点

  • 优点
    • 可以解决域名完全不同的跨域请求
    • 可以实现双向通讯
  • 缺点
    • 利用这种方法传递的数据量受到 url 大小的限制,传递数据类型有限
    • 由于数据直接暴露在 url 中则存在安全问题
    • 若浏览器不支持 onhashchange 事件,则需要通过轮训来获知 url 的变化
    • 有些浏览器会在 hash 变化时产生历史记录,因此可能影响用户体验

示例网址 (opens new window)

# 5. window.name + iframe跨域

window.name属性的独特之处:name值在不同的页面(甚至不同域名)加载后依然存在,并且可以支持非常长的 name 值(2MB)。

# 5.1 实现原理

  1. a.html欲向c.html传数据,那么先在a.html中创建一个隐藏的iframe,然后将iframe的src指向b.html(b.html也与a.html在同一域名下)
  2. b.html接收到数据后,将数据赋值给window.name
  3. a.html中监听iframe的onload事件,在此事件中将iframe的src指向c.html(c.html与a.html不同域)
  4. 因为window.name的特性,c.html可以获取到window.name中的数据

# 5.2 示例

  • 在页面 http://www.test.huoyuhao.net/a.html 中代码:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>window.name + iframe跨域</title>
</head>
<body>
  <div>A页面</div>
  <script type="text/javascript">
  var proxy = function(url, callback) {
    var state = 0
    var iframe = document.createElement('iframe')
    // 加载跨域页面
    iframe.src = url
    iframe.onload = function() {
      if (state === 1) {
        // 第2次onload(同域proxy页)成功后,读取同域window.name中数据
        callback(iframe.contentWindow.name)
        destroyFrame()

      } else if (state === 0) {
        // 第1次onload(跨域页)成功后,切换到同域代理页面
        iframe.src = 'about:blank' // 或者将里面的 about:blank 替换成某个同源页面
        state = 1
      }
    }
    document.body.appendChild(iframe)

    // 获取数据以后销毁这个iframe,释放内存;这也保证了安全(不被其他域frame js访问)
    function destroyFrame() {
      iframe.contentWindow.document.write('')
      iframe.contentWindow.close()
      document.body.removeChild(iframe)
    }
  }

  // 请求跨域b页面数据
  proxy('http://www.node.huoyuhao.net/static/crossDomain/04/b.html', function(data){
    console.log(data)
  })
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
  • 在页面 http://www.node.huoyuhao.net/b.html 中代码:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>window.name + iframe跨域</title>
</head>
<body>
  <div>B页面</div>
  <script type="text/javascript">
  window.name = '这是B页面数据内容'
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13

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

其实location.hash和window.name都是差不多的,都是利用全局对象属性的方法,然后这两种方法和jsonp也是一样的,就是只能够实现get请求。但是与 document.domain 方法相比,放宽了域名后缀要相同的限制,可以从任意页面获取 string 类型的数据。

示例网址 (opens new window)

# 6. postMessage跨域

HTML5为了解决跨域问题,引入了跨文档通信 API(Cross-document messaging),为window对象新增了postMessage方法,允许跨窗口通信,不论这两个窗口是否同源。

# 6.1 参数说明

  • otherWindow 其他窗口的一个引用,比如iframe的contentWindow属性、执行window.open返回的窗口对象、或者是命名过或数值索引的window.frames

  • message 将要发送到其他 window的数据,它将会被结构化克隆算法 (opens new window)序列化。这意味着你可以不受什么限制的将数据对象安全的传送给目标窗口而无需自己序列化

  • targetOrigin 通过窗口的origin属性来指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示无限制)或者一个URI。在发送消息的时候,如果目标窗口的协议、主机地址或端口这三者的任意一项不匹配targetOrigin提供的值,那么消息就不会被发送;只有三者完全匹配,消息才会被发送。这个机制用来控制消息可以发送到特定的窗口,例如,当用postMessage传送数据以避免其他站点的恶意攻击

    注意: 如果目标窗口是第三方站点,始终使用targetOrigin来指定确切的接收方,而不是使用"*",因为后者会让任何站点都能接收到你发送的消息

  • transfer(可选) 是一串和message 同时传递的 Transferable 对象. 这些对象的所有权将被转移给消息的接收方,而发送一方将不再保有所有权

# 6.2 示例

  • 在页面 http://www.test.huoyuhao.net/a.html 中代码:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>postMessage 跨域</title>
</head>
<body>
  <div>A页面</div>
  <iframe src="http://www.node.huoyuhao.net/static/crossDomain/05/b.html" style='display: none;'></iframe>
  <script>
  window.onload = function() {
    var targetOrigin = 'http://www.node.huoyuhao.net'
    var data = {
      name: 'liam',
      time: '2020.06.02'
    }
    // 向 b.html 发送消息
    window.frames[0].postMessage(data, targetOrigin)

    // 接收 b.html 发送的数据
    window.addEventListener('message', function(e) {
      console.log('data from node.huoyuhao.net', e.data)
    })
  }
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  • 在页面 http://www.node.huoyuhao.net/b.html 中代码:
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>postMessage 跨域</title>
</head>
<body>
  <div>B页面</div>
  <script>
  // 接收test.huoyuhao.net的数据
  window.addEventListener('message', function(e) {
    console.log('data from test.huoyuhao.net ---> ' + JSON.stringify(e.data))
    var data = e.data
    if (data) {
      data.name = 'huoyuhao'
      // 处理后再发回test.huoyuhao.net
      window.parent.postMessage(JSON.stringify(data), 'http://www.test.huoyuhao.net')
    }
  }, false)
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

示例网址 (opens new window)

更多应用请参看博客postMessage可太有用了 (opens new window)

# 7. CORS跨域

CORS(Cross-Origin Resource Sharing,跨域资源共享)是一个W3C标准,它允许浏览器向跨源服务器发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

# 7.1 请求分类

浏览器将CORS请求分为两类:

  1. 简单请求(simple request)
  2. 非简单请求(not-so-simple request)

同时满足以下条件的请求属于简单请求:

  1. 请求方法为HEAD、GET、POST之一
  2. HTTP头信息不超出以下字段:
    • Accept
    • Accept-Language
    • Content-Language
    • Last-Event-ID
    • Content-Type:仅限application/x-www-form-urlencoded、multipart/form-data、text/plain

不满足上述条件的请求为非简单请求。

# 7.2 简单请求处理

对于简单请求,浏览器直接发出CORS请求,在头信息中增加Origin字段:

GET /cors HTTP/1.1
Origin: http://api.test.com
Host: api.server.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
1
2
3
4
5
6

服务器根据Origin决定是否同意请求:

  • 若不在许可范围内,返回正常HTTP响应(无Access-Control-Allow-Origin字段)
  • 若在许可范围内,响应头包含CORS相关字段:
Access-Control-Allow-Origin: http://api.test.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: text/html; charset=utf-8
1
2
3
4

关键字段说明:

  • Access-Control-Allow-Origin(必需):值为请求Origin或"*"
  • Access-Control-Allow-Credentials(可选):布尔值,是否允许发送Cookie
  • Access-Control-Expose-Headers(可选):指定额外可获取的响应头字段

# 7.3 Cookie处理

CORS请求默认不发送Cookie,如需发送需满足:

  1. 服务器设置:Access-Control-Allow-Credentials: true
  2. 客户端设置:xhr.withCredentials = true

注意:发送Cookie时,Access-Control-Allow-Origin不能设为"*",必须指定明确域名。

# 7.4 非简单请求处理

非简单请求在正式通信前会发送"预检"请求(OPTIONS):

var url = 'http://api.server.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();
1
2
3
4
5

预检请求头信息:

OPTIONS /cors HTTP/1.1
Origin: http://api.test.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.server.com
1
2
3
4
5

服务器回应:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://api.test.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
1
2
3
4
5

关键字段说明:

  • Access-Control-Allow-Methods:服务器支持的所有跨域请求方法
  • Access-Control-Allow-Headers:服务器支持的所有头信息字段
  • Access-Control-Allow-Credentials:是否允许发送Cookie
  • Access-Control-Max-Age:预检请求有效期(秒)

# 7.5 服务端配置示例

// Express.js配置
app.all('*', function(req, res, next) {
  res.header("Access-Control-Allow-Origin", "*");
  res.header("Access-Control-Allow-Headers", "X-Requested-With,Content-Type");
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
  res.header("Content-Type", "application/json;charset=utf-8");
  next();
});
1
2
3
4
5
6
7
8

# 7.6 优缺点分析

优点:

  • 支持所有类型的HTTP请求
  • 可使用普通XMLHttpRequest发起请求,错误处理更完善
  • 比JSONP有更好的错误处理机制

缺点:

  • 需要浏览器和服务器同时支持
  • 存在兼容性问题(IE10+才支持)

# 8. Server Proxy跨域

同源策略是浏览器需要遵循的标准,而服务器向服务器请求则无需遵循同源策略。代理服务器实现跨域需要以下步骤:

  1. 接受客户端请求
  2. 将请求转发给目标服务器
  3. 获取服务器响应数据
  4. 将响应转发给客户端

# 8.1 实现示例

// 服务端代理请求代码
// 服务端只是简单的通过正常的 HTTP 请求的方式来代理请求接口数据
// 或者也可以使用 proxy 模块来代理,至于怎么使用 proxy 模块,待研究完善
const url = 'https://www.node.huoyuhao.net/crossDomain3';
https.get(url, (resp) => {
  let data = '';
  resp.on('data', (chunk) => {
    data += chunk;
  });
  resp.on('end', () => {
    res.writeHead(200, {
      'Access-Control-Allow-Origin': '*',
      'Content-Type': 'application/json; charset=utf-8',
    });
    res.end(data);
  });
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 8.2 优缺点分析

优点:

  • 实现简单,无需客户端特殊处理
  • 适用于所有类型的HTTP请求
  • 可以处理复杂的业务逻辑

缺点:

  • 需要服务端配合实现
  • 增加了请求处理环节,可能影响性能
  • 服务器压力增大

# 9. WebSocket跨域

WebSocket是一种通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

# 9.1 实现示例

前端代码:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>WebSocket跨域示例</title>
</head>
<body>
  <div>WebSocket跨域通信</div>
  <script>
    // 创建WebSocket连接
    var ws = new WebSocket("ws://localhost:7272");
    
    // 连接成功回调
    ws.onopen = function() {
      console.log('WebSocket连接成功');
      // 向服务器发送消息
      ws.send('Hello, Server!');
    };
    
    // 接收消息回调
    ws.onmessage = function(event) {
      console.log('接收到来自服务器的消息:' + event.data);
    };
    
    // 连接关闭回调
    ws.onclose = function() {
      console.log('WebSocket连接已关闭');
    };
    
    // 连接错误回调
    ws.onerror = function(error) {
      console.log('WebSocket连接发生错误:', error);
    };
  </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

后端代码(Node.js + Socket.IO):

// 安装依赖: npm install socket.io
const http = require('http');
const socketIo = require('socket.io');

// 创建HTTP服务器
const server = http.createServer((req, res) => {
  res.writeHead(200, { 'Content-Type': 'text/plain' });
  res.end('WebSocket server is running');
});

// 绑定Socket.IO到HTTP服务器
const io = socketIo(server, {
  cors: {
    origin: "*",  // 允许跨域
    methods: ["GET", "POST"]
  }
});

// 监听连接事件
io.on('connection', (socket) => {
  console.log('用户已连接');
  
  // 监听客户端消息
  socket.on('message', (data) => {
    console.log('收到客户端消息:', data);
    // 向客户端发送消息
    socket.emit('message', 'Hello, Client!');
  });
  
  // 监听断开连接事件
  socket.on('disconnect', () => {
    console.log('用户已断开连接');
  });
});

// 启动服务器
server.listen(7272, () => {
  console.log('WebSocket服务器运行在端口7272');
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

# 9.2 优缺点分析

优点:

  • 不受同源策略限制
  • 支持双向实时通信
  • 数据传输效率高
  • 适用于实时应用(聊天、游戏、实时数据推送等)

缺点:

  • 需要服务器支持WebSocket协议
  • 连接建立需要额外开销
  • 对于简单的请求可能过于复杂
  • 存在兼容性问题(老旧浏览器不支持)

# 9.3 应用场景

  • 实时聊天应用
  • 在线游戏
  • 实时数据推送(股票、天气等)
  • 协同编辑应用
  • 实时通知系统

# 10. 参考文章

前端跨域方法论 (opens new window)

前端跨域整理 (opens new window)

解决canvas图片getImageData,toDataURL跨域问题 (opens new window)

前端常见跨域解决方案(全) (opens new window)

Last Updated: 9/16/2025, 2:58:59 PM