基于Vercel Serverless部署Calc114514 API & Telegram bot


0x00 Preface 前言

最近,受到 USTC Hackergame 2020(中国科学技术大学第七届信息安全大赛)超精巧的数字论证器 一题启发,寻思着咱可以整一个网页版本 / Telegram bot 版本的 114514 数字论证器。

See also: USTC Hackergame 2020 WriteUp by MiaoTony

说干就干,考虑到 Vercel 有个 Serverless Function 服务,之前也尝试过在上面搭 API(虽然那时由于执行时间限制等原因没用上),这次的 114514 论证实际上并不复杂,于是就来整一个吧!

(Serverless 真香!


0x01 TL; DR

直接说如何体验,具体如何实现后面再展开。

前后端分离,均部署在 Vercel。

前端:https://calc114514.vercel.app/

Telegram bot: @calc114514bot.

项目开源在 GitHub 上:https://github.com/miaotony/calc114514

欢迎来点个 Star 喵~

API 开放接口

GET / POST https://calc114514.vercel.app/api/calc

Param:

num a number here.

isJson Optional. If 1, return the result(s) with JSON format, else return text/plain result.

Return:

See examples below.

Examples:

$ curl "https://calc114514.vercel.app/api/calc?num=114514"
-~-~-~-~(-~(-~-~-~-~-~(-~-~-~-~(-~(-~~-1*-~-~-~-~-~-~-~-~-~1)*-~-~-~-~-~-~4)*-~-~-~-~-~5)*-~-~-~-~-~-~-~-~-~1)*-~-~-~-~-~-~4)

$ curl "https://calc114514.vercel.app/api/calc?isJson=1&num=114514"
{"answer":"-~-~-~-~(-~(-~-~-~-~-~(-~-~-~-~(-~(-~~-1*-~-~-~-~-~-~-~-~-~1)*-~-~-~-~-~-~4)*-~-~-~-~-~5)*-~-~-~-~-~-~-~-~-~1)*-~-~-~-~-~-~4)","number":"114514"}

Telegram bot

See @calc114514bot.

Usage:

  1. Send a number to @calc114514bot. 直接给机器人发数字。
  2. Use inline query method, @calc114514bot <Number Here> . 在任意的输入框输入 @calc114514bot 你想论证的数字

Inline query

第一个不带原数,第二个带原数,效果分别如下所示。


0x02 Review of the question 题目回顾

数字论证是一种常见的定理证明方法。

简单来说,就是对于给定的自然数,找出一个等值的表达式,如果该表达式去除所有符号部分后为字符串「114514」,则完成论证。表达式仅允许整数运算,可以使用括号、常见的代数运算符 +-*/% 和位运算符 ~^&|

Via: USTC Hackergame 2020

Calculate a number with 114514 & some operators.

于是我们就需要利用 114514一些运算符构造出所给的数字。

See also: 超精巧的数字论证器 (official writeup)


0x03 Demonstration method 论证算法

六位数以内论证方法

在不超过6位数,即 0 <= num <= 999999 的论证上,参考了 官方 WriteUp 里的算法。

重要性质:

-~x == x+1
~-x == x-1 对于任意 x 都成立。

一个办法就是按照十进制的方式来凑,即 given_number = (((((((0+a)*10+b)*10+c)*10+d)*10)+e)*10)+f ,其中 a b c d e f 为 0 到 9 之间的整数。表达式中的值 0 10 10 10 10 10 可以分别利用 1 1 4 5 1 4 与嵌套 ~- 来凑出,而 +a +b +c +d +e +f 可以直接利用嵌套 ~- 来实现(不需要使用数字)。

def getans(number):
    """
    Get the answer using `114514` & `-~`, `~-`.
    参考官方 WriteUp,基于十进制分解实现,支持6位及以下数字
    """
    jz = 10
    s = []
    e = "114514"
    for c in e[:1]:
        s.append("~-" * (int(c)) + c)  # value is zero
    for c in e[1:]:
        s.append("-~" * (jz - int(c)) + c)  # value is jz
    ans = ""
    for i in range(len(e)):
        digit = (number // (jz ** (len(e) - 1 - i))) % jz
        ans = "(" + ans + "*" + s[i] + ")" if i > 0 else s[i]
        ans = "-~" * digit + ans
    return ans

超过六位数的论证方法

对于所给整数n,采用与十进制类似的方法,在 $ n^{1/6} $ 附近取六个数,每位取不同进制,最后再凑到所给的数。

(TODO)


0x04 部署 API 接口

其实写个接口的话并不难,个人习惯就使用 Python3 及 Flask 进行开发了。

简单地说,就是把路由配好,所需的参数传进来,丢到处理的函数里,把结果再按照需求返回给用户就完事了。

但是又不想为了这玩意儿就整个服务器,也懒得运维,于是想到了以 Serverless 无服务器 的形式进行部署。

常见的 Serverless 服务也挺多,比如 AWS Lambda, Heroku, Cloudflare Workers, Vercel, etc.

这里就用 Vercel 来实现吧。

Vercel 介绍

Vercel is the optimal workflow for frontend teams. All-in-one: Static and Jamstack deployment, Serverless Functions, and Global CDN.

Vercel is a cloud platform for static sites and Serverless Functions that fits perfectly with your workflow. It enables developers to host Jamstack websites and web services that deploy instantly, scale automatically, and requires no supervision, all with no configuration.

官网就是 https://vercel.com

它是一个提供了静态资源和 Serverless 函数服务的云平台,可以在同一个网站上进行部署。

对于 Vue.js / Next.js / Hexo / Hugo / … 等等主流前端框架,你只需要把原始代码上传,指定好依赖,它就能帮你构建静态文件,自动完成部署。

它支持 GitHub、GitLab 和 Bitbucket 集成,也支持通过命令行 CLI 工具进行部署。

而且 个人版本是永远免费的平台限制 也很宽裕。

更多介绍详见 官方介绍文档

Vercel 就是以前的 ZEIT,其命令行工具就是 vercel,以前是 now

首先我们安装命令行工具。

npm i -g vercel

看一下版本。

$ vercel --version
Vercel CLI 20.1.4
20.1.4

初次使用需要登录,输入邮箱,而后点击邮件里的链接进行授权。

vercel login

若直接部署当前文件夹,直接在命令行输入

vercel

而后按照提示进行操作即可。

其中会询问你项目名称,首次部署时会以此给你分配一个 *.vercel.app 之类的免费域名,这个对应着生产环境。而 *.<username>.vercel.app 则对应着 预览环境/开发环境 (preview)。

之后的每一次部署都会部署到 Preview 环境,确定没问题后再用下面的命令部署到生产环境即可。

vercel --prod

两个环境分离开来就很好用,赞一个。

当然 Vercel 还支持绑定自己的域名,只需要改一改 CNAME 就好,还能自动帮你配上 Let’s encrypt! 的证书。

除此之外,也可以直接在 Settings 下的 Tokens 里新建一个 token,利用其来进行部署。

token

vercel -t <TOKEN Here>

这一操作很适合在服务器上执行,以及可以白嫖 CI 进行部署。

(嘿嘿,可以在 Organization 里进行构建了

更多 CLI 使用方法可以参考 官方 CLI Reference

Serverless 部署

下面就到了愉快的 Serverless 部署环节了。

参考 Serverless Functions 官方文档,我们需要新建一个 api 文件夹,在里面放我们的代码。

目前支持的后端语言包括:

我这里使用的感觉的话,JS支持最好,毕竟自家有 Next.js。

而 Python 的话其实支持并没有那么好,于是就遇到了一些问题。。

Python files within the api directory, containing an handler variable that inherits from the BaseHTTPRequestHandler class or an app variable that exposes a WSGI or ASGI application, will be served as Serverless Functions.

比如路由问题,它默认是一个文件对应一个路由,即其文件名就是其路由。

但这不合适,我们这里是在一个 py 文件里通过 Flask 来处理路由,暴露一个 app 对象。于是需要自己配置一下。

api 外面的目录下新建一个 vercel.json 文件,在里面进行配置。

参考 Configuration Reference,我这里把 /api(.*) 都交给 api/main.py 文件进行处理,这样就能解决路由问题。

{
    "version": 2,
    "routes": [{
        "headers": {
            "Access-Control-Allow-Origin": "*"
        },
        "src": "/api(.*)",
        "dest": "api/main.py",
        "continue": true
    }]
}

同时要注意的是,main.py 里的路由也需要包含 /api,例如

@app.route('/api/calc', methods=['GET', 'POST'])
def api_calc():
    """
    Calculate 114514 API
    """
    pass

0x05 部署 Telegram bot

Telegram bot 才是这次的重点吧。

由于 Telegram 给我们提供了 Webhook 这种处理消息的方式,当 bot 接收到用户发来的消息后,Telegram 服务器会给你设置 Webhook 的 URI 发一个 POST 请求,对这个包进行解析就能得到用户的消息等信息。进而对数据进行处理,再通过 TG bot 的 API 给用户回复消息就完事了。

首先需要设置好 Webhook 到 Vercel 的 URI 上,利用 Flask 的路由去接收。这里略了。

而处理消息的部分,在写的时候最开始是想自己写的,毕竟并不复杂,它送来的是个 JSON 格式的字符串,提取一下就完事了。

但是主要想实现一个内联查询(inline query)的功能,同时还要兼顾消息回复,就觉得有点麻烦,还不如用现成的轮子,懒得造轮子了。

去 GitHub 上搜了一下,发现有两大现成轮子:python-telegram-botpyTelegramBotAPI

他们的文档都挺丰富的,也有很多例子。主要就是对官方的 HTTP API 接口进行了封装,处理消息上好像还做了个队列,用了异步实现的感觉。

(u1s1,那时我一度有 还不如自己写 的感觉

其中前者本身带有一个 webhook 服务器,但试了发现不符合 Vercel 的要求,没有暴露 app 对象之类的,于是跑不起来。

后者则提供了一个 webhook heroku 部署的样例,即 pyTelegramBotAPI/examples/webhook_examples/webhook_flask_heroku_echo.py

关键代码:

@server.route('/' + TOKEN, methods=['POST'])
def getMessage():
    bot.process_new_updates([telebot.types.Update.de_json(request.stream.read().decode("utf-8"))])
    return "!", 200

process_new_updates 这个方法能把接收到的信息传进去,从而建立了 Webhook 和 pyTelegramBotAPI 的联系。

于是就用 pyTelegramBotAPI 这个来实现了。

还有一个坑就是,可能是由于异步处理消息的原因,Vercel Serverless 中的 process_new_updates 结束 return 之后这个会话就关闭了,消息还来不及处理完回送给 Telegram。在测试过程中经常就消息莫名其妙丢失了,或者连着发几条消息才回复的情况。

后来尝试了加一个 bot.get_me() 来缓解。

再后来,发现直接加个延时就完事了,time.sleep(0.5),只需要 0.5s 延时,简单粗暴解决问题。

(喵喵~

相关代码位于 GitHub repo https://github.com/miaotony/calc114514/api/ 目录下,main.py 内包含了开放接口和 bot 交互的相关接口。

开发说明

  1. Install the requirements. 安装依赖。

    pip3 install -r requirements.txt
  2. Edit your Telegram bot token and Webhook URL path in the secret.example.py. 这里我把关键配置专门拿出来放到 secret.example.py 文件里了,开发的时候要修改成你的。

  3. Rename secret.example.py to secret.py. 改名。

  4. Run the backend, for example, gunicorn main:app. 运行后端程序。

基于 Vercel 部署:

在配置好 vercel.jsonapi 目录的基础上,直接用 CLI 上传即可。

vercel

0x06 FrontEnd 前端

最后,寻思着咱 API 都弄好了,不如再整个前端界面吧。

由于采用了前后端分离架构,直接调用之前写的 API 就好了。

想到 Hackergame 2020 签到题那个界面就不错,于是在此基础上魔改了一下,加了个 AJAX 网络请求,顺便做了个输入框和滑块的双向数据绑定。

前端本身就是静态文件了,直接扔根目录下,基于 Vercel 部署就完事了。

详见: https://calc114514.vercel.app/

img

BTW, 当你想要论证 114514 的时候会给你多提示个“恶臭的” (嘻嘻


0x07 Summary 小结

总之就是写了一大版,从论证算法到接口部署到 Telegram bot 部署再到前端界面,把整个实现的过程说了一通。

前端:https://calc114514.vercel.app/

Telegram bot: @calc114514bot.

代码开源在 GitHub 上:https://github.com/miaotony/calc114514

走过路过点个 STAR 吧~

(Serverless 真香!

当然最后还是提一句——

本文相关内容仅供学习研究使用,请在合理合法范围内使用,请勿滥用!


0xFF Extensive Reading

V2EX: 基于 Vercel 提供的 Serverless 写的服务

vercel 无服务部署 Go 函数

如何免费为你的组织项目配置 Vercel (这个就是用到了上面所说的 Token)

(溜了溜了喵~


文章作者: MiaoTony
版权声明: 本博客所有文章除特別声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 MiaoTony !
评论
  目录