From 9e1c3487d1664f6d2fd687b4ea08ce3b3730b2c5 Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Tue, 22 Oct 2024 11:38:12 -0400 Subject: [PATCH 1/2] Use RFC-compliant HeaderValue type. When "header-value" feature is enabled, a RFC-compliant HeaderValue type is used instead of the previously used UTF-8 String type. Since HTTP field values aren't UTF-8 encoded, using the String type meant that a plugin would crash when valid, but obsolete, non-UTF-8 characters were present in HTTP headers and/or trailers. This feature is currently optional to avoid breaking changes and to help with migration, but it will become a default feature in v0.3. The HeaderValue type is re-exported from the http crate. Signed-off-by: Piotr Sikora --- .github/workflows/rust.yml | 20 +++ BUILD | 29 ++++ Cargo.toml | 5 + README.md | 7 + bazel/cargo/Cargo.Bazel.lock | 30 ++++ bazel/cargo/remote/BUILD.bazel | 6 + bazel/cargo/remote/BUILD.bytes-1.8.0.bazel | 85 +++++++++ bazel/cargo/remote/BUILD.fnv-1.0.7.bazel | 85 +++++++++ bazel/cargo/remote/BUILD.http-1.1.0.bazel | 90 ++++++++++ bazel/cargo/remote/BUILD.itoa-1.0.11.bazel | 81 +++++++++ bazel/cargo/remote/defs.bzl | 42 +++++ examples/grpc_auth_random/Cargo.toml | 2 +- examples/grpc_auth_random/src/lib.rs | 8 +- examples/http_headers/Cargo.toml | 2 +- examples/http_headers/src/lib.rs | 19 ++- src/hostcalls.rs | 189 +++++++++++++-------- src/traits.rs | 168 ++++++++++++++++++ src/types.rs | 3 + 18 files changed, 791 insertions(+), 80 deletions(-) create mode 100644 bazel/cargo/remote/BUILD.bytes-1.8.0.bazel create mode 100644 bazel/cargo/remote/BUILD.fnv-1.0.7.bazel create mode 100644 bazel/cargo/remote/BUILD.http-1.1.0.bazel create mode 100644 bazel/cargo/remote/BUILD.itoa-1.0.11.bazel diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index da9683f9..757fb6bf 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -94,7 +94,9 @@ jobs: - name: Format (rules_rust) run: | + sed -i'' -E 's/^default = \[\]/default = \[\"header-value\"\]/' Cargo.toml bazelisk --noworkspace_rc run --noenable_bzlmod //bazel/cargo:crates_vendor + git checkout Cargo.toml git diff --exit-code msrv: @@ -148,6 +150,12 @@ jobs: - name: Clippy (wasm32-wasi) run: cargo clippy --release --all-targets --target=wasm32-wasi + - name: Build (header-value) + run: cargo build --release --all-targets --target=wasm32-wasi --features header-value + + - name: Clippy (header-value) + run: cargo clippy --release --all-targets --target=wasm32-wasi --features header-value + - name: Format (rustfmt) run: cargo fmt -- --check @@ -210,6 +218,12 @@ jobs: - name: Clippy (wasm32-wasip1) run: cargo clippy --release --all-targets --target=wasm32-wasip1 + - name: Build (header-value) + run: cargo build --release --all-targets --target=wasm32-wasip1 --features header-value + + - name: Clippy (header-value) + run: cargo clippy --release --all-targets --target=wasm32-wasip1 --features header-value + - name: Format (rustfmt) run: cargo fmt -- --check @@ -273,6 +287,12 @@ jobs: - name: Clippy (wasm32-wasip1) run: cargo clippy --release --all-targets --target=wasm32-wasip1 + - name: Build (header-value) + run: cargo build --release --all-targets --target=wasm32-wasip1 --features header-value + + - name: Clippy (header-value) + run: cargo clippy --release --all-targets --target=wasm32-wasip1 --features header-value + - name: Format (rustfmt) run: cargo fmt -- --check diff --git a/BUILD b/BUILD index 7f611e4f..6af16d0d 100644 --- a/BUILD +++ b/BUILD @@ -39,6 +39,21 @@ rust_library( ], ) +rust_library( + name = "proxy_wasm_header_value", + srcs = glob(["src/*.rs"]), + crate_features = ["header-value"], + crate_name = "proxy_wasm", + edition = "2018", + visibility = ["//visibility:public"], + deps = [ + ":proxy_wasm_build_script", + "//bazel/cargo/remote:hashbrown", + "//bazel/cargo/remote:http", + "//bazel/cargo/remote:log", + ], +) + rust_binary( name = "http_auth_random", srcs = ["examples/http_auth_random/src/lib.rs"], @@ -52,3 +67,17 @@ rust_binary( "//bazel/cargo/remote:log", ], ) + +rust_binary( + name = "grpc_auth_random", + srcs = ["examples/grpc_auth_random/src/lib.rs"], + crate_type = "cdylib", + edition = "2018", + out_binary = True, + rustc_flags = ["-Cstrip=debuginfo"], + visibility = ["//visibility:private"], + deps = [ + ":proxy_wasm_header_value", + "//bazel/cargo/remote:log", + ], +) diff --git a/Cargo.toml b/Cargo.toml index 24193eb8..a52f0d8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,13 @@ build = "build.rs" [dependencies] hashbrown = "0.15" +http = { version = "1", optional = true } log = "0.4" +[features] +default = [] +header-value = ["dep:http"] + [profile.release] lto = true opt-level = 3 diff --git a/README.md b/README.md index 7db3cdc2..404499d8 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,13 @@ [license-badge]: https://img.shields.io/github/license/proxy-wasm/proxy-wasm-rust-sdk [license-link]: https://github.com/proxy-wasm/proxy-wasm-rust-sdk/blob/main/LICENSE +## Crate features + +This crate supports the following optional features: + +- `header-value` - uses RFC-compliant `HeaderValue` instead of UTF-8 `String` for HTTP header and trailer values. + This will become the default in future releases. + ## Examples - [Hello World](./examples/hello_world/) diff --git a/bazel/cargo/Cargo.Bazel.lock b/bazel/cargo/Cargo.Bazel.lock index f4d451ea..367a120e 100644 --- a/bazel/cargo/Cargo.Bazel.lock +++ b/bazel/cargo/Cargo.Bazel.lock @@ -8,12 +8,24 @@ version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" +[[package]] +name = "bytes" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da" + [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.3" @@ -31,6 +43,23 @@ dependencies = [ "foldhash", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + [[package]] name = "log" version = "0.4.22" @@ -42,5 +71,6 @@ name = "proxy-wasm" version = "0.2.3-dev" dependencies = [ "hashbrown", + "http", "log", ] diff --git a/bazel/cargo/remote/BUILD.bazel b/bazel/cargo/remote/BUILD.bazel index f62c1da8..0e16b637 100644 --- a/bazel/cargo/remote/BUILD.bazel +++ b/bazel/cargo/remote/BUILD.bazel @@ -37,6 +37,12 @@ alias( tags = ["manual"], ) +alias( + name = "http", + actual = "@crates_vendor__http-1.1.0//:http", + tags = ["manual"], +) + alias( name = "log", actual = "@crates_vendor__log-0.4.22//:log", diff --git a/bazel/cargo/remote/BUILD.bytes-1.8.0.bazel b/bazel/cargo/remote/BUILD.bytes-1.8.0.bazel new file mode 100644 index 00000000..efac2269 --- /dev/null +++ b/bazel/cargo/remote/BUILD.bytes-1.8.0.bazel @@ -0,0 +1,85 @@ +############################################################################### +# @generated +# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To +# regenerate this file, run the following: +# +# bazel run @//bazel/cargo:crates_vendor +############################################################################### + +load("@rules_rust//rust:defs.bzl", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "bytes", + srcs = glob( + include = ["**/*.rs"], + allow_empty = True, + ), + compile_data = glob( + include = ["**"], + allow_empty = True, + exclude = [ + "**/* *", + ".tmp_git_root/**/*", + "BUILD", + "BUILD.bazel", + "WORKSPACE", + "WORKSPACE.bazel", + ], + ), + crate_features = [ + "default", + "std", + ], + crate_root = "src/lib.rs", + edition = "2018", + rustc_flags = [ + "--cap-lints=allow", + ], + tags = [ + "cargo-bazel", + "crate-name=bytes", + "manual", + "noclippy", + "norustfmt", + ], + target_compatible_with = select({ + "@rules_rust//rust/platform:aarch64-apple-darwin": [], + "@rules_rust//rust/platform:aarch64-apple-ios": [], + "@rules_rust//rust/platform:aarch64-apple-ios-sim": [], + "@rules_rust//rust/platform:aarch64-fuchsia": [], + "@rules_rust//rust/platform:aarch64-linux-android": [], + "@rules_rust//rust/platform:aarch64-pc-windows-msvc": [], + "@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [], + "@rules_rust//rust/platform:aarch64-unknown-nixos-gnu": [], + "@rules_rust//rust/platform:aarch64-unknown-nto-qnx710": [], + "@rules_rust//rust/platform:arm-unknown-linux-gnueabi": [], + "@rules_rust//rust/platform:armv7-linux-androideabi": [], + "@rules_rust//rust/platform:armv7-unknown-linux-gnueabi": [], + "@rules_rust//rust/platform:i686-apple-darwin": [], + "@rules_rust//rust/platform:i686-linux-android": [], + "@rules_rust//rust/platform:i686-pc-windows-msvc": [], + "@rules_rust//rust/platform:i686-unknown-freebsd": [], + "@rules_rust//rust/platform:i686-unknown-linux-gnu": [], + "@rules_rust//rust/platform:powerpc-unknown-linux-gnu": [], + "@rules_rust//rust/platform:riscv32imc-unknown-none-elf": [], + "@rules_rust//rust/platform:riscv64gc-unknown-none-elf": [], + "@rules_rust//rust/platform:s390x-unknown-linux-gnu": [], + "@rules_rust//rust/platform:thumbv7em-none-eabi": [], + "@rules_rust//rust/platform:thumbv8m.main-none-eabi": [], + "@rules_rust//rust/platform:wasm32-unknown-unknown": [], + "@rules_rust//rust/platform:wasm32-wasi": [], + "@rules_rust//rust/platform:x86_64-apple-darwin": [], + "@rules_rust//rust/platform:x86_64-apple-ios": [], + "@rules_rust//rust/platform:x86_64-fuchsia": [], + "@rules_rust//rust/platform:x86_64-linux-android": [], + "@rules_rust//rust/platform:x86_64-pc-windows-msvc": [], + "@rules_rust//rust/platform:x86_64-unknown-freebsd": [], + "@rules_rust//rust/platform:x86_64-unknown-linux-gnu": [], + "@rules_rust//rust/platform:x86_64-unknown-nixos-gnu": [], + "@rules_rust//rust/platform:x86_64-unknown-none": [], + "//conditions:default": ["@platforms//:incompatible"], + }), + version = "1.8.0", +) diff --git a/bazel/cargo/remote/BUILD.fnv-1.0.7.bazel b/bazel/cargo/remote/BUILD.fnv-1.0.7.bazel new file mode 100644 index 00000000..39a1f179 --- /dev/null +++ b/bazel/cargo/remote/BUILD.fnv-1.0.7.bazel @@ -0,0 +1,85 @@ +############################################################################### +# @generated +# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To +# regenerate this file, run the following: +# +# bazel run @//bazel/cargo:crates_vendor +############################################################################### + +load("@rules_rust//rust:defs.bzl", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "fnv", + srcs = glob( + include = ["**/*.rs"], + allow_empty = True, + ), + compile_data = glob( + include = ["**"], + allow_empty = True, + exclude = [ + "**/* *", + ".tmp_git_root/**/*", + "BUILD", + "BUILD.bazel", + "WORKSPACE", + "WORKSPACE.bazel", + ], + ), + crate_features = [ + "default", + "std", + ], + crate_root = "lib.rs", + edition = "2015", + rustc_flags = [ + "--cap-lints=allow", + ], + tags = [ + "cargo-bazel", + "crate-name=fnv", + "manual", + "noclippy", + "norustfmt", + ], + target_compatible_with = select({ + "@rules_rust//rust/platform:aarch64-apple-darwin": [], + "@rules_rust//rust/platform:aarch64-apple-ios": [], + "@rules_rust//rust/platform:aarch64-apple-ios-sim": [], + "@rules_rust//rust/platform:aarch64-fuchsia": [], + "@rules_rust//rust/platform:aarch64-linux-android": [], + "@rules_rust//rust/platform:aarch64-pc-windows-msvc": [], + "@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [], + "@rules_rust//rust/platform:aarch64-unknown-nixos-gnu": [], + "@rules_rust//rust/platform:aarch64-unknown-nto-qnx710": [], + "@rules_rust//rust/platform:arm-unknown-linux-gnueabi": [], + "@rules_rust//rust/platform:armv7-linux-androideabi": [], + "@rules_rust//rust/platform:armv7-unknown-linux-gnueabi": [], + "@rules_rust//rust/platform:i686-apple-darwin": [], + "@rules_rust//rust/platform:i686-linux-android": [], + "@rules_rust//rust/platform:i686-pc-windows-msvc": [], + "@rules_rust//rust/platform:i686-unknown-freebsd": [], + "@rules_rust//rust/platform:i686-unknown-linux-gnu": [], + "@rules_rust//rust/platform:powerpc-unknown-linux-gnu": [], + "@rules_rust//rust/platform:riscv32imc-unknown-none-elf": [], + "@rules_rust//rust/platform:riscv64gc-unknown-none-elf": [], + "@rules_rust//rust/platform:s390x-unknown-linux-gnu": [], + "@rules_rust//rust/platform:thumbv7em-none-eabi": [], + "@rules_rust//rust/platform:thumbv8m.main-none-eabi": [], + "@rules_rust//rust/platform:wasm32-unknown-unknown": [], + "@rules_rust//rust/platform:wasm32-wasi": [], + "@rules_rust//rust/platform:x86_64-apple-darwin": [], + "@rules_rust//rust/platform:x86_64-apple-ios": [], + "@rules_rust//rust/platform:x86_64-fuchsia": [], + "@rules_rust//rust/platform:x86_64-linux-android": [], + "@rules_rust//rust/platform:x86_64-pc-windows-msvc": [], + "@rules_rust//rust/platform:x86_64-unknown-freebsd": [], + "@rules_rust//rust/platform:x86_64-unknown-linux-gnu": [], + "@rules_rust//rust/platform:x86_64-unknown-nixos-gnu": [], + "@rules_rust//rust/platform:x86_64-unknown-none": [], + "//conditions:default": ["@platforms//:incompatible"], + }), + version = "1.0.7", +) diff --git a/bazel/cargo/remote/BUILD.http-1.1.0.bazel b/bazel/cargo/remote/BUILD.http-1.1.0.bazel new file mode 100644 index 00000000..51969aca --- /dev/null +++ b/bazel/cargo/remote/BUILD.http-1.1.0.bazel @@ -0,0 +1,90 @@ +############################################################################### +# @generated +# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To +# regenerate this file, run the following: +# +# bazel run @//bazel/cargo:crates_vendor +############################################################################### + +load("@rules_rust//rust:defs.bzl", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "http", + srcs = glob( + include = ["**/*.rs"], + allow_empty = True, + ), + compile_data = glob( + include = ["**"], + allow_empty = True, + exclude = [ + "**/* *", + ".tmp_git_root/**/*", + "BUILD", + "BUILD.bazel", + "WORKSPACE", + "WORKSPACE.bazel", + ], + ), + crate_features = [ + "default", + "std", + ], + crate_root = "src/lib.rs", + edition = "2018", + rustc_flags = [ + "--cap-lints=allow", + ], + tags = [ + "cargo-bazel", + "crate-name=http", + "manual", + "noclippy", + "norustfmt", + ], + target_compatible_with = select({ + "@rules_rust//rust/platform:aarch64-apple-darwin": [], + "@rules_rust//rust/platform:aarch64-apple-ios": [], + "@rules_rust//rust/platform:aarch64-apple-ios-sim": [], + "@rules_rust//rust/platform:aarch64-fuchsia": [], + "@rules_rust//rust/platform:aarch64-linux-android": [], + "@rules_rust//rust/platform:aarch64-pc-windows-msvc": [], + "@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [], + "@rules_rust//rust/platform:aarch64-unknown-nixos-gnu": [], + "@rules_rust//rust/platform:aarch64-unknown-nto-qnx710": [], + "@rules_rust//rust/platform:arm-unknown-linux-gnueabi": [], + "@rules_rust//rust/platform:armv7-linux-androideabi": [], + "@rules_rust//rust/platform:armv7-unknown-linux-gnueabi": [], + "@rules_rust//rust/platform:i686-apple-darwin": [], + "@rules_rust//rust/platform:i686-linux-android": [], + "@rules_rust//rust/platform:i686-pc-windows-msvc": [], + "@rules_rust//rust/platform:i686-unknown-freebsd": [], + "@rules_rust//rust/platform:i686-unknown-linux-gnu": [], + "@rules_rust//rust/platform:powerpc-unknown-linux-gnu": [], + "@rules_rust//rust/platform:riscv32imc-unknown-none-elf": [], + "@rules_rust//rust/platform:riscv64gc-unknown-none-elf": [], + "@rules_rust//rust/platform:s390x-unknown-linux-gnu": [], + "@rules_rust//rust/platform:thumbv7em-none-eabi": [], + "@rules_rust//rust/platform:thumbv8m.main-none-eabi": [], + "@rules_rust//rust/platform:wasm32-unknown-unknown": [], + "@rules_rust//rust/platform:wasm32-wasi": [], + "@rules_rust//rust/platform:x86_64-apple-darwin": [], + "@rules_rust//rust/platform:x86_64-apple-ios": [], + "@rules_rust//rust/platform:x86_64-fuchsia": [], + "@rules_rust//rust/platform:x86_64-linux-android": [], + "@rules_rust//rust/platform:x86_64-pc-windows-msvc": [], + "@rules_rust//rust/platform:x86_64-unknown-freebsd": [], + "@rules_rust//rust/platform:x86_64-unknown-linux-gnu": [], + "@rules_rust//rust/platform:x86_64-unknown-nixos-gnu": [], + "@rules_rust//rust/platform:x86_64-unknown-none": [], + "//conditions:default": ["@platforms//:incompatible"], + }), + version = "1.1.0", + deps = [ + "@crates_vendor__bytes-1.8.0//:bytes", + "@crates_vendor__fnv-1.0.7//:fnv", + "@crates_vendor__itoa-1.0.11//:itoa", + ], +) diff --git a/bazel/cargo/remote/BUILD.itoa-1.0.11.bazel b/bazel/cargo/remote/BUILD.itoa-1.0.11.bazel new file mode 100644 index 00000000..d8c549e8 --- /dev/null +++ b/bazel/cargo/remote/BUILD.itoa-1.0.11.bazel @@ -0,0 +1,81 @@ +############################################################################### +# @generated +# DO NOT MODIFY: This file is auto-generated by a crate_universe tool. To +# regenerate this file, run the following: +# +# bazel run @//bazel/cargo:crates_vendor +############################################################################### + +load("@rules_rust//rust:defs.bzl", "rust_library") + +package(default_visibility = ["//visibility:public"]) + +rust_library( + name = "itoa", + srcs = glob( + include = ["**/*.rs"], + allow_empty = True, + ), + compile_data = glob( + include = ["**"], + allow_empty = True, + exclude = [ + "**/* *", + ".tmp_git_root/**/*", + "BUILD", + "BUILD.bazel", + "WORKSPACE", + "WORKSPACE.bazel", + ], + ), + crate_root = "src/lib.rs", + edition = "2018", + rustc_flags = [ + "--cap-lints=allow", + ], + tags = [ + "cargo-bazel", + "crate-name=itoa", + "manual", + "noclippy", + "norustfmt", + ], + target_compatible_with = select({ + "@rules_rust//rust/platform:aarch64-apple-darwin": [], + "@rules_rust//rust/platform:aarch64-apple-ios": [], + "@rules_rust//rust/platform:aarch64-apple-ios-sim": [], + "@rules_rust//rust/platform:aarch64-fuchsia": [], + "@rules_rust//rust/platform:aarch64-linux-android": [], + "@rules_rust//rust/platform:aarch64-pc-windows-msvc": [], + "@rules_rust//rust/platform:aarch64-unknown-linux-gnu": [], + "@rules_rust//rust/platform:aarch64-unknown-nixos-gnu": [], + "@rules_rust//rust/platform:aarch64-unknown-nto-qnx710": [], + "@rules_rust//rust/platform:arm-unknown-linux-gnueabi": [], + "@rules_rust//rust/platform:armv7-linux-androideabi": [], + "@rules_rust//rust/platform:armv7-unknown-linux-gnueabi": [], + "@rules_rust//rust/platform:i686-apple-darwin": [], + "@rules_rust//rust/platform:i686-linux-android": [], + "@rules_rust//rust/platform:i686-pc-windows-msvc": [], + "@rules_rust//rust/platform:i686-unknown-freebsd": [], + "@rules_rust//rust/platform:i686-unknown-linux-gnu": [], + "@rules_rust//rust/platform:powerpc-unknown-linux-gnu": [], + "@rules_rust//rust/platform:riscv32imc-unknown-none-elf": [], + "@rules_rust//rust/platform:riscv64gc-unknown-none-elf": [], + "@rules_rust//rust/platform:s390x-unknown-linux-gnu": [], + "@rules_rust//rust/platform:thumbv7em-none-eabi": [], + "@rules_rust//rust/platform:thumbv8m.main-none-eabi": [], + "@rules_rust//rust/platform:wasm32-unknown-unknown": [], + "@rules_rust//rust/platform:wasm32-wasi": [], + "@rules_rust//rust/platform:x86_64-apple-darwin": [], + "@rules_rust//rust/platform:x86_64-apple-ios": [], + "@rules_rust//rust/platform:x86_64-fuchsia": [], + "@rules_rust//rust/platform:x86_64-linux-android": [], + "@rules_rust//rust/platform:x86_64-pc-windows-msvc": [], + "@rules_rust//rust/platform:x86_64-unknown-freebsd": [], + "@rules_rust//rust/platform:x86_64-unknown-linux-gnu": [], + "@rules_rust//rust/platform:x86_64-unknown-nixos-gnu": [], + "@rules_rust//rust/platform:x86_64-unknown-none": [], + "//conditions:default": ["@platforms//:incompatible"], + }), + version = "1.0.11", +) diff --git a/bazel/cargo/remote/defs.bzl b/bazel/cargo/remote/defs.bzl index e4051261..9f2308c6 100644 --- a/bazel/cargo/remote/defs.bzl +++ b/bazel/cargo/remote/defs.bzl @@ -296,6 +296,7 @@ _NORMAL_DEPENDENCIES = { "": { _COMMON_CONDITION: { "hashbrown": Label("@crates_vendor__hashbrown-0.15.0//:hashbrown"), + "http": Label("@crates_vendor__http-1.1.0//:http"), "log": Label("@crates_vendor__log-0.4.22//:log"), }, }, @@ -413,6 +414,16 @@ def crate_repositories(): build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.allocator-api2-0.2.18.bazel"), ) + maybe( + http_archive, + name = "crates_vendor__bytes-1.8.0", + sha256 = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da", + type = "tar.gz", + urls = ["https://static.crates.io/crates/bytes/1.8.0/download"], + strip_prefix = "bytes-1.8.0", + build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.bytes-1.8.0.bazel"), + ) + maybe( http_archive, name = "crates_vendor__equivalent-1.0.1", @@ -423,6 +434,16 @@ def crate_repositories(): build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.equivalent-1.0.1.bazel"), ) + maybe( + http_archive, + name = "crates_vendor__fnv-1.0.7", + sha256 = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1", + type = "tar.gz", + urls = ["https://static.crates.io/crates/fnv/1.0.7/download"], + strip_prefix = "fnv-1.0.7", + build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.fnv-1.0.7.bazel"), + ) + maybe( http_archive, name = "crates_vendor__foldhash-0.1.3", @@ -443,6 +464,26 @@ def crate_repositories(): build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.hashbrown-0.15.0.bazel"), ) + maybe( + http_archive, + name = "crates_vendor__http-1.1.0", + sha256 = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258", + type = "tar.gz", + urls = ["https://static.crates.io/crates/http/1.1.0/download"], + strip_prefix = "http-1.1.0", + build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.http-1.1.0.bazel"), + ) + + maybe( + http_archive, + name = "crates_vendor__itoa-1.0.11", + sha256 = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b", + type = "tar.gz", + urls = ["https://static.crates.io/crates/itoa/1.0.11/download"], + strip_prefix = "itoa-1.0.11", + build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.itoa-1.0.11.bazel"), + ) + maybe( http_archive, name = "crates_vendor__log-0.4.22", @@ -455,5 +496,6 @@ def crate_repositories(): return [ struct(repo = "crates_vendor__hashbrown-0.15.0", is_dev_dep = False), + struct(repo = "crates_vendor__http-1.1.0", is_dev_dep = False), struct(repo = "crates_vendor__log-0.4.22", is_dev_dep = False), ] diff --git a/examples/grpc_auth_random/Cargo.toml b/examples/grpc_auth_random/Cargo.toml index c3e6ec01..3e5b3be6 100644 --- a/examples/grpc_auth_random/Cargo.toml +++ b/examples/grpc_auth_random/Cargo.toml @@ -11,7 +11,7 @@ crate-type = ["cdylib"] [dependencies] log = "0.4" -proxy-wasm = { path = "../../" } +proxy-wasm = { path = "../../", features = ["header-value"] } [profile.release] lto = true diff --git a/examples/grpc_auth_random/src/lib.rs b/examples/grpc_auth_random/src/lib.rs index d1c589e9..11df530e 100644 --- a/examples/grpc_auth_random/src/lib.rs +++ b/examples/grpc_auth_random/src/lib.rs @@ -27,12 +27,12 @@ struct GrpcAuthRandom; impl HttpContext for GrpcAuthRandom { fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action { match self.get_http_request_header("content-type") { - Some(value) if value.starts_with("application/grpc") => {} + Some(value) if value.as_bytes().starts_with(b"application/grpc") => {} _ => { // Reject non-gRPC clients. self.send_http_response( 503, - vec![("Powered-By", "proxy-wasm")], + vec![("Powered-By", &HeaderValue::from_static("proxy-wasm"))], Some(b"Service accessible only to gRPC clients.\n"), ); return Action::Pause; @@ -40,7 +40,7 @@ impl HttpContext for GrpcAuthRandom { } match self.get_http_request_header(":path") { - Some(value) if value.starts_with("/grpc.reflection") => { + Some(value) if value.as_bytes().starts_with(b"/grpc.reflection") => { // Always allow gRPC calls to the reflection API. Action::Continue } @@ -61,7 +61,7 @@ impl HttpContext for GrpcAuthRandom { } fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action { - self.set_http_response_header("Powered-By", Some("proxy-wasm")); + self.set_http_response_header("Powered-By", Some(&HeaderValue::from_static("proxy-wasm"))); Action::Continue } } diff --git a/examples/http_headers/Cargo.toml b/examples/http_headers/Cargo.toml index 02afd242..2fb8fa6a 100644 --- a/examples/http_headers/Cargo.toml +++ b/examples/http_headers/Cargo.toml @@ -12,7 +12,7 @@ crate-type = ["cdylib"] [dependencies] log = "0.4" -proxy-wasm = { path = "../../" } +proxy-wasm = { path = "../../", features = ["header-value"] } [profile.release] lto = true diff --git a/examples/http_headers/src/lib.rs b/examples/http_headers/src/lib.rs index 315a7b88..a6a4942b 100644 --- a/examples/http_headers/src/lib.rs +++ b/examples/http_headers/src/lib.rs @@ -44,14 +44,22 @@ impl Context for HttpHeaders {} impl HttpContext for HttpHeaders { fn on_http_request_headers(&mut self, _: usize, _: bool) -> Action { for (name, value) in &self.get_http_request_headers() { - info!("#{} -> {}: {}", self.context_id, name, value); + info!( + "#{} -> {}: {}", + self.context_id, + name, + value.to_str().unwrap_or("") + ); } match self.get_http_request_header(":path") { Some(path) if path == "/hello" => { self.send_http_response( 200, - vec![("Hello", "World"), ("Powered-By", "proxy-wasm")], + vec![ + ("Hello", &HeaderValue::from_static("World")), + ("Powered-By", &HeaderValue::from_static("proxy-wasm")), + ], Some(b"Hello, World!\n"), ); Action::Pause @@ -62,7 +70,12 @@ impl HttpContext for HttpHeaders { fn on_http_response_headers(&mut self, _: usize, _: bool) -> Action { for (name, value) in &self.get_http_response_headers() { - info!("#{} <- {}: {}", self.context_id, name, value); + info!( + "#{} <- {}: {}", + self.context_id, + name, + value.to_str().unwrap_or("") + ); } Action::Continue } diff --git a/src/hostcalls.rs b/src/hostcalls.rs index 15687888..68059899 100644 --- a/src/hostcalls.rs +++ b/src/hostcalls.rs @@ -145,6 +145,26 @@ extern "C" { ) -> Status; } +#[cfg(feature = "header-value")] +pub fn get_map(map_type: MapType) -> Result, Status> { + unsafe { + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + match proxy_get_header_map_pairs(map_type, &mut return_data, &mut return_size) { + Status::Ok => { + if !return_data.is_null() { + let serialized_map = Vec::from_raw_parts(return_data, return_size, return_size); + Ok(utils::deserialize_map(&serialized_map)) + } else { + Ok(Vec::new()) + } + } + status => panic!("unexpected status: {}", status as u32), + } + } +} + +#[cfg(not(feature = "header-value"))] pub fn get_map(map_type: MapType) -> Result, Status> { unsafe { let mut return_data: *mut u8 = null_mut(); @@ -189,7 +209,10 @@ extern "C" { ) -> Status; } -pub fn set_map(map_type: MapType, map: Vec<(&str, &str)>) -> Result<(), Status> { +pub fn set_map(map_type: MapType, map: Vec<(&str, V)>) -> Result<(), Status> +where + V: AsRef<[u8]>, +{ let serialized_map = utils::serialize_map(map); unsafe { match proxy_set_header_map_pairs(map_type, serialized_map.as_ptr(), serialized_map.len()) { @@ -200,13 +223,7 @@ pub fn set_map(map_type: MapType, map: Vec<(&str, &str)>) -> Result<(), Status> } pub fn set_map_bytes(map_type: MapType, map: Vec<(&str, &[u8])>) -> Result<(), Status> { - let serialized_map = utils::serialize_map_bytes(map); - unsafe { - match proxy_set_header_map_pairs(map_type, serialized_map.as_ptr(), serialized_map.len()) { - Status::Ok => Ok(()), - status => panic!("unexpected status: {}", status as u32), - } - } + set_map(map_type, map) } extern "C" { @@ -219,6 +236,38 @@ extern "C" { ) -> Status; } +#[cfg(feature = "header-value")] +pub fn get_map_value(map_type: MapType, key: &str) -> Result, Status> { + let mut return_data: *mut u8 = null_mut(); + let mut return_size: usize = 0; + unsafe { + match proxy_get_header_map_value( + map_type, + key.as_ptr(), + key.len(), + &mut return_data, + &mut return_size, + ) { + Status::Ok => { + if !return_data.is_null() { + match HeaderValue::from_bytes(std::slice::from_raw_parts( + return_data, + return_size, + )) { + Ok(value) => Ok(Some(value)), + Err(_) => panic!("illegal field value in: {}", key), + } + } else { + Ok(None) + } + } + Status::NotFound => Ok(None), + status => panic!("unexpected status: {}", status as u32), + } + } +} + +#[cfg(not(feature = "header-value"))] pub fn get_map_value(map_type: MapType, key: &str) -> Result, Status> { let mut return_data: *mut u8 = null_mut(); let mut return_size: usize = 0; @@ -296,15 +345,18 @@ extern "C" { ) -> Status; } -pub fn set_map_value(map_type: MapType, key: &str, value: Option<&str>) -> Result<(), Status> { +pub fn set_map_value(map_type: MapType, key: &str, value: Option) -> Result<(), Status> +where + V: AsRef<[u8]>, +{ unsafe { if let Some(value) = value { match proxy_replace_header_map_value( map_type, key.as_ptr(), key.len(), - value.as_ptr(), - value.len(), + value.as_ref().as_ptr(), + value.as_ref().len(), ) { Status::Ok => Ok(()), status => panic!("unexpected status: {}", status as u32), @@ -323,25 +375,7 @@ pub fn set_map_value_bytes( key: &str, value: Option<&[u8]>, ) -> Result<(), Status> { - unsafe { - if let Some(value) = value { - match proxy_replace_header_map_value( - map_type, - key.as_ptr(), - key.len(), - value.as_ptr(), - value.len(), - ) { - Status::Ok => Ok(()), - status => panic!("unexpected status: {}", status as u32), - } - } else { - match proxy_remove_header_map_value(map_type, key.as_ptr(), key.len()) { - Status::Ok => Ok(()), - status => panic!("unexpected status: {}", status as u32), - } - } - } + set_map_value(map_type, key, value) } extern "C" { @@ -354,14 +388,17 @@ extern "C" { ) -> Status; } -pub fn add_map_value(map_type: MapType, key: &str, value: &str) -> Result<(), Status> { +pub fn add_map_value(map_type: MapType, key: &str, value: V) -> Result<(), Status> +where + V: AsRef<[u8]>, +{ unsafe { match proxy_add_header_map_value( map_type, key.as_ptr(), key.len(), - value.as_ptr(), - value.len(), + value.as_ref().as_ptr(), + value.as_ref().len(), ) { Status::Ok => Ok(()), status => panic!("unexpected status: {}", status as u32), @@ -370,18 +407,7 @@ pub fn add_map_value(map_type: MapType, key: &str, value: &str) -> Result<(), St } pub fn add_map_value_bytes(map_type: MapType, key: &str, value: &[u8]) -> Result<(), Status> { - unsafe { - match proxy_add_header_map_value( - map_type, - key.as_ptr(), - key.len(), - value.as_ptr(), - value.len(), - ) { - Status::Ok => Ok(()), - status => panic!("unexpected status: {}", status as u32), - } - } + add_map_value(map_type, key, value) } extern "C" { @@ -705,11 +731,14 @@ extern "C" { ) -> Status; } -pub fn send_http_response( +pub fn send_http_response( status_code: u32, - headers: Vec<(&str, &str)>, + headers: Vec<(&str, V)>, body: Option<&[u8]>, -) -> Result<(), Status> { +) -> Result<(), Status> +where + V: AsRef<[u8]>, +{ let serialized_headers = utils::serialize_map(headers); unsafe { match proxy_send_local_response( @@ -766,13 +795,16 @@ extern "C" { ) -> Status; } -pub fn dispatch_http_call( +pub fn dispatch_http_call( upstream: &str, - headers: Vec<(&str, &str)>, + headers: Vec<(&str, V)>, body: Option<&[u8]>, - trailers: Vec<(&str, &str)>, + trailers: Vec<(&str, V)>, timeout: Duration, -) -> Result { +) -> Result +where + V: AsRef<[u8]>, +{ let serialized_headers = utils::serialize_map(headers); let serialized_trailers = utils::serialize_map(trailers); let mut return_token: u32 = 0; @@ -1140,6 +1172,8 @@ pub fn increment_metric(metric_id: u32, offset: i64) -> Result<(), Status> { mod utils { use crate::types::Bytes; + #[cfg(feature = "header-value")] + use crate::types::HeaderValue; use std::convert::TryFrom; pub(super) fn serialize_property_path(path: Vec<&str>) -> Bytes { @@ -1159,46 +1193,59 @@ mod utils { bytes } - pub(super) fn serialize_map(map: Vec<(&str, &str)>) -> Bytes { + pub(super) fn serialize_map(map: Vec<(&str, V)>) -> Bytes + where + V: AsRef<[u8]>, + { let mut size: usize = 4; for (name, value) in &map { - size += name.len() + value.len() + 10; + size += name.len() + value.as_ref().len() + 10; } let mut bytes: Bytes = Vec::with_capacity(size); bytes.extend_from_slice(&map.len().to_le_bytes()); for (name, value) in &map { bytes.extend_from_slice(&name.len().to_le_bytes()); - bytes.extend_from_slice(&value.len().to_le_bytes()); + bytes.extend_from_slice(&value.as_ref().len().to_le_bytes()); } for (name, value) in &map { bytes.extend_from_slice(name.as_bytes()); bytes.push(0); - bytes.extend_from_slice(value.as_bytes()); + bytes.extend_from_slice(value.as_ref()); bytes.push(0); } bytes } pub(super) fn serialize_map_bytes(map: Vec<(&str, &[u8])>) -> Bytes { - let mut size: usize = 4; - for (name, value) in &map { - size += name.len() + value.len() + 10; - } - let mut bytes: Bytes = Vec::with_capacity(size); - bytes.extend_from_slice(&map.len().to_le_bytes()); - for (name, value) in &map { - bytes.extend_from_slice(&name.len().to_le_bytes()); - bytes.extend_from_slice(&value.len().to_le_bytes()); + serialize_map(map) + } + + #[cfg(feature = "header-value")] + pub(super) fn deserialize_map(bytes: &[u8]) -> Vec<(String, HeaderValue)> { + let mut map = Vec::new(); + if bytes.is_empty() { + return map; } - for (name, value) in &map { - bytes.extend_from_slice(name.as_bytes()); - bytes.push(0); - bytes.extend_from_slice(value); - bytes.push(0); + let size = u32::from_le_bytes(<[u8; 4]>::try_from(&bytes[0..4]).unwrap()) as usize; + let mut p = 4 + size * 8; + for n in 0..size { + let s = 4 + n * 8; + let size = u32::from_le_bytes(<[u8; 4]>::try_from(&bytes[s..s + 4]).unwrap()) as usize; + let key = bytes[p..p + size].to_vec(); + p += size + 1; + let size = + u32::from_le_bytes(<[u8; 4]>::try_from(&bytes[s + 4..s + 8]).unwrap()) as usize; + let value = &bytes[p..p + size]; + p += size + 1; + map.push(( + String::from_utf8(key).unwrap(), + HeaderValue::from_bytes(value).unwrap(), + )); } - bytes + map } + #[cfg(not(feature = "header-value"))] pub(super) fn deserialize_map(bytes: &[u8]) -> Vec<(String, String)> { let mut map = Vec::new(); if bytes.is_empty() { diff --git a/src/traits.rs b/src/traits.rs index bd54bcbe..63a3db97 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -58,6 +58,19 @@ pub trait Context { hostcalls::enqueue_shared_queue(queue_id, value) } + #[cfg(feature = "header-value")] + fn dispatch_http_call( + &self, + upstream: &str, + headers: Vec<(&str, &HeaderValue)>, + body: Option<&[u8]>, + trailers: Vec<(&str, &HeaderValue)>, + timeout: Duration, + ) -> Result { + hostcalls::dispatch_http_call(upstream, headers, body, trailers, timeout) + } + + #[cfg(not(feature = "header-value"))] fn dispatch_http_call( &self, upstream: &str, @@ -78,6 +91,12 @@ pub trait Context { ) { } + #[cfg(feature = "header-value")] + fn get_http_call_response_headers(&self) -> Vec<(String, HeaderValue)> { + hostcalls::get_map(MapType::HttpCallResponseHeaders).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_call_response_headers(&self) -> Vec<(String, String)> { hostcalls::get_map(MapType::HttpCallResponseHeaders).unwrap() } @@ -86,6 +105,12 @@ pub trait Context { hostcalls::get_map_bytes(MapType::HttpCallResponseHeaders).unwrap() } + #[cfg(feature = "header-value")] + fn get_http_call_response_header(&self, name: &str) -> Option { + hostcalls::get_map_value(MapType::HttpCallResponseTrailers, name).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_call_response_header(&self, name: &str) -> Option { hostcalls::get_map_value(MapType::HttpCallResponseHeaders, name).unwrap() } @@ -98,6 +123,12 @@ pub trait Context { hostcalls::get_buffer(BufferType::HttpCallResponseBody, start, max_size).unwrap() } + #[cfg(feature = "header-value")] + fn get_http_call_response_trailers(&self) -> Vec<(String, HeaderValue)> { + hostcalls::get_map(MapType::HttpCallResponseTrailers).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_call_response_trailers(&self) -> Vec<(String, String)> { hostcalls::get_map(MapType::HttpCallResponseTrailers).unwrap() } @@ -106,6 +137,12 @@ pub trait Context { hostcalls::get_map_bytes(MapType::HttpCallResponseTrailers).unwrap() } + #[cfg(feature = "header-value")] + fn get_http_call_response_trailer(&self, name: &str) -> Option { + hostcalls::get_map_value(MapType::HttpCallResponseTrailers, name).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_call_response_trailer(&self, name: &str) -> Option { hostcalls::get_map_value(MapType::HttpCallResponseTrailers, name).unwrap() } @@ -311,6 +348,12 @@ pub trait HttpContext: Context { Action::Continue } + #[cfg(feature = "header-value")] + fn get_http_request_headers(&self) -> Vec<(String, HeaderValue)> { + hostcalls::get_map(MapType::HttpRequestHeaders).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_request_headers(&self) -> Vec<(String, String)> { hostcalls::get_map(MapType::HttpRequestHeaders).unwrap() } @@ -319,6 +362,12 @@ pub trait HttpContext: Context { hostcalls::get_map_bytes(MapType::HttpRequestHeaders).unwrap() } + #[cfg(feature = "header-value")] + fn set_http_request_headers(&self, headers: Vec<(&str, &HeaderValue)>) { + hostcalls::set_map(MapType::HttpRequestHeaders, headers).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn set_http_request_headers(&self, headers: Vec<(&str, &str)>) { hostcalls::set_map(MapType::HttpRequestHeaders, headers).unwrap() } @@ -327,6 +376,12 @@ pub trait HttpContext: Context { hostcalls::set_map_bytes(MapType::HttpRequestHeaders, headers).unwrap() } + #[cfg(feature = "header-value")] + fn get_http_request_header(&self, name: &str) -> Option { + hostcalls::get_map_value(MapType::HttpRequestHeaders, name).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_request_header(&self, name: &str) -> Option { hostcalls::get_map_value(MapType::HttpRequestHeaders, name).unwrap() } @@ -335,6 +390,12 @@ pub trait HttpContext: Context { hostcalls::get_map_value_bytes(MapType::HttpRequestHeaders, name).unwrap() } + #[cfg(feature = "header-value")] + fn set_http_request_header(&self, name: &str, value: Option<&HeaderValue>) { + hostcalls::set_map_value(MapType::HttpRequestHeaders, name, value).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn set_http_request_header(&self, name: &str, value: Option<&str>) { hostcalls::set_map_value(MapType::HttpRequestHeaders, name, value).unwrap() } @@ -343,6 +404,12 @@ pub trait HttpContext: Context { hostcalls::set_map_value_bytes(MapType::HttpRequestHeaders, name, value).unwrap() } + #[cfg(feature = "header-value")] + fn add_http_request_header(&self, name: &str, value: &HeaderValue) { + hostcalls::add_map_value(MapType::HttpRequestHeaders, name, value).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn add_http_request_header(&self, name: &str, value: &str) { hostcalls::add_map_value(MapType::HttpRequestHeaders, name, value).unwrap() } @@ -367,6 +434,12 @@ pub trait HttpContext: Context { Action::Continue } + #[cfg(feature = "header-value")] + fn get_http_request_trailers(&self) -> Vec<(String, HeaderValue)> { + hostcalls::get_map(MapType::HttpRequestTrailers).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_request_trailers(&self) -> Vec<(String, String)> { hostcalls::get_map(MapType::HttpRequestTrailers).unwrap() } @@ -375,6 +448,12 @@ pub trait HttpContext: Context { hostcalls::get_map_bytes(MapType::HttpRequestTrailers).unwrap() } + #[cfg(feature = "header-value")] + fn set_http_request_trailers(&self, trailers: Vec<(&str, &HeaderValue)>) { + hostcalls::set_map(MapType::HttpRequestTrailers, trailers).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn set_http_request_trailers(&self, trailers: Vec<(&str, &str)>) { hostcalls::set_map(MapType::HttpRequestTrailers, trailers).unwrap() } @@ -383,6 +462,12 @@ pub trait HttpContext: Context { hostcalls::set_map_bytes(MapType::HttpRequestTrailers, trailers).unwrap() } + #[cfg(feature = "header-value")] + fn get_http_request_trailer(&self, name: &str) -> Option { + hostcalls::get_map_value(MapType::HttpRequestTrailers, name).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_request_trailer(&self, name: &str) -> Option { hostcalls::get_map_value(MapType::HttpRequestTrailers, name).unwrap() } @@ -391,6 +476,12 @@ pub trait HttpContext: Context { hostcalls::get_map_value_bytes(MapType::HttpRequestTrailers, name).unwrap() } + #[cfg(feature = "header-value")] + fn set_http_request_trailer(&self, name: &str, value: Option<&HeaderValue>) { + hostcalls::set_map_value(MapType::HttpRequestTrailers, name, value).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn set_http_request_trailer(&self, name: &str, value: Option<&str>) { hostcalls::set_map_value(MapType::HttpRequestTrailers, name, value).unwrap() } @@ -399,6 +490,12 @@ pub trait HttpContext: Context { hostcalls::set_map_value_bytes(MapType::HttpRequestTrailers, name, value).unwrap() } + #[cfg(feature = "header-value")] + fn add_http_request_trailer(&self, name: &str, value: &HeaderValue) { + hostcalls::add_map_value(MapType::HttpRequestTrailers, name, value).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn add_http_request_trailer(&self, name: &str, value: &str) { hostcalls::add_map_value(MapType::HttpRequestTrailers, name, value).unwrap() } @@ -419,6 +516,12 @@ pub trait HttpContext: Context { Action::Continue } + #[cfg(feature = "header-value")] + fn get_http_response_headers(&self) -> Vec<(String, HeaderValue)> { + hostcalls::get_map(MapType::HttpResponseHeaders).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_response_headers(&self) -> Vec<(String, String)> { hostcalls::get_map(MapType::HttpResponseHeaders).unwrap() } @@ -427,6 +530,12 @@ pub trait HttpContext: Context { hostcalls::get_map_bytes(MapType::HttpResponseHeaders).unwrap() } + #[cfg(feature = "header-value")] + fn set_http_response_headers(&self, headers: Vec<(&str, &HeaderValue)>) { + hostcalls::set_map(MapType::HttpResponseHeaders, headers).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn set_http_response_headers(&self, headers: Vec<(&str, &str)>) { hostcalls::set_map(MapType::HttpResponseHeaders, headers).unwrap() } @@ -435,6 +544,12 @@ pub trait HttpContext: Context { hostcalls::set_map_bytes(MapType::HttpResponseHeaders, headers).unwrap() } + #[cfg(feature = "header-value")] + fn get_http_response_header(&self, name: &str) -> Option { + hostcalls::get_map_value(MapType::HttpResponseHeaders, name).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_response_header(&self, name: &str) -> Option { hostcalls::get_map_value(MapType::HttpResponseHeaders, name).unwrap() } @@ -443,6 +558,12 @@ pub trait HttpContext: Context { hostcalls::get_map_value_bytes(MapType::HttpResponseHeaders, name).unwrap() } + #[cfg(feature = "header-value")] + fn set_http_response_header(&self, name: &str, value: Option<&HeaderValue>) { + hostcalls::set_map_value(MapType::HttpResponseHeaders, name, value).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn set_http_response_header(&self, name: &str, value: Option<&str>) { hostcalls::set_map_value(MapType::HttpResponseHeaders, name, value).unwrap() } @@ -451,6 +572,12 @@ pub trait HttpContext: Context { hostcalls::set_map_value_bytes(MapType::HttpResponseHeaders, name, value).unwrap() } + #[cfg(feature = "header-value")] + fn add_http_response_header(&self, name: &str, value: &HeaderValue) { + hostcalls::add_map_value(MapType::HttpResponseHeaders, name, value).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn add_http_response_header(&self, name: &str, value: &str) { hostcalls::add_map_value(MapType::HttpResponseHeaders, name, value).unwrap() } @@ -475,6 +602,12 @@ pub trait HttpContext: Context { Action::Continue } + #[cfg(feature = "header-value")] + fn get_http_response_trailers(&self) -> Vec<(String, HeaderValue)> { + hostcalls::get_map(MapType::HttpResponseTrailers).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_response_trailers(&self) -> Vec<(String, String)> { hostcalls::get_map(MapType::HttpResponseTrailers).unwrap() } @@ -483,6 +616,12 @@ pub trait HttpContext: Context { hostcalls::get_map_bytes(MapType::HttpResponseTrailers).unwrap() } + #[cfg(feature = "header-value")] + fn set_http_response_trailers(&self, trailers: Vec<(&str, &HeaderValue)>) { + hostcalls::set_map(MapType::HttpResponseTrailers, trailers).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn set_http_response_trailers(&self, trailers: Vec<(&str, &str)>) { hostcalls::set_map(MapType::HttpResponseTrailers, trailers).unwrap() } @@ -491,6 +630,12 @@ pub trait HttpContext: Context { hostcalls::set_map_bytes(MapType::HttpResponseTrailers, trailers).unwrap() } + #[cfg(feature = "header-value")] + fn get_http_response_trailer(&self, name: &str) -> Option { + hostcalls::get_map_value(MapType::HttpResponseTrailers, name).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn get_http_response_trailer(&self, name: &str) -> Option { hostcalls::get_map_value(MapType::HttpResponseTrailers, name).unwrap() } @@ -499,6 +644,12 @@ pub trait HttpContext: Context { hostcalls::get_map_value_bytes(MapType::HttpResponseTrailers, name).unwrap() } + #[cfg(feature = "header-value")] + fn set_http_response_trailer(&self, name: &str, value: Option<&HeaderValue>) { + hostcalls::set_map_value(MapType::HttpResponseTrailers, name, value).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn set_http_response_trailer(&self, name: &str, value: Option<&str>) { hostcalls::set_map_value(MapType::HttpResponseTrailers, name, value).unwrap() } @@ -507,6 +658,12 @@ pub trait HttpContext: Context { hostcalls::set_map_value_bytes(MapType::HttpResponseTrailers, name, value).unwrap() } + #[cfg(feature = "header-value")] + fn add_http_response_trailer(&self, name: &str, value: &HeaderValue) { + hostcalls::add_map_value(MapType::HttpResponseTrailers, name, value).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn add_http_response_trailer(&self, name: &str, value: &str) { hostcalls::add_map_value(MapType::HttpResponseTrailers, name, value).unwrap() } @@ -523,6 +680,17 @@ pub trait HttpContext: Context { hostcalls::reset_http_response().unwrap() } + #[cfg(feature = "header-value")] + fn send_http_response( + &self, + status_code: u32, + headers: Vec<(&str, &HeaderValue)>, + body: Option<&[u8]>, + ) { + hostcalls::send_http_response(status_code, headers, body).unwrap() + } + + #[cfg(not(feature = "header-value"))] fn send_http_response( &self, status_code: u32, diff --git a/src/types.rs b/src/types.rs index 7407d3ca..36f387e7 100644 --- a/src/types.rs +++ b/src/types.rs @@ -139,3 +139,6 @@ pub enum GrpcStatusCode { } pub type Bytes = Vec; + +#[cfg(feature = "header-value")] +pub use http::HeaderValue; From 0542a785f2cbaf9d9fa36cdf75729cc6a0f71bbd Mon Sep 17 00:00:00 2001 From: Piotr Sikora Date: Sun, 27 Oct 2024 18:29:43 -0400 Subject: [PATCH 2/2] review: retain values from more forgiving hosts. Signed-off-by: Piotr Sikora --- BUILD | 1 + Cargo.toml | 3 ++- bazel/cargo/Cargo.Bazel.lock | 1 + bazel/cargo/remote/BUILD.bazel | 6 +++++ bazel/cargo/remote/defs.bzl | 2 ++ src/hostcalls.rs | 40 +++++++++++++++++++--------------- 6 files changed, 34 insertions(+), 19 deletions(-) diff --git a/BUILD b/BUILD index 6af16d0d..eb96231e 100644 --- a/BUILD +++ b/BUILD @@ -48,6 +48,7 @@ rust_library( visibility = ["//visibility:public"], deps = [ ":proxy_wasm_build_script", + "//bazel/cargo/remote:bytes", "//bazel/cargo/remote:hashbrown", "//bazel/cargo/remote:http", "//bazel/cargo/remote:log", diff --git a/Cargo.toml b/Cargo.toml index a52f0d8c..fd322592 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,14 @@ edition = "2018" build = "build.rs" [dependencies] +bytes = { version = "1", optional = true } hashbrown = "0.15" http = { version = "1", optional = true } log = "0.4" [features] default = [] -header-value = ["dep:http"] +header-value = ["dep:bytes", "dep:http"] [profile.release] lto = true diff --git a/bazel/cargo/Cargo.Bazel.lock b/bazel/cargo/Cargo.Bazel.lock index 367a120e..3a373e6d 100644 --- a/bazel/cargo/Cargo.Bazel.lock +++ b/bazel/cargo/Cargo.Bazel.lock @@ -70,6 +70,7 @@ checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" name = "proxy-wasm" version = "0.2.3-dev" dependencies = [ + "bytes", "hashbrown", "http", "log", diff --git a/bazel/cargo/remote/BUILD.bazel b/bazel/cargo/remote/BUILD.bazel index 0e16b637..46e22fdb 100644 --- a/bazel/cargo/remote/BUILD.bazel +++ b/bazel/cargo/remote/BUILD.bazel @@ -31,6 +31,12 @@ filegroup( ) # Workspace Member Dependencies +alias( + name = "bytes", + actual = "@crates_vendor__bytes-1.8.0//:bytes", + tags = ["manual"], +) + alias( name = "hashbrown", actual = "@crates_vendor__hashbrown-0.15.0//:hashbrown", diff --git a/bazel/cargo/remote/defs.bzl b/bazel/cargo/remote/defs.bzl index 9f2308c6..97ddadb4 100644 --- a/bazel/cargo/remote/defs.bzl +++ b/bazel/cargo/remote/defs.bzl @@ -295,6 +295,7 @@ def aliases( _NORMAL_DEPENDENCIES = { "": { _COMMON_CONDITION: { + "bytes": Label("@crates_vendor__bytes-1.8.0//:bytes"), "hashbrown": Label("@crates_vendor__hashbrown-0.15.0//:hashbrown"), "http": Label("@crates_vendor__http-1.1.0//:http"), "log": Label("@crates_vendor__log-0.4.22//:log"), @@ -495,6 +496,7 @@ def crate_repositories(): ) return [ + struct(repo = "crates_vendor__bytes-1.8.0", is_dev_dep = False), struct(repo = "crates_vendor__hashbrown-0.15.0", is_dev_dep = False), struct(repo = "crates_vendor__http-1.1.0", is_dev_dep = False), struct(repo = "crates_vendor__log-0.4.22", is_dev_dep = False), diff --git a/src/hostcalls.rs b/src/hostcalls.rs index 68059899..630bc3e4 100644 --- a/src/hostcalls.rs +++ b/src/hostcalls.rs @@ -153,8 +153,9 @@ pub fn get_map(map_type: MapType) -> Result, Status> match proxy_get_header_map_pairs(map_type, &mut return_data, &mut return_size) { Status::Ok => { if !return_data.is_null() { - let serialized_map = Vec::from_raw_parts(return_data, return_size, return_size); - Ok(utils::deserialize_map(&serialized_map)) + let serialized_map = + bytes::Bytes::from(std::slice::from_raw_parts(return_data, return_size)); + Ok(utils::deserialize_map(serialized_map)) } else { Ok(Vec::new()) } @@ -1174,6 +1175,8 @@ mod utils { use crate::types::Bytes; #[cfg(feature = "header-value")] use crate::types::HeaderValue; + #[cfg(feature = "header-value")] + use bytes::Buf; use std::convert::TryFrom; pub(super) fn serialize_property_path(path: Vec<&str>) -> Bytes { @@ -1221,25 +1224,26 @@ mod utils { } #[cfg(feature = "header-value")] - pub(super) fn deserialize_map(bytes: &[u8]) -> Vec<(String, HeaderValue)> { - let mut map = Vec::new(); + pub(super) fn deserialize_map(mut bytes: bytes::Bytes) -> Vec<(String, HeaderValue)> { if bytes.is_empty() { - return map; + return Vec::new(); } - let size = u32::from_le_bytes(<[u8; 4]>::try_from(&bytes[0..4]).unwrap()) as usize; - let mut p = 4 + size * 8; - for n in 0..size { - let s = 4 + n * 8; - let size = u32::from_le_bytes(<[u8; 4]>::try_from(&bytes[s..s + 4]).unwrap()) as usize; - let key = bytes[p..p + size].to_vec(); - p += size + 1; - let size = - u32::from_le_bytes(<[u8; 4]>::try_from(&bytes[s + 4..s + 8]).unwrap()) as usize; - let value = &bytes[p..p + size]; - p += size + 1; + let size = bytes.get_u32_le() as usize; + let mut sizes = bytes.split_to(size * 8); + let mut map = Vec::with_capacity(size); + for _ in 0..size { + let size = sizes.get_u32_le() as usize; + let key = bytes.split_to(size); + bytes.advance(1); + let size = sizes.get_u32_le() as usize; + let value = bytes.split_to(size); + bytes.advance(1); map.push(( - String::from_utf8(key).unwrap(), - HeaderValue::from_bytes(value).unwrap(), + String::from_utf8(key.to_vec()).unwrap(), + // We're intentionally using the unchecked variant in order to retain + // values accepted by the hosts and proxies that don't enforce strict + // RFC compliance on HTTP field values. + unsafe { HeaderValue::from_maybe_shared_unchecked(value) }, )); } map