内容简介:同源政策很简单,它的含义是指两个网页:一旦以上三点中有任意一点不同,两个网站都不能称为同源。举例:同源政策的目的其实就是为了保证用户信息的安全,防止恶意的网站数据窃取。 在阮一峰的博客中,在同源政策一节中对其作用描述如下:
同源政策很简单,它的含义是指两个网页:
- 协议相同
- 域名相同
- 端口相同
一旦以上三点中有任意一点不同,两个网站都不能称为同源。举例:
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》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Big Java Late Objects
Horstmann, Cay S. / 2012-2 / 896.00元
The introductory programming course is difficult. Many students fail to succeed or have trouble in the course because they don't understand the material and do not practice programming sufficiently. ......一起来看看 《Big Java Late Objects》 这本书的介绍吧!