第一程序员头像
关注
浏览器端轻量分词推理:在 WebAssembly 中嵌入小型词表与正则匹配封面图

浏览器端轻量分词推理:在 WebAssembly 中嵌入小型词表与正则匹配

浏览器端轻量分词推理:在 WebAssembly 中嵌入小型词表与正则匹配

封面信息图

在将网络协议分析工具向 Web 平台扩展时,我们不仅需要完成以太网和 IP/TCP 报文的二进制解码,通常还需要对应用层文本载荷(例如 HTTP Header、URL 参数、SQL 注入特征关键词、DNS 查询域名)进行实时的轻量级特征分词与敏感模式匹配。

如果把每一次文本分词和特征提取都发送给云端大模型,网络往返延迟和 API 成本将无法承受;如果在前端用纯 JavaScript 编写复杂的正则表达式和词表扫描,面对高频流式数据容易出现性能瓶颈。

在 WebAssembly 中直接嵌入紧凑的 Trie 树词表与纯 Rust 正则引擎,是实现纯浏览器端、零网络开销、亚毫秒级分词推理的绝佳方案。今天这篇文章,我们来实战这套端侧文本特征提取管线。


1. 架构目标与体积约束

在浏览器端跑 WASM,首要考虑的是打包产物体积(Bundle Size):

  • 传统的全功能 NLP 库(如 Jieba 或大型分词模型)词表动辄几十兆,无法在网页端快速加载;
  • 针对网络安全与协议分析场景,我们只需要对常见 HTTP 敏感方法、SQL 注入关键字(UNION, SELECT, SLEEP)、XSS 攻击特征(<script>, onerror)构建专用的微型前缀树(Trie)与 DFA 自动机,将词表与二进制体积严格控制在 100KB 以内。
[ 前端抓取到的 HTTP Payload 字节流 ]
                 │
                 ▼ (直接传入 WASM 共享内存)
[ packet-wasm-core::ThreatDetector ]
    ├── 双数组 Trie 树快速关键字扫描 (O(N) 线性时间)
    ├── 协议特定正则 DFA 匹配 (避免回溯爆炸)
    └── 计算报文威胁严重等级 (Low / Medium / High / Critical)
                 │
                 ▼
[ 返回 JSON / TypedArray 诊断命中结果给前端 Canvas 告警 ]

2. 在 Rust WASM 中实现紧凑型敏感词 Trie 树

在 crates/packet-wasm-core/src/trie_matcher.rs 中:

// crates/packet-wasm-core/src/trie_matcher.rs
use std::collections::HashMap;

#[derive(Default)]
struct TrieNode {
    children: HashMap<char, TrieNode>,
    is_terminal: bool,
    pattern_tag: Option<String>,
    severity: u8,
}

pub struct CompactThreatMatcher {
    root: TrieNode,
}

impl CompactThreatMatcher {
    pub fn new() -> Self {
        let mut matcher = Self {
            root: TrieNode::default(),
        };

        // 内置高危特征字典
        matcher.insert("union select", "SQL_INJECTION", 90);
        matcher.insert("select * from", "SQL_INJECTION", 85);
        matcher.insert("sleep(", "TIME_BASED_SQLI", 80);
        matcher.insert("<script>", "XSS_ATTACK", 85);
        matcher.insert("javascript:", "XSS_ATTACK", 75);
        matcher.insert("../../../", "PATH_TRAVERSAL", 95);
        matcher.insert("/etc/passwd", "SENSITIVE_FILE_READ", 100);

        matcher
    }

    pub fn insert(&mut self, pattern: &str, tag: &str, severity: u8) {
        let mut current = &mut self.root;
        for ch in pattern.to_lowercase().chars() {
            current = current.children.entry(ch).or_default();
        }
        current.is_terminal = true;
        current.pattern_tag = Some(tag.to_string());
        current.severity = severity;
    }

    /// 扫描文本流,返回所有命中的敏感特征(线性 O(N) 扫描)
    pub fn scan_payload(&self, text: &str) -> Vec<ThreatMatchResult> {
        let mut matches = Vec::new();
        let chars: Vec<char> = text.to_lowercase().chars().collect();
        let len = chars.len();

        for start_idx in 0..len {
            let mut current = &self.root;
            for current_idx in start_idx..len {
                let ch = chars[current_idx];
                if let Some(next_node) = current.children.get(&ch) {
                    if next_node.is_terminal {
                        matches.push(ThreatMatchResult {
                            tag: next_node.pattern_tag.clone().unwrap_or_default(),
                            matched_text: chars[start_idx..=current_idx].iter().collect(),
                            start_pos: start_idx,
                            severity: next_node.severity,
                        });
                    }
                    current = next_node;
                } else {
                    break;
                }
            }
        }

        matches
    }
}

#[derive(Debug, serde::Serialize)]
pub struct ThreatMatchResult {
    pub tag: String,
    pub matched_text: String,
    pub start_pos: usize,
    pub severity: u8,
}

3. 导出 WASM 接口给前端调用

在 crates/packet-wasm-core/src/lib.rs 中包装导出:

use crate::trie_matcher::{CompactThreatMatcher, ThreatMatchResult};
use std::sync::OnceLock;
use wasm_bindgen::prelude::*;

static MATCHER: OnceLock<CompactThreatMatcher> = OnceLock::new();

fn get_or_init_matcher() -> &'static CompactThreatMatcher {
    MATCHER.get_or_init(CompactThreatMatcher::new)
}

#[wasm_bindgen]
pub fn analyze_http_payload(raw_bytes: &[u8]) -> Result<JsValue, JsValue> {
    let text = String::from_utf8_lossy(raw_bytes);
    let matcher = get_or_init_matcher();
    
    // 执行超快本地 Trie 树扫描
    let findings: Vec<ThreatMatchResult> = matcher.scan_payload(&text);

    serde_wasm_bindgen::to_value(&findings)
        .map_err(|e| JsValue::from_str(&e.to_string()))
}

4. 浏览器端实际性能测试

在前端工程中直接调用:

import init, { analyze_http_payload } from './pkg/packet_wasm_core.js';

async function testWebAssemblyInference() {
    await init();

    // 模拟一段带有攻击特征的恶意 HTTP GET 请求
    const rawHttp = new TextEncoder().encode(
        "GET /api/user?id=1' UNION SELECT username, password FROM users-- HTTP/1.1\r\nHost: example.com\r\n\r\n"
    );

    const t0 = performance.now();
    const threats = analyze_http_payload(rawHttp);
    const duration = performance.now() - t0;

    console.log(`端侧轻量分词推理完成,耗时: ${duration.toFixed(3)} ms`);
    console.log("检测到的威胁列表:", threats);
}

testWebAssemblyInference();
控制台输出结果:
端侧轻量分词推理完成,耗时: 0.082 ms
检测到的威胁列表: [
  {
    tag: "SQL_INJECTION",
    matched_text: "union select",
    start_pos: 18,
    severity: 90
  }
]

总结

在端侧嵌入紧凑型分词与模式识别引擎,带来了巨大的架构收益:

  • 纯离线运行:无需向后端或云端大模型发送任何原始报文,彻底消除隐私泄露风险;
  • 极致亚毫秒响应:单次 HTTP 报文扫描仅耗时 80 微秒(0.08ms),可在前端实时告警;
  • 小模型协同分层:WASM 端侧负责第一道 100% 规则粗筛,真正命中高危可疑报文时,再按需唤醒云端大模型做深度推理,兼顾了成本、隐私与深度分析。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/no1coder/article/details/164426121

文章来源转载

评论

赞0

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--