内容简介:同源政策很简单,它的含义是指两个网页:一旦以上三点中有任意一点不同,两个网站都不能称为同源。举例:同源政策的目的其实就是为了保证用户信息的安全,防止恶意的网站数据窃取。 在阮一峰的博客中,在同源政策一节中对其作用描述如下:
同源政策很简单,它的含义是指两个网页:
- 协议相同
- 域名相同
- 端口相同
一旦以上三点中有任意一点不同,两个网站都不能称为同源。举例:
http://www.example.com/xxx http://www.example.com/yyy 以上两个网站是同源的,满足协议,域名,端口都相同(http协议默认端口为80) --------------------------- http://example.com/xxx http://www.example.com/xxx 以上两个网站是非同源的,因为域名不同 --------------------------- http://127.0.0.1:8080/xxx http://127.0.0.1:8888/xxx 以上两个网站是非同源的,因为端口号不同 复制代码
为什么要有同源政策
同源政策的目的其实就是为了保证用户信息的安全,防止恶意的网站数据窃取。 在阮一峰的博客中,在同源政策一节中对其作用描述如下:
"设想这样一种情况: A网站是一家银行,用户登录以后,又去浏览其他网站。 如果其他网站可以读取A网站的 Cookie,会发生什么? 很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。 更可怕的是: Cookie 往往用来保存用户的登录状态。 如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。" 复制代码
所以自1995起,"同源政策"由网景引入浏览器后,所有浏览器都开始效仿了这一政策。不过同源政策带来的安全保障的同时,也带来了一些限制,其中一个限制就是 AJAX 请求不能发送 。
聊一聊XMLHttpRequest
上文说到同源政策的限制之一就是AJAX请求无法发送,我们知道AJAX的核心就是XMLHttpRequest,所以借机我也简单谈一谈XMLHttpRequest。先看一个示例:
在我的hosts文件中,我事先已经写好了ip与域名的映射。
代码如下:
var http = require('http') var fs = require('fs') var url = require('url') var port = process.argv[2] if(!port){ console.log('Please appoint the port number\n Like node 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 = '' var query = parsedUrl.query var path = parsedUrl.pathname if(path.indexOf('?') >= 0){ queryString = pathWithQuery.substring(pathWithQuery.indexOf('?')) } var method = request.method console.log('HTTP Path:\n'+path) if(path ==='/'){ // sync是同步,async代表异步 let string = fs.readFileSync('./index.html','utf8'); response.statusCode = 200 response.setHeader('Content-Type','text/html;charset=utf-8') response.write(string); response.end(); }else if(path ==='/xxx'){ response.statusCode = 200 response.setHeader('Content-Type','text/json;charset=utf-8') response.write(` { "info":{ "name":"DobbyKim", "age":"25", "hobby":"唱跳rap篮球", "girlfriend":"rightHand" } } `) response.end(); } else{ response.statusCode = 404 response.setHeader('Content-Type','text/html;charset=utf-8') response.write('wrong') response.end() } console.log(method+''+request.url) }) server.listen(port) console.log('Listen'+port+'Success\n Please open http://localhost:'+port) 复制代码
前端代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>你咬我啊</title> </head> <body> <button id="btn">你咬我啊</button> <script> btn.addEventListener('click',()=>{ // 创建XMLHttpRequest对象 let request = new XMLHttpRequest(); // 初始化 request.open('POST','http://dobby.com:8888/xxx'); // 发送请求 request.send(); request.onreadystatechange = ()=>{ // 请求及响应均成功 if(request.readyState === 4){ if(request.status>=200 && request.status<300){ let string = request.responseText; let obj = window.JSON.parse(string); console.log(string); console.log(obj); }else{console.log('fail');} } } }) </script> </body> </html> 复制代码
在前端script代码中,我们为按钮添加了事件,当按钮被click,当前页面就会向服务端发起请求,我们再来回想一下request.readyState的五个状态值:
0 :代理被创建,但尚未调用open()方法 1 : open()方法已经被调用 2 : send()方法已经被调用 3 : 响应数据下载中 4 : 响应数据下载已完成 复制代码
首先我们开启两个node-server,它们指定的端口号分别为:8888和8889。我们在浏览器分别输入URL: dobby.com:8888
以及 frank.com:8889
。当我们在 dobby.com:8888
下点击按钮时,在浏览器的控制台上打印出了我们接收到的JSON数据。
但是,当我们在 frank.com:8889
下点击按钮,在控制台上则会报错:
这也就进一步验证了AJAX受限于"同源政策",对于我们上述示例来说,实际上这是一次跨域请求的过程即:A网站想要给B网站发送请求。由于同源政策,AJAX只能请求于协议,域名,端口号相同的网站,而在实际开发中,又有很多跨域的需求,所以AJAX自然也会使用一些方法规避同源政策。其实这也很简单,我们只需在后端代码中添加一句话即可:
else if(path ==='/xxx'){ response.statusCode = 200 response.setHeader('Content-Type','text/json;charset=utf-8') // 添加了这句话以后,任何网站都可以请求dobbykim.com:8888 // response.setHeader('Access-Control-Allow-Origin','*') response.setHeader('Access-Control-Allow-Origin','http://frank.com:8889') response.write(` { "info":{ "name":"DobbyKim", "age":"25", "hobby":"唱跳篮球rap", "girlfriend":"rightHand" } } `) response.end(); } 复制代码
上面我们实际上用到了CORS机制,CORS即Cross-Origin-Resource-Sharing,翻译成跨域资源共享,它使用额外的 HTTP 头来告诉浏览器 让运行在一个 origin (domain) 上的Web应用被准许访问来自不同源服务器上的指定的资源。当一个资源从与该资源本身所在的服务器不同的域、协议或端口请求一个资源时,资源会发起一个跨域 HTTP 请求。有了CORS机制,可以使AJAX进行跨域请求,AJAX同时也支持多种请求方式:get,post,put,delete等等。那么在没有AJAX之前,我们是怎样进行跨域请求的呢?这就要引出我们今天的主角JSONP了,但是在谈JSONP之前,我们还要再聊一聊历史~
不得不说的历史
假设我们有一个文件db,这个文件db暂时作为我们的数据库进行数据的存储,文件存储着当前金额的数量100。 后台程序如下:
var http = require('http') var fs = require('fs') var url = require('url') var port = process.argv[2] if(!port){ console.log('Please appoint the port number\n Like node 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 = '' var query = parsedUrl.query var path = parsedUrl.pathname if(path.indexOf('?') >= 0){ queryString = pathWithQuery.substring(pathWithQuery.indexOf('?')) } var method = request.method console.log('HTTP Path:\n'+path) if(path == '/'){ var string = fs.readFileSync('./index.html','utf8') var amount = fs.readFileSync('./db','utf-8') string = string.replace('&amount',amount); response.setHeader('Content-Type','text/html;charset=utf8') response.write(string) response.end() }else if(path==='/pay' && method.toUpperCase()==='POST'){ var amount = fs.readFileSync('./db','utf8') var newAmount = parseInt(amount) - 1; fs.writeFileSync('./db',newAmount); response.write('success'); response.end() } else{ response.statusCode = 404 response.setHeader('Content-Type','text/html;charset=utf-8') response.write('找不到对应的路径') response.end() } console.log(method+''+request.url) }) server.listen(port) console.log('Listen'+port+'Success\n Please open http://localhost:'+port) 复制代码
前端代码如下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> <link rel="stylesheet" href="./style.css"> </head> <body> <h5>您的账余额是 <span id="amount">&amount</span></h5> <form action="/pay" method="post"> <input type="submit" value="付款"> </form> </body> </html> 复制代码
form表单的核心功能就是提交。如本例:当我们点击submit进行提交时,浏览器会跳转到pay这个路径下 如果 path==='/pay' && method.toUpperCase()==='POST'
,我们就会将db文件存储的金额-1,然后返回一个"success"。开启server后,程序运行的结果如下:
当点击付款按钮时,form表单提交,页面发生跳转。
我们可以看到浏览器输入框的路径已经变成了pay,并且服务器返回了响应至浏览器即: response.write('success');
,在页面上我们看到了success的字样,后退至index.html页面,并点击刷新,我们可以看到,金额减少了一元钱。
其实,从功能上来讲,这是没有问题的。但是这却给用户造成了不好的体验。因为,用户每次点击付款按钮,页面都会发生跳转,而且用户需要自己点击后退按钮并刷新页面,才可以看到自己的账户余额。我们希望的是:点击付款后,浏览器会告诉我们付款成功or失败,在不刷新页面的情况下我们可以实时看到自己的账户余额。很显然,form表单是做不到的。为什么呢?仔细想一想,form表单在提交时,必定会发生页面的跳转,当然有一种方法可以做出稍稍的改进。在"远古时期"人们会使用iframe标签让form表单每次post都跳转到当前页面的内嵌的iframe中:
<form action="/pay" method="post" target="result"> <input type="submit" value="付款"> </form> <iframe name="result" src="about:blank" frameborder="0" height="200"></iframe> 复制代码
当点击付款按钮时:
form表单的post发生在了页面内嵌的iframe标签中,但是金额还是没有刷新,我们仍然需要自己手动刷新页面。
放弃POST,使用GET
form表单最大的问题就是会刷新页面或打开新的页面,不过form表单却有一个特性即:没有跨域的问题。在上面的程序中,我们如果将form标签变为 <form action="http://www.baidu.com/pay" method="get">
。实际上这个请求是可以发送的。在知乎上有一个问题:为什么form表单提交没有跨域问题,但是ajax提交有跨域问题?我在这里面借用下 方老师 的答案 :-)
言归正传,为了优化用户的体验,我们不得不放弃使用form表单,而改用其他的,可以让浏览器发起请求的标签,这些标签有:
- a标签
- img标签
- link标签
- script标签
- ......
a标签可以发起get请求,不过也会刷新或打开页面,img标签会发起get请求,但是只能以图片形式进行展示,经过多方面考虑,于是乎,当时的前端 程序员 决定使用script标签,因为script标签不仅能发起请求,同时也能作为脚本执行,最重要的是,script标签支持跨域请求。接下来,我们来看一个示例:
首先,在我的hosts文件中,我已经写好了ip与域名的映射。
开启两个node-server,分别为: http://dobby.com:8888
以及 http://frank.com:8889
,模拟dobby.com向frank.com发起跨域请求。
前端代码如下
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>首页</title> <link rel="stylesheet" href="style.css"> </head> <body> <h5>您的账余额是 <span id="amount">&amount</span></h5> <button id="btn">付款</button> <script> btn.addEventListener('click',()=>{ // 动态创建script标签 let script = document.createElement('script'); // 随机生成函数名 let functionName = 'dobby'+parseInt(Math.random()*10000,10); window[functionName] = (result)=>{ if(result === 'success'){ amount.innerText = amount.innerText - 1; }else{ alert('fail'); } } // 指定发起请求的地址 script.src = 'http://frank.com:8889/pay?callback='+functionName; // 一定要将script加进去 document.body.appendChild(script); script.onload = (e)=>{ // 每次动态创建script标签之后,都将script标签删掉 e.currentTarget.remove(); // 无论script标签加载成功或失败都需要将window[functionName]属性删除 delete window[functionName]; } script.onerror = ()=>{ alert('fail'); delete window[functionName]; } }) </script> </body> </html> 复制代码
对于frank.com的后端来讲,只需要这样做即可:
else if(path==='/pay'){ var amount = fs.readFileSync('./db','utf8') var newAmount = parseInt(amount) - 1; fs.writeFileSync('./db',newAmount); response.setHeader('Content-Type','application/javascript') response.statusCode = 200 // query为path后面的查询参数 response.write(` ${query.callback}.call(undefined,'success'); `) response.end() } 复制代码
frank.com的后端程序员只需要拿到查询参数中的callback的值,并调用此方法,而前端程序员通过后端传入的参数进行判断,这样就做到了低耦合高复用的代码。实际上,这就是 JSONP 。
什么是JSONP
JSONP是一种动态script标签跨域请求技术。指的是请求方动态创建script标签,src指向响应方的服务器,同时传一个参数callback,callback后面是一个随机生成的functionName,当请求方向响应方发起请求时,响应方根据传过来的参数callback,构造并调用形如:xxx.call(undefined,'你要的数据'),其中'你要的数据'的传入格式是以JSON格式传入的,因为传入的JSON数据具有左右padding,因而得名JSONP。后端代码构造并调用了xxx,浏览器接收到了响应,就会执行xxx.call(undefined,'你要的数据'),于是乎,请求方就知道了他要的数据,这就是JSONP。在知乎上,看到了有关于JSONP的回答:
其实就是这样。
jQuery的JSONP
我们首先需要引入jQuery,然后将代码中script标签里面的内容变为这样即可:
btn.addEventListener('click',function () { $.ajax({ url: "http://jack.com:8001/pay", // The name of the callback parameter, as specified by the YQL service jsonp: "callback", // Tell jQuery we're expecting JSONP dataType: "jsonp", // Tell YQL what we want and that we want JSON data: { q: "select title,abstract,url from search.news where query=\"cat\"", format: "json" }, // Work with the response success: function( response ) { if(response === 'success'){ amount.innerText = amount.innerText - 1; } } }); }) 复制代码
值得吐槽的一点是:调用jQuery的JSONP API里面出现了ajax这样的字眼,实际上JSONP和Ajax毛关系都没有。
以上所述就是小编给大家介绍的《浅谈JSONP》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
人工智能
腾讯研究院、中国信通院互联网法律研究中心、腾讯AI Lab、腾讯开放平台 / 中国人民大学出版社 / 2017-10-25 / 68.00元
面对科技的迅猛发展,中国政府制定了《新一代人工智能发展规划》,将人工智能上升到国家战略层面,并提出:不仅人工智能产业要成为新的经济增长点,而且要在2030年达到世界领先水平,让中国成为世界主要人工智能创新中心,为跻身创新型国家前列和经济强国奠定基础。 《人工智能》一书由腾讯一流团队与工信部高端智库倾力创作。本书从人工智能这一颠覆性技术的前世今生说起,对人工智能产业全貌、最新进展、发展趋势进行......一起来看看 《人工智能》 这本书的介绍吧!
JS 压缩/解压工具
在线压缩/解压 JS 代码
URL 编码/解码
URL 编码/解码