• 跨域


    1. 什么是跨域?

    浏览器有一个同源策略:如果两个 url 的协议、域名、端口三者完全相同,那就称之为同源。

    同源之间获取资源是不受限制的,如果不满足同源(即协议、域名、端口有一个条件不同),那么获取资源就会受到限制,此时我们称之为跨域。

    总结来说:如果两个url之间需要进行通信,但是不满足同源策略,此时就发生了跨域。

    下面是一个例子:(所有url场景取自新浪首页,即url_1)

    /*
        url_1: https://www.sina.com.cn/
        url_2: https://www.sina.com.cn/api/hotword.json
        url_3: https://sspapi.zenyou.71360.com/js?i=537&o=2&ran=7160123520
        url_4: https://b.zenyou.71360.com/bid/zhendao
        
        在 url_1 向 url_2 发送一个请求,此时不发生跨域
        在 url_1 向 url_3 发送一个请求,此时产生跨域问题,因为 url_1 url_3 之间域名不同
        在 url_1 向 url_4 发送一个请求,此时产生跨域问题,因为 url_1 url_4 之间域名不同
    */
    

    2. HTTP 请求的理解

    要了解跨域, 首先要知道HTTP 协议的两个概念: 简单请求和预检请求

    2.1 简单请求

    某些请求不会触发cors预检请求,这样的请求称之为简单请求,值得注意的是,该术语并不属于fetch规范;

    对于简单请求,浏览器直接发出CORS请求,具体来说,就是在头信息之中,增加一个Origin字段;在返回头信息中,增加一个Access-Control-Allow-Origin: * 字段(后端不作处理时)

    简单请求并不会触发跨域, 只有非简单请求才会触发跨域.

    满意所有下述条件,则该请求可视为简单请求:

    1. 使用下列方法
     1.1 GET
        1.2 HEAD
        1.3 POST
    2. HTTP头部只包含如下字段(这些字段统称为对 CORS 安全的首部字段集合)
     2.1 Accept
        2.2 Accept-Language
        2.3 Content-Language
        2.4 Content-Type (需要注意额外的限制)
        2.5 DPR
        2.6 Downlink
        2.7 Save-Data
        2.8 Viewport-Width
        2.9 Width
    3. Content-Type 的值仅限于下列三者之一:
     3.1 text/plain
     3.2 multipart/form-data
     3.3 application/x-www-form-urlencoded
    4. 请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器
    5. 请求中没有使用 ReadableStream 对象
    

    2.2 非简单请求

    非简单请求, 就是字面上的意思; 需要注意的是, 开发过程中基本上所有的请求都是非简单请求. 在浏览器发送非简单请求时, 并不是直接发送请求到服务器, 而是会先发送一个预检请求到服务器.

    当一个请求满足下列任意一个条件时,它会变成非简单请求(即该请求会先发送一个options请求到服务器)

    1. 使用下列方法
     1.1 PUT
        1.2 DELETE
        1.3 CONNECT
        1.4 OPTIONS
        1.5 TRACE
        1.6 PATCH
    2. 人为设置了“对CORS 安全的首部字段集合”之外的其他首部字段(这些字段统称为对 CORS 安全的首部字段集合)
     2.1 Accept
        2.2 Accept-Language
        2.3 Content-Language
        2.4 Content-Type (需要注意额外的限制)
        2.5 DPR
        2.6 Downlink
        2.7 Save-Data
        2.8 Viewport-Width
        2.9 Width
    3. Content-Type 的值不属于下列三者之一:
     3.1 text/plain
     3.2 multipart/form-data
     3.3 application/x-www-form-urlencoded
    4. 请求中的XMLHttpRequestUpload 对象注册了任意多个事件监听器
    5. 请求中使用了ReadableStream对象
    

    2.3 预检请求

    预检请求, 即option 请求; 他表示当浏览器需要向服务器发送非简单请求时, 提前发送的一个请求.

    预检请求的作用是确认服务器与浏览器之间是否能够进行通信, 以及为后续的请求做准备

    PS: 对于预检请求, 这里只知道在跨域问题中可以用于确认一个请求是否发生了跨域, 至于在其他方面的应用, 暂时还没有接触过.

    3. 跨域演示

    express(app.js):

    const express = require('express')
    
    const log = console.log.bind(console)
    const app = express()
    
    app.get('/helloworld', (request, response) => {
        response.send('hello')
    })
    
    const main = () => {
        let server = app.listen(2300, () => {
            let host = server.address().address
            let port = server.address().port
    
            log(`应用实例,访问地址为 http://${host}:${port}`)
        })
    }
    
    if (require.main === module) {
        main()
    }
    
    

    html(crossOriginDemo.html):

    <!doctype html>
    <html>
    <head>
        <meta charset="utf-8">
        <title>跨域demo</title>
        <style type="text/css">
            .ajaxButton {
                 100px;
                height: 50px;
                background: blue;
                color: #fff;
                display: flex;
                justify-content: center;
                align-items: center;
            }
        </style>
    </head>
    <body>
        <div class="ajaxButton">发送请求</div>
        <script>
            const log = console.log.bind(console)
    
            const ajax = (method, url, data, headers, callback) => {
                let r = new XMLHttpRequest()
                r.open(method, url, true)
    
                // 设置 headers
                Object.entries(headers).forEach(([k, v]) => {
                    r.setRequestHeader(k, v)
                })
                r.onreadystatechange = () => {
                    if (r.readyState === 4) {
                        callback(r.response)
                    }
                }
                if (method === 'POST') {
                    data = JSON.stringify(data)
                }
                r.send(data)
            }
            
            let ele = document.querySelector('.ajaxButton')
            ele.addEventListener('click', function () {
                let url = 'http://localhost:2300/helloworld'
                let method = 'GET'
                let data = {}
                let headers = {
                    'Content-Type': 'application/json',
                }
                ajax(method, url, data, headers, (r) => {
                    log('cors r is', r)
                })
            })
        </script>
    </body>
    </html>
    

    跨域原因:

    由于页面使用的端口号是63342, 而服务器 设置的端口号是2300, 这破坏了同源策略, 因此发生了跨域, 不能够正常请求到数据.

    点击页面按钮之后, 可以看到跨域提示:

    4. 跨域的解决方案

    1. cors 模块: https://www.cnblogs.com/oulae/p/12784186.html
    2. json: https://www.cnblogs.com/oulae/p/12784187.html
    3. webpack devServer 代理: https://www.cnblogs.com/oulae/p/12784188.html
    4. 自定义node 转发: https://www.cnblogs.com/oulae/p/12784189.html
    5. nginx
    6. postMessage
    7. iframe

    5. 跨域demo

    跨域Demo演示

    6. 测试/生产环境中的跨域问题

    1. webpack dev Server(develop server)
    2. nginx
    3. cors
      上面的这三个内容是在web 项目中常见的跨域解决方案, 但是, 如果webpack 使用webpack dev Server 解决跨域, 那么这个解决方式只能在本地运行时解决跨域问题, 如果后端在部署项目的时候没有部署webpack dev server(即没有使用在服务器建一个中转服务器, 并配置dev server), 那么还是会有跨域问题; webpack dev Server 是前端跨域解决方案, 但是不适用于生产环境和测试环境, 只适用于本机

    cors 模块的配置和nginx 的配置一般是后端完成的.

    因此测试/生产环境中的跨域问题, 大多数情况下都是后端去解决.

    7. 参考链接

    1. MDN
    2. 跨域资源共享 CORS 详解
    3. 项目地址
  • 相关阅读:
    B1295 [SCOI2009]最长距离 最短路
    B1588 [HNOI2002]营业额统计 set||平衡树
    B1202 [HNOI2005]狡猾的商人 并查集
    B1303 [CQOI2009] 中位数图 数学
    B2002 [Hnoi2010]Bounce 弹飞绵羊 分块
    B1192 [HNOI2006]超级英雄Hero 二分图匹配
    逐个击破
    HAOI2009 毛毛虫
    HNOI/AHOI2018 道路
    NOI2005 瑰丽华尔兹
  • 原文地址:https://www.cnblogs.com/oulae/p/12784170.html
Copyright © 2020-2023  润新知