JavaScript——Ajax与Comet

XMLHttpRequest对象

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
function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
return new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined"){
if (typeof arguments.callee.activeXString != "string"){
//在早期的IE中,XHR对象是通过MSXML库中一个ActiveX对象实现的。
//对于IE早期的版本IE7-,可能会遇到三种不同版本的XHR对象。
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(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex){
//跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("No XHR object available.");
}
}

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

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

readyState 属性
0:未初始化。尚未调用open()方法
1:启动。已经调用oepn()方法,但尚未调用send()方法。
2:发送。已经调用send()方法,但尚未接收到响应。
3:接收。已经接收到部分响应数据。
4:完成。已经接收到全部响应数据,而且已经可以在客户端使用了。

1
2
var xhr = createXHR();
//这里不使用this的原因。因为onreadystatechange

是哪吃力程序的作用域问题。如果使用this对象,在有的浏览器中会导致函数执行错误

1
2
3
4
5
6
7
8
9
10
11
xhr.onreadystatechange = function (event) {
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", "example.txt", true);
xhr.send(null);

HTTP头部信息

默认情况下,在发送XHR请求的同时,还会发送下列头部信息。
Accept:浏览器能够处理的内容类型。
Accept-Charset:浏览器能够显小的字符集。
Accept-Encoding:浏览器能够处理的压缩编码。
Accept-Language:浏览器当前设置的语言。
Connection:浏览器与服务器之间连接的类型。
Cookie:当前页面设置的任何Cookie
Host:发出请求的页面所在的域
Referer:发出请求的页面的URI。注意,HTTP规范将这个头部字段拼写错了,而为保证与规范一致,也只能将错就错了(这个英文单词的正确拼法应该是referrer)
User-Agent:浏览器的用户代理字符串。
setRequestHeader()
参数:头部信息的名称和值

1
xhr.setRequestHeader("MyHeader", "MyValue");

getRequestHeader()
参数:头部信息的名称

1
var header=xhr.getRequestHeader("MyHeader");

getAllRequestHeader()
包含所有头部信息的长字符串

GET请求

必须使用encodeURIComponent()编码

1
2
3
4
5
6
7
8
9
function addURLParam(url, name, value) {
url += (url.indexOf(";?") == -1 ? "?" : "&")
url += encodeURIComponent(name) + "=" + encodeURIComponent(value)
return url
}
var url = "example. php"
url = addURLParam(url, "name", "vincent")
url = addURLParam(url, "bock", "java")
xhr.open("get", url, false);

POST请求

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
function submitData() {
var xhr = createXHR();
xhr.onreadystatechange = function (event) {
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("post", "postexample.php", true);
//表单请求
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
var form = document.getElementById("user-info");
//发送序列化数据
xhr.send(serialize(form));
}
function serialize(form) {
var parts = new Array();
var field = null;
for (var i = 0, len = form.elements.length; i < len; i++) {
field = form.elements[i];
switch (field.type) {
case "select-one":
case "select-multiple":
for (var j = 0, optLen = field.options.length; j < optLen; j++) {
var option = field.options[j];
if (option.selected) {
var optValue = "";
if (option.hasAttribute) {
optValue = (option.hasAttribute("value") ? option.value : option.text);
} else {
optValue = (option.attributes["value"].specified ? option.value : option.text);
}
parts.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(optValue));
}
}
break;
case undefined: //fieldset
case "file": //file input
case "submit": //submit button
case "reset": //reset button
case "button": //custom button
break;
case "radio": //radio button
case "checkbox": //checkbox
if (!field.checked) {
break;
}
/* falls through */
default:
parts.push(encodeURIComponent(field.name) + "=" + encodeURIComponent(field.value));
}
}
return parts.join("&");
}

XMLHttpRequest2级

FormData

序列化表单以及创建于表单格式相同的数据(用于通过XHR传输)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function submitData() {
var xhr = createXHR();
xhr.onreadystatechange = function (event) {
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("post", "postexample.php", true);
var form = document.getElementById("user-info");
xhr.send(new FormData(form));
}

超时设置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var xhr = createXHR();
xhr.onreadystatechange = function (event) {
try {
if (xhr.readyState == 4) {
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", "timeout.php", true);
xhr.timeout = 1000;
xhr.ontimeout = function () {
alert("Request did not return in a second.");
};
xhr.send(null);

overrideMimeType()

用于重写XHR相应的MIME类型

1
xhr.overrideMImeType("text/xml");

进度事件

loadstart:在接收到响应数据的第一个字节时触发
progress:在接收响应期间持续不断地触发。
error:在请求发生错误时触发。
abort:在因为调用abort()方法而终止连接时触发。
load:在接收到完整的响应数据时触发。
loadend:在通信完成或者触发error、abort或load事件后触发

load事件

用以代替readystatechange事件
响应接受完毕后将会触发load事件,因此没有必要去检查readyState
只要浏览器接收到服务器的响应,不管状态如何,都会触发load事件

1
2
3
4
5
6
7
8
xhr.onload = function (event) {
if ((xhr.status >= 200 && xhr.status < 300) ||
xhr.status == 304) {
alert(xhr.responseText);
} else {
alert("Request was unsuccessful: " + xhr.status);
}
};

progress事件

在浏览器接受新数据期间周期性触发
onprogress事件 处理程序会接受一个event对象,其target属性是XHR对象,有额外三个属性:
lengthComputable:表示进度信息是否可用
position:已经接受的字节
totalSize:Content-Length响应头部确定的预期字节数

1
2
3
4
5
6
xhr.onprogress = function(event){
var divStatus = document.getElementById("status");
if (event.lengthComputable){
divStatus.innerHTML = "Received " + event.position + " of " + event.totalSize + " bytes";
}
};

跨源资源共享

默认情况下,XHR对象只能访问与包含它的页面位于同一域中的资源。
CORS(跨源资源共享)背后的基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
GET或POST发送的请求,它没有自定义的头部,主题内容是text/plain。
在发送该请求时,需要给他附加一个额外的Origin头部
Origin:http//www.baidu.com
如果服务器认为这个请求可以接受,就在Access-Control-Allow-Origin头部中会发同样的源信息
Access-Control-Allow-Origin:http//www.baidu.com

IE对CORS的实现

IE8中引入XDR(XdomainRequest)类型。
与XHR类似,但能实现安全可靠的跨域通信。
XDR与XHR不同之处

  • cookie不会随请求发送,也不会随响应返回。
  • 只能设置请求头部信息中的Content-Type字段。
  • 不能访问响应头部信息。
  • 只支持GET和POST请求。
    这些变化使CSRF(Cross-Site Request Forgery,跨站点请求伪造)和XSS(Cross-SiteScnpting,跨站点脚本)的问题得到了缓解。被请求的资源可以根据它认为合适的任意数据(用户代理、来源页面等)。来决定是否设置Allow-origin头部作为请求的一部分,origin头部的值表示请求的来源域,以便远程资源明确地识别XDR请求。
    所有XDR请求都是异步执行的,不能用它来创建同步请求。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var xdr = new XDomainRequest();
    xdr.onload = function () {
    alert(xdr.responseText);
    };
    //如果失败,会触发error事件,遗憾的是没有其他信息可用
    xdr.onerror = function () {
    alert("Error!");
    };
    xdr.open("get", "http://www.somewhere-else.com/xdr.php");
    xdr.send(null);
    //终止请求
    xdr.abort();

与XHR一样,XDR对象也支持timeout属性以及ontimeout事件处理程序。
为支持POST请求,XDR对象提供了contentType属性,用于表示发送数据的格式

其他浏览器对CORS的实现

在尝试打开不同来源的资源时,无需额外编写代码就可以触发这个行为。要请求位于另一个域中的资源,使用标准的XHR对象并在open()方法中传入绝对URL即可
与IE不同,通过跨域XHR对象可以访问status和statusText属性。
限制

  • 不能使用setRequestHeader()设置自定义头部
  • 不能发送和接收cokie
  • 调用getAllRespnseHeaders()方法总会返回空字符串
    因此,对于本地资源,最好使用相对URL

    Preflighted Requests

    CORS通过一种叫做Prefltghted Requests的透明服务器验证机制支持开发人员使用自定义的头部、GET或POST之外的方法,以及不同类型的主体内容。在使用下列高级选项来发送请求时,就会向服务器发送一个Preflight请求。这种请求使用OPTIONS方法,发送下列头部
  • origin:与简单的请求相同。
  • Access-Control-Request-Method:请求自身使用的方法。
  • Access-ControlRequest-Headers:(可选)自定义的头部信息,多个头部以逗号分隔
    POST发送的请求
    Origin:http:\www.nczonline.net
    Access-Control-RequestMethod:POST
    Access-Control-Request-Headers:NCZ
    服务器通过在响应中发送如下头部
  • Access- Control-Allow-Origin:与简单的请求相同。
  • Access- Control-AllowMethods:允许的方法·多个方法以逗号分隔。
  • Access- Control-Allow-Headers:允许的头部,多个头部以逗号分隔
  • Access- Control-Max-Age:应该将这个Preflight请求缓存多长时间(以秒表小)。
    例如:
    1
    2
    3
    4
    Access- Control-Allow-Origin:http://www.nczonline.net
    Access- Control-AllowMethods :POST,GET
    Access- Control-Allow-Headers:NCZ
    Access- Control-Max-Age:1728000g

带凭据的请求

默认情况下,跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等)。
如果服务器接受带凭据的请求,会用下面的HTTP头部来响应。
Access-Control-Allow-Credentialg:true
如果发送的是带凭据的请求,但服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给JavaScript(于是,responseText中将是空字符串,status的值为0,而且会调用onerror事件处理程序)。

跨浏览器的CORS

检测XHR是否支持CORS的最简单方式,就是检查是否存在withCredentials属性。再结合检测XDornainRequest对象是否存在·就可以兼顾所有浏览器了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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/xdr.php");
if (request) {
request.onload = function () {
//do something with request.responseText
};
request.send();
}

XMLHttpRequest与XDomainRequest共同属性方法

  • abort():用于停止正在进行的清求。
  • onerror():用于替代onreadystatechange检测错误
  • onload():用于替代onreadystatechange检测成功
  • responseText():用于取得响应内容。
  • send():比用于发送请求
    以上成员都包含在createcoCORSRequest()函数返回的对象中

其他跨域技术

图像Ping

上述第一种跨域请求技术是使用标签。我们知道,一个网页可以从任何网页中加载图像,不用担心跨域不跨域。这也是在线广告跟踪浏览量的主要方式。

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

图像Ping最常用于跟踪用户点击页面或动态广告光次数。图像Ping有两个主要的缺点

  • 只能发送GET请求
  • 无法访问服务器的响应文本
    因此,图像Ping只能用于浏览器与服务器间的单向通信。

    JSONP

    JSONP跟JSON差不多,只不过是被包含在函数调用中的JSON
    callback({“name”:”vincent”})
    JSONP是通过动态<script>元素来使用的,使用时可以为src属性指定一个跨域URL这里的<script>元素,与以<img>兀素类似,都有能力不受限制地从其他域加载资源。
    因为JSONP是有效的JavaScript代码,所以在请求完成后,即在JSONP响应加载到页面中以后,就会立即执行。
    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);

优点:

  • 在于能与直接访问相应文本,支持浏览器与服务器之间双向通信

    Comet

    Ajax是种从页面向服务器请求数据的技术,而Comet则是一种服务器向页面推送数据的技术。Comet能够让信息近乎实时地被推送到页面上,非常适合处理体育比赛的分数和股票报价。
    有两种实现Comet的方式:长轮询和流。

    短轮询



    长轮询

    长轮询是传统轮询(也称为短轮询)的一个翻版,页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求。这一过程在页面打开期间一直持续不断。


    HTTP流

    流不同于上述两种轮询,因为它在页面的整个生命周期内只使用一个HTTP连接。具体来说,就是浏览器向服务器发送一个请求·而服务器保持连接打开,然后周期性地向浏览器发送数据。
    在Firefox,Opera,Safari和Chrome中,通过侦听readystatechange事件及检测readystate的值是否为3,就可以利用XHR对象实现HTTP流。在上述这些浏览器中,随着不断从服务器接收数据,readystate的值会周期性地变为3。当readystate值变为3时,respongerext属性中就会保存接收到的所有数据。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    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(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!");
    });

服务器发送事件

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

SSE API

1
2
3
var source = new EventSource("event.php")
//手动关闭连接
source.close()

EventSource有一个readyState属性
0:正在链接到服务器
1:打开了连接
2:关闭了连接
EventSource三个事件

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

事件

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

1
2
3
4
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

在JavaScript中创建了Web Socket之后,会有一个HTTP请求发到浏览器以发起连接。在取得服务器响应后,建立的连接会使用HTTP升级从HTTP协议交换为Web Socket协议。
由于WebSockets使用了自定义的协议,所以URL模式也略有不同。未加密的连接不再是http,而是ws。加密的连接也不是https,而是wss。

Web Sockets API

必须给WebSocket构造函数 传入绝对URL。同源策略对Web Socket不适用。

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

实例化WebSocket对象后,浏览器就会马上尝试创建链接
readyState属性表示当前状态

  • Websocket.OPENING(0):正在建立连接。
  • WebSocket.OPEN(1):已经建立连接
  • WebSoCket.CLOSING(2)正在关闭连接。
  • WebSocket.CLOSE(3)已经关团连接。
    关闭连接
1
socket.close()

发送和接收数据

1
2
3
4
5
6
7
8
9
10
11
12
13
var socket = new WebSocket("ws://www.examplr.com/server.php")
var socket = new WebSocket("ws://www.examplr.com/server.php")
socket.send("hello world!")
//由于只能发送纯文本数据,对于复杂的数据必须进行序列化
var message = {
time: new Date(),
text: 'hello world'
}
socket.send(JSON.stringify(message))
//当服务器向客户端发来消息时,WebSocket对象就会触发message事件。
socket.onmessage = function (event) {
var data = event.data
}

其他事件

Web Socket事件

  • open:在成功建立连接时触发。
  • error:在发生错误时触发,连接不能持续。
  • close:在连接关团时触发
    WebSocket不支持DOM2级事件侦听器。
文章目录
  1. 1. XMLHttpRequest对象
    1. 1.1. HTTP头部信息
    2. 1.2. GET请求
    3. 1.3. POST请求
  2. 2. XMLHttpRequest2级
    1. 2.1. FormData
    2. 2.2. 超时设置
    3. 2.3. overrideMimeType()
  3. 3. 进度事件
    1. 3.1. load事件
    2. 3.2. progress事件
  4. 4. 跨源资源共享
    1. 4.1. IE对CORS的实现
  5. 5. 其他浏览器对CORS的实现
    1. 5.1. Preflighted Requests
    2. 5.2. 带凭据的请求
    3. 5.3. 跨浏览器的CORS
  6. 6. 其他跨域技术
    1. 6.1. 图像Ping
    2. 6.2. JSONP
    3. 6.3. Comet
      1. 6.3.1. 短轮询
      2. 6.3.2. 长轮询
      3. 6.3.3. HTTP流
    4. 6.4. 服务器发送事件
      1. 6.4.1. SSE API
      2. 6.4.2. 事件
    5. 6.5. Web Sockets
      1. 6.5.1. Web Sockets API
      2. 6.5.2. 发送和接收数据
      3. 6.5.3. 其他事件
|