# C & Rust Library & Examples for Building WebAssembly Modules for NGINX Unit This provides a C library (lbunit-wasm) and a Rust crate based on that library to aid in the creation of WebAssembly modules in C and Rust. It also has some demo WebAssembly modules written in C and Rust. 1. [C & Rust Library & Examples for Building WebAssembly Modules for NGINX Unit](#c---rust-library---examples-for-building-webassembly-modules-for-nginx-unit) 2. [Repository Layout](#repository-layout) 3. [Quickstart in Developing Rust WebAssembly Modules for Unit](#quickstart-in-developing-rust-webassembly-modules-for-unit) 4. [Getting Started](#getting-started) 1. [Requirements](#requirements) * [wasi-sysroot](#wasi-sysroot) * [libclang_rt.builtins-wasm32-wasi](#libclang_rtbuiltins-wasm32-wasi) 2. [Building libunit-wasm and C Examples](#building-libunit-wasm-and-c-examples) 3. [Building the Rust libunit-wasm Crate and Examples](#building-the-rust-libunit-wasm-crate-and-examples) 4. [Using With Unit](#using-with-unit) 5. [Consuming the C Library](#consuming-the-c-library) 6. [License](#license) ## Repository Layout **src/c** contains the main libunit-wasm library. **src/rust** contains the rust version of the above. **examples/c** contains some demo WebAssembly modules that show both the raw interface to Unit (\*-raw.c) and also the use of libunit-wasm (luw-\*.c). **examples/rust** contains rust versions of the above C demo modules. **examples/docker** contains docker files for building Unit with WebAssembly support and the C examples. ## Quickstart in Developing Rust WebAssembly Modules for Unit 1) Have a suitable rust/wasm environment setup, See [Building the Rust libunit-wasm Crate and Examples](#building-the-rust-libunit-wasm-crate-and-examples) for some details 2) Create a new rust project ```shell $ cargo init --lib my-wasm-example ``` 3) Add the [unit-wasm crate](https://crates.io/crates/unit-wasm) as dependency ```shell $ cd my-wasm-example $ cargo add unit-wasm ``` 4) Set the crate type ```shell $ printf '\n[lib]\ncrate-type = ["cdylib"]\n' >>Cargo.toml ``` 5) Create an example application To do this you can simply take a copy of our echo-request demo in this repository ```shell $ wget -O src/lib.rs https://raw.githubusercontent.com/nginx/unit-wasm/main/examples/rust/echo-request/src/lib.rs ``` 6) Build it! ```shell $ cargo build --target wasm32-wasi ``` You should now have a *target/wasm32-wasi/debug/my_wasm_example.wasm* file (yes, hyphens will be turned to underscores) You can now use this in Unit with the following config ```JSON { "listeners": { "[::1]:8888": { "pass": "applications/my-wasm-example" } }, "applications": { "my-wasm-example": { "type": "wasm", "module": "/path/to/my-wasm-example/target/wasm32-wasi/debug/my_wasm_example.wasm", "request_handler": "uwr_request_handler", "malloc_handler": "luw_malloc_handler", "free_handler": "luw_free_handler", "module_init_handler": "uwr_module_init_handler", "module_end_handler": "uwr_module_end_handler" } } } ``` and curl command ```shell $ curl http://localhost:8888/ ``` 7) Enjoy! ## Getting Started ### Requirements To build the C library and demo modules you will need make, clang, llvm, compiler-rt & lld 8.0+. (GCC does not currently have any support for WebAssembly). #### wasi-sysroot This is essentially a C library (based at least partly on cloudlibc and musl libc) for WebAssembly and is required for the _wasm32-wasi_ target. Distributions are starting to package this. On Fedora for example you can install the ``` wasi-libc-devel wasi-libc-static ``` packages. On FreeBSD you can install the ``` wasi-libc ``` package. The Makefiles will pick this up automatically. Where _wasi-sysroot_ is not available you can grab it from [here](https://github.com/WebAssembly/wasi-sdk/releases). Just grab the latest wasi-sysroot-VERSION.tar.gz tarball. Untar the wasi-sysroot package someplace. #### libclang_rt.builtins-wasm32-wasi You will probably also need to grab the latest libclang\_rt.builtins-wasm32-wasi-VERSION.tar.gz tarball from the same location and keep it handy. ### Building libunit-wasm and C Examples Once you have the above sorted you can simply try doing ``` $ make WASI_SYSROOT=/path/to/wasi-sysroot examples ``` **NOTE:** The Makefiles will look for an already installed wasi-sysroot on Fedora & FreeBSD, so you may not need to specify it as above. If you do, you can set the WASI\_SYSROOT environment variable in your shell so you don't need to specify it here. This will attempt to build libunit-wasm and the two example WebAssembly modules, _luw-echo-request.wasm_ & _luw-upload-reflector.wasm_. If the above fails (which currently there is a good chance it will) with an error message like ``` wasm-ld: error: cannot open /usr/lib64/clang/16/lib/wasi/libclang_rt.builtins-wasm32.a: No such file or directory clang: error: linker command failed with exit code 1 (use -v to see invocation) ``` Then this is where that other tarball you downloaded comes in. Extract the *libclang\_rt.builtins-wasm32.a* from it and copy it into the location mentioned in the above error message. Try the make command again... ### Building the Rust libunit-wasm Crate and Examples To build with Rust you will of course need rust and also cargo and the rust Wasm/WASI components. For example, the Fedora packages are: ``` cargo rust rust-std-static rust-std-static-wasm32-unknown-unknown rust-std-static-wasm32-wasi ``` Install with $PKGMGR. If you have also completed the above building of the C library and examples you should now be good to go. ``` $ make examples-rust ``` If you need to specify the *WASI_SYSROOT*, specify it in the make command as above. This will build the libunit-wasm rust crate and rust example modules. ### Using With Unit Now that you have all the above built, you are now ready to test it out with Unit. If you created both the C and rust examples you will now have the following WebAssembly modules ``` examples/c/luw-echo-request.wasm examples/c/luw-upload-reflector.wasm examples/rust/echo-request/target/wasm32-wasi/debug/rust_echo_test.wasm examples/rust/upload-reflector/target/wasm32-wasi/debug/rust_upload_reflector.wasm ``` We won't go into the details of building Unit from source and enabling the Unit WebAssembly language module here (see the [HOWTO.md](https://github.com/nginx/unit-wasm/blob/main/HOWTO.md) in the repository root for more details) but will instead assume you already have a Unit with the WebAssembly language module already running. Create the following Unit config ```JSON { "listeners": { "[::1]:8888": { "pass": "routes" } }, "settings": { "http": { "max_body_size": 1073741824 } }, "routes": [ { "match": { "uri": "/echo*" }, "action": { "pass": "applications/luw-echo-request" } }, { "match": { "uri": "/upload*" }, "action": { "pass": "applications/luw-upload-reflector" } }, { "match": { "uri": "/rust-echo*" }, "action": { "pass": "applications/rust-echo-request" } }, { "match": { "uri": "/rust-upload*" }, "action": { "pass": "applications/rust-upload-reflector" } } ], "applications": { "luw-echo-request": { "type": "wasm", "module": "/path/to/unit-wasm/examples/c/luw-echo-request.wasm", "request_handler": "luw_request_handler", "malloc_handler": "luw_malloc_handler", "free_handler": "luw_free_handler", "module_init_handler": "luw_module_init_handler", "module_end_handler": "luw_module_end_handler" }, "luw-upload-reflector": { "type": "wasm", "module": "/path/to/unit-wasm/examples/c/luw-upload-reflector.wasm", "request_handler": "luw_request_handler", "malloc_handler": "luw_malloc_handler", "free_handler": "luw_free_handler", "request_end_handler": "luw_request_end_handler", "response_end_handler": "luw_response_end_handler" }, "rust-echo-request": { "type": "wasm", "module": "/path/to/unit-wasm/examples/rust/echo-request/target/wasm32-wasi/debug/rust_echo_request.wasm", "request_handler": "uwr_request_handler", "malloc_handler": "luw_malloc_handler", "free_handler": "luw_free_handler", "module_init_handler": "uwr_module_init_handler", "module_end_handler": "uwr_module_end_handler" }, "rust-upload-reflector": { "type": "wasm", "module": "/path/to/unit-wasm/examples/rust/upload-reflector/rust_upload_reflector.wasm", "request_handler": "uwr_request_handler", "malloc_handler": "luw_malloc_handler", "free_handler": "luw_free_handler", "request_end_handler": "uwr_request_end_handler", "response_end_handler": "uwr_response_end_handler" } } } ``` Load this config then you should be ready to try it. ``` $ curl -X POST -d "Hello World" --cookie "mycookie=hmmm" http://localhost:8888/echo/?q=a *** Welcome to WebAssembly on Unit! [libunit-wasm (0.1.0/0x00010000)] *** [Request Info] REQUEST_PATH = /echo/?q=a METHOD = POST VERSION = HTTP/1.1 QUERY = q=a REMOTE = ::1 LOCAL_ADDR = ::1 LOCAL_PORT = 8080 SERVER_NAME = localhost [Request Headers] Host = localhost:8080 User-Agent = curl/8.0.1 Accept = */* Cookie = mycookie=hmmm Content-Length = 11 Content-Type = application/x-www-form-urlencoded [POST data] Hello World ``` ``` $ curl -v -X POST --data-binary @audio.flac -H "Content-Type: audio/flac" http://localhost:8888/upload-reflector/ -o wasm-test.dat ... > Content-Type: audio/flac > Content-Length: 60406273 ... % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 115M 100 57.6M 100 57.6M 47.6M 47.6M 0:00:01 0:00:01 --:--:-- 95.2M ... < Content-Type: audio/flac < Content-Length: 60406273 ... $ sha1sum audio.flac wasm-test.dat ef5c9c228544b237022584a8ac4612005cd6263e audio.flac ef5c9c228544b237022584a8ac4612005cd6263e wasm-test.dat ``` ## Consuming the C Library If **unit/unit-wasm.h** and **libunit.a** are installed into standard include/library directories then Include the libunit-wasm header file ```C .... #include ... ``` Link against libunit-wasm ```shell $ clang ... -o myapp.wasm myapp.c -lunit-wasm ``` See [API-C.md](https://github.com/nginx/unit-wasm/blob/main/API-C.md) for an overview of the API. ## License This project is licensed under the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0).