本文最后编辑于 前,其中的内容可能需要更新。
proxy_wasm 是一套相对通用的标准,他允许实现了这套标准的wasm插件在envoy或者apisix网关中对请求进行一些操作。
proxy_wasm目前支持多个语言的sdk,支持的sdk参考此处Proxy-Wasm · GitHub,这边使用rust来写,实际上各个语言的写法大同小异。
开发环境
安装rust
1 2 3 4
| curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh # 安装rust cargo install wasm-gc # 裁剪wasm包,也可不安装
|
初始化项目
1
| cargo new hello-wold --lib
|
在Cargo.toml中添加动态库编译配置和sdk依赖
1 2 3 4 5 6 7
| [lib] path = "src/lib.rs" crate-type = ["cdylib"]
[dependencies] proxy-wasm = "0.2"
|
代码部分
初始化配置
其实比较简单,基本都是固定写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #[no_mangle] pub fn _start() { proxy_wasm::set_log_level(LogLevel::Trace); proxy_wasm::set_root_context(|_| -> Box<dyn RootContext> { Box::new(MydRoot{ config: FilterConfig{ host: "".to_string(), } }) }); }
struct MyRoot { config: FilterConfig }
struct MyFilter{ context_id: u32, config: FilterConfig, }
struct FilterConfig { host: String, }
|
以上定义了三个结构体,实际上起作用的部分为MyFilter,MyRoot结构体用于初始化配置的读取以及参数传递
wasm启动时,将初始化一些配置,我们实际的配置则通过配置文件读取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| impl RootContext for MyRoot { fn on_configure(&mut self, _plugin_configuration_size: usize) -> bool { if self.config.host == "" { match self.get_plugin_configuration { Some(config_bytes) => { let cfg:Value = serde_json::from_slice(config_bytes.as_slice()).unwrap(); self.config.host = cfg.get("host") .unwrap() .as_str() .unwrap() .to_string(); } None => { warn!("can not get config"); } } } true } fn create_http_context(&self, context_id: u32) -> Option<Box<dyn HttpContext>> { info!("::::create_http_context,context_id: {} ,host: {}",context_id,self.config.host); Some(Box::new(MyFilter{ context_id, config: FilterConfig{ host: self.config.host.clone(), } })) } fn get_type(&self) -> Option<ContextType> { Some(ContextType::HttpContext) } }
impl Context for MyRoot {}
|
逻辑
到这边开始就可以写具体逻辑了,一般性来说我们只需要在HttpContext的trait中实现逻辑即可。这边的操作是在返回的请求中添加了两个请求头。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| impl HttpContext for Filter { fn on_http_request_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action {
for (name, value) in &self.get_http_request_headers() { info!("::::H[{}] -> {}: {}", self.context_id, name, value); } Action::Pause }
fn on_http_response_headers(&mut self, _num_headers: usize, _end_of_stream: bool) -> Action { self.set_http_response_header("X-Test-Hosts", Some(self.config.host.as_str())); self.set_http_response_header("Powered-By", Some("proxy-wasm")); Action::Continue } }
|
编译
1 2 3 4
| cargo build --target=wasm32-unknown-unknown --release
wasm-gc target/wasm32-unknown-unknown/release/$WASMFILE.wasm # 裁剪wasm包
|
测试
编写docker-compose文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| version: '3.7' services: envoy: image: envoyproxy/envoy:v1.21-latest depends_on: - httpbin networks: - wasmtest ports: - "10000:10000" volumes: - ./envoy.yaml:/etc/envoy/envoy.yaml - ./$WASMFILE.wasm:/etc/envoy/main.wasm httpbin: image: kennethreitz/httpbin:latest networks: - wasmtest ports: - "12345:80"
networks: wasmtest: {}
|
修改envoy.yaml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| admin: address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 9901 static_resources: listeners: - name: listener_0 address: socket_address: protocol: TCP address: 0.0.0.0 port_value: 10000 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager scheme_header_transformation: scheme_to_overwrite: https stat_prefix: ingress_http route_config: name: local_route virtual_hosts: - name: local_service domains: ["*"] routes: - match: prefix: "/" route: cluster: httpbin http_filters: - name: wasmdemo typed_config: "@type": type.googleapis.com/udpa.type.v1.TypedStruct type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm value: config: name: wasmdemo vm_config: runtime: envoy.wasm.runtime.v8 code: local: filename: /etc/envoy/main.wasm configuration: "@type": "type.googleapis.com/google.protobuf.StringValue" value: | { "host":"http://auth", } - name: envoy.filters.http.router clusters: - name: httpbin connect_timeout: 30s type: LOGICAL_DNS dns_lookup_family: V4_ONLY lb_policy: ROUND_ROBIN load_assignment: cluster_name: httpbin endpoints: - lb_endpoints: - endpoint: address: socket_address: address: httpbin port_value: 80
|
启动验证即可
附录
Http请求阶段挂载表
附注: grpc 也有类似方法,此处不多做讲解
| HTTP 处 理 挂 载 点 |
触 发 时 机 |
挂 载 ⽅ 法 |
| HTTP 请 求 头 处 理 阶 段 |
- 关 接 收 到 客 户 端 发 送 来 的 请 求 头 数 据 时 |
on_http_request_headers |
| HTTP 请 求 Body 处 理 阶 段 |
- 关 接 收 到 客 户 端 发 送 来 的 请 求 Body 数 据 时 |
on_http_request_body |
| HTTP 请 求 Trailer 处 理 阶 段 |
- 关 接 收 到 客 户 端 发 送 来 的 请 求 Trailer 数 据 时 |
on_http_request_trailers |
| HTTP 应 答 头 处 理 阶 段 |
- 关 接 收 到 后 端 服 务 响 应 的 应 答 头 数 据 时 |
on_http_response_headers |
| HTTP 应 答 Body 处 理 阶 段 |
- 关 接 收 到 后 端 服 务 响 应 的 应 答 Body 数 据 时 |
on_http_response_body |
| HTTP 应 答 Trailer 处 理 阶 段 |
- 关 接 收 到 后 端 服 务 响 应 的 应 答 Trailer 数 据 时 |
on_http_response_trailers |
方法汇总(仅列出部分)
| 分 类 |
方法 名 称 |
用途 |
可 以 ⽣ 效 的 挂 载 ⽅ 法 |
| 请 求 头 处 理 |
get_http_request_headers |
获 取 客 户 端 请 求 的 全 部 请 求 头 |
on_http_request_headers |
|
set_http_request_headers |
替 换 客 户 端 请 求 的 全 部 请 求 头 |
on_http_request_headers |
|
get_http_request_header |
获 取 客 户 端 请 求 的 指 定 请 求 头 |
on_http_request_headers |
|
set_http_request_header |
设置客 户 端 请 求 的 指 定 请 求 头 |
on_http_request_headers |
|
add_http_request_header |
新 增 ⼀ 个 客 户 端 请 求 头 |
on_http_request_headers |
| 请 求 Body 处 理 |
get_http_request_body |
获 取 客 户 端 请 求 Body |
on_http_request_body |
|
set_http_request_body |
替 换 客 户 端 请 求 Body |
on_http_request_body |
| 请 求 Trailer 处 理 |
get_http_request_trailers |
获 取 客 户 端 请 求 的 全 部 请 求 Trailer |
on_http_request_trailers |
|
set_http_request_trailers |
替 换 客 户 端 请 求 的 全 部 请 求 Trailer |
on_http_request_trailers |
|
get_http_request_trailer |
获 取 客 户 端 请 求 的 指 定 请 求 Trailer |
on_http_request_trailers |
|
set_http_request_trailer |
替 换 客 户 端 请 求 的 指 定 请 求 Trailer |
on_http_request_trailers |
|
add_http_response_trailer |
新 增 ⼀ 个 客 户 端 请 求 Trailer |
on_http_request_trailers |
| 应 答 头 处 理 |
get_http_response_headers |
获 取 后 端 响 应 的 全 部 应 答 头 |
on_http_response_headers |
|
set_http_response_headers |
替 换 后 端 响 应 的 全 部 应 答 头 |
on_http_response_headers |
|
get_http_response_header |
获 取 后 端 响 应 的 指 定 应 答 头 |
on_http_response_headers |
|
set_http_response_header |
替 换 后 端 响 应 的 指 定 应 答 头 |
on_http_response_headers |
|
add_http_response_header |
新 增 ⼀ 个 后 端 响 应 头 |
on_http_response_headers |
| 应 答 Body 处 理 |
get_http_response_body |
获 取 客 户 端 请 求 Body |
on_http_response_body |
|
set_http_response_body |
替 换 后 端 响 应 Body |
on_http_response_body |
| 应 答 Trailer 处 理 |
get_http_response_trailers |
获 取 后 端 响 应 的 全 部 应 答 Trailer |
on_http_response_trailers |
|
set_http_response_trailers |
替 换 后 端 响 应 的 全 部 应 答 |
on_http_response_trailers |
|
get_http_response_trailer |
获 取 后 端 响 应 的 指 定 应 答 Trailer |
on_http_response_trailers |
|
set_http_response_trailer |
替 换 后 端 响 应 的 指 定 应 答 Trailer |
on_http_response_trailers |
|
add_http_response_trailer |
新 增 ⼀ 个 后 端 响 应 |
on_http_response_trailers |
| HTTP 调 ⽤ |
dispatch_http_call |
发 送 ⼀ 个 HTTP 请 求 |
- |
|
get_http_call_response_headers |
获 取 DispatchHttpCall 请 求 响 应 的 应 答 头 |
- |
|
get_http_call_response_body |
获 取 DispatchHttpCall 请 求 响 应 的 应 答 Body |
- |
|
get_http_call_response_trailers |
获 取 DispatchHttpCall 请 求 响 应 的 应 答 Trailer |
- |
| 直 接 响 应 |
send_http_response |
直 接 返 回 ⼀ 个 特 定 的 HTTP 应 答 |
- |
| 上 下 ⽂ 切 换 |
resume_http_request |
恢 复 先 前 被 暂 停 的 请 求 处 理 流 程 |
- |
|
resume_http_response |
恢 复 先 前 被 暂 停 的 应 答 处 理 流 程 |
- |