美文网首页
task35 JSONP

task35 JSONP

作者: vivienYang2019 | 来源:发表于2019-05-26 00:37 被阅读0次

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

    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参数

    相关文章

      网友评论

          本文标题:task35 JSONP

          本文链接:https://www.haomeiwen.com/subject/skmgzqtx.html