Wippy에서 Rust 실행하기
Rust WebAssembly 컴포넌트를 빌드하고 함수, CLI 명령, HTTP 엔드포인트로 실행합니다.
빌드할 내용
네 개의 내보내기 함수를 가진 Rust 컴포넌트:
- greet - 이름을 받아 인사말을 반환
- add - 두 정수를 더함
- fibonacci - n번째 피보나치 수를 계산
- list-files - 마운트된 디렉터리의 파일 목록을 표시
이를 호출 가능한 함수, CLI 명령, HTTP 엔드포인트로 노출합니다.
사전 요구사항
wasm32-wasip1타겟이 포함된 Rust 툴체인- cargo-component
rustup target add wasm32-wasip1
cargo install cargo-component
프로젝트 구조
rust-wasm-demo/
├── demo/ # Rust component
│ ├── Cargo.toml
│ ├── wit/
│ │ └── world.wit # WIT interface
│ └── src/
│ └── 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
1단계: WIT 인터페이스 생성
WIT (WebAssembly Interface Types)는 호스트와 게스트 간의 계약을 정의합니다:
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;
}
각 export는 Wippy가 호출할 수 있는 함수가 됩니다.
2단계: Rust로 구현
demo/Cargo.toml을 생성합니다:
[package]
name = "demo"
version = "0.1.0"
edition = "2024"
[dependencies]
wit-bindgen-rt = { version = "0.44.0", features = ["bitflags"] }
[lib]
crate-type = ["cdylib"]
[profile.release]
opt-level = "s"
lto = true
[package.metadata.component]
package = "component:demo"
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);
bindings 모듈은 WIT 정의에서 cargo-component에 의해 생성됩니다.
3단계: 컴포넌트 빌드
cd demo
cargo component build --release
target/wasm32-wasip1/release/demo.wasm이 생성됩니다. Wippy 앱으로 복사합니다:
mkdir -p ../app/src/demo/wasm
cp target/wasm32-wasip1/release/demo.wasm ../app/src/demo/wasm/demo_component.wasm
무결성 검증을 위한 SHA-256 해시를 가져옵니다:
sha256sum ../app/src/demo/wasm/demo_component.wasm
4단계: Wippy 애플리케이션
인프라
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
파일시스템을 WASM 모듈에 마운트하는 것과 WASM 함수를 호출하는 것은 모두 보호되는 액션입니다. 이 정책이 두 액션을 허용하며, 이를 필요로 하는 엔트리가 정책을 참조합니다.
WASM 함수
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
핵심 사항:
- 단일
fs.directory엔트리가 WASM 바이너리를 제공합니다 - 여러 함수가 서로 다른
method값으로 동일한 바이너리를 참조합니다 hash필드는 로드 시 바이너리 무결성을 검증합니다inline풀은 호출마다 새 인스턴스를 생성합니다
WASI를 사용하는 함수
list-files 함수는 파일시스템에 접근하므로 WASI 임포트가 필요합니다:
- 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
wasi.mounts 섹션은 Wippy 파일시스템 엔트리를 게스트 경로에 매핑합니다. WASM 모듈 내부에서 /data는 demo.wasm:assets 디렉터리를 가리킵니다.
CLI 명령
app/src/demo/_index.yaml을 생성합니다:
version: "1.0"
namespace: demo.cli
entries:
- name: greet
kind: process.wasm
meta:
comment: Greet someone via WASM
command:
name: greet
short: Greet someone via WASM
fs: demo.wasm:assets
path: /demo_component.wasm
hash: sha256:YOUR_HASH_HERE
method: greet
- 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
meta.command 블록은 프로세스를 이름이 있는 CLI 명령으로 등록합니다. greet 명령은 문자열 연산만 사용하므로 WASI 임포트가 필요 없습니다. ls 명령은 파일시스템 접근이 필요하므로, 마운트를 허용하는 보안 컨텍스트도 함께 지닙니다.
HTTP 엔드포인트
app/src/demo/wasm/_index.yaml에 추가합니다:
- 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
wasi-http 트랜스포트는 HTTP 요청/응답 컨텍스트를 WASM 인자 및 결과에 매핑합니다.
5단계: 초기화 및 실행
cd app
wippy init
CLI 명령 실행
# 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>
명령 이름 뒤의 인자는 익스포트된 함수에 문자열 파라미터로 전달되므로, 각 명령은 WIT 시그니처가 선언한 인자를 정확히 받습니다:
# Run greet
wippy run greet World
Hello, World!
# Run ls to list mounted directory
wippy run ls /data
서비스로 실행
wippy run
포트 8090에서 HTTP 서버가 시작됩니다. wasi-http 트랜스포트는 요청 본문을 함수의 단일 문자열 인자로 전달합니다:
curl -X POST http://localhost:8090/greet -d 'World'
Hello, World!
Lua에서 호출
WASM 함수는 Lua 함수와 동일한 방식으로 호출됩니다. 호출하는 프로세스에는 대상에 대한 funcs.call 권한이 필요하며, demo:policy가 이를 부여합니다:
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