JSONP
是什么
Jsonp
(JSON with Padding
) 是 json
的一种"使用模式",可以让网页从别的域名(网站)那获取资料,即跨域读取数据。
需求:点击按钮付款一块钱
<h5>您的账户余额是<span id="amount">&&&amount&&&</span></h5>
<button id="button">付款</button>
- 用
form
发请求
<form action="/pay" method="POST" target="result">
<input type="submit" value="付款">
</form>
<iframe name="result" src="about:black" frameborder="0" height="200"></iframe>
后端部分代码:
if(path === '/'){
let string=fs.readFileSync('./index.html','utf8')
let amount=fs.readFileSync('./db','utf8')
string=string.replace('&&&amount&&&',amount)
response.statusCode = 200
response.setHeader('Content-Type', 'text/html;charset=utf-8')
response.write(string)
response.end()
}else if (path === '/pay') {
let amount = fs.readFileSync('./db', 'utf8')
let number = amount - 1
if(Math.random() > 0.5){ //假设大于0.5就成功
fs.writeFile('./db', number)
response.write('success')
}else{
response.write('fail')
}
response.end()
}
form
表单提交之后一定会刷新当前页面,这样用户体验不好,利用 iframe
局部刷新页面,优化用户体验。
有没有想过,不返回 HTML
,返回 JS
- 方案一:用图片造
get
请求
浏览器有个特点一旦发现你在内存里创建了一个 img
,就会去请求这个 img
,这个方法可以悄无声息的创造一个请求,但有个缺陷无法设置 POST
。那就 发GET
请求吧,总比用 iframe
刷新页面好。
button.addEventListener('click', () => {
let image = document.createElement('img')
image.src = './pay'
image.onload = function(){
alert('付款成功')
amount.innerText = amount.innerText -1
}
image.onerror = function(){
alert('付款失败')
}
})
浏览器怎么知道一个图片请求是成功还是失败呢?状态码说的很清楚,假设如果成功就返回 200
,失败返回 400
if (path === '/pay') {
let amount = fs.readFileSync('./db', 'utf8')
let number = amount - 1
if(Math.random() > 0.5){
fs.writeFile('./db', number)
response.setHeader('Content-Type', 'image/png')
response.statusCode = 200
response.write(fs.readFileSync('./th.jpg'))
}else{
response.statusCode = 400
response.write('fail')
}
response.end()
}
这样就做到了无刷新的也没有用什么特殊的技术,只用了一个小技巧就是创建了一个 img
用它发请求。现在的问题就是无法 POST
,因为浏览器没有给我们一个接口让我们 POST
,所以那就只好 GET
了, 这种方法只能知道成功或失败,不能知道更多的数据。
- 方案二:用
script
造get
请求
这种方法一定要把 script
放到页面里面,浏览器才会发起请求。怎么样才能知道是请求成功还是失败了呢,当然 script
也有 onload
和 onerror
事件。
button.addEventListener('click', () => {
let script = document.createElement('script')
script.src = '/pay'
document.body.appendChild(script)
script.onload = function(){
alert('付款成功')
amount.innerText = amount.innerText -1
}
script.onerror = function(){
alert('付款失败')
}
}
改进之后的好处就是不用返回图片了,返回一个字符串就可以了,这样请求就会快一点了。
点击 button
后,就会去创建一个 script
标签会放在页面的最后方,由于页面中出现了一个 script
,浏览器就会将 /pay
里面的内容执行掉。这样就不需要去监听 onload
事件,就监听 onerror
事件就可以了。 onload
之间由 /pay
里面的内容执行,服务器直接返回了在浏览器执行的一个 js
字符串(代码)。
if (path === '/pay') {
let amount = fs.readFileSync('./db', 'utf8')
let number = amount - 1
if(Math.random() > 0.5){
fs.writeFile('./db', number)
response.setHeader('Content-Type', 'application/javascript')
response.statusCode = 200
response.write(`
alert("付款成功")
amount.innerText = amount.innerText -1
`)
}else{
response.statusCode = 400
response.write('fail')
}
response.end()
}
不过还有一个问题就是没点击一次按钮,页面中都会多出现一个 script
,虽然说不会出现 BUG,但是会很难看,所以在请求成功或失败之后再删除它。
button.addEventListener('click', () => {
let script = document.createElement('script')
script.src = '/pay'
document.body.appendChild(script)
script.onload = function(e){
e.currentTarget.remove()
}
script.onerror = function(e){
alert('付款失败')
e.currentTarget.remove()
}
})
这就是整个的完整方案,当用户点击一个动作的时候,生成一个 script
,然后 script
的 src
就是要请求的路径。然后把 script
放到页面里,这样浏览器就是去发起一个这样路径的 GET
请求(没办法 POST
)。如果这个请求成功了,那么它首先会执行那个服务器返回的 javascript
响应,这个响应就是操作页面的局部刷新。
这种技术就叫做 SRJ - Server Rendered JavaScript
,服务器返回的 JavaScript
。这个就是在 AJAX
出现之前用的无刷新局部更新页面内容。
上面的这个 SRJ
方案,如果没有做任何的安全措施的话,任何一个网站都可以去请求这个 API
操作付款,所以像付款这些重要的操作要使要 POST
,GET
太容易伪造了。
JSONP
上面都是都一个网站的前端和后端交流,那如果这个网站的前端想和另一个域名下的接口交流怎么办呢?
前端给后端一个函数,然后后端调用这个函数,要执行的代码不要管,然后返回一个结果。那后台怎么知道这个函数名呢,我们可以在请求的时候传参。
window.yyy = function(result) {
if(result === 'success'){
amount.innerText = amount.innerText - 1
}else{
}
}
button.addEventListener("click", () => {
let script = document.createElement("script");
script.src = 'http://jackma.com:8002/pay?callback=yyy'
document.body.appendChild(script)
script.onload = function(e) {
e.currentTarget.remove()
}
script.onerror = function(e) {
e.currentTarget.remove()
}
})
if (path === '/pay') {
let amount = fs.readFileSync('./db', 'utf8')
let number = amount - 1
fs.writeFile('./db', number)
response.setHeader('Content-Type', 'application/javascript')
response.statusCode = 200
response.write(`
${query.callback}.call(undefined, 'success')
`)
response.end()
}
给什么就调什么,这样就完全耦合了, 这就叫做 JSONP
。 JSONP
要结决的一个问题就是两个网站之间怎么交流,我们用一个 script
标签就可以交流了, script
标签是不受域名限制的。既然不受限制,就可以告诉对方网站我要请求一个数据,对方给数据之后再调用我们准备的函数,把参数传到函数的第一个参数里面,我们就可以得到了。
请求方:frank.com
的前端程序员(浏览器)
响应方:jack.com
的后端程序员(服务器)
-
请求方创建
script
,src
指向响应方,同时传一个查询参数?callback=yyy
yyy
一般是要随机数,这样就不用出现函数名重复的问题。 -
响应方根据查询参数
callback
,构造形如yyy.call(undefined, '你要的数据')
yyy('你要的数据')
这样的响应
-
浏览器接收到响应,就会执行
yyy.call(undefined, '你要的数据')
-
那么请求方就知道了他要的数据
这就是 JSONP
jQuery
的写法:
$.ajax({
url: "http://jack.com:8002/pay",
dataType: "jsonp",
success: function( response ) {
if(response === 'success'){
amount.innerText = amount.innerText - 1
}
}
})
JSONP
为什么不支持 POST
请求
因为
JSONP
是通过动态创建script
实现的,动态创建script
的时候只能用GET
请求,没有办法用POST
请求。