Rust auf Wippy ausführen

Bauen Sie eine Rust-WebAssembly-Komponente, registrieren Sie sie bei Wippy und stellen Sie sie über Funktions-, CLI- und HTTP-Einträge bereit.

Klassifizierung: Ausführbares Tutorial mit einer externen Rust-Komponenten-Toolchain. Die Seite enthält WIT, Rust-Implementierung, Wippy-Registry, Workflow für Integritäts-Hashes, Befehle, erwartete Ergebnisse und Fehlerprüfungen.

Was wir bauen

Eine Rust-Komponente mit vier exportierten Funktionen:

  • greet - Nimmt einen Namen entgegen, gibt eine Begrüßung zurück
  • add - Addiert zwei Ganzzahlen
  • fibonacci - Berechnet die n-te Fibonacci-Zahl
  • list-files - Listet Dateien in einem gemounteten Verzeichnis auf

Die Wippy-Anwendung registriert diese Exports als aufrufbare Funktionen, CLI-Befehle und einen HTTP-Endpunkt.

Voraussetzungen

  • Wippy-Runtime v0.3.32a.
  • Rust-Toolchain mit dem Target wasm32-wasip1.
  • Eine funktionierende C-Toolchain. Unter Linux benötigt cargo-component außerdem die OpenSSL-Entwicklungsbibliotheken.
  • cargo-component 0.21.1, die für dieses Tutorial verwendete Version.
rustup target add wasm32-wasip1
cargo install cargo-component --version 0.21.1 --locked

Erstellen Sie das generierte Komponenten-Grundgerüst und die Wippy-Verzeichnisse:

mkdir rust-wasm-demo
cd rust-wasm-demo
cargo component new --lib demo
mkdir -p app/src/demo/wasm

In PowerShell:

New-Item -ItemType Directory -Path rust-wasm-demo
Set-Location rust-wasm-demo
cargo component new --lib demo
New-Item -ItemType Directory -Path app\src\demo\wasm -Force

cargo component new schreibt kompatible Dateien Cargo.toml, src/lib.rs und WIT und erzeugt später src/bindings.rs neu. Lassen Sie die generierte Version von wit-bindgen-rt mit dem installierten cargo-component gekoppelt. Das Tool bezeichnet diese Schnittstelle als experimentell und garantiert keine Kompatibilität des generierten Codes zwischen Versionen.

Projektstruktur

rust-wasm-demo/
├── demo/                    # Rust component
│   ├── Cargo.toml
│   ├── wit/
│   │   └── world.wit       # WIT interface
│   └── src/
│       ├── bindings.rs      # generated by cargo-component
│       └── lib.rs           # implementation
└── app/                     # Wippy application
    ├── wippy.lock
    └── src/
        ├── _index.yaml      # Infrastructure
        └── demo/
            ├── _index.yaml  # CLI processes
            └── wasm/
                ├── _index.yaml          # WASM entries
                └── demo_component.wasm  # Compiled binary

Schritt 1: WIT-Schnittstelle erstellen

WebAssembly Interface Types (WIT) definiert den Vertrag zwischen Host und Gast.

Erstellen Sie demo/wit/world.wit:

package component:demo;

world demo {
    export greet: func(name: string) -> string;
    export add: func(a: s32, b: s32) -> s32;
    export fibonacci: func(n: u32) -> u64;
    export list-files: func(path: string) -> string;
}

Jeder Export wird zu einer Funktion, die Wippy aufrufen kann.

Schritt 2: In Rust implementieren

Behalten Sie die generierte Datei demo/Cargo.toml bei. Ihre Paketmetadaten müssen auf component:demo verweisen und damit zum WIT-Paket passen; der Crate-Typ der Bibliothek muss cdylib bleiben.

Erstellen Sie demo/src/lib.rs:

#[allow(warnings)]
mod bindings;

use bindings::Guest;

struct Component;

impl Guest for Component {
    fn greet(name: String) -> String {
        format!("Hello, {}!", name)
    }

    fn add(a: i32, b: i32) -> i32 {
        a + b
    }

    fn fibonacci(n: u32) -> u64 {
        if n <= 1 {
            return n as u64;
        }
        let (mut a, mut b) = (0u64, 1u64);
        for _ in 2..=n {
            let next = a + b;
            a = b;
            b = next;
        }
        b
    }

    fn list_files(path: String) -> String {
        let mut result = String::new();
        match std::fs::read_dir(&path) {
            Ok(entries) => {
                for entry in entries {
                    match entry {
                        Ok(e) => {
                            let name = e.file_name().to_string_lossy().to_string();
                            let meta = e.metadata();
                            let (kind, size) = match meta {
                                Ok(m) => {
                                    let kind = if m.is_dir() { "dir" } else { "file" };
                                    (kind, m.len())
                                }
                                Err(_) => ("?", 0),
                            };
                            let line = format!("{:<6} {:>8}  {}", kind, size, name);
                            println!("{}", line);
                            result.push_str(&line);
                            result.push('\n');
                        }
                        Err(e) => {
                            let line = format!("error: {}", e);
                            eprintln!("{}", line);
                            result.push_str(&line);
                            result.push('\n');
                        }
                    }
                }
            }
            Err(e) => {
                let line = format!("cannot read {}: {}", path, e);
                eprintln!("{}", line);
                result.push_str(&line);
                result.push('\n');
            }
        }
        result
    }
}

bindings::export!(Component with_types_in bindings);

Das bindings-Modul wird von cargo-component aus der WIT-Definition generiert.

Schritt 3: Komponente bauen

cd demo
cargo component build --release

Dies erzeugt target/wasm32-wasip1/release/demo.wasm. Kopieren Sie es in Ihre Wippy-App:

mkdir -p ../app/src/demo/wasm
cp target/wasm32-wasip1/release/demo.wasm ../app/src/demo/wasm/demo_component.wasm

In PowerShell:

New-Item -ItemType Directory -Path ..\app\src\demo\wasm -Force
Copy-Item -LiteralPath target\wasm32-wasip1\release\demo.wasm `
  -Destination ..\app\src\demo\wasm\demo_component.wasm

Ermitteln Sie den SHA-256-Hash für die Integritätsprüfung:

sha256sum ../app/src/demo/wasm/demo_component.wasm

In PowerShell:

(Get-FileHash ..\app\src\demo\wasm\demo_component.wasm -Algorithm SHA256).Hash.ToLowerInvariant()

Kopieren Sie die 64 kleingeschriebenen Hexadezimalzeichen in jedes Feld YOUR_HASH_HERE unten. Das endgültige Feld muss die Form sha256:<64-hex-characters> besitzen. Es ist der Hash der kopierten Binärdatei, nicht der Rust-Quelle oder des ursprünglichen Build-Pfads.

Schritt 4: Wippy-Anwendung

Infrastruktur

Erstellen Sie app/src/_index.yaml:

version: "1.0"
namespace: demo

entries:
  - name: gateway
    kind: http.service
    meta:
      comment: HTTP server
    addr: ":8090"
    lifecycle:
      auto_start: true

  - name: api
    kind: http.router
    meta:
      comment: Public API router
      server: demo:gateway
    prefix: /

  - name: processes
    kind: process.host
    lifecycle:
      auto_start: true

  - name: terminal
    kind: terminal.host
    lifecycle:
      auto_start: true

  - name: policy
    kind: security.policy
    meta:
      comment: Grants access to mounted filesystems and WASM functions
    policy:
      actions:
        - fs.get
        - funcs.call
      resources: "*"
      effect: allow

Das Einhängen eines Dateisystems in ein WASM-Modul und der Aufruf einer WASM-Funktion sind beides abgesicherte Aktionen. Die Richtlinie gewährt sie; Einträge, die sie benötigen, referenzieren sie.

WASM-Funktionen

Erstellen Sie app/src/demo/wasm/_index.yaml:

version: "1.0"
namespace: demo.wasm

entries:
  - name: assets
    kind: fs.directory
    meta:
      comment: Filesystem with WASM binaries
    directory: ./src/demo/wasm

  - name: greet_function
    kind: function.wasm
    meta:
      comment: Greet function via payload transport
    fs: demo.wasm:assets
    path: /demo_component.wasm
    hash: sha256:YOUR_HASH_HERE
    method: greet
    pool:
      type: inline

  - name: add_function
    kind: function.wasm
    meta:
      comment: Add function via payload transport
    fs: demo.wasm:assets
    path: /demo_component.wasm
    hash: sha256:YOUR_HASH_HERE
    method: add
    pool:
      type: inline

  - name: fibonacci_function
    kind: function.wasm
    meta:
      comment: Fibonacci function via payload transport
    fs: demo.wasm:assets
    path: /demo_component.wasm
    hash: sha256:YOUR_HASH_HERE
    method: fibonacci
    pool:
      type: inline

Wichtige Punkte:

  • Ein einzelner fs.directory-Eintrag stellt die WASM-Binärdatei bereit.
  • Mehrere Funktionen referenzieren dieselbe Binärdatei mit unterschiedlichen method-Werten.
  • Das Feld hash prüft die Integrität der Binärdatei beim Laden.
  • Der Pool inline serialisiert Aufrufe durch eine warme Instanz. Er setzt den Ausführungszustand zwischen synchronen Aufrufen zurück; verwenden Sie einen anderen Pool-Typ, wenn Sie parallele Worker benötigen.

Funktionen mit WASI

Die list-files-Funktion greift auf das Dateisystem zu und benötigt daher WASI-Imports:

  - name: list_files_function
    kind: function.wasm
    meta:
      comment: Filesystem listing with WASI mounts
    fs: demo.wasm:assets
    path: /demo_component.wasm
    hash: sha256:YOUR_HASH_HERE
    method: list-files
    imports:
      - wasi:cli
      - wasi:io
      - wasi:clocks
      - wasi:filesystem
    wasi:
      mounts:
        - fs: demo.wasm:assets
          guest: /data
    pool:
      type: inline

Der Abschnitt wasi.mounts bildet einen Wippy-Dateisystem-Eintrag auf einen Guest-Pfad ab. Innerhalb des WASM-Moduls zeigt /data auf das Verzeichnis demo.wasm:assets.

CLI-Befehle

Erstellen Sie app/src/demo/_index.yaml:

version: "1.0"
namespace: demo.cli

entries:
  - name: wasm_cli_policy
    kind: security.policy
    policy:
      actions:
        - fs.get
      resources:
        - demo.wasm:assets
      effect: allow

  - name: ls
    kind: process.wasm
    meta:
      comment: List files from mounted WASI filesystem
      command:
        name: ls
        short: List files from mounted directory
        security:
          actor: {id: demo.cli:ls}
          policies: [demo:policy]
    fs: demo.wasm:assets
    path: /demo_component.wasm
    hash: sha256:YOUR_HASH_HERE
    method: list-files
    imports:
      - wasi:cli
      - wasi:io
      - wasi:clocks
      - wasi:filesystem
    wasi:
      mounts:
        - fs: demo.wasm:assets
          guest: /data

Der meta.command-Block registriert den Prozess als benannten CLI-Befehl. Der greet-Befehl benötigt keine WASI-Imports, da er nur String-Operationen verwendet. Der ls-Befehl benötigt Dateisystemzugriff und trägt daher zusätzlich den Sicherheitskontext, der das Einhängen gewährt.

HTTP-Endpunkt

Fügen Sie zu app/src/demo/wasm/_index.yaml hinzu:

  - name: http_greet
    kind: function.wasm
    meta:
      comment: Greet exposed via wasi-http transport
    fs: demo.wasm:assets
    path: /demo_component.wasm
    hash: sha256:YOUR_HASH_HERE
    method: greet
    transport: wasi-http
    pool:
      type: inline

  - name: http_greet_endpoint
    kind: http.endpoint
    meta:
      comment: HTTP POST endpoint for WASM greet
      router: demo:api
    method: POST
    path: /greet
    func: http_greet

Der wasi-http-Transport bildet HTTP-Request/Response-Kontext auf WASM-Argumente und -Ergebnisse ab.

Schritt 5: Initialisieren und ausführen

cd app
wippy init

CLI-Befehle ausführen

# List available commands
wippy run list
Available commands:

  greet  Greet someone via WASM  (demo.cli:greet)
  ls  List files from mounted directory  (demo.cli:ls)

Run with: wippy run <command>

Argumente nach dem Befehlsnamen werden der exportierten Funktion als String-Parameter übergeben, sodass jeder Befehl genau die Argumente entgegennimmt, die seine WIT-Signatur deklariert:

# Run greet
wippy run greet World
Hello, World!
# Run ls to list mounted directory
wippy run ls /data

Der Befehl sollte mindestens demo_component.wasm mit seiner Dateigröße ausgeben und mit Status 0 enden. Wippy gibt beliebige Rückgabe-Payloads von process.wasm nicht aus; deshalb verwendet das CLI-Beispiel die Rust-Funktion, die nach WASI-stdout schreibt.

Als Dienst ausführen

wippy run

Dies startet den HTTP-Server auf Port 8090. Der wasi-http-Transport übergibt den Request-Body als einziges String-Argument der Funktion:

curl -X POST http://localhost:8090/greet -d 'World'
Hello, World!

Aus Lua aufrufen

WASM-Funktionen werden auf dieselbe Weise wie Lua-Funktionen aufgerufen. Der aufrufende Prozess benötigt funcs.call auf dem Ziel, was demo:policy gewährt:

local funcs = require("funcs")

local greeting, err = funcs.call("demo.wasm:greet_function", "World")
-- greeting: "Hello, World!"

local sum, err = funcs.call("demo.wasm:add_function", 6, 7)
-- sum: 13

local fib, err = funcs.call("demo.wasm:fibonacci_function", 10)
-- fib: 55

Fehlerbehebung und Bereinigung

  • Wenn cargo component unbekannt ist, installieren Sie es und führen Sie cargo component build erneut aus. Ein gewöhnliches cargo build erzeugt für diese Einrichtung nicht dieselben Bindings oder dieselbe Komponentenausgabe.
  • Eine vor dem ersten Build fehlende Datei src/bindings.rs ist normal. Fehlt sie nach cargo component build, konnten das WIT-Paket oder die Komponentenmetadaten nicht aufgelöst werden; beheben Sie diesen Build-Fehler vor dem Kopieren einer Binärdatei.
  • WASM hash mismatch bedeutet, dass die Binärdatei nach der Hash-Berechnung geändert wurde oder ein Platzhalter verblieben ist. Kopieren Sie die Release-Binärdatei erneut, berechnen Sie den Digest neu und aktualisieren Sie jeden referenzierenden Eintrag.
  • Ein Fehler bei der Import-Instanziierung bedeutet, dass die Komponente ein nicht im Eintrag enthaltenes Host-Profil importiert. Behalten Sie für die Dateisystembeispiele die Imports wasi:cli, wasi:io, wasi:clocks und wasi:filesystem bei.
  • cannot read /data bedeutet, dass der Gastpfad unter wasi.mounts oder dessen Dateisystemeintrag nicht zur Registry passt.
  • Beenden Sie die HTTP-Runtime mit Strg+C. Rust-Buildausgaben verbleiben unter demo/target/; entfernen Sie dieses Verzeichnis und die kopierte .wasm-Datei, um die generierten Artefakte zu bereinigen.

Nächste Schritte