Memoria 开发记事 2 云服务的Serverless部署和对接
本项目主要是一个“本地APP”,涉及到的在线功能主要包括:
- 注册用户和登录
- 访问AI功能接口
- 部分复杂的计算任务
本地APP理论上不需要一个注册用户和登录的功能,但是在实际使用时由于存在部分功能需要联网,网络避免出现未经授权的访问,我们需要对 API 进行一定程度上的鉴权,只有“已知用户”方能登录使用。
登录有关功能
登录一般有2个关键条件,用户名和密码;但是为了便于管理,通常会让用户提供邮箱或者电话号码等个人信息;此外由于用户名的唯一性,用户通常需要自定义一个“显示名称”作为更加“人类可读”的名称,以解决重名问题;此外,邮箱需要验证,并实现重置密码的鉴权流程。
因此,涉及到的页面包括:
- 注册页面:提供用户邮箱、输入两次密码,并发送电子邮件验证码让用户确认邮箱的正确性,最后完成注册
- 登录页面:输入用户名(或者邮箱)和密码登录;在某些情况下可能需要有二次认证,比如发送电子邮件验证码
- 密码重置页面:输入邮箱,发送邮件验证码,并重新输入2次密码实现密码重置
由于发送邮件是免费的,而通常来说发送SMS信息需要几分钱到几毛钱不等,我们为了简单起见就只启用了电子邮件有关的功能,但是使用SMS的流程和功能设计也完全类似。
由于这部分功能已经非常常见,本着不重复造轮子的理论,我们直接调用了 AWS Cognito 有关功能,其帮助集成了用户管理、多因素认证、邮件通知等复杂特性。此外,其作为客户端也存在 JWT token 管理、登录状态管理等功能,可以直接调用 AWS Amplify 的 Flutter 包实现,我们只需要在客户端维护几个服务及其接口,细枝末节的工作可以直接使用其 API。
AI功能接口
改功能主要是提供一个接口,供本地APP调用,来访问AI功能。这个接口本质上就是反向代理(转发)一个 openAI 格式的接口,并没有什么特别的技术含量,因此不赘述。关键的问题在于部署和鉴权。这部分和后续的复杂计算任务功能做在了一起。
复杂计算任务(音频解析)
这部分主要是对音乐文件的解析和节拍特征的提取,因此需要一定的计算资源;而且由于有关的库是使用 Python 编写的,我们也就采用 FastAPI 来实现这个接口。
考虑到目前并没有提供一个持续的在线服务,为了尽可能节约成本,但是由于该应用程序打包体积过于巨大而且冷启动需要时间,并不适合使用 AWS Lambda 这类 Serverless 计算服务,因此我们选择了 Hugging Face Spaces 的容器托管服务。该服务主要是提供一个容器化的环境,用户可以将自己的应用程序打包成一个 Docker 镜像,并部署到 Hugging Face 的服务器上,免费的配额是 2vCPU 和 16GB 内存,足以满足我们的需求;此外该服务还提供了一个公共的 URL,但是为了防止未经授权的访问,我们将容器私有化部署并透过 Cloudflare Workers 实现鉴权,再利用 Hugging Face 的 token 去访问该服务。这种方式可以保证只有授权用户才能访问该服务,同时也可以利用 Hugging Face 的计算资源能只被我们使用。
这部分的代码逻辑非常简单。接口参数 audio: UploadFile = File(...) 接受 multipart/form-data 上传的文件,代码会根据上传文件名提取扩展名,写入带后缀的临时文件,再调用有关的计算库实现对音乐文件的解析。再程序结束后,不论成功或者失败,都调用 finally 模块的 os.remove 删除临时文件,避免磁盘空间被占满。
API 使用 FastAPI 的 Security 模块实现鉴权。但是,由于 Hugging Face Spaces 的 token 已经占据了默认的 Authorization 头,因此我们需要使用一个自定义的头来传递用户的 JWT token。我们定义了 X-Memoria-Token取 token,如果没有,再从 cookie memoria_token 取;二者都没有就返回 401。
对于用户鉴权,通过 JWKS 获取公钥来验证 JWT 的 RS256 签名。在验签通过后,还会进一步检查:
token_use必须是 accessclient_id必须匹配服务端配置的 COGNITO_APP_CLIENT_IDissuer必须匹配当前 User Pool
此外程序把 Cognito 返回的 JWKS 缓存 1 小时,避免每次请求都去远程拉公钥集合。
业务代码结束后,需要构建一个能转发请求的 Cloudflare Worker 程序。程序其实非常简单,就是给发送到 Worker 的请求添加一个 Authorization 头,值为 Bearer <HUGGING_FACE_TOKEN>,然后转发到 Hugging Face Spaces 的 URL 上。Worker 的部署也非常简单,可以直接在 Cloudflare 的在线编辑器中编写和部署。这里涉及到一个问题,由于 Cloudflare Worker 的域名存在被SNI拦截和RST重置的中间人攻击现象,我们最终换用了一个自己花钱购买的域名绑定到 Worker 上,解决了这个问题。
总之,这部分的实现虽然涉及到多个服务的对接,但是每个服务的使用都非常简单,主要是一些配置和调用细节的问题,以及“尽可能不花钱”的部署问题。
