Ajax 与 Comet

2019-08-17  本文已影响0人  了凡和纤风

本章内容:使用 XMLHttpRequest对象、使用 XMLHttpRequest 事件、跨域 Ajax通信

2005年,Jesse James Garrett 发表了一篇在线文章,,题为“Ajax:A new Approach to Web Applications”。他在这篇文章中介绍了一种技术,用他的话来说,就叫 Ajax,是对 Asyncchronous JavaScript + XML 的缩写。这一技术能够像服务器请求额外的数据而无须卸载页面,会带来更好的用户体验。Garrett 还解释了怎样使用这一技术改变自从 Web诞生以来就一直沿用的“单机,等待”的交互模式。

Ajax技术的核心是 XMLHttpRequest 对象(检测 XHR),这是由微软首先引进的一个特性,其它浏览器提供商后来都提供了相同的实现。

一、XMLHttpRequest 对象

IE5 是第一款引入 XHR 对象的浏览器。XHR 对象通过 MSXML 库中的一个 ActiveX 对象实现。
因此,在 IE 中可能会遇到三种不同版本的 XHR 对象,即 MSXML2.XMLHttp、MSXML2.XMLHttp.3.0、MSXML2.XMLHttp.6.0
创建适用于 IE7 之前的版本

function createXHR() {

  if (typeof arguments.callee.activeXString != 'string') {
    var version = ['MSXML2.XHLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']

    for (var i = 0, len = version.length;  i< len; i++) {
      try {
        new ActiveXObject(version[i])
        arguments.callee.activeXString = version[i]
        break;
      } catch(err) {

      }
    }
  }
  return new ActiveXObject(arguments.callee.activeXString)
}

这个函数会激励根据IE中可用的 MSXML 库的情况创建最新版本的 XHR 对象

IE7+、Firefox、Opera、Chrome、Safari 都支持原生的 XHR 对象,在这些浏览器中创建 XHR 对象要像下面这样使用 XMLHttpRequest 构造函数

var xhr = new XMLHttpRequest()

如果想要兼顾 IE的早起版本,那么则可以在这个 createXHR() 函数中加入对原生 XHR 对象的支持。

function createXHR() {
  if (typeof XMLHttpRequest != 'undefined') {
    return new XMLHttpRequest()
  } else if (typeof ActiveXObject != 'undefined') { 
    if (typeof arguments.callee.activeXString != 'string') {
      var version = ['MSXML2.XHLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp']

      for (var i = 0, len = version.length;  i< len; i++) {
        try {
          new ActiveXObject(version[i])
          arguments.callee.activeXString = version[i]
          break;
        } catch(err) {

        }
      }
    }
    return new ActiveXObject(arguments.callee.activeXString)
  } else {
    throw new Error('No XHR object availbale.')
  }
}

然后,就可以使用下面的代码在所有浏览器中创建 XHR 对象了

var xhr = createXHR()

1.1、XHR 的用法

在使用 XHR 对象时,要调用的第一个方法是 open(),他接受 3 个参数:

下面是调用这个方法的实例:

xhr.open('get', 'example.php', false)

调用 open() 方法并不会真正发送请求,而只是启动一个请求以备发送

第二步是,调用 send() 方法,接收一个参数:

调用 send() 之后,请求就会被分派到服务器。
在接收到响应后,响应的数据会自动填充 XHR 对象的属性,相关的属性简介如下:

在接收到响应后,第一步是检查 status 属性,已确定响应已经成功返回。一般来说,可以将 HTTP 状态码为 200 作为成功的标志。状态码 为 304 表示请求资源并没有被修改,可以直接使用浏览器缓存的版本;
为了确保接受都适当的响应,应该像下面这样检测上述的两种状态代码。

xhr.open('get', 'example.txt', false)
xhr.send(null)
if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { // 成功状态
  // todo
} else { // 失败状态
  // todo
}

无论内容类型是什么,响应主题的内容都后悔保存到 responseText 属性中;而对于 非 XML 数据而言,responseXML 属性的值将为 null。


多数情况下,我们是要发送异步请求,才能让 JavaScript 继续执行而不必等待响应。此时,可以检测 XHR 对象的 readyState 属性,该属性表示请求/响应过程的当前活动阶段。

只要 readyState 属性的值由一个值变成另一个值,都会触发一次 readystatechange 事件。可以利用 这个事件来检测每次状态变化后 readyState 的值。通常,我们只对 readyState 值为 4 的阶段感兴趣,因为这时所有数据都已经就绪。
不过,必须在调用 open() 之前指定 onreadystatechange 事件处理程序才能确保浏览器兼容性。

var xhr = new createXHR()

xhr.onreadystatechange = function(event) {
  if (xhr.readyState == 4) {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { // 成功状态
      // todo
    } else { // 失败状态
      // todo
    }
  }
}
xhr.open('get', 'example.txt', true)
xhr.send(null)

另外,在接收到响应之前还可以调用 abort() 方法来取消异步请求

xhr.abort()

调用这个方法后,XHR对象会停止触发事件,而且也不再允许访问任何域响应有关的对象属性。

1.2、HTTP 头部信息

每个 HTTP 请求和响应都会带有相应的头部信息,其中有的对开发人员有用。XHR 对象也提供了操作这两种头部(请求头 和 响应头)信息的方法
默认情况下,在发送 XHR 请求的同时,还会发送下列头部信息

不同浏览器实际发送的头部信息会有所不同,但以上列出的基本上是所有浏览器都会发送的。

使用 setRequestHeader() 方法可以设置自定义的请求头部信息,这个方法接受两个参数:

var xhr = new createXHR()

xhr.onreadystatechange = function(event) {
  // ...
}
xhr.open('get', 'example.php', true)
xhr.setRequestHeader('MyHeader', 'MyValue') // 设置自定义请求头信息
xhr.send(null)

建议使用 自定义的 头部字段名称,不要使用浏览器正常发送的字段名称,否则有 可能 会影响服务器的响应。有的浏览器允许开发人员重写默认的头部信息,但有的浏览器则不允许这样做。


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

var myHeader = xhr.getResponseHeader('myHeader')
var allHeaders = xhr.getAllResponseHeaders()

1.3、GET 请求

GET 是最常见的请求类型学,最常用于向服务器查询某些信息。可以将查询字符串参数最佳到 URL 的末尾,以便将信息发送给服务器。传入 open() 方法的 URL 末尾的查询字符串必须经过正确的编码才行。

建议对查询字符串中每个参数的名称和值都使用 encodeURIComponent() 进行编码
下面这个函数可以辅助向现有 URL 的末尾添加查询字符串参数:

function addURLParam(url, key, value) {
  url += (url.indexOf('?') == -1 ? '?' : '&') 
  url += encodeURIComponent(key) + '=' + encodeURIComponent(value)
  return url
}

下面是使用这个函数构建请求 URL 的示例:

var url = 'example.php'

// 添加参数
url = addURLParam(url, 'name', '纤风')
url = addURLParam(url, 'friend', '了凡')
// example.php?name=%E8%86%BE%E3%82%89%EF%BF%BD%EF%BF%BD&friend=%E7%AF%8B%EF%BF%BD%EF%BF%BD%EF%BF%BD
// 初始化请求
xhr.open('get', url, false)
// ....

1.4、POST 请求

使用评论仅次于 GET 的是 POST 请求,通常用于向服务器发送应该被保存的数据。POST 请求应该把数据作为请求的主体提交,POST请求的主体可以包含非常多的数据,而且格式不限。


第一步首先初始化一个 POST 请求。

xhr.open('post', 'example.php', true)

第二步是向 send() 方法中传入某些数据。
默认情况下,服务器对 POST 请求和 提交Web 表单的请求并不会一视同仁。因此,服务器端必须有程序来读取发送过来的原始数据,并从中解析出有用的部分。不过,我们可以使用 XHR 来模仿表单提交:
首先将 Content-type 头部信息设置为 application/x-www-form-urlencoded表单提交内容类型
其次是创建一个适当格式的字符串

xhr.onreadystatechange = function(event) {}
xhr.open('post', 'postexample.php', true)
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
xhr.send(serialize(document.forms[0])) // 序列化

关于更多的 请求类型风格规范,可以参考 restful接口风格

二、XMLHttpRequest 2级

鉴于 XHR 已经得到广泛接受,成为了事实标准,W3C 也着手制定相应的标准以规范其行为。XMLHttpRequest 1级 只是把已有的 XHR 对象的实现细节描述了出来。而XMLHttpRequest2 级则进一步发展了 XHR。

2.1、FormData

现代 Web 应用中频繁使用 的一项功能就是表单序列化,XMLHttpRequest 2级为此定义了 FormData 类型。FormData 为序列化表单以及创建于表单格式相同的数据提供了便利。
下面创建一个 FormData 对象,并向其中添加了一些数据。

var data = new FormData()
data.append('name', '风子')

append() 方法接受两个参数:

通过向 FormData 构造函数中传入表单元素,也可以用表单元素的数据预先向其中填入键值对儿

var data = new FormData(document.forms[0])

// ...
xhr.send(data)

使用 FormData的方便之处体现在 不必明确地在 XHR 对象上设置请求头。XHR 对象能够识别传入的数据类型是 FormData 的实例,并配置适当的头部信息。

2.2、超时设定

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

var xhr = new createXHR()
xhr.onreadystatechcange = fucntion(event) {}
xhr.open('get', 'timeout.php', true)
xhr.timeout = 1000  // 设置超时时间,1s
xhr.ontimeout = function(event) { // 超时 事件监听
  console.log('you are late')
}
xhr.send(null)

需要注意的是:超时的情况下,readyState 也可能为4。但这是很去访问 xhr.status 就会导致错误。为了避免这种错误,可以将检测 state属性的语句,包含在 try-catch 语句块中,

2.3、overrideMimeType() 方法

Firefox 最早引入了 overrideMimeType() 方法,用于重写 XHR 响应的 MIME 类型。这个方法后来也被纳入了 XMLHttpRequest 2级规范。因为返回响应的 MIME 类型决定了 XHR 对象如何处理它,所以提供一种方法能够重写服务器返回的MIME类型是很有用的。

xhr.overrideMimeType('text/xml')
xhr.send(null)

调用 overrideMimeType() 必须在 send() 方法之前,才能保证重写响应的 MIME 类型

三、进度事件

Progress Events规范,定义了与客户端服务器通信的有关事件。这些事件最早其实只针对 XHR 操作,但也被其它 API 借鉴,有以下6个进度事件。

这些事件大都很直观,但其中有两个事件有一些细节需要注意。

3.1、load 事件

Firefox 在实现XHR 对象的某个版本时,曾致力于简化异步交互模型。最终,Firefox 实现中引入了 load 事件,用以替代 readystatechange 事件。

onload 事件 处理程序会接收到一个 event 对象,其 target 属性,就指向XHR对象实例,因而可以访问到XHR对象的所有方法和属性。
然而,并非所有浏览器都为这个事件实现了适当的事件对象。结果,还是需要使用到xhr变量

var xhr = createXHR()
xhr.onload = function() {
    if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) { // 成功状态
      // todo
    } else { // 失败状态
      // todo
    }
}

// xhr.open()
// xhr.send()

3.2、progress 事件

Mozilla 对 XHR 的另一个 革新就是添加了 progress 事件,这个事件会在浏览器接受新数据期间周期性触发
onprogress 事件处理程序会接收到一个 event 对象,其 target 属性是 XHR 对象,但包含三个额外的属性:

有了这些信息,我们就可以为用户创建一个进度指示器

var xhr = new createXHR()

xhr.onload = function(event) {}

xhr.onprogress = function(event) {
  var divStatus = document.getElementById('status') // 用于显示进度的 DOM 元素

  if (event.lengthComputale) {
    divStatus.innerHTML = 'Received ' + event.postion + ' of ' + event.totalSize + ' bytes';
  }
}

xhr.open('get', 'altevents.php', true)
xhr.send(null)

为了确保正常执行,必须在调用 open() 方法之前添加 onporgress 事件处理程序。
如果响应头部中包含 Content-Length 字段,那么也可以利用此信息来计算从响应中已经接收到的数据的百分比。

四、跨域源资源共享

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

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

下面是 Origin 头部的一个示例:

Origin: http://www.nczoline.net

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

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

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

4.1、IE 对 CORS 的实现

微软在 IE8 中引入了 XDR(XDomainRequest)类型。这个对象与XHR类似,但能实现安全可靠的跨域通信。XDR对象的安全机制部分实现了对 W3C 的 CORS 规范。以下是 XDR 与 XHR 的一些不同之处。

XDR 对象的使用方法和 XHR 对象非常相似。也是创建一个 XDomainRequest 的实例,调用 open() 方法,再调用 send() 方法。丹玉 XHR 对象的 open() 方法不同,XDR对象的 open() 方法只接受两个参数:

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

var xdr = new XDomainRequest()
xdr.onload = function() {
  alert(xdr.respoonseText)
}
xdr.open('get', 'http://www.somewhere-else.com/pages/')
xdr.send(null)

只要响应有效就会触发 load 事件,如果失败(包括响应中缺少 Access-Control-Allow-Origin 头部)就会触发 error 事件。
要检测错误,可以像下面这样指定一个 onerror 事件处理程序

xdr.onerror = function() {
  alert('GG')
}

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

var xdr = new XDomainRequest()

xdr.onload = function() {
  alert(xdr.responseText)
}

xdr.onerror = function() {
  alert('An error occured')
}

xdr.timeout = 1000
xdr.ontimeout = function() {
  alert('Requesy 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() {
  // todo
}

xdr.onerror = function() {
  // todo
}

xdr.open('post', 'http://www.somewhere-else.com/page/')
xdr.contentType = 'application/x-www-form-urlencoded'
xdr.send('name1=value1&name2=value2')

4.2、其他浏览器对 CORS 的实现

Firefox3.5+、Safari4+、Chrome、iOS版 Safari 和 Android 平台中的 WebKit 都通过 XMLHttpRequest 对象实现了 对象 CORS 的原生支持。要请求位于另一个域中的资源,使用标准的 XHR 对象并在 open() 方法中传入绝对的 URL 即可。
例如:

var xhr = new XMLHttpRequest()

xhr.onreadystatechange = function() {
  // todo
}
xhr.open('get', 'http://www.xxx.com/xxx/', true)
xhr.send(null)

跨域的 XHR 对象也有一些限制,但为了安全这些限制时必须的。

4.3、Preflighted Reqeusts

CORS 通过 一种叫做 Preflighted Requests 的透明服务器验证机制支持开发人员使用 自定义的头部、GET 或 POST 之外的方法,以及不同类型的主体内容。在使用下列高级选项来发送请求时,就会向服务器发送一个 Preflight请求。这种请求使用 OPTIONS 方法,发送下列头部

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

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

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

例如:

Access-Control-Allow-Origin: http://www.nczonline.net
Access-Control-Allow-Methods: POST, GET, PUT
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000

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

4.4、带凭据的请求

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

Access-Control-Allow-Credentials: true

4.5、跨浏览器的 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') {
    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()
}

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

五、其他跨域技术

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

5.1、图像 Ping

第一种跨域请求技术是 使用 <img>标签。一个网页可以从任何网页中加载图像,不用担心跨域不跨域。也可以动态创建图像,使用它们的 onload 和 onerror 事件处理程序来确定是否接收到响应。
动态创建图像经常用于图像 Ping。图像Ping 是与服务器 进行简单、单向的跨域通信的一种方式。通过图像 Ping,浏览器得不到任何具体的数据,但通过侦听 load 和 error 事件,他能知道响应是什么时候接受到的。
如下示例:

var img = new Image()
img.onload = img.error = function() {
  console.log('Done')
}
img.src = 'http://www.example.com/test?name=Nicolas'

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

5.2、 JSONP

JSONP 是 JSON with padding(填充式 JSON 或 参数式JSON)的简写,JSONP 看起来与 JSON 差不多,只不过是被包含在函数调用中的 JSON,就像下面这样。

callback({"name": "Nicholas"})

JSONP 由两部分组成:

下面是一个典型的JSONP请求。

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

这里指定的回掉函数的名字叫 handleResponse()

JSONP 是通过动态 <script> 元素 来使用的,使用时可以为 src 属性指定一个跨域的 URL。因为 JSONP 是有效的 Javascript 代码,所以在请求完成后,即在 JSONP 响应加载到页面中以后,就会立即执行。
如下示例:

var script = document.createElement('script')
script.src = 'http://freegeoip.net/json/?callback=handleResponse'
document.body.insertBefore(script, document.body.firstChild)

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

5.3、Comet

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

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

短轮询

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

长轮询

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


第二种流行的 Comet 实现就是 HTTP 流。流不同于上述两种轮询,因为它能在页面的整个生命周期内只使用一个 HTTP 连接。具体来说,就是浏览器向服务器发送一个请求,而服务器保持连接打开,然后服务器周期性地向浏览器发送数据
而浏览器通过侦听 readystatechange 事件及检测 readtState 的值是否为3,就可以利用 XHR 对象实现 HTTP 流。随着不断从服务器接收数据,readyState 的值会周期性地变为3.当 readyState 变成3时,responseText 属性中就会保存接收的 所有数据。此时,就需要比较此前接收到的数据,决定从什么位置开始取得最新的数据。
使用 XHR 对象实现HTTP流的 典型代码如下

function createSteamingClient(url, progress, finished) {

  var xhr = new XMLHttpRequest()
  var 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()

    } else if(xhr.readyState == 4) {
      finished(xhr.responseText)
    }
  }
  xhr.send(null)
  return xhr
}

var client = createSteamingClient('streaming.php', function(data) {
  console.log('Received' + data)
}, function(data) {
  console.log('Done')
})

5.4、服务器发送事件

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

5.4.1、SSE API

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

var source = new EventSource('myevents.php')

传入的 URL 必须与创建对象的页面同源。EventSource 的实例有一个 readyState属性,

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

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

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

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

source.close()
5.4.2、事件流

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

data: foo

data: bar

data: foo
data: bar

只有在包含 data: 的数据航后面有空行时,才会触发 message 事件

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

data: foo
id: 1

设置了 ID 后,EventSource 对象会跟踪上一次触发的事件。如果断开连接,会向服务器发送一个包含为 Last-Event-ID 的特殊 HTTP 头部的请求,以便服务器知道下一次该触发哪个事件。

5.5、Web Sockets

要说最令人津津乐道的新浏览器API,就得数 Web Sockets了。Web Sockets 的目标是一个单独的持久连接上提供全双工、双向通信。在JavaScript中创建了 Web Sockets 之后,会有一个 HTTP 请求发送到浏览器已发起连接。在取得服务器响应后,建立的连接会使用HTTP 升级从 HTTP 协议交换为 Web Sockets 协议。

由于 Web Sockets 使用了自定义的协议,所以 URL 模式也略有不同,未加密的连接不再是 http:// 而是 ws://;加密的连接也不再是https://,而是 wss://

5.5.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 并不相同。
如下所示:

WebSocket 没有 readystatechange 事件;不过,他有其他事件,对应值不同的状态。readyState 的值永远 从0开始。
要关闭 WebSockets 的连接,可以在任何时候调用 close() 方法。

socket.close()
5.5.2、发送和接收数据

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

var socket = new WebSocket('ws://www.example.com/server.php')
socket.send('Hello World!')

WebSocket 只能通过连接发送纯文本数据,所以对于复杂的数据结构,再通过连接发送之前必须进行序列化

var message = {
  time:new Date(),
  text:"Hello world!",
  cliendId: "asdfp56sd"
}

socket.send(JSON.stringify(message))

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

socket.onmessage = function(event) {
  var data = event.data
}

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

5.5.3、其他事件

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

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

var socket = new WebSocket('ws://example.com/server.php')

socket.onopen = function() {
  console.log('Connection established')
}

socket.onerror = function() {
  console.log('Connection error')
}

socket.onclose = function() {
  console.log('Connection close')
}

其中 close 事件的 event对象有额外的信息,这个事件的事件对象有三个额外的属性:

5.6、SSE 与 Web Sockets

面对某个具体的用例。在考虑使用 SSE 还是 Web Sockets 时,可以考虑如下几个因数。

六、安全

讨论 Ajax 和 Comet 安全的文字可谓是连篇累牍,但我们可以从普通意义上探讨一些基本问题。

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

/getuserinfo.php?id = 23

无法保证别人不会将这个 URL 的用户 ID 改为24、25或其他值。因此,getuserinfo.php 必须知道请求者 是否真的有权限要请求的数据;否则,你的服务器就会门户大开,任何人的数据都可能被泄漏出去。

对于未被授权系统有权访问某个资源的情况,我们称之为 CSRF(Cross-Site Request Forgery,跨站点请求伪造)。未被授权系统会伪装自己,让处理请求的服务器认为它是合法的。
为确保通过 XHR 访问的 URL 安全,同喜的做法就是验证发送请求者是否有权限访问相应的资源。有下列几种方式可供选择:

XHR 对象也提供了一些安全机制,虽然表面上看可以保证安全,但实际上却相当不可靠。

七、 小结

Ajax 是无需刷新页面就能够从服务器取得数据的一种方法。

上一篇 下一篇

猜你喜欢

热点阅读