前端常见跨域解决方案(jsonp,cors,proxy,postMessage,webSocket)
作者:mmseoamin日期:2023-12-02

跨域的几种解决方案:

一、JSONP(jsonp)

概念:

JSONP(JSON with Padding,填充式 JSON 或参数式 JSON)是一种通过

优点:
  • 简单易用
  • 兼容性好,支持各种浏览器
    缺点:
    • 只能实现 GET 请求,无法实现 POST 等其他类型的请求
    • 安全性较低,容易受到 XSS 攻击
      Eg:
      为什么需要动态生成标签,而不是直接通过
      原因有以下几点:
      1. 避免脚本注入攻击:如果直接将需要请求的数据以及回调函数名称写在

      二、 CORS(Cross-Origin Resource Sharing)(cors)

      概念:

      CORS 是一种通过服务器设置响应头信息,允许指定域名的请求跨域访问资源,从而实现跨域资源共享的技术。具体而言,在服务端设置 Access-Control-Allow-Origin 响应头字段来指定允许的来源域名,以及其他相关的响应头信息。

      优点:
      • 支持各种请求类型,包括 GET、POST、PUT 等
      • 安全性比 JSONP 较高,不容易受到 XSS 攻击
        缺点:
        • 需要服务端进行配合,设置相应的响应头信息
        • 不是所有浏览器都支持 CORS(例如 IE8 和 IE9)
          如何配置cors:

          在服务端实现 CORS 跨域请求,需要配置相应的 HTTP 响应头。常见的响应头字段包括:

          • Access-Control-Allow-Origin:指定允许跨域请求的来源域名,如果希望允许所有来源域名,则可以设置为 *。
          • Access-Control-Allow-Methods:指定允许跨域请求的 HTTP 方法,如 GET、POST、PUT 等。多个方法之间使用逗号分隔。
          • Access-Control-Allow-Headers:指定允许跨域请求的自定义请求头,多个请求头之间使用逗号分隔。
          • Access-Control-Allow-Credentials:用于指示是否允许发送 Cookie 以及其他凭证信息到跨域请求中。如果设置为 true,则说明请求可以发送身份凭证。如果未设置或设置为 false,则不能发送凭证。

            下面是一个 Node.js Express 框架的示例代码:

            const express = require('express');
            const app = express();
            app.use(function(req, res, next) {
              res.setHeader('Access-Control-Allow-Origin', '*');
              res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
              res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
              res.setHeader('Access-Control-Allow-Credentials', 'true');
              next();
            });
            // 处理路由和请求
            // ...
            app.listen(8080);
            

            上述代码中,我们通过设置响应头来实现 CORS 跨域请求的支持。需要注意的是,如果设置了 Access-Control-Allow-Credentials 为 true,则在服务端也需要设置 req.header(‘Access-Control-Allow-Credentials’, ‘true’) 来响应凭证信息。此外,CORS 跨域请求还需要确保客户端和服务端的协议、域名和端口号均相同或允许跨域。

            三、代理服务器(proxy)

            概念:

            代理服务器是一种通过在同一个域名下设置一个代理服务器,将需要跨域访问的请求先发送到代理服务器上,再由代理服务器向目标服务器发出请求,并将请求结果返回给客户端的技术。这样客户端就可以避免直接访问跨域服务器,从而可以绕开浏览器的同源策略。

            优点:
            • 可以实现各种请求类型,包括 GET、POST、PUT 等
            • 安全性较高,不容易受到 XSS 攻击
              缺点:
              • 需要额外的代理服务器来进行转发,增加了服务器的负担
              • 需要进行额外的配置和编码
                举个常规的配置例子:

                在 dva 项目中配置 proxy 可以通过修改 package.json 文件来实现。下面是一个详细的 dva 项目配置 proxy 的示例:

                1. 在 dva 项目根目录下创建一个名为 setupProxy.js 的文件,用于配置代理。示例代码如下:
                const proxy = require('http-proxy-middleware');
                module.exports = function(app) {
                  app.use(
                    '/api', // 需要代理的请求前缀
                    proxy({
                      target: 'http://localhost:8080', // 设置代理目标地址
                      changeOrigin: true, // 如果代理服务器和目标服务器协议、域名或端口不同,需要设置为 true
                      pathRewrite: {
                        '^/api': '' // 将请求路径的前缀 /api 去掉
                      }
                    })
                  );
                };
                

                上述代码配置了一个代理,将所有以 /api 开头的请求转发到 http://localhost:8080/ 上,并将请求路径的前缀 /api 去掉。如果代理服务器和目标服务器协议、域名或端口不同,需要将 changeOrigin 设置为 true。

                2. 在 package.json 文件中添加 “proxy” 字段,指定代理配置文件的路径。示例代码如下:
                {
                  "name": "my-dva-app",
                  "version": "0.1.0",
                  "dependencies": {
                    // ...
                  },
                  "scripts": {
                    // ...
                  },
                  "proxy": "http://localhost:3000/",
                }
                

                上述代码中,我们将 “proxy” 字段设置为 http://localhost:3000/,这意味着所有以 /api 开头的请求会被转发到 http://localhost:3000/api 上。

                3. 在 dva model 中使用 fetch 或者 dva-loading 等异步请求方式时,只需要在 URL 中添加前缀 /api 即可。示例代码如下:
                import { delay } from 'dva/saga';
                import request from '../utils/request';
                export default {
                  namespace: 'example',
                  state: {
                    data: null,
                  },
                  effects: {
                    *fetchData(_, { call, put }) {
                      yield call(delay, 1000); // 模拟延迟
                      const result = yield call(request, '/api/data'); // 请求地址为 /api/data
                      yield put({
                        type: 'saveData',
                        payload: result.data,
                      });
                    },
                  },
                  reducers: {
                    saveData(state, { payload }) {
                      return {
                        ...state,
                        data: payload,
                      };
                    },
                  },
                };
                

                上述代码中,我们在 effects 的 fetchData 方法中使用了 request 工具函数来发起一个 GET 请求,请求的地址为 /api/data,实际上会被代理转发到 http://localhost:8080/data 上。无论是 fetch 还是使用 dva-loading,只要在 URL 中加上 /api 前缀即可。

                四、postMessage(postmessage)

                概念:

                postMessage 是一种在不同窗口之间通过 postMessage 方法发送消息,从而实现数据的跨域传输的技术。具体而言,在前端页面中使用 postMessage 方法来向目标域名发送一条消息,并在目标页面中通过监听 message 事件来接收并处理该消息。

                优点:
                • 可以实现各种请求类型,包括 GET、POST、PUT 等
                • 安全性较高,不容易受到 XSS 攻击
                  缺点:
                  • 需要浏览器支持 HTML5,不兼容 IE8 及以下版本的浏览器
                  • 不支持传输大量数据
                    如何使用postMessage实现父窗口<–>子窗口通信:

                    在实际应用场景中,我们可能需要在不同的窗口/iframe 中进行通信,比如说,在一个页面中内嵌了多个 iframe 窗口,这些窗口之间需要进行数据交互。如果这些窗口处于同一个域名下,我们就可以通过 JavaScript 变量或者全局事件来进行通信,但是如果它们处于不同的域名下,我们就无法直接进行通信了。

                    这时候,就可以使用 postMessage 来完成跨域通信了。具体实现步骤如下:

                    子—>父
                    父窗口
                    1. 在父窗口中创建一个 iframe 窗口,并指定它的 src 属性为要加载的子窗口页面。
                    2. 在父窗口中监听子窗口发送的消息,并在处理函数中获取到消息内容,进行相应的处理。
                    
                    
                    
                      
                      Parent Window
                    
                    
                      
                      
                      
                    
                    
                    

                    在上面的代码中,我们在父窗口中创建了一个 iframe 窗口,并加载了一个跨域网站(http://example.com)。在父窗口中,我们使用 window.addEventListener() 方法监听 message 事件,当子窗口向父窗口发送消息时会触发该事件。在事件处理函数中,我们首先判断消息来源是否合法,如果合法,则获取到消息的内容并进行相应的处理。

                    子窗口
                    1. 在子窗口中使用 window.parent 获取到父窗口对象,并向父窗口发送消息。
                    2. 在子窗口中监听父窗口发送的消息,并在处理函数中获取到消息内容,进行相应的处理。
                    
                    
                    
                      
                      Child Window
                    
                    
                      
                    
                    
                    

                    在上面的代码中,我们在子窗口中使用 window.parent 获取到父窗口对象,并使用 postMessage 方法向父窗口发送了一条消息。在最后一行代码中,targetOrigin 参数指定了目标窗口的域名和端口号,以确保消息能够被正确地发送到父窗口。在子窗口中,我们同样监听 message 事件,并在事件处理函数中获取到父窗口发送的消息内容。

                    父–>子:

                    要通过 postMessage 实现父窗口向子窗口发送数据,可以在父窗口中使用 iframe 元素的 contentWindow 属性获取到子窗口的 window 对象,然后调用该对象的 postMessage() 方法来发送消息。

                    具体实现步骤如下:

                    父窗口
                    1. 在父窗口中创建一个 iframe 窗口,并指定它的 src 属性为要加载的子窗口页面。
                    2. 在需要发送数据的时候,使用 contentWindow 属性获取到子窗口的 window 对象,并调用它的 postMessage() 方法发送消息。
                    
                    
                    
                      
                      Parent Window
                    
                    
                      
                      
                      
                    
                    
                    

                    在上面的代码中,我们在父窗口中创建了一个 iframe 窗口,并加载了一个跨域网站(http://example.com)。在父窗口中,我们通过 document.getElementById() 方法获取到子窗口的 iframe 元素,然后使用 contentWindow 属性获取到子窗口的 window 对象。在 sendDataToChild() 函数中,我们调用该对象的 postMessage() 方法来向子窗口发送数据。

                    需要注意的是,在调用 postMessage() 方法时,第一个参数为要发送的数据,可以是任意类型的数据,比如字符串、数字、数组等;第二个参数为目标窗口的源(origin),可以指定一个字符串或者一个 URL 对象,用来控制数据发送的目标。如果不需要限制目标窗口,可以传入 ‘*’。具体使用时需要根据实际需求来设置。

                    综上所述,通过使用 postMessage,我们可以在不同的窗口/iframe 或者跨域时进行跨文档通信,实现数据的传递和交互。需要注意的是,在实际应用中,我们需要根据具体的需求和安全性考虑来选择合适的参数和方式进行配置和使用。

                    五、WebSocket(websocket)

                    概念:

                    WebSocket 是一种使用 HTTP 协议进行握手,建立一条持久性的连接,并通过该连接实现数据的实时推送和跨域传输的技术。

                    它允许客户端和服务器之间建立持久连接,使得双方可以实时地传递数据。相比于传统的 HTTP 请求-响应模式,WebSockets 可以减少网络延迟,提高数据传输的效率,同时也支持服务端向客户端主动推送数据。

                    WebSockets 协议实现了一个基于帧的消息传递机制,通过使用 WebSocket API,开发者可以方便地实现复杂的实时数据传输场景,比如在线聊天、多人游戏等等。

                    优点:
                    • 建立的连接是持久性的,可以轻松地实现实时推送和通信
                    • 支持双向数据传输
                    • 安全性较高,不容易受到 XSS 攻击
                      缺点:
                      • 需要浏览器和服务端同时支持 WebSocket 技术
                      • 建立连接时需要进行额外的握手,增加了网络开销
                        如何使用 WebSockets:
                        服务器端

                        在服务器端,我们需要创建一个 WebSocket 的实例,并监听 connection 事件,在该事件处理函数中,可以获取到客户端连接的 WebSocket 对象,并通过它来处理后续的通信。

                        const WebSocket = require('ws');
                        const wss = new WebSocket.Server({ port: 8080 });
                        wss.on('connection', (ws) => {
                          console.log('A new client connected.');
                          // 监听客户端发送的消息
                          ws.on('message', (data) => {
                            console.log('Received message from client:', data);
                            // 发送消息给客户端
                            ws.send('Hello, Client!');
                          });
                        });
                        

                        在上面的代码中,我们首先引入了 Node.js 的 WebSocket 模块,并创建了一个 WebSocket 的服务器实例,监听本地的 8080 端口。在 connection 事件处理函数中,我们可以获取到客户端连接的 WebSocket 对象,并通过它来监听 message 事件和发送消息。

                        客户端

                        在客户端,我们同样需要创建一个 WebSocket 的实例,并连接到服务器。在连接成功后,可以通过该实例来监听服务器发送的消息。

                        
                        
                        
                          
                          WebSocket Example
                        
                        
                          
                        
                        
                        

                        在上面的代码中,我们创建了一个 WebSocket 的实例,并连接到服务器的地址(ws://localhost:8080)。在连接成功后,onopen 回调函数会被触发,此时我们可以通过 send() 方法向服务器发送消息。客户端还可以监听 onmessage 回调函数,从而获取服务器发送的消息内容。

                        综上所述,WebSocket 是一种实现双向通信的协议,其使用方式与传统的 HTTP 协议有一些不同,需要开发者进行相应的调整和适配。在实际应用中,我们需要根据具体的需求和场景来选择合适的协议和工具来实现数据通信。