那些年那些跨域的事

那些年那些跨域的事

1. 什么是跨域?

出于浏览器的同源策略限制,浏览器会拒绝跨域请求。那什么样的请求才算是跨域请求呢?

  1. 协议不相同
  2. 域名不相同
  3. 端口不相同

2. 跨域请求举例

假设我们现在浏览器中的地址是http://www.zaoren.com

URL是否跨域原因
http://www.zaoren.com/index同源
https://www.zaoren.com协议不同
http://www.zaorenll.com域名不同
http://www.zaorenll.com:8080端口号不同

3. 为什么会有跨域请求呢?

  • 场景一: 项目工程化后,一个需求可能对应到多个服务。而往往不同服务是放在不同的服务器上的,尽管在同一台服务器上部署,那么端口号也是不一样同的。
  • 场景二: 当我们使用FileReader()去读取本地文件(比如C://Documents…)的时候,往往会跨域,因为此时的协议是不相同的(比如我引用了第三方的库objLoader.js去加载一个obj文件,就会报跨域的错,这时候需要本地启一个服务,或者把资源放到能被所有用户访问到的服务器上)。

可能有朋友就会问了:不可能!我写程序都已经写了好几个月了!读取了成百上千的js和img文件,为什么从来没有遇到过跨域的问题?而且主要是面试官还老问我跨域的问题,要不然我才不会点开这篇博客来看呐。

偷偷告诉你,朋友,你之前可能一直使用的是专门为跨域请求提供的一种解决方案:JSONP

4.如何解决跨域问题?

在这里先说一个笑话,之前在学校的时候学习Three.js这个库,由于Three.js需要通过loader去加载本地的一些文件,而加载文件的时候会跨域,所以老师倡导我们每个人都装一个Hbuilder,然后用Hbuilder打开官方提供的demo,就能正常运行了。

其实我们在使用Hbuilder启动项目的时候,编译器会帮我们自动启动一个服务,然后从项目的根目录上开始,将整个文件放在这个服务下,那么这个时候情况就和表格中的第一种情况一样,localhost://127.0.0.1:83992访问localhost://127.0.0.1:83992/src/obj/objDemo.obj。

那么我们实际开发中有那些跨域的解决方案呢?下面仅介绍以下三种方法

  1. JSONP(上面提到过)
  2. CORS 跨域资源共享(重点!)
  3. Nginx反向代理

4.1 JSONP的跨域方式

JSONP是单纯的为了实现跨域请求而实现的一个方式。虽然因为同源策略的影响,不能通过XMLHttpRequest请求不同域上的数据。但在页面上通过html标签去引入不同域上的js脚本文件却是可以的。通常为了减轻web服务器的负载,我们把js、css,img等静态资源分离到另一台独立域名的服务器上,在html页面中再通过相应的标签从不同域名下加载静态资源。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      function show(json) {
        console.log(json.s);
      }
    </script>

    <script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=web&cb=show"></script> 
  </body>
</html>

4.2 CORS的跨域方式

跨源资源共享 (CORS) (或通俗地译为跨域资源共享)是一种机制,该机制使用附加的 HTTP头来告诉浏览器,准许运行在一个源上的Web应用访问位于另一不同源选定的资源。

4.2.1 预请求

此规则规定,处于跨域环境并在下面几个条件下,浏览器会发送两个请求给服务器;

举例说明:假设客户端需要发起PUT请求

  1. 首先,客户端需要发送OPTIONS求情给服务器
  2. 在服务器内部,需要对OPTIONS请求,做一些设定,告诉客户端是否允许访问
  3. 客户端确认服务器允许该方法,最终发送PUT请求;否则,跑出错误,服务器拒绝访问此方法。

触发预发请求有三种情况:

  1. 使用了某些方法,比如说 PUT, DELETE 等。
  2. Fetch 规范规定了对 CORS安全的首部字段集合,人为设置会触发预请求。
  3. Content-Type的值不是text/plain ,multipart/form-data,application/x-www-form-urlencoded

判断是否需要发送预请求的详细链接HTTP访问控制(CORS)

4.2.2 非预请求

此规则规定,在跨域产生的条件下,某些请求和方法,不需要预请求,只发送一个请求就行了。比如GET,POST和HEAD这三个方法。

接下来,我们来模拟一次不需要发预请求的简单请求(以下称简单请求)

前端代码

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Ajax测试</title>
  </head>
  <body>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
      $.ajax({
        url: "http://localhost:3000",
        type: "get",
        success: function (result) {
          console.log(result);
        },
        error: function (msg) {
          console.log(msg);
        },
      });
    </script>
  </body>
</html>

后端代码

const http = require("http");

const server = http.createServer((request, response) => {
  if (request.url === "/") {
    if (request.method === "GET") {
      console.log('recevied');
      response.end("{name: 'BruceLee', password: '123456'}");
    }

    if (request.method === "POST") {
      console.log('recevied');
      response.end("true");
    }
    
    if(request.method === 'OPTIONS') {
      console.log('发起了一个预请求');
      response.end(JSON.stringify({state:true}));
    }
  }

  response.end("false");
});

server.listen(3000, () => {
  console.log("The server is running at http://localhost:3000");
});

查看结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-d9DnHRty-1600861428589)(https://imgkr2.cn-bj.ufileos.com/3a25f34e-d680-49b6-9240-57c2489aea0f.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=Ko4cJjGMCD0GXvq7vwVg%252FasBFtg%253D&Expires=1600943129)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fg7UkorD-1600861428591)(https://imgkr2.cn-bj.ufileos.com/688eb10a-a72d-4b69-a49f-e6a03f6bd0dd.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=n5bft2f%252FwixpmQ1ufeG4%252F%252FZ2sIQ%253D&Expires=1600943142)]

很明显,我们后端已经收到请求了,但是返回的数据被浏览器的同源策略劫持了。

4.2.3 处理没有预请求的跨域请求

关键代码:设置一条 响应首部字段,允许 CORS 跨域资源共享:

if (request.method === "GET") {
  console.log('recevied');
  response.writeHead(200, {
    'Access-Control-Allow-Origin': '*'
  });
  response.end("{name: 'BruceLee', password: '123456'}");
}

跨域请求成功了!!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-59D5dIMP-1600861428592)(https://imgkr2.cn-bj.ufileos.com/6b6c5d76-92f6-42dd-a6b4-78b68c253d05.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=UsHeggdQimUG4E9DncBqBa9xydY%253D&Expires=1600943578)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yAeHZiTA-1600861428593)(https://imgkr2.cn-bj.ufileos.com/ea0dfd6a-fb8a-475c-935c-0bc43c8406ff.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=XKAX5hNOnobJPt62wbXFuQ%252Fepb8%253D&Expires=1600943417)]

处理 非预请求 就是这么简单,只需要在后台设置,一条 响应首部字段 即可。

4.2.4 处理有预请求的跨域请求

POST 方法在设置 contentType 为 application/json 时会触发预请求。

前端代码

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Ajax测试</title>
  </head>
  <body>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
      var data = { name: "Zaoren", password: "123456" };

      $.ajax({
        url: "http://localhost:3000",
        type: "post",
        data: JSON.stringify(data),
        contentType: "application/json;charset=utf-8",
        success: function (result) {
          console.log(result);
        },
        error: function (msg) {
          console.log(msg);
        },
      });
    </script>
  </body>
</html>

后端代码

const http = require("http");

const server = http.createServer((request, response) => {
  if (request.url === "/") {
    if (request.method === "GET") {
      console.log('recevied');
      response.writeHead(200, {
        'Access-Control-Allow-Origin': '*'
      });
      response.end("{name: 'Zaoren', password: '123456'}");
    }

    if (request.method === "POST") {
      console.log('recevied');
      response.end("true");
    }

    if(request.method === 'OPTIONS') {
      console.log('发起了一个预请求');
      response.end(JSON.stringify({state:true}));
    }
  }

  response.end("false");
});

server.listen(3000, () => {
  console.log("The server is running at http://localhost:3000");
});

查看前后端效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SL1OZBxZ-1600861428595)(https://imgkr2.cn-bj.ufileos.com/fb32ef04-dbef-431b-bd49-779978c6aa20.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=q9fdZHMuH39pOtNEJ%252BnVyLLPxlM%253D&Expires=1600944303)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1lL02Wlw-1600861428597)(https://imgkr2.cn-bj.ufileos.com/c101fbb4-5e33-401d-a375-75fbf1b61db5.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=I7GHOg8oG6wzZ4hPodyvxzN8YCU%253D&Expires=1600944284)]

看到,如我们所愿,我们发了一个预请求,但是我们的请求还是跨域了!很明显,是因为我们OPTIONS方法内部没有设置CORS响应首部字段,所以出现跨域错误。

修改代码:

if (request.method === 'OPTIONS') {	
    response.writeHead(200, {
        'Access-Control-Allow-Origin': '*',	 // 设置 optins 方法允许所有服务器访问 
        'Access-Control-Allow-Methods': '*', // 允许访问 POST PUT DELETE 等所有方法 
    });		
    response.end( JSON.stringify({state: true}) );
}

查看结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zf6ycYUM-1600861428597)(https://imgkr2.cn-bj.ufileos.com/67c07ebe-54cf-4b20-bd64-23bf00631ad5.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=Th%252FdWWk3bfwsXYLogPbHEGgSSKc%253D&Expires=1600944746)]

嗯? 按照报错信息,我们应该设置Access-Control-Allow-Headers响应首部字段。

最终修改

const http = require("http");

const server = http.createServer((request, response) => {
  if (request.url === "/") {
    if (request.method === "GET") {
      console.log("recevied");
      response.writeHead(200, {
        "Access-Control-Allow-Origin": "*",
      });
      response.end("{name: 'Zaoren', password: '123456'}");
    }

    if (request.method === "POST") {
      console.log("recevied");
      response.writeHead(200, {
        "Access-Control-Allow-Origin": "*",
      });
      response.end("true");
    }

    if (request.method === "OPTIONS") {
      console.log('发起了一个预请求!');
      response.writeHead(200, {
        "Access-Control-Allow-Origin": "*", // 设置 optins 方法允许所有服务器访问
        "Access-Control-Allow-Methods": "*", // 允许访问 POST PUT DELETE 等所有方法
        "Access-Control-Allow-Headers": "*"
      });
      response.end(JSON.stringify({ state: true }));
    }
  }

  response.end("false");
});

server.listen(3000, () => {
  console.log("The server is running at http://localhost:3000");
});

查看结果:没错,是成功的两个请求了

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2SlrnU7Q-1600861428598)(https://imgkr2.cn-bj.ufileos.com/e0ab904a-6781-4272-b176-2019d473d0d5.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=7NeOV4WC5sFFrXSvej%252BjtlOmwqE%253D&Expires=1600944952)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q4WmhWMQ-1600861428598)(https://imgkr2.cn-bj.ufileos.com/debe3048-c9a4-43e0-bafb-b9c5d8247ed5.png?UCloudPublicKey=TOKEN_8d8b72be-579a-4e83-bfd0-5f6ce1546f13&Signature=bWU1sVeKP7PlD2d6FTy3eBtiSZk%253D&Expires=1600944988)]

总结:

  1. 使用 CORS 跨域资源共享,是需要分成 预请求 与 非预请求 处理的。

  2. 非预请求,在服务器内,只需要简单设置:
    ‘Access-Control-Allow-Origin’: '*
    复制代码

  3. 预请求,在服务器内,至少要设置三个 响应首部字段:

'Access-Control-Allow-Origin': ?,	  
'Access-Control-Allow-Methods': ?, 
'Access-Control-Allow-Headers': 'Content-Type',
  1. 前端使用 content-Type: application/json 的时候,必须注意这是 预请求 ,后端需要处理 OPTIONS 方法

4.3 使用Nginx代理

之前说了,跨域是浏览器的行为,那么如果我用一台服务器专门转发我的请求。跨域的问题就迎刃而解了。目前用的比较多的是nignx做代理,解决跨域问题。

如何使用Nginx做反向代理

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值