• 跨域问题的多种解决方案


    1. jsonp

    原理:script标签不受同源策略的影响,把链接挂在script标签上,通过回调函数传递数据
    优点:兼容性好,前后端分离
    缺点:仅支持get请求,安全性较差,容易引发xss攻击

    /* server.js */
    const express = require('express');
    const app = express();
    app.get('/say', (req, res) => {
        let { wd, cb } = req.query;
        console.log('客户端:' + wd);//客户端:Hello
        res.end(`${cb}('服务端:Hi')`)
    })
    app.listen(3333, () => {
        console.log("Server start http://localhost:3333");
    })
    
    <script>
        function jsonp({ url, params, cb }) {
            /* 返回一个Promise */
            return new Promise((resolve) => {
                /* 创建script */
                let script = document.createElement('script');
                /* 全局cb函数 */
                window[cb] = function (data) {
                    resolve(data);/* 执行返回的数据 */
                    document.body.remove(script);/* 执行完毕删除标签 */
                }
                /* 转换url */
                let arr = [];
                params = { ...params, cb };
                for (let key in params)
                    arr.push(`${key}=${params[key]}`);
                //http://localhost:3333/say?wd=Hello&cb=show
                script.src = `${url}?${arr.join('&')}`;
                document.body.appendChild(script);
            })
        }
        jsonp({
            url: 'http://localhost:3333/say',
            params: { wd: 'Hello' },
            cb: 'show'
        }).then(data => console.log(data));//服务端:Hi
    </script>
    

    2. CORS

    原理:通过在服务端添加白名单,放宽对请求源的限制,从而实现跨域

    优点:可以发任意请求
    缺点:上是复杂请求的时候得先做一个预检,再发真实的请求,发了两次请求会有性能上的损耗。
    3333端口下的indexhtml发出ajax请求

    <!-- html在3333端口服务器上 -->
    <script>
        let xhr = new XMLHttpRequest;
        /* 设置cookie 需要Access-Control-Allow-Credential设置 */
        document.cookie = "name=aeipyuan";
        xhr.withCredentials = true;
        /* 请求4444端口数据 */
        xhr.open('put', 'http://localhost:4444/getData', true);
        /* 需要设置Access-Control-Allow-Headers */
        xhr.setRequestHeader('name', 'A');
        xhr.send();
        xhr.onreadystatechange = function (e) {
            if (xhr.readyState === 4) {
                if (xhr.status >= 200 && xhr.status < 300 || xhr.status === 304) {
                    console.log(xhr.response);//4444已收到
                    // 需要设置Access-Control-Expose-Headers
                    console.log(xhr.getResponseHeader('name'));//mike
                }
            }
        }
    </script>
    

    4444端口服务器对数据进行处理

    const express = require('express');
    const app = express();
    /* 设置白名单 */
    let writeList = ['http://localhost:3333'];
    app.use((req, res, next) => {
        /* 获取请求源 */
        let { origin } = req.headers;
        /* 判断请求源 */
        if (writeList.includes(origin)) {
            /* 允许origin访问 */
            res.setHeader('Access-Control-Allow-Origin', origin)
            /* 允许哪个头 */
            res.setHeader('Access-Control-Allow-Headers', 'name')
            /* 允许哪个方法 */
            res.setHeader('Access-Control-Allow-Methods', 'PUT')
            /* 允许携带cookie */
            res.setHeader('Access-Control-Allow-Credentials', true)
            /* 预检的存活时间 */
            res.setHeader('Access-Control-Allow-Max-Age', 6)
            /* 允许前端获取哪个头 */
            res.setHeader('Access-Control-Expose-Headers', 'name')
            /* OPTIONS请求不做处理 */
            if (req.method === 'OPTIONS') {
                res.end();
            }
        }
        next();
    })
    app.put('/getData', (req, res) => {
        console.log(req.headers);
        res.setHeader('name', 'B');
        res.send('4444已收到');
    })
    app.listen(4444, () => {
        console.log("Server start http://localhost:4444");
    })
    

    3. postMessage实现跨域

    原理:将另一个域的网页放到iframe中,利用postMessage进行传值
    3333端口下的a.html

    <div>AAAAAAA</div>
    <iframe id="frame" src="http://localhost:4444/b.html" frameborder="0" onload="load()"></iframe>
    <script>
        function load() {
            frame.contentWindow.postMessage(
                'Hello', 'http://localhost:4444/b.html'
            )
        }
        window.onmessage = function (e) {
            console.log('B说:' + e.data);//B说:Hi
        }
    </script>
    

    4444端口下的b.html

    <div>BBBBBB</div>
    <script>
        window.onmessage = function (e) {
            console.log('A说:' + e.data);//A说:Hello
            e.source.postMessage('Hi', e.origin);//给A发送消息
        }
    </script>
    

    4. window.name传值

    原理:先用iframe的window.name存储跨域页面要传入的数据,然后将iframe的src属性改变为同源src,实现获取name存储的值

    举例:
    A,B页面在3333端口下,C页面在4444端口下,目标是实现a页面获取c页面数据
    第一步,A页面用iframe标签引入C页面
    第二步,C页面设置window.name=数据
    第三步,将iframe的src由C页面切换为B页面(同源)
    第四步,获取iframe页面的window.name属性
    
    <!-- a.html -->
    <iframe src="http://localhost:8082/c.html" frameborder="10" onload="load()" id="frame"></iframe>
    <script>
        let first = true;
        function load() {
            if (first) {
                let frame = document.getElementById('frame');
                frame.src = "http://localhost:8081/b.html";//切换src
                first = false;
            } else {
                console.log(frame.contentWindow.name)
            }
        }
    </script>
    <!-- c.html -->
    <script>
        window.onload = function () {
            window.name = "传给A的数据"
        }
    </script>
    

    5. hash传值

    原理:和window.name相似,A使用iframe引入C并给C传hash值,C使用iframe引入B并给B传hash值,B和A同源,所以把hash值赋给A,A监听到hash变化输出hash值

    <!-- a.html -->
    <iframe id="frame" src="http://localhost:4444/c.html#A2C" frameborder="0"></iframe>
    <script>
        window.onhashchange = function () {
            console.log('C传入数据:' + location.hash)//C传入数据:#C2B2A
        }
    </script>
    <!-- b.html -->
    <script>
        window.parent.parent.location.hash = location.hash;/* 将hash传给A */
    </script>
    <!-- c.html -->
    <script>
        console.log('A传入的数据:' + location.hash);//A传入的数据:#A2C
        /* 创建iframe */
        let iframe = document.createElement('iframe');
        iframe.src = "http://localhost:3333/b.html#C2B2A";
        document.body.appendChild(iframe);
    </script>
    

    6. Websocket

    原理:Websocket是HTML5的一个持久化的协议,它实现了浏览器与服务器的全双工通信,同时也是跨域的一种解决方案。WebSocket和HTTP都是应用层协议,都基于 TCP 协议。但是 WebSocket 是一种双向通信协议,在建立连接之后,WebSocket 的 server 与 client 都能主动向对方发送或接收数据。同时,WebSocket 在建立连接时需要借助 HTTP 协议,连接建立好了之后 client 与 server 之间的双向通信就与 HTTP 无关了
    开启服务

    /* server.js */
    let WebSocket = require('ws');
    /* 创建服务 */
    let wss = new WebSocket.Server({ port: 3333 });
    wss.on('connection', ws => {
        ws.on('message', e => {
            console.log(e);//前端数据
            ws.send('后台数据');
        })
    })
    

    传送数据

    <script>
        let socket = new WebSocket('ws://localhost:3333');
        socket.onopen = function () {
            socket.send('前端数据');
        }
        socket.onmessage = ({ data }) => {
            console.log(data);//后台数据
        }
    </script>
    

    7. domain实现跨域

    不同的页面可能放在不同的服务器上,这些服务器域名不同,但是拥有相同的上级域名,比如id.qq.com、www.qq.com、user.qzone.qq.com,它们都有公共的上级域名qq.com ,设置页面documen.domain为上级域名即可实现跨域

    <!-- http://a.aeipyuan.cn:3333/a.html  -->
    <div>AAAAA</div>
    <iframe id="frame" src="http://b.aeipyuan.cn:4444/b.html" frameborder="0" onload="load()"></iframe>
    <script>
        document.domain = "aeipyuan.cn";
        function load() {
            console.log('b页面数据:' + frame.contentWindow.a);//b页面数据:100
        }
    </script>
    <!-- http://b.aeipyuan.cn:4444/b.html -->
    <div>BBBBB</div>
    <script>
        document.domain = "aeipyuan.cn";
        window.a = 100;
    </script>
    

    8. nginx实现

    在conf文件配置以下参数,了解较浅,日后补充

    location / {  
        add_header Access-Control-Allow-Origin *;
        add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
        add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
        if ($request_method = 'OPTIONS') {
            return 204;
        }
    } 
    

    9. webpack配置实现跨域

    //方式1  webpack.config.js
    devServer: {
        port: 8081,
        contentBase: './build',
        hot: true,
        proxy: {
            '/api': {
                target: 'http://localhost:8888',//目标域
                pathRewrite: { '/api': '' }/* 路径重写 */
            }
        }
    }
    //方式2 server.js 直接在8888端口访问webpack打包文件
    let express = require('express');
    let app = express();
    /* webpack */
    let webpack = require('webpack');
    let config = require('../webpack.config.js');
    let compiler = webpack(config);
    //中间件
    let middle = require('webpack-dev-middleware');
    app.use(middle(compiler));
    /* 请求 */
    app.get('/user', (req, res) => {
        res.json({ name: "aeipyuan" })
    })
    app.listen(8888);
    
  • 相关阅读:
    log4j使用教程
    (POI)Excel格式转Html格式
    log4j2使用教程
    Spring AOP 面向切面编程入门
    C# 标准事件模式
    1Angular的MVC和作用域
    3Angular的模块化
    2Angular的双向数据绑定(MVVM)
    5手动初始化Angular的模块与控制器
    python读取 UCS2 little endian(utf16le) 格式的文件
  • 原文地址:https://www.cnblogs.com/aeipyuan/p/12990192.html
Copyright © 2020-2023  润新知