Ajax和跨域

Ajax技术的出现,使浏览器能够像服务器请求额外的数据而无须重新加载界面,带来了更好的用户体验。Ajax技术的核心是XMLHttpRequest对象(简称XHR)。

XMLHttpRequest对象

创建

创建一个XHR对象非常简单,只需要使用XMLHttpRequest构造函数即可:

1
var xhr = new XMLHttpRequest()

在IE7之前的IE版本中,使用的是ActiveXObject构造函数来创建,不过现在应该大多数应用都不会兼容这么老版本的浏览器了,如果业务需要的话,自行查找相关内容就好了,这里就不在赘述了。

启动一个请求,最少需要两行代码:

1
2
xhr.open("get", "example.htpl", false)
xhr.send(null)

第一句,开启一个Ajax请求,第一个参数是请求的方法(”get”,”post”等),第二个参数是请求的链接,第三个参数表示是否异步发送请求。

第二句,发送请求,send() 方法接受一个参数,作为请求主体发送的数据,如果不需要通过请求主体发送数据,则必须传入null,因为这个参数对有些浏览器来说是必须的,所以为了保险起见,还会加上为好。调用send()之后,请求就会被发送到服务器。

需要注意的是,以上代码为同步请求,所以JavaScript代码会等待服务器响应之后再继续执行。如果需要异步,open的第三个参数设为true就可以了。

当收到响应后,响应的数据会自动填充XHR对象的属性:

  • responseText:作为响应主体被返回的文本。
  • responseXML:如果响应的内容类型是”text/xml”或”application/xml”,这个属性中将保存包含着响应数据的XML DOM文档。
  • status:响应的HTTP状态。
  • statusText:HTTP状态的说明。

设置HTTP头部信息

可以使用setRequestHeader()方法来设置自定义的请求头部信息:

1
xhr.setRequestHeader("headerKey", "headerValue")

接收数据

同步

同步接收数据就非常简单了,上面已经提到过,等待服务器响应之后,填充XHR对象,我们只要读取XHR对象中的属性就可以接受响应数据了:

1
2
3
4
5
6
7
8
xhr.open("get", "example.htpl", false)
xhr.send(null)

if ((xhr.status >= 200 && xhr.status <= 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("failed :", xhr.status)
}

关于返回的状态码含义,后面将会有文章介绍。

异步

接收异步请求的数据,我们可以使用load事件。当响应接收完毕之后将会触发load事件,load事件会接收到一个event对象,其target属性就是指向XHR对象实例,因此可以访问到XHR对象的所有方法和属性,但是并不是所有浏览器都实现了这个event对象,所以使用的时候最好还是读取XHR对象的属性:

1
2
3
4
5
6
7
8
9
10
xhr.open("get", "example.htpl", true)
xhr.send(null)

xhr.onload = function() {
if ((xhr.status >= 200 && xhr.status <= 300) || xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("failed :", xhr.status)
}
}

取消请求

在接收到响应之前可以使用abort()方法来取消异步请求:

1
xhr.abort()

跨域

通过XHR实现Ajax通信的一个主要限制,就是跨域安全策略。

XHR对象只能访问与包含它的页面位于同一个域中的资源。这种同源策略是浏览器为了防止一些恶意行为而制定的。但是有时候,我们需要请求不同源服务器的接口怎么办呢?接下来就是讲解跨域的几个解决方案。

JSONP (Json with padding)

这种方法的原理是:<script>标签能够不受限制的从其他域加载资源,通过这个原理,动态的加载<script>标签(请求接口),来实现跨域的服务器请求。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
function (response) {
console.log(response)
}

var script = document.createElement("script")
script.src = "http://another.com/api/json/?callback=handleResponse"
document.body.insertBefor(script, document.body.firstChild)


<script>
handleResponse({name: 'jack'})
</scrpit>

<script>标签的src中写入想要请求的接口,并且一般都会带上一个类似于callback的参数和需要的其他参数,而服务器返回的也是一段JavaScript代码,并且带上了客户端想要的数据,加载到DOM上之后,被直接执行,调用了response函数,这样就实现了一个从请求到响应的完整流程,而且完成了跨域的需求。

当然还有很多标签能够实现跨域请求,如<img>这里就不一一列举了。

Web Sockets

web Sockets是不受跨域限制的,所以web Sockets也是跨域的一个解决方案的选择。

API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var socket = new WebSocket("wss://www.example.com/server") // 创建
socket.onopen = function() { // 当成功建立连接时触发
ws.send("hello") // 发送消息
}

socket.onmessage = function(event) { // 监听服务端返回的消息
var data = event.data
}

socket.onerror = function(err) { //在发生错误时触发,连接不能继续

}

socket.onclose = function(event) { // 在连接关闭时触发

}

socket.close() // 关闭连接

api比较简单,能熟练使用以上api就已经基本上掌握了。

CORS (跨域源资源共享)

CORS的基本原理,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求和响应是否成功。

也就是说,在我们发送一个请求是,给它额外的附加一个Origin头部,其中包含请求页面的源信息(协议、域名、端口),一遍服务器根据这个头部信息来决定是否给予响应,如:

1
Origin: http://www.myWeb.com

如果服务器认为这个请求可以接受,就会在Access-Control-Allow-Origin头部中回发相同的源信息(如果是公共的资源,可以回发” * “)。例如:

1
Access-Control-Allow-Origin: http://www.myWeb.com

如果没有这个头部,或者这个头部源信息不匹配,那么浏览器就会拒绝请求。

postMessage

postMessage是新兴的api,可以实现安全的跨域通信,具体的api使用请看window.postMessage

这种方式可以对于两个页面的脚本之间进行跨域通信。

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/*
* A窗口的域名是<http://example.com:8080>,以下是A窗口的script标签下的代码:
*/

var popup = window.open(...popup details...);

// 如果弹出框没有被阻止且加载完成

// 这行语句没有发送信息出去,即使假设当前页面没有改变location(因为targetOrigin设置不对)
popup.postMessage("The user is 'bob' and the password is 'secret'",
"https://secure.example.net");

// 假设当前页面没有改变location,这条语句会成功添加message到发送队列中去(targetOrigin设置对了)
popup.postMessage("hello there!", "http://example.org");

function receiveMessage(event)
{
// 我们能相信信息的发送者吗? (也许这个发送者和我们最初打开的不是同一个页面).
if (event.origin !== "http://example.org")
return;

// event.source 是我们通过window.open打开的弹出页面 popup
// event.data 是 popup发送给当前页面的消息 "hi there yourself! the secret response is: rheeeeet!"
}
window.addEventListener("message", receiveMessage, false);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/*
* 弹出页 popup 域名是<http://example.org>,以下是script标签中的代码:
*/

//当A页面postMessage被调用后,这个function被addEventListenner调用
function receiveMessage(event)
{
// 我们能信任信息来源吗?
if (event.origin !== "http://example.com:8080")
return;

// event.source 就当前弹出页的来源页面
// event.data 是 "hello there!"

// 假设你已经验证了所受到信息的origin (任何时候你都应该这样做), 一个很方便的方式就是把enent.source
// 作为回信的对象,并且把event.origin作为targetOrigin
event.source.postMessage("hi there yourself! the secret response " +
"is: rheeeeet!",
event.origin);
}

window.addEventListener("message", receiveMessage, false);

HASH

首先什么是hash呢?

一个链接:http://www.ep.com/index#data,而#后面的字符串就是所谓的hash。那么怎么通过hash来进行跨域通信呢?

首先改变页面的hash,页面是不会刷新的,这一点非常重要!在这一点的前提下,我们来通过代码演示:

1
2
3
4
5
6
7
8
// 场景是当前页面A 通过iframe或frame嵌入了跨域的页面B
var B = document.getElementByTagName("iframe")
B.src = B.src + '#' + 'data' // 将需要传送的数据放入hash中

// 在B中脚本
window.onhashchange = function() { // 监听hash改变
var data = window.location.hash // 获取A页面传送的数据
}

这样B页面就能得到A页面传过来的数据(示例中比较简单,如果实际使用,得到的数据需要做处理)了,也就实现了跨域通信。