21.Ajax 与Comet(2)

4.跨源资源共享:通过XHR 实现Ajax 通信的一个主要限制,来源于跨域安全策略。默认情况下,XHR 对象只能访问与包含它的页面位于同一个域中的资源。这种安全策略可以预防某些恶意行为。但是,实现合理的跨域请求对开发某些浏览器应用程序也是至关重要的。

CORS(Cross-Origin Resource Sharing,跨源资源共享)是W3C 的一个工作草案,定义了在必须访问跨源资源时,浏览器与服务器应该如何沟通。CORS 背后的基本思想,就是使用自定义的HTTP 头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。

比如一个简单的使用GET 或POST 发送的请求,它没有自定义的头部,而主体内容是text/plain。在发送该请求时,需要给它附加一个额外的Origin 头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。下面是Origin 头部的一个示例:

Origin: http://www.nczonline.net

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

Access-Control-Allow-Origin: http://www.nczonline.net

如果没有这个头部,或者有这个头部但源信息不匹配,浏览器就会驳回请求。正常情况下,浏览器会处理请求。注意,请求和响应都不包含cookie 信息。

  • IE对CORS的实现:微软在IE8 中引入了XDR(XDomainRequest)类型。这个对象与XHR 类似,但能实现安全可靠的跨域通信。XDR 对象的安全机制部分实现了W3C 的CORS 规范。以下是XDR 与XHR 的一些不同之处。
  1. cookie 不会随请求发送,也不会随响应返回。
  2. 只能设置请求头部信息中的Content-Type 字段。
  3. 不能访问响应头部信息。
  4. 只支持GET 和POST 请求。

这些变化使CSRF(Cross-Site Request Forgery,跨站点请求伪造)和XSS(Cross-Site Scripting,跨站点脚本)的问题得到了缓解。被请求的资源可以根据它认为合适的任意数据(用户代理、来源页面等)来决定是否设置Access-Control- Allow-Origin 头部。作为请求的一部分,Origin 头部的值表示请求的来源域,以便远程资源明确地识别XDR 请求。

XDR对象的使用方法与XHR对象非常相似。也是创建一个XDomainRequest 的实例,调用open()方法,再调用send()方法。但与XHR 对象的open()方法不同,XDR 对象的open()方法只接收两个参数:请求的类型和URL。

所有XDR 请求都是异步执行的,不能用它来创建同步请求。请求返回之后,会触发load 事件,响应的数据也会保存在responseText 属性中,如下所示。

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

在接收到响应后,你只能访问响应的原始文本;没有办法确定响应的状态代码。而且,只要响应有效就会触发load 事件,如果失败(包括响应中缺少Access-Control-Allow-Origin 头部)就会触发error 事件。遗憾的是,除了错误本身之外,没有其他信息可用,因此唯一能够确定的就只有请求未成功了。要检测错误,可以像下面这样指定一个onerror 事件处理程序。

var xdr = new XDomainRequest();
xdr.onload = function(){
	alert(xdr.responseText);
};
xdr.onerror = function(){
	alert("An error occurred.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

鉴于导致XDR 请求失败的因素很多,因此建议你不要忘记通过onerror 事件处理程序来捕获该事件;否则,即使请求失败也不会有任何提示。

在请求返回前调用abort()方法可以终止请求:

xdr.abort(); //终止请求

与XHR 一样,XDR 对象也支持timeout 属性以及ontimeout 事件处理程序。下面是一个例子。

var xdr = new XDomainRequest();
xdr.onload = function(){
	alert(xdr.responseText);
};
xdr.onerror = function(){
	alert("An error occurred.");
};
xdr.timeout = 1000;
xdr.ontimeout = function(){
	alert("Request took too long.");
};
xdr.open("get", "http://www.somewhere-else.com/page/");
xdr.send(null);

这个例子会在运行1 秒钟后超时,并随即调用ontimeout 事件处理程序。

为支持POST 请求,XDR 对象提供了contentType 属性,用来表示发送数据的格式,如下面的例子所示。

var xdr = new XDomainRequest();
xdr.onload = function(){
    alert(xdr.responseText);
};
xdr.onerror = function(){
    alert("An error occurred.");
};
xdr.open("post", "http://www.somewhere-else.com/page/");
xdr.contentType = "application/x-www-form-urlencoded";
xdr.send("name1=value1&name2=value2");

这个属性是通过XDR 对象影响头部信息的唯一方式。

  • 其他浏览器对CORS的实现:Firefox 3.5+、Safari 4+、Chrome、iOS 版Safari 和Android 平台中的WebKit 都通过XMLHttpRequest对象实现了对CORS 的原生支持。在尝试打开不同来源的资源时,无需额外编写代码就可以触发这个行为。要请求位于另一个域中的资源,使用标准的XHR 对象并在open()方法中传入绝对URL 即可,例如:
var xhr = createXHR();
xhr.onreadystatechange = function(){
	if (xhr.readyState == 4){
		if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
			alert(xhr.responseText);
		} else {
			alert("Request was unsuccessful: " + xhr.status);
		}
	}
};
xhr.open("get", "http://www.somewhere-else.com/page/", true);
xhr.send(null);

与IE 中的XDR 对象不同,通过跨域XHR 对象可以访问status 和statusText 属性,而且还支持同步请求。跨域XHR 对象也有一些限制,但为了安全这些限制是必需的。以下就是这些限制。

  1. 不能使用setRequestHeader()设置自定义头部。
  2. 不能发送和接收cookie。
  3. 调用getAllResponseHeaders()方法总会返回空字符串。

由于无论同源请求还是跨源请求都使用相同的接口,因此对于本地资源,最好使用相对URL,在访问远程资源时再使用绝对URL。这样做能消除歧义,避免出现限制访问头部或本地cookie 信息等问题。

  • Preflighted Reqeusts:CORS 通过一种叫做Preflighted Requests 的透明服务器验证机制支持开发人员使用自定义的头部、GET 或POST 之外的方法,以及不同类型的主体内容。在使用下列高级选项来发送请求时,就会向服务器发送一个Preflight 请求。这种请求使用OPTIONS 方法,发送下列头部。
  1. Origin:与简单的请求相同。
  2. Access-Control-Request-Method:请求自身使用的方法。
  3. Access-Control-Request-Headers:(可选)自定义的头部信息,多个头部以逗号分隔。

以下是一个带有自定义头部NCZ 的使用POST 方法发送的请求。

Origin: http://www.nczonline.net
Access-Control-Request-Method: POST
Access-Control-Request-Headers: NCZ

发送这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部与浏览器进行沟通。

  1. Access-Control-Allow-Origin:与简单的请求相同。
  2. Access-Control-Allow-Methods:允许的方法,多个方法以逗号分隔。
  3. Access-Control-Allow-Headers:允许的头部,多个头部以逗号分隔。
  4. Access-Control-Max-Age:应该将这个Preflight 请求缓存多长时间(以秒表示)。
Access-Control-Allow-Origin: http://www.nczonline.net
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000

Preflight 请求结束后,结果将按照响应中指定的时间缓存起来。而为此付出的代价只是第一次发送这种请求时会多一次HTTP 请求。

支持Preflight 请求的浏览器包括Firefox 3.5+、Safari 4+和Chrome。IE 10 及更早版本都不支持。

  • 带凭据的请求:默认情况下,跨源请求不提供凭据(cookie、HTTP 认证及客户端SSL 证明等)。通过将withCredentials 属性设置为true,可以指定某个请求应该发送凭据。如果服务器接受带凭据的请求,会用下面的HTTP 头部来响应。
Access-Control-Allow-Credentials: true

如果发送的是带凭据的请求,但服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给JavaScript(于是,responseText 中将是空字符串,status 的值为0,而且会调用onerror()事件处理程序)。另外,服务器还可以在Preflight 响应中发送这个HTTP 头部,表示允许源发送带凭据的请求。

支持withCredentials 属性的浏览器有Firefox 3.5+、Safari 4+和Chrome。IE 10 及更早版本都不支持。

  • 跨浏览器的CORS:即使浏览器对CORS 的支持程度并不都一样,但所有浏览器都支持简单的(非Preflight 和不带凭据的)请求,因此有必要实现一个跨浏览器的方案。检测XHR 是否支持CORS 的最简单方式,就是检查是否存在withCredentials 属性。再结合检测XDomainRequest 对象是否存在,就可以兼顾所有浏览器了。
function createCORSRequest(method, url){
	var xhr = new XMLHttpRequest();
	if ("withCredentials" in xhr){
		xhr.open(method, url, true);
	} else if (typeof XDomainRequest != "undefined"){
		vxhr = new XDomainRequest();
		xhr.open(method, url);
	} else {
		xhr = null;
	}
	return xhr;
}
var request = createCORSRequest("get", "http://www.somewhere-else.com/page/");
if (request){
	request.onload = function(){
	//对request.responseText 进行处理
};
	request.send();
}

Firefox、Safari 和Chrome 中的XMLHttpRequest 对象与IE 中的XDomainRequest 对象类似,都提供了够用的接口,因此以上模式还是相当有用的。这两个对象共同的属性/方法如下。

  1. abort():用于停止正在进行的请求。
  2. onerror:用于替代onreadystatechange 检测错误。
  3. onload:用于替代onreadystatechange 检测成功。
  4. responseText:用于取得响应内容。
  5. send():用于发送请求。

以上成员都包含在createCORSRequest()函数返回的对象中,在所有浏览器中都能正常使用。

5.其他跨域技术:在CORS 出现以前,要实现跨域Ajax 通信颇费一些周折。开发人员想出了一些办法,利用DOM 中能够执行跨域请求的功能,在不依赖XHR 对象的情况下也能发送某种请求。虽然CORS 技术已经无处不在,但开发人员自己发明的这些技术仍然被广泛使用,毕竟这样不需要修改服务器端代码。

  • 图像Ping:上述第一种跨域请求技术是使用<img>标签。我们知道,一个网页可以从任何网页中加载图像,不用担心跨域不跨域。这也是在线广告跟踪浏览量的主要方式。正如第13 章讨论过的,也可以动态地创建图像,使用它们的onload 和onerror 事件处理程序来确定是否接收到了响应。

动态创建图像经常用于图像Ping。图像Ping 是与服务器进行简单、单向的跨域通信的一种方式。请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或204 响应。通过图像Ping,浏览器得不到任何具体的数据,但通过侦听load 和error 事件,它能知道响应是什么时候接收到的。来看下面的例子。

var img = new Image();
img.onload = img.onerror = function(){
    alert("Done!");
};
img.src = "http://www.example.com/test?name=Nicholas";

这里创建了一个Image 的实例,然后将onload 和onerror 事件处理程序指定为同一个函数。这样无论是什么响应,只要请求完成,就能得到通知。请求从设置src 属性那一刻开始,而这个例子在请求中发送了一个name 参数。

图像Ping 最常用于跟踪用户点击页面或动态广告曝光次数。图像Ping 有两个主要的缺点,一是只能发送GET 请求,二是无法访问服务器的响应文本。因此,图像Ping 只能用于浏览器与服务器间的单向通信。

  • JSONP:JSONP 是JSON with padding(填充式JSON 或参数式JSON)的简写,是应用JSON 的一种新方法,在后来的Web 服务中非常流行。JSONP 看起来与JSON 差不多,只不过是被包含在函数调用中的JSON,就像下面这样。
callback({ "name": "Nicholas" });

JSONP 由两部分组成:回调函数和数据。回调函数是当响应到来时应该在页面中调用的函数。回调函数的名字一般是在请求中指定的。而数据就是传入回调函数中的JSON数据。下面是一个典型的JSONP请求。

http://freegeoip.net/json/?callback=handleResponse

这个URL 是在请求一个JSONP 地理定位服务。通过查询字符串来指定JSONP 服务的回调参数是很常见的,就像上面的URL 所示,这里指定的回调函数的名字叫handleResponse()。

JSONP 是通过动态<script>元素来使用的,使用时可以为src 属性指定一个跨域URL。这里的<script>元素与<img>元素类似,都有能力不受限制地从其他域加载资源。因为JSONP 是有效的JavaScript 代码,所以在请求完成后,即在JSONP 响应加载到页面中以后,就会立即执行。来看一个例子。

function handleResponse(response){
	alert("You’re at IP address " + response.ip + ", which is in " +
response.city + ", " + response.region_name);
}
var script = document.createElement("script");
script.src = "http://freegeoip.net/json/?callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);

这个例子通过查询地理定位服务来显示你的IP 地址和位置信息。

JSONP 之所以在开发人员中极为流行,主要原因是它非常简单易用。与图像Ping 相比,它的优点在于能够直接访问响应文本,支持在浏览器与服务器之间双向通信。不过,JSONP 也有两点不足。

首先,JSONP 是从其他域中加载代码执行。如果其他域不安全,很可能会在响应中夹带一些恶意代码,而此时除了完全放弃JSONP 调用之外,没有办法追究。因此在使用不是你自己运维的Web 服务时,一定得保证它安全可靠。

其次,要确定JSONP 请求是否失败并不容易。虽然HTML5 给<script>元素新增了一个onerror事件处理程序,但目前还没有得到任何浏览器支持。为此,开发人员不得不使用计时器检测指定时间内是否接收到了响应。但就算这样也不能尽如人意,毕竟不是每个用户上网的速度和带宽都一样。

  • Comet

Comet 是Alex Russell发明的一个词儿,指的是一种更高级的Ajax 技术(经常也有人称为“服务器推送”)。Ajax 是一种从页面向服务器请求数据的技术,而Comet 则是一种服务器向页面推送数据的技术。Comet 能够让信息近乎实时地被推送到页面上,非常适合处理体育比赛的分数和股票报价。

有两种实现Comet 的方式:长轮询和流。长轮询是传统轮询(也称为短轮询)的一个翻版,即浏览器定时向服务器发送请求,看有没有更新的数据。

长轮询把短轮询颠倒了一下。页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求。这一过程在页面打开期间一直持续不断。

无论是短轮询还是长轮询,浏览器都要在接收数据之前,先发起对服务器的连接。两者最大的区别在于服务器如何发送数据。短轮询是服务器立即发送响应,无论数据是否有效,而长轮询是等待发送响应。轮询的优势是所有浏览器都支持,因为使用XHR 对象和setTimeout()就能实现。而你要做的就是决定什么时候发送请求。

第二种流行的Comet 实现是HTTP 流。流不同于上述两种轮询,因为它在页面的整个生命周期内只使用一个HTTP 连接。具体来说,就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。比如,下面这段PHP 脚本就是采用流实现的服务器中常见的形式。

<?php
    $i = 0;
    while(true){
        //输出一些数据,然后立即刷新输出缓存
        echo "Number is $i";
        flush();
        //等几秒钟
        sleep(10);
        $i++;
}

所有服务器端语言都支持打印到输出缓存然后刷新(将输出缓存中的内容一次性全部发送到客户端)的功能。而这正是实现HTTP 流的关键所在。

在Firefox、Safari、Opera 和Chrome 中,通过侦听readystatechange 事件及检测readyState的值是否为3,就可以利用XHR 对象实现HTTP 流。在上述这些浏览器中,随着不断从服务器接收数据,readyState 的值会周期性地变为3。当readyState 值变为3 时,responseText 属性中就会保存接收到的所有数据。此时,就需要比较此前接收到的数据,决定从什么位置开始取得最新的数据。使用XHR 对象实现HTTP 流的典型代码如下所示。

function createStreamingClient(url, progress, finished){
	var xhr = new XMLHttpRequest(),
	received = 0;
	xhr.open("get", url, true);
	xhr.onreadystatechange = function(){
		var result;
		if (xhr.readyState == 3){
			//只取得最新数据并调整计数器
			result = xhr.responseText.substring(received);
			received += result.length;
			//调用progress 回调函数
			progress(result);
		} else if (xhr.readyState == 4){
			finished(xhr.responseText);
		}
	};
	xhr.send(null);
	return xhr;
}

var client = createStreamingClient("streaming.php", function(data){
	alert("Received: " + data);
}, function(data){
	alert("Done!");
});

这个createStreamingClient()函数接收三个参数:要连接的URL、在接收到数据时调用的函数以及关闭连接时调用的函数。有时候,当连接关闭时,很可能还需要重新建立,所以关注连接什么时候关闭还是有必要的。

只要readystatechange 事件发生,而且readyState 值为3,就对responseText 进行分割以取得最新数据。这里的received 变量用于记录已经处理了多少个字符,每次readyState 值为3 时都递增。然后,通过progress 回调函数来处理传入的新数据。而当readyState 值为4 时,则执行finished 回调函数,传入响应返回的全部内容。

虽然这个例子比较简单,而且也能在大多数浏览器中正常运行(IE 除外),但管理Comet 的连接是很容易出错的,需要时间不断改进才能达到完美。浏览器社区认为Comet 是未来Web 的一个重要组成部分,为了简化这一技术,又为Comet 创建了两个新的接口。

  • 服务器发送事件:SSE(Server-Sent Events,服务器发送事件)是围绕只读Comet 交互推出的API 或者模式。SSE API用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。服务器响应的MIME类型必须是text/event-stream,而且是浏览器中的JavaScript API 能解析格式输出。SSE 支持短轮询、长轮询和HTTP 流,而且能在断开连接时自动确定何时重新连接。有了这么简单实用的API,再实现Comet 就容易多了。

支持SSE 的浏览器有Firefox 6+、Safari 5+、Opera 11+、Chrome 和iOS 4+版Safari。

1. SSE API:SSE 的JavaScript API 与其他传递消息的JavaScript API 很相似。要预订新的事件流,首先要创建一个新的EventSource 对象,并传进一个入口点:

var source = new EventSource("myevents.php");

注意,传入的URL 必须与创建对象的页面同源(相同的URL 模式、域及端口)。EventSource 的实例有一个readyState 属性,值为0 表示正连接到服务器,值为1 表示打开了连接,值为2 表示关闭了连接。

另外,还有以下三个事件。

  1. open:在建立连接时触发。
  2. message:在从服务器接收到新事件时触发。
  3. error:在无法建立连接时触发。

就一般的用法而言,onmessage 事件处理程序也没有什么特别的。

source.onmessage = function(event){
    var data = event.data;
    //处理数据
};

服务器发回的数据以字符串形式保存在event.data 中。

默认情况下,EventSource 对象会保持与服务器的活动连接。如果连接断开,还会重新连接。这就意味着SSE 适合长轮询和HTTP 流。如果想强制立即断开连接并且不再重新连接,可以调用close()方法。

source.close();

2. 事件流:所谓的服务器事件会通过一个持久的HTTP 响应发送,这个响应的MIME 类型为text/event-stream。响应的格式是纯文本,最简单的情况是每个数据项都带有前缀data:

data: foo
data: bar
data: foo
data: bar

对以上响应而言,事件流中的第一个message 事件返回的event.data 值为"foo",第二个message 事件返回的event.data 值为"bar",第三个message 事件返回的event.data 值为"foo\nbar"(注意中间的换行符)。对于多个连续的以data:开头的数据行,将作为多段数据解析,每个值之间以一个换行符分隔。只有在包含data:的数据行后面有空行时,才会触发message 事件,因此在服务器上生成事件流时不能忘了多添加这一行。

通过id:前缀可以给特定的事件指定一个关联的ID,这个ID 行位于data:行前面或后面皆可:

data: foo
id: 1

设置了ID 后,EventSource 对象会跟踪上一次触发的事件。如果连接断开,会向服务器发送一个包含名为Last-Event-ID 的特殊HTTP 头部的请求,以便服务器知道下一次该触发哪个事件。在多次连接的事件流中,这种机制可以确保浏览器以正确的顺序收到连接的数据段。

  • Web Sockets

要说最令人津津乐道的新浏览器API,就得数Web Sockets 了。Web Sockets 的目标是在一个单独的持久连接上提供全双工、双向通信。在JavaScript 中创建了Web Socket 之后,会有一个HTTP 请求发送到浏览器以发起连接。在取得服务器响应后,建立的连接会使用HTTP 升级从HTTP 协议交换为WebSocket 协议。也就是说,使用标准的HTTP 服务器无法实现Web Sockets,只有支持这种协议的专门服务器才能正常工作。

由于Web Sockets 使用了自定义的协议,所以URL 模式也略有不同。未加密的连接不再是http://,而是ws://;加密的连接也不是https://,而是wss://。在使用Web Socket URL 时,必须带着这个模式,因为将来还有可能支持其他模式。

使用自定义协议而非HTTP 协议的好处是,能够在客户端和服务器之间发送非常少量的数据,而不必担心HTTP 那样字节级的开销。由于传递的数据包很小,因此Web Sockets 非常适合移动应用。毕竟对移动应用而言,带宽和网络延迟都是关键问题。使用自定义协议的缺点在于,制定协议的时间比制定JavaScript API 的时间还要长。Web Sockets 曾几度搁浅,就因为不断有人发现这个新协议存在一致性和安全性的问题。Firefox 4 和Opera 11 都曾默认启用Web Sockets,但在发布前夕又禁用了,因为又发现了安全隐患。目前支持Web Sockets 的浏览器有Firefox 6+、Safari 5+、Chrome 和iOS 4+版Safari。

1. Web Sockets API:要创建Web Socket,先实例一个WebSocket 对象并传入要连接的URL:

var socket = new WebSocket("ws://www.example.com/server.php");

注意,必须给WebSocket 构造函数传入绝对URL。同源策略对Web Sockets 不适用,因此可以通过它打开到任何站点的连接。至于是否会与某个域中的页面通信,则完全取决于服务器。(通过握手信息就可以知道请求来自何方。)

实例化了WebSocket 对象后,浏览器就会马上尝试创建连接。与XHR 类似,WebSocket 也有一个表示当前状态的readyState 属性。不过,这个属性的值与XHR 并不相同,而是如下所示。

  1. WebSocket.OPENING (0):正在建立连接。
  2. WebSocket.OPEN (1):已经建立连接。
  3. WebSocket.CLOSING (2):正在关闭连接。
  4. WebSocket.CLOSE (3):已经关闭连接。

WebSocket 没有readystatechange 事件;不过,它有其他事件,对应着不同的状态。readyState的值永远从0 开始。

要关闭Web Socket 连接,可以在任何时候调用close()方法。

socket.close();

调用了close()之后,readyState 的值立即变为2(正在关闭),而在关闭连接后就会变成3。

2. 发送和接收数据:Web Socket 打开之后,就可以通过连接发送和接收数据。要向服务器发送数据,使用send()方法并传入任意字符串,例如:

var socket = new WebSocket("ws://www.example.com/server.php");
socket.send("Hello world!");

因为Web Sockets 只能通过连接发送纯文本数据,所以对于复杂的数据结构,在通过连接发送之前,必须进行序列化。下面的例子展示了先将数据序列化为一个JSON 字符串,然后再发送到服务器:

var message = {
	time: new Date(),
	text: "Hello world!",
	clientId: "asdfp8734rew"
};
socket.send(JSON.stringify(message));

接下来,服务器要读取其中的数据,就要解析接收到的JSON 字符串。

当服务器向客户端发来消息时,WebSocket 对象就会触发message 事件。这个message 事件与其他传递消息的协议类似,也是把返回的数据保存在event.data 属性中。

socket.onmessage = function(event){
	var data = event.data;
	//处理数据
};

与通过send()发送到服务器的数据一样,event.data 中返回的数据也是字符串。如果你想得到其他格式的数据,必须手工解析这些数据。

3. 其他事件:WebSocket 对象还有其他三个事件,在连接生命周期的不同阶段触发。

  1. open:在成功建立连接时触发。
  2. error:在发生错误时触发,连接不能持续。
  3. close:在连接关闭时触发。

WebSocket 对象不支持DOM 2 级事件侦听器,因此必须使用DOM 0 级语法分别定义每个事件处理程序。

var socket = new WebSocket("ws://www.example.com/server.php");
socket.onopen = function(){
    alert("Connection established.");
};
socket.onerror = function(){
    alert("Connection error.");
};
socket.onclose = function(){
    alert("Connection closed.");
};

在这三个事件中,只有close 事件的event 对象有额外的信息。这个事件的事件对象有三个额外的属性:wasClean、code 和reason。其中,wasClean 是一个布尔值,表示连接是否已经明确地关闭;code 是服务器返回的数值状态码;而reason 是一个字符串,包含服务器发回的消息。可以把这些信息显示给用户,也可以记录到日志中以便将来分析。

socket.onclose = function(event){
    console.log("Was clean? " + event.wasClean + " Code=" + event.code + " Reason=" + event.reason);
};
  • SSE与Web Sockets:

面对某个具体的用例,在考虑是使用SSE 还是使用Web Sockets 时,可以考虑如下几个因素。首先,你是否有自由度建立和维护Web Sockets 服务器?因为Web Socket 协议不同于HTTP,所以现有服务器不能用于Web Socket 通信。SSE 倒是通过常规HTTP 通信,因此现有服务器就可以满足需求。

第二个要考虑的问题是到底需不需要双向通信。如果用例只需读取服务器数据(如比赛成绩),那么SSE 比较容易实现。如果用例必须双向通信(如聊天室),那么Web Sockets 显然更好。别忘了,在不能选择Web Sockets 的情况下,组合XHR 和SSE 也是能实现双向通信的。

6.安全

讨论Ajax 和Comet 安全的文章可谓连篇累牍,而相关主题的书也已经出了很多本了。大型Ajax 应用程序的安全问题涉及面非常之广,但我们可以从普遍意义上探讨一些基本的问题。

首先,可以通过XHR 访问的任何URL 也可以通过浏览器或服务器来访问。下面的URL 就是一个例子。

/getuserinfo.php?id=23

如果是向这个URL 发送请求,可以想象结果会返回ID 为23 的用户的某些数据。谁也无法保证别人不会将这个URL 的用户ID 修改为24、56 或其他值。因此,getuserinfo.php 文件必须知道请求者是否真的有权限访问要请求的数据;否则,你的服务器就会门户大开,任何人的数据都可能被泄漏出去。

对于未被授权系统有权访问某个资源的情况,我们称之为CSRF(Cross-Site Request Forgery,跨站点请求伪造)。未被授权系统会伪装自己,让处理请求的服务器认为它是合法的。受到CSRF 攻击的Ajax程序有大有小,攻击行为既有旨在揭示系统漏洞的恶作剧,也有恶意的数据窃取或数据销毁。

为确保通过XHR 访问的URL 安全,通行的做法就是验证发送请求者是否有权限访问相应的资源。有下列几种方式可供选择。

  • 要求以SSL 连接来访问可以通过XHR 请求的资源。
  • 要求每一次请求都要附带经过相应算法计算得到的验证码。

请注意,下列措施对防范CSRF 攻击不起作用。

  • 要求发送POST 而不是GET 请求——很容易改变。
  • 检查来源URL 以确定是否可信——来源记录很容易伪造。
  • 基于cookie 信息进行验证——同样很容易伪造。

XHR 对象也提供了一些安全机制,虽然表面上看可以保证安全,但实际上却相当不可靠。实际上,前面介绍的open()方法还能再接收两个参数:要随请求一起发送的用户名和密码。带有这两个参数的请求可以通过SSL 发送给服务器上的页面,如下面的例子所示。

xhr.open("get", "example.php", true, "username", "password"); //不要这样做!!

即便可以考虑这种安全机制,但还是尽量不要这样做。把用户名和密码保存在JavaScript 代码中本身就是极为不安全的。任何人,只要他会使用JavaScript 调试器,就可以通过查看相应的变量发现纯文本形式的用户名和密码。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值