web推送技术 | 一枝秋雨

web推送技术

Q: 基础知识

Ajax 是一种从页面向服务器请求数据的技术,Comet 是一种服务器向页面推送数据的技术。

Comet 实现方式分为长轮询和流。

短轮询就是传统轮询的翻版,即浏览器定时向服务器发送数据,看看有没有数据更新。

长轮询是页面发起一个到服务器的请求,然后服务器一直保持打开,知道有数据才发送。发送完数据之后,连接关闭,随即发起一个新的请求。

无论短轮询、长轮询,浏览器都要在接收数据之前先发起对服务器的连接,两者最大的区别在于服务器如何发送数据。短轮询是服务器立即发送响应,无论是否有数据。而长轮询是等待发送响应。

轮询的优势是浏览器都支持 XHR 和 setInterval()。

timeout 超时设定,可以在规定的时间内浏览器没有接到响应,会触发 timeout 事件进而调用 ontimeout 事件处理程序。

Q: 轮询

短轮询

利用 XHR,通过 setInterval() 定时发送请求,但会造成数据同步不及时和无效的请求,增加后端处理压力。

setInterval(function() {
  $.ajax({
    url: "http://127.0.0.0.1:8080",
    success: function() {
        //code from here
    }
  });
}, 1000);

优点: 实现简单,无需做过多的更改

缺点: 轮询的间隔过长,会导致用户不能及时接收到更新的数据;轮询的间隔过短,会导致查询请求过多,增加服务器端的负担。

长轮询

在短轮询基础上做了一些改进,在没有更新的时候不再返回空响应,而且把连接保持到有更新的时候。

客户端向服务器发送 Ajax 请求,服务器接到请求后 hold 住连接,直到有新消息才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求,通常把这种实现也叫做 Comet。

function comet() {
  $.ajax({
    url: "http://127.0.0.0.1:8080",
    success: function() {
      comet();
      //code
    }
  });
}

通常的做法是,在服务器的程序中加入一个死循环,在循环中监测数据的变动。当发现新数据时,立即将其输出给浏览器并断开连接,浏览器在收到数据后,再次发起请求以进入下一个周期。

优点: 对短轮询(Polling)做了优化,有较好的时效性

缺点: 需第三方库支持,实现较为复杂;每次连接只能发送一个数据,多个数据发送时耗费服务器性能

HTTP 流

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

所有的服务器都支持打印到输出缓存然后刷新,这是实现 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.responsText.substring(received);
      received += result.length;
 
      // 调用 progress 
      progress(result);
    } else if (xhr.readystate === 4) {
      finished(xhr.responsText)
    }
  }
 
  xhr.send(null);
  return xhr;
}

通过监听 onreadystatechange 事件及检测 readystate 的值,可以利用 XHR 对象实现 HTTP 流,随着不断从服务器接收收据,readystate 的值会周期性的变为 3。当 readystate 值为 3 时,responsText 中保存接收到所有数据,此时需要根据之前接收到的数据,决定从什么位置开始取得最新的数据。 

Q: Server-sent-events(SSE)

概述

SSE 让服务端可以向客户端流式发送文本消息,在客户端浏览器中增加 EventSource 对象,使其能通过事件的方式接收到服务器推送的消息,在服务端,使用长连接的事件流协议,即请求响应时增加新数据流数据格式。

非常适应于后端数据更新频繁且对实时性要求较高而又不需要客户端向服务端通信的场景下。

source.addEventListener("message", function(e) {
  console.log(e.data);
}, false);
 
source.addEventListener("open", function(e) {
  // Connection was opened.
}, false);
 
source.addEventListener("error", function(e) {
  if (e.readyState == EventSource.CLOSED) {
    // Connection was closed.
  }
}, false);
 
source.addEventListener("userlogin", function(e) {
  console.log(e.data);
}, false);

SSE 事件流以流式 HTTP 响应请求,客户端发起普通的 HTTP 请求,服务器以自定义的 text/event-stream 内容类型响应,然后通过事件传递数据。

响应头

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

响应数据格式

id: 123\n
retry: 10000\n 
event: userlogin\n 
data: {"username": "slogeor"}\n\n

id: 在连接中断时恢复中断过程中丢失的消息,服务器在响应时可以给每条消息关联任意的 ID 字符串,浏览器会自动记录最后一次接收到消息 ID,并在发送重新连接请求时自动在 HTTP 请求头中追加 Last-Event-ID,以便服务器知道下一次该触发哪个事件

retry: 设置中断后重连时间间隔

event: 指定消息类型给

data: 通过字符串的方式传递数据

API

new 

var source = new EventSource("http://127.0.0.1:8080"); 

传入的 URL 必须与创建对象的页面同源(域名、端口号、协议)

readyState 属性 {
  0: 连接到服务器
  1: 打开连接
  2: 关闭连接
}

三个事件

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

总结

优点

  • 基于现有 HTTP 协议,实现简单
  • 断开后自动重联,并可设置重联超时
  • 派发任意事件
  • 跨域并有相应的安全过滤

缺点

  • 只能单向通信,服务器端向客户端推送事件
  • 事件流协议只能传输 UTF-8 数据,不支持二进制流
  • IE 下目前所有不支持 EventSource

Q: Web Sockets

概述

Web Sockets 的目标是在一个单独的持久连接上提供全双工、双向通信。在 JavaScript 中创建了 Web Sockets 之后,会有一个 HTTP 请求发送到浏览器以发起连接。在取得服务器响应后,建立的连接会使用 HTTP 升级为 HTTP 协议交换为 Web Scoket 协议。

Web Sockets 使用自定义协议,未加密的连接是 ws://,加密的是 wss://。

var socket = new WebSocket("ws://127.0.0.1:8080");
 
// When the connection is open, send some data to the server
socket.onopen = function () {
  socket.send("Ping"); 
};
 
// Log errors
socket.onerror = function (error) {
  console.log("WebSocket Error " + error);
};
 
// Log messages from the server
socket.onmessage = function (e) {
  console.log("Server: " + e.data);
};
// Sending String
socket.send("your message");
 
 
// Sending canvas ImageData as ArrayBuffer
var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
socket.send(binary.buffer);
 
// Sending file as Blob
var file = document.querySelector("input[type="file"]").files[0];
socket.send(file);

Web Sockets 的好处能够在客户端和服务器之间发送非常少量的数据,而不必担心 HTTP 那样字节级的开销,由于传递的数据包非常小,因此非常适合移动应用。

API

new

var socket = new WebSocket("ws://127.0.0.1:8080");

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

WebSocket 有一个表示当前状态的 readystate 属性 {
  WebSocket.OPENING(0): 正在建立连接
  WebSocket.OPEN(1): 已经建立连接
  WebSocket.CLOSING(2): 正在关闭连接
  WebSocket.CLOSE(3): 已经关闭连接
}

socket.send("hello world");

send() 只发送纯文本数据,对于复杂数据需要先进行序列化。

message 事件

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

其他事件

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

使用场景

适合于对数据的实时性要求比较强的场景,如通信、股票、Feed、直播、共享桌面,特别适合于客户端与服务频繁交互的情况下,如实时共享、多人协作等平台。

总结

优点

  • 真正的全双工通信
  • 支持跨域设置(Access-Control-Allow-Origin)

缺点

  • 采用新的协议,后端需要单独实现
  • 客户端并不是所有浏览器都支持
  • 代理服务器会有不支持websocket的情况
  • 无超时处理
  • 更耗电及占用资源

Q: SSE 和 Web Sockets 区别

主要因素

是否有自由度建立和维护 Web Sockets 服务器? 现有的服务器不能用于 Web Sockets 通信,SSE 可以通过常规的 HTTP 通信。

是否需要双向通信。如果只需读取服务器数据,SSE 比较容易实现,如果需要双向通信,则 Web Sockets 显然比较好。(XHR + SSE 也能实现双向通信)

兼容情况

SSE

Web Sockets

Q: 安全

对于未被授权系统有权访问某个资源的情况,我们称作为 CSRF(跨站点请求伪造)

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

下面方法对防范 CSRF 攻击不起作用

  1. 要求发生 POST 而不是 GET 请求 –> 很容易改变
  2. 检查来源 URL 以确定是否可信 –> 来源记录很容易伪造
  3. 基于 cookie 信息进行验证 –> 同样很容易伪造

Q: 参考链接

http://www.w3ctech.com/topic/1754

http://ju.outofmemory.cn/entry/201991

面试题