Bytecode im Browser: Mit WebAssembly und Rust zur Web-Anwendung

Seite 3: WebAssembly zur Integration von Dritthersteller-Komponenten

Inhaltsverzeichnis

Muss der Server um eine nur bedingt vertrauenswürdige Komponente eines Dritthersteller erweitert werden, gilt es, die Anwendung vor dem Zugriff Dritter zu schützen. Dazu bietet WebAssembly die Möglichkeit, eine Komponente in einer Sandbox auszuführen. Es spricht außerdem grundsätzlich nichts dagegen, WebAssembly außerhalb des Browsers zu verwenden und Anwendungen als WebAssembly betriebssystemunabhängig auszuliefern. Der Bytecode verspricht eine Performance, die nahezu der von nativem Code entspricht. Allerdings definiert WebAssembly nur eine abstrakte Maschine und keine Schnittstelle zu einem abstrakten und entsprechende Abbildung zum verwendeten Betriebssystem. Diesem Problem widmet sich die WebAssembly System Interface (WASI) Subgroup, die an einer entsprechenden Schnittstelle zu einem abstrakten Betriebssystem arbeitet.

Sie befindet sich in der Entwicklung und gilt noch nicht als stabil. Allerdings existiert heute bereits eine brauchbare Unterstützung innerhalb der Rust Toolchain. Mit rustup target add wasm32-wasi lassen sich die Komponenten des Compilers installieren, um entsprechenden WebAssembly-Code zu erzeugen. Die Laufzeitumgebung wasmtime erlaubt es, WASI-basierten WebAssembly-Code auf jedem Betriebssystem auszuführen.

Steht eine Komponente als WASI-Komponente zur Verfügung, lässt sich innerhalb der nativen Anwendung der WebAssembly-Code in einer Sandbox ausführen. Mit wasmtime ist dies einfach zu realisieren. Im Beispiel soll die Funktion add_emoji als WebAssembly zur Verfügung gestellt werden. Laut Hersteller wandelt die Funktion alle Zeichenfolgen von :-) in 😀 um.

thread::spawn(move || {
    let pipe_stdout = Arc::new(RwLock::new(Cursor::new(vec![])));
    let pipe_stdin = Arc::new(RwLock::new(Cursor::new(vec![])));

    let store = Store::default();
    let mut linker = Linker::new(&store);
    let wasi_ctx = WasiCtxBuilder::new()
        .inherit_args()
        .inherit_env()
        .stdin(ReadPipe::from_shared(pipe_stdin.clone()))
        .stdout(WritePipe::from_shared(pipe_stdout.clone()))
        .inherit_stderr()
        .build().expect("Unable to build WasiCtx");
    let wasi = Wasi::new(&store, wasi_ctx);
    wasi.add_to_linker(&mut linker)
        .expect("Unable to add linker");

    // Load WebAssembly Code
    let module = Module::from_file(store.engine(), "plugin.wasm").unwrap();
    linker.module("", &module).unwrap();
    let add_emoji = linker
        .get_one_by_name("", "add_emoji")
        .expect("Unable to find symbol")
        .into_func()
        .expect("Unable to convert into a function")
        .get0::<()>()
        .expect("Unable to specify the signature");

    while let Ok(msg) = dispatcher_rx.recv() {
        for sender in client_senders.lock().expect("Unable to get lock").iter() {
            // forward message to webassembly code
            pipe_stdin.write().unwrap().get_mut().extend_from_slice(msg.as_bytes());

            // call WebAssembly function
            add_emoji().expect("Unable to call add_emoji");

            // receive result
            let new_msg = String::from_utf8(pipe_stdout.write().unwrap().get_ref().to_vec()).unwrap();

            let _ = sender.send(new_msg.clone());
        }
    }
});

Der Beispielcode erzeugt eine Sandbox, die über zwei virtuelle Pipes für den Eingabe- (pipe_stdin) und Ausgabe-Stream (pipe_stdout) mit der Außenwelt kommunizieren kann. Nachdem die WebAssembly-Funktion als Module geladen sind und dem wasmtime-Linker zur Verfügung stehen, lässt sich die aufzurufende Funktion mit get_one_by_name im Modul suchen. Ist das Symbol gefunden, gilt es zu überprüfen, ob es eine Funktion ist, die kein Argument erwartet (get0) und zudem kein Ergebnis zurückliefert (() in get0::<()>). In dem Fall lässt sich anschließend die Funktion add_emoji aufrufen und mit ihr über die virtuellen Pipes kommunizieren.

Dank WebAssembly wird die Funktion einmal vor dem Ausführen kompiliert und danach nativ ausgeführt. Theoretisch ist zu erwarten, dass die Leistungseinbußen im Vergleich zu einer nativen Bibliothek bei wiederholter Ausführung der Funktion relativ gering ausfallen. Da sich allerdings WebAssembly und WASI noch in Entwicklung befinden, sind erst noch praktische Erfahrungen abzuwarten.