【从原理到实战】彻底搞懂跨域问题 (一)(1)

本文涉及的产品
.cn 域名,1个 12个月
简介: 前言什么是跨域: 浏览器为了安全性,设置同源策略导致的, 或者说是一种浏览器的限制同源策略: 是一种约定,WEB 应用只能请求同一个源的资源什么时候会跨域: 协议名、域名、端口号 不同本文将从原理, 到最简代码实现, 演示解决跨域的方法和流程,纸上得来终觉浅 绝知此事要躬行, 只有自己手敲实现过, 才能对其原理理解更加深刻。

前言

什么是跨域: 浏览器为了安全性,设置同源策略导致的, 或者说是一种浏览器的限制同源策略: 是一种约定,WEB 应用只能请求同一个源的资源什么时候会跨域: 协议名、域名、端口号 不同

本文将从原理, 到最简代码实现, 演示解决跨域的方法和流程,纸上得来终觉浅 绝知此事要躬行, 只有自己手敲实现过, 才能对其原理理解更加深刻。

一、JSONP

1、原理

JSONP是利用 html 中的script标签没有跨域访问限制的来实现的 工作原理:

  • 客户端通过script标签向服务器发送请求, 同时定义好回调函数接收 <script src="http://example.com/api?callback=handleResponse"></script>
  • 服务器接收到请求后,会把数据进行填充,并且放回回调函数中,例如 handleResponse({data:'response'})
  • 客户端在页面中定义相应的回调函数 handleResponse, 服务器返回的脚本会被浏览器执行, 从而触发回调函数,处理服务器返回的数据(把数据作为参数传给客户端定义的函数并执行), 这样就实现了跨域请求和数据获取

局限: JSONP 的限制是需要通过<script> 标签加载外部脚本, 但是它有一些局限性

JSONP 只支持 GET 请求, 无法通过 POST 或 其他类型请求 JSONP 只能接收 纯文本数据,无法处理 JSON 对象 或 XML 数据

2、实站(最简化代码)

接下来就通过最简代码的方式, 实现JSONP的原理

客户端 index.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>JSONP</title>
  </head>
  <body>
    JSONP
  </body>
  <script>
    // 像页面中添加一个script标签, 通过script标签进行访问, 返回一个可执行的 函数
    function sendJsonp() {
      const callbackName = `jsonpFn_${Date.now()}`; // 为了防止缓存 加一个时间戳
      // 发送请求前先将函数名放到window上, 赋值一个函数, 当后端收到请求后,会返回一个该函数执行的脚本, 并将函数的参数传入到该函数中
      window[callbackName] = function (data) {
        console.log(data, ".datadatadatadata");
      };
      // 将该函数拼成一个url地址请求到后端
      const url = `http://localhost:4000/jsonp?q=1&w=2&jsonp=${callbackName}`;
      const script = document.createElement("script");
      script.src = url;
      // 将脚本标签添加到head中,发送请求
      document.head.appendChild(script);
    }
    sendJsonp();
  </script>
</html>

服务端代码

const express = require("express");
const app = express();
app.get("/jsonp", (req, res) => {
  // 查询参数
  const { jsonp, q, w } = req.query;
  const result = {
    g: Array.from({ length: 10 }, (_, i) => ({ q: `${w}${i + 1}` })),
  };
  res.send(`${jsonp}(${JSON.stringify(result)})`);
});
app.listen(4000, () => {
  console.log("sever 4000");
});

解释: 从上面简化的代码中,可以看出, 客户端就是通过构建 script 标签, 通过src 的访问将函数名发送给服务端, 并且构建一个函数等待执行, 服务端收到后,拿着函数名在拼装成一个函数调用的字符串, 当客户端发送 script 请求时,会将结果进行执行, 因为函数挂载到了window上, 所以会执行我们之前构造好的函数, 并将data 这个参数传递进去,我们就拿到了结果

屏幕截图 2023-07-05 231756.png

二、CORS

1、概念

CORS 跨域资源共享:是一种浏览器机制,允许跨域请求共享资源,在Web开发中,当一个网页向另一个域名下的资源发起请求时,如果请求的目标域和当前域不同,就会设计到跨域问题, 默认情况下, 浏览器会限制这种跨域请求,以保护用户安全

2、工作流程 (可略)

CORS 跨域资源共享通过在服务端设置相应的响应头来解决跨域问题, 它使用一些特定的HTTP 头部和预检请求已实现跨域通信

  • 1、发出跨域请求: 在浏览器中的网页向其他域名下的资源发起跨域请求时, 浏览器会首先发送一个跨域请求
  • 2、发送预检请求(可选):对于某些类型的跨域请求,例如带有一些自定义的HTTP头部或特殊方法(PUT、DELETE)的请求,浏览器会发送一个OPTIONS请求,称为预检请求,用于向服务器验证跨域请求是否被允许
  • 3、服务器设置响应头: 服务器收到跨域请求后,可以设置响应头来告诉浏览器允许请求的源(Origin)、允许的 HTTP 方法、允许的自定义头部信息
  • Access-Control-Allow-Origin 指定允许访问域名,可以单个 通配符 列表
  • Access-Control-Allow-Methids 指定允许的 HTTP 方法
  • Access-Control-Allow-Headers 指定允许的自定义头
  • access-Control-Allow-Credentials 是否允许发送身份凭证 允许时 Origin不能为*
  • 4、处理跨域响应,如果服务器设置了响应头,浏览器会根据指定的规则处理

3、实战 ( 最简化代码 )

客户端代码

我们通过 livesever 先给页面起一个静态服务, 域名为  http://127.0.0.1:5501/HTTP/xxx/indexMini.html

indexMini.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>
    <script>
      async function getUsers() {
        const response = await fetch("http://localhost:3000/users", {
          method: "POST",
        });
        const data = await response.json();
        console.log(data);
      }
      getUsers();
    </script>
  </body>
</html>

服务端代码

const express = require("express");
const app = express();
app.use((req, res, next) => {
  next();
});
app.post("/users", (req, res) => {
  res.json([{ id: 1, name: "zhangsan11" }]);
});
app.listen(3000, () => {
  console.log("sever 3000");
});

解释: 上面用静态服务起一个客户端 端口 5501, 然后通过express 起一个后端服务 端口3000,  客户端刷新页面会发起一个/users的请求, 一定会发生跨域问题, 'no-cors' to fetch the resource with CORS disabled. 显示 no-cors 获取资源失败, 其实后端是收到了请求,也将数据返回去了, 但是接口的响应表头中没有设置允许的 源 Orign, 所以浏览器对于这种跨域又没有设置 Orign 的情况, 就会报错 阻止客户端收到返回值

1、 跨域问题的解决

app.use((req, res, next) => {
  res.header("Access-Control-Allow-Origin", "*");
  next();
});

实际上只需要后端增加一行代码, 就可以解决跨域问题了, 因为 * 代表所有源都允许, 如果希望指定的源或者给几个指定源添加白名单的形式,可以这样写

const whiteList = ["http://127.0.0.1:5501"];
if (whiteList.includes(req.headers.origin)) {
    res.header("Access-Control-Allow-Origin", req.headers.origin);
}

这样就只能针对特定的源允许跨域请求了

2、针对复杂请求的相关处理与解决

上面我们解决完跨域问题了, 但是还存在几个问题, 我们的请求是post的,还没有添加header, 一旦修改请求方法,或者header增加一些字段,就发现有问题了,所以下面开始处理这些复杂请求 先简单区分什么是复杂请求什么是简单请求: 区分他们有两个指标, 一个是请求方法: HEAD、GET、POST是简单请求, header中常见的 Content-Type 是 application/x-www-form-urlencodedmultipart/form-data 和一些 AcceptAccept-LanguageContent-Language 这些都是简单请求, 简单请求就很简单,不需要在俄额外进行服务端的处理设置了, 可以尽情使用

除此之外,其他的请求方法 和 其他的请求头。都是复杂请求, 复杂请求会额外发送一个 OPTION 请求来询问服务端是否支持, 那么怎么才能支持呢? 就是要分别处理 这里格外需要注意, header 中的 Content-Type: "application/json 是复杂请求, 比如 POST 的时候 我们经常需要这种类型, 那么在跨域的时候就需要特殊处理了,否则会报错 Request header field content-type is not allowed  这种解决只需要在代码中增加一行

 res.header("Access-Control-Allow-Headers", "Content-Type");

如果想增加自定义的头 比如请求时候 再增加一个 custom: 3000

 headers: {
    "Content-Type": "application/json",
    custom: "3000",
 },

这样也只需要后端在允许的Headers中加一下

js

复制代码

 res.header("Access-Control-Allow-Headers", "Content-Type, custom");

相关文章
|
前端开发
前端常用小知识点总结
前端常用小知识点总结
|
6月前
|
JSON 前端开发 JavaScript
详细剖析让前端头疼的跨域问题是怎么产生的,又该如何解决
详细剖析让前端头疼的跨域问题是怎么产生的,又该如何解决
|
缓存 安全 前端开发
【从原理到实战】彻底搞懂跨域问题 (一)(2)
3、预检请求的优化 复杂请求会发预检请求, 相当于每个接口会发两次请求, 比较消耗资源, 那么是可以对预检请求进行优化, 可以采用以下两种方式 设置预检请求的缓存时长
97 0
|
6月前
|
存储 安全 网络协议
面试必备基本知识HTTPS 原理分析
面试必备基本知识HTTPS 原理分析
|
6月前
|
JavaScript 前端开发 安全
前端部分知识点总结
前端部分知识点总结
72 0
|
6月前
|
前端开发 安全 JavaScript
前端知识笔记(二)———跨域到底是什么意思?
前端知识笔记(二)———跨域到底是什么意思?
75 0
|
11月前
|
JavaScript 前端开发 IDE
前端—每天5道面试题(十四)
前端—每天5道面试题(十四)
|
SQL 存储 前端开发
前端面试的游览器部分(3)每天10个小知识点
前端面试的游览器部分(3)每天10个小知识点
45 0
|
存储 缓存 安全
前端面试的游览器部分(1)每天10个小知识点
前端面试的游览器部分(1)每天10个小知识点
36 0
|
缓存 JavaScript 前端开发
前端面试的游览器部分(8)每天10个小知识点
前端面试的游览器部分(8)每天10个小知识点
58 0