Ajax第四篇:跨域JSONP、CORS

第一章:同源策略

1.1-Ajax请求限制问题

Ajax 只能向自己的服务器发送请求。比如现在有一个A网站、有一个B网站,A网站中的 HTML 文件只能向A网站服务器中发送 Ajax 请求,B网站中的 HTML 文件只能向 B 网站中发送 Ajax 请求,但是 A 网站是不能向 B 网站发送 Ajax请求的,同理,B 网站也不能向 A 网站发送 Ajax请求。

原因:同源策略的规定

1.2-什么是同源策略

同源策略(Same origin policy)是一种约定,它是浏览器最核心也最基本的安全功能,如果两个页面拥有相同的协议、域名和端口,那么这两个页面就属于同一个源,其中只要有一个不相同,就是不同源。

1.3-同源策略的目的

同源政策是为了保证用户信息的安全,防止恶意的网站窃取数据。最初的同源政策是指 A 网站在客户端设置的 Cookie,B网站是不能访问的。

随着互联网的发展,同源政策也越来越严格,在不同源的情况下,其中有一项规定就是无法向非同源地址发送Ajax 请求,如果请求,浏览器就会报错。

第二章:跨域解决方案-JSONP

2.1-什么是JSONP

JSONP(JSON with Padding)是JSON的一种“使用模式”,可用于解决主流浏览器的跨域数据访问的问题。由于同源策略,一般来说位于 server1.example.com 的网页无法与不是 server1.example.com的服务器沟通,而 HTML 的<script> 元素是一个例外。利用 <script> 元素的这个开放策略,网页可以得到从其他来源动态产生的 JSON 资料,而这种使用模式就是所谓的 JSONP。用 JSONP 抓到的资料并不是 JSON,而是任意的JavaScript,用 JavaScript 直译器执行而不是用 JSON 解析器解析。

2.2-JSONP的实现步骤

一. 将不同源的服务器端请求地址写在 script 标签的 src 属性中

<script src="www.example.com"></script>

二. 服务器端响应数据必须是一个函数的调用,真正要发送给客户端的数据需要作为函数调用的参数。

const data = 'fn({name: "小明", age: "10"})';
res.send(data);

三. 在客户端全局作用域下定义函数 fn

function fn (data) { }

四. 在 fn 函数内部对服务器端返回的数据进行处理

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

2.3-JSONP代码实现

web1: 提供接口 http://localhost:4001/test

接口程序

const express = require('express')
const app = express();

app.get('/test', (req, res) => {
  res.send('fn({name:"bruce",age:10})')
})


app.listen(4001, "localhost")

web2: 发送请求 http://localhost/index.html

服务器部署程序

const express = require('express')
const path = require('path')



const app = express();
app.use(express.static(path.join(__dirname, '/public')))


app.listen(80, "localhost")

前端页面程序

  <button id="btn">发送请求</button>
  <script>
    // 定义全局函数 fn 处理请求的数据
    function fn(data) {
      console.log(data);
    }
    // 点击按钮发送请求
    btn.onclick = function(){
      var script = document.createElement('script')  // 创建script标签
      script.src = 'http://localhost:4001/test';   // 设置请求地址
      document.body.appendChild(script);      // 追加到页面中,返回的结果会自动调用全局函数fn
      script.onload = function(){ // 脚本加载完毕后,移除脚本
        document.body.removeChild(script)
      }            
    }
  </script>

2.4-JSONP 封装

jsonp脚本封装

(function (window) {
  /**
   * 
   * @param {*} options  配置参数
   * options.url 请求地址
   * options.data 请求参数,对象格式:{key:value,key:value}
   * options.success 请求数据处理程序  第一个参数标识响应的数据
   */
  function jsonp(options) {
    var params;
    /*处理请求参数 */
    if (options.data) {
      var arr = [];
      for (var key in options.data) {
        arr.push(key + '=' + options.data[key])
      }
      params = arr.join('&');
    }
    /*创建全局的处理程序-处理响应的结果 */
    var fnName = 'jsonp' + Date.now() + (Math.random() + '').replace('.', '');
    window[fnName] = options.success;
    /*创建script标签,并设置请求路径和参数 */
    var script = document.createElement('script');
    options.url = options.url + '?callback=' + fnName;
    if (params) {
      options.url += '&' + params;
    }
    script.src = options.url;
    /*追加到页面中 */
    document.body.appendChild(script);
    /*页面加载完毕后,移除script和处理响应的方法,因为是一次性的使用,所以要移除 */
    script.onload = function () {
      document.body.removeChild(script);
      delete window[fnName];
    }

  }
  window.jsonp = jsonp;
})(window)

web01提供接口 http://localhost:4001/test1

const express = require('express')
const app = express();

app.get('/test1', (req, res) => {
  res.jsonp(req.query);
})


app.listen(4001, "localhost")

web02调用接口http://localhost/index.html

  <button id="btn">发送请求</button>
  <script src="./jsonp.js"></script>
  <script>
    // 点击按钮发送请求
    btn.onclick = function(){
      jsonp({
        url:'http://localhost:4001/test1',
        data: {name:'admin',age:10},
        success:function(data){
          console.log(data);
        }
      })
    }
  </script>

2.5-JSONP案例

需求

调用腾讯天气接口,获取天气信息

接口文档

腾讯天气接口

参数名 必选 类型 说明
source String pc、xw
weather_type String forecast_1h 未来48小时
forecast_24h 未来7天
两个都传用|分割
province String 省份 如:河北省
city string 城市 如:邯郸市
  • 返回值
    myJsonp017363176455629659({
        "data": {
            "forecast_1h": {
                "0": {
                    "degree": "20",     // 温度
                    "update_time": "20190529100000",  // 更新时间
                    "weather": "多云",     // 天气 weather
                    "weather_code": "01",
                    "weather_short": "多云",
                    "wind_direction": "西风",  // 风向 
                    "wind_power": "5"         // 风力
                }, 
                "1": {
                    "degree": "21",
                    "update_time": "20190529110000",
                    "weather": "多云",
                    "weather_code": "01",
                    "weather_short": "多云",
                    "wind_direction": "西风",
                    "wind_power": "5"
                },
                
            }
        },
        "message": "OK",
        "status": 200
    })



代码

  <h2>近两天天气情况</h2>
  <div>
    <input type="text" id="province" value="河北">省
    <input type="text" id="city" value="邯郸">市
    <button id="btn">查询</button>
  </div>
  <table>
    <thead>
      <th>时间</th>
      <th>空气</th>
      <th>温度</th>
      <th>风向</th>
      <th>风力</th>
    </thead>
    <tbody id="tbody"></tbody>
  </table>
  <!-- 天气列表模板 -->
  <script type="text/html" id="list">
    {{each list}}
    <tr>
      <td>{{dateFormat($value.update_time)}}</td>
      <td>{{$value.weather}}</td>
      <td>{{$value.degree}}℃</td>
      <td>{{$value.wind_direction}}</td>
      <td>{{$value.wind_power}}级</td>
    </tr>
    {{/each}}
  </script>
  <script src="./jsonp.js"></script>
  <script src="./template-web.js"></script>
  <script>
    // 日期处理格式
    function dateFormat(str) {
      // 20200510100000
      var arr = str.match(/d{2}/g);
      return arr[0] + arr[1] + '年' + arr[2] + '月' + arr[3] + '日 ' + arr[4] + ':' + arr[5] + ':' + arr[6];

    }
    // 在模块引擎中模板中使用外部全局变量
    template.defaults.imports.dateFormat = dateFormat;
    // 点击按钮查询
    btn.onclick = function () {
      // jsonp请求腾讯天气
      jsonp({
        url: 'https://wis.qq.com/weather/common',
        data: {
          source: 'pc',
          weather_type: 'forecast_1h',
          province: province.value,
          city: city.value
        },
        success: function (data) {
          var html = template('list', { list: data.data.forecast_1h })
          tbody.innerHTML = html;
        }
      })
    }
    btn.onclick();
  </script>

第三章:跨域解决方案-CORS

3.1-什么是CORS

CORS:全称为 Cross-origin resource sharing,即跨域资源共享,它允许浏览器向跨域服务器发送 Ajax 请求,克服了 Ajax 只能同源使用的限制。

3.2-Node中配置CORS

Node 服务器端设置响应头示例代码:

// 请求拦截
app.use((req, res, next) => {
  // 允许所有的其他源跨域访问
  res.header('Access-Control-Allow-Origin', '*')
  // 允许的访问方式
  res.header('Access-Control-Allow-Methods', 'GET,POST,DELETE,PUT')
  //允许的header类型
  res.header("Access-Control-Allow-Headers", "content-type")
  // 允许携带cookie,注意Access-Control-Allow-Origin此时不能使用通配符*
  res.header("Access-Control-Allow-Credentials", true)
  next();
})

注意:

  • 在使用Ajax技术发送跨域请求时,默认情况下不会在请求中携带cookie信息。

  • XMLHttpRequest实例的withCredentials属性,指定在涉及到跨域请求时,是否携带cookie信息,默认值为false

  • 同时,若需要携带cookie,后端也需要配置响应头Access-Control-Allow-Credentials:true 允许客户端发送请求时携带cookie

3.3-示例代码

需求

输入账号和密码

点击登录按钮,输出登录信息

点击检测登录状态按钮,输出是否登录

web01 接口程序

const express = require('express')
const app = express();
const formidable = require('formidable')
// 导入express-session模块
const session = require('express-session')
// session的名称
let identityKey = 'skey';
 
//使用session
app.use(session({
  name: identityKey,
  secret: 'appkey', // 用来对session id相关的cookie进行签名
  // store: new FileStore(), // 本地存储session(文本文件,也可以选择其他store,比如redis的)
  saveUninitialized: false, // 是否自动保存未初始化的会话,建议false
  resave: false, // 是否每次都重新保存会话,建议false
  cookie: {
    maxAge: 10 * 1000 // 有效期,单位是毫秒
  }
}));
 

// 请求拦截
app.use((req, res, next) => {
  // 允许所有的其他源跨域访问
  res.header('Access-Control-Allow-Origin', 'http://localhost')
  // 允许的访问方式
  res.header('Access-Control-Allow-Methods', 'GET,POST,DELETE,PUT')
  //允许的header类型
  res.header("Access-Control-Allow-Headers", "content-type")
  // 允许携带cookie,注意Access-Control-Allow-Origin此时不能使用通配符*
  res.header("Access-Control-Allow-Credentials", true)
  next();
})

app.post('/login', (req, res) => {
  const form = new formidable.IncomingForm()

  form.parse(req, (err, fields, files) => {
    if (fields.username == 'admin' && fields.pwd == '111') {
      req.session.isLogin = true;
    }
    res.send(fields);
  })
})

app.get('/check', (req, res) => {
  if (req.session.isLogin) {
    res.send('已经登录')
  } else {
    res.send('未登录')
  }
})


app.listen(4001, "localhost")

web02 前端页面程序

  <form id="loginData">
    账户:<input type="text" name="username">
    密码:<input type="password" name="pwd">
  </form>
  <button id="goLogin">登录</button>
  <button id="checkLogin">检测登录状态</button>
  <script src="./myajax.js"></script>
  <script>
    goLogin.onclick = function(){
       var xhr = new XMLHttpRequest();
       xhr.withCredentials = true;
       xhr.open('post',"http://localhost:4001/login");
       var formData = new FormData(loginData);
       xhr.send(formData)
       xhr.onload = function(){
         console.log(xhr.responseText)
       }
    }
    checkLogin.onclick = function(){
       var xhr = new XMLHttpRequest();
       xhr.open('get',"http://localhost:4001/check");
       xhr.withCredentials = true;
       xhr.send();
       xhr.onload = function(){
         console.log(xhr.responseText)
       }
    }
    
  </script>

第四章:服务端跨域解决方案

同源政策是浏览器给予Ajax技术的限制,服务器端是不存在同源政策限制。

在Node中需要使用第三方模块request

web01 程序

后端

const express = require('express')
const path = require('path')
const request = require('request')

const app = express();
app.use(express.static(path.join(__dirname, '/public')))

app.get('/test', (req, res) => {
  request('http://localhost:4001/test02', (err, response, body) => {
    console.log(body) // 其他服务端接口响应的数据
    res.send('ok');
  })
})

app.listen(80, "localhost")

前端

  <script src="./myajax.js"></script>
  <script>
    ajax({
      url:'http://localhost/test',
      success:function(data) {
        console.log(data);
      }
    })
  </script>

web02 接口程序

const express = require('express')
const app = express();
const formidable = require('formidable')
app.get('/test02', (req, res) => {
  res.jsonp('ok');
})
app.listen(4001, "localhost")
原文地址:https://www.cnblogs.com/lpl666/p/12873360.html