訂閱以接收新文章的通知:

宣佈在 Cloudflare Workers 上支援 WASI

2022-07-07

閱讀時間:4 分鐘
本貼文還提供以下語言版本:English简体中文

今天,我們宣佈在 Cloudflare Workers 上提供對 WASI(WebAssembly 系統介面)的實驗性支援,並在 wrangler2 中提供支援,以打造輕鬆愉悅的使用體驗。對於整個 WebAssembly 生態系統,我們仍然激動不已,並渴望隨著標準的開發而對其進行採用。

Announcing support for WASI on Cloudflare Workers

WebAssembly 快速入門

那麼 WASI 到底是什麼?若要瞭解 WASI,以及為什麼我們對其感到興奮,則有必要快速回顧一下 WebAssembly 及圍繞它的生態系統。

WebAssembly 向我們承諾了這樣一個未來︰用程式設計語言編寫的程式碼可以編譯成通用的二進位格式,並以接近本機的速度在安全的沙盒中執行。雖然 WebAssembly 在設計時只考慮了瀏覽器,但該模型迅速擴展至伺服器端平台,例如 Cloudflare Workers(自 2017 年以來一直支援 WebAssembly)。

WebAssembly 最初設計為與 Javascript _一起_執行,並且要求開發人員直接與 Javascript 互動,以便存取沙盒之外的世界。換句話說,WebAssembly 沒有針對與檔案互動、存取網路或讀取系統時鐘等之類的 I/O 任務提供任何標準介面。這意味著,如果您想回應來自外部世界的事件,則需要由開發人員在 JavaScript 中處理該事件,並直接呼叫從 WebAssembly 模組匯出的函數。同樣,如果您想在 WebAssembly 中執行 I/O,則需要在 Javascript 中實作該邏輯,並將其匯入 WebAssembly 模組。

Emscripten 等自訂工具鏈或 wasm-bindgen 等程式庫已經應運而生,使得此操作更為便捷,但它們特定於具體的程式設計語言,並且會增加大量的複雜性和臃腫性。我們甚至使用 wasm-bindgen 建置了自己的庫 workers-rs,試圖讓在 Rust 中編寫應用程式,感覺就像在 Worker 中一樣是原生的——但這已經證實為不僅難以維護,還需要開發人員編寫特定於 Workers 的程式碼,並且不可移植到 Workers 生態系統之外。

我們需要做得更多。

WebAssembly 系統介面 (WASI)

WASI 旨在提供一個標準介面,任何編譯到 WebAssembly 的語言都可以適用。您可以在這裡閱讀 Lin Clark 的原始貼文,其中使用程式碼卡通圖和全方位的內容進行了很好的介紹。簡而言之,Lin 將 WebAssembly 描述為「概念機器」的_彙總語言_,而 WASI 是「概念作業系統」的_系統介面_。

系統介面的這種標準化為現有工具鏈將現有程式碼庫交叉編譯到 wasm32-wasi 目標鋪平了道路。目前已經取得了巨大的進展,特別是在 Clang/LLVM 中使用了 wasi-sdkRust 工具鏈。這些工具鏈運用 Libc 的一個版本,可提供在 WASI「系統呼叫」之上建置的 POSIX 標準 API 呼叫。甚至在 TinyGoSwiftWasm 等更多外緣工具鏈中已基本實作。

實際上這意味著,您現在編寫的應用程式不僅能夠與任何實作該標準的 WebAssembly 執行階段互操作,還能夠與任何 POSIX 相容系統互操作!這意味著完全相同的「Hello, world!」可在本地 Linux/Mac/Windows WSL 機器上執行。

展示程式碼

WASI 聽起來很棒,但真的會讓我的生活更輕鬆嗎?想必這是大家的疑問。我們舉例來說明這在實際中是如何運作的。

首先,產生一個基本的 Rust「Hello, world!」應用程式,編譯並執行該應用程式。

這非常簡單。您會注意到我們只定義了一個 main() 函數,接著是使用 println 進行標準輸出。

$ cargo new hello_world
$ cd ./hello_world
$ cargo build --release
   Compiling hello_world v0.1.0 (/Users/benyule/hello_world)
    Finished release [optimized] target(s) in 0.28s
$ ./target/release/hello_world
Hello, world!

現在,我們使用完全相同的程式,並針對 wasm32-wasi 目標進行編譯,然後在「現成」的 wasm 執行階段(如 Wasmtime)中執行 Wasmtime

fn main() {
    println!("Hello, world!");
}

非常乾淨利落!相同的程式碼在多個 POSIX 環境中編譯和執行。

$ cargo build --target wasm32-wasi --release
$ wasmtime target/wasm32-wasi/release/hello_world.wasm

Hello, world!

最後,我們使用剛剛為 Wasmtime 產生的二進位檔案,但是使用 Wrangler2 將其發佈到 Workers。

不出所料,非常有效!同一程式碼在多個 POSIX 環境中相容,同一個二進位檔案在多個 WASM 執行階段相容。

$ npx wrangler@wasm dev target/wasm32-wasi/release/hello_world.wasm
$ curl http://localhost:8787/

Hello, world!

在雲端執行 CLI 應用程式

細心的讀者可能會注意到,我們對透過 cURL 發出的 HTTP 請求玩了個小把戲。在此範例中,我們實際上分別使用 HTTP 請求和回應正文,對往/返 Worker 的標準輸入和標準輸出進行串流。此模式可實現一些非常有趣的使用案例,尤其是,設計用於在命令列上執行的程式,可以作為「服務」部署至雲端。

「Hexyl」是一個完全現成可用的範例。在這裡,我們在本地機器上對一個二進位檔案執行「cat」,並將輸出「pipe」到 curl,然後將該輸出 POST 到我們的服務並串流傳回結果。我們可以使用編譯「Hello World!」的步驟,來編譯 hexyl。

無需進一步修改,我們即可採用真實世界的程式,並建立我們現在可以執行或部署的內容。同樣,我們告訴 wrangler2 預覽 hexyl,但這次向它提供一些輸入。

$ git clone [email protected]:sharkdp/hexyl.git
$ cd ./hexyl
$ cargo build --target wasm32-wasi --release

請點繫 https://hexyl.examples.workers.dev 來親自嘗試操作。

$ npx wrangler@wasm dev target/wasm32-wasi/release/hexyl.wasm
$ echo "Hello, world\!" | curl -X POST --data-binary @- http://localhost:8787

┌────────┬─────────────────────────┬─────────────────────────┬────────┬────────┐
│00000000│ 48 65 6c 6c 6f 20 77 6f ┊ 72 6c 64 21 0a          │Hello wo┊rld!_   │
└────────┴─────────────────────────┴─────────────────────────┴────────┴────────┘

一個更實用但需要更多工作的範例,是將 swc (swc.rs) 等公用程式部署至雲端,並將其用作隨需 JavaScript/TypeScript 轉換服務。在這裡,我們採用一些額外的步驟,來確保編譯的輸出盡可能小,但它本身立即就可執行。這些步驟在 https://github.com/zebp/wasi-example-swc 中進行了詳細介紹,但現在我們先不管這些步驟,並與託管範例進行互動。

echo "Hello world\!" | curl https://hexyl.examples.workers.dev/ -X POST --data-binary @- --output -

最後,我們也可以用 C/C++ 進行同樣的操作,但需要更多的提升才能讓 Makefile 正確。我們在這裡展示了編譯 zstd 並將其作為串流壓縮服務上傳的範例。

$ echo "const x = (x, y) => x * y;" | curl -X POST --data-binary @- https://swc-wasi.examples.workers.dev/ --output -

var x=function(a,b){return a*b}

https://github.com/zebp/wasi-example-zstd

如果我想要在 JavaScript Worker 中使用 WASI,該怎麼做?

$ echo "Hello world\!" | curl https://zstd.examples.workers.dev/ -s -X POST --data-binary @- | file -

Wrangler 可以讓部署程式碼變得非常容易,同時無需擔憂 Workers 生態系統,但在某些情況下,您可能實際上需要從 Javascript 叫用基於 WASI 的 WASM 模組。這可以透過以下簡單的範本來實現。更新的 README 將保存在 https://github.com/cloudflare/workers-wasi

現在,使用我們的 JavaScript 範本和 wasm,我們可以使用 Wrangler 的 WASM 功能來輕鬆部署我們的 Worker。

import { WASI } from "@cloudflare/workers-wasi";
import demoWasm from "./demo.wasm";

export default {
  async fetch(request, _env, ctx) {
    // Creates a TransformStream we can use to pipe our stdout to our response body.
    const stdout = new TransformStream();
    const wasi = new WASI({
      args: [],
      stdin: request.body,
      stdout: stdout.writable,
    });

    // Instantiate our WASM with our demo module and our configured WASI import.
    const instance = new WebAssembly.Instance(demoWasm, {
      wasi_snapshot_preview1: wasi.wasiImport,
    });

    // Keep our worker alive until the WASM has finished executing.
    ctx.waitUntil(wasi.start(instance));

    // Finally, let's reply with the WASM's output.
    return new Response(stdout.readable);
  },
};

回到未來

$ npx wrangler publish
Total Upload: 473.89 KiB / gzip: 163.79 KiB
Uploaded wasi-javascript (2.75 sec)
Published wasi-javascript (0.30 sec)
  wasi-javascript.zeb.workers.dev

如果過去幾十年的大部分時間一直在使用 RFC3875,您可能會注意到,這看起來與 RFC3875 非常相似,也就是眾所周知的 CGI(通用閘道介面)。雖然我們這裡的範例肯定不符合規範,但您可以想像如何擴展,以將基本「命令列」應用程式的標準輸入轉換為成熟的 http 處理常式。

我們非常期待看到開發人員使用本產品建構的成果。在 DiscordTwitter 上與我們分享您建置的內容吧!

我們保護整個企業網路,協助客戶有效地建置網際網路規模的應用程式,加速任何網站或網際網路應用程式抵禦 DDoS 攻擊,阻止駭客入侵,並且可以協助您實現 Zero Trust

從任何裝置造訪 1.1.1.1,即可開始使用我們的免費應用程式,讓您的網際網路更快速、更安全。

若要進一步瞭解我們協助打造更好的網際網路的使命,請從這裡開始。如果您正在尋找新的職業方向,請查看我們的職缺
Cloudflare WorkersWebAssemblyWASMDevelopersDeveloper Platform

在 X 上進行關注

Ben Yule|@bjyule
Zebulon Piasecki|@zebassembly
Cloudflare|@cloudflare

相關貼文