同源策略概述

  1. 浏览器有一个重要的安全策略,称之为 「同源策略」;其中 源 = 协议 + 主机 + 端口,两个源相同称之为同源,两个源不同称之为跨源或跨域,比如:

    源 1 源 2 是否同源
    http://www.baidu.com http://www.baidu.com/news
    https://www.baidu.com http://www.baidu.com
    http://localhost:5000 http://localhost:7000
    http://localhost:5000 http://127.0.0.1:5000
    http://www.baidu.com http://baidu.com
  2. 同源策略是指,若页面的源和页面运行过程中加载的源不一致时,出于安全考虑,浏览器会对跨域的资源访问进行一些限制

  3. 同源策略对 ajax 的跨域限制的最为 凶狠 ,默认情况下,它不允许 ajax 访问跨域资源;

  4. 所以通常所说的跨域问题,就是同源策略对 ajax 产生的影响,有多种方式解决跨域问题,常见的有:

    1. proxy 代理,常用
    2. CORS,常用
    3. JSONP
    4. web socket
    5. postMessage
    6. window.name + iframe
    7. window.location.hash + iframe
    8. document.domain + iframe
    9. nginx

跨域 - proxy 代理

开发代理

  1. 对于前端开发而言,大部分的跨域问题,都是通过代理解决的;

  2. 代理适用的场景是:生产环境不发生跨域,但开发环境发生跨域 ,因此只需要在开发环境使用代理解决跨域即可,这种代理又称之为开发代理;

  3. 在实际开发中,只需要对开发服务器稍加配置即可完成

    // vue 的开发服务器代理配置
    // vue.config.js
    module.exports = {
      devServer: { // 配置开发服务器
        proxy: { // 配置代理
          "/api": { // 若请求路径以 /api 开头
            target: "http://dev.taobao.com", // 将其转发到 http://dev.taobao.com
          },
        },
      },
    };
    

proxy 代理的原理

  1. 代理可以解决浏览器跨域请求的问题:

    1. 服务器之间是不存在跨域的,可以使用 NodeJS 创建一个客户端代理,由它代替浏览器客户端直接向服务端发送请求;
    2. 浏览器客户端可以将发送给服务端的请求发送给客户端代理,由客户端代理转为发送,解决跨域问题;
  2. 代理图解

  3. 示例代码

    JavaScript
    JavaScript
    HTML
    // 服务端
    const http = require('http')
    
    const server = http.createServer((req, res) => {
      const arr = []
      req.on('data', chunk => {
        arr.push(chunk)
      })
    
      req.on('end', () => {
        console.log(Buffer.concat(arr).toString())
        res.end('获取到了客户端的数据')
      })
    })
    
    server.listen(1234, () => {
      console.log('外部服务端启动了')
    })
    
    // 客户端代理
    const http = require('http')
    
    const options = {
      host: 'localhost',
      port: 1234,
      path: '/',
      method: 'POST'
    }
    
    const server = http.createServer((request, response) => {
      const req = http.request(options, res => {
        const arr = []
        res.on('data', chunk => {
          arr.push(chunk)
        })
        res.on('end', () => {
          const ret = Buffer.concat(arr).toString()
          response.setHeader('content-type', 'text/html;charset=utf-8')
          response.end(ret)
        })
      })
      req.end('你好张三')
    })
    
    server.listen(1000, () => {
      console.log('本地服务端启动了')
    })
    
    <!-- 浏览器访问 localhost: 1000 -->
    <!-- 控制台:'获取到了客户端的数据' -->
    

跨域 - CORS

概述

  1. CORS 是基于 http 1.1 的一种跨域解决方案,它的全称是 Cross-Origin Resource Sharing 跨域资源共享;

  2. 它的总体思路是:如果浏览器要跨域访问服务器的资源,需要获得服务器的允许 ,而一个请求可以附带很多信息,从而会对服务器造成不同程度的影响,比如有的请求只是获取一些新闻,有的请求会改动服务器的数据,针对不同的请求,CORS 规定了三种不同的交互模式,分别是:

    • 简单请求
    • 需要预检的请求
    • 附带身份凭证的请求
  3. 这三种模式从上到下层层递进,请求可以做的事越来越多,要求也越来越严格;

简单请求

  1. 当浏览器端运行了一段 ajax 代码(无论是使用 XMLHttpRequest 还是 fetch api),浏览器会首先判断它属于哪一种请求模式

  2. 当请求 同时满足 以下条件时,浏览器会认为它是一个简单请求:

    1. 请求方法属于下面的一种:get、post、head
    2. 请求头仅包含安全的字段,常见的安全字段如下:Accept、Accept-Language、Content-Language、Content-Type、DPR、Downlink、Save-Data、Viewport-Width、Width
    3. 请求头如果包含 Content-Type 且仅限下面的值之一:text/plain、multipart/form-data、application/x-www-form-urlencoded
    4. 下面是一些例子:
      // 简单请求
      fetch('http://crossdomain.com/api/news');
      
      // 请求方法不满足要求,不是简单请求
      fetch('http://crossdomain.com/api/news', { method: 'PUT' });
      
      // 加入了额外的请求头,不是简单请求
      fetch('http://crossdomain.com/api/news', { headers: { a: 1 } });
      
      // 简单请求
      fetch('http://crossdomain.com/api/news', { method: 'post' });
      
      // content-type 不满足要求,不是简单请求
      fetch('http://crossdomain.com/api/news', {
        method: 'post',
        headers: { 'content-type': 'application/json' },
      });
      
  3. 当浏览器判定某个 ajax 跨域请求简单请求 时,会发生以下的事情;

    1. 请求头中会自动添加 Origin 字段
      • 比如,在页面 http://my.com/index.html 中有以下代码造成了跨域
        // 简单请求
        fetch('http://crossdomain.com/api/news');
        
      • 请求发出后,请求头会是下面的格式:
        GET /api/news/ HTTP/1.1
        Host: crossdomain.com
        Connection: keep-alive
        ...
        Referer: http://my.com/index.html
        
        # 告诉服务器,是哪个源地址在跨域请求
        Origin: http://my.com
        
    2. 服务器响应头中应包含 Access-Control-Allow-Origin
      • 当服务器收到请求后,如果允许该请求跨域访问,需要在响应头中添加 Access-Control-Allow-Origin 字段;
      • 该字段的值可以是:
        # 表示什么人我都允许访问
        Access-Control-Allow-Origin: *
        
        # 表示我就允许 http://my.com 访问
        Access-Control-Allow-Origin: http://my.com
        

需要预检的请求

  1. 简单的请求对服务器的威胁不大,所以允许使用上述的简单交互即可完成,但如果浏览器不认为这是一种简单请求,就会按照下面的流程进行:

    1. 浏览器发送预检请求,询问服务器是否允许
    2. 服务器允许
    3. 浏览器发送真实请求
    4. 服务器完成真实的响应
  2. 比如,在页面 http://my.com/index.html 中有以下代码造成了跨域

    // 需要预检的请求
    fetch('http://crossdomain.com/api/user', {
      method: 'POST', // post 请求
      headers: {
        // 设置请求头
        a: 1,
        b: 2,
        'content-type': 'application/json',
      },
      body: JSON.stringify({ name: '袁小进', age: 18 }), // 设置请求体
    });
    
  3. 浏览器发现它不是一个简单请求,则会按照下面的流程与服务器交互

    1. 浏览器发送预检请求,询问服务器是否允许
      OPTIONS /api/user HTTP/1.1
      Host: crossdomain.com
      ...
      Origin: http://my.com
      Access-Control-Request-Method: POST
      Access-Control-Request-Headers: a, b, content-type
      
      # 1. 请求中不包含请求头,也没有消息体,这是一个预检请求,它的目的是询问服务器,是否允许后续的真实请求
      # 2. 预检请求有以下特征:
      #     - 请求方法为 `OPTIONS`
      #     - 没有请求体
      #     - 请求头中包含
      #       - `Origin`:请求的源,和简单请求的含义一致
      #       - `Access-Control-Request-Method`:后续的真实请求将使用的请求方法
      #       - `Access-Control-Request-Headers`:后续的真实请求会改动的请求头
      
    2. 服务器允许:服务器收到预检请求后,可以检查预检请求中包含的信息,如果允许这样的请求,需要响应下面的消息格式
      HTTP/1.1 200 OK
      Date: Tue, 21 Apr 2020 08:03:35 GMT
      ...
      Access-Control-Allow-Origin: http://my.com
      Access-Control-Allow-Methods: POST
      Access-Control-Allow-Headers: a, b, content-type
      Access-Control-Max-Age: 86400
      ...
      
      # 对于预检请求,不需要响应任何的消息体,只需要在响应头中添加:
      #   - `Access-Control-Allow-Origin`:和简单请求一样,表示允许的源
      #   - `Access-Control-Allow-Methods`:表示允许的后续真实的请求方法
      #   - `Access-Control-Allow-Headers`:表示允许改动的请求头
      #   - `Access-Control-Max-Age`:告诉浏览器,多少秒内,对于同样的请求源、方法、头,都不需要再发送预检请求了
      
    3. 浏览器发送真实请求:预检被服务器允许后,浏览器就会发送真实请求了,上面的代码会发生下面的请求数据
      POST /api/user HTTP/1.1
      Host: crossdomain.com
      Connection: keep-alive
      ...
      Referer: http://my.com/index.html
      Origin: http://my.com
      
      {"name": "张三", "age": 18 }
      
    4. 服务器响应真实请求
      HTTP/1.1 200 OK
      Date: Tue, 21 Apr 2020 08:03:35 GMT
      ...
      Access-Control-Allow-Origin: http://my.com
      ...
      
      添加用户成功
      
  4. 可以看出,当完成预检之后,后续的处理与简单请求相同,下图简述了整个交互过程

附带身份凭证的请求

  1. 默认情况下 ajax 的跨域请求并不会附带 cookie ,这样一来,某些需要权限的操作就无法进行;

  2. 不过可以通过简单的配置就可以实现附带 cookie

    // xhr
    var xhr = new XMLHttpRequest();
    xhr.withCredentials = true;
    
    // fetch api
    fetch(url, {
      credentials: 'include',
    });
    
  3. 这样一来,该跨域的 ajax 请求就是一个附带身份凭证的请求 ,当一个请求需要附带 cookie 时,无论它是简单请求,还是预检请求,都会在请求头中添加 cookie 字段,而服务器响应时,需要明确告知客户端:服务器允许这样的凭据;告知的方式也非常的简单,只需要在响应头中添加:Access-Control-Allow-Credentials: true 即可;

  4. 对于一个附带身份凭证的请求,若服务器没有明确告知,浏览器仍然视为跨域被拒绝,另外要特别注意的是:对于附带身份凭证的请求,服务器不得设置 Access-Control-Allow-Origin: * ,这就是为什么不推荐使用 * 的原因;

一个额外的补充

  1. 在跨域访问时 JS 只能拿到一些最基本的响应头,如:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma ,如果要访问其他头,则需要服务器设置本响应头;

  2. Access-Control-Expose-Headers 头让服务器把允许浏览器访问的头放入白名单,例如:

    Access-Control-Expose-Headers: authorization, a, b
    
  3. 这样 JS 就能够访问指定的响应头了;

代码实现

JavaScript
HTML
// 后端代码:http://localhost:8888
const http = require('http');
const url = require('url');
let server = http.createServer()

server.on('request', (req, res) => {
    let { pathname } = url.parse(req.url);

    // 配置跨域
    res.setHeader('Access-Control-Allow-Origin', req.headers.origin); // 允许任何网站(源)访问
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization'); // 允许携带的 header
    res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT'); // 允许的请求方式

    // 设置当前 options 的发送频率(一般 30 分钟)
    res.setHeader('Access-Control-Max-Age', '10'); // 10s 之后再发送 options 请求

    // 预检请求: 先发一个尝试的请求,如果能跑通在发送真正的请求
    // 如果碰到 options 请求,直接成功即可
    if (req.method === 'OPTIONS') {
        res.statusCode = 200;
        res.end(); // 内部会自己判断 是否增加了跨域头
    }

    res.end('get user')
});

server.listen(8888, () => { console.log(`Server running at http://localhost:8888/`) });
<!-- 前端代码:http://127.0.0.1:5500/proxy/proxy.html -->
<body>
  <div id="get">get请求</div>
</body>
<script>
    get.addEventListener('click', function () {
        let xhr = new XMLHttpRequest;
        xhr.open('get', 'http://127.0.0.1:8888/user?name=张三');
        xhr.send();
    });
</script>

跨域 - JSONP

  1. CORS 出现之前,人们想了一种奇妙的办法来实现跨域,这就是 JSONP;要实现 JSONP 需要浏览器和服务器来一个天衣无缝的绝妙配合;利用了 scriptimgiframe 等有 src 属性的标签不存在跨域请求限制的特点,实现跨域请求;

  2. 实现原理

    1. 基于 script 标签,向跨域服务器发送请求,并把本地的一个全局函数 result 传给服务器,拼接在 url 中;
    2. 服务端收到请求,查询出结果并返回数据,返回 result(${JSON.stringify(data)}) 字符串;
    3. 浏览器收到返回的字符串,解析执行全局函数 result
  3. 缺点:

    1. Jsonp 只支持 GET 请求,而不支持 POST 等其它类型的 HTTP 请求;
    2. Jsonp 在调用失败的时候不会返回各种 HTTP 状态码;
    3. Jsonp 安全性不够,假如提供 Jsonp 的服务存在页面注入漏洞,即它返回的 javascript 的内容被人控制,那么结果是什么?所有调用这个 Jsonp 的网站都会存在漏洞,于是无法把危险控制在一个域名下,所以在使用 Jsonp 的时候必须要保证使用的 Jsonp 服务必须是安全可信的;
  4. 示例

    HTML
    JavaScript
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8" />
      <meta http-equiv="X-UA-Compatible" content="IE=edge" />
      <meta name="viewport" content="width=device-width, initial-scale=1.0" />
      <title>Document</title>
    </head>
    
    <body>
      <button>点击获取用户</button>
      <script>
        function result(resp) {
          console.log(resp);
        }
    
        function request(url) {
          const script = document.createElement('script');
          script.src = url;
          script.onload = function () {
            script.remove();
          };
          document.body.appendChild(script);
        }
    
        document.querySelector('button').onclick = function () {
          // 将自定义的回调函数名 result 传入 callback 参数中
          request("http://localhost:8000/api/user?callback=result&_=${new Date().getTime()}");
        };
      </script>
    </body>
    
    </html>
    
    const express = require('express');
    const app = express();
    
    const path = '/api/user';
    const users = [
      { name: 'monica', age: 17, sex: 'female' },
      { name: '张三', age: 27, sex: 'male' },
    ];
    
    app.get(path, (req, res) => {
      let { callback } = req.query;
      res.setHeader('content-type', 'text/javascript');
      res.send(`${callback}(${JSON.stringify(users)})`);
    });
    
    const port = 8000;
    app.listen(port, () => {
      console.log(`server listen on ${port}`);
      console.log(`request for users: http://localhost:${port}${path}`);
    });
    

web socket

postMessage

  1. window.postMessage() 允许来自一个文档的脚本可以传递文本消息到另一个文档里的脚本,而不用管是否跨域,一个文档里的脚本还是不能调用在其他文档里方法和读取属性,但可以用这种消息传递技术来实现安全的通信;这项技术称为 “跨文档消息传递”,又称为 “窗口间消息传递” 或者 “跨域消息传递”

  2. 语法:postMessage(message, targetOrigin, [transfer])

    1. message:要发送的数据,它将会被结构化克隆算法序列化,所以无需自己序列化;
    2. targetOrigin:指定哪些窗口能接收到消息事件,其值可以是字符串 * (表示无限制) 或者一个 URI (如果要指定和当前窗口同源的话可设置为 “/”);在发送消息的时候,如果目标窗口的协议、主机地址或端口号这三者的任意一项不匹配 targetOrigin 提供的值,那么消息就不会发送;
  3. 使用场景

    1. 页面和其打开的新窗口的数据传递;
    2. 页面与嵌套的 iframe 消息传递;
    3. 多窗口之间消息传递;
  4. 安全问题

    1. 如果希望从其他网站接收 message,请不要为 message 事件添加任何事件监听器;
    2. 如果希望从其他网站接收 message,请始终使用 originsource 属性验证发件人的身份;
    3. 当使用 postMessage 将数据发送到其他窗口时,始终指定精确的目标 origin 而不是 *
  5. 代码实现

    HTML
    HTML
    <!-- a.html http://127.0.0.1:5500-->
    <body>
        <iframe id="iframe" src="http://127.0.0.1:5501/b.html"></iframe>
    </body>
    <script>
        var iframe = document.getElementById('iframe');
        iframe.onload = function () {
            // 向 domain2 发送跨域数据
            iframe.contentWindow.postMessage('a.html 发出的消息', 'http://127.0.0.1:5501');
        };
        // 接受 domain2 返回数据
        window.addEventListener('message', (e) => {
            console.log(e.data);
        }, false);
    </script>
    
    <!-- b.html http://127.0.0.1:5501-->
    <script>
        // 接收 domain1 的数据
        window.addEventListener('message', (e) => {
            // data: 从其他 window 传递过来的数据副本 
            console.log(e.data);
            // origin: 调用 postMessage 时,消息发送窗口的 origin
            console.log(e.origin);
            // source: 发送数据一方 window 对象
            console.log(e.source);
    
            if (e.origin !== 'http://127.0.0.1:5500') return;
    
            // 发送消息给 domain1
            window.parent.postMessage('b.html 发送的消息', e.origin);
        }, false);
    </script>
    

window.name + iframe

  1. 实现原理:每个窗口都有自己独立的 window.name,当修改一个窗口的 src 属性的时候,不会影响这个窗口的 window.name 属性;window.name 可以存储不超过 2M 的数据,传递的数据都会变成 string 类型;

  2. 具体实现:

    1. A 域:a.html、proxy.html
    2. B 域:b.html
    3. a 想要拿到 b 的数据,需要把 iframe 的指向重新指回 A 相同的源下 proxy.html
  3. 实现代码

    HTML
    HTML
    HTML
    <!-- a.html http://127.0.0.1:5500/proxy/a.html -->
    <script>
        var proxy = function (url, callback) {
            var state = 0;
    
            var iframe = document.createElement('iframe');
            iframe.src = url;// 加载跨域页面
            document.body.appendChild(iframe);
    
            // onload 事件会触发 2 次:
            //  第 1 次加载跨域页 b.html,并留存数据于 window.name
            //  第 2 次加载同域页 proxy.html,获取 window.name
            iframe.onload = function () {
                if (state === 0) {
                    // 第 1 次 onload 成功后,切换到同域代理页面
                    // 想要正常拿到 B 中的内容,还需要把 iframe 的指向,重新指回 A 相同的源下
                    iframe.contentWindow.location = 'http://127.0.0.1:5500/proxy/proxy.html';
                    state = 1;
                } else if (state === 1) {
                    // 第 2 次 onload(同域 proxy 页)成功后,读取同域 window.name 中数据
                    callback(iframe.contentWindow.name);
                    // 获取数据以后销毁这个 iframe,释放内存,也保证了安全(不被其他域访问)
                    iframe.contentWindow.document.write('');
                    iframe.contentWindow.close();
                    document.body.removeChild(iframe);
                }
            };
        };
    
        // 请求跨域 b 页面数据
        proxy('http://127.0.0.1:5501/b.html', function (data) {
            alert(data);
        });
    </script>
    
    <!-- proxy.html http://127.0.0.1:5500/proxy/proxy.html -->
    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Document</title>
    </head>
    <body>
    </body>
    </html>
    
    <!-- b.html http://127.0.0.1:5501/b.html -->
    <script type="text/javascript">
        var person = {
            name: "张三",
            age: "26",
        }
        window.name = JSON.stringify(person)
    </script>
    

window.location.hash + iframe

  1. 该方法跟 window.name 类似;

  2. 具体实现

    1. 访问 a.html 会加载 b.html,并把值放在 biframehash 中;
    2. 然后 b 载入后,会加载 c.html,并把值放在 ciframehash 中;
    3. ac 是同域的,那么也就是说 chash 可以直接复制给 ahash,这样 a 就得到了 b 的值;
  3. 代码实现

    HTML
    HTML
    HTML
    <!-- a.html http://localhost:5500 -->
    <body>
      <iframe id="iframe" src="http://127.0.0.1:5501/B.html" style="display:none;"></iframe>
    </body>
    <script>
        let iframe = document.getElementById('iframe');
        // => 向 B.html传 hash值
        iframe.onload = function () {
            // 后面多传了一个哈希值 name=zhangsan
            iframe.src = 'http://127.0.0.1:5501/B.html#name=zhangsan';
        }
        function func(res) {
            alert(res);
        }
    </script>
    
    <!-- b.html http://localhost:5501 -->
    <body>
      <iframe id="iframe" src="http://127.0.0.1:5500/C.html" style="display:none;"></iframe>
    </body>
    <script>
        let iframe = document.getElementById('iframe');
        // => 监听 A 传来的 HASH 值改变,再传给 C.html
        window.onhashchange = function () {
            iframe.src = "http://127.0.0.1:5500/C.html" + location.hash;
        }
    </script>
    
    <!-- c.html http://localhost:5500 -->
    <script>
        // => 监听 B 传来的 HASH 值
        window.onhashchange = function () {
            // => 再通过操作同域 A 的 js 回调,将结果传回
            window.parent.parent.func(location.hash);
        };
    </script>
    

document.domain + iframe

  1. document.domain 用来得到当前网页的域名

    1. 比如在百度页面控制台中输入:
      alert(document.domain); //"www.baidu.com"
      
    2. 也可以给 document.domain 属性赋值,不过是有限制的,只能赋成当前的域名或者一级域名,比如:
      alert(document.domain = "baidu.com");     //"baidu.com"
      alert(document.domain = "www.baidu.com"); //"www.baidu.com"
      
    3. 上面的赋值都是成功的,因为 www.baidu.com 是当前的域名,而 baidu.com 是一级域名,但是下面的赋值就会出来 “参数无效” 的错误,比如:
      alert(document.domain = "qq.com");     //参数无效  报错
      alert(document.domain = "www.qq.com"); //参数无效  报错
      
    4. 因为 qq.combaidu.com 的一级域名不相同,所以会有错误出现,这是为了防止有人恶意修改 document.domain 来实现跨域偷取数据;
  2. 利用 document.domain 实现跨域

    1. 前提条件:这两个域名必须属于同一个一级域名,而且所用的协议、端口都要一致,否则无法利用 document.domain 进行跨域,Javascript 出于对安全性的考虑,禁止两个或者多个不同域的页面进行互相操作,而相同域的页面在相互操作的时候不会有任何问题;
    2. 有两个子域名:news.baidu.com(news.html)、map.baidu.com(map.html)
      • news.baidu.com 里的一个网页 (news.html) 引入了 map.baidu.com 里的一个网页 (map.html),这时 news.html 里同样是不能操作 map.html 里面的内容的,因为 document.domain 不一样,一个是 news.baidu.com,另一个是 map.baidu.com
      • 这时就可以通过 Javascript 将两个页面的 domain 改成一样的,需要在 a.htmlb.html 里都加入 document.domain = “baidu.com”;
      • 这样这两个页面就可以互相操作了,也就是实现了同一一级域名之间的 “跨域”
  3. 实现代码

    HTML
    HTML
    <!-- news.baidu.com 下的 news.html 页面 -->
    <script>
        document.domain = 'baidu.com';
    
        var ifr = document.createElement('iframe');
        ifr.src = 'map.baidu.com/map.html';
        ifr.style.display = 'none';
        document.body.appendChild(ifr);
    
        ifr.onload = function () {
            var doc = ifr.contentDocument || ifr.contentWindow.document;
            // 这里可以操作 map.baidu.com 下的 map.html 页面
            var oUl = doc.getElementById('ul1');
            alert(oUl.innerHTML);
            ifr.onload = null;
        };
    </script>
    
    <!-- map.baidu.com 下的 map.html 页面 -->
    <ul id="ul1">我是map.baidu.com中的ul</ul>
    <script>
      document.domain = 'baidu.com';
    </script>
    

面试题

为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片?

  1. 首先,在很多场景中,处理埋点的服务器很有可能是第三方服务器,比如百度的站点统计埋点,百度就是一个第三方服务器,这就不可避免的带来跨域问题;

  2. 其次,埋点服务方需要提供一种特别利于安装的埋点置入代码,使用传统的 ajax 会使代码变得臃肿;

  3. 同时,埋点请求绝大部分都是 get 请求,又无须得到服务器的响应结果;

  4. 基于以上的特点,使用 img 元素请求服务器就变得理所当然了,img 元素发出的请求天生支持跨域,书写的代码简单,只需要创建一个 img 元素,然后设置 src 为埋点请求地址即可;

  5. 其实请求一旦发出,埋点就成功了,无须得到服务器的响应结果;但如果服务器不给予任何响应的话,可能会导致浏览器端控制台报错,尽管这个报错并不影响实质的功能;为了避免这种情况,服务器于是响应一个最小体积的图片即可,而 1x1 像素的透明 gif 图片是体积最小的图片,自然就选用了它作为响应结果;

表单可以跨域吗

  1. Form 表单可以跨域是因为要保持兼容性,当请求到另一个域名后,原页面得脚本无法获取新页面中得内容,提交的 Form 表单数据不需要返回,所以浏览器认为是安全得行为,所以浏览器不会阻止 Form 表单跨域;

  2. ajax 请求到另一个域名后,是需要获取返回的数据,浏览器认为不安全,所以会阻止这个请求行为;

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 同源策略概述
  2. 2. 跨域 - proxy 代理
    1. 2.1. 开发代理
    2. 2.2. proxy 代理的原理
  3. 3. 跨域 - CORS
    1. 3.1. 概述
    2. 3.2. 简单请求
    3. 3.3. 需要预检的请求
    4. 3.4. 附带身份凭证的请求
    5. 3.5. 一个额外的补充
    6. 3.6. 代码实现
  4. 4. 跨域 - JSONP
  5. 5. web socket
  6. 6. postMessage
  7. 7. window.name + iframe
  8. 8. window.location.hash + iframe
  9. 9. document.domain + iframe
  10. 10. 面试题
    1. 10.1. 为什么通常在发送数据埋点请求的时候使用的是 1x1 像素的透明 gif 图片?
    2. 10.2. 表单可以跨域吗