From 3a5deb5c6fc7ae1354cc5c59773055e85519a3cc Mon Sep 17 00:00:00 2001 From: Conrad Irwin Date: Thu, 3 Oct 2024 01:00:48 +0530 Subject: [PATCH] Replace isahc with async ureq (#18414) REplace isahc with ureq everywhere gpui is used. This should allow us to make http requests without libssl; and avoid a long-tail of panics caused by ishac. Release Notes: - (potentially breaking change) updated our http client --------- Co-authored-by: Mikayla --- Cargo.lock | 612 ++++++++++++------ Cargo.toml | 17 +- crates/client/Cargo.toml | 3 +- crates/client/src/client.rs | 27 +- crates/collab/Cargo.toml | 2 +- crates/collab/src/api/events.rs | 40 +- crates/collab/src/llm.rs | 12 +- crates/collab/src/rpc.rs | 6 +- crates/evals/Cargo.toml | 2 +- crates/evals/src/eval.rs | 7 +- crates/extension/Cargo.toml | 4 +- crates/extension/src/extension_builder.rs | 2 +- crates/extension/src/extension_store_test.rs | 52 +- crates/extension_cli/Cargo.toml | 2 +- crates/extension_cli/src/main.rs | 9 +- crates/http_client/Cargo.toml | 4 +- crates/http_client/src/http_client.rs | 29 +- crates/isahc_http_client/LICENSE-APACHE | 1 - .../src/isahc_http_client.rs | 105 --- crates/live_kit_server/Cargo.toml | 2 +- crates/reqwest_client/Cargo.toml | 31 + crates/reqwest_client/LICENSE-GPL | 1 + crates/reqwest_client/examples/client.rs | 16 + crates/reqwest_client/src/reqwest_client.rs | 232 +++++++ crates/semantic_index/Cargo.toml | 2 +- crates/semantic_index/examples/index.rs | 7 +- .../Cargo.toml | 16 +- crates/ureq_client/LICENSE-GPL | 1 + crates/ureq_client/examples/client.rs | 24 + crates/ureq_client/src/ureq_client.rs | 187 ++++++ crates/vim/Cargo.toml | 2 +- crates/zed/Cargo.toml | 2 +- crates/zed/src/main.rs | 10 +- 33 files changed, 1068 insertions(+), 401 deletions(-) delete mode 120000 crates/isahc_http_client/LICENSE-APACHE delete mode 100644 crates/isahc_http_client/src/isahc_http_client.rs create mode 100644 crates/reqwest_client/Cargo.toml create mode 120000 crates/reqwest_client/LICENSE-GPL create mode 100644 crates/reqwest_client/examples/client.rs create mode 100644 crates/reqwest_client/src/reqwest_client.rs rename crates/{isahc_http_client => ureq_client}/Cargo.toml (52%) create mode 120000 crates/ureq_client/LICENSE-GPL create mode 100644 crates/ureq_client/examples/client.rs create mode 100644 crates/ureq_client/src/ureq_client.rs diff --git a/Cargo.lock b/Cargo.lock index b69e4541cc..a96e59df34 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -846,8 +846,8 @@ dependencies = [ "chrono", "futures-util", "http-types", - "hyper", - "hyper-rustls", + "hyper 0.14.30", + "hyper-rustls 0.24.2", "serde", "serde_json", "serde_path_to_error", @@ -880,15 +880,14 @@ checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" [[package]] name = "async-tls" -version = "0.12.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfeefd0ca297cbbb3bd34fd6b228401c2a5177038257afd751bc29f0a2da4795" +checksum = "b2ae3c9eba89d472a0e4fe1dea433df78fbbe63d2b764addaf2ba3a6bde89a5e" dependencies = [ "futures-core", "futures-io", - "rustls 0.20.9", + "rustls 0.21.12", "rustls-pemfile 1.0.4", - "webpki", "webpki-roots 0.22.6", ] @@ -905,9 +904,9 @@ dependencies = [ [[package]] name = "async-tungstenite" -version = "0.23.0" +version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e9efbe14612da0a19fb983059a0b621e9cf6225d7018ecab4f9988215540dc" +checksum = "90e661b6cb0a6eb34d02c520b052daa3aa9ac0cc02495c9d066bbce13ead132b" dependencies = [ "async-std", "async-tls", @@ -915,7 +914,7 @@ dependencies = [ "futures-util", "log", "pin-project-lite", - "tungstenite 0.20.1", + "tungstenite 0.24.0", ] [[package]] @@ -1064,7 +1063,7 @@ dependencies = [ "fastrand 2.1.1", "hex", "http 0.2.12", - "ring 0.17.8", + "ring", "time", "tokio", "tracing", @@ -1233,7 +1232,7 @@ dependencies = [ "once_cell", "p256", "percent-encoding", - "ring 0.17.8", + "ring", "sha2", "subtle", "time", @@ -1336,13 +1335,13 @@ dependencies = [ "aws-smithy-types", "bytes 1.7.1", "fastrand 2.1.1", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "http-body 1.0.1", "httparse", - "hyper", - "hyper-rustls", + "hyper 0.14.30", + "hyper-rustls 0.24.2", "once_cell", "pin-project-lite", "pin-utils", @@ -1432,7 +1431,7 @@ dependencies = [ "headers", "http 0.2.12", "http-body 0.4.6", - "hyper", + "hyper 0.14.30", "itoa", "matchit", "memchr", @@ -1445,7 +1444,7 @@ dependencies = [ "serde_path_to_error", "serde_urlencoded", "sha1", - "sync_wrapper", + "sync_wrapper 0.1.2", "tokio", "tokio-tungstenite 0.20.1", "tower", @@ -1584,7 +1583,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.76", ] @@ -1604,7 +1603,7 @@ dependencies = [ "proc-macro2", "quote", "regex", - "rustc-hash", + "rustc-hash 1.1.0", "shlex", "syn 2.0.76", ] @@ -2100,12 +2099,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "castaway" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2698f953def977c68f935bb0dfa959375ad4638570e969e2f1e9f433cbf1af6" - [[package]] name = "cbc" version = "0.1.2" @@ -2363,8 +2356,8 @@ dependencies = [ "clickhouse-derive", "clickhouse-rs-cityhash-sys", "futures 0.3.30", - "hyper", - "hyper-tls", + "hyper 0.14.30", + "hyper-tls 0.5.0", "lz4", "sealed", "serde", @@ -2402,6 +2395,7 @@ dependencies = [ "anyhow", "async-native-tls", "async-recursion 0.3.2", + "async-tls", "async-tungstenite", "chrono", "clock", @@ -2419,8 +2413,6 @@ dependencies = [ "rand 0.8.5", "release_channel", "rpc", - "rustls 0.20.9", - "rustls-native-certs 0.8.0", "schemars", "serde", "serde_json", @@ -2567,9 +2559,8 @@ dependencies = [ "headless", "hex", "http_client", - "hyper", + "hyper 0.14.30", "indoc", - "isahc_http_client", "jsonwebtoken", "language", "language_model", @@ -2593,7 +2584,8 @@ dependencies = [ "release_channel", "remote", "remote_server", - "reqwest", + "reqwest 0.11.27", + "reqwest_client", "rpc", "rustc-demangle", "scrypt", @@ -2677,7 +2669,7 @@ dependencies = [ name = "collections" version = "0.1.0" dependencies = [ - "rustc-hash", + "rustc-hash 1.1.0", ] [[package]] @@ -2995,7 +2987,7 @@ dependencies = [ "log", "rangemap", "rayon", - "rustc-hash", + "rustc-hash 1.1.0", "rustybuzz", "self_cell", "swash", @@ -3085,7 +3077,7 @@ dependencies = [ "hashbrown 0.14.5", "log", "regalloc2", - "rustc-hash", + "rustc-hash 1.1.0", "smallvec", "target-lexicon", ] @@ -3341,36 +3333,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "curl" -version = "0.4.46" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e2161dd6eba090ff1594084e95fd67aeccf04382ffea77999ea94ed42ec67b6" -dependencies = [ - "curl-sys", - "libc", - "openssl-probe", - "openssl-sys", - "schannel", - "socket2 0.5.7", - "windows-sys 0.52.0", -] - -[[package]] -name = "curl-sys" -version = "0.4.74+curl-8.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8af10b986114528fcdc4b63b6f5f021b7057618411046a4de2ba0f0149a097bf" -dependencies = [ - "cc", - "libc", - "libz-sys", - "openssl-sys", - "pkg-config", - "vcpkg", - "windows-sys 0.52.0", -] - [[package]] name = "cursor-icon" version = "1.1.0" @@ -4032,7 +3994,6 @@ dependencies = [ "git", "gpui", "http_client", - "isahc_http_client", "language", "languages", "node_runtime", @@ -4043,6 +4004,7 @@ dependencies = [ "serde_json", "settings", "smol", + "ureq_client", ] [[package]] @@ -4127,7 +4089,6 @@ dependencies = [ "gpui", "http_client", "indexed_docs", - "isahc_http_client", "language", "log", "lsp", @@ -4136,6 +4097,7 @@ dependencies = [ "paths", "project", "release_channel", + "reqwest_client", "schemars", "semantic_version", "serde", @@ -4145,8 +4107,10 @@ dependencies = [ "snippet_provider", "task", "theme", + "tokio", "toml 0.8.19", "ui", + "ureq_client", "url", "util", "wasm-encoder 0.215.0", @@ -4166,9 +4130,9 @@ dependencies = [ "env_logger", "extension", "fs", - "isahc_http_client", "language", "log", + "reqwest_client", "rpc", "serde", "serde_json", @@ -4415,7 +4379,7 @@ dependencies = [ "futures-core", "futures-sink", "nanorand", - "spin 0.9.8", + "spin", ] [[package]] @@ -5181,6 +5145,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes 1.7.1", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.4.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "2.4.1" @@ -5561,8 +5544,10 @@ dependencies = [ "anyhow", "derive_more", "futures 0.3.30", - "http 0.2.12", + "http 1.1.0", "log", + "rustls 0.21.12", + "rustls-native-certs 0.8.0", "serde", "serde_json", "smol", @@ -5603,7 +5588,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", "httparse", @@ -5617,6 +5602,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes 1.7.1", + "futures-channel", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.24.2" @@ -5625,12 +5630,29 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper", + "hyper 0.14.30", "log", "rustls 0.21.12", "rustls-native-certs 0.6.3", "tokio", - "tokio-rustls", + "tokio-rustls 0.24.1", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http 1.1.0", + "hyper 1.4.1", + "hyper-util", + "rustls 0.23.13", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.0", + "tower-service", ] [[package]] @@ -5640,12 +5662,47 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes 1.7.1", - "hyper", + "hyper 0.14.30", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes 1.7.1", + "http-body-util", + "hyper 1.4.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" +dependencies = [ + "bytes 1.7.1", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -6013,44 +6070,6 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" -[[package]] -name = "isahc" -version = "1.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" -dependencies = [ - "async-channel 1.9.0", - "castaway", - "crossbeam-utils", - "curl", - "curl-sys", - "encoding_rs", - "event-listener 2.5.3", - "futures-lite 1.13.0", - "http 0.2.12", - "log", - "mime", - "once_cell", - "polling 2.8.0", - "slab", - "sluice", - "tracing", - "tracing-futures", - "url", - "waker-fn", -] - -[[package]] -name = "isahc_http_client" -version = "0.1.0" -dependencies = [ - "anyhow", - "futures 0.3.30", - "http_client", - "isahc", - "util", -] - [[package]] name = "itertools" version = "0.10.5" @@ -6155,7 +6174,7 @@ dependencies = [ "base64 0.21.7", "js-sys", "pem", - "ring 0.17.8", + "ring", "serde", "serde_json", "simple_asn1", @@ -6406,7 +6425,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -6601,7 +6620,7 @@ dependencies = [ "prost", "prost-build", "prost-types", - "reqwest", + "reqwest 0.12.8", "serde", ] @@ -7085,7 +7104,7 @@ dependencies = [ "hexf-parse", "indexmap 2.4.0", "log", - "rustc-hash", + "rustc-hash 1.1.0", "spirv", "termcolor", "thiserror", @@ -8742,6 +8761,54 @@ dependencies = [ "zed_actions", ] +[[package]] +name = "quinn" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c7c5fdde3cdae7203427dc4f0a68fe0ed09833edc525a03456b153b79828684" +dependencies = [ + "bytes 1.7.1", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash 2.0.0", + "rustls 0.23.13", + "socket2 0.5.7", + "thiserror", + "tokio", + "tracing", +] + +[[package]] +name = "quinn-proto" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fadfaed2cd7f389d0161bb73eeb07b7b78f8691047a6f3e73caaeae55310a4a6" +dependencies = [ + "bytes 1.7.1", + "rand 0.8.5", + "ring", + "rustc-hash 2.0.0", + "rustls 0.23.13", + "slab", + "thiserror", + "tinyvec", + "tracing", +] + +[[package]] +name = "quinn-udp" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fe68c2e9e1a1234e218683dbdf9f9dfcb094113c5ac2b938dfcb9bab4c4140b" +dependencies = [ + "libc", + "once_cell", + "socket2 0.5.7", + "tracing", + "windows-sys 0.59.0", +] + [[package]] name = "quote" version = "1.0.37" @@ -9019,7 +9086,7 @@ checksum = "ad156d539c879b7a24a363a2016d77961786e71f48f2e2fc8302a92abd2429a6" dependencies = [ "hashbrown 0.13.2", "log", - "rustc-hash", + "rustc-hash 1.1.0", "slice-group-by", "smallvec", ] @@ -9196,11 +9263,11 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper", - "hyper-tls", + "hyper 0.14.30", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -9213,8 +9280,8 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", + "sync_wrapper 0.1.2", + "system-configuration 0.5.1", "tokio", "tokio-native-tls", "tower-service", @@ -9225,6 +9292,68 @@ dependencies = [ "winreg 0.50.0", ] +[[package]] +name = "reqwest" +version = "0.12.8" +source = "git+https://github.com/zed-industries/reqwest.git?rev=fd110f6998da16bbca97b6dddda9be7827c50e29#fd110f6998da16bbca97b6dddda9be7827c50e29" +dependencies = [ + "base64 0.22.1", + "bytes 1.7.1", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.6", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-rustls 0.27.3", + "hyper-tls 0.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls 0.23.13", + "rustls-pemfile 2.1.3", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration 0.6.1", + "tokio", + "tokio-native-tls", + "tokio-rustls 0.26.0", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + +[[package]] +name = "reqwest_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "bytes 1.7.1", + "futures 0.3.30", + "http_client", + "reqwest 0.12.8", + "serde", + "smol", + "tokio", +] + [[package]] name = "resvg" version = "0.41.0" @@ -9273,21 +9402,6 @@ dependencies = [ "util", ] -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.8" @@ -9298,8 +9412,8 @@ dependencies = [ "cfg-if", "getrandom 0.2.15", "libc", - "spin 0.9.8", - "untrusted 0.9.0", + "spin", + "untrusted", "windows-sys 0.52.0", ] @@ -9455,7 +9569,7 @@ dependencies = [ "futures 0.3.30", "glob", "rand 0.8.5", - "ring 0.17.8", + "ring", "serde", "serde_json", "shellexpand 3.1.0", @@ -9527,6 +9641,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +[[package]] +name = "rustc-hash" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583034fd73374156e66797ed8e5b0d5690409c9226b22d87cb7f19821c05d152" + [[package]] name = "rustc_version" version = "0.4.1" @@ -9576,18 +9696,6 @@ dependencies = [ "rustix 0.38.35", ] -[[package]] -name = "rustls" -version = "0.20.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b80e3dec595989ea8510028f30c408a4630db12c9cbb8de34203b89d6577e99" -dependencies = [ - "log", - "ring 0.16.20", - "sct", - "webpki", -] - [[package]] name = "rustls" version = "0.21.12" @@ -9595,11 +9703,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f56a14d1f48b391359b22f731fd4bd7e43c97f3c50eee276f3aa09c94784d3e" dependencies = [ "log", - "ring 0.17.8", - "rustls-webpki", + "ring", + "rustls-webpki 0.101.7", "sct", ] +[[package]] +name = "rustls" +version = "0.23.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki 0.102.8", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" version = "0.6.3" @@ -9656,8 +9778,19 @@ version = "0.101.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b6275d1ee7a1cd780b64aca7726599a1dbc893b1e64144529e55c3c2f745765" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", +] + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", ] [[package]] @@ -9771,8 +9904,8 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da046153aa2352493d6cb7da4b6e5c0c057d8a1d0a9aa8560baffdd945acd414" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -9968,7 +10101,6 @@ dependencies = [ "gpui", "heed", "http_client", - "isahc_http_client", "language", "language_model", "languages", @@ -9986,6 +10118,7 @@ dependencies = [ "tree-sitter", "ui", "unindent", + "ureq_client", "util", "workspace", "worktree", @@ -10418,17 +10551,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "sluice" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" -dependencies = [ - "async-channel 1.9.0", - "futures-core", - "futures-io", -] - [[package]] name = "smallvec" version = "1.13.2" @@ -10543,12 +10665,6 @@ dependencies = [ "smallvec", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -11178,6 +11294,15 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + [[package]] name = "synchronoise" version = "1.0.1" @@ -11218,7 +11343,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation 0.9.4", - "system-configuration-sys", + "system-configuration-sys 0.5.0", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.9.4", + "system-configuration-sys 0.6.0", ] [[package]] @@ -11231,6 +11367,16 @@ dependencies = [ "libc", ] +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -11607,7 +11753,7 @@ dependencies = [ "fancy-regex", "lazy_static", "parking_lot", - "rustc-hash", + "rustc-hash 1.1.0", ] [[package]] @@ -11822,6 +11968,17 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.13", + "rustls-pki-types", + "tokio", +] + [[package]] name = "tokio-socks" version = "0.5.2" @@ -11871,9 +12028,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes 1.7.1", "futures-core", @@ -12055,16 +12212,6 @@ dependencies = [ "valuable", ] -[[package]] -name = "tracing-futures" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" -dependencies = [ - "pin-project", - "tracing", -] - [[package]] name = "tracing-log" version = "0.2.0" @@ -12371,6 +12518,24 @@ dependencies = [ "utf-8", ] +[[package]] +name = "tungstenite" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18e5b8366ee7a95b16d32197d0b2604b43a0be89dc5fac9f8e96ccafbaedda8a" +dependencies = [ + "byteorder", + "bytes 1.7.1", + "data-encoding", + "http 1.1.0", + "httparse", + "log", + "rand 0.8.5", + "sha1", + "thiserror", + "utf-8", +] + [[package]] name = "typeid" version = "1.0.2" @@ -12529,18 +12694,43 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1766d682d402817b5ac4490b3c3002d91dfa0d22812f341609f97b08757359c" -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8cdd25c339e200129fe4de81451814e5228c9b771d57378817d6117cc2b3f97" +dependencies = [ + "base64 0.21.7", + "flate2", + "log", + "once_cell", + "rustls 0.21.12", + "rustls-webpki 0.101.7", + "url", + "webpki-roots 0.25.4", +] + +[[package]] +name = "ureq_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "futures 0.3.30", + "gpui", + "http_client", + "parking_lot", + "serde", + "smol", + "ureq", + "util", +] + [[package]] name = "url" version = "2.5.2" @@ -12844,7 +13034,7 @@ dependencies = [ "futures-util", "headers", "http 0.2.12", - "hyper", + "hyper 0.14.30", "log", "mime", "mime_guess", @@ -12980,6 +13170,19 @@ dependencies = [ "wasmparser 0.201.0", ] +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "wasmparser" version = "0.201.0" @@ -13395,8 +13598,8 @@ version = "0.22.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed63aea5ce73d0ff405984102c42de94fc55a6b75765d621c65262469b3c9b53" dependencies = [ - "ring 0.17.8", - "untrusted 0.9.0", + "ring", + "untrusted", ] [[package]] @@ -13653,6 +13856,17 @@ dependencies = [ "syn 2.0.76", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result 0.2.0", + "windows-strings", + "windows-targets 0.52.6", +] + [[package]] name = "windows-result" version = "0.1.2" @@ -14443,7 +14657,6 @@ dependencies = [ "image_viewer", "inline_completion_button", "install_cli", - "isahc_http_client", "journal", "language", "language_model", @@ -14496,6 +14709,7 @@ dependencies = [ "tree-sitter-md", "tree-sitter-rust", "ui", + "ureq_client", "url", "urlencoding", "util", diff --git a/Cargo.toml b/Cargo.toml index 1ef14dae70..fea528db5b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,7 @@ members = [ "crates/assistant", "crates/assistant_slash_command", "crates/assistant_tool", + "crates/ureq_client", "crates/audio", "crates/auto_update", "crates/breadcrumbs", @@ -52,7 +53,6 @@ members = [ "crates/indexed_docs", "crates/inline_completion_button", "crates/install_cli", - "crates/isahc_http_client", "crates/journal", "crates/language", "crates/language_model", @@ -87,6 +87,7 @@ members = [ "crates/release_channel", "crates/remote", "crates/remote_server", + "crates/reqwest_client", "crates/repl", "crates/rich_text", "crates/rope", @@ -186,6 +187,8 @@ assets = { path = "crates/assets" } assistant = { path = "crates/assistant" } assistant_slash_command = { path = "crates/assistant_slash_command" } assistant_tool = { path = "crates/assistant_tool" } +ureq_client = { path = "crates/ureq_client" } +async-compat = { version = "0.2.1" } audio = { path = "crates/audio" } auto_update = { path = "crates/auto_update" } breadcrumbs = { path = "crates/breadcrumbs" } @@ -228,7 +231,6 @@ image_viewer = { path = "crates/image_viewer" } indexed_docs = { path = "crates/indexed_docs" } inline_completion_button = { path = "crates/inline_completion_button" } install_cli = { path = "crates/install_cli" } -isahc_http_client = { path = "crates/isahc_http_client" } journal = { path = "crates/journal" } language = { path = "crates/language" } language_model = { path = "crates/language_model" } @@ -265,6 +267,7 @@ release_channel = { path = "crates/release_channel" } remote = { path = "crates/remote" } remote_server = { path = "crates/remote_server" } repl = { path = "crates/repl" } +reqwest_client = { path = "crates/reqwest_client" } rich_text = { path = "crates/rich_text" } rope = { path = "crates/rope" } rpc = { path = "crates/rpc" } @@ -325,7 +328,7 @@ async-pipe = { git = "https://github.com/zed-industries/async-pipe-rs", rev = "8 async-recursion = "1.0.0" async-tar = "0.5.0" async-trait = "0.1" -async-tungstenite = "0.23" +async-tungstenite = "0.28" async-watch = "0.3.1" async_zip = { version = "0.0.17", features = ["deflate", "deflate64"] } base64 = "0.22" @@ -364,10 +367,7 @@ ignore = "0.4.22" image = "0.25.1" indexmap = { version = "1.6.2", features = ["serde"] } indoc = "2" -# We explicitly disable http2 support in isahc. -isahc = { version = "1.7.2", default-features = false, features = [ - "text-decoding", -] } + itertools = "0.13.0" jsonwebtoken = "9.3" libc = "0.2" @@ -392,13 +392,14 @@ pulldown-cmark = { version = "0.12.0", default-features = false } rand = "0.8.5" regex = "1.5" repair_json = "0.1.0" +reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f6998da16bbca97b6dddda9be7827c50e29" } rsa = "0.9.6" runtimelib = { version = "0.15", default-features = false, features = [ "async-dispatcher-runtime", ] } rustc-demangle = "0.1.23" rust-embed = { version = "8.4", features = ["include-exclude"] } -rustls = "0.20.3" +rustls = "0.21.12" rustls-native-certs = "0.8.0" schemars = { version = "0.8", features = ["impl_json_schema"] } semver = "1.0" diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml index dd420bbbe6..c3fbea1f98 100644 --- a/crates/client/Cargo.toml +++ b/crates/client/Cargo.toml @@ -18,6 +18,7 @@ test-support = ["clock/test-support", "collections/test-support", "gpui/test-sup [dependencies] anyhow.workspace = true async-recursion = "0.3" +async-tls = "0.13" async-tungstenite = { workspace = true, features = ["async-std", "async-tls"] } chrono = { workspace = true, features = ["serde"] } clock.workspace = true @@ -34,8 +35,6 @@ postage.workspace = true rand.workspace = true release_channel.workspace = true rpc = { workspace = true, features = ["gpui"] } -rustls.workspace = true -rustls-native-certs.workspace = true schemars.workspace = true serde.workspace = true serde_json.workspace = true diff --git a/crates/client/src/client.rs b/crates/client/src/client.rs index d565d620c3..819bd7551f 100644 --- a/crates/client/src/client.rs +++ b/crates/client/src/client.rs @@ -1023,7 +1023,7 @@ impl Client { &self, http: Arc, release_channel: Option, - ) -> impl Future> { + ) -> impl Future> { #[cfg(any(test, feature = "test-support"))] let url_override = self.rpc_url.read().clone(); @@ -1117,7 +1117,7 @@ impl Client { // for us from the RPC URL. // // Among other things, it will generate and set a `Sec-WebSocket-Key` header for us. - let mut request = rpc_url.into_client_request()?; + let mut request = IntoClientRequest::into_client_request(rpc_url.as_str())?; // We then modify the request to add our desired headers. let request_headers = request.headers_mut(); @@ -1137,30 +1137,13 @@ impl Client { match url_scheme { Https => { - let client_config = { - let mut root_store = rustls::RootCertStore::empty(); - - let root_certs = rustls_native_certs::load_native_certs(); - for error in root_certs.errors { - log::warn!("error loading native certs: {:?}", error); - } - root_store.add_parsable_certificates( - &root_certs - .certs - .into_iter() - .map(|cert| cert.as_ref().to_owned()) - .collect::>(), - ); - rustls::ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(root_store) - .with_no_client_auth() - }; let (stream, _) = async_tungstenite::async_tls::client_async_tls_with_connector( request, stream, - Some(client_config.into()), + Some(async_tls::TlsConnector::from( + http_client::TLS_CONFIG.clone(), + )), ) .await?; Ok(Connection::new( diff --git a/crates/collab/Cargo.toml b/crates/collab/Cargo.toml index ad43d2d1f0..7d4c5d0c70 100644 --- a/crates/collab/Cargo.toml +++ b/crates/collab/Cargo.toml @@ -37,7 +37,7 @@ futures.workspace = true google_ai.workspace = true hex.workspace = true http_client.workspace = true -isahc_http_client.workspace = true +reqwest_client.workspace = true jsonwebtoken.workspace = true live_kit_server.workspace = true log.workspace = true diff --git a/crates/collab/src/api/events.rs b/crates/collab/src/api/events.rs index bbfa69c0b8..dd1370e886 100644 --- a/crates/collab/src/api/events.rs +++ b/crates/collab/src/api/events.rs @@ -674,7 +674,7 @@ pub struct EditorEventRow { copilot_enabled_for_language: bool, historical_event: bool, architecture: String, - is_staff: Option, + is_staff: bool, major: Option, minor: Option, patch: Option, @@ -708,7 +708,7 @@ impl EditorEventRow { installation_id: body.installation_id.clone().unwrap_or_default(), session_id: body.session_id.clone(), metrics_id: body.metrics_id.clone().unwrap_or_default(), - is_staff: body.is_staff, + is_staff: body.is_staff.unwrap_or_default(), time: time.timestamp_millis(), operation: event.operation, file_extension: event.file_extension.unwrap_or_default(), @@ -741,7 +741,7 @@ pub struct InlineCompletionEventRow { region_code: String, city: String, time: i64, - is_staff: Option, + is_staff: bool, major: Option, minor: Option, patch: Option, @@ -772,7 +772,7 @@ impl InlineCompletionEventRow { os_version: body.os_version.clone().unwrap_or_default(), installation_id: body.installation_id.clone().unwrap_or_default(), session_id: body.session_id.clone(), - is_staff: body.is_staff, + is_staff: body.is_staff.unwrap_or_default(), time: time.timestamp_millis(), file_extension: event.file_extension.unwrap_or_default(), signed_in: wrapper.signed_in, @@ -800,7 +800,7 @@ pub struct CallEventRow { // ClientEventBase installation_id: String, session_id: Option, - is_staff: Option, + is_staff: bool, time: i64, // CallEventRow @@ -832,7 +832,7 @@ impl CallEventRow { os_version: body.os_version.clone().unwrap_or_default(), installation_id: body.installation_id.clone().unwrap_or_default(), session_id: body.session_id.clone(), - is_staff: body.is_staff, + is_staff: body.is_staff.unwrap_or_default(), time: time.timestamp_millis(), operation: event.operation, room_id: event.room_id, @@ -856,7 +856,7 @@ pub struct AssistantEventRow { // ClientEventBase installation_id: Option, session_id: Option, - is_staff: Option, + is_staff: bool, time: i64, // AssistantEventRow @@ -891,7 +891,7 @@ impl AssistantEventRow { os_version: body.os_version.clone().unwrap_or_default(), installation_id: body.installation_id.clone(), session_id: body.session_id.clone(), - is_staff: body.is_staff, + is_staff: body.is_staff.unwrap_or_default(), time: time.timestamp_millis(), conversation_id: event.conversation_id.unwrap_or_default(), kind: event.kind.to_string(), @@ -909,7 +909,7 @@ impl AssistantEventRow { pub struct CpuEventRow { installation_id: Option, session_id: Option, - is_staff: Option, + is_staff: bool, usage_as_percentage: f32, core_count: u32, app_version: String, @@ -947,7 +947,7 @@ impl CpuEventRow { os_version: body.os_version.clone().unwrap_or_default(), installation_id: body.installation_id.clone(), session_id: body.session_id.clone(), - is_staff: body.is_staff, + is_staff: body.is_staff.unwrap_or_default(), time: time.timestamp_millis(), usage_as_percentage: event.usage_as_percentage, core_count: event.core_count, @@ -970,7 +970,7 @@ pub struct MemoryEventRow { // ClientEventBase installation_id: Option, session_id: Option, - is_staff: Option, + is_staff: bool, time: i64, // MemoryEventRow @@ -1001,7 +1001,7 @@ impl MemoryEventRow { os_version: body.os_version.clone().unwrap_or_default(), installation_id: body.installation_id.clone(), session_id: body.session_id.clone(), - is_staff: body.is_staff, + is_staff: body.is_staff.unwrap_or_default(), time: time.timestamp_millis(), memory_in_bytes: event.memory_in_bytes, virtual_memory_in_bytes: event.virtual_memory_in_bytes, @@ -1024,7 +1024,7 @@ pub struct AppEventRow { // ClientEventBase installation_id: Option, session_id: Option, - is_staff: Option, + is_staff: bool, time: i64, // AppEventRow @@ -1054,7 +1054,7 @@ impl AppEventRow { os_version: body.os_version.clone().unwrap_or_default(), installation_id: body.installation_id.clone(), session_id: body.session_id.clone(), - is_staff: body.is_staff, + is_staff: body.is_staff.unwrap_or_default(), time: time.timestamp_millis(), operation: event.operation, } @@ -1076,7 +1076,7 @@ pub struct SettingEventRow { // ClientEventBase installation_id: Option, session_id: Option, - is_staff: Option, + is_staff: bool, time: i64, // SettingEventRow setting: String, @@ -1106,7 +1106,7 @@ impl SettingEventRow { os_version: body.os_version.clone().unwrap_or_default(), installation_id: body.installation_id.clone(), session_id: body.session_id.clone(), - is_staff: body.is_staff, + is_staff: body.is_staff.unwrap_or_default(), time: time.timestamp_millis(), setting: event.setting, value: event.value, @@ -1129,7 +1129,7 @@ pub struct ExtensionEventRow { // ClientEventBase installation_id: Option, session_id: Option, - is_staff: Option, + is_staff: bool, time: i64, // ExtensionEventRow @@ -1164,7 +1164,7 @@ impl ExtensionEventRow { os_version: body.os_version.clone().unwrap_or_default(), installation_id: body.installation_id.clone(), session_id: body.session_id.clone(), - is_staff: body.is_staff, + is_staff: body.is_staff.unwrap_or_default(), time: time.timestamp_millis(), extension_id: event.extension_id, extension_version: event.version, @@ -1198,7 +1198,7 @@ pub struct ReplEventRow { // ClientEventBase installation_id: Option, session_id: Option, - is_staff: Option, + is_staff: bool, time: i64, // ReplEventRow @@ -1230,7 +1230,7 @@ impl ReplEventRow { os_version: body.os_version.clone().unwrap_or_default(), installation_id: body.installation_id.clone(), session_id: body.session_id.clone(), - is_staff: body.is_staff, + is_staff: body.is_staff.unwrap_or_default(), time: time.timestamp_millis(), kernel_language: event.kernel_language, kernel_status: event.kernel_status, diff --git a/crates/collab/src/llm.rs b/crates/collab/src/llm.rs index 14f10342a7..2d040cfa28 100644 --- a/crates/collab/src/llm.rs +++ b/crates/collab/src/llm.rs @@ -22,7 +22,8 @@ use chrono::{DateTime, Duration, Utc}; use collections::HashMap; use db::{usage_measure::UsageMeasure, ActiveUserCount, LlmDatabase}; use futures::{Stream, StreamExt as _}; -use isahc_http_client::IsahcHttpClient; + +use reqwest_client::ReqwestClient; use rpc::ListModelsResponse; use rpc::{ proto::Plan, LanguageModelProvider, PerformCompletionParams, EXPIRED_LLM_TOKEN_HEADER_NAME, @@ -43,7 +44,7 @@ pub struct LlmState { pub config: Config, pub executor: Executor, pub db: Arc, - pub http_client: IsahcHttpClient, + pub http_client: ReqwestClient, pub clickhouse_client: Option, active_user_count_by_model: RwLock, ActiveUserCount)>>, @@ -69,11 +70,8 @@ impl LlmState { let db = Arc::new(db); let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION")); - let http_client = IsahcHttpClient::builder() - .default_header("User-Agent", user_agent) - .build() - .map(IsahcHttpClient::from) - .context("failed to construct http client")?; + let http_client = + ReqwestClient::user_agent(&user_agent).context("failed to construct http client")?; let this = Self { executor, diff --git a/crates/collab/src/rpc.rs b/crates/collab/src/rpc.rs index 5f21df4ab9..27c95a5b44 100644 --- a/crates/collab/src/rpc.rs +++ b/crates/collab/src/rpc.rs @@ -36,8 +36,8 @@ use collections::{HashMap, HashSet}; pub use connection_pool::{ConnectionPool, ZedVersion}; use core::fmt::{self, Debug, Formatter}; use http_client::HttpClient; -use isahc_http_client::IsahcHttpClient; use open_ai::{OpenAiEmbeddingModel, OPEN_AI_API_URL}; +use reqwest_client::ReqwestClient; use sha2::Digest; use supermaven_api::{CreateExternalUserRequest, SupermavenAdminApi}; @@ -954,8 +954,8 @@ impl Server { tracing::info!("connection opened"); let user_agent = format!("Zed Server/{}", env!("CARGO_PKG_VERSION")); - let http_client = match IsahcHttpClient::builder().default_header("User-Agent", user_agent).build() { - Ok(http_client) => Arc::new(IsahcHttpClient::from(http_client)), + let http_client = match ReqwestClient::user_agent(&user_agent) { + Ok(http_client) => Arc::new(http_client), Err(error) => { tracing::error!(?error, "failed to create HTTP client"); return; diff --git a/crates/evals/Cargo.toml b/crates/evals/Cargo.toml index 400ab139aa..52af0ce446 100644 --- a/crates/evals/Cargo.toml +++ b/crates/evals/Cargo.toml @@ -16,6 +16,7 @@ path = "src/eval.rs" [dependencies] clap.workspace = true anyhow.workspace = true +ureq_client.workspace = true client.workspace = true clock.workspace = true collections.workspace = true @@ -24,7 +25,6 @@ feature_flags.workspace = true fs.workspace = true git.workspace = true gpui.workspace = true -isahc_http_client.workspace = true language.workspace = true languages.workspace = true http_client.workspace = true diff --git a/crates/evals/src/eval.rs b/crates/evals/src/eval.rs index 899d821053..e2dc5c8e03 100644 --- a/crates/evals/src/eval.rs +++ b/crates/evals/src/eval.rs @@ -32,6 +32,7 @@ use std::{ Arc, }, }; +use ureq_client::UreqClient; const CODESEARCH_NET_DIR: &'static str = "target/datasets/code-search-net"; const EVAL_REPOS_DIR: &'static str = "target/datasets/eval-repos"; @@ -100,7 +101,11 @@ fn main() -> Result<()> { gpui::App::headless().run(move |cx| { let executor = cx.background_executor().clone(); - let client = isahc_http_client::IsahcHttpClient::new(None, None); + let client = Arc::new(UreqClient::new( + None, + "Zed LLM evals".to_string(), + executor.clone(), + )); cx.set_http_client(client.clone()); match cli.command { Commands::Fetch {} => { diff --git a/crates/extension/Cargo.toml b/crates/extension/Cargo.toml index 6ce1bd6862..9fea3a768a 100644 --- a/crates/extension/Cargo.toml +++ b/crates/extension/Cargo.toml @@ -56,10 +56,12 @@ task.workspace = true serde_json_lenient.workspace = true [dev-dependencies] -isahc_http_client.workspace = true +ureq_client.workspace = true ctor.workspace = true env_logger.workspace = true parking_lot.workspace = true +reqwest_client.workspace = true +tokio.workspace = true fs = { workspace = true, features = ["test-support"] } gpui = { workspace = true, features = ["test-support"] } diff --git a/crates/extension/src/extension_builder.rs b/crates/extension/src/extension_builder.rs index 7380e699f9..876d0336dc 100644 --- a/crates/extension/src/extension_builder.rs +++ b/crates/extension/src/extension_builder.rs @@ -25,7 +25,7 @@ use wit_component::ComponentEncoder; /// Once Rust 1.78 is released, there will be a `wasm32-wasip2` target available, so we will /// not need the adapter anymore. const RUST_TARGET: &str = "wasm32-wasip1"; -const WASI_ADAPTER_URL: &str = +pub const WASI_ADAPTER_URL: &str = "https://github.com/bytecodealliance/wasmtime/releases/download/v18.0.2/wasi_snapshot_preview1.reactor.wasm"; /// Compiling Tree-sitter parsers from C to WASM requires Clang 17, and a WASM build of libc diff --git a/crates/extension/src/extension_store_test.rs b/crates/extension/src/extension_store_test.rs index 126e6b2cfb..7a3c645e04 100644 --- a/crates/extension/src/extension_store_test.rs +++ b/crates/extension/src/extension_store_test.rs @@ -1,3 +1,4 @@ +use crate::extension_builder::WASI_ADAPTER_URL; use crate::extension_manifest::SchemaVersion; use crate::extension_settings::ExtensionSettings; use crate::{ @@ -11,14 +12,14 @@ use collections::BTreeMap; use fs::{FakeFs, Fs, RealFs}; use futures::{io::BufReader, AsyncReadExt, StreamExt}; use gpui::{Context, SemanticVersion, TestAppContext}; -use http_client::{FakeHttpClient, Response}; +use http_client::{AsyncBody, FakeHttpClient, HttpClient, Response}; use indexed_docs::IndexedDocsRegistry; -use isahc_http_client::IsahcHttpClient; use language::{LanguageMatcher, LanguageRegistry, LanguageServerBinaryStatus, LanguageServerName}; use node_runtime::NodeRuntime; use parking_lot::Mutex; use project::{Project, DEFAULT_COMPLETION_CONTEXT}; use release_channel::AppVersion; +use reqwest_client::ReqwestClient; use serde_json::json; use settings::{Settings as _, SettingsStore}; use snippet_provider::SnippetRegistry; @@ -28,6 +29,7 @@ use std::{ sync::Arc, }; use theme::ThemeRegistry; +use ureq_client::UreqClient; use util::test::temp_tree; #[cfg(test)] @@ -576,7 +578,7 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { std::env::consts::ARCH ) }); - let builder_client = IsahcHttpClient::new(None, Some(user_agent)); + let builder_client = Arc::new(UreqClient::new(None, user_agent, cx.executor().clone())); let extension_store = cx.new_model(|cx| { ExtensionStore::new( @@ -769,6 +771,50 @@ async fn test_extension_store_with_test_extension(cx: &mut TestAppContext) { assert!(fs.metadata(&expected_server_path).await.unwrap().is_none()); } +#[gpui::test] +async fn test_wasi_adapter_download(cx: &mut TestAppContext) { + let client = Arc::new(UreqClient::new( + None, + "zed-test-wasi-adapter-download".to_string(), + cx.executor().clone(), + )); + + let mut response = client + .get(WASI_ADAPTER_URL, AsyncBody::default(), true) + .await + .unwrap(); + + let mut content = Vec::new(); + let mut body = BufReader::new(response.body_mut()); + body.read_to_end(&mut content).await.unwrap(); + + assert!(wasmparser::Parser::is_core_wasm(&content)); + assert_eq!(content.len(), 96801); // Determined by downloading this to my computer + wit_component::ComponentEncoder::default() + .adapter("wasi_snapshot_preview1", &content) + .unwrap(); +} + +#[tokio::test] +async fn test_wasi_adapter_download_tokio() { + let client = Arc::new(ReqwestClient::new()); + + let mut response = client + .get(WASI_ADAPTER_URL, AsyncBody::default(), true) + .await + .unwrap(); + + let mut content = Vec::new(); + let mut body = BufReader::new(response.body_mut()); + body.read_to_end(&mut content).await.unwrap(); + + assert!(wasmparser::Parser::is_core_wasm(&content)); + assert_eq!(content.len(), 96801); // Determined by downloading this to my computer + wit_component::ComponentEncoder::default() + .adapter("wasi_snapshot_preview1", &content) + .unwrap(); +} + fn init_test(cx: &mut TestAppContext) { cx.update(|cx| { let store = SettingsStore::test(cx); diff --git a/crates/extension_cli/Cargo.toml b/crates/extension_cli/Cargo.toml index bc649d8e04..3e109a0036 100644 --- a/crates/extension_cli/Cargo.toml +++ b/crates/extension_cli/Cargo.toml @@ -18,7 +18,7 @@ clap = { workspace = true, features = ["derive"] } env_logger.workspace = true extension = { workspace = true, features = ["no-webrtc"] } fs.workspace = true -isahc_http_client.workspace = true +reqwest_client.workspace = true language.workspace = true log.workspace = true rpc.workspace = true diff --git a/crates/extension_cli/src/main.rs b/crates/extension_cli/src/main.rs index 6eaebca2f0..dd6f221378 100644 --- a/crates/extension_cli/src/main.rs +++ b/crates/extension_cli/src/main.rs @@ -13,8 +13,8 @@ use extension::{ extension_builder::{CompileExtensionOptions, ExtensionBuilder}, ExtensionManifest, }; -use isahc_http_client::IsahcHttpClient; use language::LanguageConfig; +use reqwest_client::ReqwestClient; use theme::ThemeRegistry; use tree_sitter::{Language, Query, WasmStore}; @@ -66,12 +66,7 @@ async fn main() -> Result<()> { std::env::consts::OS, std::env::consts::ARCH ); - let http_client = Arc::new( - IsahcHttpClient::builder() - .default_header("User-Agent", user_agent) - .build() - .map(IsahcHttpClient::from)?, - ); + let http_client = Arc::new(ReqwestClient::user_agent(&user_agent)?); let builder = ExtensionBuilder::new(http_client, scratch_dir); builder diff --git a/crates/http_client/Cargo.toml b/crates/http_client/Cargo.toml index 0244ac4104..52c2947b8a 100644 --- a/crates/http_client/Cargo.toml +++ b/crates/http_client/Cargo.toml @@ -16,7 +16,9 @@ path = "src/http_client.rs" doctest = true [dependencies] -http = "0.2" +http = "1.1" +rustls.workspace = true +rustls-native-certs.workspace = true anyhow.workspace = true derive_more.workspace = true futures.workspace = true diff --git a/crates/http_client/src/http_client.rs b/crates/http_client/src/http_client.rs index 2f029a1d23..015c73a448 100644 --- a/crates/http_client/src/http_client.rs +++ b/crates/http_client/src/http_client.rs @@ -11,13 +11,21 @@ use http::request::Builder; #[cfg(feature = "test-support")] use std::fmt; use std::{ - sync::{Arc, Mutex}, + sync::{Arc, LazyLock, Mutex}, time::Duration, }; pub use url::Url; +#[derive(Clone)] pub struct ReadTimeout(pub Duration); -#[derive(Default, Debug, Clone)] +impl Default for ReadTimeout { + fn default() -> Self { + Self(Duration::from_secs(5)) + } +} + +#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)] + pub enum RedirectPolicy { #[default] NoFollow, @@ -26,6 +34,23 @@ pub enum RedirectPolicy { } pub struct FollowRedirects(pub bool); +pub static TLS_CONFIG: LazyLock> = LazyLock::new(|| { + let mut root_store = rustls::RootCertStore::empty(); + + let root_certs = rustls_native_certs::load_native_certs(); + for error in root_certs.errors { + log::warn!("error loading native certs: {:?}", error); + } + root_store.add_parsable_certificates(&root_certs.certs); + + Arc::new( + rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_no_client_auth(), + ) +}); + pub trait HttpRequestExt { /// Set a read timeout on the request. /// For isahc, this is the low_speed_timeout. diff --git a/crates/isahc_http_client/LICENSE-APACHE b/crates/isahc_http_client/LICENSE-APACHE deleted file mode 120000 index 1cd601d0a3..0000000000 --- a/crates/isahc_http_client/LICENSE-APACHE +++ /dev/null @@ -1 +0,0 @@ -../../LICENSE-APACHE \ No newline at end of file diff --git a/crates/isahc_http_client/src/isahc_http_client.rs b/crates/isahc_http_client/src/isahc_http_client.rs deleted file mode 100644 index 778f6a0459..0000000000 --- a/crates/isahc_http_client/src/isahc_http_client.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::{mem, sync::Arc, time::Duration}; - -use futures::future::BoxFuture; -use util::maybe; - -pub use isahc::config::Configurable; -pub struct IsahcHttpClient(isahc::HttpClient); - -pub use http_client::*; - -impl IsahcHttpClient { - pub fn new(proxy: Option, user_agent: Option) -> Arc { - let mut builder = isahc::HttpClient::builder() - .connect_timeout(Duration::from_secs(5)) - .low_speed_timeout(100, Duration::from_secs(5)) - .proxy(proxy.clone()); - if let Some(agent) = user_agent { - builder = builder.default_header("User-Agent", agent); - } - Arc::new(IsahcHttpClient(builder.build().unwrap())) - } - pub fn builder() -> isahc::HttpClientBuilder { - isahc::HttpClientBuilder::new() - } -} - -impl From for IsahcHttpClient { - fn from(client: isahc::HttpClient) -> Self { - Self(client) - } -} - -impl HttpClient for IsahcHttpClient { - fn proxy(&self) -> Option<&Uri> { - None - } - - fn send( - &self, - req: http_client::http::Request, - ) -> BoxFuture<'static, Result, anyhow::Error>> - { - let redirect_policy = req - .extensions() - .get::() - .cloned() - .unwrap_or_default(); - let read_timeout = req - .extensions() - .get::() - .map(|t| t.0); - let req = maybe!({ - let (mut parts, body) = req.into_parts(); - let mut builder = isahc::Request::builder() - .method(parts.method) - .uri(parts.uri) - .version(parts.version); - if let Some(read_timeout) = read_timeout { - builder = builder.low_speed_timeout(100, read_timeout); - } - - let headers = builder.headers_mut()?; - mem::swap(headers, &mut parts.headers); - - let extensions = builder.extensions_mut()?; - mem::swap(extensions, &mut parts.extensions); - - let isahc_body = match body.0 { - http_client::Inner::Empty => isahc::AsyncBody::empty(), - http_client::Inner::AsyncReader(reader) => isahc::AsyncBody::from_reader(reader), - http_client::Inner::SyncReader(reader) => { - isahc::AsyncBody::from_bytes_static(reader.into_inner()) - } - }; - - builder - .redirect_policy(match redirect_policy { - http_client::RedirectPolicy::FollowAll => isahc::config::RedirectPolicy::Follow, - http_client::RedirectPolicy::FollowLimit(limit) => { - isahc::config::RedirectPolicy::Limit(limit) - } - http_client::RedirectPolicy::NoFollow => isahc::config::RedirectPolicy::None, - }) - .body(isahc_body) - .ok() - }); - - let client = self.0.clone(); - - Box::pin(async move { - match req { - Some(req) => client - .send_async(req) - .await - .map_err(Into::into) - .map(|response| { - let (parts, body) = response.into_parts(); - let body = http_client::AsyncBody::from_reader(body); - http_client::Response::from_parts(parts, body) - }), - None => Err(anyhow::anyhow!("Request was malformed")), - } - }) - } -} diff --git a/crates/live_kit_server/Cargo.toml b/crates/live_kit_server/Cargo.toml index bad4c5a05f..4b4b5e13da 100644 --- a/crates/live_kit_server/Cargo.toml +++ b/crates/live_kit_server/Cargo.toml @@ -20,7 +20,7 @@ jsonwebtoken.workspace = true log.workspace = true prost.workspace = true prost-types.workspace = true -reqwest = "0.11" +reqwest.workspace = true serde.workspace = true [build-dependencies] diff --git a/crates/reqwest_client/Cargo.toml b/crates/reqwest_client/Cargo.toml new file mode 100644 index 0000000000..d393191252 --- /dev/null +++ b/crates/reqwest_client/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "reqwest_client" +version = "0.1.0" +edition = "2021" +publish = false +license = "Apache-2.0" + +[lints] +workspace = true + +[features] +test-support = [] + +[lib] +path = "src/reqwest_client.rs" +doctest = true + +[[example]] +name = "client" +path = "examples/client.rs" + +[dependencies] +anyhow.workspace = true +futures.workspace = true +serde.workspace = true +smol.workspace = true +http_client.workspace = true +tokio.workspace = true +bytes = "1.0" + +reqwest = { workspace = true, features = ["rustls-tls-manual-roots", "stream"] } diff --git a/crates/reqwest_client/LICENSE-GPL b/crates/reqwest_client/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/reqwest_client/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/reqwest_client/examples/client.rs b/crates/reqwest_client/examples/client.rs new file mode 100644 index 0000000000..1f50d21e4e --- /dev/null +++ b/crates/reqwest_client/examples/client.rs @@ -0,0 +1,16 @@ +use futures::AsyncReadExt as _; +use http_client::AsyncBody; +use http_client::HttpClient; +use reqwest_client::ReqwestClient; + +#[tokio::main] +async fn main() { + let resp = ReqwestClient::new() + .get("http://zed.dev", AsyncBody::empty(), true) + .await + .unwrap(); + + let mut body = String::new(); + resp.into_body().read_to_string(&mut body).await.unwrap(); + println!("{}", &body); +} diff --git a/crates/reqwest_client/src/reqwest_client.rs b/crates/reqwest_client/src/reqwest_client.rs new file mode 100644 index 0000000000..6e84c58954 --- /dev/null +++ b/crates/reqwest_client/src/reqwest_client.rs @@ -0,0 +1,232 @@ +use std::{borrow::Cow, io::Read, pin::Pin, task::Poll}; + +use anyhow::anyhow; +use bytes::{BufMut, Bytes, BytesMut}; +use futures::{AsyncRead, TryStreamExt}; +use http_client::{http, AsyncBody, ReadTimeout}; +use reqwest::header::{HeaderMap, HeaderValue}; +use smol::future::FutureExt; + +const DEFAULT_CAPACITY: usize = 4096; + +pub struct ReqwestClient { + client: reqwest::Client, +} + +impl ReqwestClient { + pub fn new() -> Self { + Self { + client: reqwest::Client::new(), + } + } + + pub fn user_agent(agent: &str) -> anyhow::Result { + let mut map = HeaderMap::new(); + map.insert(http::header::USER_AGENT, HeaderValue::from_str(agent)?); + Ok(Self { + client: reqwest::Client::builder().default_headers(map).build()?, + }) + } +} + +impl From for ReqwestClient { + fn from(client: reqwest::Client) -> Self { + Self { client } + } +} + +// This struct is essentially a re-implementation of +// https://docs.rs/tokio-util/0.7.12/tokio_util/io/struct.ReaderStream.html +// except outside of Tokio's aegis +struct ReaderStream { + reader: Option>>, + buf: BytesMut, + capacity: usize, +} + +impl ReaderStream { + fn new(reader: Pin>) -> Self { + Self { + reader: Some(reader), + buf: BytesMut::new(), + capacity: DEFAULT_CAPACITY, + } + } +} + +impl futures::Stream for ReaderStream { + type Item = std::io::Result; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + let mut this = self.as_mut(); + + let mut reader = match this.reader.take() { + Some(r) => r, + None => return Poll::Ready(None), + }; + + if this.buf.capacity() == 0 { + let capacity = this.capacity; + this.buf.reserve(capacity); + } + + match poll_read_buf(&mut reader, cx, &mut this.buf) { + Poll::Pending => Poll::Pending, + Poll::Ready(Err(err)) => { + self.reader = None; + + Poll::Ready(Some(Err(err))) + } + Poll::Ready(Ok(0)) => { + self.reader = None; + Poll::Ready(None) + } + Poll::Ready(Ok(_)) => { + let chunk = this.buf.split(); + self.reader = Some(reader); + Poll::Ready(Some(Ok(chunk.freeze()))) + } + } + } +} + +/// Implementation from https://docs.rs/tokio-util/0.7.12/src/tokio_util/util/poll_buf.rs.html +/// Specialized for this use case +pub fn poll_read_buf( + io: &mut Pin>, + cx: &mut std::task::Context<'_>, + buf: &mut BytesMut, +) -> Poll> { + if !buf.has_remaining_mut() { + return Poll::Ready(Ok(0)); + } + + let n = { + let dst = buf.chunk_mut(); + + // Safety: `chunk_mut()` returns a `&mut UninitSlice`, and `UninitSlice` is a + // transparent wrapper around `[MaybeUninit]`. + let dst = unsafe { &mut *(dst as *mut _ as *mut [std::mem::MaybeUninit]) }; + let mut buf = tokio::io::ReadBuf::uninit(dst); + let ptr = buf.filled().as_ptr(); + let unfilled_portion = buf.initialize_unfilled(); + // SAFETY: Pin projection + let io_pin = unsafe { Pin::new_unchecked(io) }; + std::task::ready!(io_pin.poll_read(cx, unfilled_portion)?); + + // Ensure the pointer does not change from under us + assert_eq!(ptr, buf.filled().as_ptr()); + buf.filled().len() + }; + + // Safety: This is guaranteed to be the number of initialized (and read) + // bytes due to the invariants provided by `ReadBuf::filled`. + unsafe { + buf.advance_mut(n); + } + + Poll::Ready(Ok(n)) +} + +enum WrappedBodyInner { + None, + SyncReader(std::io::Cursor>), + Stream(ReaderStream), +} + +struct WrappedBody(WrappedBodyInner); + +impl WrappedBody { + fn new(body: AsyncBody) -> Self { + match body.0 { + http_client::Inner::Empty => Self(WrappedBodyInner::None), + http_client::Inner::SyncReader(cursor) => Self(WrappedBodyInner::SyncReader(cursor)), + http_client::Inner::AsyncReader(pin) => { + Self(WrappedBodyInner::Stream(ReaderStream::new(pin))) + } + } + } +} + +impl futures::stream::Stream for WrappedBody { + type Item = Result; + + fn poll_next( + mut self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + match &mut self.0 { + WrappedBodyInner::None => Poll::Ready(None), + WrappedBodyInner::SyncReader(cursor) => { + let mut buf = Vec::new(); + match cursor.read_to_end(&mut buf) { + Ok(_) => { + return Poll::Ready(Some(Ok(Bytes::from(buf)))); + } + Err(e) => return Poll::Ready(Some(Err(e))), + } + } + WrappedBodyInner::Stream(stream) => { + // SAFETY: Pin projection + let stream = unsafe { Pin::new_unchecked(stream) }; + futures::Stream::poll_next(stream, cx) + } + } + } +} + +impl http_client::HttpClient for ReqwestClient { + fn proxy(&self) -> Option<&http::Uri> { + None + } + + fn send( + &self, + req: http::Request, + ) -> futures::future::BoxFuture< + 'static, + Result, anyhow::Error>, + > { + let (parts, body) = req.into_parts(); + + let mut request = self.client.request(parts.method, parts.uri.to_string()); + + request = request.headers(parts.headers); + + if let Some(redirect_policy) = parts.extensions.get::() { + request = request.redirect_policy(match redirect_policy { + http_client::RedirectPolicy::NoFollow => reqwest::redirect::Policy::none(), + http_client::RedirectPolicy::FollowLimit(limit) => { + reqwest::redirect::Policy::limited(*limit as usize) + } + http_client::RedirectPolicy::FollowAll => reqwest::redirect::Policy::limited(100), + }); + } + + if let Some(ReadTimeout(timeout)) = parts.extensions.get::() { + request = request.timeout(*timeout); + } + + let body = WrappedBody::new(body); + let request = request.body(reqwest::Body::wrap_stream(body)); + + async move { + let response = request.send().await.map_err(|e| anyhow!(e))?; + let status = response.status(); + let mut builder = http::Response::builder().status(status.as_u16()); + for (name, value) in response.headers() { + builder = builder.header(name, value); + } + let bytes = response.bytes_stream(); + let bytes = bytes + .map_err(|e| futures::io::Error::new(futures::io::ErrorKind::Other, e)) + .into_async_read(); + let body = http_client::AsyncBody::from_reader(bytes); + builder.body(body).map_err(|e| anyhow!(e)) + } + .boxed() + } +} diff --git a/crates/semantic_index/Cargo.toml b/crates/semantic_index/Cargo.toml index 691d6e57f6..8842093f78 100644 --- a/crates/semantic_index/Cargo.toml +++ b/crates/semantic_index/Cargo.toml @@ -51,7 +51,7 @@ workspace.workspace = true worktree.workspace = true [dev-dependencies] -isahc_http_client.workspace = true +ureq_client.workspace = true env_logger.workspace = true client = { workspace = true, features = ["test-support"] } fs = { workspace = true, features = ["test-support"] } diff --git a/crates/semantic_index/examples/index.rs b/crates/semantic_index/examples/index.rs index c5c2c633a1..1ebed4c17f 100644 --- a/crates/semantic_index/examples/index.rs +++ b/crates/semantic_index/examples/index.rs @@ -2,7 +2,6 @@ use client::Client; use futures::channel::oneshot; use gpui::App; use http_client::HttpClientWithUrl; -use isahc_http_client::IsahcHttpClient; use language::language_settings::AllLanguageSettings; use project::Project; use semantic_index::{OpenAiEmbeddingModel, OpenAiEmbeddingProvider, SemanticDb}; @@ -29,7 +28,11 @@ fn main() { let clock = Arc::new(FakeSystemClock::default()); let http = Arc::new(HttpClientWithUrl::new( - IsahcHttpClient::new(None, None), + Arc::new(ureq_client::UreqClient::new( + None, + "Zed semantic index example".to_string(), + cx.background_executor().clone(), + )), "http://localhost:11434", None, )); diff --git a/crates/isahc_http_client/Cargo.toml b/crates/ureq_client/Cargo.toml similarity index 52% rename from crates/isahc_http_client/Cargo.toml rename to crates/ureq_client/Cargo.toml index 82f7621bf8..a14419a226 100644 --- a/crates/isahc_http_client/Cargo.toml +++ b/crates/ureq_client/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "isahc_http_client" +name = "ureq_client" version = "0.1.0" edition = "2021" publish = false @@ -12,11 +12,21 @@ workspace = true test-support = [] [lib] -path = "src/isahc_http_client.rs" +path = "src/ureq_client.rs" +doctest = true + +[[example]] +name = "client" +path = "examples/client.rs" [dependencies] anyhow.workspace = true futures.workspace = true +serde.workspace = true +smol.workspace = true +gpui.workspace = true http_client.workspace = true -isahc.workspace = true util.workspace = true +parking_lot.workspace = true + +ureq = "=2.9.1" diff --git a/crates/ureq_client/LICENSE-GPL b/crates/ureq_client/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/ureq_client/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/ureq_client/examples/client.rs b/crates/ureq_client/examples/client.rs new file mode 100644 index 0000000000..c5caae40da --- /dev/null +++ b/crates/ureq_client/examples/client.rs @@ -0,0 +1,24 @@ +use futures::AsyncReadExt; +use http_client::{AsyncBody, HttpClient}; +use ureq_client::UreqClient; + +fn main() { + gpui::App::headless().run(|cx| { + println!("{:?}", std::thread::current().id()); + cx.spawn(|cx| async move { + let resp = UreqClient::new( + None, + "Conrad's bot".to_string(), + cx.background_executor().clone(), + ) + .get("http://zed.dev", AsyncBody::empty(), true) + .await + .unwrap(); + + let mut body = String::new(); + resp.into_body().read_to_string(&mut body).await.unwrap(); + println!("{}", body); + }) + .detach(); + }) +} diff --git a/crates/ureq_client/src/ureq_client.rs b/crates/ureq_client/src/ureq_client.rs new file mode 100644 index 0000000000..8951e80ac2 --- /dev/null +++ b/crates/ureq_client/src/ureq_client.rs @@ -0,0 +1,187 @@ +use std::collections::HashMap; +use std::io::Read; +use std::sync::Arc; +use std::time::Duration; +use std::{pin::Pin, task::Poll}; + +use anyhow::Error; +use futures::channel::mpsc; +use futures::future::BoxFuture; +use futures::{AsyncRead, SinkExt, StreamExt}; +use http_client::{http, AsyncBody, HttpClient, RedirectPolicy, Uri}; +use smol::future::FutureExt; +use util::ResultExt; + +pub struct UreqClient { + // Note in ureq 2.x the options are stored on the Agent. + // In ureq 3.x we'll be able to set these on the request. + // In practice it's probably "fine" to have many clients, the number of distinct options + // is low; and most requests to the same connection will have the same options so the + // connection pool will work. + clients: Arc>>, + proxy_url: Option, + proxy: Option, + user_agent: String, + background_executor: gpui::BackgroundExecutor, +} + +impl UreqClient { + pub fn new( + proxy_url: Option, + user_agent: String, + background_executor: gpui::BackgroundExecutor, + ) -> Self { + Self { + clients: Arc::default(), + proxy_url: proxy_url.clone(), + proxy: proxy_url.and_then(|url| ureq::Proxy::new(url.to_string()).log_err()), + user_agent, + background_executor, + } + } + + fn agent_for(&self, redirect_policy: RedirectPolicy, timeout: Duration) -> ureq::Agent { + let mut clients = self.clients.lock(); + // in case our assumption of distinct options is wrong, we'll sporadically clean it out. + if clients.len() > 50 { + clients.clear() + } + + clients + .entry((timeout, redirect_policy.clone())) + .or_insert_with(|| { + let mut builder = ureq::AgentBuilder::new() + .timeout_connect(Duration::from_secs(5)) + .timeout_read(timeout) + .timeout_write(timeout) + .user_agent(&self.user_agent) + .tls_config(http_client::TLS_CONFIG.clone()) + .redirects(match redirect_policy { + RedirectPolicy::NoFollow => 0, + RedirectPolicy::FollowLimit(limit) => limit, + RedirectPolicy::FollowAll => 100, + }); + if let Some(proxy) = &self.proxy { + builder = builder.proxy(proxy.clone()); + } + builder.build() + }) + .clone() + } +} +impl HttpClient for UreqClient { + fn proxy(&self) -> Option<&Uri> { + self.proxy_url.as_ref() + } + + fn send( + &self, + request: http::Request, + ) -> BoxFuture<'static, Result, Error>> { + let agent = self.agent_for( + request + .extensions() + .get::() + .cloned() + .unwrap_or_default(), + request + .extensions() + .get::() + .cloned() + .unwrap_or_default() + .0, + ); + let mut req = agent.request(&request.method().as_ref(), &request.uri().to_string()); + for (name, value) in request.headers().into_iter() { + req = req.set(name.as_str(), value.to_str().unwrap()); + } + let body = request.into_body(); + let executor = self.background_executor.clone(); + + self.background_executor + .spawn(async move { + let response = req.send(body)?; + + let mut builder = http::Response::builder() + .status(response.status()) + .version(http::Version::HTTP_11); + for name in response.headers_names() { + if let Some(value) = response.header(&name) { + builder = builder.header(name, value); + } + } + + let body = AsyncBody::from_reader(UreqResponseReader::new(executor, response)); + let http_response = builder.body(body)?; + + Ok(http_response) + }) + .boxed() + } +} + +struct UreqResponseReader { + receiver: mpsc::Receiver>>, + buffer: Vec, + idx: usize, + _task: gpui::Task<()>, +} + +impl UreqResponseReader { + fn new(background_executor: gpui::BackgroundExecutor, response: ureq::Response) -> Self { + let (mut sender, receiver) = mpsc::channel(1); + let mut reader = response.into_reader(); + let task = background_executor.spawn(async move { + let mut buffer = vec![0; 8192]; + loop { + let n = match reader.read(&mut buffer) { + Ok(0) => break, + Ok(n) => n, + Err(e) => { + let _ = sender.send(Err(e)).await; + break; + } + }; + let _ = sender.send(Ok(buffer[..n].to_vec())).await; + } + }); + + UreqResponseReader { + _task: task, + receiver, + buffer: Vec::new(), + idx: 0, + } + } +} + +impl AsyncRead for UreqResponseReader { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &mut [u8], + ) -> Poll> { + if self.buffer.is_empty() { + match self.receiver.poll_next_unpin(cx) { + Poll::Ready(Some(Ok(data))) => self.buffer = data, + Poll::Ready(Some(Err(e))) => { + return Poll::Ready(Err(e)); + } + Poll::Ready(None) => { + return Poll::Ready(Ok(0)); + } + Poll::Pending => { + return Poll::Pending; + } + } + } + let n = std::cmp::min(buf.len(), self.buffer.len() - self.idx); + buf[..n].copy_from_slice(&self.buffer[self.idx..self.idx + n]); + self.idx += n; + if self.idx == self.buffer.len() { + self.buffer.clear(); + self.idx = 0; + } + Poll::Ready(Ok(n)) + } +} diff --git a/crates/vim/Cargo.toml b/crates/vim/Cargo.toml index dcbf2e8b59..99394b7922 100644 --- a/crates/vim/Cargo.toml +++ b/crates/vim/Cargo.toml @@ -17,7 +17,7 @@ neovim = ["nvim-rs", "async-compat", "async-trait", "tokio"] [dependencies] anyhow.workspace = true -async-compat = { version = "0.2.1", "optional" = true } +async-compat = { workspace = true, "optional" = true } async-trait = { workspace = true, "optional" = true } collections.workspace = true command_palette.workspace = true diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index e22f75f5bb..ac73bf15ee 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -57,7 +57,7 @@ http_client.workspace = true image_viewer.workspace = true inline_completion_button.workspace = true install_cli.workspace = true -isahc_http_client.workspace = true +ureq_client.workspace = true journal.workspace = true language.workspace = true language_model.workspace = true diff --git a/crates/zed/src/main.rs b/crates/zed/src/main.rs index 06f1d926ae..adb5feb9fe 100644 --- a/crates/zed/src/main.rs +++ b/crates/zed/src/main.rs @@ -24,9 +24,9 @@ use gpui::{ UpdateGlobal as _, VisualContext, }; use http_client::{read_proxy_from_env, Uri}; -use isahc_http_client::IsahcHttpClient; use language::LanguageRegistry; use log::LevelFilter; +use ureq_client::UreqClient; use assets::Assets; use node_runtime::{NodeBinaryOptions, NodeRuntime}; @@ -334,9 +334,7 @@ fn main() { log::info!("========== starting zed =========="); - let app = App::new() - .with_assets(Assets) - .with_http_client(IsahcHttpClient::new(None, None)); + let app = App::new().with_assets(Assets); let system_id = app.background_executor().block(system_id()).ok(); let installation_id = app.background_executor().block(installation_id()).ok(); @@ -470,8 +468,8 @@ fn main() { .ok() }) .or_else(read_proxy_from_env); - let http = IsahcHttpClient::new(proxy_url, Some(user_agent)); - cx.set_http_client(http); + let http = UreqClient::new(proxy_url, user_agent, cx.background_executor().clone()); + cx.set_http_client(Arc::new(http)); ::set_global(fs.clone(), cx);