Nodejs 环境下 Deno 原理详解,让我们一起从源码分析开始

发布于 1周前 作者 songsunli 来自 nodejs/Nestjs

Nodejs 环境下 Deno 原理详解,让我们一起从源码分析开始

Node 之父 ry:在“ Node 中的设计错误”演讲中表示:

  • 不允许将任意本地函数绑定至 V8 当中。
  • 所有系统调用都将通过消息传递完成( protobuf 序列化)。
  • 两项原生函数:send 与 recv。
  • 这既简化了设计流程,又使得系统更易于审计。

这几点很大程度上体现出了 node 和 deno 在设计本质上的区别,同时这几点体现了 deno 的安全性(利用 JavaScript 本身即为安全沙箱这一事实)

V8worker2 是 Go 和 V8 连接的桥梁

  • 允许从 GO 程序执行 JavaScript
  • 只允许 GO 和 V8 之间的消息传递(传统:暴露 C++函数作为函数在 JavaScript。)
  • 维护一个安全的 JS 沙箱
  • JS 中只允许绑定 3 个函数:send(), recv(), print()

deno 架构图(Parsa Ghadimi 绘制) 从图中可以清晰的看出,V8worker2 是 v8 和 Go 之间实现调用的核心组件

v8worker2 架构图

可以看出 V8worker2 是通过 binding C++ 模块进行绑定 V8,bingding 暴露了基础操作方法:v8_init() 、worker_load()、worker_send_bytes()、worker_dispose()...提供给 GO 进行调用

//binding.h
const char* worker_version();
void worker_set_flags(int* argc, char** argv);
void v8_init();
worker* worker_new(int table_index);
int worker_load(worker* w, char* name_s, char* source_s);
const char* worker_last_exception(worker* w);
int worker_send_bytes(worker* w, void* data, size_t len);
void worker_dispose(worker* w);
void worker_terminate_execution(worker* w);

通过 Golang 的 GC 提供的 CGO 模块调用 C 语言暴露的方法,就可以实现 GO 和 V8 之间的通信了:

  1. 创建一个实例:v8worker2.New(ReceiveMessageCallback)
  2. 加载执行 JS:worker.Load(scriptName,codeString)
// worker.go
package v8worker2

import “C” …

func recvCb(buf unsafe.Pointer, buflen C.int, index workerTableIndex) C.buf { … }

func New(cb ReceiveMessageCallback) *Worker { … initV8Once.Do(func() { C.v8_init() }) }

func (w *Worker) Load(scriptName string, code string) error { … r := C.worker_load(w.worker.cWorker, scriptName_s, code_s) … }

func (w *Worker) SendBytes(msg []byte) error { … r := C.worker_send_bytes(w.worker.cWorker, msg_p, C.size_t(len(msg))) }

案例演示

  • 实现 Js 中的 console.log() 方法
  • Js 发送数据给 Go
  • Go 发送数据给 Js
// hello.go
package main

import ( “fmt”

"github.com/ry/v8worker2"

)

func main() { worker := v8worker2.New(recv)

// 实现 JS 的 console.log 方法
err := worker.Load("hello.js", `
	this["console"] = {
		log(...args) {
			V8Worker2.print(args)
		}
	};
	console.log("Hello World");
`)

if err != nil {
	fmt.Println(err)
}

// 发送数据给 GO
err = worker.Load("sendData.js", `
	V8Worker2.send(new ArrayBuffer(5))
`)
if err != nil {
	fmt.Println(err)
}

// 发送数据给 JS
err = worker.Load("recvData.js", `
	V8Worker2.recv(function(msg) {
		const len =msg.byteLength;
		console.log("recv data from go,length: "+len);
	});
`)
if err != nil {
	fmt.Println(err)
}
err = worker.SendBytes([]byte("abcd"))

}

func recv(buf []byte) []byte { fmt.Println(“recv data from js,length:”, len(buf)) return nil }

在控制台运行: go run hello.go 运行结果

需要运行测试代码,可以直接访问我的 github:deno 案例源码

参考资料


3 回复

不错的资料


加了这么多东西 性能不会很差吗 ?

以下是对“Node.js环境下Deno原理详解,让我们一起从源码分析开始”这一帖子内容的简要回复:

Deno并非基于Node.js环境,而是与Node.js并列的JavaScript和TypeScript运行时环境。不过,可以从源码角度对Deno的原理进行简要分析。

Deno基于V8 JavaScript引擎开发,并整合了Rust和Tokio(Rust的异步运行时)。其设计目标是解决Node.js中的一些问题,如安全性、模块系统和依赖管理等。

从架构上看,Deno的核心组件包括CLI、core、deno_typescript等。CLI负责实现各种接口,core负责实例化和执行模块代码,处理JavaScript与Rust之间的交互,deno_typescript则负责编译TypeScript文件。

Deno的一个关键特性是默认安全性。它不允许访问文件系统、网络等系统资源,除非明确授权。这一特性在很大程度上得益于其细粒度的权限系统和ES模块系统的采用。

以下是一个简单的Deno代码示例,展示了其安全性特性:

// deno-allow-read
const text = await Deno.readTextFile("example.txt");
console.log(text);

在上述代码中,deno-allow-read权限标志明确授权Deno读取文件。

由于Deno的源码分析涉及复杂的编程语言和系统调用,建议查阅官方文档或专业书籍以深入了解。

回到顶部