前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >对 deno deploy 的逆向工程

对 deno deploy 的逆向工程

作者头像
tyrchen
发布2022-05-07 10:44:58
1.1K0
发布2022-05-07 10:44:58
举报
文章被收录于专栏:程序人生程序人生

deno deploy 是 deno 官方推出的一个 serverless 服务,它并没有开放源码。在 deno deploy 服务的 playground 中,你可以直接撰写 javascript 代码,保存后一两秒内就部署运行成功(运行在 deno runtime 的 v8 isolate 中):

我在上一篇文章:『被低估的 deno』对这个能力做了一个简单的分析。

然而如果你仔细阅读上面我刻意写就的代码,会有一丝困惑:

代码语言:javascript
复制
import { serve } from "https://deno.land/std@0.120.0/http/server.ts";

function handler(req: Request): Response {
  return new Response("Hello My dear friends!!!");
}

console.log("Listening on http://1.2.3.4:567890");
await serve(handler, {addr: "1.2.3.4:567890"});

首先, serverless function 应该是响应某个事件,进行相应的处理。比如 aws lambda function 是这么写的:

代码语言:javascript
复制
exports.handler = async function(event, context) {
   ...
}

而 cloudflare worker 是这么写的:

代码语言:javascript
复制
addEventListener('fetch', event => {
   ...
});

其实 deno deploy 早期(在网上还能找到旧版本的教程),也是借鉴了 cloudflare 的 ABI,提供了类似的接口。但使用这样的接口构造的代码只能用于 deno deploy,不像一个「真正」的 deno 脚本,和 deno 本身的生态衔接起来很别扭,于是 deno deploy 就把接口进化成现在这个样子。

这个改进非常英明 —— deno deploy 可以无缝利用 deno 的生态,且用户在使用时,本地(基本上)无需一个模拟器(就像 cloudflare 的 wrangler 那样),直接用 deno 命令行就可以运行了。

不过问题来了 —— serverless function 是事件驱动的,上述代码中有监听端口的行为,这明显不是 serverless function 可以做的,它怎么可能运行?难道在云端运行的 serverless function 真的跑了个 HTTP 服务器么?

非也非也。你擦亮眼睛又双叒叕看了一遍代码,发现我这里故意监听了一个不存在地址:1.2.3.4:567890,为啥没报错?

这个问题引起了我的兴趣,在大致捋了一遍 deno 的源码后,我觉得 deno deploy 大概是重新定义了一些底层的 ops(如 listen / accept),使它们啥也不做,直接 bypass。

在 deno 里,ops 的地位类似于 unix 系统的 syscall。用户代码中调用的诸如 Deno.open() 这样的操作,在底层会调用 Rust 的 op_open(),并在 resource table 里记录相应的资源的使用,这一点也和 unix 的 file descriptor 如出一辙。ops 和 resource 的管理是 deno 相对于 node 的一大进步,所有的底层操作都通过 ops 被暴露出来,有清晰的轨迹可循,也可以很方便地进行统计和监控。

带着这个思考,我试着在 deno 的 discord channel 里问了一下,但没人给出有意义的答案:

于是,我只好自己继续探索,这个探索的过程还是很有挑战的,因为网上有关 deno 内部如何工作的文档和视频寥寥无几,更别提探讨这样深入底层的内容了。我唯一可以依赖的就是 deno 的源代码。好在 deno 的代码结构非常清晰,基本上分成这么几层:

回头我会在 B 站上发布一系列和 deno 底层实现相关的视频,敬请期待。

一般来说,如果你只是一个 deno 的用户,你并不需要知道 deno_runtime 及之下的内容,就跟使用 node 一样,你的 JS/TS 代码跟 Rust 无关。如果你想把 deno 集成到你自己的系统中,那么你需要了解 deno_runtime 以及它依赖的 crates。

我顺着一个简单的 http server 的流程,了解并绘制了 deno 的处理流程:

基本上,如果我们想要达到 deno deploy 的效果,那么,好些个 JS API 和其内部封装的 Rust ops 都需要修改(或者旁路):

比如在 listen API 被调用时:

代码语言:javascript
复制
function listen({ hostname, ...options }) {
  const res = opListen({
    transport: "tcp",
    hostname: typeof hostname === "undefined" ? "0.0.0.0" : hostname,
    ...options,
  });

  return new Listener(res.rid, res.localAddr);
}

function opListen(args) {
  return core.opSync("op_net_listen", args);
}

同样的,我们可以修改 opListen 使其不调用 op_net_listen:

代码语言:javascript
复制
function opListen(args) {
  return {
    rid: 0,
    localAddr: "127.0.0.1:65432" // 使用某个 magic port
  }
}

同样的,opAccept 也可以旁路:

代码语言:javascript
复制
function opAccept(rid, transport) {
  return {
    rid: 0,
    localAddr: "127.0.0.1:65432",
    remoteAddr: "<remote ip>:<port>",
  }
}

以此类推。对于获取一个 HTTP request,就需要改 Rust 代码了,因为之前我把 Listener 以及 accept 下来的 TcpConn 都旁路了,导致在 Rust 侧没有对应的资源,所以我需要手工创建一个,提供给修改过的 op_http_accept_patch:

代码看不懂没关系,思路就是:能在 JS API hack 的就在 JS API hack,否则就在 Rust op 上 hack。这个过程非常折磨人,一不小心就会产生莫名其妙的错误,需要花很多时间来除虫。我上一次这么大规模地动别人的底层代码来达成某个目标,可能还是在神州数码的时候把路由器的 kernel 从 2.4 迁移到 2.6。这样的工作需要长时间聚精会神去和近乎无休止的问题作斗争,但好处是目标非常明确,一旦完成成就感非常高。

感谢 deno 清晰的架构,和优雅的设计,两个周末下来,前前后后花了二三十个小时,我几乎照着 deno_runtime 重写了一个 runtime 出来,终于达到了我想要的目标:运行一个 Rust axum web server,把 HTTP 请求通过 v8 isolate 转给相应的 JS 脚本,在 JS 中处理,然后把结果返回给 axum,再由 axum 返回给用户。以下是同样的 JS 脚本在我的 web server + JS runtime 中运行,以及在 deno 下运行的结果:

对于客户端来说,两次请求得到一样的结果:

JS 脚本如下:

虽然目前我做的 JS runtime 还不支持 typescript,所以还难以利用 deno 的生态,所以我无法给出更漂亮的实现(如本文开头的 TS 代码示例),但这一步并不困难,它更像是一个从 1 到 100 的问题。

逆向 deno deploy 的意义

服务器的未来必然是朝 serverless 的方向发展。在一个越来越复杂的 web app 下面,我们需要一套轻便的架构来更快更直观地构建业务逻辑。而大多数业务逻辑,无非就是响应和处理事件,再激发出更多的事件供系统的其它微服务消费。从这个角度来看,更加轻量的 v8 isolate 必然优于庞大的 container。虽然 cloudflare 和 deno 都推出了自己的使用 v8 isolate 的 serverless 产品,但这些产品有很多局限,比如运行时间和内存的限制,对网络协议的限制(目前它们都只支持 HTTP),以及对第三方工具的支持的限制。如果在自己的 infrastructure 下运行一个个修改版的 deno runtime,再通过 op 和公司内部的其它基础设施打通(比如 kv / RDBMS / Message Queue),那么,可以创造出来一套非常 NB 的开发和部署环境,就像我上周那篇文章说的,我可以创造出来各种各样的环境,做各种复杂的 experiment,消耗的资源不过是额外的 JS 文件的存储而已:

对我自己而言,在西雅图阴雨绵绵的周末,找到一个有趣的问题(逆向 deno deploy),并找到这个问题的一个潜在的解,岂不乐哉?

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-04-05,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序人生 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 逆向 deno deploy 的意义
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档