WebSocket Programming
WebSocket is also a common application layer protocol in network programming. Like HTTP, it is also TCP-based and achieves wide adoption in web server app development.
The difference lies in that WebSocket requires only a handshake between the client and server to establish a persistent connection for bidirectional data transfer. In other words, the server implemented based on WebSocket can actively transmit data to the client, achieving real-time communication.
WebSocket is an independent protocol whose handshake is interpreted as an upgrade request by the HTTP server. Therefore, Cangjie incorporates WebSocket into the HTTP packet.
Cangjie abstracts the WebSocket protocol communication mechanism as a WebSocket class. It provides a method to upgrade an HTTP/1.1 or HTTP/2.0 server handle to a WebSocket protocol instance, and realizes communication (for example, data packet reading and writing) through the returned WebSocket instance.
In Cangjie, a basic unit of data transferred by WebSocket is referred to as a frame. Frames are classified into two types. The first type is for transferring control information, such as a Close frame for closing a connection, the Ping frame for implementing keepalive, and the Pong frame, which is a response to the Ping frame. The second type is for transferring app data, which supports segmented transfer.
A Cangjie frame consists of three attributes, in which fin and frameType collectively indicate whether the frame is segmented and the frame type, and payload indicates the frame payload. Other attributes can be ignored during a packet transfer.
The following example shows a WebSocket handshake and the message sending and receiving process: An HTTP client and server are created to initiate a WebSocket upgrade (or handshake) separately, and frames are read and written after the handshake is successful.
import net.http.*
import encoding.url.*
import std.time.*
import std.sync.*
import std.collection.*
import std.log.*
let server = ServerBuilder()
.addr("127.0.0.1")
.port(0)
.build()
// client:
main() {
// 1. Start the server.
spawn { startServer() }
sleep(Duration.millisecond * 200)
let client = ClientBuilder().build()
let u = URL.parse("ws://127.0.0.1:${server.port}/webSocket")
let subProtocol = ArrayList<String>(["foo1", "bar1"])
let headers = HttpHeaders()
headers.add("test", "echo")
// 2. Complete the WebSocket handshake and obtain the WebSocket instance.
let websocket: WebSocket
let respHeaders: HttpHeaders
(websocket, respHeaders) = WebSocket.upgradeFromClient(client, u, subProtocols: subProtocol, headers: headers)
client.close()
println("subProtocol: ${websocket.subProtocol}") // fool1
println(respHeaders.getFirst("rsp") ?? "") // echo
// 3. Send and receive a message.
// Send "hello."
websocket.write(TextWebFrame, "hello".toArray())
// Receive a message.
let data = ArrayList<UInt8>()
var frame = websocket.read()
while(true) {
match(frame.frameType) {
case ContinuationWebFrame =>
data.appendAll(frame.payload)
if (frame.fin) {
break
}
case TextWebFrame | BinaryWebFrame =>
if (!data.isEmpty()) {
throw Exception("invalid frame")
}
data.appendAll(frame.payload)
if (frame.fin) {
break
}
case CloseWebFrame =>
websocket.write(CloseWebFrame, frame.payload)
break
case PingWebFrame =>
websocket.writePongFrame(frame.payload)
case _ => ()
}
frame = websocket.read()
}
println("data size: ${data.size}") // 4097
println("last item: ${String.fromUtf8(Array(data)[4096])}") // a
// 4. Close the WebSocket.
// Receive and send CloseFrame.
websocket.writeCloseFrame(status: 1000)
let websocketFrame = websocket.read()
println("close frame type: ${websocketFrame.frameType}") // CloseWebFrame
println("close frame payload: ${websocketFrame.payload}") // 3, 232
// Close the low-level connection.
websocket.closeConn()
server.close()
}
func startServer() {
// 1. Register the handler.
server.distributor.register("/webSocket", handler1)
server.logger.level = OFF
server.serve()
}
// server:
func handler1(ctx: HttpContext): Unit {
// 2. Complete the WebSocket handshake and obtain the WebSocket instance.
let websocketServer = WebSocket.upgradeFromServer(ctx, subProtocols: ArrayList<String>(["foo", "bar", "foo1"]),
userFunc: {request: HttpRequest =>
let value = request.headers.getFirst("test") ?? ""
let headers = HttpHeaders()
headers.add("rsp", value)
headers
})
// 3. Send and receive a message.
// Receive "hello."
let data = ArrayList<UInt8>()
var frame = websocketServer.read()
while(true) {
match(frame.frameType) {
case ContinuationWebFrame =>
data.appendAll(frame.payload)
if (frame.fin) {
break
}
case TextWebFrame | BinaryWebFrame =>
if (!data.isEmpty()) {
throw Exception("invalid frame")
}
data.appendAll(frame.payload)
if (frame.fin) {
break
}
case CloseWebFrame =>
websocketServer.write(CloseWebFrame, frame.payload)
break
case PingWebFrame =>
websocketServer.writePongFrame(frame.payload)
case _ => ()
}
frame = websocketServer.read()
}
println("data: ${String.fromUtf8(Array(data))}") // hello
// Send letter a for 4097 times.
websocketServer.write(TextWebFrame, Array<UInt8>(4097, item: 97))
// 4. Close the WebSocket.
// Receive and send CloseFrame.
let websocketFrame = websocketServer.read()
println("close frame type: ${websocketFrame.frameType}") // CloseWebFrame
println("close frame payload: ${websocketFrame.payload}") // 3, 232
websocketServer.write(CloseWebFrame, websocketFrame.payload)
// Close the low-level connection.
websocketServer.closeConn()
}
The execution result of the example code is as follows:
subProtocol: foo1
echo
data: hello
data size: 4097
last item: a
close frame type: CloseWebFrame
close frame payload: [3, 232]
close frame type: CloseWebFrame
close frame payload: [3, 232]