Implementation of chat tool based on Serverless and Websocket

Posted by markepic on Wed, 27 May 2020 09:47:32 +0200

It is not difficult for traditional business to implement Websocket. However, function calculation is basically event driven and does not support long link operation. If the function calculation is combined with the API gateway, can there be a Websocket implementation scheme?

Implementation of Websocket with API gateway trigger

WebSocket protocol is a new network protocol based on TCP. It realizes full duplex communication between browser and server, that is, it allows server to send information to client actively. WebSocket can actively send data to the client when there is data push demand on the server. The original HTTP protocol server can only obtain the data to be pushed by polling or long poll.

Because cloud functions are stateless and run in trigger mode, they will be triggered only when an event arrives. Therefore, in order to implement WebSocket, the cloud function SCF is combined with the API gateway to receive and maintain the connection with the client through the API gateway. You can think that cloud function and API gateway together realize the server. When a message is sent from the client, it will be delivered to the API gateway first, and then the API gateway will trigger the execution of the cloud function. When the cloud function of the service end wants to send a message to the client, the cloud function will first POST the message to the reverse push link of the API gateway, and then the API gateway will push the message to the client.

The specific implementation architecture is as follows:

For the whole life cycle of WebSocket, it mainly consists of the following events:

  • Connection establishment: the client requests the server to establish a connection and complete the connection establishment;
  • Data uplink: the client sends data to the server through the established connection;
  • Data downlink: the server sends data to the client through the established connection;
  • Client disconnect: the client requests to disconnect the established connection;
  • Server disconnect: the server requests to disconnect the established connection.

For the events in the whole WebSocket life cycle, the processing procedures of cloud functions and API gateways are as follows:

  • Connection establishment: the client establishes WebSocket connection with API gateway, which sends the connection establishment event to SCF;
  • Data uplink: the client sends data through WebSocket, and the API gateway forwards the data to SCF;
  • Data downlink: SCF sends a request to the push address specified by the API gateway, and the API gateway will send the data to the client through WebSocket after receiving it;
  • Client disconnection: when the client requests disconnection, the API gateway sends the disconnection event to SCF;
  • Server disconnect: SCF sends a disconnect request to the push address specified by API gateway, and the API gateway disconnects WebSocket after receiving the request.

Therefore, the interaction between cloud functions and API gateways needs to be hosted by three types of cloud functions:

  • Registration function: trigger this function when establishing WebSocket connection between client initiation and API gateway to inform SCF of secConnectionID of WebSocket connection. In this function, secConnectionID is usually recorded in persistent storage for backward push of subsequent data;
  • Clean up function: this function is triggered when the client initiatively initiates the WebSocket connection interruption request to inform the SCF of the secConnectionID to disconnect. The secConnectionID recorded in the persistent store is usually cleared in this function;
  • Transfer function: trigger this function when the client sends data through WebSocket connection to inform the secConnectionID of SCF connection and the data sent. Business data is usually processed in this function. For example, whether to push data to another secConnectionID in the persistent store.

Implementation of Websocket function

According to the overall architecture of this function provided by Tencent cloud official website:

Here we can use object store COS as a persistence scheme. When the user establishes a link store ConnectionId into COS, when the user disconnects, the link ID will be deleted.

Where registration function:

# -*- coding: utf8 -*-
import os
from qcloud_cos_v5 import CosConfig
from qcloud_cos_v5 import CosS3Client

bucket = os.environ.get('bucket')
region = os.environ.get('region')
secret_id = os.environ.get('secret_id')
secret_key = os.environ.get('secret_key')
cosClient = CosS3Client(CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key))


def main_handler(event, context):
    print("event is %s" % event)

    connectionID = event['websocket']['secConnectionID']

    retmsg = {}
    retmsg['errNo'] = 0
    retmsg['errMsg'] = "ok"
    retmsg['websocket'] = {
        "action": "connecting",
        "secConnectionID": connectionID
    }

    cosClient.put_object(
        Bucket=bucket,
        Body='websocket'.encode("utf-8"),
        Key=str(connectionID),
        EnableMD5=False
    )

    return retmsg

Transfer function:

# -*- coding: utf8 -*-
import os
import json
import requests
from qcloud_cos_v5 import CosConfig
from qcloud_cos_v5 import CosS3Client

bucket = os.environ.get('bucket')
region = os.environ.get('region')
secret_id = os.environ.get('secret_id')
secret_key = os.environ.get('secret_key')
cosClient = CosS3Client(CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key))

sendbackHost = os.environ.get("url")


def Get_ConnectionID_List():
    response = cosClient.list_objects(
        Bucket=bucket,
    )
    return [eve['Key'] for eve in response['Contents']]


def send(connectionID, data):
    retmsg = {}
    retmsg['websocket'] = {}
    retmsg['websocket']['action'] = "data send"
    retmsg['websocket']['secConnectionID'] = connectionID
    retmsg['websocket']['dataType'] = 'text'
    retmsg['websocket']['data'] = data
    requests.post(sendbackHost, json=retmsg)


def main_handler(event, context):
    print("event is %s" % event)

    connectionID_List = Get_ConnectionID_List()
    connectionID = event['websocket']['secConnectionID']
    count = len(connectionID_List)
    data = event['websocket']['data'] + "(===Online people:" + str(count) + "===)"
    for ID in connectionID_List:
        if ID != connectionID:
            send(ID, data)

    return "send success"

Cleanup function:

# -*- coding: utf8 -*-
import os
import requests
from qcloud_cos_v5 import CosConfig
from qcloud_cos_v5 import CosS3Client

bucket = os.environ.get('bucket')
region = os.environ.get('region')
secret_id = os.environ.get('secret_id')
secret_key = os.environ.get('secret_key')
cosClient = CosS3Client(CosConfig(Region=region, SecretId=secret_id, SecretKey=secret_key))

sendbackHost = os.environ.get("url")


def main_handler(event, context):
    print("event is %s" % event)

    connectionID = event['websocket']['secConnectionID']

    retmsg = {}
    retmsg['websocket'] = {}
    retmsg['websocket']['action'] = "closing"
    retmsg['websocket']['secConnectionID'] = connectionID
    requests.post(sendbackHost, json=retmsg)

    cosClient.delete_object(
        Bucket=bucket,
        Key=str(connectionID),
    )

    return event

Yaml file is as follows:

Conf:
  component: "serverless-global"
  inputs:
    region: ap-guangzhou
    bucket: chat-cos-1256773370
    secret_id: 
    secret_key: 

myBucket:
  component: '@serverless/tencent-cos'
  inputs:
    bucket: ${Conf.bucket}
    region: ${Conf.region}

restApi:
  component: '@serverless/tencent-apigateway'
  inputs:
    region: ${Conf.region}
    protocols:
      - http
      - https
    serviceName: ChatDemo
    environment: release
    endpoints:
      - path: /
        method: GET
        protocol: WEBSOCKET
        serviceTimeout: 800
        function:
          transportFunctionName: ChatTrans
          registerFunctionName: ChatReg
          cleanupFunctionName: ChatClean


ChatReg:
  component: "@serverless/tencent-scf"
  inputs:
    name: ChatReg
    codeUri: ./code
    handler: reg.main_handler
    runtime: Python3.6
    region:  ${Conf.region}
    environment:
      variables:
        region: ${Conf.region}
        bucket: ${Conf.bucket}
        secret_id: ${Conf.secret_id}
        secret_key: ${Conf.secret_key}
        url: http://set-gwm9thyc.cb-guangzhou.apigateway.tencentyun.com/api-etj7lhtw

ChatTrans:
  component: "@serverless/tencent-scf"
  inputs:
    name: ChatTrans
    codeUri: ./code
    handler: trans.main_handler
    runtime: Python3.6
    region:  ${Conf.region}
    environment:
      variables:
        region: ${Conf.region}
        bucket: ${Conf.bucket}
        secret_id: ${Conf.secret_id}
        secret_key: ${Conf.secret_key}
        url: http://set-gwm9thyc.cb-guangzhou.apigateway.tencentyun.com/api-etj7lhtw

ChatClean:
  component: "@serverless/tencent-scf"
  inputs:
    name: ChatClean
    codeUri: ./code
    handler: clean.main_handler
    runtime: Python3.6
    region:  ${Conf.region}
    environment:
      variables:
        region: ${Conf.region}
        bucket: ${Conf.bucket}
        secret_id: ${Conf.secret_id}
        secret_key: ${Conf.secret_key}
        url: http://set-gwm9thyc.cb-guangzhou.apigateway.tencentyun.com/api-etj7lhtw

Note that the API gateway needs to be deployed first. When the deployment is completed, the push back address is obtained and written to the environment variable of the corresponding function in the form of url:

Theoretically, it should be able to pass${ restApi.url[0].internalDomain} automatically gets the URL, but I didn't get the URL successfully. I had to deploy the API gateway first, get the address, and then redeploy.

After deployment, we can write HTML code to realize visual Websocket Client. The core JavaScript code is:

window.onload = function () {
    var conn;
    var msg = document.getElementById("msg");
    var log = document.getElementById("log");

    function appendLog(item) {
        var doScroll = log.scrollTop === log.scrollHeight - log.clientHeight;
        log.appendChild(item);
        if (doScroll) {
            log.scrollTop = log.scrollHeight - log.clientHeight;
        }
    }

    document.getElementById("form").onsubmit = function () {
        if (!conn) {
            return false;
        }
        if (!msg.value) {
            return false;
        }
        conn.send(msg.value);
        //msg.value = "";
		
		var item = document.createElement("div");
		item.innerText = "send out↑:";
		appendLog(item);
		
		var item = document.createElement("div");
		item.innerText = msg.value;
		appendLog(item);
		
        return false;
    };

    if (window["WebSocket"]) {
        //Replace with websocket connection address
        conn = new WebSocket("ws://service-01era6ni-1256773370.gz.apigw.tencentcs.com/release/");
        conn.onclose = function (evt) {
            var item = document.createElement("div");
            item.innerHTML = "<b>Connection closed.</b>";
            appendLog(item);
        };
        conn.onmessage = function (evt) {
			var item = document.createElement("div");
			item.innerText = "receive↓:";
			appendLog(item);
		
            var messages = evt.data.split('\n');
            for (var i = 0; i < messages.length; i++) {
                var item = document.createElement("div");
                item.innerText = messages[i];
                appendLog(item);
            }
        };
    } else {
        var item = document.createElement("div");
        item.innerHTML = "<b>Your browser does not support WebSockets.</b>";
        appendLog(item);
    }
};

After that, we open two pages for testing:

summary

The practice of Websocket through cloud function + API gateway is definitely not just a chat tool, it can be used in many aspects, such as the production of real-time log system through Websocket.

Single function computing is only a computing platform. Only when it is combined with the surrounding BaaS, can it show the value and real ability of Serverless architecture. That's why many people say Serverless=FaaS+BaaS.

Expect more small partners to create more interesting applications through the Serverless architecture.

Serverless Framework 30 day trial plan

We invite you to experience the most convenient way to develop and deploy Serverless. During the trial period, the related products and services provide free resources and professional technical support to help your business quickly and conveniently realize Serverless!

Details can be found at: Serverless Framework trial plan

One More Thing

What can you do in three seconds? Take a sip, read an email, or - deploy a complete Serverless application?

Copy link to PC browser: https://serverless.cloud.tencent.com/deploy/express

Deploy in 3 seconds and experience the fastest Serverless HTTP Practical development!

Transfer gate:

Welcome to: Serverless Chinese network , you can Best practices Experience more about Serverless application development!

Recommended reading: Serverless architecture: from principle, design to project implementation

Topics: JSON network github Javascript