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:
- GitHub: github.com/serverless
- Official website: serverless.com
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