From dc43b24f14bba924e52c9c009efde2f4ff1891bc Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Fri, 10 Nov 2023 08:56:10 -0700 Subject: [PATCH] [Backport to v2.0] fix non-streaming outgoing POST requests in Rust SDK This was my fault for over-simplifying the code in `spin_sdk::http::send`. I've added a test to make sure this stays fixed. Fixed #2080 Signed-off-by: Joel Dice --- Cargo.lock | 1 + Cargo.toml | 1 + build.rs | 2 + .../.cargo/config.toml | 2 + examples/http-rust-outbound-post/Cargo.lock | 723 ++++++++++++++++++ examples/http-rust-outbound-post/Cargo.toml | 17 + examples/http-rust-outbound-post/spin.toml | 18 + examples/http-rust-outbound-post/src/lib.rs | 30 + sdk/rust/src/http.rs | 9 +- sdk/rust/src/http/executor.rs | 1 - tests/http/headers-env-routes-test/Cargo.lock | 2 +- tests/http/simple-spin-rust/Cargo.lock | 2 +- tests/http/vault-variables-test/Cargo.lock | 2 +- tests/integration.rs | 85 +- .../http-rust-outbound-redis/Cargo.lock | 2 +- 15 files changed, 886 insertions(+), 11 deletions(-) create mode 100644 examples/http-rust-outbound-post/.cargo/config.toml create mode 100644 examples/http-rust-outbound-post/Cargo.lock create mode 100644 examples/http-rust-outbound-post/Cargo.toml create mode 100644 examples/http-rust-outbound-post/spin.toml create mode 100644 examples/http-rust-outbound-post/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1191cd0198..c50558a2ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5637,6 +5637,7 @@ dependencies = [ "futures", "glob", "hex", + "http-body-util", "hyper 1.0.0-rc.3", "indicatif 0.17.6", "is-terminal", diff --git a/Cargo.toml b/Cargo.toml index 2acf7c4205..66b792c7fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -86,6 +86,7 @@ hyper = { workspace = true } sha2 = "0.10.1" which = "4.2.5" e2e-testing = { path = "crates/e2e-testing" } +http-body-util = { workspace = true } [build-dependencies] cargo-target-dep = { git = "https://github.com/fermyon/cargo-target-dep", rev = "b7b1989fe0984c0f7c4966398304c6538e52fe49" } diff --git a/build.rs b/build.rs index 48c5ca8a27..ec5ebbec6b 100644 --- a/build.rs +++ b/build.rs @@ -13,6 +13,7 @@ const RUST_HTTP_VAULT_VARIABLES_TEST: &str = "tests/http/vault-variables-test"; const RUST_OUTBOUND_REDIS_INTEGRATION_TEST: &str = "tests/outbound-redis/http-rust-outbound-redis"; const TIMER_TRIGGER_INTEGRATION_TEST: &str = "examples/spin-timer/app-example"; const WASI_HTTP_INTEGRATION_TEST: &str = "examples/wasi-http-rust-streaming-outgoing-body"; +const OUTBOUND_HTTP_POST_INTEGRATION_TEST: &str = "examples/http-rust-outbound-post"; fn main() { // Extract environment information to be passed to plugins. @@ -92,6 +93,7 @@ error: the `wasm32-wasi` target is not installed cargo_build(RUST_OUTBOUND_REDIS_INTEGRATION_TEST); cargo_build(TIMER_TRIGGER_INTEGRATION_TEST); cargo_build(WASI_HTTP_INTEGRATION_TEST); + cargo_build(OUTBOUND_HTTP_POST_INTEGRATION_TEST); } fn build_wasm_test_program(name: &'static str, root: &'static str) { diff --git a/examples/http-rust-outbound-post/.cargo/config.toml b/examples/http-rust-outbound-post/.cargo/config.toml new file mode 100644 index 0000000000..6b77899cb3 --- /dev/null +++ b/examples/http-rust-outbound-post/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +target = "wasm32-wasi" diff --git a/examples/http-rust-outbound-post/Cargo.lock b/examples/http-rust-outbound-post/Cargo.lock new file mode 100644 index 0000000000..65034ef19a --- /dev/null +++ b/examples/http-rust-outbound-post/Cargo.lock @@ -0,0 +1,723 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "anyhow" +version = "1.0.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" + +[[package]] +name = "async-trait" +version = "0.1.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "bitflags" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2bd12c1caf447e69cd4528f47f94d203fd2582878ecb9e9465484c4148a8223" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cpufeatures" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[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 = "form_urlencoded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a62bc1cf6f830c2ec14a513a9fb124d0a213a629668a4186f329db21fe045652" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0290714b38af9b4a7b094b8a37086d1b4e61f2df9122c3cad2577669145335" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4dd66668b557604244583e3e1e1eada8c5c2e96a6d0d6653ede395b78bbacb" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb1d22c66e66d9d72e1758f0bd7d4fd0bee04cad842ee34587d68c07e45d088c" + +[[package]] +name = "futures-executor" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f4fb8693db0cf099eadcca0efe2a5a22e4550f98ed16aba6c48700da29597bc" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bf34a163b5c4c52d0478a4d757da8fb65cabef42ba90515efee0f6f9fa45aaa" + +[[package]] +name = "futures-macro" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b153fd91e4b0147f4aced87be237c98248656bb01050b96bf3ee89220a8ddb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "futures-sink" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36d3378ee38c2a36ad710c5d30c2911d752cb941c00c72dbabfb786a7970817" + +[[package]] +name = "futures-task" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "efd193069b0ddadc69c46389b740bbccdd97203899b48d09c5f7969591d6bae2" + +[[package]] +name = "futures-util" +version = "0.3.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19526d624e703a3179b3d322efec918b6246ea0fa51d41124525f00f1cc8104" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "hashbrown" +version = "0.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f93e7192158dbcda357bdec5fb5788eebf8bbac027f3f33e719d29135ae84156" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f95b9abcae896730d42b78e09c155ed4ddf82c07b4de772c64aee5b2d8b7c150" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "idna" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d20d6b07bfbc108882d88ed8e37d39636dcc260e15e30c45e6ba089610b917c" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" +dependencies = [ + "equivalent", + "hashbrown", + "serde", +] + +[[package]] +name = "itoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + +[[package]] +name = "memchr" +version = "2.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" + +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + +[[package]] +name = "percent-encoding" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" + +[[package]] +name = "pin-project-lite" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "proc-macro2" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "routefinder" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94f8f99b10dedd317514253dda1fa7c14e344aac96e1f78149a64879ce282aca" +dependencies = [ + "smartcow", + "smartstring", +] + +[[package]] +name = "ryu" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" + +[[package]] +name = "semver" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" + +[[package]] +name = "serde" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.192" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "serde_json" +version = "1.0.108" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dccd0940a2dcdf68d092b8cbab7dc0ad8fa938bf95787e1b916b0e3d0e8e970" + +[[package]] +name = "smartcow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "656fcb1c1fca8c4655372134ce87d8afdf5ec5949ebabe8d314be0141d8b5da2" +dependencies = [ + "smartstring", +] + +[[package]] +name = "smartstring" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fb72c633efbaa2dd666986505016c32c3044395ceaf881518399d2f4127ee29" +dependencies = [ + "autocfg", + "static_assertions", + "version_check", +] + +[[package]] +name = "spdx" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b19b32ed6d899ab23174302ff105c1577e45a06b08d4fe0a9dd13ce804bbbf71" +dependencies = [ + "smallvec", +] + +[[package]] +name = "spin-macro" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes", + "http", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "spin-outbound-post" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures", + "hex", + "sha2", + "spin-sdk", + "url", +] + +[[package]] +name = "spin-sdk" +version = "2.0.0" +dependencies = [ + "anyhow", + "async-trait", + "bytes", + "form_urlencoded", + "futures", + "http", + "once_cell", + "routefinder", + "serde", + "serde_json", + "spin-macro", + "thiserror", + "wit-bindgen", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9a7210f5c9a7156bb50aa36aed4c95afb51df0df00713949448cf9e97d382d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "266b2e40bc00e5a6c09c3584011e08b06f123c00362c92b975ba9843aaaa14b8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.39", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unicode-bidi" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92888ba5573ff080736b3648696b70cafad7d250551175acbaa4e0385b3e1460" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + +[[package]] +name = "url" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143b538f18257fac9cad154828a57c6bf5157e1aa604d4816b5995bf6de87ae5" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasm-encoder" +version = "0.36.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822b645bf4f2446b949776ffca47e2af60b167209ffb70814ef8779d299cd421" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-metadata" +version = "0.10.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2167ce53b2faa16a92c6cafd4942cff16c9a4fa0c5a5a0a41131ee4e49fc055f" +dependencies = [ + "anyhow", + "indexmap", + "serde", + "serde_derive", + "serde_json", + "spdx", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.116.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a58e28b80dd8340cb07b8242ae654756161f6fc8d0038123d679b7b99964fa50" +dependencies = [ + "indexmap", + "semver", +] + +[[package]] +name = "wit-bindgen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38726c54a5d7c03cac28a2a8de1006cfe40397ddf6def3f836189033a413bc08" +dependencies = [ + "bitflags", + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen-core" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8bf1fddccaff31a1ad57432d8bfb7027a7e552969b6c68d6d8820dcf5c2371f" +dependencies = [ + "anyhow", + "wit-component", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7200e565124801e01b7b5ddafc559e1da1b2e1bed5364d669cd1d96fb88722" +dependencies = [ + "anyhow", + "heck", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae33920ad8119fe72cf59eb00f127c0b256a236b9de029a1a10397b1f38bdbd" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn 2.0.39", + "wit-bindgen-core", + "wit-bindgen-rust", + "wit-component", +] + +[[package]] +name = "wit-component" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "480cc1a078b305c1b8510f7c455c76cbd008ee49935f3a6c5fd5e937d8d95b1e" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43771ee863a16ec4ecf9da0fc65c3bbd4a1235c8e3da5f094b562894843dfa76" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", +] diff --git a/examples/http-rust-outbound-post/Cargo.toml b/examples/http-rust-outbound-post/Cargo.toml new file mode 100644 index 0000000000..412fc15578 --- /dev/null +++ b/examples/http-rust-outbound-post/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "spin-outbound-post" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +anyhow = "1.0.71" +futures = "0.3.28" +hex = "0.4.3" +sha2 = "0.10.6" +url = "2.4.0" +spin-sdk = { path = "../../sdk/rust" } + +[workspace] diff --git a/examples/http-rust-outbound-post/spin.toml b/examples/http-rust-outbound-post/spin.toml new file mode 100644 index 0000000000..7ca703f5dc --- /dev/null +++ b/examples/http-rust-outbound-post/spin.toml @@ -0,0 +1,18 @@ +spin_manifest_version = 2 + +[application] +authors = ["Fermyon Engineering "] +description = "Demonstrates outbound HTTP calls" +name = "spin-outbound-post" +version = "1.0.0" + +[[trigger.http]] +route = "/..." +component = "spin-outbound-post" + +[component.spin-outbound-post] +source = "target/wasm32-wasi/release/spin_outbound_post.wasm" +allowed_outbound_hosts = ["http://*:*", "https://*:*"] +[component.spin-outbound-post.build] +command = "cargo build --target wasm32-wasi --release" +watch = ["src/**/*.rs", "Cargo.toml"] diff --git a/examples/http-rust-outbound-post/src/lib.rs b/examples/http-rust-outbound-post/src/lib.rs new file mode 100644 index 0000000000..d5f93bafe3 --- /dev/null +++ b/examples/http-rust-outbound-post/src/lib.rs @@ -0,0 +1,30 @@ +use { + anyhow::{anyhow, Result}, + spin_sdk::{ + http::{self, IncomingResponse, IntoResponse, Method, Request, RequestBuilder, Response}, + http_component, + }, +}; + +#[http_component] +async fn handle(req: Request) -> Result { + let request = RequestBuilder::new(Method::Post, "/") + .uri( + req.header("url") + .ok_or_else(|| anyhow!("missing url header"))? + .as_str() + .ok_or_else(|| anyhow!("invalid utf-8 in url header value"))?, + ) + .method(Method::Post) + .body("Hello, world!") + .build(); + + let response: IncomingResponse = http::send(request).await?; + let status = response.status(); + + Ok(Response::builder() + .status(200) + .header("content-type", "text/plain") + .body(format!("response status: {status}")) + .build()) +} diff --git a/sdk/rust/src/http.rs b/sdk/rust/src/http.rs index dd6c6f9f9d..5705480400 100644 --- a/sdk/rust/src/http.rs +++ b/sdk/rust/src/http.rs @@ -363,7 +363,7 @@ impl HeaderValue { pub fn as_str(&self) -> Option<&str> { match &self.inner { HeaderValueRep::String(s) => Some(s), - HeaderValueRep::Bytes(_) => None, + HeaderValueRep::Bytes(b) => std::str::from_utf8(b).ok(), } } @@ -587,14 +587,13 @@ where // It is part of the contract of the trait that implementors of `TryIntoOutgoingRequest` // do not call `OutgoingRequest::write`` if they return a buffered body. let mut body_sink = request.take_body(); - let response = executor::outgoing_request_send(request) - .await - .map_err(SendError::Http)?; + let response = executor::outgoing_request_send(request); body_sink .send(body_buffer) .await .map_err(|e| SendError::Http(Error::UnexpectedError(e.to_string())))?; - response + drop(body_sink); + response.await.map_err(SendError::Http)? } else { executor::outgoing_request_send(request) .await diff --git a/sdk/rust/src/http/executor.rs b/sdk/rust/src/http/executor.rs index aedc7182d5..5852681a90 100644 --- a/sdk/rust/src/http/executor.rs +++ b/sdk/rust/src/http/executor.rs @@ -91,7 +91,6 @@ pub(crate) fn outgoing_body(body: OutgoingBody) -> impl Sink, Error = ty move |context| { let pair = pair.borrow(); let (stream, _) = &pair.0.as_ref().unwrap(); - loop { match stream.check_write() { Ok(0) => { diff --git a/tests/http/headers-env-routes-test/Cargo.lock b/tests/http/headers-env-routes-test/Cargo.lock index d57a7d2a78..490730043f 100644 --- a/tests/http/headers-env-routes-test/Cargo.lock +++ b/tests/http/headers-env-routes-test/Cargo.lock @@ -377,7 +377,7 @@ dependencies = [ [[package]] name = "spin-sdk" -version = "2.0.0-rc.1" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", diff --git a/tests/http/simple-spin-rust/Cargo.lock b/tests/http/simple-spin-rust/Cargo.lock index 058f48c78d..a25ae3c9a9 100644 --- a/tests/http/simple-spin-rust/Cargo.lock +++ b/tests/http/simple-spin-rust/Cargo.lock @@ -376,7 +376,7 @@ dependencies = [ [[package]] name = "spin-sdk" -version = "2.0.0-rc.1" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", diff --git a/tests/http/vault-variables-test/Cargo.lock b/tests/http/vault-variables-test/Cargo.lock index f56259bcd4..c705762b5f 100644 --- a/tests/http/vault-variables-test/Cargo.lock +++ b/tests/http/vault-variables-test/Cargo.lock @@ -367,7 +367,7 @@ dependencies = [ [[package]] name = "spin-sdk" -version = "2.0.0-rc.1" +version = "2.0.0" dependencies = [ "anyhow", "async-trait", diff --git a/tests/integration.rs b/tests/integration.rs index 45315517fc..14f09eafc3 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -2,6 +2,7 @@ mod integration_tests { use anyhow::{anyhow, Context, Error, Result}; use futures::{channel::oneshot, future, stream, FutureExt}; + use http_body_util::BodyExt; use hyper::{body::Bytes, server::conn::http1, service::service_fn, Method, StatusCode}; use reqwest::{Client, Response}; use sha2::{Digest, Sha256}; @@ -14,7 +15,7 @@ mod integration_tests { net::{Ipv4Addr, SocketAddrV4, TcpListener}, path::Path, process::{self, Child, Command, Output}, - sync::Arc, + sync::{Arc, Mutex}, time::Duration, }; use tempfile::tempdir; @@ -674,6 +675,88 @@ route = "/..." do_test_build_command("tests/build/simple").await } + #[tokio::test] + async fn test_outbound_post() -> Result<()> { + let listener = tokio::net::TcpListener::bind((Ipv4Addr::new(127, 0, 0, 1), 0)).await?; + + let prefix = format!("http://{}", listener.local_addr()?); + + let body = Arc::new(Mutex::new(Vec::new())); + + let server = { + let body = body.clone(); + async move { + loop { + let (stream, _) = listener.accept().await?; + let body = body.clone(); + task::spawn(async move { + if let Err(e) = http1::Builder::new() + .keep_alive(true) + .serve_connection( + stream, + service_fn( + move |request: hyper::Request| { + let body = body.clone(); + async move { + if let &Method::POST = request.method() { + let req_body = request.into_body(); + let bytes = + req_body.collect().await?.to_bytes().to_vec(); + *body.lock().unwrap() = bytes; + Ok::<_, Error>(hyper::Response::new(body::empty())) + } else { + Ok(hyper::Response::builder() + .status(StatusCode::METHOD_NOT_ALLOWED) + .body(body::empty())?) + } + } + }, + ), + ) + .await + { + log::warn!("{e:?}"); + } + }); + + // Help rustc with type inference: + if false { + return Ok::<_, Error>(()); + } + } + } + } + .then(|result| { + if let Err(e) = result { + log::warn!("{e:?}"); + } + future::ready(()) + }) + .boxed(); + + let (_tx, rx) = oneshot::channel::<()>(); + + task::spawn(async move { + drop(future::select(server, rx).await); + }); + let controller = SpinTestController::with_manifest( + "examples/http-rust-outbound-post/spin.toml", + &[], + &[], + ) + .await?; + + let response = Client::new() + .get(format!("http://{}/", controller.url)) + .header("url", format!("{prefix}/")) + .send() + .await?; + assert_eq!(200, response.status()); + assert_eq!(b"Hello, world!", body.lock().unwrap().as_slice()); + + Ok(()) + } + #[tokio::test] async fn test_wasi_http_hash_all() -> Result<()> { let bodies = Arc::new( diff --git a/tests/outbound-redis/http-rust-outbound-redis/Cargo.lock b/tests/outbound-redis/http-rust-outbound-redis/Cargo.lock index c967fa6eee..73eeb114cf 100644 --- a/tests/outbound-redis/http-rust-outbound-redis/Cargo.lock +++ b/tests/outbound-redis/http-rust-outbound-redis/Cargo.lock @@ -377,7 +377,7 @@ dependencies = [ [[package]] name = "spin-sdk" -version = "2.0.0-rc.1" +version = "2.0.0" dependencies = [ "anyhow", "async-trait",