Deno原理詳解,讓我們一起從原始碼分析開始

_西門吹牛發表於2019-03-02

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 案例原始碼

參考資料

相關文章