Serverless 实践

本篇文章源自我自己的一个需求:我需要在博客中将链接转化为卡片(类似知乎或飞书中的链接转换功能)。但问题来了,我的博客是使用 Jekyll 生成的纯静态页面,如何来实现这个需求呢?

zhihu

一张卡片中的内容至少要有 图标 标题 描述 三个要素,而这三个内容都是存放在页面的 <head> 标签中的,因此就不得不访问目标链接获取页面内容了。首先思考不引入后端的方案:

  1. 纯前端动态渲染应该是不可能的,因为有跨域请求的问题。(用 iframe 或许可以,但实现起来可能比较复杂而且 DOM 结构也更乱了)
  2. 在构建的时候就生成卡片,但如果目标网页有更新,就无法拿到更新后的内容了。

因此似乎不得不引入后端了。但这是一个很小的需求,专门搞一台服务器做 API 似乎有些 overkill,于是便想到了现在挺流行的 Serverless。

Function 需要做的事非常简单:访问一个 URL 拿到 HTML 后直接返回,至于 HTML 如何 parse 来获取感兴趣的数据,放在用户浏览器来处理。

先看价格

这个需求应该目前所有的 Serverless 产品都能实现,那主要还是看如何省(白)钱(嫖),下面是写作本文时的调研结果(不会更新,也许内容已过期):

产品 Free Tier
AWS Lambda 每月 100 万次免费请求,400000 GB-s 计算时间
Azure Functions 每月 100 万次免费请求,400000 GB-s 计算时间
Vercel 不限请求次数,每月 100GB 带宽,100 GB-h 计算时间
Cloudflare Workers 每天 10W 次请求,免费 KV 存储
Google Functions 每月 200W 次免费请求,5GB 流量

这里所有产品提供的免费套餐基本都能满足需求,所以选了可能使用最多的 AWS Lambda,另外经过测试,AWS Lambda 在国内也是可以访问的。

AWS Lambda

开通的方式不再赘述,这里直接创建一个 Lambda Function,注意在创建时,需要打开「高级设置」,勾选「Enable function URL」选项,否则不能使用链接来调用 Function,并且将 Auth type 修改为「None」,因为需要所有用户都可以使用该 Function,另外还需要勾选 CORS 来支持跨域访问。

config

创建完毕后,还需要继续配置 CORS,切到「Function URL」的配置菜单中:

config2

因为大多数 Serverless 产品都支持 Node.js,因此为了以后可能会有的平台迁移需求,这里也使用 Node.js 来写该 Function 的逻辑。代码非常简单,就是发送一个 HTTPS 请求,但要注意处理 gzip 压缩的情况:

const https = require('https');
const zlib = require("zlib");

exports.handler = (event, context, callback) => {
    const requestData = JSON.parse(event.body)
    const req = https.request(requestData.url, (res) => {
        let body = '';

        let output = res;
        if (res.headers['content-encoding'] === "gzip") {
            output = zlib.createGunzip();
            res.pipe(output)
            res = output
        }
        output.on('data', (chunk) => body += chunk);

        output.on('end', () => {
            callback(null, {html: body});
        });
    });
    req.on('error', callback);
    req.end();
};

配置完成之后,就可以通过 cURL 工具来测试这个 Function:

curl "https://crijyxhk4hfjcnftlnx5fkhemu0lqaxd.lambda-url.ap-northeast-1.on.aws/" \
    -H "Content-type: application/json" \
    -d '{"url":"https://chenglu.me/blogs/effective-colab"}'

现在只需要加上前端的代码就可以实现这个功能了。

前端逻辑

因为并不是所有链接都需要转换为卡片,因此给需要转换的链接标签加一个特殊的 class card-link,前端逻辑就比较简单了:

  1. 找出页面中所有的 a.card-link
  2. 获取 href 属性,发请求给 Lambda Function,获得页面 HTML
  3. 解析 HTML 以渲染出期望的样子

前端代码就不展示了,感兴趣的可以直接浏览器 inspect 来看,放一些 show case 吧,样式致敬 Twitter 的卡片链接。

  • GitHub 个人 / 团队主页的链接:

github-card

  • GitHub 项目链接:

github-card

  • B 站视频

bilibili-card

  • 油管视频

youtube-card

  • 普通链接

zhihu-card

后记

Serverless Function 还有许多其他的玩法,从结构化、KV 存储到机器学习推断,Function 都可以胜任。如果是业务逻辑不是特别复杂,使用 Function 来做后端可能是更有效的方式。