原始时代
在最早的时候,当我们需要向服务器提交请求时,用的是<form>
表单提交到服务器,服务器接收到请求并且返回响应后,浏览器的页面是会刷新的。也就是说我们每次更新一个数据,都需要刷新当前页面一次,这种行为是无法忍受的。
为了解决这个问题,前辈们最先想到的方法就是使用一个<iframe>
,将刷新结果放在<iframe>
中显示,这样我们页面浏览就不会受到太大的影响。
<form action="/pay" method="POST" target="result">
<input type="submit" value="付款">
</form>
<iframe src="about:blank" frameborder="0" height="200px" name="result"></iframe>
<iframe>
的各种缺点就不提了,随着技术的发展与进步现在几乎没有多少网页会再使用<iframe>
了。
于是乎为了避免使用<iframe>
,前辈们又思考是否可以不用<form>
发送请求呢?
我们发现<img>
<link>
<a>
,这些标签都是通过向服务器发送请求以获取资源的,也就是说我们可以用这些标签代替<form>
向服务器发送一个请求。
用img构建GET请求
img的加载就是一个GET请求,如果我们用img来加载一个假的图片的话,就可以变相伪造一个请求了。
<button id="button">付款</button>
<script>
button.onclick = ()=> {
let img = document.createElement("img");
img.src = "/pay"
img.onload = ()=> {
alert("付款成功")
}
img.onerror = () => {
alert("付款失败")
}
}
</script>
这样我们就可以通过动态创建一个img设置它的src为服务器请求地址,来构造一个GET请求(使用这种方法无法产生POST请求)。这样后端再做一些处理(例如当成功时返回一个1x1的透明图片),前端就可以通过onload和oneror来判断请求成功与否。
用script构建GET请求
与<img>
一样,<script>
也可以通过src来访问服务器。
<button id="button">付款</button>
<script>
button.onclick = ()=> {
let spt = document.createElement("script");
spt.src = "/pay";
document.body.appendChild(spt);
spt.onload = ()=> {
alert("付款成功");
}
spt.onerror = () => {
alert("付款失败");
}
}
</script>
与<img>
不同的是,动态创建的<script>
必须插入到html中才能生效。
与上面一种方法相比较,动态创建js的方式不需要添加一个无谓的图片来浪费,但是js也是有自己的副作用的,那就是服务器返回的js代码一定会执行(因为它已经添加的html中了),由此我们可以延伸出直接通过后端来响应页面的成功请求。
我们前端就不需要像之前那样监听onload事件了,因为后端返回的<script>
会先执行,我们在那上面执行成功代码就可以了。
因为每次响应都会在html添加一个<script>
非常不美观,所以我们在onload里把至这些新插入的标签移除就可以了。
button.onload = (e)=>{
e.currentTarget.remove();
}
这种方案通过动态创建<script>
来构造GET请求的方法被叫做SRJ, server render javascript。
JSONP跨站请求
我们发现,使用html的带有src属性的标签都可以构造一个GET请求(不能构造POST),并且这个请求是可以跨站的。
于是,基于SRJ便诞生出了利用<script>
来构建跨站请求的方法JSONP:JSON with PADDING
虽然名字里带了JSON但这个JSONP和JSON没有半毛钱关系,不要误会了,JSONP是一种跨站请求方案的名字。
那么什么是JSONP呢?
我们前端发送跨站请求时,查询参数附上一个我们前端定好的函数。
<script>
var fuckBrowser = (response)=>{
if (response === 'sucess'){
console.log("scuess");
}
}
var script = document.createElement("script");
script.src = 'http://www.domain.com/?callbackName=fuckBrowser'
</script>
对面的后端接收的GET请求后作出判断,若成功,则发送响应,这个响应的内容就是让我们这边的前端执行查询参数上的函数。
//后端响应
if(响应成功条件满足){
//这里后面的callback就是url里的?callback=''
var callbackFunctionName = request.query.callback;
//这样后端就可以知道前端定好的函数是fuckBrowser
// callbackFunctionName === fuckBrowser /*true*/
response.write('callbackFunctionName.call(undefined, 'scuess')');
//这样前端就会执行fuckBrowser并且第二个参数时scuess
}
这就是一个简单JSONP例子,这样就可以达到前后端分离的跨站请求,不过我们还是要遵循一些业界的规定。
- 这个回调函数的参数名统一命名为callback
- callback的函数名为随机数
上面的后端如果返回的不是’scuess’而是一个json对象,那么这就符合最初JSONP设计的初衷了,不过现在的JSONP更多指的是这样一种跨站请求解决方法的形式。
JSONP总结
JSONP的全称是 json with padding,它是利用SRJ(服务器响应创建js)技术来达成发送跨站请求。
- 前端首先定义好一个用来接收后端数据的functionName(参数1, 参数2)
- 前端在发送查询请求的URL中指定一个callback参数
?callback=fuctionName
,并且这个functionName应该是由随机数组成(防止重名与缓存) - 后端通过request解析callback的名字(后端并不需要知道它叫什么)
- 后端返回一个response,这个response是要求前端执行在1中前端定义好的functionName也就是传进来的callback
- 4中执行的callback的参数是由后端的数据提供的,可以是字符串也可以是JSON
这个流程就是JSONP,解决跨站请求的方案