diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 02835b36..2ff86288 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -96,7 +96,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:
@@ -150,9 +152,18 @@ 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: Test
       run: cargo test
 
+    - name: Test (header-value)
+      run: cargo test --features header-value
+
     - name: Format (rustfmt)
       run: cargo fmt -- --check
 
@@ -215,9 +226,18 @@ 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: Test
       run: cargo test
 
+    - name: Test (header-value)
+      run: cargo test --features header-value
+
     - name: Format (rustfmt)
       run: cargo fmt -- --check
 
@@ -281,12 +301,24 @@ 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: Test
       run: cargo test
 
+    - name: Test (header-value)
+      run: cargo test --features header-value
+
     - name: Bench
       run: cargo bench
 
+    - name: Bench (header-value)
+      run: cargo bench --features header-value
+
     - name: Format (rustfmt)
       run: cargo fmt -- --check
 
diff --git a/BUILD b/BUILD
index 7f611e4f..eb96231e 100644
--- a/BUILD
+++ b/BUILD
@@ -39,6 +39,22 @@ 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:bytes",
+        "//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 +68,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 d23f2ce8..ea592ce8 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -11,9 +11,15 @@ 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:bytes", "dep:http"]
+
 [profile.release]
 lto = true
 opt-level = 3
diff --git a/README.md b/README.md
index fa53c221..9b29505c 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 3426df9d..af59abd0 100644
--- a/bazel/cargo/Cargo.Bazel.lock
+++ b/bazel/cargo/Cargo.Bazel.lock
@@ -8,12 +8,24 @@ version = "0.2.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
 
+[[package]]
+name = "bytes"
+version = "1.10.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
+
 [[package]]
 name = "equivalent"
 version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
 
+[[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.5"
@@ -31,6 +43,23 @@ dependencies = [
  "foldhash",
 ]
 
+[[package]]
+name = "http"
+version = "1.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565"
+dependencies = [
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
 [[package]]
 name = "log"
 version = "0.4.27"
@@ -41,6 +70,8 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
 name = "proxy-wasm"
 version = "0.2.4-dev"
 dependencies = [
+ "bytes",
  "hashbrown",
+ "http",
  "log",
 ]
diff --git a/bazel/cargo/remote/BUILD.bazel b/bazel/cargo/remote/BUILD.bazel
index f2d36366..5453a065 100644
--- a/bazel/cargo/remote/BUILD.bazel
+++ b/bazel/cargo/remote/BUILD.bazel
@@ -31,6 +31,18 @@ filegroup(
 )
 
 # Workspace Member Dependencies
+alias(
+    name = "bytes-1.10.1",
+    actual = "@crates_vendor__bytes-1.10.1//:bytes",
+    tags = ["manual"],
+)
+
+alias(
+    name = "bytes",
+    actual = "@crates_vendor__bytes-1.10.1//:bytes",
+    tags = ["manual"],
+)
+
 alias(
     name = "hashbrown-0.15.3",
     actual = "@crates_vendor__hashbrown-0.15.3//:hashbrown",
@@ -43,6 +55,18 @@ alias(
     tags = ["manual"],
 )
 
+alias(
+    name = "http-1.3.1",
+    actual = "@crates_vendor__http-1.3.1//:http",
+    tags = ["manual"],
+)
+
+alias(
+    name = "http",
+    actual = "@crates_vendor__http-1.3.1//:http",
+    tags = ["manual"],
+)
+
 alias(
     name = "log-0.4.27",
     actual = "@crates_vendor__log-0.4.27//:log",
diff --git a/bazel/cargo/remote/BUILD.bytes-1.10.1.bazel b/bazel/cargo/remote/BUILD.bytes-1.10.1.bazel
new file mode 100644
index 00000000..1f0ddc76
--- /dev/null
+++ b/bazel/cargo/remote/BUILD.bytes-1.10.1.bazel
@@ -0,0 +1,96 @@
+###############################################################################
+# @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//cargo:defs.bzl", "cargo_toml_env_vars")
+load("@rules_rust//rust:defs.bzl", "rust_library")
+
+package(default_visibility = ["//visibility:public"])
+
+cargo_toml_env_vars(
+    name = "cargo_toml_env_vars",
+    src = "Cargo.toml",
+)
+
+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_env_files = [
+        ":cargo_toml_env_vars",
+    ],
+    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-linux-android": [],
+        "@rules_rust//rust/platform:aarch64-pc-windows-msvc": [],
+        "@rules_rust//rust/platform:aarch64-unknown-fuchsia": [],
+        "@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:aarch64-unknown-uefi": [],
+        "@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-wasip1": [],
+        "@rules_rust//rust/platform:x86_64-apple-darwin": [],
+        "@rules_rust//rust/platform:x86_64-apple-ios": [],
+        "@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-fuchsia": [],
+        "@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": [],
+        "@rules_rust//rust/platform:x86_64-unknown-uefi": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    version = "1.10.1",
+)
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..66e000d8
--- /dev/null
+++ b/bazel/cargo/remote/BUILD.fnv-1.0.7.bazel
@@ -0,0 +1,96 @@
+###############################################################################
+# @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//cargo:defs.bzl", "cargo_toml_env_vars")
+load("@rules_rust//rust:defs.bzl", "rust_library")
+
+package(default_visibility = ["//visibility:public"])
+
+cargo_toml_env_vars(
+    name = "cargo_toml_env_vars",
+    src = "Cargo.toml",
+)
+
+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_env_files = [
+        ":cargo_toml_env_vars",
+    ],
+    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-linux-android": [],
+        "@rules_rust//rust/platform:aarch64-pc-windows-msvc": [],
+        "@rules_rust//rust/platform:aarch64-unknown-fuchsia": [],
+        "@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:aarch64-unknown-uefi": [],
+        "@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-wasip1": [],
+        "@rules_rust//rust/platform:x86_64-apple-darwin": [],
+        "@rules_rust//rust/platform:x86_64-apple-ios": [],
+        "@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-fuchsia": [],
+        "@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": [],
+        "@rules_rust//rust/platform:x86_64-unknown-uefi": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    version = "1.0.7",
+)
diff --git a/bazel/cargo/remote/BUILD.http-1.3.1.bazel b/bazel/cargo/remote/BUILD.http-1.3.1.bazel
new file mode 100644
index 00000000..502f6e03
--- /dev/null
+++ b/bazel/cargo/remote/BUILD.http-1.3.1.bazel
@@ -0,0 +1,101 @@
+###############################################################################
+# @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//cargo:defs.bzl", "cargo_toml_env_vars")
+load("@rules_rust//rust:defs.bzl", "rust_library")
+
+package(default_visibility = ["//visibility:public"])
+
+cargo_toml_env_vars(
+    name = "cargo_toml_env_vars",
+    src = "Cargo.toml",
+)
+
+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_env_files = [
+        ":cargo_toml_env_vars",
+    ],
+    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-linux-android": [],
+        "@rules_rust//rust/platform:aarch64-pc-windows-msvc": [],
+        "@rules_rust//rust/platform:aarch64-unknown-fuchsia": [],
+        "@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:aarch64-unknown-uefi": [],
+        "@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-wasip1": [],
+        "@rules_rust//rust/platform:x86_64-apple-darwin": [],
+        "@rules_rust//rust/platform:x86_64-apple-ios": [],
+        "@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-fuchsia": [],
+        "@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": [],
+        "@rules_rust//rust/platform:x86_64-unknown-uefi": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    version = "1.3.1",
+    deps = [
+        "@crates_vendor__bytes-1.10.1//:bytes",
+        "@crates_vendor__fnv-1.0.7//:fnv",
+        "@crates_vendor__itoa-1.0.15//:itoa",
+    ],
+)
diff --git a/bazel/cargo/remote/BUILD.itoa-1.0.15.bazel b/bazel/cargo/remote/BUILD.itoa-1.0.15.bazel
new file mode 100644
index 00000000..f781307a
--- /dev/null
+++ b/bazel/cargo/remote/BUILD.itoa-1.0.15.bazel
@@ -0,0 +1,92 @@
+###############################################################################
+# @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//cargo:defs.bzl", "cargo_toml_env_vars")
+load("@rules_rust//rust:defs.bzl", "rust_library")
+
+package(default_visibility = ["//visibility:public"])
+
+cargo_toml_env_vars(
+    name = "cargo_toml_env_vars",
+    src = "Cargo.toml",
+)
+
+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_env_files = [
+        ":cargo_toml_env_vars",
+    ],
+    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-linux-android": [],
+        "@rules_rust//rust/platform:aarch64-pc-windows-msvc": [],
+        "@rules_rust//rust/platform:aarch64-unknown-fuchsia": [],
+        "@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:aarch64-unknown-uefi": [],
+        "@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-wasip1": [],
+        "@rules_rust//rust/platform:x86_64-apple-darwin": [],
+        "@rules_rust//rust/platform:x86_64-apple-ios": [],
+        "@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-fuchsia": [],
+        "@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": [],
+        "@rules_rust//rust/platform:x86_64-unknown-uefi": [],
+        "//conditions:default": ["@platforms//:incompatible"],
+    }),
+    version = "1.0.15",
+)
diff --git a/bazel/cargo/remote/defs.bzl b/bazel/cargo/remote/defs.bzl
index 80b74d6b..ab786d99 100644
--- a/bazel/cargo/remote/defs.bzl
+++ b/bazel/cargo/remote/defs.bzl
@@ -295,7 +295,9 @@ def aliases(
 _NORMAL_DEPENDENCIES = {
     "": {
         _COMMON_CONDITION: {
+            "bytes": Label("@crates_vendor//:bytes-1.10.1"),
             "hashbrown": Label("@crates_vendor//:hashbrown-0.15.3"),
+            "http": Label("@crates_vendor//:http-1.3.1"),
             "log": Label("@crates_vendor//:log-0.4.27"),
         },
     },
@@ -415,6 +417,16 @@ def crate_repositories():
         build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.allocator-api2-0.2.21.bazel"),
     )
 
+    maybe(
+        http_archive,
+        name = "crates_vendor__bytes-1.10.1",
+        sha256 = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a",
+        type = "tar.gz",
+        urls = ["https://static.crates.io/crates/bytes/1.10.1/download"],
+        strip_prefix = "bytes-1.10.1",
+        build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.bytes-1.10.1.bazel"),
+    )
+
     maybe(
         http_archive,
         name = "crates_vendor__equivalent-1.0.2",
@@ -425,6 +437,16 @@ def crate_repositories():
         build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.equivalent-1.0.2.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.5",
@@ -445,6 +467,26 @@ def crate_repositories():
         build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.hashbrown-0.15.3.bazel"),
     )
 
+    maybe(
+        http_archive,
+        name = "crates_vendor__http-1.3.1",
+        sha256 = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565",
+        type = "tar.gz",
+        urls = ["https://static.crates.io/crates/http/1.3.1/download"],
+        strip_prefix = "http-1.3.1",
+        build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.http-1.3.1.bazel"),
+    )
+
+    maybe(
+        http_archive,
+        name = "crates_vendor__itoa-1.0.15",
+        sha256 = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c",
+        type = "tar.gz",
+        urls = ["https://static.crates.io/crates/itoa/1.0.15/download"],
+        strip_prefix = "itoa-1.0.15",
+        build_file = Label("@proxy_wasm_rust_sdk//bazel/cargo/remote:BUILD.itoa-1.0.15.bazel"),
+    )
+
     maybe(
         http_archive,
         name = "crates_vendor__log-0.4.27",
@@ -456,6 +498,8 @@ def crate_repositories():
     )
 
     return [
+        struct(repo = "crates_vendor__bytes-1.10.1", is_dev_dep = False),
         struct(repo = "crates_vendor__hashbrown-0.15.3", is_dev_dep = False),
+        struct(repo = "crates_vendor__http-1.3.1", is_dev_dep = False),
         struct(repo = "crates_vendor__log-0.4.27", 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("<non-printable>")
+            );
         }
 
         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("<non-printable>")
+            );
         }
         Action::Continue
     }
diff --git a/src/hostcalls.rs b/src/hostcalls.rs
index 76c3513d..a259d946 100644
--- a/src/hostcalls.rs
+++ b/src/hostcalls.rs
@@ -145,6 +145,27 @@ extern "C" {
     ) -> Status;
 }
 
+#[cfg(feature = "header-value")]
+pub fn get_map(map_type: MapType) -> Result<Vec<(String, HeaderValue)>, 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 =
+                        bytes::Bytes::from(std::slice::from_raw_parts(return_data, 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<Vec<(String, String)>, Status> {
     unsafe {
         let mut return_data: *mut u8 = null_mut();
@@ -189,7 +210,10 @@ extern "C" {
     ) -> Status;
 }
 
-pub fn set_map(map_type: MapType, map: Vec<(&str, &str)>) -> Result<(), Status> {
+pub fn set_map<V>(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 +224,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 +237,38 @@ extern "C" {
     ) -> Status;
 }
 
+#[cfg(feature = "header-value")]
+pub fn get_map_value(map_type: MapType, key: &str) -> Result<Option<HeaderValue>, 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(Some(HeaderValue::from_static("")))
+                }
+            }
+            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<Option<String>, Status> {
     let mut return_data: *mut u8 = null_mut();
     let mut return_size: usize = 0;
@@ -305,15 +355,18 @@ extern "C" {
     ) -> Status;
 }
 
-pub fn set_map_value(map_type: MapType, key: &str, value: Option<&str>) -> Result<(), Status> {
+pub fn set_map_value<V>(map_type: MapType, key: &str, value: Option<V>) -> 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),
@@ -332,25 +385,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" {
@@ -363,14 +398,17 @@ extern "C" {
     ) -> Status;
 }
 
-pub fn add_map_value(map_type: MapType, key: &str, value: &str) -> Result<(), Status> {
+pub fn add_map_value<V>(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),
@@ -379,18 +417,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" {
@@ -714,11 +741,14 @@ extern "C" {
     ) -> Status;
 }
 
-pub fn send_http_response(
+pub fn send_http_response<V>(
     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(
@@ -775,13 +805,16 @@ extern "C" {
     ) -> Status;
 }
 
-pub fn dispatch_http_call(
+pub fn dispatch_http_call<V>(
     upstream: &str,
-    headers: Vec<(&str, &str)>,
+    headers: Vec<(&str, V)>,
     body: Option<&[u8]>,
-    trailers: Vec<(&str, &str)>,
+    trailers: Vec<(&str, V)>,
     timeout: Duration,
-) -> Result<u32, Status> {
+) -> Result<u32, Status>
+where
+    V: AsRef<[u8]>,
+{
     let serialized_headers = utils::serialize_map(&headers);
     let serialized_trailers = utils::serialize_map(&trailers);
     let mut return_token: u32 = 0;
@@ -1149,6 +1182,10 @@ 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;
+    #[cfg(feature = "header-value")]
+    use bytes::Buf;
     use std::convert::TryFrom;
 
     pub(super) fn serialize_property_path(path: Vec<&str>) -> Bytes {
@@ -1168,46 +1205,60 @@ mod utils {
         bytes
     }
 
-    pub(super) fn serialize_map(map: &[(&str, &str)]) -> Bytes {
+    pub(super) fn serialize_map<V>(map: &[(&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() as u32).to_le_bytes());
         for (name, value) in map {
             bytes.extend_from_slice(&(name.len() as u32).to_le_bytes());
-            bytes.extend_from_slice(&(value.len() as u32).to_le_bytes());
+            bytes.extend_from_slice(&(value.as_ref().len() as u32).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: &[(&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() as u32).to_le_bytes());
-        for (name, value) in map {
-            bytes.extend_from_slice(&(name.len() as u32).to_le_bytes());
-            bytes.extend_from_slice(&(value.len() as u32).to_le_bytes());
+        serialize_map(map)
+    }
+
+    #[cfg(feature = "header-value")]
+    pub(super) fn deserialize_map(mut bytes: bytes::Bytes) -> Vec<(String, HeaderValue)> {
+        if bytes.is_empty() {
+            return Vec::new();
         }
-        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 = 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.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) },
+            ));
         }
-        bytes
+        map
     }
 
+    #[cfg(not(feature = "header-value"))]
     pub(super) fn deserialize_map(bytes: &[u8]) -> Vec<(String, String)> {
         if bytes.is_empty() {
             return Vec::new();
@@ -1297,23 +1348,32 @@ mod utils {
             112, 114, 111, 120, 121, 45, 119, 97, 115, 109, 0,
         ];
 
+        #[rustfmt::skip]
+        static SERIALIZED_EMPTY_MAP: &[u8] = &[
+            // num entries
+            0, 0, 0, 0,
+        ];
+
         #[test]
         fn test_serialize_map_empty() {
-            let serialized_map = serialize_map(&[]);
-            assert_eq!(serialized_map, [0, 0, 0, 0]);
+            let serialized_map = serialize_map::<&str>(&[]);
+            assert_eq!(serialized_map, SERIALIZED_EMPTY_MAP);
         }
 
         #[test]
         fn test_serialize_map_empty_bytes() {
             let serialized_map = serialize_map_bytes(&[]);
-            assert_eq!(serialized_map, [0, 0, 0, 0]);
+            assert_eq!(serialized_map, SERIALIZED_EMPTY_MAP);
         }
 
         #[test]
         fn test_deserialize_map_empty() {
-            let map = deserialize_map(&[]);
+            let empty_map: &[u8] = &[];
+            #[allow(clippy::useless_conversion)]
+            let map = deserialize_map(empty_map.into());
             assert_eq!(map, []);
-            let map = deserialize_map(&[0, 0, 0, 0]);
+            #[allow(clippy::useless_conversion)]
+            let map = deserialize_map(SERIALIZED_EMPTY_MAP.into());
             assert_eq!(map, []);
         }
 
@@ -1321,7 +1381,7 @@ mod utils {
         fn test_deserialize_map_empty_bytes() {
             let map = deserialize_map_bytes(&[]);
             assert_eq!(map, []);
-            let map = deserialize_map_bytes(&[0, 0, 0, 0]);
+            let map = deserialize_map_bytes(SERIALIZED_EMPTY_MAP);
             assert_eq!(map, []);
         }
 
@@ -1340,7 +1400,8 @@ mod utils {
 
         #[test]
         fn test_deserialize_map() {
-            let map = deserialize_map(SERIALIZED_MAP);
+            #[allow(clippy::useless_conversion)]
+            let map = deserialize_map(SERIALIZED_MAP.into());
             assert_eq!(map.len(), MAP.len());
             for (got, expected) in map.into_iter().zip(MAP) {
                 assert_eq!(got.0, expected.0);
@@ -1360,9 +1421,10 @@ mod utils {
 
         #[test]
         fn test_deserialize_map_roundtrip() {
-            let map = deserialize_map(SERIALIZED_MAP);
+            #[allow(clippy::useless_conversion)]
+            let map = deserialize_map(SERIALIZED_MAP.into());
             // TODO(v0.3): fix arguments, so that maps can be reused without conversion.
-            let map_refs: Vec<(&str, &str)> =
+            let map_refs: Vec<(&str, &[u8])> =
                 map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect();
             let serialized_map = serialize_map(&map_refs);
             assert_eq!(serialized_map, SERIALIZED_MAP);
@@ -1378,12 +1440,29 @@ mod utils {
             assert_eq!(serialized_map, SERIALIZED_MAP);
         }
 
+        #[cfg(feature = "header-value")]
+        #[test]
+        fn test_deserialize_map_all_chars() {
+            // We're intentionally accepting all values to support hosts and proxies that
+            // don't enforce strict RFC compliance on HTTP field values.
+            for i in 0..0xff {
+                let serialized_src: &[u8] = &[1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0];
+                let map = deserialize_map(serialized_src.to_vec().into());
+                // TODO(v0.3): fix arguments, so that maps can be reused without conversion.
+                let map_refs: Vec<(&str, &[u8])> =
+                    map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect();
+                let serialized_map = serialize_map(&map_refs);
+                assert_eq!(serialized_map, serialized_src);
+            }
+        }
+
+        #[cfg(not(feature = "header-value"))]
         #[test]
         fn test_deserialize_map_all_chars() {
             // 0x00-0x7f are valid single-byte UTF-8 characters.
             for i in 0..0x7f {
-                let serialized_src = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0];
-                let map = deserialize_map(&serialized_src);
+                let serialized_src: &[u8] = &[1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0];
+                let map = deserialize_map(serialized_src);
                 // TODO(v0.3): fix arguments, so that maps can be reused without conversion.
                 let map_refs: Vec<(&str, &str)> =
                     map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect();
@@ -1392,10 +1471,10 @@ mod utils {
             }
             // 0x80-0xff are invalid single-byte UTF-8 characters.
             for i in 0x80..0xff {
-                let serialized_src = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0];
+                let serialized_src: &[u8] = &[1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0];
                 std::panic::set_hook(Box::new(|_| {}));
                 let result = std::panic::catch_unwind(|| {
-                    deserialize_map(&serialized_src);
+                    deserialize_map(serialized_src);
                 });
                 assert!(result.is_err());
             }
@@ -1405,8 +1484,8 @@ mod utils {
         fn test_deserialize_map_all_chars_bytes() {
             // All 256 single-byte characters are allowed when emitting bytes.
             for i in 0..0xff {
-                let serialized_src = [1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0];
-                let map = deserialize_map_bytes(&serialized_src);
+                let serialized_src: &[u8] = &[1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 99, 0, i, 0];
+                let map = deserialize_map_bytes(serialized_src);
                 // TODO(v0.3): fix arguments, so that maps can be reused without conversion.
                 let map_refs: Vec<(&str, &[u8])> =
                     map.iter().map(|x| (x.0.as_ref(), x.1.as_ref())).collect();
@@ -1433,6 +1512,17 @@ mod utils {
             });
         }
 
+        #[cfg(feature = "header-value")]
+        #[cfg(nightly)]
+        #[bench]
+        fn bench_deserialize_map(b: &mut Bencher) {
+            let serialized_map: bytes::Bytes = SERIALIZED_MAP.into();
+            b.iter(|| {
+                deserialize_map(test::black_box(serialized_map.clone()));
+            });
+        }
+
+        #[cfg(not(feature = "header-value"))]
         #[cfg(nightly)]
         #[bench]
         fn bench_deserialize_map(b: &mut Bencher) {
diff --git a/src/traits.rs b/src/traits.rs
index f43663a1..2724297e 100644
--- a/src/traits.rs
+++ b/src/traits.rs
@@ -62,6 +62,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<u32, Status> {
+        hostcalls::dispatch_http_call(upstream, headers, body, trailers, timeout)
+    }
+
+    #[cfg(not(feature = "header-value"))]
     fn dispatch_http_call(
         &self,
         upstream: &str,
@@ -82,6 +95,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()
     }
@@ -90,6 +109,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<HeaderValue> {
+        hostcalls::get_map_value(MapType::HttpCallResponseTrailers, name).unwrap()
+    }
+
+    #[cfg(not(feature = "header-value"))]
     fn get_http_call_response_header(&self, name: &str) -> Option<String> {
         hostcalls::get_map_value(MapType::HttpCallResponseHeaders, name).unwrap()
     }
@@ -102,6 +127,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()
     }
@@ -110,6 +141,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<HeaderValue> {
+        hostcalls::get_map_value(MapType::HttpCallResponseTrailers, name).unwrap()
+    }
+
+    #[cfg(not(feature = "header-value"))]
     fn get_http_call_response_trailer(&self, name: &str) -> Option<String> {
         hostcalls::get_map_value(MapType::HttpCallResponseTrailers, name).unwrap()
     }
@@ -317,6 +354,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()
     }
@@ -325,6 +368,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()
     }
@@ -333,6 +382,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<HeaderValue> {
+        hostcalls::get_map_value(MapType::HttpRequestHeaders, name).unwrap()
+    }
+
+    #[cfg(not(feature = "header-value"))]
     fn get_http_request_header(&self, name: &str) -> Option<String> {
         hostcalls::get_map_value(MapType::HttpRequestHeaders, name).unwrap()
     }
@@ -341,6 +396,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()
     }
@@ -349,6 +410,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()
     }
@@ -377,6 +444,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()
     }
@@ -385,6 +458,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()
     }
@@ -393,6 +472,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<HeaderValue> {
+        hostcalls::get_map_value(MapType::HttpRequestTrailers, name).unwrap()
+    }
+
+    #[cfg(not(feature = "header-value"))]
     fn get_http_request_trailer(&self, name: &str) -> Option<String> {
         hostcalls::get_map_value(MapType::HttpRequestTrailers, name).unwrap()
     }
@@ -401,6 +486,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()
     }
@@ -409,6 +500,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()
     }
@@ -433,6 +530,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()
     }
@@ -441,6 +544,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()
     }
@@ -449,6 +558,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<HeaderValue> {
+        hostcalls::get_map_value(MapType::HttpResponseHeaders, name).unwrap()
+    }
+
+    #[cfg(not(feature = "header-value"))]
     fn get_http_response_header(&self, name: &str) -> Option<String> {
         hostcalls::get_map_value(MapType::HttpResponseHeaders, name).unwrap()
     }
@@ -457,6 +572,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()
     }
@@ -465,6 +586,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()
     }
@@ -493,6 +620,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()
     }
@@ -501,6 +634,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()
     }
@@ -509,6 +648,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<HeaderValue> {
+        hostcalls::get_map_value(MapType::HttpResponseTrailers, name).unwrap()
+    }
+
+    #[cfg(not(feature = "header-value"))]
     fn get_http_response_trailer(&self, name: &str) -> Option<String> {
         hostcalls::get_map_value(MapType::HttpResponseTrailers, name).unwrap()
     }
@@ -517,6 +662,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()
     }
@@ -525,6 +676,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()
     }
@@ -545,6 +702,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 4272ba90..a8e78f61 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -140,3 +140,6 @@ pub enum GrpcStatusCode {
 }
 
 pub type Bytes = Vec<u8>;
+
+#[cfg(feature = "header-value")]
+pub use http::HeaderValue;