Go WebAssembly 尝试&瘦身

产品中有个需求,涉及私钥安全问题,要在浏览器本地进行证书格式转换(例如JKS、PFX)。

证书格式转换已有Go的版本,想到用WebAssembly技术可以复用Go的代码,还能具备一定的代码逻辑保密性。

有关WebAssembly的介绍可以参考 几张图让你看懂WebAssembly 简单来说WebAssembly就是将其他语言C/Go/Rust等语言编译成wasm可执行二进制文件,浏览器来执行wasm。wasm相比JS,拥有体积更小,执行更快,因为最终编译成二进制文件,所以一些安全策略代码也更适合wasm。 经过尝试C和Go分别编写WebAssembly,相较而言我认为Go无论从语言层面还是工具链,用起来都更加方便一些。 本文使用原生go build,生成的wasm文件大约在1.4M左右,在生产环境中这个体积是很大的,优化go的wasm体积可以使用tinygo来build,同样的代码使用tinygo构建之后约为22K,甚至比C语言构建wasm的体积还要小(C语言 build后约为44K,不同版本不同环境可能略有差异)。参考https://tinygo.org/

Go Wasm 使用

wasm.go

package main

import (
	"syscall/js"
)

func runner(this js.Value, args []js.Value) interface{} {
	return args[0].Invoke(args[1]).String()
}

func main() {
	wait := make(chan struct{}, 0)
	js.Global().Set("runner", js.FuncOf(runner))
	<-wait
}

index.html

<!DOCTYPE html>

<html>

<head>
    <meta charset="utf-8"/>
    <title>Go WebAssembly</title>
    <meta name="viewport" content="width=device-width, initial-scale=1"/>
    <script src="wasm_exec.js" defer></script>
    <script src="wasm.js" defer></script>
</head>

<body>
    <h1>WebAssembly</h1>
    <p>Edit on either side to mimic values, using WebAssembly:</p>
    <input type="text" id="a" value=""/>==<input type="text" id="b" value=""/>
</body>

</html>

wasm.js

'use strict';

const WASM_URL = 'wasm.wasm';

var wasm;

function updateRight() {
    const value = document.getElementById("a").value;
    window.runner(function (value) {
        document.getElementById("b").value = value;
    }, value);
}

function updateLeft() {
    const value = document.getElementById("b").value;
    window.runner(function (value) {
        document.getElementById("a").value = value;
    }, value);
}

function init() {
    document.querySelector('#a').oninput = updateRight;
    document.querySelector('#b').oninput = updateLeft;

    const go = new Go();
    if ('instantiateStreaming' in WebAssembly) {
        WebAssembly.instantiateStreaming(fetch(WASM_URL), go.importObject).then(function (obj) {
            wasm = obj.instance;
            go.run(wasm);
        })
    } else {
        fetch(WASM_URL).then(resp =>
            resp.arrayBuffer()
        ).then(bytes =>
            WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
                wasm = obj.instance;
                go.run(wasm);
            })
        )
    }
}

init();

server.go 提供wasm下载、演示服务

package main

import (
	"log"
	"net/http"
	"strings"
)

const dir = "./html"

func main() {
	fs := http.FileServer(http.Dir(dir))
	log.Print("Serving " + dir + " on http://localhost:8080")
	http.ListenAndServe(":8080", http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) {
		resp.Header().Add("Cache-Control", "no-cache")
		if strings.HasSuffix(req.URL.Path, ".wasm") {
			resp.Header().Set("content-type", "application/wasm")
		}
		fs.ServeHTTP(resp, req)
	}))
}

大小优化测试

安装 TinyGo https://tinygo.org/getting-started/macos/

!! 注意有没有不支持的包,https://tinygo.org/lang-support/stdlib 比如我们要用到的crypto/x509包就不支持,要考虑抽离出来。

GOOS=js GOARCH=wasm go build -o ./html/wasm.wasm ./invoke/wasm.go 1.3 MB

image-20200916210018804

tinygo build -o ./html/wasm.wasm -target wasm -no-debug ./invoke/wasm.go 29.9 KB

image-20200916210145627

参考:

https://tinygo.org/webassembly/webassembly/

https://github.com/tinygo-org/tinygo/tree/master/src/examples/wasm

https://stackoverflow.com/questions/25547475/save-to-local-file-from-blob

https://www.jianshu.com/p/b3ef3be94fa7