课前预习:《阮一峰:浏览器同源政策及其规避方法》
1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。
最初,它的含义是指,A网页设置的 Cookie,B网页不能打开,除非这两个网页"同源"。所谓"同源"指的是"三个相同"。
- 协议相同
- 域名相同
- 端口相同
同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
随着互联网的发展,"同源政策"越来越严格。目前,如果非同源,共有三种行为受到限制。
1.Cookie、LocalStorage 和 IndexDB 无法读取。
2.DOM 无法获得。
3.AJAX 请求不能发送。
数据库是什么鬼
- 文件系统是一种数据库
- 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参数
网友评论