task35 JSONP

2019-05-26  本文已影响0人  vivienYang2019

课前预习:《阮一峰:浏览器同源政策及其规避方法

1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。
最初,它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同"。

  • 协议相同
  • 域名相同
  • 端口相同

同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

随着互联网的发展,"同源政策"越来越严格。目前,如果非同源,共有三种行为受到限制。
1.Cookie、LocalStorage 和 IndexDB 无法读取。
2.DOM 无法获得。
3.AJAX 请求不能发送。


数据库是什么鬼

  1. 文件系统是一种数据库
  2. MySQL 是一种数据库

只要能长久地存数据,就是数据库

用数据库做加法

https://github.com/FrankFang/nodejs-test

image.png

用文件db来当数据库

  //读数据库
  if(path === '/'){
    var string = fs.readFileSync('./index.html','utf8')
    var amount = fs.readFileSync('./db_amount.tb','utf8')
    string = string.replace("{{amount}}",amount)
    response.setHeader('Content-Type','text/html;charset=utf-8')
    response.write(string)
    response.end()
  }
  //读写数据库
  else if(path === '/pay'){
      var amount = fs.readFileSync('./db_amount.tb','utf8')//读数据库
      var new_amount = amount - 1
      fs.writeFileSync('./db_amount.tb',new_amount)//写数据库
      response.write('success')
      response.end()
  }

可以加个Math.random()函数,控制有成功,也有失败。
——成功了返回success,改变db里面的值
——失败了返回error,不改变db里面的值

  else if(path === '/pay/image'){//用图片造get请求
    var amount = fs.readFileSync('./db_amount.tb','utf8')
    if(Math.random() > 0.5){
      var new_amount = amount - 1
      fs.writeFileSync('./db_amount.tb',new_amount)
      response.statusCode = 200
      response.write('success')
    }
    else{
      response.statusCode = 400
      response.write('error')
    }
    response.end()
  }

一开始程序员都是用表单提交的方式来请求服务端接口的↓


index.html.png

但是这个体验不太好。在页面点击付款,会跳到/pay的页面,显示/pay接口的请求结果,返回上一个页面,刷新一下,才能看到新的账户余额。用户体验不好,2005年之前一直都是这样的。。。


页面.png
点击付款.png

用form表单,点击提交,一定会刷新当前页面的
如何优化呢?

方法1:用iframe

image.png image.png

这样就不会跳转页面了
form可以发请求,还有什么办法发请求呢?比如说css link可以发请求,图片可以发请求。。。

方法2 用图片发请求

点击按钮,就创建一个img标签,设置img的src为请求的地址


image.png

前端终于想到了一个方法可以悄无声息的去发起一个请求——用img去创建一个请求
缺陷:这种方法只能用get,没法改成post请求
那么怎么知道请求成功还是失败了呢?如何监听?
——状态码,2xx成功;3xx重定向;4xx客户端错误;5xx服务器错误


image.png
image.png
成功了也可以不用刷新页面
//成功了就给页面的余额减一,不用刷新页面
amount.innerText = amout.innerText -1

真的要给一个图片,不然img一直都onerror↓

  else if(path === '/pay/image'){//用图片造get请求
    var amount = fs.readFileSync('./db_amount.tb','utf8')
    if(Math.random() > 0.5){
      var new_amount = amount - 1
      fs.writeFileSync('./db_amount.tb',new_amount)
      response.statusCode = 200
      //真的要返回一张图片,不然img就一直onerror
      response.setHeader('Content-Type', 'image/png') //Content-Type需要是图片!!!
      response.write(fs.readFileSync('./leaf.png'))//!!!要真的返回一个图片
    }
    else{
      response.statusCode = 400
      response.write('error')
    }
    response.end()
  }

用图片发请求必须要返回真实的图片,浪费资源,而且无法获取更多有用的信息。
那么有没有其他方法呢?既然image可以发请求,那么script也可以发请求呀

方法3 用script发请求

image.png
<body>
  <div class="main">
    <p>您的账号余额是<span id="amount">{{amount}}</span>元</p>
    <div class="payBtn" id="scriptPayBtn">script打钱1元</div>
  </div>
<script>
  //用script发请求
  scriptPayBtn.addEventListener('click',function(){
    let script=document.createElement('script')
    script.src='/pay/script'
    document.body.appendChild(script) //!!!必需要加到页面上才可以生效
    script.onload=function(){
      alert('打钱成功')
    }
    script.onerror=function(){
      alert('打钱失败')
    }
  })
</script>
</body>

必须要把script加到页面上才可以生效:document.appendChild(script)

  else if(path === '/pay/script'){//用script造get请求
    var amount = fs.readFileSync('./db_amount.tb','utf8')
    if(Math.random() > 0.5){
      var new_amount = amount - 1
      fs.writeFileSync('./db_amount.tb',new_amount)
      response.statusCode = 200
      response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
      response.write('amount.innerText=amount.innerText-1')//这个是可以执行的js代码
    }
    else{
      response.statusCode = 400
      response.write('error')
    }
    response.end()
  }

既然返回的js加到了页面,那么就可以执行!!!


image.png image.png

这样页面就可以不用监听success了,直接在请求返回的js文件中进行相关的操作
这样有一个问题,每点击一次,页面就多一个script标签。可以在onload和onerror中操作把script给remove


image.png image.png

SRJ方案(server rendered javascipt)
服务器返回的JavaScript:在ajax出现之前的一种无刷新局部更新页面内容的方案

域名什么的无所谓

跨域 SRJ
因为默认情况下,script标签可以引用任何域名下的js,比如cdn里面的js和我们的网站域名不同,也是可以引用的。
所以我们用SRJ技术可以请求其他域名网站的接口,跨域请求接口。这样是不安全的,所以一些重要的接口如pay一般不会支持get请求(script发请求【srj】只能get,image发请求也只能get)

修改host文件,两个不同的域名
host文件位置
macos: /etc/hosts
win:C:\Windows\System32\drivers\etc


image.png
image.png

可以从frank.com请求jack.com的接口,操作的也是jack.com的数据库
用script是可以的,用ajax是不行的,不能跨域请求

现在的SRJ方案有一个问题


image.png

就是后端程序员需要对前端细节很清楚
耦合度太高了->需要解耦!

  //定义回调函数yyy!!!
  window.yyy=function(result){
    alert('这是frank写的前端代码')
    alert(`我得到的结果是${result}`)
    amount.innerText=amount.innerText-1
  }
  //用script发请求
  scriptPayBtn.addEventListener('click',function(){
    let script=document.createElement('script')
    script.src='http://jack.com:8002/pay/script?callbackName=yyy'//!!!!这个yyy是请求成功后的回调函数名称
    document.body.appendChild(script)
    script.onload=function(e){
      alert('打钱成功')
      e.currentTarget.remove()
    }
    script.onerror=function(e){
      alert('打钱失败')
      e.currentTarget.remove()
    }
  })
  else if(path === '/pay/script'){//用script造get请求
    var amount = fs.readFileSync('./db_amount.tb','utf8')
    if(Math.random() > 0.5){
      var new_amount = amount - 1
      fs.writeFileSync('./db_amount.tb',new_amount)
      response.statusCode = 200
      response.setHeader('Content-Type', 'text/javascript;charset=utf-8')
      response.write(`
        ${query.callbackName}.call(undefined,'success')
      `)//这个是可以执行的js代码,执行callbackName传过来的函数
    }
    else{
      response.statusCode = 400
      response.write('error')
    }
    response.end()
  }
//客户端
window.yyy=function(result){
  alert('这是frank写的前端代码')
  alert(`我得到的结果是${result}`)
  amount.innerText=amount.innerText-1
}
script.src='http://jack.com:8002/pay?callbackName=yyy'
//服务端
${query.callbackName}.call(undefined, 'success') //在这里等价于yyy.call(undefined,'success')

jsonp要解决的问题就是两个网站之间如何交流?


image.png

返回的是字符串——'success'⬆️
返回的是json——{"success":true,"left":${newAmount}} ⬇️
但返回的不只是json,两边还有内容padding,
JSON + padding = JSONP


image.png
JSONP
请求方:frank.com 的前端程序员(浏览器)
响应方:jack.com 的后端程序员(服务器)

1. 请求方创建 script,src 指向响应方,同时传一个查询参数 ?callbackName=yyy
2. 响应方根据查询参数callbackName,构造形如
    1. yyy.call(undefined, '你要的数据')
    2. yyy('你要的数据')
    这样的响应
3. 浏览器接收到响应,就会执行 yyy.call(undefined, '你要的数据')
4. 那么请求方就知道了他要的数据

这就是 JSONP

约定:
1. callbackName -> callback
2. yyy -> 随机数 frank1122334111()

jQuery如何发送jsonp请求?
 $.ajax({
 url: "http://jack.com:8002/pay",
 dataType: "jsonp",
 success: function( response ) {
     if(response === 'success'){
     amount.innerText = amount.innerText - 1
     }
 }
 })

 $.jsonp()

JSONP

符合约定的写法⬇️


image.png
  //用script发请求
  scriptPayBtn.addEventListener('click',function(){
    let script=document.createElement('script')
    //随机生成函数名称!!!!
    let functionName=`vivienYang${parseInt(Math.random()*10000)}`
    window[functionName]=function(result){
      if(result.success===true){
        amount.innerText=amount.innerText-1
      }
    }
    script.src='http://jack.com:8002/pay/script?callback='+functionName
    document.body.appendChild(script)
    script.onload=function(e){
      alert('打钱成功')
      e.currentTarget.remove()
      delete window[functionName]//函数执行完成后就删了!!!
    }
    script.onerror=function(e){
      alert('打钱失败')
      e.currentTarget.remove()
      delete window[functionName]//函数执行完成后就删了!!!
    }
  })

用jquery实现⬇️


image.png
  jqPayBtn.addEventListener('click',function(){
    $.ajax({
      url:'http://jack.com:8002/pay/script',
      dataType:'jsonp',//!!!!指定dataType为jsonp
      success:function(res){
        if(res.success===true){
          alert('打钱成功')
          amount.innerText=amount.innerText-1
        }
      },
      error:function(){
        alert('打钱失败')
      }
    })
  })

面试题:请问为什么jsonp不支持post请求?
1.因为jsonp是通过动态创建script实现的
2.我们动态创建script时智能支持get,没有办法用post
jsonp就是script+callback参数

上一篇 下一篇

猜你喜欢

热点阅读