惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Jina AI
Jina AI
宝玉的分享
宝玉的分享
Last Week in AI
Last Week in AI
Help Net Security
Help Net Security
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
人人都是产品经理
人人都是产品经理
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
GbyAI
GbyAI
博客园_首页
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
MongoDB | Blog
MongoDB | Blog
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
L
LINUX DO - 最新话题
PCI Perspectives
PCI Perspectives
博客园 - 三生石上(FineUI控件)
V2EX - 技术
V2EX - 技术
Spread Privacy
Spread Privacy
T
Tor Project blog
量子位
阮一峰的网络日志
阮一峰的网络日志
S
SegmentFault 最新的问题
小众软件
小众软件
博客园 - 叶小钗
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Blog — PlanetScale
Blog — PlanetScale
H
Help Net Security
Y
Y Combinator Blog
N
News | PayPal Newsroom
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
T
Tenable Blog
Scott Helme
Scott Helme
G
GRAHAM CLULEY
大猫的无限游戏
大猫的无限游戏
aimingoo的专栏
aimingoo的专栏
IT之家
IT之家
Schneier on Security
Schneier on Security
F
Fortinet All Blogs
Martin Fowler
Martin Fowler
T
Threat Research - Cisco Blogs
博客园 - 司徒正美
Application and Cybersecurity Blog
Application and Cybersecurity Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Attack and Defense Labs
Attack and Defense Labs
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
The Last Watchdog
The Last Watchdog
L
LangChain Blog
C
Check Point Blog
Google Online Security Blog
Google Online Security Blog
V
Visual Studio Blog
Latest news
Latest news

Serverless

大陆的 serverless 服务最近几年发展如何? 刷新下限?免费一个月也能说成每月免费 无服务架构方面,是 cf 的 workerd 还是 openfaas 纯容器堆栈版的方案好? - V2EX 国内能用真正按用量收费的 serverless 数据库 原来 serverless framework 只是一个开发者工具啊 请教大家访问数据库的接口用这东西怎么整? 求推荐学习基于 serverless, 最好是腾讯云的 cloudbase 开发的开源项目 几百万条更新频率很低的数据用什么低(零)成本的存储方便查询? - V2EX Cloudflare Workers 返回 request method、headers、body,用于开发自测请求信息 Cloudflare Workers 反向代理+跨域 6 月 19 日 周日,华为 Serverless 等你来捧场 为什么 JavaScript 在云服务商的 serverless functions 服务中几乎都有支持? V2EX AI 事件驱动场景 Serverless 实践 Knativa 基于流量的灰度发布和自动弹性实践 Serverless Wordpress 建站三部曲 Serverless 如何在阿里巴巴实现规模化落地? 一图看懂:腾讯云 Serverless 2020 年的突破与收获 Serverless 架构到底要不要服务器? 创业公司用 Serverless,到底香不香? Serverless 是一种思想状态 - V2EX 都 2021 年了, Serverless 能取代微服务吗? 资源成本双优化!看 Serverless 颠覆编程教育的创新实践 Serverless 的价值 Serverless 在大规模数据处理的实践 5 大场景深度探讨何为 Serverless 架构模式? 人力节省 50%,研发效能提升 40%,阿里 Serverless 架构落地实践 一文搞懂函数计算及其工作原理 - V2EX Serverless 的初心、现状和未来 在线应用的 Serverless 实践 一文讲透 Serverless Kubernetes 容器服务 阿里 Midway 正式发布 Serverless v1.0 从单体迈向 Serverless 的避坑指南 为什么下一个十年的主战场在 Serverless? - V2EX 阿里云研究员叔同: Serverless 正当时! Serverless 架构下的服务优雅下线实践 Serverless Rap 一段有意思的 serverless Rap 视频 Serverless 安装 Laravel,flask,gin 等 web 框架,是否违背了它的使用场景? Serverless V2EX 客户端 国内有类似 Vercel 的平台吗? - V2EX 如何优雅地部署一个 Serverless Next.js 应用 - V2EX 如何将 Web 框架迁移到 Serverless - V2EX 现在各大云厂商都在推广的 Serverless 产品,和当初的 GAE/SAE/BAE,甚至是更早的“虚拟主机”,主要区别在哪? - V2EX 前端福音: Serverless 和 SSR 的天作之合 我又开始鼓捣 serverless 的无头浏览器了 - V2EX Serverless 架构下, 3 分钟实现文本敏感词过滤 - V2EX Serverless Dashboard 设计解读与实战 现有的 Serverless 平台足够实时聊天的社区应用开发吗? - V2EX
基于 Serverless 与 Websocket 的聊天工具实现
Aceyclee · 2020-05-26 · via Serverless

传统业务实现 Websocket 并不难,然而函数计算基本上都是事件驱动,不支持长链接操作。如果将函数计算与 API 网关结合,是否可以有 Websocket 的实现方案呢?

WebSocket 协议是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工 (full-duplex) 通信,即允许服务器主动发送信息给客户端。WebSocket 在服务端有数据推送需求时,可以主动发送数据至客户端。而原有 HTTP 协议的服务端对于需推送的数据,仅能通过轮询或 long poll 的方式来让客户端获得。

由于云函数是无状态且以触发式运行,即在有事件到来时才会被触发。因此,为了实现 WebSocket,云函数 SCF 与 API 网关相结合,通过 API 网关承接及保持与客户端的连接。您可以认为云函数与 API 网关一起实现了服务端。当客户端有消息发出时,会先传递给 API 网关,再由 API 网关触发云函数执行。当服务端云函数要向客户端发送消息时,会先由云函数将消息 POST 到 API 网关的反向推送链接,再由 API 网关向客户端完成消息的推送。

具体的实现架构如下:

实现架构

对于 WebSocket 的整个生命周期,主要由以下几个事件组成:

  • 连接建立:客户端向服务端请求建立连接并完成连接建立;
  • 数据上行:客户端通过已经建立的连接向服务端发送数据;
  • 数据下行:服务端通过已经建立的连接向客户端发送数据;
  • 客户端断开:客户端要求断开已经建立的连接;
  • 服务端断开:服务端要求断开已经建立的连接。

对于 WebSocket 整个生命周期的事件,云函数和 API 网关的处理过程如下:

  • 连接建立:客户端与 API 网关建立 WebSocket 连接,API 网关将连接建立事件发送给 SCF ;
  • 数据上行:客户端通过 WebSocket 发送数据,API 网关将数据转发送给 SCF ;
  • 数据下行:SCF 通过向 API 网关指定的推送地址发送请求,API 网关收到后会将数据通过 WebSocket 发送给客户端;
  • 客户端断开:客户端请求断开连接,API 网关将连接断开事件发送给 SCF ;
  • 服务端断开:SCF 通过向 API 网关指定的推送地址发送断开请求,API 网关收到后断开 WebSocket 连接。

因此,云函数与 API 网关之间的交互,需要由 3 类云函数来承载:

  • 注册函数:在客户端发起和 API 网关之间建立 WebSocket 连接时触发该函数,通知 SCF WebSocket 连接的 secConnectionID 。通常会在该函数记录 secConnectionID 到持久存储中,用于后续数据的反向推送;
  • 清理函数:在客户端主动发起 WebSocket 连接中断请求时触发该函数,通知 SCF 准备断开连接的 secConnectionID 。通常会在该函数清理持久存储中记录的该 secConnectionID ;
  • 传输函数:在客户端通过 WebSocket 连接发送数据时触发该函数,告知 SCF 连接的 secConnectionID 以及发送的数据。通常会在该函数处理业务数据。例如,是否将数据推送给持久存储中的其他 secConnectionID 。

Websocket 功能实现

根据腾讯云官网提供的该功能的整体架构图:

整体架构图

这里我们可以使用对象存储 COS 作为持久化的方案,当用户建立链接存储 ConnectionId 到 COS 中,当用户断开连接删除该链接 ID 。

其中注册函数:

# -*- 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

传输函数:

# -*- 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"

清理函数:

# -*- 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 文件如下:

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

注意,这里需要先部署 API 网关。当部署完成,获得回推地址,将回推地址以 url 的形式写入到对应函数的环境变量中:

理论上应该是可以通过 ${restApi.url[0].internalDomain} 自动获得到 url 的,但是我并没有成功获得到这个 url,只能先部署 API 网关,获得到这个地址之后,再重新部署。

部署完成之后,我们可以编写 HTML 代码,实现可视化的 Websocket Client,其核心的 JavaScript 代码为:

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 = "发送↑:";
		appendLog(item);
		
		var item = document.createElement("div");
		item.innerText = msg.value;
		appendLog(item);
		
        return false;
    };

    if (window["WebSocket"]) {
        //替换为 websocket 连接地址
        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 = "接收↓:";
			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);
    }
};

完成之后,我们打开两个页面,进行测试:

总结

通过云函数 + API 网关进行 Websocket 的实践,绝对不仅仅是一个聊天工具这么简单,它可以用在很多方面,例如通过 Websocket 进行实时日志系统的制作等。

单独的函数计算,仅仅是一个计算平台,只有和周边的 BaaS 结合,才能展示出 Serverless 架构的价值和真正的能力。这也是为什么很多人说 Serverless=FaaS+BaaS 的一个原因。

期待更多小伙伴,可以通过 Serverless 架构,创造出更多有趣的应用。

欢迎访问:Serverless 中文社区