高程3总结#第21章Ajax与Comet

Ajax与Comet

XMLHttpRequest对象

  • IE5是第一款引入XHR对象的浏览器,在IE5中,XHR对象是通过MSXML库中的一个ActiveX对象实现的

    //适用于 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(versions[i]);
    arguments.callee.activeXString = versions[i];
    break;
    } catch (ex){
    //跳过
    }
    }
    }
    return new ActiveXObject(arguments.callee.activeXString);
    }
  • 这个函数会尽力根据IE中可用的MSXML库的情况创建最新版本的XHR对象
  • IE7+、Firefox、Opera、Chrome、Safari都支持原生的XHR对象,这些浏览器中创建XHR对象,可以使用XMLHttpRequest构造函数

    var xhr=new XMLHttpRequest();
  • 如果还必须要支持IE的更早版本,可以在createHXR()函数中加入对原生XHR对象的支持

    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(versions[i]);
            arguments.callee.activeXString = versions[i];
            break;
          } catch (ex){
            //跳过
          }
        }
      }
      return new ActiveXObject(arguments.callee.activeXString);
    } else {
      throw new Error("No XHR object available.");
    }
    }
  • 这个函数中新增的代码首先检测原生XHR对象是否存在,如果存在则返回它的新实例,如果原生对象不存在,则检测ActiveX对象,如果这两种对象都不存在,就抛出一个错误,然后就可以使用下面的代码在所有浏览器中创建XHR对象了

    var xhr=createXHR();

XHR的用法

  • 在使用XHR对象时,要调用的第一个方法是open(),接收3个参数:要发送的请求的类型(get或者post)、请求的URL和表示是否异步发送请求的布尔值

    xhr.open("get","example.php",false);
  • 这行代码会启动一个针对example.php的GET请求
  • URL相对于执行代码的当前页面
  • open()方法并不会真正发送请求,而是启动一个请求以备发送
  • 要想发送特定的请求,调用send()方法

    xhr.open("get","example.txt",false);
    xhr.send(null);
  • send()方法接收一个参数,要作为请求主体发送的数据,如果不需要通过请求主体发送数据,必须传入null
  • 响应的数据会自动填充XHR对象的属性

    • responseText,作为响应主体被返回的文本
    • responseXML,如果响应的内容类型是"text/xml"或"application/xml",这个属性中将保存包含着响应数据的XML DOM文档
    • status,响应的HTTP状态
    • statusText,HTTP状态的说明
  • XHR对象的readyState属性表示请求响应过程的当前活动阶段

    • 0,未初始化,尚未调用open()方法
    • 1,启动,已经调用open()方法,但尚未调用send()方法
    • 2,发送,已经调用send()方法,但尚未接收到响应
    • 3,接收,已经接收到部分响应数据
    • 4,完成,已经接收到全部响应数据,而且已经可以在客户端使用了
  • 必须在调用open()之前指定onreadystatechange事件处理程序才能确保跨浏览器兼容性

    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", "example.txt", true);
    xhr.send(null);
  • 在接收到响应之前还可以调用abort()方法来取消异步请求

HTTP头部信息

  • 在发送XHR请求的同时,还会发送下列头部信息

    • Accept,浏览器能够处理的内容类型
    • Accept-Charset,浏览器能够显示的字符集
    • Accept-Encoding,浏览器能够处理的压缩编码
    • Accept-Language,浏览器当前设置的语言
    • Connection,浏览器与服务器之间连接的类型
    • Cookie,当前页面设置的任何Cookie
    • Host,发出请求的页面所在的域
    • Referer,发出请求的页面的URI
    • User-Agent,浏览器的用户代理字符串
  • setRequestHeader()方法,可以设置自定义的请求头部信息,这个方法接收两个参数:头部字段名称和头部字段的值。要成功发送头部信息,必须在调用open()方法之后且调用send()方法之前调用setRequestHeader()

    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", "example.php", true);
    xhr.setRequestHeader("MyHeader", "MyValue");
    xhr.send(null);

GET请求

  • GET是最常见的请求类型,最常用于向服务器查询某些信息,必要时可以将查询字符串参数追加到URI的末尾,以便将信息发送给服务器
  • 使用GET请求经常会发生一个错误,就是查询字符串的格式问题。
  • 查询字符串中每个参数的名称和值都必须使用encodeURIComponent()进行编码,然后才能放到URL的末尾,而且所有的名-值对都必须由&分隔

    xhr.open("get","example.php?name1=value1&name2=value2",true)
  • 向现有URL的末尾添加查询字符串参数

    function addURLParam(url, name, value) {
      url += (url.indexOf("?") == -1 ? "?" : "&");
      url += encodeURIComponent(name) + "=" + encodeURIComponent(value);
      return url;
    }
  • addURLParam()函数接收3个参数:要添加参数的URL、参数的名称和参数的值

POST请求

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

    function submitData(){
      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("post", "postexample.php", true);
      xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
      var form = document.getElementById("user-info");
      xhr.send(serialize(form));
    }

XMLHttpRequest 2级

FormData

  • FormData为序列化表单以及创建与表单格式相同的数据,提供了便利

    var data=new FormData();
    data.append("name","Nicholas");
  • 这个append()方法接收两个参数,键和值
  • 创建了FormData的实例后,可以将它直接传给XHR的send()方法

    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("post","postexample.php", true);
    var form = document.getElementById("user-info");
    xhr.send(new FormData(form));

超时设定

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

    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){
          //假设由 ontimeout 事件处理程序处理
        }
      }
    };
    xhr.open("get", "timeout.php", true);
    xhr.timeout = 1000; // 将超时设置为 1  秒钟(仅适用于 IE8+ )
    xhr.ontimeout = function(){
      alert("Request did not return in a second.");
    };
    xhr.send(null);

overrideMimeType()方法

  • Firefox最早引入了overrideMimeType()方法,用于重写XHR响应的MIME类型
  • 通过调用overrideMimeType()方法,可以保证把响应当做XML而非纯文本来处理

    var xhr = createXHR();
    xhr.open("get", "text.php", true);
    xhr.overrideMimeType("text/xml");
    xhr.send(null);

进度事件

  • 6个进度事件

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

load事件

var xhr = createXHR();
xhr.onload = function(){
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304){
  alert(xhr.responseText);
} else {
  alert("Request was unsuccessful: " + xhr.status);
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);

progress事件

var xhr = createXHR();
xhr.onload = function(event){
if ((xhr.status >= 200 && xhr.status < 300) ||
    xhr.status == 304){
  alert(xhr.responseText);
} else {
  alert("Request was unsuccessful: " + xhr.status);
}
};
xhr.onprogress = function(event){
var divStatus = document.getElementById("status");
if (event.lengthComputable){
  divStatus.innerHTML = "Received " + event.position + " of " +
    event.totalSize +" bytes";
}
};
xhr.open("get", "altevents.php", true);
xhr.send(null);
  • 为确保正常执行,必须在调用open()方法之前添加onprogress事件处理程序

跨源资源共享

  • CORS,跨资源共享,定义了在必须访问跨源资源时,浏览器远服务器应该如何沟通。CORS基本思想就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是应该失败

IE对CORS的实现

  • 微软在IE8中引入了XDR类型,这个对象与XHR类型,但是能实现安全可靠的跨域通信

    • cookie不会随请求发布,也不会随响应返回
    • 只能设置请求头部信息中的Content-Type字段
    • 不能访问响应头部信息
    • 只支持GET和POST请求
  • XDR对象的使用方法与XHR对象非常相似,也是创建一个XDomainRequest的实例,调用open()方法,再调用send()方法,与XHR对象的open()方法不同,XDR对象的open()方法只接收2个参数:请求类型和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)
  • 请求返回前调用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);
  • 为支持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");

其他浏览器对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);
  • 安全限制

    • 不能使用setRequsetHeader()设置自定义头部
    • 不能发送和接收cookie
    • 调用getAllResponseHeaders()方法总会返回空字符串

Preflighted Requests

  • 通过Preflighted Request的透明服务器验证机制支持开发人员使用自定义的头部、GET和POST之外的方法,以及不同类型的主体内容
  • 这种请求使用OPITONS方法,发送下列头部

    • Origin,与简单的请求相同
    • Access-Control-Method,请求自身使用的方法
    • Access-Control-Headers,自定义的头部信息,多个头部以逗号分隔
    Origin: http://www.nczonline.net
    Access-Control-Request-Method: POST
    Access-Control-Request-Headers: NCZ
  • 服务器可以决定是否允许这种类型的请求,服务器在响应中发送如下头部与浏览器进行沟通

    • Access-Control-Allow-Origin,与简单的请求相同
    • Access-Control-Allow-Methods,允许的方法,多个方法以逗号分隔
    • Access-Control-Allow-Headers,允许的头部,多个头部以逗号分隔
    • 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

带凭据的请求

  • 通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据

    Access-Control-Allow-Credentials:true
  • 如果发送的是带凭据的请求,但服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给JavaScript,于是responseText中将是空字符串,status的值为0,而且会调用onerror()事件处理程序

跨浏览器的CORS

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对象类似,都提供了够用的接口,这两个对象共同的属性方法如下

    • abort(),用于停止正在进行的请求
    • onerror(),用于替代onreadystatechange检测错误
    • onload(),用于替代onreadystatechange检测成功
    • responseText(),用于取得响应内容
    • send(),用于发送请求

其他跨域技术

图像Ping

  • 一个网页可以从任何网页中加载图像,不用担心跨域不跨域
  • 通过图像Ping,浏览器得不到任何具体的数据,但通过侦听load和error事件,它能知道响应什么时候接收到的

    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数据

    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

  • 两种实现Comet的方式

    • 长轮询

      • 短轮询时间线
        高程3总结#第21章Ajax与Comet
      • 长轮询把短轮询颠倒了一下。页面发起一个到服务器的请求,然后服务器一直保持连接打开,直到有数据可发送。发送完数据之后,浏览器关闭连接,随即又发起一个到服务器的新请求,这一过程在页面打开期间一直持续不断
        高程3总结#第21章Ajax与Comet
      • 在页面的整个生命周期内只使用一个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、在接收到数据时调用的函数以及关闭连接时调用的函数

服务器发送事件

  • SSE是围绕只读Comet交互推出的API或者模式

    var source=new EventSource("myevents.php");
  • EventSource的实例有一个readyState属性,值为0表示正连接到服务器,值为1表示打开了连接,值为2表示关闭了连接
  • 另外还有三个事件

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

Web Sockets

  • Web Sockets的目标是在一个单独的持久连接上提供全双工、双向通信
  • 要创建Web Socket,先实例一个WebSocket对象并传入要连接的URL

    var socket = new WebSocket("ws://www.example.com/server.php");
  • 实例化WebSocket对象后,浏览器会马上尝试创建连接,与XHR类似,WebSocket也有一个表示当前状态的readyState属性,这个属性的值与XHR并不相同

    • WebSocket.OPENING(0),正在建立连接
    • WebSocket.OPEN(1),已经建立连接
    • WebSocket.CLOSING(2),正在关闭连接
    • WebSocket.CLOSE(3),已经关闭连接
  • WebSocket没有readystatechange事件,不过有其他事件,对应着不同的状态,readyState的值永远从0开始
  • 要关闭Web Socket连接,可以在任何时候调用close()方法

    socket.close();
  • 调用close()之后,readyState的值立即变为2,而关闭连接后就会变成3
  • 使用send()方法并传入任意字符串

    var socket = new WebSocket("ws://www.example.com/server.php");
    socket.send("Hello world!");
    //将数据序列化为JSON字符串,然后发送到服务器
    var message = {
      time: new Date(),
      text: "Hello world!",
      clientId: "asdfp8734rew"
    };
    socket.send(JSON.stringify(message));
    //当服务器收到消息时,WebSocket对象就会触发message事件,这个message事件与其他传递消息的协议类似,也是把返回的数据保存在event.data属性中
    socket.onmessage = function(event){
      var data = event.data;
      //处理数据
    };
  • 其他事件

    • open,在成功建立连接时触发
    • error,在发生错误时触发,连接不能持续
    • close,在连接关闭时触发
    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.");
    };

SSE与Web Sockets

  • 考虑是使用 SSE 还是使用 Web Sockets 时,可以考虑如下几个因素。

    • 首先,你是否有自由度建立和维护 Web Sockets服务器?因为 Web Socket 协议不同于 HTTP,所以现有服务器不能用于 Web Socket 通信。SSE 倒是通过常规 HTTP 通信,因此现有服务器就可以满足需求。
    • 第二个要考虑的问题是到底需不需要双向通信。如果用例只需读取服务器数据(如比赛成绩),那么 SSE 比较容易实现。如果用例必须双向通信(如聊天室),那么 Web Sockets 显然更好。在不能选择 Web Sockets 的情况下,组合 XHR 和 SSE 也是能实现双向通信的。

安全

  • 为确保通过 XHR 访问的 URL 安全,通行的做法就是验证发送请求者是否有权限访问相应的资源

    • 要求以 SSL 连接来访问可以通过 XHR 请求的资源。
    • 要求每一次请求都要附带经过相应算法计算得到的验证码。请注意,下列措施对防范 CSRF 攻击不起作用。
    • 要求发送 POST 而不是 GET 请求——很容易改变。
    • 检查来源 URL 以确定是否可信——来源记录很容易伪造。
    • 基于 cookie 信息进行验证——同样很容易伪造

相关推荐