├── NOTICE ├── examples ├── json_validation │ ├── Dockerfile │ ├── gatewaydeploymentpatch.yaml │ ├── wasmplugin.yaml │ ├── go.mod │ ├── envoyfilter.yaml │ ├── go.sum │ └── main_test.go ├── postpone_requests │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── main_test.go │ ├── envoy.yaml │ └── main.go ├── wasm-image.Dockerfile ├── shared_queue │ ├── go.mod │ └── receiver │ │ └── main.go ├── http_routing │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── main_test.go │ └── main.go ├── metrics │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── main_test.go │ ├── envoy.yaml │ └── main.go ├── network │ ├── go.mod │ ├── README.md │ ├── go.sum │ ├── envoy.yaml │ └── main_test.go ├── helloworld │ ├── go.mod │ ├── README.md │ ├── go.sum │ ├── envoy.yaml │ ├── main_test.go │ └── main.go ├── http_body │ ├── go.mod │ ├── go.sum │ └── README.md ├── properties │ ├── go.mod │ ├── go.sum │ ├── README.md │ ├── main.go │ └── main_test.go ├── shared_data │ ├── go.mod │ ├── README.md │ ├── go.sum │ ├── envoy.yaml │ ├── main_test.go │ └── main.go ├── vm_plugin_configuration │ ├── README.md │ ├── go.mod │ ├── go.sum │ ├── main.go │ ├── main_test.go │ └── envoy.yaml ├── http_auth_random │ ├── go.mod │ ├── go.sum │ ├── envoy.yaml │ └── README.md ├── http_body_chunk │ ├── go.mod │ ├── go.sum │ ├── README.md │ └── envoy.yaml ├── dispatch_call_on_tick │ ├── go.mod │ ├── go.sum │ ├── README.md │ ├── main_test.go │ └── main.go ├── foreign_call_on_tick │ ├── go.mod │ ├── go.sum │ ├── README.md │ ├── main_test.go │ ├── envoy.yaml │ └── main.go ├── multiple_dispatches │ ├── go.mod │ ├── go.sum │ ├── README.md │ ├── envoy.yaml │ ├── main_test.go │ └── main.go └── http_headers │ ├── go.mod │ ├── README.md │ ├── go.sum │ └── envoy.yaml ├── README.md ├── go.mod ├── properties ├── properties.go ├── xsd_test.go ├── types_test.go ├── xsd.go ├── response.go ├── util.go └── request.go ├── proxywasm ├── proxytest │ ├── README.md │ ├── option.go │ └── timing_off_test.go ├── internal │ ├── abi_callback_version.go │ ├── hostcall_utls.go │ ├── abi_callback_alloc.go │ ├── timing_off.go │ ├── abi_callback_timers.go │ ├── abi_callback_queue.go │ ├── hostcall_utils_test.go │ ├── timing_on.go │ ├── vmstate_test_export.go │ ├── hostcall_utils.go │ ├── abi_callback_timers_test.go │ ├── abi_callback_queue_test.go │ ├── abi_callback_configuration.go │ ├── abi_callback_l4_test.go │ ├── abi_callback_lifecycle.go │ ├── serde_test.go │ ├── abi_callback_configuration_test.go │ ├── serde.go │ ├── abi_callback_l4.go │ ├── abi_enums.go │ ├── abi_callback_test_export.go │ └── vmstate.go ├── entrypoint.go └── types │ └── types.go ├── go.sum └── Makefile /NOTICE: -------------------------------------------------------------------------------- 1 | proxy-wasm-go-sdk 2 | Copyright 2020-2024 Tetrate 3 | -------------------------------------------------------------------------------- /examples/json_validation/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM scratch 2 | 3 | COPY main.wasm ./plugin.wasm 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | The work here has been revived and continued at https://github.com/proxy-wasm/proxy-wasm-go-sdk for GOOS=wasip1 support. 2 | -------------------------------------------------------------------------------- /examples/postpone_requests/README.md: -------------------------------------------------------------------------------- 1 | ## postpone_requests 2 | 3 | this example postpones http requests for 0.0...1.0 seconds. 4 | 5 | ``` 6 | wasm log: postpone request with contextID=2 7 | wasm log: resume request with contextID=2 8 | ``` 9 | -------------------------------------------------------------------------------- /examples/wasm-image.Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for building "compat" variant of Wasm Image Specification. 2 | # https://github.com/solo-io/wasm/blob/master/spec/spec-compat.md 3 | FROM scratch 4 | 5 | ARG WASM_BINARY_PATH 6 | COPY ${WASM_BINARY_PATH} ./plugin.wasm 7 | -------------------------------------------------------------------------------- /examples/shared_queue/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/shared_queue 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 8 | -------------------------------------------------------------------------------- /examples/http_routing/README.md: -------------------------------------------------------------------------------- 1 | ## http_routing 2 | 3 | this example proxies http requests and randomly route them to primary/canary clusters by manipulating :authorty header. 4 | 5 | ``` 6 | $ curl localhost:18000 7 | hello from primary! 8 | 9 | $ curl localhost:18000 10 | hello from canary! 11 | ``` -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/stretchr/testify v1.9.0 7 | github.com/tetratelabs/wazero v1.7.2 8 | ) 9 | 10 | require ( 11 | github.com/davecgh/go-spew v1.1.1 // indirect 12 | github.com/pmezard/go-difflib v1.0.0 // indirect 13 | gopkg.in/yaml.v3 v3.0.1 // indirect 14 | ) 15 | -------------------------------------------------------------------------------- /examples/metrics/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## metrics 3 | 4 | this example creates simple request counter with prometheus tags. 5 | 6 | ``` 7 | $ curl localhost:18000 -v -H "my-custom-header: foo" 8 | 9 | $ curl -s 'localhost:8001/stats/prometheus'| grep proxy 10 | # TYPE custom_header_value_counts counter 11 | custom_header_value_counts{value="foo",reporter="wasmgosdk"} 1 12 | ``` 13 | -------------------------------------------------------------------------------- /examples/json_validation/gatewaydeploymentpatch.yaml: -------------------------------------------------------------------------------- 1 | spec: 2 | template: 3 | spec: 4 | containers: 5 | - name: istio-proxy 6 | volumeMounts: 7 | - name: wasm-plugins 8 | mountPath: /var/local/lib/wasm-plugins 9 | readOnly: true 10 | volumes: 11 | - name: wasm-plugins 12 | configMap: 13 | name: wasm-plugins 14 | -------------------------------------------------------------------------------- /examples/json_validation/wasmplugin.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions.istio.io/v1alpha1 2 | kind: WasmPlugin 3 | metadata: 4 | name: json-validation 5 | namespace: istio-system 6 | spec: 7 | selector: 8 | matchLabels: 9 | istio: ingressgateway 10 | url: oci://YOUR_CONTAINER_REGISTRY/json-validation:v1 11 | imagePullPolicy: IfNotPresent 12 | phase: AUTHN 13 | pluginConfig: 14 | requiredKeys: ["id", "token"] 15 | -------------------------------------------------------------------------------- /properties/properties.go: -------------------------------------------------------------------------------- 1 | // Package properties provides helper functions for retrieving properties in the Envoy/Istio specific environment. 2 | // 3 | // WARNING: There's absolutely no guarantee that all properties will be available across versions, and the availability is totally 4 | // dependent of the configuration, so users are highly encouraged to ensure that plugins work as expected when deploying 5 | // the plugins using these properties. 6 | package properties 7 | -------------------------------------------------------------------------------- /proxywasm/proxytest/README.md: -------------------------------------------------------------------------------- 1 | ## Test framework for proxy-wasm-go-sdk 2 | 3 | Using proxytest, you can test your extension with the official command: 4 | 5 | ``` 6 | go test ./... 7 | ``` 8 | 9 | This framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy. 10 | For detail, see `examples/*/main_test.go`. 11 | 12 | 13 | Note that we have not covered all the functionality, and the API is very likely to change in the future. -------------------------------------------------------------------------------- /examples/metrics/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/metrics 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/network/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/network 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/helloworld/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/helloworld 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/http_body/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/http_body 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/properties/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/properties 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/shared_data/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/shared_data 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/http_routing/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/http_routing 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/vm_plugin_configuration/README.md: -------------------------------------------------------------------------------- 1 | ## vm_plugin_configuration 2 | 3 | This example reads the json string from Envoy's configuration yaml at the startup time 4 | 5 | 6 | ``` 7 | wasm log my_root_id: vm config: { 8 | "name": "vm configuration" 9 | } 10 | 11 | 12 | wasm log my_root_id: plugin config: { 13 | "name": "plugin configuration" 14 | } 15 | 16 | 17 | wasm log my_root_id: vm config: { 18 | "name": "vm configuration" 19 | } 20 | 21 | 22 | wasm log my_root_id: plugin config: { 23 | "name": "plugin configuration" 24 | } 25 | ``` 26 | -------------------------------------------------------------------------------- /examples/http_auth_random/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/http_auth_random 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/http_body_chunk/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/http_body_chunk 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/postpone_requests/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/postpone_requests 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/dispatch_call_on_tick/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/dispatch_call_on_tick 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/foreign_call_on_tick/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/foreign_call_on_tick 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/multiple_dispatches/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/multiple_dispatches 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/vm_plugin_configuration/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/vm_plugin_configuration 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | ) 11 | 12 | require ( 13 | github.com/davecgh/go-spew v1.1.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/tetratelabs/wazero v1.7.2 // indirect 16 | gopkg.in/yaml.v3 v3.0.1 // indirect 17 | ) 18 | -------------------------------------------------------------------------------- /examples/helloworld/README.md: -------------------------------------------------------------------------------- 1 | ## helloworld 2 | 3 | this example periodically logs the current time in nanoseconds 4 | 5 | ``` 6 | wasm log helloworld: OnPluginStart from Go! 7 | wasm log helloworld: OnPluginStart from Go! 8 | wasm log helloworld: OnTick on 1, it's 1601543078943978000 9 | wasm log helloworld: OnTick on 1, it's 1601543078947916000 10 | wasm log helloworld: OnTick on 1, it's 1601543078951979000 11 | wasm log helloworld: OnTick on 1, it's 1601543079947462000 12 | wasm log helloworld: OnTick on 1, it's 1601543079951503000 13 | wasm log helloworld: OnTick on 1, it's 1601543079955484000 14 | ``` 15 | -------------------------------------------------------------------------------- /examples/json_validation/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/json_validation 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.16.0 10 | github.com/tidwall/gjson v1.14.1 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | github.com/tetratelabs/wazero v1.7.2 // indirect 17 | github.com/tidwall/match v1.1.1 // indirect 18 | github.com/tidwall/pretty v1.2.0 // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /examples/network/README.md: -------------------------------------------------------------------------------- 1 | ## network 2 | 3 | this example handles tcp connections and output data into the log stream. 4 | 5 | 6 | ```bash 7 | wasm log: new connection! 8 | wasm log: >>>>>> downstream data received >>>>>> 9 | GET /uuid HTTP/1.1 10 | Host: localhost:18000 11 | User-Agent: curl/7.68.0 12 | Accept: */* 13 | 14 | 15 | wasm log: remote address: 127.0.0.1:8099 16 | wasm log: <<<<<< upstream data received <<<<<< 17 | HTTP/1.1 200 OK 18 | content-length: 13 19 | content-type: text/plain 20 | date: Thu, 01 Oct 2020 09:16:33 GMT 21 | server: envoy 22 | 23 | example body 24 | 25 | wasm log: downstream connection close! 26 | wasm log: connection complete! 27 | ``` 28 | -------------------------------------------------------------------------------- /examples/http_headers/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/tetratelabs/proxy-wasm-go-sdk/examples/http_headers 2 | 3 | go 1.19 4 | 5 | replace github.com/tetratelabs/proxy-wasm-go-sdk => ../.. 6 | 7 | require ( 8 | github.com/stretchr/testify v1.9.0 9 | github.com/tetratelabs/proxy-wasm-go-sdk v0.0.0-00010101000000-000000000000 10 | github.com/tidwall/gjson v1.14.3 11 | ) 12 | 13 | require ( 14 | github.com/davecgh/go-spew v1.1.1 // indirect 15 | github.com/pmezard/go-difflib v1.0.0 // indirect 16 | github.com/tetratelabs/wazero v1.7.2 // indirect 17 | github.com/tidwall/match v1.1.1 // indirect 18 | github.com/tidwall/pretty v1.2.0 // indirect 19 | gopkg.in/yaml.v3 v3.0.1 // indirect 20 | ) 21 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_version.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import "time" 18 | 19 | //export proxy_abi_version_0_2_0 20 | func proxyABIVersion() { 21 | if recordTiming { 22 | defer logTiming("proxyABIVersion", time.Now()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /proxywasm/internal/hostcall_utls.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "unsafe" 19 | ) 20 | 21 | func StringBytePtr(msg string) *byte { 22 | if len(msg) == 0 { 23 | return nil 24 | } 25 | bt := *(*[]byte)(unsafe.Pointer(&msg)) 26 | return &bt[0] 27 | } 28 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_alloc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import "time" 18 | 19 | //export proxy_on_memory_allocate 20 | func proxyOnMemoryAllocate(size uint) *byte { 21 | if recordTiming { 22 | defer logTiming("proxyOnMemoryAllocate", time.Now()) 23 | } 24 | buf := make([]byte, size) 25 | return &buf[0] 26 | } 27 | -------------------------------------------------------------------------------- /examples/json_validation/envoyfilter.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.istio.io/v1alpha3 2 | kind: EnvoyFilter 3 | metadata: 4 | name: json-validation 5 | namespace: istio-system 6 | spec: 7 | configPatches: 8 | - applyTo: HTTP_FILTER 9 | match: 10 | context: GATEWAY 11 | patch: 12 | operation: INSERT_BEFORE 13 | value: 14 | name: json-validation 15 | typed_config: 16 | "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 17 | config: 18 | configuration: 19 | "@type": type.googleapis.com/google.protobuf.StringValue 20 | value: | 21 | { "requiredKeys": ["id", "token"] } 22 | vm_config: 23 | code: 24 | local: 25 | filename: /var/local/lib/wasm-plugins/main.wasm 26 | runtime: envoy.wasm.runtime.v8 27 | vm_id: json-validation 28 | -------------------------------------------------------------------------------- /examples/shared_data/README.md: -------------------------------------------------------------------------------- 1 | 2 | ## shared_data 3 | 4 | this example uses the shared key value store (across VMs) 5 | and increments the global value in response to http requests atomically. 6 | 7 | ``` 8 | wasm log: shared value: 1 9 | wasm log: shared value: 2 10 | wasm log: shared value: 3 11 | wasm log: shared value: 4 12 | wasm log: shared value: 5 13 | wasm log: shared value: 6 14 | wasm log: shared value: 7 15 | wasm log: shared value: 8 16 | wasm log: shared value: 9 17 | wasm log: shared value: 10 18 | wasm log: shared value: 11 19 | wasm log: shared value: 12 20 | wasm log: shared value: 13 21 | wasm log: shared value: 14 22 | wasm log: shared value: 15 23 | wasm log: shared value: 16 24 | wasm log: shared value: 17 25 | wasm log: shared value: 18 26 | wasm log: shared value: 19 27 | wasm log: shared value: 20 28 | wasm log: shared value: 21 29 | wasm log: shared value: 22 30 | wasm log: shared value: 23 31 | wasm log: shared value: 24 32 | wasm log: shared value: 25 33 | ``` -------------------------------------------------------------------------------- /proxywasm/internal/timing_off.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !proxywasm_timing 16 | 17 | package internal 18 | 19 | import "time" 20 | 21 | // When no build tag is specified, we do record print timing information so set this to false. 22 | const recordTiming = false 23 | 24 | func logTiming(msg string, start time.Time) { 25 | panic("BUG: logTiming should not be called when timing is disabled") 26 | } 27 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_timers.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import "time" 18 | 19 | //export proxy_on_tick 20 | func proxyOnTick(pluginContextID uint32) { 21 | if recordTiming { 22 | defer logTiming("proxyOnTick", time.Now()) 23 | } 24 | ctx, ok := currentState.pluginContexts[pluginContextID] 25 | if !ok { 26 | panic("invalid root_context_id") 27 | } 28 | currentState.setActiveContextID(pluginContextID) 29 | ctx.context.OnTick() 30 | } 31 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /proxywasm/entrypoint.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxywasm 16 | 17 | import ( 18 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/internal" 19 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 20 | ) 21 | 22 | // SetVMContext is the entrypoint for setting up the entire Wasm VM. 23 | // Please make sure to call this entrypoint during "main()" function; 24 | // otherwise, the VM fails. 25 | func SetVMContext(ctx types.VMContext) { 26 | internal.SetVMContext(ctx) 27 | } 28 | -------------------------------------------------------------------------------- /examples/metrics/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/network/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_queue.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import "time" 18 | 19 | //export proxy_on_queue_ready 20 | func proxyOnQueueReady(contextID, queueID uint32) { 21 | if recordTiming { 22 | defer logTiming("proxyOnQueueReady", time.Now()) 23 | } 24 | ctx, ok := currentState.pluginContexts[contextID] 25 | if !ok { 26 | panic("invalid context") 27 | } 28 | 29 | currentState.setActiveContextID(contextID) 30 | ctx.context.OnQueueReady(queueID) 31 | } 32 | -------------------------------------------------------------------------------- /examples/helloworld/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/http_body/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/http_routing/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/properties/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/shared_data/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/http_auth_random/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/http_body_chunk/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/postpone_requests/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/dispatch_call_on_tick/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/foreign_call_on_tick/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/multiple_dispatches/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/vm_plugin_configuration/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 10 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 11 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /examples/http_body/README.md: -------------------------------------------------------------------------------- 1 | ## http_body 2 | 3 | this example demonstrates how to perform operation on a request or response body like append/prepend/replace. 4 | 5 | To modify the request: 6 | ``` 7 | $ curl -XPUT localhost:18000 --data '[initial body]' -H "buffer-operation: prepend" 8 | [this is prepended body][initial body] 9 | 10 | $ curl -XPUT localhost:18000 --data '[initial body]' -H "buffer-operation: append" 11 | [initial body][this is appended body] 12 | 13 | $ curl -XPUT localhost:18000 --data '[initial body]' -H "buffer-operation: replace" 14 | [this is replaced body] 15 | ``` 16 | 17 | To modify the response: 18 | ``` 19 | $ curl -XPUT localhost:18000 --data '[initial body]' -H "buffer-operation: prepend" -H "buffer-replace-at: response" 20 | [this is prepended body][initial body] 21 | 22 | $ curl -XPUT localhost:18000 --data '[initial body]' -H "buffer-operation: append" -H "buffer-replace-at: response" 23 | [initial body][this is appended body] 24 | 25 | $ curl -XPUT localhost:18000 --data '[initial body]' -H "buffer-operation: replace" -H "buffer-replace-at: response" 26 | [this is replaced body] 27 | ``` 28 | -------------------------------------------------------------------------------- /proxywasm/internal/hostcall_utils_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "reflect" 19 | "testing" 20 | "unsafe" 21 | 22 | "github.com/stretchr/testify/require" 23 | ) 24 | 25 | func Test_StringBytePtr(t *testing.T) { 26 | exp := "abcd" 27 | ptr := StringBytePtr(exp) 28 | 29 | //nolint 30 | actual := *(*string)(unsafe.Pointer(&reflect.SliceHeader{ 31 | Data: uintptr(unsafe.Pointer(ptr)), 32 | Len: len(exp), 33 | Cap: len(exp), 34 | })) 35 | require.Equal(t, exp, actual) 36 | } 37 | -------------------------------------------------------------------------------- /examples/dispatch_call_on_tick/README.md: -------------------------------------------------------------------------------- 1 | ## dispatch_call_on_tick 2 | 3 | this example periodically dispatches http calls 4 | 5 | ``` 6 | wasm log dispatch: called! 1 7 | wasm log dispatch: called! 1 8 | wasm log dispatch: called! 1 9 | wasm log dispatch: called! 2 10 | wasm log dispatch: called! 2 11 | wasm log dispatch: called! 2 12 | wasm log dispatch: called! 3 13 | wasm log dispatch: called! 3 14 | wasm log dispatch: called! 3 15 | wasm log dispatch: called! 4 16 | wasm log dispatch: called! 4 17 | wasm log dispatch: called! 4 18 | wasm log dispatch: called! 5 19 | wasm log dispatch: called! 5 20 | wasm log dispatch: called! 5 21 | wasm log dispatch: called! 6 22 | wasm log dispatch: called! 6 23 | wasm log dispatch: called! 6 24 | wasm log dispatch: called! 7 25 | wasm log dispatch: called! 7 26 | wasm log dispatch: called! 7 27 | wasm log dispatch: called! 8 28 | wasm log dispatch: called! 8 29 | wasm log dispatch: called! 8 30 | wasm log dispatch: called! 9 31 | wasm log dispatch: called! 9 32 | wasm log dispatch: called! 9 33 | wasm log dispatch: called! 10 34 | wasm log dispatch: called! 10 35 | wasm log dispatch: called! 10 36 | 37 | ``` 38 | -------------------------------------------------------------------------------- /proxywasm/internal/timing_on.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2022 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build proxywasm_timing 16 | 17 | package internal 18 | 19 | import ( 20 | "fmt" 21 | "time" 22 | ) 23 | 24 | // When the build tag is specified, we record timing information so set this to true. 25 | const recordTiming = true 26 | 27 | func logTiming(msg string, start time.Time) { 28 | if !recordTiming { 29 | panic("BUG: logTiming should not be called when timing is disabled") 30 | } 31 | f := fmt.Sprintf("%s took %s", msg, time.Since(start)) 32 | ProxyLog(LogLevelDebug, StringBytePtr(f), len(f)) 33 | } 34 | -------------------------------------------------------------------------------- /properties/xsd_test.go: -------------------------------------------------------------------------------- 1 | package properties 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/require" 7 | 8 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 9 | ) 10 | 11 | func TestGetXdsClusterName(t *testing.T) { 12 | opt := proxytest.NewEmulatorOption().WithProperty(xdsClusterName, []byte("outbound|80||httpbin.org")) 13 | _, reset := proxytest.NewHostEmulator(opt) 14 | defer reset() 15 | 16 | result, err := GetXdsClusterName() 17 | require.NoError(t, err) 18 | require.Equal(t, "outbound|80||httpbin.org", result) 19 | } 20 | 21 | func TestGetXdsRouteName(t *testing.T) { 22 | opt := proxytest.NewEmulatorOption().WithProperty(xdsRouteName, []byte("routename")) 23 | _, reset := proxytest.NewHostEmulator(opt) 24 | defer reset() 25 | 26 | result, err := GetXdsRouteName() 27 | require.NoError(t, err) 28 | require.Equal(t, "routename", result) 29 | } 30 | func TestGetXdsListenerFilterChainName(t *testing.T) { 31 | opt := proxytest.NewEmulatorOption().WithProperty(xdsListenerFilterChainName, []byte("mychain")) 32 | _, reset := proxytest.NewHostEmulator(opt) 33 | defer reset() 34 | 35 | result, err := GetXdsListenerFilterChainName() 36 | require.NoError(t, err) 37 | require.Equal(t, "mychain", result) 38 | } 39 | -------------------------------------------------------------------------------- /examples/http_body_chunk/README.md: -------------------------------------------------------------------------------- 1 | ## http_body_chunk 2 | 3 | This example demonstrates how to perform operations on a request body, chunk by chunk. 4 | 5 | Reading the received body chunk by chunk, it looks for the string `pattern` inside the body. If it finds it, a 403 response is returned providing the number of the chunk where the pattern was found. Logs are printed every time a chunk is received providing also the size of the read chunk. 6 | 7 | Build and run the example: 8 | ```bash 9 | $ make build.example name=http_body_chunk 10 | $ make run name=http_body_chunk 11 | ``` 12 | 13 | Perform a request with a body containing the string `pattern`: 14 | ```bash 15 | $ head -c 700000 /dev/urandom | base64 > /tmp/file.txt && echo "pattern" >> /tmp/file.txt && curl 'localhost:18000/anything' -d @/tmp/file.txt 16 | pattern found in chunk: 2 17 | ``` 18 | 19 | Generated logs: 20 | ``` 21 | wasm log: OnHttpRequestBody called. BodySize: 114532, totalRequestBodyReadSize: 0, endOfStream: false 22 | wasm log: read chunk size: 114532 23 | wasm log: OnHttpRequestBody called. BodySize: 114532, totalRequestBodyReadSize: 114532, endOfStream: false 24 | wasm log: OnHttpRequestBody called. BodySize: 933343, totalRequestBodyReadSize: 114532, endOfStream: true 25 | wasm log: read chunk size: 818811 26 | wasm log: pattern found in chunk: 2 27 | wasm log: local 403 response sent 28 | ``` 29 | -------------------------------------------------------------------------------- /proxywasm/internal/vmstate_test_export.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !tinygo 16 | 17 | package internal 18 | 19 | import "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 20 | 21 | func VMStateReset() { 22 | // (@mathetake) I assume that the currentState be protected by lock on hostMux 23 | currentState = &state{ 24 | pluginContexts: make(map[uint32]*pluginContextState), 25 | httpContexts: make(map[uint32]types.HttpContext), 26 | tcpContexts: make(map[uint32]types.TcpContext), 27 | contextIDToRootID: make(map[uint32]uint32), 28 | } 29 | } 30 | 31 | func VMStateGetActiveContextID() uint32 { 32 | return currentState.activeContextID 33 | } 34 | 35 | func VMStateSetActiveContextID(contextID uint32) { 36 | currentState.activeContextID = contextID 37 | } 38 | -------------------------------------------------------------------------------- /examples/multiple_dispatches/README.md: -------------------------------------------------------------------------------- 1 | ## multiple_dispatches 2 | 3 | This example dispatches multiple http calls to remote clusters while pausing the original http response processing from the upstream. 4 | Once the plugin recieved all the responses to all dispatched calls, it adds an http header to the original http response, and resumes it 5 | inside the dispatched callback. 6 | 7 | Note: the same logic can be performed for http *requests* as well with the corresponding functions. 8 | 9 | ### message on clients 10 | ``` 11 | $ curl --head localhost:18000 12 | HTTP/1.1 200 OK 13 | date: Tue, 16 Aug 2022 03:21:37 GMT 14 | content-type: text/html; charset=utf-8 15 | content-length: 9593 16 | server: envoy 17 | access-control-allow-origin: * 18 | access-control-allow-credentials: true 19 | x-envoy-upstream-service-time: 362 20 | total-dispatched: 10 <---- added inside the dispatched callback. 21 | ``` 22 | 23 | ### message on Envoy 24 | 25 | ``` 26 | wasm log: pending dispatched requests: 9 27 | wasm log: pending dispatched requests: 8 28 | wasm log: pending dispatched requests: 7 29 | wasm log: pending dispatched requests: 6 30 | wasm log: pending dispatched requests: 5 31 | wasm log: pending dispatched requests: 4 32 | wasm log: pending dispatched requests: 3 33 | wasm log: pending dispatched requests: 2 34 | wasm log: pending dispatched requests: 1 35 | wasm log: response resumed after processed 10 dispatched request 36 | ``` 37 | -------------------------------------------------------------------------------- /proxywasm/internal/hostcall_utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | // Since the difference of the types in SliceHeader.{Len, Cap} between tinygo and go, 16 | // we have to have separated functions for converting bytes 17 | // https://github.com/tinygo-org/tinygo/issues/1284 18 | 19 | package internal 20 | 21 | import ( 22 | "reflect" 23 | "unsafe" 24 | ) 25 | 26 | func RawBytePtrToString(raw *byte, size int) string { 27 | //nolint 28 | return *(*string)(unsafe.Pointer(&reflect.SliceHeader{ 29 | Data: uintptr(unsafe.Pointer(raw)), 30 | Len: size, 31 | Cap: size, 32 | })) 33 | } 34 | 35 | func RawBytePtrToByteSlice(raw *byte, size int) []byte { 36 | //nolint 37 | return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ 38 | Data: uintptr(unsafe.Pointer(raw)), 39 | Len: size, 40 | Cap: size, 41 | })) 42 | } 43 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_timers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | 22 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 23 | ) 24 | 25 | type timerContext struct { 26 | types.DefaultPluginContext 27 | onTick bool 28 | } 29 | 30 | func (ctx *timerContext) OnTick() { 31 | ctx.onTick = true 32 | } 33 | 34 | func Test_onTick(t *testing.T) { 35 | var id uint32 = 100 36 | currentStateMux.Lock() 37 | defer currentStateMux.Unlock() 38 | 39 | currentState = &state{pluginContexts: map[uint32]*pluginContextState{id: {context: &timerContext{}}}} 40 | ctx, ok := currentState.pluginContexts[id].context.(*timerContext) 41 | require.True(t, ok) 42 | proxyOnTick(id) 43 | require.True(t, ctx.onTick) 44 | } 45 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_queue_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | 22 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 23 | ) 24 | 25 | type queueContext struct { 26 | types.DefaultPluginContext 27 | onQueueReady bool 28 | } 29 | 30 | func (ctx *queueContext) OnQueueReady(uint32) { 31 | ctx.onQueueReady = true 32 | } 33 | 34 | func Test_queueReady(t *testing.T) { 35 | var id uint32 = 100 36 | currentStateMux.Lock() 37 | defer currentStateMux.Unlock() 38 | 39 | currentState = &state{pluginContexts: map[uint32]*pluginContextState{id: {context: &queueContext{}}}} 40 | ctx, ok := currentState.pluginContexts[id].context.(*queueContext) 41 | require.True(t, ok) 42 | proxyOnQueueReady(id, 10) 43 | require.True(t, ctx.onQueueReady) 44 | } 45 | -------------------------------------------------------------------------------- /examples/http_headers/README.md: -------------------------------------------------------------------------------- 1 | ## http_headers 2 | 3 | This example handles http request/response headers events and log all headers. 4 | 5 | In envoy.yaml, the custom header is given as the plugin configuration like the following: 6 | 7 | ```yaml 8 | configuration: 9 | "@type": type.googleapis.com/google.protobuf.StringValue 10 | value: | 11 | { 12 | "header": "x-wasm-header", 13 | "value": "demo-wasm" 14 | } 15 | ``` 16 | 17 | and this adds the `x-wasm-header: demo-wasm` header to all the responses. 18 | Also, it adds a hardcoded header "x-proxy-wasm-go-sdk-example" with value "http_headers". 19 | 20 | ``` 21 | wasm log: request header --> :authority: localhost:18000 22 | wasm log: request header --> :path: /uuid 23 | wasm log: request header --> :method: GET 24 | wasm log: request header --> user-agent: curl/7.68.0 25 | wasm log: request header --> accept: */* 26 | wasm log: request header --> x-forwarded-proto: http 27 | wasm log: request header --> x-request-id: 5692b633-fd9c-4700-b4dd-7a58e2853eb4 28 | wasm log: response header <-- :status: 200 29 | wasm log: response header <-- content-length: 13 30 | wasm log: response header <-- content-type: text/plain 31 | wasm log: response header <-- date: Thu, 01 Oct 2020 09:10:09 GMT 32 | wasm log: response header <-- server: envoy 33 | wasm log: response header <-- x-envoy-upstream-service-time: 0 34 | wasm log: response header --> x-proxy-wasm-go-sdk-example: http_headers 35 | wasm log: response header --> x-wasm-header: demo-wasm 36 | wasm log: 2 finished 37 | ``` 38 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_configuration.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 21 | ) 22 | 23 | //export proxy_on_vm_start 24 | func proxyOnVMStart(_ uint32, vmConfigurationSize int) types.OnVMStartStatus { 25 | if recordTiming { 26 | defer logTiming("proxyOnVMStart", time.Now()) 27 | } 28 | return currentState.vmContext.OnVMStart(vmConfigurationSize) 29 | } 30 | 31 | //export proxy_on_configure 32 | func proxyOnConfigure(pluginContextID uint32, pluginConfigurationSize int) types.OnPluginStartStatus { 33 | if recordTiming { 34 | defer logTiming("proxyOnConfigure", time.Now()) 35 | } 36 | ctx, ok := currentState.pluginContexts[pluginContextID] 37 | if !ok { 38 | panic("invalid context on proxy_on_configure") 39 | } 40 | currentState.setActiveContextID(pluginContextID) 41 | return ctx.context.OnPluginStart(pluginConfigurationSize) 42 | } 43 | -------------------------------------------------------------------------------- /examples/foreign_call_on_tick/README.md: -------------------------------------------------------------------------------- 1 | ## call_foreign_function_on_tick 2 | 3 | this example periodically calls the foreign functions named "compress" 4 | 5 | ``` 6 | foreign function (compress) called: 1, result: 789ccb48cdc9c95728cf2fca495104001e89047e 7 | foreign function (compress) called: 1, result: 789ccb48cdc9c95728cf2fca495104001e89047e 8 | foreign function (compress) called: 2, result: 789ccb48cdc9c95728cf2fca495104001e89047e 9 | foreign function (compress) called: 2, result: 789ccb48cdc9c95728cf2fca495104001e89047e 10 | foreign function (compress) called: 3, result: 789ccb48cdc9c95728cf2fca495104001e89047e 11 | foreign function (compress) called: 3, result: 789ccb48cdc9c95728cf2fca495104001e89047e 12 | foreign function (compress) called: 4, result: 789ccb48cdc9c95728cf2fca495104001e89047e 13 | foreign function (compress) called: 4, result: 789ccb48cdc9c95728cf2fca495104001e89047e 14 | foreign function (compress) called: 5, result: 789ccb48cdc9c95728cf2fca495104001e89047e 15 | foreign function (compress) called: 5, result: 789ccb48cdc9c95728cf2fca495104001e89047e 16 | foreign function (compress) called: 6, result: 789ccb48cdc9c95728cf2fca495104001e89047e 17 | foreign function (compress) called: 6, result: 789ccb48cdc9c95728cf2fca495104001e89047e 18 | foreign function (compress) called: 7, result: 789ccb48cdc9c95728cf2fca495104001e89047e 19 | foreign function (compress) called: 7, result: 789ccb48cdc9c95728cf2fca495104001e89047e 20 | foreign function (compress) called: 8, result: 789ccb48cdc9c95728cf2fca495104001e89047e 21 | foreign function (compress) called: 8, result: 789ccb48cdc9c95728cf2fca495104001e89047e 22 | ``` 23 | -------------------------------------------------------------------------------- /examples/http_headers/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | github.com/tidwall/gjson v1.14.3 h1:9jvXn7olKEHU1S9vwoMGliaT8jq1vJ7IH/n9zD9Dnlw= 10 | github.com/tidwall/gjson v1.14.3/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 11 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 12 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 13 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 14 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 18 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 19 | -------------------------------------------------------------------------------- /examples/json_validation/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= 6 | github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 7 | github.com/tetratelabs/wazero v1.7.2 h1:1+z5nXJNwMLPAWaTePFi49SSTL0IMx/i3Fg8Yc25GDc= 8 | github.com/tetratelabs/wazero v1.7.2/go.mod h1:ytl6Zuh20R/eROuyDaGPkp82O9C/DJfXAwJfQ3X6/7Y= 9 | github.com/tidwall/gjson v1.14.1 h1:iymTbGkQBhveq21bEvAQ81I0LEBork8BFe1CUZXdyuo= 10 | github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= 11 | github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= 12 | github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= 13 | github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= 14 | github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= 15 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 16 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 17 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 18 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 19 | -------------------------------------------------------------------------------- /properties/types_test.go: -------------------------------------------------------------------------------- 1 | package properties 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestEnvoyTrafficDirectionString(t *testing.T) { 11 | tests := []struct { 12 | input EnvoyTrafficDirection 13 | expected string 14 | }{ 15 | {EnvoyTrafficDirectionUnspecified, "UNSPECIFIED"}, 16 | {EnvoyTrafficDirectionInbound, "INBOUND"}, 17 | {EnvoyTrafficDirectionOutbound, "OUTBOUND"}, 18 | {EnvoyTrafficDirection(9999), "UNSPECIFIED"}, 19 | } 20 | 21 | for _, test := range tests { 22 | result := test.input.String() 23 | require.Equal(t, test.expected, result) 24 | } 25 | } 26 | 27 | func TestIstioTrafficInterceptionModeString(t *testing.T) { 28 | tests := []struct { 29 | input IstioTrafficInterceptionMode 30 | expected string 31 | }{ 32 | {IstioTrafficInterceptionModeNone, "NONE"}, 33 | {IstioTrafficInterceptionModeTproxy, "TPROXY"}, 34 | {IstioTrafficInterceptionModeRedirect, "REDIRECT"}, 35 | {IstioTrafficInterceptionMode(9999), "REDIRECT"}, 36 | } 37 | 38 | for _, test := range tests { 39 | result := test.input.String() 40 | require.Equal(t, test.expected, result) 41 | } 42 | } 43 | 44 | func TestParseIstioTrafficInterceptionMode(t *testing.T) { 45 | tests := []struct { 46 | input string 47 | expected IstioTrafficInterceptionMode 48 | err error 49 | }{ 50 | {"NONE", IstioTrafficInterceptionModeNone, nil}, 51 | {"TPROXY", IstioTrafficInterceptionModeTproxy, nil}, 52 | {"REDIRECT", IstioTrafficInterceptionModeRedirect, nil}, 53 | {"INVALID", IstioTrafficInterceptionModeRedirect, fmt.Errorf("invalid IstioTrafficInterceptionMode: INVALID")}, 54 | } 55 | 56 | for _, test := range tests { 57 | result, err := ParseIstioTrafficInterceptionMode(test.input) 58 | require.Equal(t, test.expected, result) 59 | 60 | if test.err != nil { 61 | require.EqualError(t, err, test.err.Error()) 62 | } else { 63 | require.NoError(t, err) 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /examples/foreign_call_on_tick/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | 14 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 15 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 16 | ) 17 | 18 | func TestPluginContext_OnTick(t *testing.T) { 19 | vmTest(t, func(t *testing.T, vm types.VMContext) { 20 | opt := proxytest.NewEmulatorOption().WithVMContext(vm) 21 | host, reset := proxytest.NewHostEmulator(opt) 22 | defer reset() 23 | 24 | // Call OnVMStart. 25 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 26 | require.Equal(t, tickMilliseconds, host.GetTickPeriod()) 27 | 28 | // Register foreign function named "compress". 29 | host.RegisterForeignFunction("compress", func(b []byte) []byte { return b }) 30 | 31 | for i := 1; i < 10; i++ { 32 | host.Tick() 33 | // Check Envoy logs. 34 | logs := host.GetInfoLogs() 35 | require.Contains(t, logs, fmt.Sprintf("foreign function (compress) called: %d, result: %s", i, "68656c6c6f20776f726c6421")) 36 | } 37 | }) 38 | } 39 | 40 | // vmTest executes f twice, once with a types.VMContext that executes plugin code directly 41 | // in the host, and again by executing the plugin code within the compiled main.wasm binary. 42 | // Execution with main.wasm will be skipped if the file cannot be found. 43 | func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { 44 | t.Helper() 45 | 46 | t.Run("go", func(t *testing.T) { 47 | f(t, &vmContext{}) 48 | }) 49 | 50 | t.Run("wasm", func(t *testing.T) { 51 | wasm, err := os.ReadFile("main.wasm") 52 | if err != nil { 53 | t.Skip("wasm not found") 54 | } 55 | v, err := proxytest.NewWasmVMContext(wasm) 56 | require.NoError(t, err) 57 | defer v.Close() 58 | f(t, v) 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /examples/helloworld/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: main 4 | address: 5 | socket_address: 6 | address: 0.0.0.0 7 | port_value: 18000 8 | filter_chains: 9 | - filters: 10 | - name: envoy.http_connection_manager 11 | typed_config: 12 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 13 | stat_prefix: ingress_http 14 | codec_type: auto 15 | route_config: 16 | name: local_route 17 | virtual_hosts: 18 | - name: local_service 19 | domains: 20 | - "*" 21 | routes: 22 | - match: 23 | prefix: "/" 24 | direct_response: 25 | status: 200 26 | body: 27 | inline_string: "example body\n" 28 | http_filters: 29 | - name: envoy.filters.http.wasm 30 | typed_config: 31 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 32 | type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 33 | value: 34 | config: 35 | vm_config: 36 | runtime: "envoy.wasm.runtime.v8" 37 | code: 38 | local: 39 | filename: "./examples/helloworld/main.wasm" 40 | - name: envoy.filters.http.router 41 | typed_config: 42 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 43 | 44 | admin: 45 | access_log_path: "/dev/null" 46 | address: 47 | socket_address: 48 | address: 0.0.0.0 49 | port_value: 8001 50 | -------------------------------------------------------------------------------- /examples/postpone_requests/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 14 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 15 | ) 16 | 17 | func TestSetEffectiveContext(t *testing.T) { 18 | vmTest(t, func(t *testing.T, vm types.VMContext) { 19 | opt := proxytest.NewEmulatorOption().WithVMContext(vm) 20 | host, reset := proxytest.NewHostEmulator(opt) 21 | defer reset() 22 | 23 | // Call OnVMStart. 24 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 25 | require.Equal(t, tickMilliseconds, host.GetTickPeriod()) 26 | 27 | // Initialize context. 28 | contextID := host.InitializeHttpContext() 29 | 30 | // Call OnHttpRequestHeaders. 31 | action := host.CallOnRequestHeaders(contextID, [][2]string{}, false) 32 | require.Equal(t, types.ActionPause, action) 33 | 34 | // Call OnTick. 35 | host.Tick() 36 | 37 | action = host.GetCurrentHttpStreamAction(contextID) 38 | require.Equal(t, types.ActionContinue, action) 39 | }) 40 | } 41 | 42 | // vmTest executes f twice, once with a types.VMContext that executes plugin code directly 43 | // in the host, and again by executing the plugin code within the compiled main.wasm binary. 44 | // Execution with main.wasm will be skipped if the file cannot be found. 45 | func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { 46 | t.Helper() 47 | 48 | t.Run("go", func(t *testing.T) { 49 | f(t, &vmContext{}) 50 | }) 51 | 52 | t.Run("wasm", func(t *testing.T) { 53 | wasm, err := os.ReadFile("main.wasm") 54 | if err != nil { 55 | t.Skip("wasm not found") 56 | } 57 | v, err := proxytest.NewWasmVMContext(wasm) 58 | require.NoError(t, err) 59 | defer v.Close() 60 | f(t, v) 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /examples/shared_data/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: main 4 | address: 5 | socket_address: 6 | address: 0.0.0.0 7 | port_value: 18000 8 | filter_chains: 9 | - filters: 10 | - name: envoy.http_connection_manager 11 | typed_config: 12 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 13 | stat_prefix: ingress_http 14 | codec_type: auto 15 | route_config: 16 | name: local_route 17 | virtual_hosts: 18 | - name: local_service 19 | domains: 20 | - "*" 21 | routes: 22 | - match: 23 | prefix: "/" 24 | direct_response: 25 | status: 200 26 | body: 27 | inline_string: "example body\n" 28 | http_filters: 29 | - name: envoy.filters.http.wasm 30 | typed_config: 31 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 32 | type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 33 | value: 34 | config: 35 | vm_config: 36 | runtime: "envoy.wasm.runtime.v8" 37 | code: 38 | local: 39 | filename: "./examples/shared_data/main.wasm" 40 | - name: envoy.filters.http.router 41 | typed_config: 42 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 43 | 44 | admin: 45 | access_log_path: "/dev/null" 46 | address: 47 | socket_address: 48 | address: 0.0.0.0 49 | port_value: 8001 50 | -------------------------------------------------------------------------------- /examples/postpone_requests/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: main 4 | address: 5 | socket_address: 6 | address: 0.0.0.0 7 | port_value: 18000 8 | filter_chains: 9 | - filters: 10 | - name: envoy.http_connection_manager 11 | typed_config: 12 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 13 | stat_prefix: ingress_http 14 | codec_type: auto 15 | route_config: 16 | name: local_route 17 | virtual_hosts: 18 | - name: local_service 19 | domains: 20 | - "*" 21 | routes: 22 | - match: 23 | prefix: "/" 24 | direct_response: 25 | status: 200 26 | body: 27 | inline_string: "example body\n" 28 | http_filters: 29 | - name: envoy.filters.http.wasm 30 | typed_config: 31 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 32 | type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 33 | value: 34 | config: 35 | vm_config: 36 | runtime: "envoy.wasm.runtime.v8" 37 | code: 38 | local: 39 | filename: "./examples/postpone_requests/main.wasm" 40 | - name: envoy.filters.http.router 41 | typed_config: 42 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 43 | 44 | admin: 45 | access_log_path: "/dev/null" 46 | address: 47 | socket_address: 48 | address: 0.0.0.0 49 | port_value: 8001 50 | -------------------------------------------------------------------------------- /examples/foreign_call_on_tick/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: main 4 | address: 5 | socket_address: 6 | address: 0.0.0.0 7 | port_value: 18000 8 | filter_chains: 9 | - filters: 10 | - name: envoy.http_connection_manager 11 | typed_config: 12 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 13 | stat_prefix: ingress_http 14 | codec_type: auto 15 | route_config: 16 | name: local_route 17 | virtual_hosts: 18 | - name: local_service 19 | domains: 20 | - "*" 21 | routes: 22 | - match: 23 | prefix: "/" 24 | direct_response: 25 | status: 200 26 | body: 27 | inline_string: "example body\n" 28 | http_filters: 29 | - name: envoy.filters.http.wasm 30 | typed_config: 31 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 32 | type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 33 | value: 34 | config: 35 | vm_config: 36 | runtime: "envoy.wasm.runtime.v8" 37 | code: 38 | local: 39 | filename: "./examples/foreign_call_on_tick/main.wasm" 40 | - name: envoy.filters.http.router 41 | typed_config: 42 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 43 | 44 | 45 | admin: 46 | access_log_path: "/dev/null" 47 | address: 48 | socket_address: 49 | address: 0.0.0.0 50 | port_value: 8001 51 | -------------------------------------------------------------------------------- /properties/xsd.go: -------------------------------------------------------------------------------- 1 | package properties 2 | 3 | // This file hosts helper functions to retrieve xsd-configuration-related properties as described in: 4 | // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes#configuration-attributes 5 | 6 | var ( 7 | xdsClusterName = []string{"xds", "cluster_name"} 8 | xdsClusterMetadata = []string{"xds", "cluster_metadata", "filter_metadata", "istio"} 9 | xdsRouteName = []string{"xds", "route_name"} 10 | xdsRouteMetadata = []string{"xds", "route_metadata", "filter_metadata", "istio"} 11 | xdsUpstreamHostMetadata = []string{"xds", "upstream_host_metadata", "filter_metadata", "istio"} 12 | xdsListenerFilterChainName = []string{"xds", "filter_chain_name"} 13 | ) 14 | 15 | // GetXdsClusterName returns the upstream cluster name. 16 | // 17 | // Example value: "outbound|80||httpbin.org". 18 | func GetXdsClusterName() (string, error) { 19 | return getPropertyString(xdsClusterName) 20 | } 21 | 22 | // GetXdsClusterMetadata returns the upstream cluster metadata. 23 | func GetXdsClusterMetadata() (IstioFilterMetadata, error) { 24 | return getIstioFilterMetadata(xdsClusterMetadata) 25 | } 26 | 27 | // GetXdsRouteName returns the upstream route name (available in both 28 | // the request response path, cfr getRouteName()). This matches the 29 | // in an istio VirtualService CR. 30 | func GetXdsRouteName() (string, error) { 31 | return getPropertyString(xdsRouteName) 32 | } 33 | 34 | // GetXdsRouteMetadata returns the upstream route metadata. 35 | func GetXdsRouteMetadata() (IstioFilterMetadata, error) { 36 | return getIstioFilterMetadata(xdsRouteMetadata) 37 | } 38 | 39 | // GetXdsUpstreamHostMetadata returns the upstream host metadata. 40 | func GetXdsUpstreamHostMetadata() (IstioFilterMetadata, error) { 41 | return getIstioFilterMetadata(xdsUpstreamHostMetadata) 42 | } 43 | 44 | // GetXdsListenerFilterChainName returns the listener filter chain name. 45 | func GetXdsListenerFilterChainName() (string, error) { 46 | return getPropertyString(xdsListenerFilterChainName) 47 | } 48 | -------------------------------------------------------------------------------- /examples/metrics/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 14 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 15 | ) 16 | 17 | func TestMetric(t *testing.T) { 18 | vmTest(t, func(t *testing.T, vm types.VMContext) { 19 | opt := proxytest.NewEmulatorOption().WithVMContext(vm) 20 | host, reset := proxytest.NewHostEmulator(opt) 21 | defer reset() 22 | 23 | // Call OnVMStart. 24 | require.Equal(t, types.OnVMStartStatusOK, host.StartVM()) 25 | 26 | // Initialize http context. 27 | headers := [][2]string{{"my-custom-header", "foo"}} 28 | contextID := host.InitializeHttpContext() 29 | exp := uint64(3) 30 | for i := uint64(0); i < exp; i++ { 31 | // Call OnRequestHeaders 32 | action := host.CallOnRequestHeaders(contextID, headers, false) 33 | require.Equal(t, types.ActionContinue, action) 34 | } 35 | 36 | // Check metrics. 37 | value, err := host.GetCounterMetric("custom_header_value_counts_value=foo_reporter=wasmgosdk") 38 | require.NoError(t, err) 39 | require.Equal(t, uint64(3), value) 40 | }) 41 | } 42 | 43 | // vmTest executes f twice, once with a types.VMContext that executes plugin code directly 44 | // in the host, and again by executing the plugin code within the compiled main.wasm binary. 45 | // Execution with main.wasm will be skipped if the file cannot be found. 46 | func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { 47 | t.Helper() 48 | 49 | t.Run("go", func(t *testing.T) { 50 | f(t, &vmContext{}) 51 | }) 52 | 53 | t.Run("wasm", func(t *testing.T) { 54 | wasm, err := os.ReadFile("main.wasm") 55 | if err != nil { 56 | t.Skip("wasm not found") 57 | } 58 | v, err := proxytest.NewWasmVMContext(wasm) 59 | require.NoError(t, err) 60 | defer v.Close() 61 | f(t, v) 62 | }) 63 | } 64 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | goimports := golang.org/x/tools/cmd/goimports@v0.21.0 2 | golangci_lint := github.com/golangci/golangci-lint/cmd/golangci-lint@v1.59.0 3 | 4 | 5 | .PHONY: build.example 6 | build.example: 7 | @find ./examples -type f -name "main.go" | grep ${name}\ 8 | | xargs -I {} bash -c 'dirname {}' \ 9 | | xargs -I {} bash -c 'cd {} && tinygo build -o main.wasm -scheduler=none -target=wasi ./main.go' 10 | 11 | 12 | .PHONY: build.examples 13 | build.examples: 14 | @find ./examples -mindepth 1 -type f -name "main.go" \ 15 | | xargs -I {} bash -c 'dirname {}' \ 16 | | xargs -I {} bash -c 'cd {} && tinygo build -o main.wasm -scheduler=none -target=wasi ./main.go' 17 | 18 | .PHONY: test 19 | test: 20 | @go test $(shell go list ./... | grep -v e2e) 21 | @go test -tags "proxywasm_timing" ./proxywasm/proxytest 22 | 23 | .PHONY: test.examples 24 | test.examples: 25 | @find ./examples -mindepth 1 -type f -name "main.go" \ 26 | | xargs -I {} bash -c 'dirname {}' \ 27 | | xargs -I {} bash -c 'cd {} && go test ./...' 28 | 29 | .PHONY: run 30 | run: 31 | @envoy -c ./examples/${name}/envoy.yaml --concurrency 2 --log-format '%v' 32 | 33 | .PHONY: lint 34 | lint: 35 | @find . -name "go.mod" \ 36 | | grep go.mod \ 37 | | xargs -I {} bash -c 'dirname {}' \ 38 | | xargs -I {} bash -c 'echo "=> {}"; cd {}; go run $(golangci_lint) run; ' 39 | 40 | .PHONY: format 41 | format: 42 | @find . -type f -name '*.go' | xargs gofmt -s -w 43 | @for f in `find . -name '*.go'`; do \ 44 | awk '/^import \($$/,/^\)$$/{if($$0=="")next}{print}' $$f > /tmp/fmt; \ 45 | mv /tmp/fmt $$f; \ 46 | done 47 | @go run $(goimports) -w -local github.com/tetratelabs/proxy-wasm-go-sdk `find . -name '*.go'` 48 | 49 | .PHONY: check 50 | check: 51 | @$(MAKE) format 52 | @go mod tidy 53 | @if [ ! -z "`git status -s`" ]; then \ 54 | echo "The following differences will fail CI until committed:"; \ 55 | git diff --exit-code; \ 56 | fi 57 | 58 | .PHONY: tidy 59 | tidy: ## Runs go mod tidy on every module 60 | @find . -name "go.mod" \ 61 | | grep go.mod \ 62 | | xargs -I {} bash -c 'dirname {}' \ 63 | | xargs -I {} bash -c 'echo "=> {}"; cd {}; go mod tidy -v; ' 64 | -------------------------------------------------------------------------------- /examples/vm_plugin_configuration/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 19 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 20 | ) 21 | 22 | func main() { 23 | proxywasm.SetVMContext(&vmContext{}) 24 | } 25 | 26 | // vmContext implements types.VMContext. 27 | type vmContext struct{} 28 | 29 | // OnVMStart implements types.VMContext. 30 | func (*vmContext) OnVMStart(vmConfigurationSize int) types.OnVMStartStatus { 31 | data, err := proxywasm.GetVMConfiguration() 32 | if err != nil { 33 | proxywasm.LogCriticalf("error reading vm configuration: %v", err) 34 | } 35 | 36 | proxywasm.LogInfof("vm config: %s", string(data)) 37 | return types.OnVMStartStatusOK 38 | } 39 | 40 | // NewPluginContext implements types.VMContext. 41 | func (*vmContext) NewPluginContext(uint32) types.PluginContext { 42 | return &pluginContext{} 43 | } 44 | 45 | // pluginContext implements types.PluginContext. 46 | type pluginContext struct { 47 | // Embed the default plugin context here, 48 | // so that we don't need to reimplement all the methods. 49 | types.DefaultPluginContext 50 | } 51 | 52 | // OnPluginStart implements types.PluginContext. 53 | func (ctx pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { 54 | data, err := proxywasm.GetPluginConfiguration() 55 | if err != nil { 56 | proxywasm.LogCriticalf("error reading plugin configuration: %v", err) 57 | } 58 | 59 | proxywasm.LogInfof("plugin config: %s", string(data)) 60 | return types.OnPluginStartStatusOK 61 | } 62 | -------------------------------------------------------------------------------- /proxywasm/proxytest/option.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package proxytest 16 | 17 | import ( 18 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/internal" 19 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 20 | ) 21 | 22 | // EmulatorOption is an option that can be passed to NewHostEmulator. 23 | type EmulatorOption struct { 24 | pluginConfiguration []byte 25 | vmConfiguration []byte 26 | vmContext types.VMContext 27 | properties map[string][]byte 28 | } 29 | 30 | // NewEmulatorOption creates a new EmulatorOption. 31 | func NewEmulatorOption() *EmulatorOption { 32 | return &EmulatorOption{vmContext: &types.DefaultVMContext{}} 33 | } 34 | 35 | // WithVMContext sets the VMContext. 36 | func (o *EmulatorOption) WithVMContext(context types.VMContext) *EmulatorOption { 37 | o.vmContext = context 38 | return o 39 | } 40 | 41 | // WithPluginConfiguration sets the plugin configuration. 42 | func (o *EmulatorOption) WithPluginConfiguration(data []byte) *EmulatorOption { 43 | o.pluginConfiguration = data 44 | return o 45 | } 46 | 47 | // WithVMConfiguration sets the VM configuration. 48 | func (o *EmulatorOption) WithVMConfiguration(data []byte) *EmulatorOption { 49 | o.vmConfiguration = data 50 | return o 51 | } 52 | 53 | // WithProperty sets a property. If the property already exists, it will be overwritten. 54 | func (o *EmulatorOption) WithProperty(path []string, value []byte) *EmulatorOption { 55 | if o.properties == nil { 56 | o.properties = map[string][]byte{} 57 | } 58 | o.properties[string(internal.SerializePropertyPath(path))] = value 59 | return o 60 | } 61 | -------------------------------------------------------------------------------- /examples/shared_data/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 14 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 15 | ) 16 | 17 | func TestData(t *testing.T) { 18 | vmTest(t, func(t *testing.T, vm types.VMContext) { 19 | opt := proxytest.NewEmulatorOption().WithVMContext(vm) 20 | host, reset := proxytest.NewHostEmulator(opt) 21 | defer reset() 22 | 23 | // Call OnVMStart -> set initial value. 24 | require.Equal(t, types.OnVMStartStatusOK, host.StartVM()) 25 | // Initialize http context. 26 | contextID := host.InitializeHttpContext() 27 | // Call OnHttpRequestHeaders. 28 | action := host.CallOnRequestHeaders(contextID, nil, false) 29 | require.Equal(t, types.ActionContinue, action) 30 | 31 | // Check Envoy logs. 32 | logs := host.GetInfoLogs() 33 | require.Contains(t, logs, "shared value: 1") 34 | 35 | // Call OnHttpRequestHeaders again. 36 | action = host.CallOnRequestHeaders(contextID, nil, false) 37 | require.Equal(t, types.ActionContinue, action) 38 | action = host.CallOnRequestHeaders(contextID, nil, false) 39 | require.Equal(t, types.ActionContinue, action) 40 | 41 | // Check Envoy logs. 42 | logs = host.GetInfoLogs() 43 | require.Contains(t, logs, "shared value: 3") 44 | }) 45 | } 46 | 47 | // vmTest executes f twice, once with a types.VMContext that executes plugin code directly 48 | // in the host, and again by executing the plugin code within the compiled main.wasm binary. 49 | // Execution with main.wasm will be skipped if the file cannot be found. 50 | func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { 51 | t.Helper() 52 | 53 | t.Run("go", func(t *testing.T) { 54 | f(t, &vmContext{}) 55 | }) 56 | 57 | t.Run("wasm", func(t *testing.T) { 58 | wasm, err := os.ReadFile("main.wasm") 59 | if err != nil { 60 | t.Skip("wasm not found") 61 | } 62 | v, err := proxytest.NewWasmVMContext(wasm) 63 | require.NoError(t, err) 64 | defer v.Close() 65 | f(t, v) 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /examples/helloworld/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 14 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 15 | ) 16 | 17 | func TestHelloWorld_OnTick(t *testing.T) { 18 | vmTest(t, func(t *testing.T, vm types.VMContext) { 19 | opt := proxytest.NewEmulatorOption().WithVMContext(vm) 20 | host, reset := proxytest.NewHostEmulator(opt) 21 | defer reset() 22 | 23 | // Call OnPluginStart. 24 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 25 | require.Equal(t, tickMilliseconds, host.GetTickPeriod()) 26 | 27 | // Call OnTick. 28 | host.Tick() 29 | 30 | // Check Envoy logs. 31 | logs := host.GetInfoLogs() 32 | require.Contains(t, logs, "OnTick called") 33 | }) 34 | } 35 | 36 | func TestHelloWorld_OnPluginStart(t *testing.T) { 37 | vmTest(t, func(t *testing.T, vm types.VMContext) { 38 | opt := proxytest.NewEmulatorOption().WithVMContext(vm) 39 | host, reset := proxytest.NewHostEmulator(opt) 40 | defer reset() 41 | 42 | // Call OnPluginStart. 43 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 44 | 45 | // Check Envoy logs. 46 | logs := host.GetInfoLogs() 47 | require.Contains(t, logs, "OnPluginStart from Go!") 48 | require.Equal(t, tickMilliseconds, host.GetTickPeriod()) 49 | }) 50 | } 51 | 52 | // vmTest executes f twice, once with a types.VMContext that executes plugin code directly 53 | // in the host, and again by executing the plugin code within the compiled main.wasm binary. 54 | // Execution with main.wasm will be skipped if the file cannot be found. 55 | func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { 56 | t.Helper() 57 | 58 | t.Run("go", func(t *testing.T) { 59 | f(t, &vmContext{}) 60 | }) 61 | 62 | t.Run("wasm", func(t *testing.T) { 63 | wasm, err := os.ReadFile("main.wasm") 64 | if err != nil { 65 | t.Skip("wasm not found") 66 | } 67 | v, err := proxytest.NewWasmVMContext(wasm) 68 | require.NoError(t, err) 69 | defer v.Close() 70 | f(t, v) 71 | }) 72 | } 73 | -------------------------------------------------------------------------------- /examples/http_auth_random/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: main 4 | address: 5 | socket_address: 6 | address: 0.0.0.0 7 | port_value: 18000 8 | filter_chains: 9 | - filters: 10 | - name: envoy.http_connection_manager 11 | typed_config: 12 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 13 | stat_prefix: ingress_http 14 | codec_type: auto 15 | route_config: 16 | name: httpbin 17 | virtual_hosts: 18 | - name: httpbin 19 | domains: 20 | - "*" 21 | routes: 22 | - match: 23 | prefix: "/" 24 | route: 25 | cluster: httpbin 26 | http_filters: 27 | - name: envoy.filters.http.wasm 28 | typed_config: 29 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 30 | type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 31 | value: 32 | config: 33 | vm_config: 34 | runtime: "envoy.wasm.runtime.v8" 35 | code: 36 | local: 37 | filename: "./examples/http_auth_random/main.wasm" 38 | - name: envoy.filters.http.router 39 | typed_config: 40 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 41 | 42 | clusters: 43 | - name: httpbin 44 | connect_timeout: 5000s 45 | type: strict_dns 46 | lb_policy: round_robin 47 | load_assignment: 48 | cluster_name: httpbin 49 | endpoints: 50 | - lb_endpoints: 51 | - endpoint: 52 | address: 53 | socket_address: 54 | address: httpbin.org 55 | port_value: 80 56 | 57 | admin: 58 | access_log_path: "/dev/null" 59 | address: 60 | socket_address: 61 | address: 0.0.0.0 62 | port_value: 8001 63 | -------------------------------------------------------------------------------- /examples/multiple_dispatches/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: main 4 | address: 5 | socket_address: 6 | address: 0.0.0.0 7 | port_value: 18000 8 | filter_chains: 9 | - filters: 10 | - name: envoy.http_connection_manager 11 | typed_config: 12 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 13 | stat_prefix: ingress_http 14 | codec_type: auto 15 | route_config: 16 | name: httpbin 17 | virtual_hosts: 18 | - name: httpbin 19 | domains: 20 | - "*" 21 | routes: 22 | - match: 23 | prefix: "/" 24 | route: 25 | cluster: httpbin 26 | http_filters: 27 | - name: envoy.filters.http.wasm 28 | typed_config: 29 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 30 | type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 31 | value: 32 | config: 33 | vm_config: 34 | runtime: "envoy.wasm.runtime.v8" 35 | code: 36 | local: 37 | filename: "./examples/multiple_dispatches/main.wasm" 38 | - name: envoy.filters.http.router 39 | typed_config: 40 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 41 | 42 | clusters: 43 | - name: httpbin 44 | connect_timeout: 5000s 45 | type: strict_dns 46 | lb_policy: round_robin 47 | load_assignment: 48 | cluster_name: httpbin 49 | endpoints: 50 | - lb_endpoints: 51 | - endpoint: 52 | address: 53 | socket_address: 54 | address: httpbin.org 55 | port_value: 80 56 | 57 | admin: 58 | access_log_path: "/dev/null" 59 | address: 60 | socket_address: 61 | address: 0.0.0.0 62 | port_value: 8001 63 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_l4_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | 22 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 23 | ) 24 | 25 | type l4Context struct { 26 | types.DefaultTcpContext 27 | onDownstreamData, 28 | onDownstreamClose, 29 | onNewConnection, 30 | onUpstreamData, 31 | onUpstreamStreamClose bool 32 | } 33 | 34 | func (ctx *l4Context) OnDownstreamData(int, bool) types.Action { 35 | ctx.onDownstreamData = true 36 | return types.ActionContinue 37 | } 38 | 39 | func (ctx *l4Context) OnDownstreamClose(types.PeerType) { ctx.onDownstreamClose = true } 40 | 41 | func (ctx *l4Context) OnNewConnection() types.Action { 42 | ctx.onNewConnection = true 43 | return types.ActionContinue 44 | } 45 | 46 | func (ctx *l4Context) OnUpstreamData(int, bool) types.Action { 47 | ctx.onUpstreamData = true 48 | return types.ActionContinue 49 | } 50 | 51 | func (ctx *l4Context) OnUpstreamClose(types.PeerType) { 52 | ctx.onUpstreamStreamClose = true 53 | } 54 | 55 | func Test_l4(t *testing.T) { 56 | var cID uint32 = 100 57 | currentStateMux.Lock() 58 | defer currentStateMux.Unlock() 59 | 60 | currentState = &state{tcpContexts: map[uint32]types.TcpContext{cID: &l4Context{}}} 61 | ctx, ok := currentState.tcpContexts[cID].(*l4Context) 62 | require.True(t, ok) 63 | 64 | proxyOnNewConnection(cID) 65 | require.True(t, ctx.onNewConnection) 66 | proxyOnDownstreamData(cID, 0, false) 67 | require.True(t, ctx.onDownstreamData) 68 | proxyOnDownstreamConnectionClose(cID, 0) 69 | require.True(t, ctx.onDownstreamClose) 70 | proxyOnUpstreamData(cID, 0, false) 71 | require.True(t, ctx.onUpstreamData) 72 | proxyOnUpstreamConnectionClose(cID, 0) 73 | require.True(t, ctx.onUpstreamStreamClose) 74 | } 75 | -------------------------------------------------------------------------------- /examples/helloworld/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "math/rand" 19 | "time" 20 | 21 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 22 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 23 | ) 24 | 25 | const tickMilliseconds uint32 = 1000 26 | 27 | func main() { 28 | proxywasm.SetVMContext(&vmContext{}) 29 | } 30 | 31 | // vmContext implements types.VMContext. 32 | type vmContext struct { 33 | // Embed the default VM context here, 34 | // so that we don't need to reimplement all the methods. 35 | types.DefaultVMContext 36 | } 37 | 38 | // NewPluginContext implements types.VMContext. 39 | func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { 40 | return &helloWorld{} 41 | } 42 | 43 | // helloWorld implements types.PluginContext. 44 | type helloWorld struct { 45 | // Embed the default plugin context here, 46 | // so that we don't need to reimplement all the methods. 47 | types.DefaultPluginContext 48 | } 49 | 50 | // OnPluginStart implements types.PluginContext. 51 | func (ctx *helloWorld) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { 52 | rand.Seed(time.Now().UnixNano()) 53 | 54 | proxywasm.LogInfo("OnPluginStart from Go!") 55 | if err := proxywasm.SetTickPeriodMilliSeconds(tickMilliseconds); err != nil { 56 | proxywasm.LogCriticalf("failed to set tick period: %v", err) 57 | } 58 | 59 | return types.OnPluginStartStatusOK 60 | } 61 | 62 | // OnTick implements types.PluginContext. 63 | func (ctx *helloWorld) OnTick() { 64 | t := time.Now().UnixNano() 65 | proxywasm.LogInfof("It's %d: random value: %d", t, rand.Uint64()) 66 | proxywasm.LogInfof("OnTick called") 67 | } 68 | 69 | // NewHttpContext implements types.PluginContext. 70 | func (*helloWorld) NewHttpContext(uint32) types.HttpContext { return &types.DefaultHttpContext{} } 71 | -------------------------------------------------------------------------------- /examples/vm_plugin_configuration/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 14 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 15 | ) 16 | 17 | func TestContext_OnPluginStart(t *testing.T) { 18 | vmTest(t, func(t *testing.T, vm types.VMContext) { 19 | // Setup configurations. 20 | pluginConfigData := `tinygo plugin configuration` 21 | opt := proxytest.NewEmulatorOption(). 22 | WithPluginConfiguration([]byte(pluginConfigData)). 23 | WithVMContext(vm) 24 | host, reset := proxytest.NewHostEmulator(opt) 25 | defer reset() 26 | 27 | // Call OnPluginStart. 28 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 29 | 30 | // Check Envoy logs. 31 | logs := host.GetInfoLogs() 32 | require.Contains(t, logs, "plugin config: "+pluginConfigData) 33 | }) 34 | } 35 | 36 | func TestContext_OnVMStart(t *testing.T) { 37 | vmTest(t, func(t *testing.T, vm types.VMContext) { 38 | // Setup configurations. 39 | vmConfigData := `tinygo vm configuration` 40 | opt := proxytest.NewEmulatorOption(). 41 | WithVMConfiguration([]byte(vmConfigData)). 42 | WithVMContext(vm) 43 | host, reset := proxytest.NewHostEmulator(opt) 44 | defer reset() 45 | 46 | // Call OnVMStart. 47 | require.Equal(t, types.OnVMStartStatusOK, host.StartVM()) 48 | 49 | // Check Envoy logs. 50 | logs := host.GetInfoLogs() 51 | require.Contains(t, logs, "vm config: "+vmConfigData) 52 | }) 53 | } 54 | 55 | // vmTest executes f twice, once with a types.VMContext that executes plugin code directly 56 | // in the host, and again by executing the plugin code within the compiled main.wasm binary. 57 | // Execution with main.wasm will be skipped if the file cannot be found. 58 | func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { 59 | t.Helper() 60 | 61 | t.Run("go", func(t *testing.T) { 62 | f(t, &vmContext{}) 63 | }) 64 | 65 | t.Run("wasm", func(t *testing.T) { 66 | wasm, err := os.ReadFile("main.wasm") 67 | if err != nil { 68 | t.Skip("wasm not found") 69 | } 70 | v, err := proxytest.NewWasmVMContext(wasm) 71 | require.NoError(t, err) 72 | defer v.Close() 73 | f(t, v) 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /examples/metrics/envoy.yaml: -------------------------------------------------------------------------------- 1 | stats_config: 2 | stats_tags: 3 | # Envoy extracs the first matching group as a value. 4 | # This case, the part ([a-zA-Z]+) is extracted as a value for "value" tag. 5 | # See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/metrics/v3/stats.proto#config-metrics-v3-statsconfig. 6 | - tag_name: value 7 | regex: '(_value=([a-zA-Z]+))' 8 | - tag_name: reporter 9 | regex: '(_reporter=([a-zA-Z]+))' 10 | 11 | static_resources: 12 | listeners: 13 | - name: main 14 | address: 15 | socket_address: 16 | address: 0.0.0.0 17 | port_value: 18000 18 | filter_chains: 19 | - filters: 20 | - name: envoy.http_connection_manager 21 | typed_config: 22 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 23 | stat_prefix: ingress_http 24 | codec_type: auto 25 | route_config: 26 | name: local_route 27 | virtual_hosts: 28 | - name: local_service 29 | domains: 30 | - "*" 31 | routes: 32 | - match: 33 | prefix: "/" 34 | direct_response: 35 | status: 200 36 | body: 37 | inline_string: "example body\n" 38 | http_filters: 39 | - name: envoy.filters.http.wasm 40 | typed_config: 41 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 42 | type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 43 | value: 44 | config: 45 | vm_config: 46 | runtime: "envoy.wasm.runtime.v8" 47 | code: 48 | local: 49 | filename: "./examples/metrics/main.wasm" 50 | - name: envoy.filters.http.router 51 | typed_config: 52 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 53 | 54 | admin: 55 | access_log_path: "/dev/null" 56 | address: 57 | socket_address: 58 | address: 0.0.0.0 59 | port_value: 8001 60 | -------------------------------------------------------------------------------- /examples/foreign_call_on_tick/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/hex" 19 | 20 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 21 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 22 | ) 23 | 24 | const tickMilliseconds uint32 = 1 25 | 26 | func main() { 27 | proxywasm.SetVMContext(&vmContext{}) 28 | } 29 | 30 | // vmContext implements types.VMContext. 31 | type vmContext struct { 32 | // Embed the default VM context here, 33 | // so that we don't need to reimplement all the methods. 34 | types.DefaultVMContext 35 | } 36 | 37 | // NewPluginContext implements types.VMContext. 38 | func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { 39 | return &pluginContext{} 40 | } 41 | 42 | // pluginContext implements types.PluginContext. 43 | type pluginContext struct { 44 | // Embed the default plugin context here, 45 | // so that we don't need to reimplement all the methods. 46 | types.DefaultPluginContext 47 | callNum uint32 48 | } 49 | 50 | // OnPluginStart implements types.PluginContext. 51 | func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { 52 | if err := proxywasm.SetTickPeriodMilliSeconds(tickMilliseconds); err != nil { 53 | proxywasm.LogCriticalf("failed to set tick period: %v", err) 54 | return types.OnPluginStartStatusFailed 55 | } 56 | proxywasm.LogInfof("set tick period milliseconds: %d", tickMilliseconds) 57 | return types.OnPluginStartStatusOK 58 | } 59 | 60 | // OnTick implements types.PluginContext. 61 | func (ctx *pluginContext) OnTick() { 62 | ctx.callNum++ 63 | ret, err := proxywasm.CallForeignFunction("compress", []byte("hello world!")) 64 | if err != nil { 65 | proxywasm.LogCriticalf("foreign function (compress) failed: %v", err) 66 | } 67 | proxywasm.LogInfof("foreign function (compress) called: %d, result: %s", ctx.callNum, hex.EncodeToString(ret)) 68 | } 69 | -------------------------------------------------------------------------------- /examples/vm_plugin_configuration/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: main 4 | address: 5 | socket_address: 6 | address: 0.0.0.0 7 | port_value: 18000 8 | filter_chains: 9 | - filters: 10 | - name: envoy.http_connection_manager 11 | typed_config: 12 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 13 | stat_prefix: ingress_http 14 | codec_type: auto 15 | route_config: 16 | name: local_route 17 | virtual_hosts: 18 | - name: local_service 19 | domains: 20 | - "*" 21 | routes: 22 | - match: 23 | prefix: "/" 24 | direct_response: 25 | status: 200 26 | body: 27 | inline_string: "example body\n" 28 | http_filters: 29 | - name: envoy.filters.http.wasm 30 | typed_config: 31 | "@type": type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 32 | config: 33 | configuration: 34 | "@type": type.googleapis.com/google.protobuf.StringValue 35 | value: | 36 | { 37 | "name": "plugin configuration" 38 | } 39 | vm_config: 40 | runtime: envoy.wasm.runtime.v8 41 | configuration: 42 | "@type": type.googleapis.com/google.protobuf.StringValue 43 | value: | 44 | { 45 | "name": "vm configuration" 46 | } 47 | code: 48 | local: 49 | filename: "./examples/vm_plugin_configuration/main.wasm" 50 | - name: envoy.filters.http.router 51 | typed_config: 52 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 53 | 54 | admin: 55 | access_log_path: "/dev/null" 56 | address: 57 | socket_address: 58 | address: 0.0.0.0 59 | port_value: 8001 60 | -------------------------------------------------------------------------------- /examples/http_body_chunk/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: main 4 | address: 5 | socket_address: 6 | address: 0.0.0.0 7 | port_value: 18000 8 | filter_chains: 9 | - filters: 10 | - name: envoy.http_connection_manager 11 | typed_config: 12 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 13 | stat_prefix: ingress_http 14 | codec_type: auto 15 | route_config: 16 | name: admin 17 | virtual_hosts: 18 | - name: admin 19 | domains: 20 | - "*" 21 | routes: 22 | - match: 23 | prefix: "/" 24 | route: 25 | cluster: admin 26 | http_filters: 27 | - name: envoy.filters.http.wasm 28 | typed_config: 29 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 30 | type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 31 | value: 32 | config: 33 | configuration: 34 | "@type": type.googleapis.com/google.protobuf.StringValue 35 | value: "body-set" 36 | vm_config: 37 | runtime: "envoy.wasm.runtime.v8" 38 | code: 39 | local: 40 | filename: "./examples/http_body_chunk/main.wasm" 41 | - name: envoy.filters.http.router 42 | typed_config: 43 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 44 | 45 | clusters: 46 | - name: admin 47 | connect_timeout: 5000s 48 | type: strict_dns 49 | lb_policy: round_robin 50 | load_assignment: 51 | cluster_name: admin 52 | endpoints: 53 | - lb_endpoints: 54 | - endpoint: 55 | address: 56 | socket_address: 57 | address: 0.0.0.0 58 | port_value: 8001 59 | 60 | 61 | admin: 62 | access_log_path: "/dev/null" 63 | address: 64 | socket_address: 65 | address: 0.0.0.0 66 | port_value: 8001 67 | -------------------------------------------------------------------------------- /properties/response.go: -------------------------------------------------------------------------------- 1 | package properties 2 | 3 | // This file hosts helper functions to retrieve response-related properties as described in: 4 | // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes#response-attributes 5 | 6 | var ( 7 | responseCode = []string{"response", "code"} 8 | responseCodeDetails = []string{"response", "code_details"} 9 | responseFlags = []string{"response", "flags"} 10 | responseGrpcStatusCode = []string{"response", "grpc_status"} 11 | responseHeaders = []string{"response", "headers"} 12 | responseTrailers = []string{"response", "trailers"} 13 | responseSize = []string{"response", "size"} 14 | responseTotalSize = []string{"response", "total_size"} 15 | ) 16 | 17 | // GetResponseCode returns the response HTTP status code. 18 | func GetResponseCode() (uint64, error) { 19 | return getPropertyUint64(responseCode) 20 | } 21 | 22 | // GetResponseCodeDetails returns the internal response code details (subject to change). 23 | func GetResponseCodeDetails() (string, error) { 24 | return getPropertyString(responseCodeDetails) 25 | } 26 | 27 | // GetResponseFlags returns additional details about the response beyond the standard 28 | // response code encoded as a bit-vector. 29 | // 30 | // https://www.envoyproxy.io/docs/envoy/latest/configuration/observability/access_log/usage#config-access-log-format-response-flags 31 | func GetResponseFlags() (uint64, error) { 32 | return getPropertyUint64(responseFlags) 33 | } 34 | 35 | // GetResponseGrpcStatusCode returns the response gRPC status code. 36 | func GetResponseGrpcStatusCode() (uint64, error) { 37 | return getPropertyUint64(responseGrpcStatusCode) 38 | } 39 | 40 | // GetResponseHeaders returns all response headers indexed by the lower-cased header name. 41 | func GetResponseHeaders() (map[string]string, error) { 42 | return getPropertyStringMap(responseHeaders) 43 | } 44 | 45 | // GetResponseTrailers returns all response trailers indexed by the lower-cased trailer name. 46 | func GetResponseTrailers() (map[string]string, error) { 47 | return getPropertyStringMap(responseTrailers) 48 | } 49 | 50 | // GetResponseSize returns the size of the response body. 51 | func GetResponseSize() (uint64, error) { 52 | return getPropertyUint64(responseSize) 53 | } 54 | 55 | // GetResponseTotalSize returns the total size of the response including the approximate 56 | // uncompressed size of the headers and the trailers. 57 | func GetResponseTotalSize() (uint64, error) { 58 | return getPropertyUint64(responseTotalSize) 59 | } 60 | -------------------------------------------------------------------------------- /examples/dispatch_call_on_tick/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | 14 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 15 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 16 | ) 17 | 18 | func TestPluginContext_OnTick(t *testing.T) { 19 | vmTest(t, func(t *testing.T, vm types.VMContext) { 20 | opt := proxytest.NewEmulatorOption().WithVMContext(vm) 21 | host, reset := proxytest.NewHostEmulator(opt) 22 | defer reset() 23 | 24 | // Call OnVMStart. 25 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 26 | require.Equal(t, tickMilliseconds, host.GetTickPeriod()) 27 | 28 | for i := 1; i < 10; i++ { 29 | host.Tick() // call OnTick 30 | attrs := host.GetCalloutAttributesFromContext(proxytest.PluginContextID) 31 | // Verify DispatchHttpCall is called 32 | require.Equal(t, len(attrs), i) 33 | // Receive callout response. 34 | host.CallOnHttpCallResponse(attrs[0].CalloutID, nil, nil, nil) 35 | // Check Envoy logs. 36 | logs := host.GetInfoLogs() 37 | require.Contains(t, logs, fmt.Sprintf("called %d for contextID=%d", i, proxytest.PluginContextID)) 38 | } 39 | }) 40 | } 41 | 42 | func TestPluginContext_OnVMStart(t *testing.T) { 43 | vmTest(t, func(t *testing.T, vm types.VMContext) { 44 | opt := proxytest.NewEmulatorOption().WithVMContext(vm) 45 | host, reset := proxytest.NewHostEmulator(opt) 46 | defer reset() 47 | 48 | // Call OnVMStart. 49 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 50 | require.Equal(t, tickMilliseconds, host.GetTickPeriod()) 51 | }) 52 | } 53 | 54 | // vmTest executes f twice, once with a types.VMContext that executes plugin code directly 55 | // in the host, and again by executing the plugin code within the compiled main.wasm binary. 56 | // Execution with main.wasm will be skipped if the file cannot be found. 57 | func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { 58 | t.Helper() 59 | 60 | t.Run("go", func(t *testing.T) { 61 | f(t, &vmContext{}) 62 | }) 63 | 64 | t.Run("wasm", func(t *testing.T) { 65 | wasm, err := os.ReadFile("main.wasm") 66 | if err != nil { 67 | t.Skip("wasm not found") 68 | } 69 | v, err := proxytest.NewWasmVMContext(wasm) 70 | require.NoError(t, err) 71 | defer v.Close() 72 | f(t, v) 73 | }) 74 | } 75 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_lifecycle.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import "time" 18 | 19 | //export proxy_on_context_create 20 | func proxyOnContextCreate(contextID uint32, pluginContextID uint32) { 21 | if recordTiming { 22 | defer logTiming("proxyOnContextCreate", time.Now()) 23 | } 24 | if pluginContextID == 0 { 25 | currentState.createPluginContext(contextID) 26 | } else if currentState.createHttpContext(contextID, pluginContextID) { 27 | } else if currentState.createTcpContext(contextID, pluginContextID) { 28 | } else { 29 | panic("invalid context id on proxy_on_context_create") 30 | } 31 | } 32 | 33 | //export proxy_on_log 34 | func proxyOnLog(contextID uint32) { 35 | if recordTiming { 36 | defer logTiming("proxyOnLog", time.Now()) 37 | } 38 | if ctx, ok := currentState.tcpContexts[contextID]; ok { 39 | currentState.setActiveContextID(contextID) 40 | ctx.OnStreamDone() 41 | } else if ctx, ok := currentState.httpContexts[contextID]; ok { 42 | currentState.setActiveContextID(contextID) 43 | ctx.OnHttpStreamDone() 44 | } 45 | } 46 | 47 | //export proxy_on_done 48 | func proxyOnDone(contextID uint32) bool { 49 | if recordTiming { 50 | defer logTiming("proxyOnDone", time.Now()) 51 | } 52 | if ctx, ok := currentState.pluginContexts[contextID]; ok { 53 | currentState.setActiveContextID(contextID) 54 | return ctx.context.OnPluginDone() 55 | } 56 | return true 57 | } 58 | 59 | //export proxy_on_delete 60 | func proxyOnDelete(contextID uint32) { 61 | if recordTiming { 62 | defer logTiming("proxyOnDelete", time.Now()) 63 | } 64 | delete(currentState.contextIDToRootID, contextID) 65 | if _, ok := currentState.tcpContexts[contextID]; ok { 66 | delete(currentState.tcpContexts, contextID) 67 | } else if _, ok = currentState.httpContexts[contextID]; ok { 68 | delete(currentState.httpContexts, contextID) 69 | } else if _, ok = currentState.pluginContexts[contextID]; ok { 70 | delete(currentState.pluginContexts, contextID) 71 | } else { 72 | panic("invalid context on proxy_on_delete") 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /proxywasm/internal/serde_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "strconv" 19 | "testing" 20 | 21 | "github.com/stretchr/testify/require" 22 | ) 23 | 24 | var mapSerdeTestCases = []struct { 25 | maps [][2]string 26 | bytes []byte 27 | }{ 28 | { 29 | maps: [][2]string{{"a", "A"}}, 30 | bytes: []byte{ 31 | 1, 0, 0, 0, 32 | 1, 0, 0, 0, 33 | 1, 0, 0, 0, 34 | 97, 0, 65, 0, 35 | }, 36 | }, 37 | { 38 | maps: [][2]string{{"a", "A"}, {"b", "B"}}, 39 | bytes: []byte{ 40 | 2, 0, 0, 0, 41 | 1, 0, 0, 0, 42 | 1, 0, 0, 0, 43 | 1, 0, 0, 0, 44 | 1, 0, 0, 0, 45 | 97, 0, 65, 0, 46 | 98, 0, 66, 0, 47 | }, 48 | }, 49 | { 50 | maps: [][2]string{{"a", "ABCDEFG"}, {"@AB", "<1234"}}, 51 | bytes: []byte{ 52 | 2, 0, 0, 0, 53 | 1, 0, 0, 0, 54 | 7, 0, 0, 0, 55 | 3, 0, 0, 0, 56 | 5, 0, 0, 0, 57 | 97, 0, 58 | 65, 66, 67, 68, 69, 70, 71, 0, 59 | 64, 65, 66, 0, 60 | 60, 49, 50, 51, 52, 0, 61 | }, 62 | }, 63 | } 64 | 65 | func TestDeserializeMap(t *testing.T) { 66 | for i, c := range mapSerdeTestCases { 67 | t.Run(strconv.Itoa(i), func(t *testing.T) { 68 | require.Equal(t, c.maps, DeserializeMap(c.bytes)) 69 | }) 70 | } 71 | } 72 | 73 | func TestSerializeMap(t *testing.T) { 74 | for i, c := range mapSerdeTestCases { 75 | t.Run(strconv.Itoa(i), func(t *testing.T) { 76 | require.Equal(t, c.bytes, SerializeMap(c.maps)) 77 | }) 78 | } 79 | } 80 | 81 | func TestSerializePropertyPath(t *testing.T) { 82 | for i, c := range []struct { 83 | path []string 84 | exp []byte 85 | }{ 86 | { 87 | path: []string{"path", "to", "a"}, 88 | exp: []byte{ 89 | 'p', 'a', 't', 'h', 0, 90 | 't', 'o', 0, 91 | 'a', 92 | }, 93 | }, 94 | { 95 | path: []string{"a", "b"}, 96 | exp: []byte{'a', 0, 'b'}, 97 | }, 98 | { 99 | exp: []byte{}, 100 | }, 101 | } { 102 | t.Run(strconv.Itoa(i), func(t *testing.T) { 103 | require.Equal(t, c.exp, SerializePropertyPath(c.path)) 104 | }) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /examples/multiple_dispatches/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 14 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 15 | ) 16 | 17 | func TestHttpContext_OnHttpRequestHeaders(t *testing.T) { 18 | vmTest(t, func(t *testing.T, vm types.VMContext) { 19 | opt := proxytest.NewEmulatorOption().WithVMContext(vm) 20 | host, reset := proxytest.NewHostEmulator(opt) 21 | defer reset() 22 | 23 | // Initialize context. 24 | contextID := host.InitializeHttpContext() 25 | 26 | // Call OnHttpResponseHeaders. 27 | action := host.CallOnResponseHeaders(contextID, 28 | [][2]string{{"key", "value"}}, false) 29 | require.Equal(t, types.ActionPause, action) 30 | 31 | // Verify DispatchHttpCall is called. 32 | callouts := host.GetCalloutAttributesFromContext(contextID) 33 | require.Equal(t, len(callouts), 10) 34 | 35 | // At this point, none of dispatched callouts received response. 36 | // Therefore, the current status must be paused. 37 | require.Equal(t, types.ActionPause, host.GetCurrentHttpStreamAction(contextID)) 38 | 39 | // Emulates that Envoy received all the response to the dispatched callouts. 40 | for _, callout := range callouts { 41 | host.CallOnHttpCallResponse(callout.CalloutID, nil, nil, nil) 42 | } 43 | 44 | // Check if the current action is continued. 45 | require.Equal(t, types.ActionContinue, host.GetCurrentHttpStreamAction(contextID)) 46 | 47 | // Check logs. 48 | logs := host.GetInfoLogs() 49 | require.Contains(t, logs, "pending dispatched requests: 9") 50 | require.Contains(t, logs, "pending dispatched requests: 1") 51 | require.Contains(t, logs, "response resumed after processed 10 dispatched request") 52 | }) 53 | } 54 | 55 | // vmTest executes f twice, once with a types.VMContext that executes plugin code directly 56 | // in the host, and again by executing the plugin code within the compiled main.wasm binary. 57 | // Execution with main.wasm will be skipped if the file cannot be found. 58 | func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { 59 | t.Helper() 60 | 61 | t.Run("go", func(t *testing.T) { 62 | f(t, &vmContext{}) 63 | }) 64 | 65 | t.Run("wasm", func(t *testing.T) { 66 | wasm, err := os.ReadFile("main.wasm") 67 | if err != nil { 68 | t.Skip("wasm not found") 69 | } 70 | v, err := proxytest.NewWasmVMContext(wasm) 71 | require.NoError(t, err) 72 | defer v.Close() 73 | f(t, v) 74 | }) 75 | } 76 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_configuration_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "testing" 19 | 20 | "github.com/stretchr/testify/require" 21 | 22 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 23 | ) 24 | 25 | type testConfigurationVMContext struct { 26 | types.DefaultPluginContext 27 | onVMStartCalled bool 28 | } 29 | 30 | func (c *testConfigurationVMContext) OnVMStart(int) types.OnVMStartStatus { 31 | c.onVMStartCalled = true 32 | return true 33 | } 34 | 35 | func (c *testConfigurationVMContext) NewPluginContext(uint32) types.PluginContext { 36 | return &testConfigurationPluginContext{} 37 | } 38 | 39 | type testConfigurationPluginContext struct { 40 | types.DefaultPluginContext 41 | onPluginStartCalled bool 42 | } 43 | 44 | func (c *testConfigurationPluginContext) OnPluginStart(int) types.OnPluginStartStatus { 45 | c.onPluginStartCalled = true 46 | return true 47 | } 48 | 49 | func Test_pluginInitialization(t *testing.T) { 50 | currentStateMux.Lock() 51 | defer currentStateMux.Unlock() 52 | 53 | vmContext := &testConfigurationVMContext{} 54 | currentState = &state{ 55 | vmContext: vmContext, 56 | pluginContexts: map[uint32]*pluginContextState{}, 57 | contextIDToRootID: map[uint32]uint32{}, 58 | } 59 | 60 | // Check ABI version. There is no return value so just make sure it doesn't panic. 61 | proxyABIVersion() 62 | 63 | // Call OnVMStart. 64 | proxyOnVMStart(0, 0) 65 | require.True(t, vmContext.onVMStartCalled) 66 | require.Equal(t, uint32(0), currentState.activeContextID) 67 | 68 | // Allocate memory 69 | require.NotNil(t, proxyOnMemoryAllocate(100)) 70 | 71 | // Create plugin context. 72 | pluginContextID := uint32(100) 73 | proxyOnContextCreate(pluginContextID, 0) 74 | require.Contains(t, currentState.pluginContexts, pluginContextID) 75 | pluginContext := currentState.pluginContexts[pluginContextID].context.(*testConfigurationPluginContext) 76 | 77 | // Call OnPluginStart. 78 | proxyOnConfigure(pluginContextID, 0) 79 | require.True(t, pluginContext.onPluginStartCalled) 80 | require.Equal(t, pluginContextID, currentState.activeContextID) 81 | } 82 | -------------------------------------------------------------------------------- /proxywasm/proxytest/timing_off_test.go: -------------------------------------------------------------------------------- 1 | //go:build !proxywasm_timing 2 | 3 | package proxytest 4 | 5 | import ( 6 | "testing" 7 | 8 | "github.com/stretchr/testify/require" 9 | 10 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 11 | ) 12 | 13 | type noopPlugin struct { 14 | types.DefaultVMContext 15 | tcp bool 16 | } 17 | 18 | // NewPluginContext implements the same method on types.DefaultVMContext. 19 | func (p *noopPlugin) NewPluginContext(uint32) types.PluginContext { 20 | return &noopPluginContext{tcp: p.tcp} 21 | } 22 | 23 | type noopPluginContext struct { 24 | types.DefaultPluginContext 25 | tcp bool 26 | } 27 | 28 | // NewHttpContext implements the same method on types.DefaultPluginContext. 29 | func (p *noopPluginContext) NewHttpContext(uint32) types.HttpContext { 30 | if !p.tcp { 31 | return &noopHttpContext{} 32 | } 33 | return nil 34 | } 35 | 36 | // NewTcpContext implements the same method on types.DefaultPluginContext. 37 | func (p *noopPluginContext) NewTcpContext(uint32) types.TcpContext { 38 | if p.tcp { 39 | return &noopTcpContext{} 40 | } 41 | return nil 42 | } 43 | 44 | type noopHttpContext struct { 45 | types.DefaultHttpContext 46 | } 47 | 48 | type noopTcpContext struct { 49 | types.DefaultTcpContext 50 | } 51 | 52 | // Execute lifecycle methods, there should be no logs for the no-op plugin. 53 | func TestTimingOff(t *testing.T) { 54 | t.Run("http", func(t *testing.T) { 55 | host, reset := NewHostEmulator(NewEmulatorOption().WithVMContext(&noopPlugin{})) 56 | defer reset() 57 | 58 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 59 | id := host.InitializeHttpContext() 60 | 61 | require.Equal(t, types.ActionContinue, host.CallOnRequestHeaders(id, nil, false)) 62 | require.Equal(t, types.ActionContinue, host.CallOnRequestBody(id, nil, false)) 63 | require.Equal(t, types.ActionContinue, host.CallOnRequestTrailers(id, nil)) 64 | require.Equal(t, types.ActionContinue, host.CallOnResponseHeaders(id, nil, false)) 65 | require.Equal(t, types.ActionContinue, host.CallOnResponseBody(id, nil, false)) 66 | require.Equal(t, types.ActionContinue, host.CallOnResponseTrailers(id, nil)) 67 | host.CompleteHttpContext(id) 68 | 69 | require.Empty(t, host.GetDebugLogs()) 70 | }) 71 | 72 | t.Run("tcp", func(t *testing.T) { 73 | host, reset := NewHostEmulator(NewEmulatorOption().WithVMContext(&noopPlugin{tcp: true})) 74 | defer reset() 75 | 76 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 77 | id, action := host.InitializeConnection() 78 | require.Equal(t, types.ActionContinue, action) 79 | 80 | require.Equal(t, types.ActionContinue, host.CallOnDownstreamData(id, nil)) 81 | require.Equal(t, types.ActionContinue, host.CallOnUpstreamData(id, nil)) 82 | host.CompleteConnection(id) 83 | 84 | require.Empty(t, host.GetDebugLogs()) 85 | }) 86 | } 87 | -------------------------------------------------------------------------------- /examples/properties/README.md: -------------------------------------------------------------------------------- 1 | ## properties 2 | 3 | this example prevalidates the authentication header via the usage of properties fetched from the proxy (i.e. Envoy metadata here). 4 | 5 | ### message on clients 6 | ``` 7 | curl localhost:18000/one -v 8 | * Trying 127.0.0.1:18000... 9 | * Connected to localhost (127.0.0.1) port 18000 (#0) 10 | > GET /one HTTP/1.1 11 | > Host: localhost:18000 12 | > User-Agent: curl/7.82.0 13 | > Accept: */* 14 | > 15 | < HTTP/1.1 401 Unauthorized 16 | < date: Mon, 31 Oct 2022 00:53:01 GMT 17 | < server: envoy 18 | < content-length: 0 19 | < 20 | 21 | curl localhost:18000/one -v -H 'cookie: value' 22 | * Trying 127.0.0.1:18000... 23 | * Connected to localhost (127.0.0.1) port 18000 (#0) 24 | > GET /one HTTP/1.1 25 | > Host: localhost:18000 26 | > User-Agent: curl/7.82.0 27 | > Accept: */* 28 | > cookie: value 29 | > 30 | < HTTP/1.1 200 OK 31 | < content-length: 13 32 | < content-type: text/plain 33 | < date: Mon, 31 Oct 2022 00:54:59 GMT 34 | < server: envoy 35 | < x-envoy-upstream-service-time: 0 36 | < 37 | example body 38 | 39 | curl localhost:18000/two -v 40 | * Trying 127.0.0.1:18000... 41 | * Connected to localhost (127.0.0.1) port 18000 (#0) 42 | > GET /two HTTP/1.1 43 | > Host: localhost:18000 44 | > User-Agent: curl/7.82.0 45 | > Accept: */* 46 | > 47 | < HTTP/1.1 401 Unauthorized 48 | < date: Mon, 31 Oct 2022 00:53:30 GMT 49 | < server: envoy 50 | < content-length: 0 51 | < 52 | 53 | curl localhost:18000/two -v -H 'authorization: token' 54 | * Trying 127.0.0.1:18000... 55 | * Connected to localhost (127.0.0.1) port 18000 (#0) 56 | > GET /two HTTP/1.1 57 | > Host: localhost:18000 58 | > User-Agent: curl/7.82.0 59 | > Accept: */* 60 | > authorization: token 61 | > 62 | < HTTP/1.1 200 OK 63 | < content-length: 13 64 | < content-type: text/plain 65 | < date: Mon, 31 Oct 2022 00:53:52 GMT 66 | < server: envoy 67 | < x-envoy-upstream-service-time: 0 68 | < 69 | example body 70 | 71 | curl localhost:18000/three -v 72 | * Trying 127.0.0.1:18000... 73 | * Connected to localhost (127.0.0.1) port 18000 (#0) 74 | > GET /three HTTP/1.1 75 | > Host: localhost:18000 76 | > User-Agent: curl/7.82.0 77 | > Accept: */* 78 | > 79 | < HTTP/1.1 200 OK 80 | < content-length: 13 81 | < content-type: text/plain 82 | < date: Mon, 31 Oct 2022 00:55:27 GMT 83 | < server: envoy 84 | < x-envoy-upstream-service-time: 0 85 | < 86 | example body 87 | ``` 88 | 89 | ### message on Envoy 90 | ``` 91 | wasm log: auth header is "cookie" 92 | wasm log: 2 finished 93 | wasm log: auth header is "cookie" 94 | wasm log: 3 finished 95 | wasm log: auth header is "authorization" 96 | wasm log: 2 finished 97 | wasm log: auth header is "authorization" 98 | wasm log: 3 finished 99 | wasm log: no auth header for route 100 | wasm log: 4 finished 101 | ``` 102 | -------------------------------------------------------------------------------- /examples/shared_queue/receiver/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 21 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 22 | ) 23 | 24 | func main() { 25 | proxywasm.SetVMContext(&vmContext{}) 26 | } 27 | 28 | // vmContext implements types.VMContext. 29 | type vmContext struct { 30 | // Embed the default VM context here, 31 | // so that we don't need to reimplement all the methods. 32 | types.DefaultVMContext 33 | } 34 | 35 | // NewPluginContext implements types.VMContext. 36 | func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { 37 | return &receiverPluginContext{contextID: contextID} 38 | } 39 | 40 | // receiverPluginContext implements types.PluginContext. 41 | type receiverPluginContext struct { 42 | // Embed the default plugin context here, 43 | // so that we don't need to reimplement all the methods. 44 | contextID uint32 45 | types.DefaultPluginContext 46 | queueName string 47 | } 48 | 49 | // OnPluginStart implements types.PluginContext. 50 | func (ctx *receiverPluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { 51 | // Get Plugin configuration. 52 | config, err := proxywasm.GetPluginConfiguration() 53 | if err != nil { 54 | panic(fmt.Sprintf("failed to get plugin config: %v", err)) 55 | } 56 | 57 | // Treat the config as the queue name for receiving. 58 | ctx.queueName = string(config) 59 | 60 | queueID, err := proxywasm.RegisterSharedQueue(ctx.queueName) 61 | if err != nil { 62 | panic("failed register queue") 63 | } 64 | proxywasm.LogInfof("queue \"%s\" registered as queueID=%d by contextID=%d", ctx.queueName, queueID, ctx.contextID) 65 | return types.OnPluginStartStatusOK 66 | } 67 | 68 | // OnQueueReady implements types.PluginContext. 69 | func (ctx *receiverPluginContext) OnQueueReady(queueID uint32) { 70 | data, err := proxywasm.DequeueSharedQueue(queueID) 71 | switch err { 72 | case types.ErrorStatusEmpty: 73 | return 74 | case nil: 75 | proxywasm.LogInfof("(contextID=%d) dequeued data from %s(queueID=%d): %s", ctx.contextID, ctx.queueName, queueID, string(data)) 76 | default: 77 | proxywasm.LogCriticalf("error retrieving data from queue %d: %v", queueID, err) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /proxywasm/internal/serde.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "encoding/binary" 19 | "unsafe" 20 | ) 21 | 22 | func DeserializeMap(bs []byte) [][2]string { 23 | numHeaders := binary.LittleEndian.Uint32(bs[0:4]) 24 | var sizeIndex = 4 25 | var dataIndex = 4 + 4*2*int(numHeaders) 26 | ret := make([][2]string, numHeaders) 27 | for i := 0; i < int(numHeaders); i++ { 28 | keySize := int(binary.LittleEndian.Uint32(bs[sizeIndex : sizeIndex+4])) 29 | sizeIndex += 4 30 | keyPtr := bs[dataIndex : dataIndex+keySize] 31 | key := *(*string)(unsafe.Pointer(&keyPtr)) 32 | dataIndex += keySize + 1 33 | 34 | valueSize := int(binary.LittleEndian.Uint32(bs[sizeIndex : sizeIndex+4])) 35 | sizeIndex += 4 36 | valuePtr := bs[dataIndex : dataIndex+valueSize] 37 | value := *(*string)(unsafe.Pointer(&valuePtr)) 38 | dataIndex += valueSize + 1 39 | ret[i] = [2]string{key, value} 40 | } 41 | return ret 42 | } 43 | 44 | func SerializeMap(ms [][2]string) []byte { 45 | size := 4 46 | for _, m := range ms { 47 | // key/value's bytes + len * 2 (8 bytes) + nil * 2 (2 bytes) 48 | size += len(m[0]) + len(m[1]) + 10 49 | } 50 | 51 | ret := make([]byte, size) 52 | binary.LittleEndian.PutUint32(ret[0:4], uint32(len(ms))) 53 | 54 | var base = 4 55 | for _, m := range ms { 56 | binary.LittleEndian.PutUint32(ret[base:base+4], uint32(len(m[0]))) 57 | base += 4 58 | binary.LittleEndian.PutUint32(ret[base:base+4], uint32(len(m[1]))) 59 | base += 4 60 | } 61 | 62 | for _, m := range ms { 63 | for i := 0; i < len(m[0]); i++ { 64 | ret[base] = m[0][i] 65 | base++ 66 | } 67 | base++ // nil 68 | 69 | for i := 0; i < len(m[1]); i++ { 70 | ret[base] = m[1][i] 71 | base++ 72 | } 73 | base++ // nil 74 | } 75 | return ret 76 | } 77 | 78 | func SerializePropertyPath(path []string) []byte { 79 | // TODO: for static paths, like upstream.address, we'd better pre-define []byte so that 80 | // we do not incur any serialization cost 81 | if len(path) == 0 { 82 | return []byte{} 83 | } 84 | 85 | var size int 86 | for _, p := range path { 87 | size += len(p) + 1 88 | } 89 | ret := make([]byte, 0, size) 90 | for _, p := range path { 91 | ret = append(ret, p...) 92 | ret = append(ret, 0) 93 | } 94 | ret = ret[:len(ret)-1] 95 | return ret 96 | } 97 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_l4.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "time" 19 | 20 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 21 | ) 22 | 23 | //export proxy_on_new_connection 24 | func proxyOnNewConnection(contextID uint32) types.Action { 25 | if recordTiming { 26 | defer logTiming("proxyOnNewConnection", time.Now()) 27 | } 28 | ctx, ok := currentState.tcpContexts[contextID] 29 | if !ok { 30 | panic("invalid context") 31 | } 32 | currentState.setActiveContextID(contextID) 33 | return ctx.OnNewConnection() 34 | } 35 | 36 | //export proxy_on_downstream_data 37 | func proxyOnDownstreamData(contextID uint32, dataSize int, endOfStream bool) types.Action { 38 | if recordTiming { 39 | defer logTiming("proxyOnDownstreamData", time.Now()) 40 | } 41 | ctx, ok := currentState.tcpContexts[contextID] 42 | if !ok { 43 | panic("invalid context") 44 | } 45 | currentState.setActiveContextID(contextID) 46 | return ctx.OnDownstreamData(dataSize, endOfStream) 47 | } 48 | 49 | //export proxy_on_downstream_connection_close 50 | func proxyOnDownstreamConnectionClose(contextID uint32, pType types.PeerType) { 51 | if recordTiming { 52 | defer logTiming("proxyOnDownstreamConnectionClose", time.Now()) 53 | } 54 | ctx, ok := currentState.tcpContexts[contextID] 55 | if !ok { 56 | panic("invalid context") 57 | } 58 | currentState.setActiveContextID(contextID) 59 | ctx.OnDownstreamClose(pType) 60 | } 61 | 62 | //export proxy_on_upstream_data 63 | func proxyOnUpstreamData(contextID uint32, dataSize int, endOfStream bool) types.Action { 64 | if recordTiming { 65 | defer logTiming("proxyOnUpstreamData", time.Now()) 66 | } 67 | ctx, ok := currentState.tcpContexts[contextID] 68 | if !ok { 69 | panic("invalid context") 70 | } 71 | currentState.setActiveContextID(contextID) 72 | return ctx.OnUpstreamData(dataSize, endOfStream) 73 | } 74 | 75 | //export proxy_on_upstream_connection_close 76 | func proxyOnUpstreamConnectionClose(contextID uint32, pType types.PeerType) { 77 | if recordTiming { 78 | defer logTiming("proxyOnUpstreamConnectionClose", time.Now()) 79 | } 80 | ctx, ok := currentState.tcpContexts[contextID] 81 | if !ok { 82 | panic("invalid context") 83 | } 84 | currentState.setActiveContextID(contextID) 85 | ctx.OnUpstreamClose(pType) 86 | } 87 | -------------------------------------------------------------------------------- /examples/network/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: main 4 | address: 5 | socket_address: 6 | address: 0.0.0.0 7 | port_value: 18000 8 | filter_chains: 9 | - filters: 10 | - name: envoy.filters.network.wasm 11 | typed_config: 12 | "@type": type.googleapis.com/envoy.extensions.filters.network.wasm.v3.Wasm 13 | config: 14 | vm_config: 15 | runtime: "envoy.wasm.runtime.v8" 16 | code: 17 | local: 18 | filename: "./examples/network/main.wasm" 19 | 20 | - name: envoy.tcp_proxy 21 | typed_config: 22 | "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy 23 | stat_prefix: ingress 24 | cluster: web_service 25 | 26 | - name: staticreply 27 | address: 28 | socket_address: 29 | address: 127.0.0.1 30 | port_value: 8099 31 | filter_chains: 32 | - filters: 33 | - name: envoy.http_connection_manager 34 | typed_config: 35 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 36 | stat_prefix: ingress_http 37 | codec_type: auto 38 | route_config: 39 | name: local_route 40 | virtual_hosts: 41 | - name: local_service 42 | domains: 43 | - "*" 44 | routes: 45 | - match: 46 | prefix: "/" 47 | direct_response: 48 | status: 200 49 | body: 50 | inline_string: "example body\n" 51 | http_filters: 52 | - name: envoy.filters.http.router 53 | typed_config: 54 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 55 | 56 | clusters: 57 | - name: web_service 58 | metadata: 59 | filter_metadata: 60 | location: 61 | cloud_provider: aws 62 | region: ap-northeast-1 63 | az: ap-northeast-1a 64 | connect_timeout: 0.25s 65 | type: STATIC 66 | lb_policy: ROUND_ROBIN 67 | load_assignment: 68 | cluster_name: mock_service 69 | endpoints: 70 | - lb_endpoints: 71 | - endpoint: 72 | address: 73 | socket_address: 74 | address: 127.0.0.1 75 | port_value: 8099 76 | 77 | admin: 78 | access_log_path: "/dev/null" 79 | address: 80 | socket_address: 81 | address: 0.0.0.0 82 | port_value: 8001 83 | -------------------------------------------------------------------------------- /examples/http_routing/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/require" 12 | 13 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 14 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 15 | ) 16 | 17 | func TestHttpRouting_OnHttpRequestHeaders(t *testing.T) { 18 | vmTest(t, func(t *testing.T, vm types.VMContext) { 19 | t.Run("canary", func(t *testing.T) { 20 | opt := proxytest.NewEmulatorOption().WithVMContext(vm).WithPluginConfiguration([]byte{2}) 21 | host, reset := proxytest.NewHostEmulator(opt) 22 | defer reset() 23 | 24 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 25 | 26 | // Initialize http context. 27 | id := host.InitializeHttpContext() 28 | hs := [][2]string{{":authority", "my-host.com"}} 29 | // Call OnHttpResponseHeaders. 30 | action := host.CallOnRequestHeaders(id, 31 | hs, false) 32 | require.Equal(t, types.ActionContinue, action) 33 | resultHeaders := host.GetCurrentRequestHeaders(id) 34 | require.Len(t, resultHeaders, 1) 35 | require.Equal(t, ":authority", resultHeaders[0][0]) 36 | require.Equal(t, "my-host.com-canary", resultHeaders[0][1]) 37 | }) 38 | 39 | t.Run("non-canary", func(t *testing.T) { 40 | opt := proxytest.NewEmulatorOption().WithVMContext(vm).WithPluginConfiguration([]byte{1}) 41 | host, reset := proxytest.NewHostEmulator(opt) 42 | defer reset() 43 | 44 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 45 | 46 | // Initialize http context. 47 | id := host.InitializeHttpContext() 48 | hs := [][2]string{{":authority", "my-host.com"}} 49 | // Call OnHttpResponseHeaders. 50 | action := host.CallOnRequestHeaders(id, 51 | hs, false) 52 | require.Equal(t, types.ActionContinue, action) 53 | resultHeaders := host.GetCurrentRequestHeaders(id) 54 | require.Len(t, resultHeaders, 1) 55 | require.Equal(t, ":authority", resultHeaders[0][0]) 56 | require.Equal(t, "my-host.com", resultHeaders[0][1]) 57 | }) 58 | }) 59 | } 60 | 61 | // vmTest executes f twice, once with a types.VMContext that executes plugin code directly 62 | // in the host, and again by executing the plugin code within the compiled main.wasm binary. 63 | // Execution with main.wasm will be skipped if the file cannot be found. 64 | func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { 65 | t.Helper() 66 | 67 | t.Run("go", func(t *testing.T) { 68 | f(t, &vmContext{}) 69 | }) 70 | 71 | t.Run("wasm", func(t *testing.T) { 72 | wasm, err := os.ReadFile("main.wasm") 73 | if err != nil { 74 | t.Skip("wasm not found") 75 | } 76 | v, err := proxytest.NewWasmVMContext(wasm) 77 | require.NoError(t, err) 78 | defer v.Close() 79 | f(t, v) 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_enums.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "errors" 5 | "strconv" 6 | 7 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 8 | ) 9 | 10 | type BufferType uint32 11 | 12 | const ( 13 | BufferTypeHttpRequestBody BufferType = 0 14 | BufferTypeHttpResponseBody BufferType = 1 15 | BufferTypeDownstreamData BufferType = 2 16 | BufferTypeUpstreamData BufferType = 3 17 | BufferTypeHttpCallResponseBody BufferType = 4 18 | BufferTypeGrpcReceiveBuffer BufferType = 5 19 | BufferTypeVMConfiguration BufferType = 6 20 | BufferTypePluginConfiguration BufferType = 7 21 | BufferTypeCallData BufferType = 8 22 | ) 23 | 24 | type LogLevel uint32 25 | 26 | const ( 27 | LogLevelTrace LogLevel = 0 28 | LogLevelDebug LogLevel = 1 29 | LogLevelInfo LogLevel = 2 30 | LogLevelWarn LogLevel = 3 31 | LogLevelError LogLevel = 4 32 | LogLevelCritical LogLevel = 5 33 | LogLevelMax LogLevel = 6 34 | ) 35 | 36 | func (l LogLevel) String() string { 37 | switch l { 38 | case LogLevelTrace: 39 | return "trace" 40 | case LogLevelDebug: 41 | return "debug" 42 | case LogLevelInfo: 43 | return "info" 44 | case LogLevelWarn: 45 | return "warn" 46 | case LogLevelError: 47 | return "error" 48 | case LogLevelCritical: 49 | return "critical" 50 | default: 51 | panic("invalid log level") 52 | } 53 | } 54 | 55 | type MapType uint32 56 | 57 | const ( 58 | MapTypeHttpRequestHeaders MapType = 0 59 | MapTypeHttpRequestTrailers MapType = 1 60 | MapTypeHttpResponseHeaders MapType = 2 61 | MapTypeHttpResponseTrailers MapType = 3 62 | MapTypeHttpCallResponseHeaders MapType = 6 63 | MapTypeHttpCallResponseTrailers MapType = 7 64 | ) 65 | 66 | type MetricType uint32 67 | 68 | const ( 69 | MetricTypeCounter = 0 70 | MetricTypeGauge = 1 71 | MetricTypeHistogram = 2 72 | ) 73 | 74 | type StreamType uint32 75 | 76 | const ( 77 | StreamTypeRequest StreamType = 0 78 | StreamTypeResponse StreamType = 1 79 | StreamTypeDownstream StreamType = 2 80 | StreamTypeUpstream StreamType = 3 81 | ) 82 | 83 | type Status uint32 84 | 85 | const ( 86 | StatusOK Status = 0 87 | StatusNotFound Status = 1 88 | StatusBadArgument Status = 2 89 | StatusEmpty Status = 7 90 | StatusCasMismatch Status = 8 91 | StatusInternalFailure Status = 10 92 | StatusUnimplemented Status = 12 93 | ) 94 | 95 | //go:inline 96 | func StatusToError(status Status) error { 97 | switch Status(status) { 98 | case StatusOK: 99 | return nil 100 | case StatusNotFound: 101 | return types.ErrorStatusNotFound 102 | case StatusBadArgument: 103 | return types.ErrorStatusBadArgument 104 | case StatusEmpty: 105 | return types.ErrorStatusEmpty 106 | case StatusCasMismatch: 107 | return types.ErrorStatusCasMismatch 108 | case StatusInternalFailure: 109 | return types.ErrorInternalFailure 110 | case StatusUnimplemented: 111 | return types.ErrorUnimplemented 112 | } 113 | return errors.New("unknown status code: " + strconv.Itoa(int(status))) 114 | } 115 | -------------------------------------------------------------------------------- /examples/metrics/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "fmt" 19 | 20 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 21 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 22 | ) 23 | 24 | func main() { 25 | proxywasm.SetVMContext(&vmContext{}) 26 | } 27 | 28 | // vmContext implements types.VMContext. 29 | type vmContext struct { 30 | // Embed the default VM context here, 31 | // so that we don't need to reimplement all the methods. 32 | types.DefaultVMContext 33 | } 34 | 35 | // NewPluginContext implements types.VMContext. 36 | func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { 37 | return &metricPluginContext{} 38 | } 39 | 40 | // metricPluginContext implements types.PluginContext. 41 | type metricPluginContext struct { 42 | // Embed the default plugin context here, 43 | // so that we don't need to reimplement all the methods. 44 | types.DefaultPluginContext 45 | } 46 | 47 | // NewHttpContext implements types.PluginContext. 48 | func (ctx *metricPluginContext) NewHttpContext(contextID uint32) types.HttpContext { 49 | return &metricHttpContext{} 50 | } 51 | 52 | // metricHttpContext implements types.HttpContext. 53 | type metricHttpContext struct { 54 | // Embed the default http context here, 55 | // so that we don't need to reimplement all the methods. 56 | types.DefaultHttpContext 57 | } 58 | 59 | const ( 60 | customHeaderKey = "my-custom-header" 61 | customHeaderValueTagKey = "value" 62 | ) 63 | 64 | // counters is a map from custom header value to a counter metric. 65 | // Note that Proxy-Wasm plugins are single threaded, so no need to use a lock. 66 | var counters = map[string]proxywasm.MetricCounter{} 67 | 68 | // OnHttpRequestHeaders implements types.HttpContext. 69 | func (ctx *metricHttpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action { 70 | customHeaderValue, err := proxywasm.GetHttpRequestHeader(customHeaderKey) 71 | if err == nil { 72 | counter, ok := counters[customHeaderValue] 73 | if !ok { 74 | // This metric is processed as: custom_header_value_counts{value="foo",reporter="wasmgosdk"} n. 75 | // The extraction rule is defined in envoy.yaml as a bootstrap configuration. 76 | // See https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/metrics/v3/stats.proto#config-metrics-v3-statsconfig. 77 | fqn := fmt.Sprintf("custom_header_value_counts_%s=%s_reporter=wasmgosdk", customHeaderValueTagKey, customHeaderValue) 78 | counter = proxywasm.DefineCounterMetric(fqn) 79 | counters[customHeaderValue] = counter 80 | } 81 | counter.Increment(1) 82 | } 83 | return types.ActionContinue 84 | } 85 | -------------------------------------------------------------------------------- /examples/postpone_requests/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 19 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 20 | ) 21 | 22 | const tickMilliseconds uint32 = 1000 23 | 24 | func main() { 25 | proxywasm.SetVMContext(&vmContext{}) 26 | } 27 | 28 | // vmContext implements types.VMContext. 29 | type vmContext struct { 30 | // Embed the default VM context here, 31 | // so that we don't need to reimplement all the methods. 32 | types.DefaultVMContext 33 | } 34 | 35 | // NewPluginContext implements types.VMContext. 36 | func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { 37 | return &pluginContext{ 38 | contextID: contextID, 39 | postponed: make([]uint32, 0, 1024), 40 | } 41 | } 42 | 43 | // pluginContext implements types.PluginContext. 44 | type pluginContext struct { 45 | // Embed the default plugin context here, 46 | // so that we don't need to reimplement all the methods. 47 | types.DefaultPluginContext 48 | contextID uint32 49 | postponed []uint32 50 | } 51 | 52 | // OnPluginStart implements types.PluginContext. 53 | func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { 54 | if err := proxywasm.SetTickPeriodMilliSeconds(tickMilliseconds); err != nil { 55 | proxywasm.LogCriticalf("failed to set tick period: %v", err) 56 | } 57 | 58 | return types.OnPluginStartStatusOK 59 | } 60 | 61 | // OnTick implements types.PluginContext. 62 | func (ctx *pluginContext) OnTick() { 63 | for len(ctx.postponed) > 0 { 64 | httpCtxId, tail := ctx.postponed[0], ctx.postponed[1:] 65 | proxywasm.LogInfof("resume request with contextID=%v", httpCtxId) 66 | _ = proxywasm.SetEffectiveContext(httpCtxId) 67 | _ = proxywasm.ResumeHttpRequest() 68 | ctx.postponed = tail 69 | } 70 | } 71 | 72 | // NewHttpContext implements types.PluginContext. 73 | func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext { 74 | return &httpContext{ 75 | contextID: contextID, 76 | pluginCtx: ctx, 77 | } 78 | } 79 | 80 | // httpContext implements types.HttpContext. 81 | type httpContext struct { 82 | // Embed the default http context here, 83 | // so that we don't need to reimplement all the methods. 84 | types.DefaultHttpContext 85 | contextID uint32 86 | pluginCtx *pluginContext 87 | } 88 | 89 | // OnHttpRequestHeaders implements types.HttpContext. 90 | func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action { 91 | proxywasm.LogInfof("postpone request with contextID=%d", ctx.contextID) 92 | ctx.pluginCtx.postponed = append(ctx.pluginCtx.postponed, ctx.contextID) 93 | return types.ActionPause 94 | } 95 | -------------------------------------------------------------------------------- /examples/properties/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 19 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 20 | ) 21 | 22 | func main() { 23 | proxywasm.SetVMContext(&vmContext{}) 24 | } 25 | 26 | // vmContext implements types.VMContext. 27 | type vmContext struct { 28 | // Embed the default VM context here, 29 | // so that we don't need to reimplement all the methods. 30 | types.DefaultVMContext 31 | } 32 | 33 | // NewPluginContext implements types.VMContext. 34 | func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { 35 | return &pluginContext{} 36 | } 37 | 38 | type pluginContext struct { 39 | // Embed the default plugin context here, 40 | // so that we don't need to reimplement all the methods. 41 | types.DefaultPluginContext 42 | } 43 | 44 | // NewHttpContext implements types.PluginContext. 45 | func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext { 46 | return &properties{contextID: contextID} 47 | } 48 | 49 | // properties implements types.HttpContext. 50 | type properties struct { 51 | // Embed the default http context here, 52 | // so that we don't need to reimplement all the methods. 53 | types.DefaultHttpContext 54 | contextID uint32 55 | } 56 | 57 | var propertyPrefix = []string{ 58 | "route_metadata", 59 | "filter_metadata", 60 | "envoy.filters.http.wasm", 61 | } 62 | 63 | // OnHttpRequestHeaders implements types.HttpContext. 64 | func (ctx *properties) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action { 65 | auth, err := proxywasm.GetProperty(append(propertyPrefix, "auth")) 66 | if err != nil { 67 | if err == types.ErrorStatusNotFound { 68 | proxywasm.LogInfo("no auth header for route") 69 | return types.ActionContinue 70 | } 71 | proxywasm.LogCriticalf("failed to read properties: %v", err) 72 | } 73 | proxywasm.LogInfof("auth header is \"%s\"", auth) 74 | 75 | hs, err := proxywasm.GetHttpRequestHeaders() 76 | if err != nil { 77 | proxywasm.LogCriticalf("failed to get request headers: %v", err) 78 | } 79 | 80 | // Verify authentication header exists 81 | authHeader := false 82 | for _, h := range hs { 83 | if h[0] == string(auth) { 84 | authHeader = true 85 | break 86 | } 87 | } 88 | 89 | // Reject requests without authentication header 90 | if !authHeader { 91 | _ = proxywasm.SendHttpResponse(401, nil, nil, 16) 92 | return types.ActionPause 93 | } 94 | 95 | return types.ActionContinue 96 | } 97 | 98 | // OnHttpStreamDone implements types.HttpContext. 99 | func (ctx *properties) OnHttpStreamDone() { 100 | proxywasm.LogInfof("%d finished", ctx.contextID) 101 | } 102 | -------------------------------------------------------------------------------- /examples/dispatch_call_on_tick/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "crypto/rand" 19 | 20 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 21 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 22 | ) 23 | 24 | const tickMilliseconds uint32 = 100 25 | 26 | func main() { 27 | proxywasm.SetVMContext(&vmContext{}) 28 | } 29 | 30 | // vmContext implements types.VMContext. 31 | type vmContext struct { 32 | // Embed the default VM context here, 33 | // so that we don't need to reimplement all the methods. 34 | types.DefaultVMContext 35 | } 36 | 37 | // NewPluginContext implements types.VMContext. 38 | func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { 39 | return &pluginContext{contextID: contextID} 40 | } 41 | 42 | // pluginContext implements types.PluginContext. 43 | type pluginContext struct { 44 | // Embed the default plugin context here, 45 | // so that we don't need to reimplement all the methods. 46 | types.DefaultPluginContext 47 | contextID uint32 48 | callBack func(numHeaders, bodySize, numTrailers int) 49 | cnt int 50 | } 51 | 52 | // OnPluginStart implements types.PluginContext. 53 | func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { 54 | if err := proxywasm.SetTickPeriodMilliSeconds(tickMilliseconds); err != nil { 55 | proxywasm.LogCriticalf("failed to set tick period: %v", err) 56 | return types.OnPluginStartStatusFailed 57 | } 58 | proxywasm.LogInfof("set tick period milliseconds: %d", tickMilliseconds) 59 | ctx.callBack = func(numHeaders, bodySize, numTrailers int) { 60 | ctx.cnt++ 61 | proxywasm.LogInfof("called %d for contextID=%d", ctx.cnt, ctx.contextID) 62 | headers, err := proxywasm.GetHttpCallResponseHeaders() 63 | if err != nil && err != types.ErrorStatusNotFound { 64 | panic(err) 65 | } 66 | for _, h := range headers { 67 | proxywasm.LogInfof("response header for the dispatched call: %s: %s", h[0], h[1]) 68 | } 69 | headers, err = proxywasm.GetHttpCallResponseTrailers() 70 | if err != nil && err != types.ErrorStatusNotFound { 71 | panic(err) 72 | } 73 | for _, h := range headers { 74 | proxywasm.LogInfof("response trailer for the dispatched call: %s: %s", h[0], h[1]) 75 | } 76 | } 77 | return types.OnPluginStartStatusOK 78 | } 79 | 80 | // OnTick implements types.PluginContext. 81 | func (ctx *pluginContext) OnTick() { 82 | headers := [][2]string{ 83 | {":method", "GET"}, {":authority", "some_authority"}, {"accept", "*/*"}, 84 | } 85 | // Pick random value to select the request path. 86 | buf := make([]byte, 1) 87 | _, _ = rand.Read(buf) 88 | if buf[0]%2 == 0 { 89 | headers = append(headers, [2]string{":path", "/ok"}) 90 | } else { 91 | headers = append(headers, [2]string{":path", "/fail"}) 92 | } 93 | if _, err := proxywasm.DispatchHttpCall("web_service", headers, nil, nil, 5000, ctx.callBack); err != nil { 94 | proxywasm.LogCriticalf("dispatch httpcall failed: %v", err) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /examples/shared_data/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "encoding/binary" 19 | "errors" 20 | 21 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 22 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 23 | ) 24 | 25 | const ( 26 | sharedDataKey = "shared_data_key" 27 | ) 28 | 29 | func main() { 30 | proxywasm.SetVMContext(&vmContext{}) 31 | } 32 | 33 | type ( 34 | // vmContext implements types.VMContext. 35 | vmContext struct{} 36 | // pluginContext implements types.PluginContext. 37 | pluginContext struct { 38 | // Embed the default plugin context here, 39 | // so that we don't need to reimplement all the methods. 40 | types.DefaultPluginContext 41 | } 42 | // httpContext implements types.HttpContext. 43 | httpContext struct { 44 | // Embed the default http context here, 45 | // so that we don't need to reimplement all the methods. 46 | types.DefaultHttpContext 47 | } 48 | ) 49 | 50 | // OnVMStart implements types.VMContext. 51 | func (*vmContext) OnVMStart(vmConfigurationSize int) types.OnVMStartStatus { 52 | initialValueBuf := make([]byte, 0) // Empty data to indicate that the data is not initialized. 53 | if err := proxywasm.SetSharedData(sharedDataKey, initialValueBuf, 0); err != nil { 54 | proxywasm.LogWarnf("error setting shared data on OnVMStart: %v", err) 55 | } 56 | return types.OnVMStartStatusOK 57 | } 58 | 59 | // NewPluginContext implements types.VMContext. 60 | func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { 61 | return &pluginContext{} 62 | } 63 | 64 | // NewHttpContext implements types.PluginContext. 65 | func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext { 66 | return &httpContext{} 67 | } 68 | 69 | // OnHttpRequestHeaders implements types.HttpContext. 70 | func (ctx *httpContext) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action { 71 | for { 72 | value, err := ctx.incrementData() 73 | if err == nil { 74 | proxywasm.LogInfof("shared value: %d", value) 75 | } else if errors.Is(err, types.ErrorStatusCasMismatch) { 76 | continue 77 | } 78 | break 79 | } 80 | return types.ActionContinue 81 | } 82 | 83 | // incrementData increments the shared data value by 1. 84 | func (ctx *httpContext) incrementData() (uint64, error) { 85 | data, cas, err := proxywasm.GetSharedData(sharedDataKey) 86 | if err != nil { 87 | proxywasm.LogWarnf("error getting shared data on OnHttpRequestHeaders: %v", err) 88 | return 0, err 89 | } 90 | 91 | var nextValue uint64 92 | if len(data) > 0 { 93 | nextValue = binary.LittleEndian.Uint64(data) + 1 94 | } else { 95 | nextValue = 1 96 | } 97 | 98 | buf := make([]byte, 8) 99 | binary.LittleEndian.PutUint64(buf, nextValue) 100 | if err := proxywasm.SetSharedData(sharedDataKey, buf, cas); err != nil { 101 | proxywasm.LogWarnf("error setting shared data on OnHttpRequestHeaders: %v", err) 102 | return 0, err 103 | } 104 | return nextValue, err 105 | } 106 | -------------------------------------------------------------------------------- /examples/http_auth_random/README.md: -------------------------------------------------------------------------------- 1 | ## http_auth_random 2 | 3 | this example authorize requests depending on the hash values of a response from http://httpbin.org/uuid. 4 | 5 | ### message on clients 6 | ``` 7 | $ curl localhost:18000/uuid -v 8 | * Trying ::1... 9 | * TCP_NODELAY set 10 | * Connected to localhost (::1) port 18000 (#0) 11 | > GET /uuid HTTP/1.1 12 | > Host: localhost:18000 13 | > User-Agent: curl/7.54.0 14 | > Accept: */* 15 | > 16 | < HTTP/1.1 200 OK 17 | < date: Wed, 25 Mar 2020 09:06:33 GMT 18 | < content-type: application/json 19 | < content-length: 53 20 | < server: envoy 21 | < access-control-allow-origin: * 22 | < access-control-allow-credentials: true 23 | < x-envoy-upstream-service-time: 1056 24 | < 25 | { 26 | "uuid": "e1020f65-f97a-47cd-9b31-368ba2063b6a" 27 | } 28 | 29 | 30 | 31 | # curl localhost:18000/uuid -v 32 | * Trying ::1... 33 | * TCP_NODELAY set 34 | * Connected to localhost (::1) port 18000 (#0) 35 | > GET /uuid HTTP/1.1 36 | > Host: localhost:18000 37 | > User-Agent: curl/7.54.0 38 | > Accept: */* 39 | > 40 | < HTTP/1.1 403 Forbidden 41 | < content-length: 16 42 | < content-type: text/plain 43 | < powered-by: proxy-wasm-go-sdk!! 44 | < date: Wed, 25 Mar 2020 09:07:36 GMT 45 | < server: envoy 46 | < 47 | * Connection #0 to host localhost left intact 48 | access forbidden 49 | 50 | ``` 51 | 52 | ### message on Envoy 53 | 54 | ``` 55 | wasm log: request header from: :authority: localhost:18000 56 | wasm log: request header from: :path: /uuid 57 | wasm log: request header from: :method: GET 58 | wasm log: request header from: user-agent: curl/7.68.0 59 | wasm log: request header from: accept: */* 60 | wasm log: request header from: x-forwarded-proto: http 61 | wasm log: request header from: x-request-id: fddeac7b-db59-453c-9956-7f1050dbf6d5 62 | wasm log: http call dispatched to httpbin 63 | wasm log: response header from httpbin: :status: 200 64 | wasm log: response header from httpbin: date: Thu, 01 Oct 2020 09:07:32 GMT 65 | wasm log: response header from httpbin: content-type: application/json 66 | wasm log: response header from httpbin: content-length: 53 67 | wasm log: response header from httpbin: connection: keep-alive 68 | wasm log: response header from httpbin: server: gunicorn/19.9.0 69 | wasm log: response header from httpbin: access-control-allow-origin: * 70 | wasm log: response header from httpbin: access-control-allow-credentials: true 71 | wasm log: response header from httpbin: x-envoy-upstream-service-time: 340 72 | wasm log: access granted 73 | wasm log: 2 finished 74 | wasm log: request header from: :authority: localhost:18000 75 | wasm log: request header from: :path: /uuid 76 | wasm log: request header from: :method: GET 77 | wasm log: request header from: user-agent: curl/7.68.0 78 | wasm log: request header from: accept: */* 79 | wasm log: request header from: x-forwarded-proto: http 80 | wasm log: request header from: x-request-id: 02628dd2-b985-4c4f-a2d2-164589c16f53 81 | wasm log: http call dispatched to httpbin 82 | wasm log: response header from httpbin: :status: 200 83 | wasm log: response header from httpbin: date: Thu, 01 Oct 2020 09:07:34 GMT 84 | wasm log: response header from httpbin: content-type: application/json 85 | wasm log: response header from httpbin: content-length: 53 86 | wasm log: response header from httpbin: connection: keep-alive 87 | wasm log: response header from httpbin: server: gunicorn/19.9.0 88 | wasm log: response header from httpbin: access-control-allow-origin: * 89 | wasm log: response header from httpbin: access-control-allow-credentials: true 90 | wasm log: response header from httpbin: x-envoy-upstream-service-time: 350 91 | wasm log: access forbidden 92 | wasm log: 3 finished 93 | ``` 94 | -------------------------------------------------------------------------------- /examples/json_validation/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "os" 9 | "testing" 10 | 11 | "github.com/stretchr/testify/assert" 12 | "github.com/stretchr/testify/require" 13 | 14 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 15 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 16 | ) 17 | 18 | func TestOnHTTPRequestHeaders(t *testing.T) { 19 | type testCase struct { 20 | contentType string 21 | expectedAction types.Action 22 | } 23 | 24 | vmTest(t, func(t *testing.T, vm types.VMContext) { 25 | for name, tCase := range map[string]testCase{ 26 | "fails due to unsupported content type": { 27 | contentType: "text/html", 28 | expectedAction: types.ActionPause, 29 | }, 30 | "success for JSON": { 31 | contentType: "application/json", 32 | expectedAction: types.ActionContinue, 33 | }, 34 | } { 35 | t.Run(name, func(t *testing.T) { 36 | opt := proxytest.NewEmulatorOption().WithVMContext(vm) 37 | host, reset := proxytest.NewHostEmulator(opt) 38 | defer reset() 39 | 40 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 41 | 42 | id := host.InitializeHttpContext() 43 | 44 | hs := [][2]string{{"content-type", tCase.contentType}} 45 | 46 | action := host.CallOnRequestHeaders(id, hs, false) 47 | assert.Equal(t, tCase.expectedAction, action) 48 | }) 49 | } 50 | }) 51 | } 52 | 53 | func TestOnHTTPRequestBody(t *testing.T) { 54 | type testCase struct { 55 | body string 56 | expectedAction types.Action 57 | } 58 | 59 | vmTest(t, func(t *testing.T, vm types.VMContext) { 60 | 61 | for name, tCase := range map[string]testCase{ 62 | "pauses due to invalid payload": { 63 | body: "invalid_payload", 64 | expectedAction: types.ActionPause, 65 | }, 66 | "pauses due to unknown keys": { 67 | body: `{"unknown_key":"unknown_value"}`, 68 | expectedAction: types.ActionPause, 69 | }, 70 | "success": { 71 | body: "{\"my_key\":\"my_value\"}", 72 | expectedAction: types.ActionContinue, 73 | }, 74 | } { 75 | t.Run(name, func(t *testing.T) { 76 | opt := proxytest. 77 | NewEmulatorOption(). 78 | WithPluginConfiguration([]byte(`{"requiredKeys": ["my_key"]}`)). 79 | WithVMContext(vm) 80 | host, reset := proxytest.NewHostEmulator(opt) 81 | defer reset() 82 | 83 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 84 | 85 | id := host.InitializeHttpContext() 86 | 87 | action := host.CallOnRequestBody(id, []byte(tCase.body), true) 88 | assert.Equal(t, tCase.expectedAction, action) 89 | }) 90 | } 91 | }) 92 | } 93 | 94 | // vmTest executes f twice, once with a types.VMContext that executes plugin code directly 95 | // in the host, and again by executing the plugin code within the compiled main.wasm binary. 96 | // Execution with main.wasm will be skipped if the file cannot be found. 97 | func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { 98 | t.Helper() 99 | 100 | t.Run("go", func(t *testing.T) { 101 | f(t, &vmContext{}) 102 | }) 103 | 104 | t.Run("wasm", func(t *testing.T) { 105 | wasm, err := os.ReadFile("main.wasm") 106 | if err != nil { 107 | t.Skip("wasm not found") 108 | } 109 | v, err := proxytest.NewWasmVMContext(wasm) 110 | require.NoError(t, err) 111 | defer v.Close() 112 | f(t, v) 113 | }) 114 | } 115 | -------------------------------------------------------------------------------- /examples/properties/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "fmt" 9 | "os" 10 | "testing" 11 | 12 | "github.com/stretchr/testify/require" 13 | 14 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 15 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 16 | ) 17 | 18 | func TestProperties_OnHttpRequestHeaders(t *testing.T) { 19 | vmTest(t, func(t *testing.T, vm types.VMContext) { 20 | opt := proxytest.NewEmulatorOption().WithVMContext(vm) 21 | host, reset := proxytest.NewHostEmulator(opt) 22 | defer reset() 23 | 24 | t.Run("route is unauthenticated", func(t *testing.T) { 25 | // Initialize http context. 26 | id := host.InitializeHttpContext() 27 | 28 | // Call OnRequestHeaders. 29 | action := host.CallOnRequestHeaders(id, nil, false) 30 | require.Equal(t, types.ActionContinue, action) 31 | 32 | // Call OnHttpStreamDone. 33 | host.CompleteHttpContext(id) 34 | 35 | // Check Envoy logs. 36 | logs := host.GetInfoLogs() 37 | require.Contains(t, logs, "no auth header for route") 38 | require.Contains(t, logs, fmt.Sprintf("%d finished", id)) 39 | }) 40 | 41 | // Set property 42 | path := "auth" 43 | data := "cookie" 44 | err := host.SetProperty(append(propertyPrefix, path), []byte(data)) 45 | require.NoError(t, err) 46 | 47 | // Get property 48 | actualData, _ := host.GetProperty(append(propertyPrefix, path)) 49 | require.Equal(t, string(actualData), data) 50 | 51 | t.Run("user is authenticated", func(t *testing.T) { 52 | // Initialize http context. 53 | id := host.InitializeHttpContext() 54 | 55 | // Call OnRequestHeaders. 56 | action := host.CallOnRequestHeaders(id, [][2]string{ 57 | {"cookie", "value"}, 58 | }, false) 59 | require.Equal(t, types.ActionContinue, action) 60 | 61 | // Call OnHttpStreamDone. 62 | host.CompleteHttpContext(id) 63 | 64 | // Check Envoy logs. 65 | logs := host.GetInfoLogs() 66 | require.Contains(t, logs, fmt.Sprintf("auth header is \"%s\"", data)) 67 | require.Contains(t, logs, fmt.Sprintf("%d finished", id)) 68 | }) 69 | 70 | t.Run("user is unauthenticated", func(t *testing.T) { 71 | // Initialize http context. 72 | id := host.InitializeHttpContext() 73 | 74 | // Call OnRequestHeaders. 75 | action := host.CallOnRequestHeaders(id, nil, false) 76 | require.Equal(t, types.ActionPause, action) 77 | 78 | // Call OnHttpStreamDone. 79 | host.CompleteHttpContext(id) 80 | 81 | // Check the local response. 82 | localResponse := host.GetSentLocalResponse(id) 83 | require.NotNil(t, localResponse) 84 | require.Equal(t, uint32(401), localResponse.StatusCode) 85 | require.Nil(t, localResponse.Data) 86 | }) 87 | 88 | }) 89 | } 90 | 91 | // vmTest executes f twice, once with a types.VMContext that executes plugin code directly 92 | // in the host, and again by executing the plugin code within the compiled main.wasm binary. 93 | // Execution with main.wasm will be skipped if the file cannot be found. 94 | func vmTest(t *testing.T, f func(*testing.T, types.VMContext)) { 95 | t.Helper() 96 | 97 | t.Run("go", func(t *testing.T) { 98 | f(t, &vmContext{}) 99 | }) 100 | 101 | t.Run("wasm", func(t *testing.T) { 102 | wasm, err := os.ReadFile("main.wasm") 103 | if err != nil { 104 | t.Skip("wasm not found") 105 | } 106 | v, err := proxytest.NewWasmVMContext(wasm) 107 | require.NoError(t, err) 108 | defer v.Close() 109 | f(t, v) 110 | }) 111 | } 112 | -------------------------------------------------------------------------------- /proxywasm/types/types.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package types 16 | 17 | import "errors" 18 | 19 | // Action represents the action which Wasm contexts expects hosts to take. 20 | type Action uint32 21 | 22 | const ( 23 | // ActionContinue means that the host continues the processing. 24 | ActionContinue Action = 0 25 | // ActionPause means that the host pauses the processing. 26 | ActionPause Action = 1 27 | ) 28 | 29 | // PeerType represents the type of a peer of a connection. 30 | type PeerType uint32 31 | 32 | const ( 33 | // PeerTypeUnknown means the type of a peer is unknown 34 | PeerTypeUnknown PeerType = 0 35 | // PeerTypeLocal means the type of a peer is local (i.e. proxy) 36 | PeerTypeLocal PeerType = 1 37 | // PeerTypeRemote means the type of a peer is remote (i.e. remote client) 38 | PeerTypeRemote PeerType = 2 39 | ) 40 | 41 | // OnVMStartStatus is the type of status returned by OnVMStart 42 | type OnVMStartStatus bool 43 | 44 | const ( 45 | // OnVMStartStatusOK indicates that VMContext.OnVMStartStatus succeeded. 46 | OnVMStartStatusOK OnVMStartStatus = true 47 | // OnVMStartStatusFailed indicates that VMContext.OnVMStartStatus failed. 48 | // Further processing for this VM never happens, and hosts would 49 | // delete this VM. 50 | OnVMStartStatusFailed OnVMStartStatus = false 51 | ) 52 | 53 | // OnPluginStartStatus is the type of status returned by OnPluginStart 54 | type OnPluginStartStatus bool 55 | 56 | const ( 57 | // OnPluginStartStatusOK indicates that PluginContext.OnPluginStart succeeded. 58 | OnPluginStartStatusOK OnPluginStartStatus = true 59 | // OnPluginStartStatusFailed indicates that PluginContext.OnPluginStart failed. 60 | // Further processing for this plugin context never happens. 61 | OnPluginStartStatusFailed OnPluginStartStatus = false 62 | ) 63 | 64 | var ( 65 | // ErrorStatusNotFound means not found for various hostcalls. 66 | ErrorStatusNotFound = errors.New("error status returned by host: not found") 67 | // ErrorStatusBadArgument means the arguments for a hostcall are invalid. 68 | ErrorStatusBadArgument = errors.New("error status returned by host: bad argument") 69 | // ErrorStatusEmpty means the target queue of DequeueSharedQueue call is empty. 70 | ErrorStatusEmpty = errors.New("error status returned by host: empty") 71 | // ErrorStatusCasMismatch means the CAS value provided to the SetSharedData 72 | // does not match the current value. It indicates that other Wasm VMs 73 | // have already set a value for the same key, and the current CAS 74 | // for the key gets incremented. 75 | // Having retry logic in the face of this error is recommended. 76 | ErrorStatusCasMismatch = errors.New("error status returned by host: cas mismatch") 77 | // ErrorInternalFailure indicates an internal failure in hosts. 78 | // When this error occurs, there's nothing we could do in the Wasm VM. 79 | // Abort or panic after this error is recommended. 80 | ErrorInternalFailure = errors.New("error status returned by host: internal failure") 81 | // ErrorUnimplemented indicates the API is not implemented in the host yet. 82 | ErrorUnimplemented = errors.New("error status returned by host: unimplemented") 83 | ) 84 | -------------------------------------------------------------------------------- /examples/network/main_test.go: -------------------------------------------------------------------------------- 1 | // The framework emulates the expected behavior of Envoyproxy, and you can test your extensions without running Envoy and with 2 | // the standard Go CLI. To run tests, simply run 3 | // go test ./... 4 | 5 | package main 6 | 7 | import ( 8 | "testing" 9 | 10 | "github.com/stretchr/testify/require" 11 | 12 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/proxytest" 13 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 14 | ) 15 | 16 | func TestNetwork_OnNewConnection(t *testing.T) { 17 | opt := proxytest.NewEmulatorOption().WithVMContext(&vmContext{}) 18 | host, reset := proxytest.NewHostEmulator(opt) 19 | defer reset() 20 | 21 | // Initialize plugin 22 | require.Equal(t, types.OnPluginStartStatusOK, host.StartPlugin()) 23 | 24 | // OnNewConnection is called. 25 | _, action := host.InitializeConnection() 26 | require.Equal(t, types.ActionContinue, action) 27 | 28 | // Check Envoy logs. 29 | logs := host.GetInfoLogs() 30 | require.Contains(t, logs, "new connection!") 31 | } 32 | 33 | func TestNetwork_OnDownstreamClose(t *testing.T) { 34 | opt := proxytest.NewEmulatorOption().WithVMContext(&vmContext{}) 35 | host, reset := proxytest.NewHostEmulator(opt) 36 | defer reset() 37 | 38 | // OnNewConnection is called. 39 | contextID, action := host.InitializeConnection() 40 | require.Equal(t, types.ActionContinue, action) 41 | 42 | // OnDownstreamClose is called. 43 | host.CloseDownstreamConnection(contextID) 44 | 45 | // Check Envoy logs. 46 | logs := host.GetInfoLogs() 47 | require.Contains(t, logs, "downstream connection close!") 48 | } 49 | 50 | func TestNetwork_OnDownstreamData(t *testing.T) { 51 | opt := proxytest.NewEmulatorOption().WithVMContext(&vmContext{}) 52 | host, reset := proxytest.NewHostEmulator(opt) 53 | defer reset() 54 | 55 | // OnNewConnection is called. 56 | contextID, action := host.InitializeConnection() 57 | require.Equal(t, types.ActionContinue, action) 58 | 59 | // OnDownstreamData is called. 60 | msg := "this is downstream data" 61 | data := []byte(msg) 62 | host.CallOnDownstreamData(contextID, data) 63 | 64 | // Check Envoy logs. 65 | logs := host.GetInfoLogs() 66 | require.Contains(t, logs, ">>>>>> downstream data received >>>>>>\n"+msg) 67 | } 68 | 69 | func TestNetwork_OnUpstreamData(t *testing.T) { 70 | opt := proxytest.NewEmulatorOption().WithVMContext(&vmContext{}) 71 | host, reset := proxytest.NewHostEmulator(opt) 72 | defer reset() 73 | 74 | // OnNewConnection is called. 75 | contextID, action := host.InitializeConnection() 76 | require.Equal(t, types.ActionContinue, action) 77 | 78 | // OnUpstreamData is called. 79 | msg := "this is upstream data" 80 | data := []byte(msg) 81 | host.CallOnUpstreamData(contextID, data) 82 | 83 | // Check Envoy logs. 84 | logs := host.GetInfoLogs() 85 | require.Contains(t, logs, "<<<<<< upstream data received <<<<<<\n"+msg) 86 | } 87 | 88 | func TestNetwork_counter(t *testing.T) { 89 | opt := proxytest.NewEmulatorOption().WithVMContext(&vmContext{}) 90 | host, reset := proxytest.NewHostEmulator(opt) 91 | defer reset() 92 | 93 | // Call OnVMStart -> initialize metric 94 | require.Equal(t, types.OnVMStartStatusOK, host.StartVM()) 95 | 96 | // OnNewConnection is called. 97 | contextID, action := host.InitializeConnection() 98 | require.Equal(t, types.ActionContinue, action) 99 | 100 | // call OnStreamDone on contextID -> increment the connection counter. 101 | host.CompleteConnection(contextID) 102 | 103 | // Check Envoy logs. 104 | logs := host.GetInfoLogs() 105 | require.Contains(t, logs, "connection complete!") 106 | 107 | // Check counter metric. 108 | value, err := host.GetCounterMetric("proxy_wasm_go.connection_counter") 109 | require.NoError(t, err) 110 | require.Equal(t, uint64(1), value) 111 | } 112 | -------------------------------------------------------------------------------- /examples/multiple_dispatches/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "strconv" 19 | 20 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 21 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 22 | ) 23 | 24 | const clusterName = "httpbin" 25 | 26 | func main() { 27 | proxywasm.SetVMContext(&vmContext{}) 28 | } 29 | 30 | // vmContext implements types.VMContext. 31 | type vmContext struct { 32 | // Embed the default VM context here, 33 | // so that we don't need to reimplement all the methods. 34 | types.DefaultVMContext 35 | } 36 | 37 | // NewPluginContext implements types.VMContext. 38 | func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { 39 | return &pluginContext{} 40 | } 41 | 42 | // pluginContext implements types.PluginContext. 43 | type pluginContext struct { 44 | // Embed the default plugin context here, 45 | // so that we don't need to reimplement all the methods. 46 | types.DefaultPluginContext 47 | } 48 | 49 | // NewHttpContext implements types.PluginContext. 50 | func (*pluginContext) NewHttpContext(contextID uint32) types.HttpContext { 51 | return &httpContext{contextID: contextID} 52 | } 53 | 54 | // httpContext implements types.HttpContext. 55 | type httpContext struct { 56 | // Embed the default http context here, 57 | // so that we don't need to reimplement all the methods. 58 | types.DefaultHttpContext 59 | // contextID is the unique identifier assigned to each httpContext. 60 | contextID uint32 61 | // pendingDispatchedRequest is the number of pending dispatched requests. 62 | pendingDispatchedRequest int 63 | } 64 | 65 | const totalDispatchNum = 10 66 | 67 | // OnHttpResponseHeaders implements types.HttpContext. 68 | func (ctx *httpContext) OnHttpResponseHeaders(numHeaders int, endOfStream bool) types.Action { 69 | // On each request response, we dispatch the http calls `totalDispatchNum` times. 70 | // Note: DispatchHttpCall is asynchronously processed, so each loop is non-blocking. 71 | for i := 0; i < totalDispatchNum; i++ { 72 | if _, err := proxywasm.DispatchHttpCall(clusterName, [][2]string{ 73 | {":path", "/"}, 74 | {":method", "GET"}, 75 | {":authority", ""}}, 76 | nil, nil, 50000, ctx.dispatchCallback); err != nil { 77 | panic(err) 78 | } 79 | // Now we have made a dispatched request, so we record it. 80 | ctx.pendingDispatchedRequest++ 81 | } 82 | return types.ActionPause 83 | } 84 | 85 | // dispatchCallback is the callback function called in response to the response arrival from the dispatched request. 86 | func (ctx *httpContext) dispatchCallback(numHeaders, bodySize, numTrailers int) { 87 | // Decrement the pending request counter. 88 | ctx.pendingDispatchedRequest-- 89 | if ctx.pendingDispatchedRequest == 0 { 90 | // This case, all the dispatched request was processed. 91 | // Adds a response header to the original response. 92 | _ = proxywasm.AddHttpResponseHeader("total-dispatched", strconv.Itoa(totalDispatchNum)) 93 | // And then contniue the original reponse. 94 | _ = proxywasm.ResumeHttpResponse() 95 | proxywasm.LogInfof("response resumed after processed %d dispatched request", totalDispatchNum) 96 | } else { 97 | proxywasm.LogInfof("pending dispatched requests: %d", ctx.pendingDispatchedRequest) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /proxywasm/internal/abi_callback_test_export.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build !tinygo 16 | 17 | package internal 18 | 19 | import "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 20 | 21 | // this file exists only for proxytest package, therefore, these functions are not included in a resulting Wasm binary. 22 | 23 | func ProxyOnVMStart(pluginContextID uint32, vmConfigurationSize int) types.OnVMStartStatus { 24 | return proxyOnVMStart(pluginContextID, vmConfigurationSize) 25 | } 26 | 27 | func ProxyOnConfigure(pluginContextID uint32, vmConfigurationSize int) types.OnPluginStartStatus { 28 | return proxyOnConfigure(pluginContextID, vmConfigurationSize) 29 | } 30 | 31 | func ProxyOnNewConnection(contextID uint32) types.Action { 32 | return proxyOnNewConnection(contextID) 33 | } 34 | 35 | func ProxyOnDownstreamData(contextID uint32, dataSize int, endOfStream bool) types.Action { 36 | return proxyOnDownstreamData(contextID, dataSize, endOfStream) 37 | } 38 | 39 | func ProxyOnDownstreamConnectionClose(contextID uint32, pType types.PeerType) { 40 | proxyOnDownstreamConnectionClose(contextID, pType) 41 | } 42 | 43 | func ProxyOnUpstreamData(contextID uint32, dataSize int, endOfStream bool) types.Action { 44 | return proxyOnUpstreamData(contextID, dataSize, endOfStream) 45 | } 46 | 47 | func ProxyOnUpstreamConnectionClose(contextID uint32, pType types.PeerType) { 48 | proxyOnUpstreamConnectionClose(contextID, pType) 49 | } 50 | 51 | func ProxyOnRequestHeaders(contextID uint32, numHeaders int, endOfStream bool) types.Action { 52 | return proxyOnRequestHeaders(contextID, numHeaders, endOfStream) 53 | } 54 | 55 | func ProxyOnRequestBody(contextID uint32, bodySize int, endOfStream bool) types.Action { 56 | return proxyOnRequestBody(contextID, bodySize, endOfStream) 57 | } 58 | 59 | func ProxyOnRequestTrailers(contextID uint32, numTrailers int) types.Action { 60 | return proxyOnRequestTrailers(contextID, numTrailers) 61 | } 62 | 63 | func ProxyOnResponseHeaders(contextID uint32, numHeaders int, endOfStream bool) types.Action { 64 | return proxyOnResponseHeaders(contextID, numHeaders, endOfStream) 65 | } 66 | 67 | func ProxyOnResponseBody(contextID uint32, bodySize int, endOfStream bool) types.Action { 68 | return proxyOnResponseBody(contextID, bodySize, endOfStream) 69 | } 70 | 71 | func ProxyOnResponseTrailers(contextID uint32, numTrailers int) types.Action { 72 | return proxyOnResponseTrailers(contextID, numTrailers) 73 | } 74 | 75 | func ProxyOnHttpCallResponse(pluginContextID, calloutID uint32, numHeaders, bodySize, numTrailers int) { 76 | proxyOnHttpCallResponse(pluginContextID, calloutID, numHeaders, bodySize, numTrailers) 77 | } 78 | 79 | func ProxyOnContextCreate(contextID uint32, pluginContextID uint32) { 80 | proxyOnContextCreate(contextID, pluginContextID) 81 | } 82 | 83 | func ProxyOnDone(contextID uint32) bool { 84 | return proxyOnDone(contextID) 85 | } 86 | 87 | func ProxyOnLog(pluginContextID uint32) { 88 | proxyOnLog(pluginContextID) 89 | } 90 | 91 | func ProxyOnQueueReady(contextID, queueID uint32) { 92 | proxyOnQueueReady(contextID, queueID) 93 | } 94 | 95 | func ProxyOnTick(pluginContextID uint32) { 96 | proxyOnTick(pluginContextID) 97 | } 98 | 99 | func ProxyOnDelete(contextID uint32) { 100 | proxyOnDelete(contextID) 101 | } 102 | -------------------------------------------------------------------------------- /examples/http_routing/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package main 16 | 17 | import ( 18 | "crypto/rand" 19 | "encoding/binary" 20 | 21 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 22 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 23 | ) 24 | 25 | func main() { 26 | proxywasm.SetVMContext(&vmContext{}) 27 | } 28 | 29 | // vmContext implements types.VMContext. 30 | type vmContext struct { 31 | // Embed the default VM context here, 32 | // so that we don't need to reimplement all the methods. 33 | types.DefaultVMContext 34 | } 35 | 36 | // NewPluginContext implements types.VMContext. 37 | func (*vmContext) NewPluginContext(contextID uint32) types.PluginContext { 38 | return &pluginContext{} 39 | } 40 | 41 | // pluginContext implements types.PluginContext. 42 | type pluginContext struct { 43 | // Embed the default plugin context here, 44 | // so that we don't need to reimplement all the methods. 45 | types.DefaultPluginContext 46 | 47 | diceOverride uint32 // For unit test 48 | } 49 | 50 | // OnPluginStart implements types.PluginContext. 51 | func (ctx *pluginContext) OnPluginStart(pluginConfigurationSize int) types.OnPluginStartStatus { 52 | data, err := proxywasm.GetPluginConfiguration() 53 | if err != nil && err != types.ErrorStatusNotFound { 54 | proxywasm.LogCriticalf("error reading plugin configuration: %v", err) 55 | return types.OnPluginStartStatusFailed 56 | } 57 | 58 | // If the configuration data is not empty, we use its value to override the routing 59 | // decision for unit tests. 60 | if len(data) > 0 { 61 | ctx.diceOverride = uint32(data[0]) 62 | } 63 | 64 | return types.OnPluginStartStatusOK 65 | } 66 | 67 | // NewHttpContext implements types.PluginContext. 68 | func (ctx *pluginContext) NewHttpContext(contextID uint32) types.HttpContext { 69 | return &httpRouting{diceOverride: ctx.diceOverride} 70 | } 71 | 72 | // httpRouting implements types.HttpContext. 73 | type httpRouting struct { 74 | // Embed the default http context here, 75 | // so that we don't need to reimplement all the methods. 76 | types.DefaultHttpContext 77 | 78 | diceOverride uint32 // For unit test 79 | } 80 | 81 | // dice returns a random value to be used to determine the route. 82 | func dice() uint32 { 83 | buf := make([]byte, 4) 84 | _, _ = rand.Read(buf) 85 | return binary.LittleEndian.Uint32(buf) 86 | } 87 | 88 | // OnHttpRequestHeaders implements types.HttpContext. 89 | func (ctx *httpRouting) OnHttpRequestHeaders(numHeaders int, endOfStream bool) types.Action { 90 | // Randomly routing to the canary cluster. 91 | var value uint32 92 | if ctx.diceOverride != 0 { 93 | value = ctx.diceOverride 94 | } else { 95 | value = dice() 96 | } 97 | proxywasm.LogInfof("value: %d\n", value) 98 | if value%2 == 0 { 99 | const authorityKey = ":authority" 100 | value, err := proxywasm.GetHttpRequestHeader(authorityKey) 101 | if err != nil { 102 | proxywasm.LogCritical("failed to get request header: ':authority'") 103 | return types.ActionPause 104 | } 105 | // Append "-canary" suffix to route this request to the canary cluster. 106 | value += "-canary" 107 | if err := proxywasm.ReplaceHttpRequestHeader(":authority", value); err != nil { 108 | proxywasm.LogCritical("failed to set request header: test") 109 | return types.ActionPause 110 | } 111 | } 112 | return types.ActionContinue 113 | } 114 | -------------------------------------------------------------------------------- /properties/util.go: -------------------------------------------------------------------------------- 1 | package properties 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm" 7 | ) 8 | 9 | // getIstioFilterMetadata parses istio filter metadata 10 | func getIstioFilterMetadata(path []string) (IstioFilterMetadata, error) { 11 | result := IstioFilterMetadata{} 12 | 13 | config, err := getPropertyString(append(path, "config")) 14 | if err != nil { 15 | return IstioFilterMetadata{}, nil 16 | } 17 | result.Config = config 18 | 19 | services, err := getPropertyByteSliceSlice(append(path, "services")) 20 | if err != nil || services == nil { 21 | return result, nil 22 | } 23 | 24 | for _, service := range services { 25 | if service == nil { 26 | continue 27 | } 28 | istioService := IstioService{} 29 | istioServiceMap := deserializeStringMap(service) 30 | 31 | if host, ok := istioServiceMap["host"]; ok { 32 | istioService.Host = host 33 | } 34 | if name, ok := istioServiceMap["name"]; ok { 35 | istioService.Name = name 36 | } 37 | if namespace, ok := istioServiceMap["namespace"]; ok { 38 | istioService.Namespace = namespace 39 | } 40 | 41 | result.Services = append(result.Services, istioService) 42 | } 43 | 44 | return result, nil 45 | } 46 | 47 | // getPropertyBool returns a bool property. 48 | func getPropertyBool(path []string) (bool, error) { 49 | bs, err := proxywasm.GetProperty(path) 50 | if err != nil { 51 | return false, err 52 | } 53 | 54 | return deserializeBool(bs) 55 | } 56 | 57 | // getPropertyByteSliceMap retrieves a complex property object as a map of byte slices. 58 | // to be used when dealing with mixed type properties 59 | func getPropertyByteSliceMap(path []string) (map[string][]byte, error) { //nolint:unused 60 | bs, err := proxywasm.GetProperty(path) 61 | if err != nil { 62 | return nil, err 63 | } 64 | 65 | return deserializeByteSliceMap(bs), nil 66 | } 67 | 68 | // getPropertyByteSliceSlice retrieves a complex property object as a string slice. 69 | func getPropertyByteSliceSlice(path []string) ([][]byte, error) { 70 | bs, err := proxywasm.GetProperty(path) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return deserializeByteSliceSlice(bs), nil 75 | } 76 | 77 | // getPropertyFloat64 returns a float64 property. 78 | func getPropertyFloat64(path []string) (float64, error) { 79 | bs, err := proxywasm.GetProperty(path) 80 | if err != nil { 81 | return 0, err 82 | } 83 | 84 | return deserializeFloat64(bs), nil 85 | } 86 | 87 | // getPropertyString returns a string property. 88 | func getPropertyString(path []string) (string, error) { 89 | bs, err := proxywasm.GetProperty(path) 90 | if err != nil { 91 | return "", err 92 | } 93 | return string(bs), nil 94 | } 95 | 96 | // getPropertyStringMap retrieves a complex property object as a map of string 97 | // to be used when dealing with string only type properties. 98 | func getPropertyStringMap(path []string) (map[string]string, error) { 99 | bs, err := proxywasm.GetProperty(path) 100 | if err != nil { 101 | return nil, err 102 | } 103 | 104 | return deserializeStringMap(bs), nil 105 | } 106 | 107 | // getPropertyStringSlice retrieves a complex property object as a string slice. 108 | func getPropertyStringSlice(path []string) ([]string, error) { 109 | bs, err := proxywasm.GetProperty(path) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | return deserializeStringSlice(bs), nil 115 | } 116 | 117 | // getPropertyTimestamp returns a timestamp property. 118 | func getPropertyTimestamp(path []string) (time.Time, error) { 119 | bs, err := proxywasm.GetProperty(path) 120 | if err != nil { 121 | return time.Now().UTC(), err 122 | } 123 | 124 | return deserializeTimestamp(bs).UTC(), nil 125 | } 126 | 127 | // getPropertyUint64 returns a uint64 property. 128 | func getPropertyUint64(path []string) (uint64, error) { 129 | bs, err := proxywasm.GetProperty(path) 130 | if err != nil { 131 | return 0, err 132 | } 133 | 134 | return deserializeUint64(bs), nil 135 | } 136 | -------------------------------------------------------------------------------- /examples/http_headers/envoy.yaml: -------------------------------------------------------------------------------- 1 | static_resources: 2 | listeners: 3 | - name: main 4 | address: 5 | socket_address: 6 | address: 0.0.0.0 7 | port_value: 18000 8 | filter_chains: 9 | - filters: 10 | - name: envoy.http_connection_manager 11 | typed_config: 12 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 13 | stat_prefix: ingress_http 14 | codec_type: auto 15 | route_config: 16 | name: local_route 17 | virtual_hosts: 18 | - name: local_service 19 | domains: 20 | - "*" 21 | routes: 22 | - match: 23 | prefix: "/" 24 | route: 25 | cluster: web_service 26 | http_filters: 27 | - name: envoy.filters.http.wasm 28 | typed_config: 29 | "@type": type.googleapis.com/udpa.type.v1.TypedStruct 30 | type_url: type.googleapis.com/envoy.extensions.filters.http.wasm.v3.Wasm 31 | value: 32 | config: 33 | configuration: 34 | "@type": type.googleapis.com/google.protobuf.StringValue 35 | value: | 36 | { 37 | "header": "x-wasm-header", 38 | "value": "demo-wasm" 39 | } 40 | vm_config: 41 | runtime: "envoy.wasm.runtime.v8" 42 | code: 43 | local: 44 | filename: "./examples/http_headers/main.wasm" 45 | - name: envoy.filters.http.router 46 | typed_config: 47 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 48 | 49 | - name: staticreply 50 | address: 51 | socket_address: 52 | address: 127.0.0.1 53 | port_value: 8099 54 | filter_chains: 55 | - filters: 56 | - name: envoy.http_connection_manager 57 | typed_config: 58 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager 59 | stat_prefix: ingress_http 60 | codec_type: auto 61 | route_config: 62 | name: local_route 63 | virtual_hosts: 64 | - name: local_service 65 | domains: 66 | - "*" 67 | routes: 68 | - match: 69 | prefix: "/" 70 | direct_response: 71 | status: 200 72 | body: 73 | inline_string: "example body\n" 74 | http_filters: 75 | - name: envoy.filters.http.router 76 | typed_config: 77 | "@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router 78 | 79 | clusters: 80 | - name: web_service 81 | connect_timeout: 0.25s 82 | type: STATIC 83 | lb_policy: ROUND_ROBIN 84 | load_assignment: 85 | cluster_name: mock_service 86 | endpoints: 87 | - lb_endpoints: 88 | - endpoint: 89 | address: 90 | socket_address: 91 | address: 127.0.0.1 92 | port_value: 8099 93 | 94 | admin: 95 | access_log_path: "/dev/null" 96 | address: 97 | socket_address: 98 | address: 0.0.0.0 99 | port_value: 8001 100 | -------------------------------------------------------------------------------- /properties/request.go: -------------------------------------------------------------------------------- 1 | package properties 2 | 3 | import "time" 4 | 5 | // This file hosts helper functions to retrieve request-related properties as described in: 6 | // https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/advanced/attributes#request-attributes 7 | 8 | var ( 9 | requestPath = []string{"request", "path"} 10 | requestUrlPath = []string{"request", "url_path"} 11 | requestHost = []string{"request", "host"} 12 | requestScheme = []string{"request", "scheme"} 13 | requestMethod = []string{"request", "method"} 14 | requestHeaders = []string{"request", "headers"} 15 | requestReferer = []string{"request", "referer"} 16 | requestUserAgent = []string{"request", "useragent"} 17 | requestTime = []string{"request", "time"} 18 | requestId = []string{"request", "id"} 19 | requestProtocol = []string{"request", "protocol"} 20 | requestQuery = []string{"request", "query"} 21 | requestDuration = []string{"request", "duration"} 22 | requestSize = []string{"request", "size"} 23 | requestTotalSize = []string{"request", "total_size"} 24 | ) 25 | 26 | // GetRequestPath return the path portion of the URL. 27 | func GetRequestPath() (string, error) { 28 | return getPropertyString(requestPath) 29 | } 30 | 31 | // GetRequestUrlPath returns the path portion of the URL without the query string. 32 | func GetRequestUrlPath() (string, error) { 33 | return getPropertyString(requestUrlPath) 34 | } 35 | 36 | // GetRequestHost returns the host portion of the URL. 37 | func GetRequestHost() (string, error) { 38 | return getPropertyString(requestHost) 39 | } 40 | 41 | // GetRequestScheme returns the scheme portion of the URL e.g. “http”. 42 | func GetRequestScheme() (string, error) { 43 | return getPropertyString(requestScheme) 44 | } 45 | 46 | // GetRequestMethod returns the request method e.g. “GET”. 47 | func GetRequestMethod() (string, error) { 48 | return getPropertyString(requestMethod) 49 | } 50 | 51 | // GetRequestHeaders returns all request headers indexed by the lower-cased header name. 52 | func GetRequestHeaders() (map[string]string, error) { 53 | return getPropertyStringMap(requestHeaders) 54 | } 55 | 56 | // GetRequestReferer returns the referer request header. 57 | func GetRequestReferer() (string, error) { 58 | return getPropertyString(requestReferer) 59 | } 60 | 61 | // GetRequestUserAgent returns the user agent request header. 62 | func GetRequestUserAgent() (string, error) { 63 | return getPropertyString(requestUserAgent) 64 | } 65 | 66 | // GetRequestTime returns the UTC time of the first byte received, approximated to nano-seconds. 67 | func GetRequestTime() (time.Time, error) { 68 | result, err := getPropertyTimestamp(requestTime) 69 | if err != nil { 70 | return time.Now(), err 71 | } 72 | return result, nil 73 | } 74 | 75 | // GetRequestId returns the request ID corresponding to x-request-id header value. 76 | func GetRequestId() (string, error) { 77 | return getPropertyString(requestId) 78 | } 79 | 80 | // GetRequestProtocol returns the request protocol (“HTTP/1.0”, “HTTP/1.1”, “HTTP/2”, or “HTTP/3”). 81 | func GetRequestProtocol() (string, error) { 82 | return getPropertyString(requestProtocol) 83 | } 84 | 85 | // GetRequestQuery returns the query portion of the URL in the format of “name1=value1&name2=value2”. 86 | func GetRequestQuery() (string, error) { 87 | return getPropertyString(requestQuery) 88 | } 89 | 90 | // GetRequestDuration returns the total duration of the request, approximated to nano-seconds. 91 | func GetRequestDuration() (uint64, error) { 92 | return getPropertyUint64(requestDuration) 93 | } 94 | 95 | // GetRequestSize returns the size of the request body. Content length header is used if available. 96 | func GetRequestSize() (uint64, error) { 97 | return getPropertyUint64(requestSize) 98 | } 99 | 100 | // GetRequestTotalSize returns the total size of the request including the approximate uncompressed size of the headers. 101 | func GetRequestTotalSize() (uint64, error) { 102 | return getPropertyUint64(requestTotalSize) 103 | } 104 | -------------------------------------------------------------------------------- /proxywasm/internal/vmstate.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020-2024 Tetrate 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package internal 16 | 17 | import ( 18 | "github.com/tetratelabs/proxy-wasm-go-sdk/proxywasm/types" 19 | ) 20 | 21 | type ( 22 | pluginContextState struct { 23 | context types.PluginContext 24 | httpCallbacks map[uint32]*httpCallbackAttribute 25 | } 26 | 27 | httpCallbackAttribute struct { 28 | callback func(numHeaders, bodySize, numTrailers int) 29 | callerContextID uint32 30 | } 31 | ) 32 | 33 | type state struct { 34 | vmContext types.VMContext 35 | pluginContexts map[uint32]*pluginContextState 36 | httpContexts map[uint32]types.HttpContext 37 | tcpContexts map[uint32]types.TcpContext 38 | 39 | contextIDToRootID map[uint32]uint32 40 | activeContextID uint32 41 | } 42 | 43 | var currentState = &state{ 44 | pluginContexts: make(map[uint32]*pluginContextState), 45 | httpContexts: make(map[uint32]types.HttpContext), 46 | tcpContexts: make(map[uint32]types.TcpContext), 47 | contextIDToRootID: make(map[uint32]uint32), 48 | } 49 | 50 | func SetVMContext(vmContext types.VMContext) { 51 | currentState.vmContext = vmContext 52 | } 53 | 54 | func RegisterHttpCallout(calloutID uint32, callback func(numHeaders, bodySize, numTrailers int)) { 55 | currentState.registerHttpCallOut(calloutID, callback) 56 | } 57 | 58 | func (s *state) createPluginContext(contextID uint32) { 59 | ctx := s.vmContext.NewPluginContext(contextID) 60 | s.pluginContexts[contextID] = &pluginContextState{ 61 | context: ctx, 62 | httpCallbacks: map[uint32]*httpCallbackAttribute{}, 63 | } 64 | 65 | // NOTE: this is a temporary work around for avoiding nil pointer panic 66 | // when users make http dispatch(es) on PluginContext. 67 | // See https://github.com/tetratelabs/proxy-wasm-go-sdk/issues/110 68 | // TODO: refactor 69 | s.contextIDToRootID[contextID] = contextID 70 | } 71 | 72 | func (s *state) createTcpContext(contextID uint32, pluginContextID uint32) bool { 73 | root, ok := s.pluginContexts[pluginContextID] 74 | if !ok { 75 | panic("invalid plugin context id") 76 | } 77 | 78 | if _, ok := s.tcpContexts[contextID]; ok { 79 | panic("context id duplicated") 80 | } 81 | 82 | ctx := root.context.NewTcpContext(contextID) 83 | if ctx == nil { 84 | // NewTcpContext is not defined by the user 85 | return false 86 | } 87 | s.contextIDToRootID[contextID] = pluginContextID 88 | s.tcpContexts[contextID] = ctx 89 | return true 90 | } 91 | 92 | func (s *state) createHttpContext(contextID uint32, pluginContextID uint32) bool { 93 | root, ok := s.pluginContexts[pluginContextID] 94 | if !ok { 95 | panic("invalid plugin context id") 96 | } 97 | 98 | if _, ok := s.httpContexts[contextID]; ok { 99 | panic("context id duplicated") 100 | } 101 | 102 | ctx := root.context.NewHttpContext(contextID) 103 | if ctx == nil { 104 | // NewHttpContext is not defined by the user 105 | return false 106 | } 107 | s.contextIDToRootID[contextID] = pluginContextID 108 | s.httpContexts[contextID] = ctx 109 | return true 110 | } 111 | 112 | func (s *state) registerHttpCallOut(calloutID uint32, callback func(numHeaders, bodySize, numTrailers int)) { 113 | r := s.pluginContexts[s.contextIDToRootID[s.activeContextID]] 114 | r.httpCallbacks[calloutID] = &httpCallbackAttribute{callback: callback, callerContextID: s.activeContextID} 115 | } 116 | 117 | func (s *state) setActiveContextID(contextID uint32) { 118 | s.activeContextID = contextID 119 | } 120 | --------------------------------------------------------------------------------