传统开发方式:

  • 前端代码及请求数据接口都在同一个服务器上,前端代码测试依赖服务器 如下:
  • pic
    前后端未分离

前后端分离模式:

  • 静态服务器: 运行前端代码
  • 后台服务器: 运行数据接口的服务器
  • pic
    前后端分离
    • 前后端代码不在一个服务器,互不影响,前端调试页面不用再启动整个后端代码,更加便利
  • 由于前后端分离后,前端代码和后端代码不在同一个服务器(或者端口不一样)这样必然造成了跨域的问题

同源策略: ajax/iframe

  • ajax: 协议 + 域名 + 端口 其中有一个与之前页面所的不一样,这个 ajax 请求就跨域了
  • iframe: iframe A 到 iframe B 过程中操作共享数据.比如 window.name,就报错

CORS 解决跨域

  • CORS 是一个 W3C 标准,全称是”跨域资源共享”(Cross-origin resource sharing)
  • 我们知道普通的 Ajax 是存在跨域问题的,当我们在 js 中通过 Ajax 跨域访问服务器时,服务器会正常的接收到请求,并且会返回数据,但是,浏览器出于安全(是否多管闲事)会检查这次请求是否跨域,如果是,则阻止 js 拿到服务器返回的数据,简图说明如下:
  • pic
    Ajax跨域
  • pic
    Ajax跨域时浏览器报错
  • CORS,只需要在服务器端改下代码:

    • Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS // 允许跨域的请求方式
    • Access-Control-Allow-Origin: //允许哪个域在跨域的时候访问, `` 代表所有
    • Access-Control-Allow-Credentials: true //告诉浏览器,跨域时允许有cookie,同时客户端也要设置withCredentials:true + Origin不能是*
    • Access-Control-Request-Headers:’xxx’; // 允许自己加的头- header 来通信
  • pic
    CORS解决跨域
  • CORS 前后端代码演示

  • 前端代码没什么变化:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Title</title>
    <style>

    </style>
    <script>

    function getSomeData (data) {
    let obj = JSON.parse(data);
    console.log(obj);
    let span1 = document.getElementById('sp1');
    let span2 = document.getElementById('sp2');
    span1.innerHTML = obj.name;
    span2.innerHTML = obj.age;
    }
    window.onload = function () {
    let xhr = new XMLHttpRequest();
    xhr.open('get','http://xxx.xxx.xxx.xxx:3000/a',true);
    xhr.send();
    xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
    if (xhr.status === 200) {
    getSomeData(xhr.responseText);
    }
    }
    };
    }
    </script>
    </head>
    <body ng-controller="test">
    <h1>我的名字是:<span id="sp1"></span></h1>
    <h1>我的年龄是:<span id="sp2"></span></h1>
    </body>
    </html>
  • 后端代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    //关键两行代码:
    //res.setHeader('Access-Control-Allow-Origin','*');
    //res.setHeader('Access-Control-Allow-Methods','GET,PUT,DELETE,OPTIONS');
    const http = require("http");
    let server = http.createServer();
    server.on('request',(req,res)=>{
    if (req.url.startsWith('/a')) {
    console.log('有请求来了'+ req.url);
    let data = '{"name":"xxx","age":15}';
    res.setHeader('Access-Control-Allow-Origin','*');
    res.setHeader('Access-Control-Allow-Methods','GET,PUT,DELETE,OPTIONS');
    res.write(`${data}`);
    res.end();
    }
    });

    server.listen(3000,()=>{
    console.log('监听服务器3000端口成功!');
    });
  • pic
    CORS实现的跨域

jsonp 解决跨域

  • 如上我们知道,正常的 Ajax 是不能跨域的,除非使用 CORS ,在不用 CORS 的情况下,我们可以绕过 Ajax 去使用 jsonp
  • 利用 <script></script> 标签的 src 属性可以跨域的原理
  • 关键在于以下:
    • script 标签请求的 js 代码会被执行
    • 需要在 js 中声明一个函数,用来作为 js 代码回来后的调用,参数是后台的数据
  • 前后端代码演示:
  • 前端代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Title</title>
    <style>

    </style>
    </head>
    <body>
    <h1>我的名字是:<span id="sp1"></span></h1>
    <h1>我的年龄是:<span id="sp2"></span></h1>
    <script>
    function getSomeData (data) {
    console.log(data);
    document.getElementById("sp1").innerHTML = data.name;
    document.getElementById("sp2").innerHTML = data.age;
    }
    let scriptE = document.createElement("script");
    scriptE.src = "http://xxx.xxx.xxx.xxx:3000/a?callBack=getSomeData";
    document.body.appendChild(scriptE);
    </script>
    </body>

    </html>
  • 后端代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const http = require("http");
    let server = http.createServer();
    server.on('request',(req,res)=>{
    if (req.url.startsWith('/a')) {
    let fn = req.url.split("?")[1].split("=")[1];
    console.log(fn);
    let obj = '{"name":"zhangsan","age":23}';
    res.write(`${fn}(${obj})`);//返回一个 js 的函数调用,传入的参数就是后台返回的数据
    res.end();
    }
    });

    server.listen(3000,()=>{
    console.log('监听服务器3000端口成功!');
    });
  • pic
    jsonp跨域请求数据

Nginx 代理解决跨域

  • 服务器本身不受所谓浏览器同源策略的影响,所以可以用服务器去代理请求跨域资源
  • 这里举一个例子:前端页面和 Node.js 服务器在同一个域里面,如果通过前端去请求 豆瓣API 是必然构成跨域的,但是我们通过服务器端去请求 豆瓣API 就不会有跨域的问题
  • 前端代码:(下面的代码,点击按钮发 Ajax 去请求本域的 ./proxy 接口)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="utf-8">
    <title>Title</title>
    <style>

    </style>
    </head>
    <body>
    <h1 id="h11"></h1>

    <input id="btn1" type="submit" value="点我发proxy代理" />
    </body>
    <script>
    let btn = document.getElementById("btn1");
    btn.onclick = function () {
    let xhr = new XMLHttpRequest();
    xhr.open("post","./proxy",true);
    xhr.send();
    xhr.onreadystatechange = function () {
    if (xhr.readyState === 4) {
    if (xhr.status === 200) {
    //console.log(xhr.responseText);
    console.log(typeof xhr.responseText);//string
    document.getElementById("h11").innerHTML =JSON.parse(xhr.responseText).subjects[0].title;
    }
    }
    };
    };
    </script>
    </html>
  • 后端代码:(后端代码用到了 一个模块:request,由服务器去发请求再返回给浏览器)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const http = require("http");
    const fs = require("fs");
    var request = require('request');
    let server = http.createServer();
    server.on('request',(req,res)=>{
    if (req.url === "/") {
    fs.readFile("./static/proxy.html",(err,data)=>{
    res.write(data.toString());
    res.end();
    });
    }
    if (req.url.startsWith('/proxy')) {
    var x = request('https://api.douban.com/v2/movie/top250');
    req.pipe(x);
    x.pipe(res);
    }
    });
    server.listen(3000,()=>{
    console.log('监听服务器3000端口成功!');
    });
  • 豆瓣API 接口:https://api.douban.com/v2/movie/top250 数据:

  • pic
    豆瓣接口
  • 获取结果:
  • pic
    结果截图