本文最后编辑于  前,其中的内容可能需要更新。
                
                
                    
                
                
                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 | 
恢 复 先 前 被 暂 停 的 应 答 处 理 流 程 | 
- |