I. WebSocket

WebSocket is a two-way communication protocol that uses the HTTP/1.1 protocol in the handshake phase (HTTP/2 is not supported at this time).

The handshake process is as follows.

  1. First the client initiates a special HTTP request to the server with the following message header.

    1
    2
    3
    4
    5
    6
    7
    8
    
    GET /chat HTTP/1.1  // 请求行
    Host: server.example.com
    Upgrade: websocket  // required
    Connection: Upgrade // required
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // required,一个 16bits 编码得到的 base64 串
    Origin: http://example.com  // 用于防止未认证的跨域脚本使用浏览器 websocket api 与服务端进行通信
    Sec-WebSocket-Protocol: chat, superchat  // optional, 子协议协商字段
    Sec-WebSocket-Version: 13
    
  2. If the server side supports this version of WebSocket, it will return a 101 response with the following response header.

    1
    2
    3
    4
    5
    
    HTTP/1.1 101 Switching Protocols  // 状态行
    Upgrade: websocket   // required
    Connection: Upgrade  // required
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= // required,加密后的 Sec-WebSocket-Key
    Sec-WebSocket-Protocol: chat // 表明选择的子协议
    

Once the handshake is complete, the next TCP packets are all frames of the WebSocket protocol.

As you can see, the handshake here is not a TCP handshake, but a handshake inside the TCP connection, from HTTP/1.1 upgrade to WebSocket.

WebSocket provides two protocols: unencrypted ws:// and encrypted wss:// . Because it uses the HTTP handshake, it uses the same ports as HTTP: ws is 80 (HTTP) and wss is 443 (HTTPS)

In Python programming, an asynchronous WebSocket client and server can be implemented using websockets. WebSocket support is also provided by aiohttp.

Note: If you search for Flask’s WebScoket plugin, the first result you get is most likely Flask-SocketIO. But Flask-ScoektIO uses its own proprietary SocketIO protocol and is not a standard WebSocket, it just happens to provide the same functionality as a WebSocket.

The advantage of SocketIO is that the protocol is supported as long as the Web side uses SocketIO.js. The pure WS protocol, on the other hand, is only supported by newer browsers. For non-Web client cases, a better choice might be to use Flask-Sockets.

JS API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// WebSocket API
var socket = new WebSocket('ws://websocket.example.com');

// Show a connected message when the WebSocket is opened.
socket.onopen = function(event) {
  console.log('WebSocket is connected.');
};

// Handle messages sent by the server.
socket.onmessage = function(event) {
  var message = event.data;
  console.log(message);
};

// Handle any error that occurs.
socket.onerror = function(error) {
  console.log('WebSocket Error: ' + error);
};

II. HTTP/2

HTTP/2 was standardized in 2015 with the main purpose of optimizing performance. Its features are as follows.

  • Binary protocol: HTTP/2 uses binary format for message headers instead of text format. It is compressed using a specially designed HPack algorithm.
  • Multiplexing: This means that HTTP/2 can reuse the same TCP connection, and the connection is multiplexed so that multiple requests or responses can be transmitted at the same time.
    • In contrast, HTTP/1.1’s long connections can also reuse TCP connections, but only serially, not “multiplexed”.
  • Server Push: The server can push resources directly to the client, so that when the client needs the files, they are already on the client. (The push is hidden from the Web App and is handled by the browser)
  • HTTP/2 allows to cancel a certain data stream being transferred (by sending RST_STREAM frames) without closing the TCP connection.
    • This is one of the benefits of the binary protocol, the possibility to define data frames for multiple functions.

It allows the server to push resources to the client’s cache. When we visit sites like Taobao, we often find that many requests have a header that says “provisional headers are shown”, which usually means that the resources were loaded directly from the cache, so the request wasn’t sent at all. Looking at the Size column of Chrome Network, this field is usually from disk cache or from memroy cache for such requests.

Chrome can see what protocol the request is using by looking at.

image

2019-02-10: Using Chrome, the mainstream sites are basically already partially using HTTP/2, Zhihu, bilibili, GIthub use the wss protocol, and many sites use SSE (format like data:image/png;base64,<base64 string>) And many sites use HTTP/2 + QUIC, the new name for the protocol is HTTP/3, which is a UDP-based HTTP protocol.

SSE

Server Side Push Event is a feature that pushes information over HTTP long connections. It starts with the browser establishing an HTTP long connection to the server, which then continuously pushes messages to the browser over this long connection.JS API is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// create SSE connection
var source = new EventSource('/dates');

// 连接建立时,这些 API 和 WebSocket 的很相似
source.onopen = function(event) {
  // handle open event
};

// 收到消息时(它只捕获未命名 event)
source.onmessage = function(event) {
  var data = event.data;  // 发送过来的实际数据(string)
  var origin = event.origin;  // 服务器端URL的域名部分,即协议、域名和端口。
  var lastEventId = event.lastEventId;  // 数据的编号,由服务器端发送。如果没有编号,这个属性为空。
  // handle message
};

source.onerror = function(event) {
  // handle error event
};

Specific implementation

Upon receiving an SSE request (HTTP protocol) from the client, the server returns the following response headers.

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

As for the body section, SSE defines four types of information: 1.

  1. data : data field
  2. event: custom data type
  3. id : data id
  4. retry: maximum interval time, reconnect if timeout

body Example.

 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
: 这种格式的消息是注释,会被忽略\n\n
: 通常服务器每隔一段时间就会发送一个注释,防止超时 retry\n\n

: 下面这个是一个单行数据\n\n
data: some text\n\n

: 下面这个是多行数据,在客户端会重组成一个 data\n\n
data: {\n
data: "foo": "bar",\n
data: "baz", 555\n
data: }\n\n

: 这是一个命名 event,只会被事件名与之相同的 listener 捕获\n\n
event: foo\n
data: a foo event\n\n

: 未命名事件,会被 onmessage 捕获\n\n
data: an unnamed event\n\n

event: bar\n
data: a bar event\n\n

: 这个 id 对应 event.lastEventId\n\n
id: msg1\n
data: message\n\n

Comparison of WebSocket, HTTP/2 and SSE

  • Encrypted or not.
    • WebSocket supports plaintext communication ws:// and encrypted wss://.
    • While the HTTP/2 protocol does not require encryption, mainstream browsers only support HTTP/2 over TLS.
    • SSE is the HTTP protocol used to communicate, supporting http/https
  • Message push.
    • WebSocket is a full-duplex channel that can communicate in both directions. And messages are pushed directly to the Web App.
    • SSE can only serially push data from the server to the Web App in one direction.
    • HTTP/2 also supports Server Push, but the server can only actively push resources to the client cache! It does not allow data to be pushed to the Web App itself running in the client. Server pushes can only be handled by the browser and do not populate the server data in the application code, which means the application does not have an API to get notified of these events.
      • To push data to the Web App in near real-time, HTTP/2 can be used in conjunction with SSE (Server-Sent Event).

WebSocket is useful in areas where near real-time two-way communication is required. And HTTP/2 + SSE is suitable for presenting real-time data.

Also in the case of non-browser clients, it is possible to use HTTP/2 without encryption.

requests to see the HTTP protocol version number

The HTTP version number of the response can be obtained from resp.raw.version.

1
2
3
4
>>> import requests
>>> resp = requests.get("https://zhihu.com")      
>>> resp.raw.version
11

But requests uses HTTP/1.1 by default, and does not support HTTP/2. (But this is not a big problem, HTTP/2 is just a performance optimization, using HTTP/1.1 is just a little slower.)

III. gRPC Protocol

gRPC is a remote procedure call framework that uses protobuf3 by default for efficient serialization of data and service definition, and HTTP/2 for data transfer. The protocol discussed here is gRPC over HTTP/2.

Currently gRPC is mainly used in microservice communication, but because of its superior performance, it is also well suited for scenarios such as gaming and loT that require high performance and low latency.

In fact, in terms of protocol sophistication alone, gRPC basically surpasses REST:

  • Using binary for data serialization, which is more traffic efficient and faster than json for serialization and deserialization.
  • protobuf3 requires the api to be fully and clearly defined, while the REST api can only be defined by the programmer himself.
  • gRPC officially supports code generation from api definitions, while REST api requires third-party tools such as openapi-codegen.
  • Supports 4 communication modes: one-to-one (unary), client-side flow, server-side flow, and dual-side flow. More flexible

Only the current gRPC support for broswer is still not very good, if you need to access the api through the browser, then gRPC may not be your cup of tea. If your product is only intended for controlled clients like apps, consider gRPC.

For applications that need to serve both the browser and the APP, you can also consider that the APP uses the gRPC protocol, while the browser uses the HTTP interface provided by the API gateway to perform HTTP - gRPC protocol conversion on the API gateway.

gRPC over HTTP/2 Definition

See the official document gRPC over HTTP/2 for a detailed definition.

Here are a few brief points.

  • gRPC completely hides the semantics of HTTP/2 itself, such as method, headers, path, etc. This information is completely invisible to the user.
    • Requests are uniformly POST, and the response status is uniformly 200. HTTP status codes in the response are completely ignored as long as the response is in the standard gRPC format.
  • gRPC defines its own status code, a fixed format path, and its own headers.