WASM 刮削插件开发指南
WASM (WebAssembly) 插件使用 Rust 语言编写,编译为 .wasm 文件。它提供了比 JS 更好的性能、更强的类型安全和更好的工程化支持。
1. 快速开始
1.1 项目结构
创建一个标准的 Rust 库项目:
cargo new --lib my-scraper-wasm编辑 Cargo.toml:
[package]
name = "my-scraper-wasm"
version = "0.1.0"
edition = "2021"
[lib]
crate-type = ["cdylib"]
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
# 其他依赖...提供插件配置文件 plugin.json(详情请参考 插件开发指南)。
1.2 核心代码 (src/lib.rs)
use std::ffi::{CStr, CString};
use std::os::raw::c_char;
use serde::{Deserialize, Serialize};
// 1. 定义数据结构
#[derive(Serialize)]
struct SearchResult {
items: Vec<BookItem>,
total: u32,
page: u32,
page_size: u32,
}
#[derive(Serialize)]
struct BookItem {
id: String,
title: String,
author: String,
cover_url: Option<String>,
intro: Option<String>,
tags: Vec<String>,
// ... 其他字段
}
// 2. 导出 invoke 函数 (必须!)
// 它是插件与宿主交互的唯一入口
#[no_mangle]
pub extern "C" fn invoke(method_ptr: *const c_char, params_ptr: *const c_char) -> *mut c_char {
let method = unsafe { CStr::from_ptr(method_ptr).to_string_lossy() };
let params_json = unsafe { CStr::from_ptr(params_ptr).to_string_lossy() };
let result = match method.as_ref() {
"search" => handle_search(¶ms_json).map(|r| serde_json::to_string(&r).unwrap()),
// 未来扩展其他方法...
_ => Err(format!("Unknown method: {}", method)),
};
let response_json = match result {
Ok(json) => json,
Err(e) => serde_json::json!({ "error": e }).to_string(),
};
CString::new(response_json).unwrap().into_raw()
}
// 3. 内存管理导出 (必须!)
// 宿主环境需要分配和释放 WASM 内存以传递字符串
#[no_mangle]
pub extern "C" fn alloc(len: usize) -> *mut u8 {
let mut buf = Vec::with_capacity(len);
let ptr = buf.as_mut_ptr();
std::mem::forget(buf);
ptr
}
#[no_mangle]
pub extern "C" fn dealloc(ptr: *mut u8, len: usize) {
unsafe {
let _ = Vec::from_raw_parts(ptr, 0, len);
}
}
// 4. 业务逻辑实现
fn handle_search(params_json: &str) -> Result<SearchResult, String> {
// 解析 JSON 参数
let params: SearchParams = serde_json::from_str(params_json).map_err(|e| e.to_string())?;
// 发起 HTTP 请求 (需自行封装宿主提供的 http_request)
let url = format!("https://api.example.com/search?q={}", params.query);
let body = fetch_url(&url)?;
// 解析响应并构造结果
// ...
// 最佳实践:如果提供了 author 筛选,在此处进行过滤或重排
if let Some(author) = params.author {
// ... filter logic
}
Ok(SearchResult {
items: vec![], // ...
total: 0,
page: params.page,
page_size: 20
})
}1.3 编译
cargo build --target wasm32-wasip1 --release编译产物位于 target/wasm32-wasip1/release/my_scraper_wasm.wasm。
2. 宿主函数
WASM 插件可以通过 extern "C" 调用宿主提供的功能,由于沙箱隔离,插件必须通过宿主函数进行网络请求:
#[link(wasm_import_module = "ting_env")]
extern "C" {
/// 发起 HTTP GET 请求,返回请求句柄(≥0)或错误码(<0)。
fn http_request(url_ptr: *const u8, url_len: i32) -> i32;
/// 发起 HTTP POST 请求,返回请求句柄(≥0)或错误码(<0)。
fn http_post(url_ptr: *const u8, url_len: i32, body_ptr: *const u8, body_len: i32) -> i32;
/// 发起带有 Bearer Token 的 HTTP GET 请求
fn http_get_with_token(url_ptr: *const u8, url_len: i32, token_ptr: *const u8, token_len: i32) -> i32;
/// 发起自定义 HTTP 请求(支持指定 method 和 headers JSON)
fn http_request_with_headers(
url_ptr: *const u8, url_len: i32,
method_ptr: *const u8, method_len: i32,
headers_ptr: *const u8, headers_len: i32,
body_ptr: *const u8, body_len: i32
) -> i32;
/// 获取响应体长度(字节)。
fn http_response_size(handle: i32) -> i32;
/// 读取响应体到缓冲区,返回实际读取的字节数(≤ len)或错误码。
fn http_read_body(handle: i32, ptr: *mut u8, len: i32) -> i32;
}2.1 封装示例:发起自定义网络请求
宿主提供的函数需要手动进行指针传递,在业务逻辑中,推荐像下面这样封装一层 Rust 函数,以方便直接使用 &str 等数据类型:
fn fetch_url_custom(url: &str, method: &str, headers_json: &str, body_data: &[u8]) -> Result<Vec<u8>, String> {
let handle = unsafe {
http_request_with_headers(
url.as_ptr(), url.len() as i32,
method.as_ptr(), method.len() as i32,
headers_json.as_ptr(), headers_json.len() as i32,
body_data.as_ptr(), body_data.len() as i32
)
};
if handle < 0 {
return Err(format!("HTTP custom request failed: {}", -handle));
}
let size = unsafe { http_response_size(handle) };
if size < 0 {
return Err("Failed to get response size".to_string());
}
let mut body = vec![0u8; size as usize];
let read = unsafe { http_read_body(handle, body.as_mut_ptr(), size) };
if read < 0 {
return Err("Failed to read body".to_string());
}
Ok(body)
}3. 部署
将编译好的 .wasm 文件和 plugin.json 放入 plugins/my-scraper-wasm/ 目录即可。