跨域

内容简介


《阮一峰:浏览器同源政策及其规避方法》

Windows 如何修改hosts


Windows 没有 /etc/hosts 文件,请按照如下方法修改 hosts:

  1. 用管理员身份打开记事本(在记事本的快捷方式图标上右键可以看到)
  2. 用这个记事本打开 hosts(菜单->文件->定位到C:\Windows\System32\drivers\etc目录,然后切换到所有文件)


    image.png

数据库是什么鬼


  1. 文件系统是一种数据库
  2. MySQL 是一种数据库:用的最广泛,My是MySQL作者的女儿的名字的缩写

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

同源策略


浏览器故意设置的一个功能限制

同源与同源策略

同源
同源策略
  • 浏览器规定
    • 如果 JS 裕兴在源 A 里,那么久只能获取源 A 的数据
    • 不能获取源 B 的数据,即不允许跨域
  • 举例(省略 http 协议)
    • 假设 frank.com/index.html 引用了 cdn.com/1.js
    • 那么就说,1.js 运行在 源 frank.com 里
    • 注意这跟 cdn.com 没有关系,虽然 1.js 从它那下载
    • 所以 1.js 只能获取 frank.com数据
    • 不能获取 1.frank.com 或者 qq.com 的数据
  • 这是浏览器的功能!
    • 浏览器故意要这样设计的
浏览器这样做的目的是?
  1. 为了保护隐私
  2. 怎么保护的?
    • 假如没有同源策略
      • 以QQ空间为例
        • 源为https://user.qzone.qq.com
        • 假设,当前用户已经登录(用Cookie,后面会讲)
        • 假设:AJAX请求 /friends.json可获取用户好友列表
        • 到目前为止都很正常
      • 黑客来了
问题的根源
  • 无法区分发送者
    • QQ空间页面里的JS和黑客页面里的JS发的请求几乎没有区别(referrer有区别)
    • 如果后台开发者没有检查referer,那么就完全没区别
    • 所以,没有同源策略,任何页面都能偷QQ空间的数据,甚至支付宝余额
  • 那检查referer不就好了?
    • 安全原则:安全链条的强度取决于最弱一环
    • 万一这个网站的后端开发工程师忽略了这一点呢
    • 所以浏览器应该主动预防这种偷数据的行为
    • 总之,浏览器为了用于隐私,设置了严格的同源策略
  • referer
    • 发送请求的时候,会有这个标识,我们可以后台看到
    • image.png
疑问
  • 为什么 a.qq.com访问qq.com也算跨域
    • 因为历史上,出现过不同公司共用域名,a.qq.com 和 qq.com 不一定是同一个网站,浏览器谨慎起见,认为这是不同的源
  • 为什么不同端口也算跨域?
    • 原因同上,一个端口一个公司。记住安全链条的强度取决于左若的一环,粉盒安全相关的问题都要谨慎对待
  • 为什么两个网站的IP一样的,也算跨域?
    • 原因同上,IP可以共用
  • 为什么可以跨域使用CSS、JS和图片等?
    • 同源策略限制的是数据访问,我们引用CSS、JS和图片的时候,其实并不知道其内容,我们只是在引用。

CORS(跨域资源共享)


突破浏览器限制的一个方法

  • 问题根源
    • 浏览器默认不同源之间不能互相访问数据
  • 用 CORS
    • 浏览器说,如果要共享数据,需要提前声明
    • 那么怎么声明呢?
    • 在被访问的网站那边的响应头里面写Access-Control-Allow-Orign
    • response.setHeader('Access-Control-Allow-Origin', '网址')

一个简单的服务器


  1. 代码如下
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]
// 判断是否提供端口号
if(!port){
  console.log('请指定端口号好不啦\nnode server.js 8888 这样不会吗?')
  process.exit(1)
}
// 创建服务器
var server = http.createServer(function(request, response){
  var parsedUrl = url.parse(request.url, true)
  var pathWithQuery = request.url
  var queryString = ''
  if(pathWithQuery.indexOf('?') >= 0){
    queryString = pathWithQuery.substring(pathWithQuery.indexOf('?'))
  }
  var path = parsedUrl.pathname
  var query = parsedUrl.query
  var method = request.method
  /************** 从这里开始看,上面不要看 ****************/
 // 我们打印出询问路径
  console.log('有个傻子发请求过来啦!路径(带查询参数)为 ' + pathWithQuery)
  response.write(`Hi\n`)
  response.end()
  /************** 从这里结束,下面不要看 ****************/
})
server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度后然后用电饭煲打开 http://localhost:' + port)
  1. 进入目录中,使用node server 端口号可以查看服务器是不是已经创建成功了,同时我们可以看到浏览器打开的网页就是有一个Hi


    image.png
  2. 我们发现不论我们请求什么,比如请求index.html,网页打印的都是Hi,这是因为我们写的服务器代码中,不管我们请求什么,返回的response就是一个Hi。


    image.png
  3. 我们丰富一下返回的内容,以及针对不同的请求,让返回的内容不一样;记住访问的路径跟返回的response文件名称及路径是不一样的,没有任何关系

var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]
if(!port){
  console.log('请指定端口号好不啦\nnode server.js 8888 这样不会吗?')
  process.exit(1)
}
var server = http.createServer(function(request, response){
  var parsedUrl = url.parse(request.url, true)
  var pathWithQuery = request.url
  var queryString = ''
  if(pathWithQuery.indexOf('?') >= 0){
    queryString = pathWithQuery.substring(pathWithQuery.indexOf('?'))
  }
  var path = parsedUrl.pathname
  var query = parsedUrl.query
  var method = request.method
  /************** 从这里开始看,上面不要看 ****************/
  // console.log('有个傻子发请求过来啦!路径(带查询参数)为 ' + pathWithQuery)
  if(path === '/'){
    var string = fs.readFileSync('./index.html', 'utf-8')
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(string)
    response.end()
  }else if(path === '/style.css'){
    var string = fs.readFileSync('./style.css', 'utf-8')
    response.setHeader('Content-Type', 'text/css')
    response.write(string)
    response.end()
  }else if(path === '/main.js'){
    var string = fs.readFileSync('./main.js', 'utf-8')
    response.setHeader('Content-Type', 'application/javascript')
    response.write(string)
    response.end()
  }else{
    response.statusCode = 404
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write('找不到对应的路径,你需要自行修改 index.js')
  }
  console.log(method + ' ' + request.url)
  /************** 从这里结束,下面不要看 ****************/
})
server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度后然后用电饭煲打开 http://localhost:' + port)
  1. 我们访问这个网页的时候,由于网页链接了main.js以及style.css,所以会访问这两个文件的


    image.png

做一个网页加减与服务器交互


  1. 我们先写一个网页,让其有一个按钮,点击的时候会减少金额
<title>首页</title>
<link rel="stylesheet" href="/style.css">
<h5>您 的 账 户 余 额 是 <span id="amount">100</span></h5>
<button id="button">付款1块钱</button>
<script>
    button.addEventListener('click', (e)=>{
        let n = amount.innerText    // innerText一定是String
        let number = parseInt(n, 10)   // 可以直接减,但是为了保险起见,还是转换一下
        let newNumber = number - 1
        amount.innerText = newNumber
    })
</script>
image.png
  1. 但是这个是假的,是网页上面的变化,没有跟任何服务器交互有关。当刷新页面的时候,会变回原样的。数据并没有永远的存储在一个地方。
  2. 我们如果想要数据永远存储到一个地方,需要使用硬盘存储。数据库就是硬盘文件。我们可以创建一个db,后缀没有无所谓的,我们在这个文件中只存储一个100。
  3. 然后我们是想在服务器读取文件的时候需要将db中的文件放入db中,我们在网页中对应的位置使用特殊的占位符,如&&&amount&&&,使用这个占位符可以将占位符替换成真正的100。


    image.png
  4. 这下我们需要调整一下nodejs的代码,下面这个做法就是在启动server的时候,读取db文件,将这个文件放入网页中,这样我们读取出来的值虽然是100,但是这个100,其实是db中的100


    image.png

    image.png
  5. 当我们点击的时候,我们希望告诉服务器,请更改db内容,并刷新页面;这样就是在点击的时候,需要发送post请求,存储数据。目前button做不到,我们需要使用form
<title>首页</title>
<link rel="stylesheet" href="/style.css">
<h5>您 的 账 户 余 额 是 <span id="amount">&&&amount&&&</span></h5>
<form action="/pay" method="post">
    <input type="submit" value="付款">
</form>
image.png
  1. 当我们点击付款的时候,会发送一个post请求。此时会是404状态,因为我们并没有说明再此路径下要做什么


    image.png
  2. 我们更改一下server。让当路径是/pay的时候,服务器读取db文件并减1存入。
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]
if(!port){
  console.log('请指定端口号好不啦\nnode server.js 8888 这样不会吗?')
  process.exit(1)
}
var server = http.createServer(function(request, response){
  var parsedUrl = url.parse(request.url, true)
  var pathWithQuery = request.url
  var queryString = ''
  if(pathWithQuery.indexOf('?') >= 0){
    queryString = pathWithQuery.substring(pathWithQuery.indexOf('?'))
  }
  var path = parsedUrl.pathname
  var query = parsedUrl.query
  var method = request.method
  /************** 从这里开始看,上面不要看 ****************/
  // console.log('有个傻子发请求过来啦!路径(带查询参数)为 ' + pathWithQuery)
  if(path === '/'){
    var string = fs.readFileSync('./index.html', 'utf-8')    // 同步读取文件
    var amount = fs.readFileSync('./db', 'utf-8')    // 同步读取出来的数据类型还是string的
    string = string.replace('&&&amount&&&', amount)    // 这边是使用替换将原先字符串中的&&&amount&&&替换成db
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(string)
    response.end()
  }else if(path === '/style.css'){
    var string = fs.readFileSync('./style.css', 'utf-8')
    response.setHeader('Content-Type', 'text/css')
    response.write(string)
    response.end()
  }else if(path === '/main.js'){
    var string = fs.readFileSync('./main.js', 'utf-8')
    response.setHeader('Content-Type', 'application/javascript')
    response.write(string)
    response.end()
  }else if(path === '/pay' && method.toUpperCase() === 'POST'){
      var amount = fs.readFileSync('./db', 'utf-8')
      var newAmount = amount - 1
      fs.writeFileSync('./db', newAmount)  // 将值写入db文件中
      response.write('success')
      response.end()
  }else{
    response.statusCode = 404
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write('找不到对应的路径,你需要自行修改 index.js')
  }
  console.log(method + ' ' + request.url)
  /************** 从这里结束,下面不要看 ****************/
})
server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度后然后用电饭煲打开 http://localhost:' + port)
  1. 当我们点击付款的时候,会跳转页面,然后返回去,刷新页面,就能看到金额变了,读取文件系统,返回的也是对应的值


    image.png

    image.png

    image.png
  2. 这就是旧时代前后端配合的方法,2005年之前全世界的网站都是这样,点击会返回成功还是失败,然后刷新就可以看到的
  3. 一个细节,form表单提交之后一定会刷新页面。
  4. 我们可以稍微优化一下,使用iframe,给form添加target就是添加刷新位置。承担刷新的是iframe;这个套路已经没人用了


    image.png

    image.png

使用img创建请求


  1. 我们想发请求的标签不用form,用什么呢?可以使用a,CSS的link,script,img等
  2. 当我们动态创建一个img的时候,浏览器一定会去请求这个地址
<title>首页</title>
<link rel="stylesheet" href="/style.css">
<h5>您 的 账 户 余 额 是 <span id="amount">&&&amount&&&</span></h5>
<button id="button">打钱</button>
<script>
    button.addEventListener('click', (e)=>{
        let image = document.createElement('img')
        image.src = '/pay'
    })
</script>
image.png
  1. 这个请求的缺陷是,没有办法发POST请求,并且返回的response必须是图片,只给状态码200是不够的,容易出问题。


    image.png

    image.png
  2. 我们怎么监控这个图片请求成功或者失败呢?我们可以监控状态码


    image.png

    image.png
  3. 我们操作这个图片请求的时候,需要返回一张真正的图片,我们成功之后,可以直接在玩野上操作内容变化,而不是刷新网页,这就是一个局部刷新

<title>首页</title>
<link rel="stylesheet" href="/style.css">
<h5>您 的 账 户 余 额 是 <span id="amount">&&&amount&&&</span></h5>
<button id="button">打钱</button>
<script>
    button.addEventListener('click', (e)=>{
        let image = document.createElement('img')
        image.src = '/pay'
        image.onload = function(){
            alert('打钱成功')
            amount.innerText = amount.innerText - 1
        }
        image.onerror = function(){
            alert('打钱失败')
        }
    })
</script>

所对应的server

var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]
if(!port){
  console.log('请指定端口号好不啦\nnode server.js 8888 这样不会吗?')
  process.exit(1)
}
var server = http.createServer(function(request, response){
  var parsedUrl = url.parse(request.url, true)
  var pathWithQuery = request.url
  var queryString = ''
  if(pathWithQuery.indexOf('?') >= 0){
    queryString = pathWithQuery.substring(pathWithQuery.indexOf('?'))
  }
  var path = parsedUrl.pathname
  var query = parsedUrl.query
  var method = request.method
  /************** 从这里开始看,上面不要看 ****************/
  // console.log('有个傻子发请求过来啦!路径(带查询参数)为 ' + pathWithQuery)
  if(path === '/'){
    var string = fs.readFileSync('./index.html', 'utf-8')    // 同步读取文件
    var amount = fs.readFileSync('./db', 'utf-8')    // 同步读取出来的数据类型还是string的
    string = string.replace('&&&amount&&&', amount)    // 这边是使用替换将原先字符串中的&&&amount&&&替换成db
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(string)
    response.end()
  }else if(path === '/style.css'){
    var string = fs.readFileSync('./style.css', 'utf-8')
    response.setHeader('Content-Type', 'text/css')
    response.write(string)
    response.end()
  }else if(path === '/main.js'){
    var string = fs.readFileSync('./main.js', 'utf-8')
    response.setHeader('Content-Type', 'application/javascript')
    response.write(string)
    response.end()
  }else if(path === '/pay'){
      var amount = fs.readFileSync('./db', 'utf-8')
      var newAmount = amount - 1
      if(Math.random()>0.5){
        fs.writeFileSync('/db', newAmount)
        response.setHeader('Content-Type', 'image/png')
        response.statusCode = 200
        response.write(fs.readFileSync('cat.jpg'))
      }else{
        response.statusCode = 400
        response.write('fail')
      }
      response.end()
  }else{
    response.statusCode = 404
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write('找不到对应的路径,你需要自行修改 index.js')
  }
  console.log(method + ' ' + request.url)
  /************** 从这里结束,下面不要看 ****************/
})
server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度后然后用电饭煲打开 http://localhost:' + port)

使用script发请求


  1. 我们上面通过image发请求的,能不能通过script发请求呢?
  2. 使用script标签,光创建,添加src属性是不够的,创建出来必须加入到body中才可以有用
  3. 下面是代码
<title>首页</title>
<link rel="stylesheet" href="/style.css">
<h5>您 的 账 户 余 额 是 <span id="amount">&&&amount&&&</span></h5>
<button id="button">打钱</button>
<script>
    button.addEventListener('click', (e)=>{
        let script = document.createElement('script')
        script.src = '/pay'
        document.body.appendChild(script)
        script.onload = function(){
            alert('success')
        }
        script.onerror = function(){
            alert('fail')
        }
    })
</script>
image.png

4.这样我们在发现点击的时候,会出现多个script标签,script标签里面的内容会执行的,但是我们发现服务器会弹出一个框提示成功与否,而script执行会弹出一个框,提示成功与否。这样就重复了,即下面两张图的内容重复了


image.png

image.png
  1. 也就是上面的onload我们可以不写的。我们也可以在服务器端直接局部更改页面中值
<title>首页</title>
<link rel="stylesheet" href="/style.css">
<h5>您 的 账 户 余 额 是 <span id="amount">&&&amount&&&</span></h5>
<button id="button">打钱</button>
<script>
    button.addEventListener('click', (e)=>{
        let script = document.createElement('script')
        script.src = '/pay'
        document.body.appendChild(script)
        script.onerror = function(){
            alert('fail')
        }
    })
</script>

对应服务器的代码

var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]
if(!port){
  console.log('请指定端口号好不啦\nnode server.js 8888 这样不会吗?')
  process.exit(1)
}
var server = http.createServer(function(request, response){
  var parsedUrl = url.parse(request.url, true)
  var pathWithQuery = request.url
  var queryString = ''
  if(pathWithQuery.indexOf('?') >= 0){
    queryString = pathWithQuery.substring(pathWithQuery.indexOf('?'))
  }
  var path = parsedUrl.pathname
  var query = parsedUrl.query
  var method = request.method
  /************** 从这里开始看,上面不要看 ****************/
  // console.log('有个傻子发请求过来啦!路径(带查询参数)为 ' + pathWithQuery)
  if(path === '/'){
    var string = fs.readFileSync('./index.html', 'utf-8')    // 同步读取文件
    var amount = fs.readFileSync('./db', 'utf-8')    // 同步读取出来的数据类型还是string的
    string = string.replace('&&&amount&&&', amount)    // 这边是使用替换将原先字符串中的&&&amount&&&替换成db
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(string)
    response.end()
  }else if(path === '/style.css'){
    var string = fs.readFileSync('./style.css', 'utf-8')
    response.setHeader('Content-Type', 'text/css')
    response.write(string)
    response.end()
  }else if(path === '/main.js'){
    var string = fs.readFileSync('./main.js', 'utf-8')
    response.setHeader('Content-Type', 'application/javascript')
    response.write(string)
    response.end()
  }else if(path === '/pay'){
      var amount = fs.readFileSync('./db', 'utf-8')
      var newAmount = amount - 1
      fs.writeFileSync('/db', newAmount)
      response.setHeader('Content-Type', 'application/javascript')
      response.statusCode = 200
      // response.write(`
      //   alert("success")
      //   window.location.reload()
      //   `)   // 服务器返回的在浏览器执行的JS代码,提示成功并刷新网页
      response.write('amount.innerText = amount.innerText - 1')  //直接局部刷新,不做整体刷新
      response.end()
  }else{
    response.statusCode = 404
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write('找不到对应的路径,你需要自行修改 index.js')
  }
  console.log(method + ' ' + request.url)
  /************** 从这里结束,下面不要看 ****************/
})
server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度后然后用电饭煲打开 http://localhost:' + port)
  1. 我们发现当我们每打一次钱二代时候,会创建一个script,这样页面中会出现多个script,我们可以在响应之后将script删除掉。使用script.onload和script.onerror在刷新或者失败的时候将其自身删除就好
<title>首页</title>
<link rel="stylesheet" href="/style.css">
<h5>您 的 账 户 余 额 是 <span id="amount">&&&amount&&&</span></h5>
<button id="button">打钱</button>
<script>
    button.addEventListener('click', (e)=>{
        let script = document.createElement('script')
        script.src = '/pay'
        document.body.appendChild(script)
        script.onload = function(e){
            e.currentTarget.remove()    // 从页面中移除元素
        }
        script.onerror = function(e){
            e.currentTarget.remove()
            alert('fail')
        }
    })
</script>
  1. 对这个方案的总结:当用户发生一个点击动作的时候,我们可以生成一个script,然后script的src属性就是我们要请求的路径,将这个script放入页面中,浏览器就会发起一个get请求(此处依旧无法使用POST),如果get成功了,首先会运行服务器返回的javascript响应,这个服务器返回的javascript响应就是操作局部数据进行刷新,那段代码会被当做JS执行是基于HTTP协议。
    这边能够执行是有两个条件的:
    • 第一个条件是基于HTTP协议,我们基于这个HTTP指定response是javascript
    • image.png
    • 第二个条件是,这个路径的引入方式是script里面。所以前面返回的response一定会在script里面执行的

执行之后,如果成功,用户就可以看到金额变化了,执行之后,script就删除了

  1. 上面这个方案被称为SRJ(Server Rendered Javascript),服务器返回的javascript;这个在AJAX出现之前,一些很牛逼的后端程序员想出来的无刷新局部更新页面内容的方案。

一个域名可以访问另一个域名吗?


  1. 首先有一个问题,我们在一个页面中引用一个script的时候,src的域名重要吗?如我们需要在一个fangfang.com里面访问baidu.com是可以的吗?


    image.png
  2. 答案是可以的。比如我们在引用jQuery的时候就是不一样的域名;Script是不受域名限制的,任何一个网站都可以使用另一个域名网站的JS文件
  3. 我们大部分的pay是使用post请求的,因为get请求太容易被伪造了,因为任何一个网站都可以请求这个API。重要的操作一定不要使用get,否则别人就可以使用图片或者script来驱动,我们只要将对应的src链接到相应的地址就好了


    image.png

做两个网页进行交互-->JSONP


  1. 我们首先更改一下host文件,添加两个局域的域名,两个域名不一样,但是都是访问一个IP


    image.png
  2. 我们开两个server,端口号可以这样进行设置:PORT=8001 node server.js,但是此处我们写死了,不可以这样,两个服务器端口号不一样,所以是不同的网站,只不过这两个网站的源代码是一样的


    image.png

    image.png
  3. 我们如果想要frank.com的前端去访问jack.com的后端,由于不分域名,这个技术是可以实现的。我们可以更改src链接地址到jack.com就好了


    image.png
  4. 当我们在frank.com页面点击打钱的时候,就会发送请求到jack.com,操作jack.com;这两个是完全不同的网站,但是它们是可以调对方的script的


    image.png
  5. 这种做法其实是有矛盾的,我们看到其实在后端的时候,是操作前端的,也就是我想访问另一个网页的后端,需对其前端也要了解,这就叫做耦合,当我对你的页面了解不够深的时候,就写不下去了,彼此之间太紧密了。

  6. 我们需要解耦的,我们将要执行的代码放入函数中,函数内容不管,我们只要函数名,去调用就好了

  7. 具体的情况是这样的,要访问的网站先将xxx函数写好,然后我们访问端只需要调用就好了


    image.png

    image.png
  8. 现在点打钱就能请求到相应的函数,及调用,而对内部的函数是什么样的根本不关心,两个网站就可以无缝进行交互

  9. 对于xxx这个函数怎么知道的,其实可以在网页调用的时候传参进去


    image.png

    image.png

    image.png
  10. 这样就只需告诉成功失败的结果,不需要细节,这个就是JSONP

<title>首页</title>
<link rel="stylesheet" href="/style.css">
<h5>您 的 账 户 余 额 是 <span id="amount">&&&amount&&&</span></h5>
<button id="button">打钱</button>
<script>
    window.xxx = function(result){
        alert('这是frank写的前端代码')
        alert(`我得到的结果是${result}`)
    }
    button.addEventListener('click', (e)=>{
        let script = document.createElement('script')
        script.src = 'http://jack.com:8002/pay'
        document.body.appendChild(script)
        script.onload = function(e){
            e.currentTarget.remove()    // 从页面中移除元素
        }
        script.onerror = function(e){
            e.currentTarget.remove()
            alert('fail')
        }
    })
</script>
var http = require('http')
var fs = require('fs')
var url = require('url')
var port = process.argv[2]
if(!port){
  console.log('请指定端口号好不啦\nnode server.js 8888 这样不会吗?')
  process.exit(1)
}
var server = http.createServer(function(request, response){
  var parsedUrl = url.parse(request.url, true)
  var pathWithQuery = request.url
  var queryString = ''
  if(pathWithQuery.indexOf('?') >= 0){
    queryString = pathWithQuery.substring(pathWithQuery.indexOf('?'))
  }
  var path = parsedUrl.pathname
  var query = parsedUrl.query
  var method = request.method
  /************** 从这里开始看,上面不要看 ****************/
  // console.log('有个傻子发请求过来啦!路径(带查询参数)为 ' + pathWithQuery)
  if(path === '/'){
    var string = fs.readFileSync('./index.html', 'utf-8')    // 同步读取文件
    var amount = fs.readFileSync('./db', 'utf-8')    // 同步读取出来的数据类型还是string的
    string = string.replace('&&&amount&&&', amount)    // 这边是使用替换将原先字符串中的&&&amount&&&替换成db
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write(string)
    response.end()
  }else if(path === '/style.css'){
    var string = fs.readFileSync('./style.css', 'utf-8')
    response.setHeader('Content-Type', 'text/css')
    response.write(string)
    response.end()
  }else if(path === '/main.js'){
    var string = fs.readFileSync('./main.js', 'utf-8')
    response.setHeader('Content-Type', 'application/javascript')
    response.write(string)
    response.end()
  }else if(path === '/pay'){
      var amount = fs.readFileSync('./db', 'utf-8')
      var newAmount = amount - 1
      fs.writeFileSync('/db', newAmount)
      response.setHeader('Content-Type', 'application/javascript')
      response.statusCode = 200
      // response.write(`
      //   alert("success")
      //   window.location.reload()
      //   `)   // 服务器返回的在浏览器执行的JS代码,提示成功并刷新网页
      // 说明jack.com的后端程序员需要对frank.com的页面细节了解的很清楚
      // 耦合
      // response.write('amount.innerText = amount.innerText - 1')  //直接局部刷新,不做整体刷新
      
      // 这一步很重要,一定要记住
      response.write(`
        ${query.callbackName}.call(undefined, 'success')    
      `)
      response.end()
  }else{
    response.statusCode = 404
    response.setHeader('Content-Type', 'text/html;charset=utf-8')
    response.write('找不到对应的路径,你需要自行修改 index.js')
  }
  console.log(method + ' ' + request.url)
  /************** 从这里结束,下面不要看 ****************/
})
server.listen(port)
console.log('监听 ' + port + ' 成功\n请用在空中转体720度后然后用电饭煲打开 http://localhost:' + port)
  1. JSONP要接觉得问题是两个网站之间怎么交流,我们可以使用script,因为script是不受域名限制的,而AJAX是受域名限制的;既然不受限制,我们就可以请求对方的网站,将数据给它,并调用xxx,然后将参数传入xxx的第一个参数里面;其中接受参数那一步很重要
  2. 服务器这端,函数名是不知道的,调用的时候其实只传了一个参数,就是结果'success'。如果穿的这个参数是json(json是一门新的语言,很JS很像,但是一定要加双引号,前面有一个{,后面有一个},分别叫左padding和右padding),所以可以理解成JSON+Padding,叫JSONP。但是大部分情况下,可以不是JSON,使用String,就会是StringP。高亮的部分就是JSON,我们的核心就是返回JSON或者String,但是我们需要加padding才可以,JSON前面的就是左padding,JSON右边的就是右Padding


    image.png
  3. 整个过程跟JSON没有关系的。

JSONP过程


过程

  1. 我们分为请求方和响应方,请求方是一个网站的前端,响应方是另一个网站的后端

  2. 请求方需要创建一个script标签,src指向响应方,同时传一个查询参数 ?callbackName=xxx

  3. 响应方根据查询参数callbackName,构造形如

    • xxx.call(undefined, '你要的数据')
    • xxx.('你要的数据')

    这样的响应

  4. 浏览器接收到响应,就会执行xxx.call(undefined, '你要的数据')

  5. 那么请求方就知道了他要的数据

约定

  1. 我们上面写的callbackName必须交callback,即callbackName -> callback
  2. xxx -> 随机数:因为我可能调用了10个网站的JSONP,但是每个网站我们都要想个名字太麻烦,我们就使用随机数,随便取一个,如jQuery123456785(),每到下一个我们就加个1,以避免重复,但是名字不要以数字开头
<title>首页</title>
<link rel="stylesheet" href="/style.css">
<h5>您 的 账 户 余 额 是 <span id="amount">&&&amount&&&</span></h5>
<button id="button">打钱</button>
<script>
    button.addEventListener('click', (e)=>{
        let script = document.createElement('script')
        // 实现名称+随机数,random返回的是小数,我们将其改装成整数
        let functionName = 'frank'+parseInt(Math.random()*10000, 10)
        // 将随机名称绑定成函数
        window[functionName] = function(result){
            if(result === 'success'){
                amount.innerText = amount.innerText - 1
            }else{}
        }
        script.src = 'http://jack.com:8002/pay?callback=' + functionName
        document.body.appendChild(script)
        script.onload = function(e){
            e.currentTarget.remove()    // 从页面中移除元素
            delete window[functionName]    // 删除函数,避免污染环境
        }
        script.onerror = function(e){
            e.currentTarget.remove()
            alert('fail')
            delete window[functionName]
        }
    })
</script>
image.png

使用jQuery实现JSONP


  1. 先在环境中下载jQuery,npm i jquery


    image.png
  2. 代码如下
<title>首页</title>
<link rel="stylesheet" href="/style.css">
<h5>您 的 账 户 余 额 是 <span id="amount">&&&amount&&&</span></h5>
<button id="button">打钱</button>
<!-- 此处由于没有搁jQuery做路由,所以访问不到,所谓路由就是给一个if else -->
<!-- <script src='./node_modules/jquery/dist/jquery.min.js'></script> -->
<script src='https://cdnjs.com/ajax/libs/jquery/3.2.1/jquery.min.js'></script>
<script>
    button.addEventListener('click', (e)=>{
        $.ajax({
            url: "http://jack.com:8002/pay",
            dataType: "jsonp",
            success: function(response){
                if(response === 'success'){
                    amount.innerText = amount.innerText - 1
                }
            }
        })
    })
</script>
  1. jQuery自己做了callback,服务器返回的也是callback函数


    image.png

    image.png
  2. 注意一点,JSONP不是ajax

JSONP为什么不能使用POST请求


  1. 因为JSONP是通过动态创建script来创建的
  2. 动态创建的script是不能发送post请求的。

局部刷新怎么做?


有没有想过,不返回 HTML,返回 JS

方案一:用图片造 get 请求

button.addEventListener('click', (e)=>{
    let image = document.createElement('img')
    image.src = '/pay'
    image.onload = function(){ // 状态码是 200~299 则表示成功
        alert('成功')
    }
    image.onerror = function(){ // 状态码大于等于 400 则表示失败
        alert('失败')
    }
})

方案二:用 script 造 get 请求

button.addEventListener('click', (e)=>{
    let script = document.createElement('script')
    script.src = '/pay'
    document.body.appendChild(script)
    script.onload = function(e){ // 状态码是 200~299 则表示成功
        e.currentTarget.remove()
    }
    script.onerror = function(e){ // 状态码大于等于 400 则表示失败
        e.currentTarget.remove()
    }
})
//后端代码
...
if (path === '/pay'){
    let amount = fs.readFileSync('./db', 'utf8')
    amount -= 1
    fs.writeFileSync('./db', amount)
    response.setHeader('Content-Type', 'application/javascript')
    response.write('amount.innerText = ' + amount)
    response.end()
}
...

这种技术叫做 SRJ - Server Rendered JavaScript

域名什么的无所谓


跨域 SRJ

确定函数名


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

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

这就是 JSONP

方案3:JSONP


button.addEventListener('click', (e)=>{
    let script = document.createElement('script')
    let functionName = 'frank'+ parseInt(Math.random()*10000000 ,10)
    window[functionName] = function(){  // 每次请求之前搞出一个随机的函数
        amount.innerText = amount.innerText - 0 - 1
    }
    script.src = '/pay?callback=' + functionName
    document.body.appendChild(script)
    script.onload = function(e){ // 状态码是 200~299 则表示成功
        e.currentTarget.remove()
        delete window[functionName] // 请求完了就干掉这个随机函数
    }
    script.onerror = function(e){ // 状态码大于等于 400 则表示失败
        e.currentTarget.remove()
        delete window[functionName] // 请求完了就干掉这个随机函数
    }
})
//后端代码
...
if (path === '/pay'){
    let amount = fs.readFileSync('./db', 'utf8')
    amount -= 1
    fs.writeFileSync('./db', amount)
    let callbackName = query.callback
    response.setHeader('Content-Type', 'application/javascript')
    response.write(`
        ${callbackName}.call(undefined, 'success')
    `)
    response.end()
}
...
约定:
    1. callbackName -> callback
    2. yyy -> 随机数 frank12312312312321325() 
    
        $.ajax({
        url: "http://jack.com:8002/pay",
        dataType: "jsonp",
        success: function( response ) {
            if(response === 'success'){
            amount.innerText = amount.innerText - 1
            }
        }
        })

        $.jsonp()

有关JSPNP优缺点的总结


  • 优点:
    • 支持IE
    • 可以跨域
  • 缺点:
    • script标签,拿不到状态码
    • 不支持POST,只支持GET

封装JSONP


function jsonp(url) {
  return new Promise((resolve, reject) => {
    const random = "callbackName" + Math.random()
    window[random] = data => {
      resolve(data)
    }
    const script = document.createElement("script");
    script.src = `${url}?functionName=${random}`
    script.onload = () => {
      script.remove()
    }
    script.onerror = () => {
      reject()
    }
    document.body.appendChild(script)
  })
}

jsonp('https://www.baidu.com/load.js')
  .then((data => {
    console.log(data)
  }))
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容

  • 同源限制 浏览器安全的基石是“同源政策”(same-origin policy)。 含义 1995年,同源政策由 ...
    laning312阅读 497评论 0 3
  • 什么是同源策略? 同源策略是指,浏览器出于安全方面的考虑,只允许与本域下的接口交互。不同源的客户端脚本在没有明确授...
    upup_dayday阅读 250评论 0 0
  • 整理中 目标: 了解跨域 解决跨域 服务器配置跨域(java, nginx) 前端调试时配置解决跨域 一、什么是跨...
    麦曦阅读 945评论 0 0
  • 前言部分 一、跨域是什么? 跨域是指一个域下的文档或脚本试图去请求另一个域下的资源(广义的)。 广义的跨域场景有以...
    爆发吧小宇宙阅读 3,276评论 0 12
  • 同源政策 Ajax请求限制 Ajax 只能向自己的服务器发送请求。比如现在有一个A网站、有一个B网站,A网站中的 ...
    东邪_黄药师阅读 246评论 0 1