likes
comments
collection
share

Ajax(7):跨域请求方案

作者站长头像
站长
· 阅读数 18

跨域请求

三种跨域方案

  • JSONP模拟ajax请求
  • CORS跨域请求
  • 让服务端作为客户端的代理进行请求

ajax请求限制

Ajax只能自己的服务器发送请求,也就是只能发送同源请求(判断两个请求是否同源:相同http/https协议,域名,端口),比如现在有A和B两个网站,A网站中的HTML文件只能向A服务器获取数据,B网站...获取数据,A只能向A服务器发送Ajax请求,而A如果向B发送请求,能发送,但是浏览器拒绝接收数据

同源规定

A网站只能向A网站服务器发送http/https请求,浏览器会判断请求是否同源来接收或者拒绝响应,也就是ajax是否能进行跨域请求,同源政策是为了保证用户信息而出现的,A网站在客户端设置的cookie,B网站不能进行访问!!!

同源的标准:

  1. 协议是否相同(http 和 https)
  2. 域名是否相同(www.baidu.comwww.tenct.com)
  3. 端口是否相同(www.baidu.com:3000www.baidu.com:3001)

只要以上一项不满足,就是属于非同源的数据,浏览器会拒绝接受响应

Ajax(7):跨域请求方案

Ajax(7):跨域请求方案

1. JSONP模拟请求

JSONP是 json with padding 的缩写,他不属于Ajax请求,但是可以模拟ajax请求(使用script标签),需要前后端配合才能完成jsonp的请求(所有jsonp请求都是get请求)

  1. 将不同源的服务器请求地址写在script标签的src标签中(请求地址必须返回js代码)

    <script src="www.baidu.com"></script>
    <script src="www.aaa.com/file/index.js"></script>
    /**
     * script标签的src属性不受同源政策的限制,但是请求地址返回的数据必须是js代码,并且会直接
     * 执行,所有jsonp请求都是get请求
     */
    
  2. 服务端响应的数据必须是一个函数的调用,真正要发给客户端的数据作为函数的参数

    app.get('/jsonp', (req, res) => {
      const data = {
        name: 'xyb',
        age: 21
      }
      const str = `fn(${JSON.stringify(data)})`; 
      // => "fn({"name": "xyb","age": 21})"
      res.send(str)
    })
    
  3. 在客户端全局作用于下定义函数fn,并在函数内部对数据进行处理

    function fn(data) {
    	console.log(data)
    }
    

封装JSONP函数

优化的步骤

  1. 优化一:主动传递函数名,让服务器返回带有函数名的函数执行

    客户端:

    <script src="http://127.0.0.1:4000/jsonp?callback=fn"></script>
    

    服务端:

    app.get('/jsonp', (req, res) => {
      const data = {
        name: 'xyb',
        age: 21
      }
      let callback = req.query.callback;	// 获取服务端传过来的参数
      const str = `${callback}(${JSON.stringify(data)})`; // fn({"name":...})
      res.send(str)
    })
    
  2. 优化二:将script请求的发送变成动态发送

    客户端

    <button>按钮</button>
    <script>
      function fn(data) {
        console.log(data)
      }
      let btn = document.querySelector('button');
      btn.addEventListener('click', function() {
        // 1. 点击按钮,动态创建script标签
        let script = document.createElement('script');
        // 2. 给上src属性
        script.src = "http://127.0.0.1:4000/jsonp?callback=fn";
        // 3. 网页面中添加标签,模拟ajax请求
        document.body.appendChild(script)
        // 4. script标签加载完成,删除标签
        script.addEventListener('load', function() {
          // onload => script标签加载完成调用
          document.body.removeChild(script)
        })
      })
    </script>
    
  3. 优化三:封装jsonp函数,方便发起多次请求(最终版本)

    客户端:

    btn1.addEventListener('click', function () { 
      jsonp({
        url: 'http://127.0.0.1:4000/jsonp',
        success: function(data) {
          console.log(112233)
          console.log(data)
        }
      })
    })
    
    btn2.addEventListener('click', function () { 
      jsonp({
        url: 'http://127.0.0.1:4000/jsonp',
        query: {
          name: 'xyb',
          age: 20
        },
        success: function(data) {
          console.log(444555666)
          console.log(data)
        }
      })
    })
    
    function jsonp(option) {
      // 1. 创造script标签
      let script = document.createElement('script');
      // 2. 创建处理数据的函数名,不能重复(时间戳),绑定到window上
      let myFunction = 'myFunction' + (+new Date()).toString();
      window[myFunction] = option.success
      // 3. 如果有参数传入,拼接参数
      let queryStr = "";
      for(let k in option.query) {
        queryStr += ('&' + k + '=' + option.query[k])
      }
      // 4. 给script添加请求地址,拼接其他参数
      script.src = option.url + '?callback=' + myFunction + queryStr;
      // 5. 添加script标签,执行请求
      document.body.appendChild(script)
      // 6. 加载完成,删除script函数
      script.addEventListener('load', function() {
        document.body.removeChild(script)
      })
    }
    

    服务端:

    app.get('/jsonp', (req, res) => {
      // const data = {
      //   name: 'xyb',
      //   age: 21
      // }
      // let callback = req.query.callback;
      // const str = `${callback}(${JSON.stringify(data)})`;
      // res.send(str)
      
      // 服务端的jsonp方法,会执行上面的步骤
      res.jsonp({
        name: 'xyb',
        age: 20
      })
    })
    

2. CORS跨域请求

CORS:全称为Cross-Origin-Resource-Sharing,即为资源共享,它允许浏览器向服务器发送ajax请求,克服了浏览器的同源政策,客户端代码不需要改动,只需要服务器进行配置即可


浏览器发送ajax请求,会带上origin(在请求头中),如果服务器允许当前客户端的请求,在响应头中加入Access-Control-Allow-Origin,浏览器会查看响应头中有没有该字段,或客户端是否在服务器的白名单中,也就是Access-Control-Allow-Origin中,若其值为 * 那么代表所有客户端都能跨域请求

Ajax(7):跨域请求方案


步骤:

  1. 客户端代码(不变):

    let xhr = new XMLHttpRequest();
    xhr.open('GET', 'http://127.0.0.1:4000/json');
    xhr.onreadystatechange=() => {
      if(xhr.readyState == 4) {
        if((xhr.status >= 200 && xhr.status <= 300) || xhr.status == 304) {
          console.log(xhr.responseText);
        } else {
          alert('xhr is unsuccessful' + xhr.status)
        }
      }
    }
    xhr.send(null);
    
  2. 服务端代码(加响应头):

    // 单独的请求
    app.get('/json', (req, res) => {
      // 代表允许哪些客户端访问
      res.header("Access-Control-Allow-Origin", "*")
      // 代表允许客户端通过哪些方法访问
      res.header("Access-Control-Allow-Methods", "get,post")
      res.send({
        "name": "2号",
        "age": 21,
        "hobby": "money"
      })
    })
    
    // 设置所有的请求
    app.use((req, res, next) => {
      // 在这里会拦截所有的请求,并添加请求头 
      // 代表允许哪些客户端访问
      res.header("Access-Control-Allow-Origin", "*")
      // 代表允许客户端通过哪些方法访问
      res.header("Access-Control-Allow-Methods", "get,post")
      // 继续
      next()
    })
    

3. 服务端进行请求

由于服务器之间没有同源政策的限制,可以让服务器发送请求来获取数据


A服务器向B服务器获取数据再响应给A网站,A服务器充当一个爬虫的角色

Ajax(7):跨域请求方案


步骤:

  1. A网站代码:

    <script>
      let xhr = new XMLHttpRequest();
    xhr.open('GET', 'http://127.0.0.1:4000/server')
    xhr.onreadystatechange=() => {
      if(xhr.readyState == 4) {
        if((xhr.status >= 200 && xhr.status <= 300) || xhr.status == 304) {
          console.log(xhr.responseText);
        } else {
          alert('xhr is unsuccessful' + xhr.status)
        }
      }
    }
    xhr.send(null)
    </script>
    
  2. A服务端代码:

    app.get('/server', (req, res) => {
      request.get('http://127.0.0.1:3000/json', (err, response, body) => {
        res.send(body)
      })
    })
    
  3. B服务端代码:

    app.get('/json', (req, res) => {
      res.send({
        "name": "1号",
        "age": 21,
        "hobby": "money"
      })
    })