红宝书系列读书笔记(八)

Ajax与Comet

1.XMLHttpRequest对象
IE5是第一款引入XHR对象的浏览器,但XHR对象是通过MSXML库中的一个ActiveX对象实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//适用于IE7之前的版本
function createXHR(){
if(typeof arguments.callee.activeXString != "string"){
var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],
                i,len;
        for(i = 0,len = versions.length; i < len; i++){
            try{
                new ActiveXObject(version[i]);
                arguments.callee.activeXString = versions[i];
                break;
            }catch(ex){
                //...
            }
}
return new ActiveXObject(arguments.callee.activeXString);
}
return new ActiveXObject(arguments.callee.activeXString);
}

兼容各个浏览器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function createXHR(){
    if(typeof XMLHttpRequest != "undefined"){
        return new XMLHttpRequest();
    }else if(typeof ActiveXObject != "undefined")
        if(typeof arguments.callee.activeXString != "string"){
            var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"],
                    i,len;
            for(i = 0,len = versions.length; i < len; i++){
                try{
                    new ActiveXObject(version[i]);
                    arguments.callee.activeXString = versions[i];
                    break;
                }catch(ex){
                    //...
                }
            }
        }
        return new ActiveXObject(arguments.callee.activeXString);
    }else{
        throw new Error("No XHR object available.");
    }
}

2.XHR的用法
在使用XHR对象时,调用的一个方法是open(),它接收三个参数:要发送的请求的类型(“get”、“post”等)、
请求的URL和表示是否异步发送请求的布尔值。

1
xhr.open("get","example.php",false);

如果要发送特定的请求,必须使用send()方法:

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

send()方法接收一个参数,即要作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入null.

在收到响应后,响应的数据会自动填充XHR对象的属性:
responseText: 作为响应主体被返回的文本。
responseXML: 如果响应的内容类型是“text/xml”或“application/xml”,这个属性中将保存包含着响应数据的XML DOM文档。
status: 响应的HTTP状态。
statusText: HTTP状态的说明。

在接收到响应后,第一步是检查status属性,以确定响应已成功返回。
状态码为304表示请求的资源并没有被修改,可以直接使用浏览器中缓存的版本。

1
2
3
4
5
6
7
8
xhr.open("get","example.php",false);
xhr.send(null);
if(xhr.status >=200 &&xhr.status <300 ||xhr.status==304){
    alert(xhr.responseText);
}else{
    alert("Request was unsuccessful:"+xhr.status);
}

在多数情况下,我们需要发送异步请求,才能让Javascript继续执行而不必等待响应。所以需要检测XHR对象的readyState属性,该属性表示请求/响应过程的当前活动阶段,有以下值:
0:未初始化。尚未调用open()方法。
1:启动。已经调用open()方法,但尚未调用send()方法
2:发送。已经调用send()方法,但尚未接收到响应
3:接收。已经接收部分响应数据。
4:完成。已经接收到全部响应数据,而且已经可以在客户端使用。

当readyState值改变时,将触发readyStatechange事件。

1
2
3
4
5
6
7
8
9
10
var xhr = createXHR();
xhr.onreadyStatechange = function(){
    if(xhr.readyState == 4){
        alert(xhr.responseText);
    }else{
        alert("Request was unsuccessful:"+xhr.status);
    }
}
xhr.open("get","example.php",true);
xhr.send(null);

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

3.HTTP头部信息
Accept:浏览器能够处理的内容类型
Accept-Charset:浏览器能够显示的字符集
Accept-Encoding:浏览器能够处理的压缩编码
Accept-Language:浏览器当前设置的语言
Connection:浏览器与服务器之间连接的类型
Cookie:当前页面设置的任何Cookie
Host:发出请求的页面所在的域
Referer:发出请求的页面URL
User-Agent:浏览器的用户代理字符串

可以使用setRequestHeader()方法设置自定义的请求头部信息。
这个方法接收两个参数:头部字段的名称和头部字段的值。
要想成功发送请求头部信息,必须在调用open()方法之后且调用send()方法之前调用setRequestHeader()。

获取头部信息,可以使用XHR对象的getResponseHeader()方法并传入头部字段名称,可以取得相应的响应头部信息。
而调用getAllResponseHeaders()方法则可以取得一个包含所有头部信息的长字符串。

4.GET请求
GET是最常见的请求类型,最常用于向服务器查询某些信息。
使用GET请求经常发生的一个错误,就是查询字符串的格式有问题。
下面这个函数可以辅助向现有URL的末尾添加查询字符串参数:

1
2
3
4
5
function addURLParam(url, name, value){
    url += (url.indexOf("?") == -1 ? "?" : "&");
    url += encodeURIComponent(name)+ "=" + encodeURIComponent(value);
    return url;
}

5.POST请求
通常用于向服务器发送应该被保存的数据。

XMLHttpRequest 2级

1.FormData
为了实现表单数据的序列化,XMLHttpRequest 2级为此定义了FromData类型。FormData为序列化表单以及创建与表单格式相同的数据(用于通过XHR传输)提供了便利。

1
2
var data = new FormData();
data.append("name","Peter");

创建了一个FormData表单对象,通过append()方法向表单中传入表单字段的名字和字段中包含的值。
也可以通过向FormData构造函数中传入表单元素。

1
2
3
4
5
6
7
8
9
10
11
var xhr = createXHR();
xhr.onreadyStatechange = function(){
    if(xhr.readyState == 4){
        alert(xhr.responseText);
    }else{
        alert("Request was unsuccessful:"+xhr.status);
    }
}
xhr.open("post","postexample.php",true);
var form = document.getElementById("user-info");
xhr.send(new FormData(form));

2.超时设定(仅仅IE8支持)
IE8为XHR对象添加了一个timeout属性,表示请求在等待响应多少毫秒之后就终止。
在给timeout设置一个数值后,如果在规定时间内浏览器还没有接收到响应,那么就会触发timeout事件,进而调用ontimeout事件处理程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var xhr = createXHR();
xhr.onreadyStatechange = function(){
    if(xhr.readyState == 4){
        try{         
            if(xhr.status >=200 &&xhr.status <300 ||xhr.status==304){
                alert(xhr.responseText);
            }else{
                alert("Request was unsuccessful:"+xhr.status);
            }
        }catch(ex){
        
        }
    }
};
xhr.open("get","example.php",true);
xhr.timeout = 1000;
xhr.ontimeout = function(){
    alert("Request did not return in a second.");
}
xhr.send(null);

如果在超时终止请求之后再访问status属性,会导致错误,所以要将检查status属性的语句放在try-catch中。

3.overrideMimeType()方法
firefox最早引入overrideMimeType()方法,用于重写XHR响应的MIME类型。

进度事件

Progress Events规范是W3C的一个工作草案,定义了与客户端服务器通信有关的事件。
有以下6个进度事件:
loadstart:在接收到响应数据的第一个字节时触发。
progress:在接收响应期间持续不断地触发。(必须在调用open()方法之前添加onprogress()事件处理程序)
error:在请求发生错误时触发。
abort:在因为调用abort()方法而终止连接时触发。
load:在接收到完整的响应数据时触发。
loadend:在通信完成或者触发error、abort或load事件后触发。

跨域资源共享(CORS)

CORS背后的基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
CSRF:跨域请求伪造
XSS:跨站点脚本

IE对CORS实现主要是通过XDR对象来实现,XDR对象与XHR类似,所有XDR请求都是异步的。

1
var xdr = new XDomainRequest();

CORS通过一种叫做Prefighted Request的透明服务器验证机制支持开发人员使用自定义的头部、GET或POST之外的方法,以及不同类型主体内容。

检测XHR是否支持CORS的最简单方式,就是检查是否存在withCredentials属性

跨浏览器的CORS实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function createCORSRequest(method, url){
    var xhr = new XMLHttpRequest();
    if("withCredentials" in xhr){
        xhr.open(method,url,true);
    }else if(typeof XDomainRequest != "undefined"){
        xhr = 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();
}

其他跨域技术
1.图像Ping
第一种跨域技术是使用<img>标签。因为加载图像不存在跨域问题。
动态创建图像经常用于图像Ping。图像Ping是与服务器进行简单、单向的跨域通信的一种方式。
请求的数据是通过查询字符串形式发送的,而响应可以是任意内容,但通常是像素图或204响应。
eg:
var img = new Image();
img.onload = img.onerror = function(){
alert(“Done!”);
}
img.src = “http://www.example.com/test?name=Peter“;

图像Ping最常用于跟踪用户点击页面或动态广告曝光次数。
图像Ping有两个缺点:一是只能发送GET请求;二是无法访问服务器的响应文本。

2.JSONP(JSON with padding 填充式JSON或参数式JSON)
JSONP由两部分组成:回调函数和数据。
通过查询字符串来指定JSONP服务的回调函数是很常见的。

JSONP是通过动态<script>元素来使用,使用时可以为src属性指定一个跨域URL。

1
2
3
4
5
6
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");

JSONP的优点是:能够直接访问响应文本,支持在浏览器与服务器之间双向通信。
不足:JSONP是从其他域中加载代码执行,如果其他域不安全,很可能会在响应中夹带一些恶意代码;
其次要确定JSONP请求是否失败并不容易。

Comet

Comet指的是一种更高级的Ajax技术(经常也有人称为“服务器推送”)。
Ajax是一种从页面向服务器请求数据的技术,而Comet则是一种服务器向页面推送数据的技术。

Comet能够让信息近乎实时地被推送到页面上,非常适合处理体育比赛的分数和股票报价。
有两种实现Comet的方式:长轮询和流。
长轮询是传统轮询(短轮询)的一个翻版,即浏览器定时向服务器发送请求,看有没有更新的数据。
而长轮询是把短轮询颠倒了。页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。

无论短轮询还是长轮询,浏览器都要在接收数据之前,先发起对服务器的连接。
两者最大的区别在于服务器如何发送数据。短轮询是服务器立即发送响应,无论数据是否有效,而长轮询是等待发送响应。
以请客吃饭来讲述短轮询和长轮询:
短轮询就是,我(server)说要请客吃饭,然后你(browser)就不停地发短信问我“今天请客吃饭不?”,你的行为无疑是很浪费钱的(耗资源);
长轮询就是,我(server)说要请客吃饭,然后你(browser)就发条短信给我“下次请客吃饭记得叫我”,我就记得这件事,下次请客吃饭就会发短信通知你“我今天请客吃饭”。

第二种流行的Comet实现是HTTP流。流不同于上述两种轮询,因为它在页面的整个生命周期内只使用一个HTTP连接,即浏览器向服务器发送一个请求,而服务器保持连接打开,然后周期性地向浏览器发送数据。

在Firefox、Chrome、Opera等浏览器中,通过侦听readyStatechange事件以及检测readyState的值是否为3,就可以利用XHR对象实现HTTP流。当readyState值变为3时,responseText属性中就会保存接收到的所有数据。
使用XHR对象实现HTTP流的典型代码:

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
26
27
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、在接收到数据时调用的函数以及关闭连接时调用的函数。

服务器发送事件(SSE)

SSE是围绕只读Comet交互推出的API或者模式。SSE API用于创建到服务器的单向连接,服务器通过这个连接可以发送任意数量的数据。
1.SSE API
要预订新的事件流,首先要创建一个新的EventSource对象,并传进一个同源的入口点:

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

EventSource的实例有一个readyState属性,值为0表示正连接到服务器,值为1表示打开了连接,值为2表示关闭了连接。另外还有三个事件:

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

1
2
3
source.onmessage = function(event){
    var data = event.data;
}

如果想强制立即断开连接并且不再重新连接,可以调用close()方法:source.close();

2.事件流
服务器事件会通过一个持久的HTTP响应发送,这个响应的MIME类型为text/event-stream.
Web Sockets
Web Sockets的目标是在一个单独的持久连接上提供全双工、双向通信。

单工:如果在通信过程的任意时刻,信息只能从一方A传到一方B,则称为单工;
半双工:如果在通信过程的任意时刻,信息既可以由A传到B,又能够从B传到A,但只能由一个方向上的传输存在,则称为半双工;
全双工:如果在通信过程的任意时刻,线路上存在A到B和B到A的双向信号传输,则称为全双工。

过程:
在Javascript中创建了WebSocket之后,会有一个HTTP请求发送到浏览器以发起连接。在取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为Web Socket协议。也就是说使用标准的HTTP服务器无法实现Web Sockets,只有支持这种协议的专门服务器才能正常工作。

Web Sockets使用了自定义的协议,有特定的URL模式:ws://,加密的连接是:wss://
使用自定义协议而非HTTP协议的好处是:能够在客户端和服务器之间发送非常少量的数据,而不必担心HTTP那样字节级的开销。
缺点:制定协议的时间比制定JavaScript API的时间还要长。

1.Web Sockets API

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

必须给WebSocket构造函数传入绝对URL,并且可以是跨域的URL。

与XHR类似,WebSocket也有readyState属性:
WebSocket.OPENING(0):正在建立连接。
WebSocket.OPEN(1):已经建立连接。
WebSocket.CLOSING(2):正在关闭连接。
WebSocket.CLOSE(3):已经关闭连接。

WebSocket没有readystatechange事件,readyState的值永远从0开始。

2.发送和接收数据

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

WebSocket只能通过连接发送纯文本信息,对于复杂的数据结构,如JSON数据,必须在连接发送之前进行序列化。

1
2
3
4
5
6
var message = {
    time:new Date(),
    text: "Hello world!",
    clientId: "abcdef"
};
socket.send(JSON.stringify(message));

当服务器向客户端发来消息时,WebSocket对象会触发message事件,返回的数据保存在event.data。

3.其他事件
open:在成功建立连接时触发;
error:在发生错误时触发,连续不能持续;
close:在连接关闭时触发。

在这三个事件中,只有close事件的event对象有额外信息,分别是:wasClean、code和reason。

安全
CSRF(跨站点请求伪造):未被授权系统有权访问某个资源的情况。
为确保通过XHR访问的URL安全,通行的做法就是验证发送请求者是否有权限访问相应的资源。
有效做法:
要求以SSL连接来访问可以通过XHR请求的资源;
要求每一次请求都要附带经过算法计算得到的验证码;

无效做法(对于防范CSRF攻击不起作用):
要求发送POST而不是GET请求——很容易改变;
检查来源URL以确定是否可信——来源记录很容易伪造;
基于cookie信息进行验证——很容易伪造。

不要将用户名和密码保存在Javascript代码中!!!

坚持原创技术分享,您的支持将鼓励我继续创作!