写在前面
截止到目前,《JavaScript高级程序设计》这本书我基本已经看完了,但是学习的过程总是少不了不断地回顾,我原以为我掌握了的知识,实际上手操练的时候却发现依然有很多问题。于是我决定将跨域常用的两种方式总结下来,并且附上自己的代码,希望能在加深自己对知识的理解的同时,也为大家提供便利。废话不多说,让我们进入正题。
大家在使用Ajax技术的时候肯定多少都遇到过跨域问题,可能有的同学还不自知。跨域安全策略是通过xhr对象实现Ajax通信的主要限制,默认情况下xhr对象只能访问同源资源(协议,域名,端口完全相同)。只要有一个不同,请求都不能正常完成。
1.利用CORS实现跨域:
CROS(跨域资源共享)是W3C的一个工作草案,定义了在必须访问跨域资源时,浏览器与服务器应该如何沟通。其背后的思想在于使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或相应是应该成功还是失败。
CORS的实现需要浏览器和服务器双方同时支持,但主要还是在后端进行操作。浏览器检测到跨域请求后会自动为其添加额外的Orgin头部,其中包含请求页面的源信息(协议,域名,端口等),以便于服务器根据这个头部信息来决定是否给予回应。而前端的请求代码并不需要改变。
这里我们首先利用Nodejs的express框架创建一个后台服务程序:
var express = require('express')
var app = express()
app.get('/cors_xhr', function(req, resp){
resp.end('这是你要的数据')
})
app.listen(3000, function(){
console.log('Server running at http://127.0.0.1:3000/');
})
这里部署了一个get请求的接口,访问该接口时返回数据。
接下来让我们创建前端页面:
<html>
<head>
<title>跨域接口测试</title>
</head>
<body>
</body>
<script>
//跨域资源共享
var xhr = new XMLHttpRequest()
xhr.onreadystatechange = function(){
if(xhr.readyState == 4 && xhr.status == 200){
console.log(xhr.responseText)
}
}
xhr.onerror = function(){
console.error("请求错误")
}
xhr.open('get', 'http://localhost:3000/cors_xhr', true)
xhr.send(null)
</script>
</html>
运行发现,控制台报错:
一看到Access-Control-Allow-Origin字样就知道是跨域问题。那么如何解决呢?
方法其实很简单,只需要服务端在返回数据之前加一行代码:
resp.writeHead(200, {'Access-Control-Allow-Origin':'http://127.0.0.1:8848'})
注意http://127.0.0.1:8848是该项目中html页面所部署的端口,即发送请求的页面的源信息。
最终代码为:
var express = require('express')
var app = express()
app.get('/cors_xhr', function(req, resp){
// 这是添加的一行代码,设置头部信息
resp.writeHead(200, {
'Access-Control-Allow-Origin':'http://127.0.0.1:8848'
})
resp.end('这是你要的数据')
})
app.listen(3000, function(){
console.log('Server running at http://127.0.0.1:3000/');
})
如前面提到的,如果服务器认为请求可以接受,就在Access-Control-Allow-Origin头部中回发相同的源信息,即请求页面的源信息。当然,如果是公共资源的话,可以回发“ * ”。如下:
resp.writeHead(200, {'Access-Control-Allow-Origin':'*'})
这样便可以实现跨域请求,但是要注意安全问题,毕竟跨域安全策略就是为了预防一些恶意行为。如果没有这个头部,或者有这个头部但是源信息不匹配,浏览器就会驳回请求。
执行以上代码查看控制台可以看到:
于是乎,跨域请求成功了,怎么样,也没有想象中那么难吧?
其实跨域请求都是可以发送成功的,只是返回的时候因为源信息不匹配导致浏览器拦截掉了。即请求可以发送成功,但响应被拦截。这会导致一个问题,有的请求携带参数可能会对后端的数据造成影响。所以在有些情况下会多出一次附加请求,在确认可以跨域的前提下,再发送主请求来获取或更改数据。这便是Preflighted Requests(预检请求),是一种透明服务器验证机制,感兴趣的小伙伴可以去了解下哦。
2.JSONP技术:
JSONP技术又称为填充式JSON或参数式JSON,是应用JSON的一种新方法,在Web服务中非常流行,这是开发者们通过自己的聪明才智而发明出的一种方式。我们知道通过<script>元素的src属性访问资源可以不受同源策略的影响,于是这种方法便应运而生。
其实类似的方法还有一种,叫做图像Ping,是利用<img>元素的src属性来实现的,但是这种方法局限性大,不能访问服务器的响应文本,只适合一些与服务器进行的简单的单行跨域通信。有兴趣的朋友们可以自己去了解下。
JSONP看起来与JSON差不多,只不过是被包含在函数调用中的JSON,它由两部分组成:回调函数和数据。其实现的大致过程是这样的:
- 前端利用<script>元素通过参数将回调函数名传给后端,类似于
http://localhost:3000/jsonp?callback=handler
而handler则是本地已经定义好的回调函数的函数名 - 后端解析字符串,获得callback参数的值,并返回一段函数调用的字符串
- 前端接收到这段字符串之后,由于是<script>元素,所以这段字符将被当作脚本执行,调用本地定义的构造函数并传入参数,于是我们便获得了想要的数据
接下来让我们看看具体的代码实现:
var express = require('express')
var app = express()
app.get('/jsonp', function(req, resp){
// 这部分主要是提取参数,大家知道怎么提取,采用的方式可以很多样化
// 后端的核心在于提取出callback参数的值并返回函数调用字符串
var params = url.parse(req.url).query, //获取请求参数字符串
query = params.split("&"), //将所有参数分离
queryMap = new Map()
// 提取参数,并以键值对方式保存
for(var i=0; i<query.length; i++){
var map = query[i].split("="),
key = map[0],
value = map[1]
queryMap[key] = value
}
//这是你要发送的数据
var data = {
name: "白居易",
age: 20
}
// 由于需要返回字符串,这里需要将数据转化为JSON类型
data = JSON.stringify(data)
// 构造返回的函数调用字符串
// 这里采用es6写法,不懂的朋友可以了解一下es6关于字符串的知识哦
var back = `${queryMap['callback']}(${data})`
// 结束请求并返回函数调用字符串
resp.end(back)
})
app.listen(3000, function(){
// 控制台会输出以下信息
console.log('Server running at http://localhost:3000/');
})
接下来是前端代码部分,我们有两种方式可以选择:
一种是直接在网页内嵌入<script>元素,另一种是动态插入<script>元素
<!DOCTYPE html>
<html>
<head>
<title>跨域接口测试</title>
</head>
<body>
</body>
<script>
//JSONP回调函数
function handler(result){
console.log(result)
}
//采用动态插入的方式
var script = document.createElement("script")
script.src = "http://localhost:3000/jsonp?callback=handler"
document.body.appendChild(script)
</script>
<!-- 采用直接插入的方式 -->
<script src="http://localhost:3000/jsonp?callback=handler"></script>
</html>
这两种方式都可以实现,一般我们采用动态插入的方式,这样可以在我们需要的时候再发送请求。下面让我们运行看看效果
可以看到两种方式都能成功。
跨域的实现还有一些方法,但是这两种比较常用,所以这里重点讲解这两种方式,如果这篇文章对你产生了帮助,记得帮我点个赞哈。