diff --git a/Cargo.lock b/Cargo.lock index 32ee7ed..83ce47b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] @@ -43,47 +43,48 @@ dependencies = [ [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys 0.52.0", @@ -91,22 +92,22 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "async-compression" -version = "0.4.8" +version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07dbbf24db18d609b1462965249abdf49129ccad073ec257da372adc83259c60" +checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" dependencies = [ "brotli", "flate2", "futures-core", + "futures-io", "memchr", "pin-project-lite", - "tokio", ] [[package]] @@ -126,9 +127,9 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -137,9 +138,9 @@ version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -160,10 +161,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "62f447d68cfa5a9ab0c1c862a703da2a65b5ed1b7ce1153c9eb0169506d56019" [[package]] -name = "autocfg" -version = "1.2.0" +name = "atomic-waker" +version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" @@ -178,7 +185,7 @@ dependencies = [ "futures-util", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", + "hyper 0.14.29", "itoa", "matchit", "memchr", @@ -212,9 +219,9 @@ dependencies = [ [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" dependencies = [ "addr2line", "cc", @@ -231,6 +238,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "1.3.2" @@ -254,9 +267,9 @@ dependencies = [ [[package]] name = "brotli" -version = "4.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "125740193d7fee5cc63ab9e16c2fdc4e07c74ba755cc53b327d6ea029e9fc569" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -265,9 +278,9 @@ dependencies = [ [[package]] name = "brotli-decompressor" -version = "3.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65622a320492e09b5e0ac436b14c54ff68199bac392d0e89a6832c4518eea525" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" dependencies = [ "alloc-no-stdlib", "alloc-stdlib", @@ -293,9 +306,9 @@ checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" [[package]] name = "cc" -version = "1.0.95" +version = "1.0.99" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "96c51067fd44124faa7f870b4b1c969379ad32b2ba805aa959430ceaa384f695" [[package]] name = "certs-grabber" @@ -313,9 +326,9 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "clap" -version = "4.5.4" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", @@ -323,9 +336,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", @@ -336,21 +349,21 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck", - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" [[package]] name = "clio" @@ -369,15 +382,15 @@ dependencies = [ [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "concurrent-queue" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d16048cd947b08fa32c24458a22f5dc5e835264f689f4f5653210c69fd107363" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" dependencies = [ "crossbeam-utils", ] @@ -419,16 +432,6 @@ dependencies = [ "tracing-subscriber", ] -[[package]] -name = "console_error_panic_hook" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" -dependencies = [ - "cfg-if", - "wasm-bindgen", -] - [[package]] name = "core-foundation" version = "0.9.4" @@ -456,27 +459,27 @@ dependencies = [ [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] [[package]] name = "crossbeam-channel" -version = "0.5.12" +version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab3db02a9c5b5121e1e42fbdb1aeb65f5e02624cc58c43f2884c6ccac0b82f95" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crypto-common" @@ -495,23 +498,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" dependencies = [ "cfg-if", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "lock_api", "once_cell", "parking_lot_core", ] -[[package]] -name = "default-env" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f753eb82d29277e79efc625e84aecacfd4851ee50e05a8573a4740239a77bfd3" -dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "syn 0.15.44", -] - [[package]] name = "digest" version = "0.10.7" @@ -524,23 +516,22 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "3dca9240753cf90908d7e4aac30f630662b02aebaa1b58a3cadabdb23385b58b" [[package]] name = "epoxy-client" -version = "1.5.1" +version = "2.0.0" dependencies = [ "async-compression", "async-trait", - "async_io_stream", - "base64", + "base64 0.22.1", "bytes", - "console_error_panic_hook", - "default-env", "event-listener", - "fastwebsockets 0.6.0", + "fastwebsockets", + "flume", + "futures-rustls", "futures-util", "getrandom", "http 1.1.0", @@ -551,18 +542,14 @@ dependencies = [ "pin-project-lite", "ring", "rustls-pki-types", - "send_wrapper 0.6.0", + "send_wrapper", + "thiserror", "tokio", - "tokio-rustls", - "tokio-util", "tower-service", "wasm-bindgen", "wasm-bindgen-futures", - "wasm-bindgen-test", "wasm-streams", - "wasmtimer", "web-sys", - "webpki-roots", "wisp-mux", ] @@ -575,7 +562,7 @@ dependencies = [ "clio", "console-subscriber", "dashmap", - "fastwebsockets 0.7.1", + "fastwebsockets", "futures-util", "http-body-util", "hyper 1.3.1", @@ -593,9 +580,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", @@ -603,9 +590,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "5.3.0" +version = "5.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d9944b8ca13534cdfb2800775f8dd4902ff3fc75a50101466decadfdf322a24" +checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" dependencies = [ "concurrent-queue", "parking", @@ -614,30 +601,17 @@ dependencies = [ [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fastwebsockets" -version = "0.6.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63dd7b57f9b33b1741fa631c9522eb35d43e96dcca4a6a91d5e4ca7c93acdc1" +checksum = "93da8b19e29f202ef35ddd20ddea8c86166850fce5ba2a3c3f3e3174cdbb0620" dependencies = [ - "rand", - "simdutf8", - "thiserror", - "tokio", - "utf-8", -] - -[[package]] -name = "fastwebsockets" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ac14c1f19ff7eab47c9ee7263a088296bec2abd1f9345964c863b5134da40a1" -dependencies = [ - "base64", + "base64 0.21.7", "bytes", "http-body-util", "hyper 1.3.1", @@ -653,9 +627,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -748,9 +722,20 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" +dependencies = [ + "futures-io", + "rustls", + "rustls-pki-types", ] [[package]] @@ -772,7 +757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ "gloo-timers", - "send_wrapper 0.4.0", + "send_wrapper", ] [[package]] @@ -805,9 +790,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "js-sys", @@ -818,9 +803,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.28.1" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "gloo-timers" @@ -855,15 +840,15 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.4" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ec7294445779408f36fe57bc5b7fc1cf59664059096c65f905c1c61f58069" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" dependencies = [ + "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", - "futures-util", "http 1.1.0", "indexmap 2.2.6", "slab", @@ -880,9 +865,9 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hdrhistogram" @@ -890,7 +875,7 @@ version = "7.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" dependencies = [ - "base64", + "base64 0.21.7", "byteorder", "flate2", "nom", @@ -954,12 +939,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.1" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", - "futures-core", + "futures-util", "http 1.1.0", "http-body 1.0.0", "pin-project-lite", @@ -967,9 +952,9 @@ dependencies = [ [[package]] name = "httparse" -version = "1.8.0" +version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" +checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545" [[package]] name = "httpdate" @@ -985,9 +970,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" -version = "0.14.28" +version = "0.14.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf96e135eb83a2a8ddf766e426a841d8ddd7449d5f00d34ea02b41d2f19eef80" +checksum = "f361cde2f109281a220d4307746cdfd5ee3f410da58a70377762396775634b33" dependencies = [ "bytes", "futures-channel", @@ -1016,7 +1001,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "h2 0.4.4", + "h2 0.4.5", "http 1.1.0", "http-body 1.0.0", "httparse", @@ -1034,7 +1019,7 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbb958482e8c7be4bc3cf272a766a2b0bf1a6755e7a6ae777f017a31d11b13b1" dependencies = [ - "hyper 0.14.28", + "hyper 0.14.29", "pin-project-lite", "tokio", "tokio-io-timeout", @@ -1042,9 +1027,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.3" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" dependencies = [ "bytes", "futures-util", @@ -1052,7 +1037,6 @@ dependencies = [ "http-body 1.0.0", "hyper 1.3.1", "pin-project-lite", - "socket2", "tokio", ] @@ -1093,7 +1077,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", ] [[package]] @@ -1107,6 +1091,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.12.1" @@ -1139,21 +1129,21 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "lock_api" -version = "0.4.11" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c168f8615b12bc01f9c17e2eb0cc07dcae1940121185446edc3744920e8ef45" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", @@ -1200,9 +1190,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" dependencies = [ "adler", ] @@ -1229,11 +1219,10 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ - "lazy_static", "libc", "log", "openssl", @@ -1257,9 +1246,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1276,9 +1265,9 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" dependencies = [ "memchr", ] @@ -1310,9 +1299,9 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1341,9 +1330,9 @@ checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" [[package]] name = "parking_lot" -version = "0.12.1" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", @@ -1351,15 +1340,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.9" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c42a9226546d68acdd9c0a280d17ce19bfe27a46bf68784e4066115788d008e" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", - "windows-targets 0.48.5", + "windows-targets 0.52.5", ] [[package]] @@ -1383,9 +1372,9 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1414,27 +1403,18 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "proc-macro2" -version = "0.4.30" +version = "1.0.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf3d2011ab5c909338f7887f4fc896d35932e29146c12c8d01da6b22a80ba759" -dependencies = [ - "unicode-xid", -] - -[[package]] -name = "proc-macro2" -version = "1.0.81" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" dependencies = [ "unicode-ident", ] [[package]] name = "prost" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0f5d036824e4761737860779c906171497f6d55681139d8312388f8fe398922" +checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" dependencies = [ "bytes", "prost-derive", @@ -1442,42 +1422,33 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48" +checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" dependencies = [ "anyhow", "itertools", - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "prost-types" -version = "0.12.4" +version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3235c33eb02c1f1e212abdbe34c78b264b038fb58ca612664343271e36e55ffe" +checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" dependencies = [ "prost", ] -[[package]] -name = "quote" -version = "0.6.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ce23b6b870e8f94f81fb0a363d65d86675884b34a09043c81e5562f11c1f8e1" -dependencies = [ - "proc-macro2 0.4.30", -] - [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ - "proc-macro2 1.0.81", + "proc-macro2", ] [[package]] @@ -1512,23 +1483,23 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.4.1" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", ] [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.6", - "regex-syntax 0.8.3", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", ] [[package]] @@ -1542,13 +1513,13 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.3", + "regex-syntax 0.8.4", ] [[package]] @@ -1559,9 +1530,9 @@ checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "ring" @@ -1580,9 +1551,9 @@ dependencies = [ [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc_version" @@ -1595,9 +1566,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.32" +version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.5.0", "errno", @@ -1608,9 +1579,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.5" +version = "0.23.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afabcee0551bd1aa3e18e5adbf2c0544722014b899adb31bd186ec638d3da97e" +checksum = "a218f0f6d05669de4eabfb24f31ce802035c952429d037507b4a4a39f0e60c5b" dependencies = [ "once_cell", "ring", @@ -1622,18 +1593,18 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.4.1" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ecd36cc4259e3e4514335c4a138c6b43171a8d61d8f5c9348f9fc7529416f247" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" dependencies = [ "web-time", ] [[package]] name = "rustls-webpki" -version = "0.102.2" +version = "0.102.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faaa0a62740bedb9b2ef5afa303da42764c012f743917351dc9a237ea1663610" +checksum = "ff448f7e92e913c4b7d4c6d8e4540a1724b319b4152b8aef6d4cf8339712b33e" dependencies = [ "ring", "rustls-pki-types", @@ -1642,15 +1613,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -1670,12 +1641,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -1684,11 +1649,11 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "security-framework" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" dependencies = [ - "bitflags 1.3.2", + "bitflags 2.5.0", "core-foundation", "core-foundation-sys", "libc", @@ -1697,9 +1662,9 @@ dependencies = [ [[package]] name = "security-framework-sys" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41f3cc463c0ef97e11c3461a9d3787412d30e8e7eb907c79180c4a57bf7c04ef" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" dependencies = [ "core-foundation-sys", "libc", @@ -1707,9 +1672,9 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "send_wrapper" @@ -1717,37 +1682,31 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f638d531eccd6e23b980caf34876660d38e265409d8e99b397ab71eb3612fad0" -[[package]] -name = "send_wrapper" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" - [[package]] name = "serde" -version = "1.0.198" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" dependencies = [ "itoa", "ryu", @@ -1776,9 +1735,9 @@ dependencies = [ [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" dependencies = [ "libc", ] @@ -1797,7 +1756,7 @@ dependencies = [ "bytes", "clap", "console-subscriber", - "fastwebsockets 0.7.1", + "fastwebsockets", "futures", "http-body-util", "humantime", @@ -1835,9 +1794,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" -version = "0.5.6" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", @@ -1866,23 +1825,12 @@ checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc" [[package]] name = "syn" -version = "0.15.44" +version = "2.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ca4b3b69a77cbe1ffc9e198781b7acb0c7365a883670e8f1c1bc66fba79a5c5" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" dependencies = [ - "proc-macro2 0.4.30", - "quote 0.6.13", - "unicode-xid", -] - -[[package]] -name = "syn" -version = "2.0.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" -dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", + "proc-macro2", + "quote", "unicode-ident", ] @@ -1916,22 +1864,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1946,9 +1894,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.37.0" +version = "1.38.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" dependencies = [ "backtrace", "bytes", @@ -1976,13 +1924,13 @@ dependencies = [ [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a" dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1995,17 +1943,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls", - "rustls-pki-types", - "tokio", -] - [[package]] name = "tokio-stream" version = "0.1.15" @@ -2019,16 +1956,15 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.10" +version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419f34732d9eb6ee4c3578b7989078579b7f039cbbb9ca2c4da015749371e15" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", - "tracing", ] [[package]] @@ -2040,12 +1976,12 @@ dependencies = [ "async-stream", "async-trait", "axum", - "base64", + "base64 0.21.7", "bytes", "h2 0.3.26", "http 0.2.12", "http-body 0.4.6", - "hyper 0.14.28", + "hyper 0.14.29", "hyper-timeout", "percent-encoding", "pin-project", @@ -2108,9 +2044,9 @@ version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -2156,12 +2092,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" -[[package]] -name = "unicode-xid" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" - [[package]] name = "untrusted" version = "0.9.0" @@ -2176,9 +2106,9 @@ checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "valuable" @@ -2242,9 +2172,9 @@ dependencies = [ "bumpalo", "log", "once_cell", - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", "wasm-bindgen-shared", ] @@ -2266,7 +2196,7 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ - "quote 1.0.36", + "quote", "wasm-bindgen-macro-support", ] @@ -2276,9 +2206,9 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", + "proc-macro2", + "quote", + "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2289,31 +2219,6 @@ version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" -[[package]] -name = "wasm-bindgen-test" -version = "0.3.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9bf62a58e0780af3e852044583deee40983e5886da43a271dd772379987667b" -dependencies = [ - "console_error_panic_hook", - "js-sys", - "scoped-tls", - "wasm-bindgen", - "wasm-bindgen-futures", - "wasm-bindgen-test-macro", -] - -[[package]] -name = "wasm-bindgen-test-macro" -version = "0.3.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b7f89739351a2e03cb94beb799d47fb2cac01759b40ec441f7de39b00cbf7ef0" -dependencies = [ - "proc-macro2 1.0.81", - "quote 1.0.36", - "syn 2.0.60", -] - [[package]] name = "wasm-streams" version = "0.4.0" @@ -2363,44 +2268,22 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.1" +version = "0.26.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3de34ae270483955a94f4b21bdaaeb83d508bb84a01435f393818edb0012009" +checksum = "3c452ad30530b54a4d8e71952716a212b08efd0f3562baa66c29a618b07da7c3" dependencies = [ "rustls-pki-types", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" -version = "0.1.6" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "winapi", + "windows-sys 0.52.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.42.0" @@ -2606,7 +2489,7 @@ dependencies = [ "bytes", "dashmap", "event-listener", - "fastwebsockets 0.7.1", + "fastwebsockets", "flume", "futures", "futures-timer", @@ -2616,6 +2499,6 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/client/Cargo.toml b/client/Cargo.toml index c6ad127..7d1408d 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,40 +1,37 @@ [package] name = "epoxy-client" -version = "1.5.1" +version = "2.0.0" edition = "2021" -license = "LGPL-3.0-only" [lib] -crate-type = ["cdylib", "rlib"] +crate-type = ["cdylib"] [dependencies] -bytes = "1.5.0" -http = "1.0.0" -http-body-util = "0.1.0" -hyper = { version = "1.1.0", features = ["client", "http1", "http2"] } -pin-project-lite = "0.2.13" -wasm-bindgen = { version = "0.2.91", features = ["enable-interning"] } -wasm-bindgen-futures = "0.4.39" -futures-util = "0.3.30" -js-sys = "0.3.66" -tokio-rustls = { version = "0.26.0", default-features = false, features = ["tls12", "ring"] } -web-sys = { version = "0.3.66", features = ["Request", "RequestInit", "Headers", "Response", "ResponseInit", "WebSocket", "BinaryType", "MessageEvent"] } -wasm-streams = "0.4.0" -tokio-util = { version = "0.7.10", features = ["io"] } -async-compression = { version = "0.4.5", features = ["tokio", "gzip", "brotli"] } -fastwebsockets = { version = "0.6.0", features = ["unstable-split"] } -base64 = "0.21.7" -wisp-mux = { path = "../wisp", features = ["tokio_io", "wasm"] } -async_io_stream = { version = "0.3.3", features = ["tokio_io"] } -getrandom = { version = "0.2.12", features = ["js"] } -hyper-util-wasm = { version = "0.1.3", features = ["client", "client-legacy", "http1", "http2"] } -tokio = { version = "1.36.0", default-features = false } -tower-service = "0.3.2" -console_error_panic_hook = "0.1.7" -send_wrapper = "0.6.0" -event-listener = "5.2.0" -wasmtimer = "0.2.0" +async-compression = { version = "0.4.11", features = ["futures-io", "gzip", "brotli"] } async-trait = "0.1.80" +base64 = "0.22.1" +bytes = "1.6.0" +event-listener = "5.3.1" +fastwebsockets = { version = "0.7.2", features = ["unstable-split"] } +flume = "0.11.0" +futures-rustls = { version = "0.26.0", default-features = false, features = ["tls12", "ring"] } +futures-util = { version = "0.3.30", features = ["sink"] } +getrandom = { version = "0.2.15", features = ["js"] } +http = "1.1.0" +http-body-util = "0.1.2" +hyper = "1.3.1" +hyper-util-wasm = { version = "0.1.3", features = ["client-legacy", "http1", "http2"] } +js-sys = "0.3.69" +pin-project-lite = "0.2.14" +send_wrapper = "0.4.0" +thiserror = "1.0.61" +tokio = "1.38.0" +tower-service = "0.3.2" +wasm-bindgen = "0.2.92" +wasm-bindgen-futures = "0.4.42" +wasm-streams = "0.4.0" +web-sys = { version = "0.3.69", features = ["BinaryType", "Headers", "MessageEvent", "Request", "RequestInit", "Response", "ResponseInit", "WebSocket"] } +wisp-mux = { version = "4.0.1", path = "../wisp", features = ["wasm"] } [dependencies.ring] # update whenever rustls updates @@ -45,9 +42,3 @@ features = ["wasm32_unknown_unknown_js"] # update whenever rustls updates version = "1.4.1" features = ["web"] - -[dev-dependencies] -default-env = "0.1.1" -wasm-bindgen-test = "0.3.42" -web-sys = { version = "0.3.69", features = ["FormData", "UrlSearchParams"] } -webpki-roots = "0.26.0" diff --git a/client/src/io_stream.rs b/client/src/io_stream.rs new file mode 100644 index 0000000..02cc975 --- /dev/null +++ b/client/src/io_stream.rs @@ -0,0 +1,174 @@ +use bytes::{BufMut, BytesMut}; +use futures_util::{ + io::WriteHalf, lock::Mutex, stream::SplitSink, AsyncReadExt, AsyncWriteExt, SinkExt, StreamExt, +}; +use js_sys::{Function, Uint8Array}; +use wasm_bindgen::prelude::*; +use wasm_bindgen_futures::spawn_local; + +use crate::{ + stream_provider::{ProviderAsyncRW, ProviderUnencryptedStream}, + utils::convert_body, + EpoxyError, EpoxyHandlers, +}; + +#[wasm_bindgen] +pub struct EpoxyIoStream { + tx: Mutex>, + onerror: Function, +} + +impl EpoxyIoStream { + pub(crate) fn connect(stream: ProviderAsyncRW, handlers: EpoxyHandlers) -> Self { + let (mut rx, tx) = stream.split(); + let tx = Mutex::new(tx); + + let EpoxyHandlers { + onopen, + onclose, + onerror, + onmessage, + } = handlers; + + let onerror_cloned = onerror.clone(); + + // similar to tokio::io::ReaderStream + spawn_local(async move { + let mut buf = BytesMut::with_capacity(4096); + loop { + match rx.read(buf.as_mut()).await { + Ok(cnt) => { + unsafe { buf.advance_mut(cnt) }; + + let _ = onmessage + .call1(&JsValue::null(), &Uint8Array::from(buf.split().as_ref())); + } + Err(err) => { + let _ = onerror.call1(&JsValue::null(), &JsError::from(err).into()); + break; + } + } + } + let _ = onclose.call0(&JsValue::null()); + }); + + let _ = onopen.call0(&JsValue::null()); + + Self { + tx, + onerror: onerror_cloned, + } + } + + pub async fn send(&self, payload: JsValue) -> Result<(), EpoxyError> { + let ret: Result<(), EpoxyError> = async move { + let payload = convert_body(payload) + .await + .map_err(|_| EpoxyError::InvalidPayload)? + .0 + .to_vec(); + Ok(self.tx.lock().await.write_all(&payload).await?) + } + .await; + + match ret { + Ok(ok) => Ok(ok), + Err(err) => { + let _ = self + .onerror + .call1(&JsValue::null(), &err.to_string().into()); + Err(err) + } + } + } + + pub async fn close(&self) -> Result<(), EpoxyError> { + match self.tx.lock().await.close().await { + Ok(ok) => Ok(ok), + Err(err) => { + let _ = self + .onerror + .call1(&JsValue::null(), &err.to_string().into()); + Err(err.into()) + } + } + } +} + +#[wasm_bindgen] +pub struct EpoxyUdpStream { + tx: Mutex>>, + onerror: Function, +} + +impl EpoxyUdpStream { + pub(crate) fn connect(stream: ProviderUnencryptedStream, handlers: EpoxyHandlers) -> Self { + let (tx, mut rx) = stream.split(); + let tx = Mutex::new(tx); + + let EpoxyHandlers { + onopen, + onclose, + onerror, + onmessage, + } = handlers; + + let onerror_cloned = onerror.clone(); + + spawn_local(async move { + while let Some(packet) = rx.next().await { + match packet { + Ok(buf) => { + let _ = onmessage.call1(&JsValue::null(), &Uint8Array::from(buf.as_ref())); + } + Err(err) => { + let _ = onerror.call1(&JsValue::null(), &JsError::from(err).into()); + break; + } + } + } + let _ = onclose.call0(&JsValue::null()); + }); + + let _ = onopen.call0(&JsValue::null()); + + Self { + tx, + onerror: onerror_cloned, + } + } + + pub async fn send(&self, payload: JsValue) -> Result<(), EpoxyError> { + let ret: Result<(), EpoxyError> = async move { + let payload = convert_body(payload) + .await + .map_err(|_| EpoxyError::InvalidPayload)? + .0 + .to_vec(); + Ok(self.tx.lock().await.send(payload).await?) + } + .await; + + match ret { + Ok(ok) => Ok(ok), + Err(err) => { + let _ = self + .onerror + .call1(&JsValue::null(), &err.to_string().into()); + Err(err) + } + } + } + + pub async fn close(&self) -> Result<(), EpoxyError> { + match self.tx.lock().await.close().await { + Ok(ok) => Ok(ok), + Err(err) => { + let _ = self + .onerror + .call1(&JsValue::null(), &err.to_string().into()); + Err(err.into()) + } + } + } +} diff --git a/client/src/lib.rs b/client/src/lib.rs index 5f5946d..decd466 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1,185 +1,333 @@ #![feature(let_chains, impl_trait_in_assoc_type)] -#[macro_use] -mod utils; -mod tls_stream; -mod tokioio; -mod udp_stream; -mod websocket; -mod wrappers; +use std::{str::FromStr, sync::Arc}; -use tls_stream::EpxTlsStream; -use tokioio::TokioIo; -use udp_stream::EpxUdpStream; -use utils::object_to_trustanchor; -pub use utils::{Boolinator, ReplaceErr, UriExt}; -use websocket::EpxWebSocket; -use wrappers::{IncomingBody, ServiceWrapper, TlsWispService, WebSocketWrapper}; - -use std::sync::Arc; - -use async_compression::tokio::bufread as async_comp; -use async_io_stream::IoStream; +use async_compression::futures::bufread as async_comp; use bytes::Bytes; -use futures_util::StreamExt; -use http::{uri, HeaderName, HeaderValue, Request, Response}; +use futures_util::{future::Either, TryStreamExt}; +use http::{ + header::{InvalidHeaderName, InvalidHeaderValue}, + method::InvalidMethod, + uri::{InvalidUri, InvalidUriParts}, + HeaderName, HeaderValue, Method, Request, Response, +}; use hyper::{body::Incoming, Uri}; use hyper_util_wasm::client::legacy::Client; -use js_sys::{Array, Function, Object, Reflect, Uint8Array}; -use rustls::pki_types::TrustAnchor; -use tokio::sync::RwLock; -use tokio_rustls::{client::TlsStream, rustls, rustls::RootCertStore, TlsConnector}; -use tokio_util::{ - either::Either, - io::{ReaderStream, StreamReader}, +use io_stream::{EpoxyIoStream, EpoxyUdpStream}; +use js_sys::{Array, Function, Object, Reflect}; +use stream_provider::{StreamProvider, StreamProviderService}; +use thiserror::Error; +use utils::{ + convert_body, entries_of_object, is_null_body, is_redirect, object_get, object_set, + IncomingBody, UriExt, WasmExecutor, }; -use wasm_bindgen::{intern, prelude::*}; -use wisp_mux::{ClientMux, MuxStreamIo, StreamType}; +use wasm_bindgen::prelude::*; +use wasm_streams::ReadableStream; +use web_sys::ResponseInit; +use websocket::EpoxyWebSocket; +use wisp_mux::StreamType; + +mod io_stream; +mod stream_provider; +mod tokioio; +mod utils; +mod websocket; +mod ws_wrapper; type HttpBody = http_body_util::Full; +#[derive(Debug, Error)] +pub enum EpoxyError { + #[error(transparent)] + InvalidDnsName(#[from] futures_rustls::rustls::pki_types::InvalidDnsNameError), + #[error(transparent)] + Wisp(#[from] wisp_mux::WispError), + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + Http(#[from] http::Error), + #[error(transparent)] + HyperClient(#[from] hyper_util_wasm::client::legacy::Error), + #[error(transparent)] + Hyper(#[from] hyper::Error), + #[error(transparent)] + ToStr(#[from] http::header::ToStrError), + #[error(transparent)] + GetRandom(#[from] getrandom::Error), + #[error(transparent)] + FastWebSockets(#[from] fastwebsockets::WebSocketError), + + #[error("Invalid URL scheme")] + InvalidUrlScheme, + #[error("No URL host found")] + NoUrlHost, + #[error("No URL port found")] + NoUrlPort, + #[error("Invalid request body")] + InvalidRequestBody, + #[error("Invalid request")] + InvalidRequest, + #[error("Invalid websocket response status code")] + WsInvalidStatusCode, + #[error("Invalid websocket upgrade header")] + WsInvalidUpgradeHeader, + #[error("Invalid websocket connection header")] + WsInvalidConnectionHeader, + #[error("Invalid websocket payload")] + WsInvalidPayload, + #[error("Invalid payload")] + InvalidPayload, + + #[error("Invalid certificate store")] + InvalidCertStore, + #[error("WebSocket failed to connect")] + WebSocketConnectFailed, + + #[error("Failed to construct response headers object")] + ResponseHeadersFromEntriesFailed, + #[error("Failed to construct response object")] + ResponseNewFailed, + #[error("Failed to construct define_property object")] + DefinePropertyObjFailed, + #[error("Failed to set raw header item")] + RawHeaderSetFailed, +} + +impl From for JsValue { + fn from(value: EpoxyError) -> Self { + JsError::from(value).into() + } +} + +impl From for EpoxyError { + fn from(value: InvalidUri) -> Self { + http::Error::from(value).into() + } +} + +impl From for EpoxyError { + fn from(value: InvalidUriParts) -> Self { + http::Error::from(value).into() + } +} + +impl From for EpoxyError { + fn from(value: InvalidHeaderName) -> Self { + http::Error::from(value).into() + } +} + +impl From for EpoxyError { + fn from(value: InvalidHeaderValue) -> Self { + http::Error::from(value).into() + } +} + +impl From for EpoxyError { + fn from(value: InvalidMethod) -> Self { + http::Error::from(value).into() + } +} + #[derive(Debug)] -enum EpxResponse { +enum EpoxyResponse { Success(Response), Redirect((Response, http::Request)), } -enum EpxCompression { +enum EpoxyCompression { Brotli, Gzip, } -type EpxIoUnencryptedStream = IoStream>; -type EpxIoTlsStream = TlsStream; -type EpxIoStream = Either; - -#[wasm_bindgen(start)] -fn init() { - console_error_panic_hook::set_once(); - // utils.rs - intern("value"); - intern("writable"); - intern("POST"); - - // main.rs - intern("method"); - intern("redirect"); - intern("body"); - intern("headers"); - intern("url"); - intern("redirected"); - intern("rawHeaders"); - intern("Content-Type"); +#[wasm_bindgen] +pub struct EpoxyClientOptions { + pub wisp_v2: bool, + pub udp_extension_required: bool, + #[wasm_bindgen(getter_with_clone)] + pub websocket_protocols: Vec, + pub redirect_limit: usize, + #[wasm_bindgen(getter_with_clone)] + pub user_agent: String, } -#[wasm_bindgen(inspectable)] +#[wasm_bindgen] +impl EpoxyClientOptions { + #[wasm_bindgen(constructor)] + pub fn new_default() -> Self { + Self::default() + } +} + +impl Default for EpoxyClientOptions { + fn default() -> Self { + Self { + wisp_v2: true, + udp_extension_required: true, + websocket_protocols: Vec::new(), + redirect_limit: 10, + user_agent: "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36".to_string(), + } + } +} + +#[wasm_bindgen(getter_with_clone)] +pub struct EpoxyHandlers { + pub onopen: Function, + pub onclose: Function, + pub onerror: Function, + pub onmessage: Function, +} + +#[wasm_bindgen] +impl EpoxyHandlers { + #[wasm_bindgen(constructor)] + pub fn new( + onopen: Function, + onclose: Function, + onerror: Function, + onmessage: Function, + ) -> Self { + Self { + onopen, + onclose, + onerror, + onmessage, + } + } +} + +#[wasm_bindgen] pub struct EpoxyClient { - rustls_config: Arc, - mux: Arc>, - hyper_client: Client, - #[wasm_bindgen(getter_with_clone)] - pub useragent: String, - #[wasm_bindgen(js_name = "redirectLimit")] - pub redirect_limit: usize, + stream_provider: Arc, + client: Client, + + redirect_limit: usize, + user_agent: String, } #[wasm_bindgen] impl EpoxyClient { #[wasm_bindgen(constructor)] - pub async fn new( - ws_url: String, - useragent: String, - redirect_limit: usize, + pub fn new( + wisp_url: String, certs: Array, - ) -> Result { - let ws_uri = ws_url - .parse::() - .replace_err("Failed to parse websocket url")?; - - let ws_uri_scheme = ws_uri - .scheme_str() - .replace_err("Websocket URL must have a scheme")?; - if ws_uri_scheme != "ws" && ws_uri_scheme != "wss" { - return Err(JsError::new("Scheme must be either `ws` or `wss`")); + options: EpoxyClientOptions, + ) -> Result { + let wisp_url: Uri = wisp_url.try_into()?; + if wisp_url.scheme_str() != Some("wss") && wisp_url.scheme_str() != Some("ws") { + return Err(EpoxyError::InvalidUrlScheme); } - let (mux, fut) = utils::make_mux(&ws_url).await?; - let mux = Arc::new(RwLock::new(mux)); - utils::spawn_mux_fut(mux.clone(), fut, ws_url.clone()); + let stream_provider = Arc::new(StreamProvider::new(wisp_url.to_string(), certs, &options)?); - let mut certstore = RootCertStore::empty(); - let certs: Result, JsValue> = - certs.iter().map(object_to_trustanchor).collect(); - certstore.extend( - certs - .replace_err("Failed to get certificates from cert store")? - .into_iter(), - ); + let service = StreamProviderService(stream_provider.clone()); + let client = Client::builder(WasmExecutor) + .http09_responses(true) + .http1_title_case_headers(true) + .http1_preserve_header_case(true) + .build(service); - let rustls_config = Arc::new( - rustls::ClientConfig::builder() - .with_root_certificates(certstore) - .with_no_client_auth(), - ); - - Ok(EpoxyClient { - mux: mux.clone(), - hyper_client: Client::builder(utils::WasmExecutor {}) - .http09_responses(true) - .http1_title_case_headers(true) - .http1_preserve_header_case(true) - .build(TlsWispService { - rustls_config: rustls_config.clone(), - service: ServiceWrapper(mux, ws_url), - }), - rustls_config, - useragent, - redirect_limit, + Ok(Self { + stream_provider, + client, + redirect_limit: options.redirect_limit, + user_agent: options.user_agent, }) } - async fn get_tls_io(&self, url_host: &str, url_port: u16) -> Result { - let channel = self - .mux - .write() + pub async fn connect_websocket( + &self, + handlers: EpoxyHandlers, + url: String, + protocols: Vec, + ) -> Result { + EpoxyWebSocket::connect(self, handlers, url, protocols).await + } + + pub async fn connect_tcp( + &self, + handlers: EpoxyHandlers, + url: String, + ) -> Result { + let url: Uri = url.try_into()?; + let host = url.host().ok_or(EpoxyError::NoUrlHost)?; + let port = url.port_u16().ok_or(EpoxyError::NoUrlPort)?; + match self + .stream_provider + .get_asyncread(StreamType::Tcp, host.to_string(), port) .await - .client_new_stream(StreamType::Tcp, url_host.to_string(), url_port) + { + Ok(stream) => Ok(EpoxyIoStream::connect(Either::Right(stream), handlers)), + Err(err) => { + let _ = handlers + .onerror + .call1(&JsValue::null(), &err.to_string().into()); + Err(err) + } + } + } + + pub async fn connect_tls( + &self, + handlers: EpoxyHandlers, + url: String, + ) -> Result { + let url: Uri = url.try_into()?; + let host = url.host().ok_or(EpoxyError::NoUrlHost)?; + let port = url.port_u16().ok_or(EpoxyError::NoUrlPort)?; + match self + .stream_provider + .get_tls_stream(host.to_string(), port) .await - .replace_err("Failed to create multiplexor channel")? - .into_io() - .into_asyncrw(); - let connector = TlsConnector::from(self.rustls_config.clone()); - let io = connector - .connect( - url_host - .to_string() - .try_into() - .replace_err("Failed to parse URL (rustls)")?, - channel, - ) + { + Ok(stream) => Ok(EpoxyIoStream::connect(Either::Left(stream), handlers)), + Err(err) => { + let _ = handlers + .onerror + .call1(&JsValue::null(), &err.to_string().into()); + Err(err) + } + } + } + + pub async fn connect_udp( + &self, + handlers: EpoxyHandlers, + url: String, + ) -> Result { + let url: Uri = url.try_into()?; + let host = url.host().ok_or(EpoxyError::NoUrlHost)?; + let port = url.port_u16().ok_or(EpoxyError::NoUrlPort)?; + match self + .stream_provider + .get_stream(StreamType::Udp, host.to_string(), port) .await - .replace_err("Failed to perform TLS handshake")?; - Ok(io) + { + Ok(stream) => Ok(EpoxyUdpStream::connect(stream, handlers)), + Err(err) => { + let _ = handlers + .onerror + .call1(&JsValue::null(), &err.to_string().into()); + Err(err) + } + } } async fn send_req_inner( &self, req: http::Request, should_redirect: bool, - ) -> Result { + ) -> Result { let new_req = if should_redirect { Some(req.clone()) } else { None }; - let res = self - .hyper_client - .request(req) - .await - .replace_err("Failed to send request"); + let res = self.client.request(req).await; match res { Ok(res) => { - if utils::is_redirect(res.status().as_u16()) + if is_redirect(res.status().as_u16()) && let Some(mut new_req) = new_req && let Some(location) = res.headers().get("Location") && let Ok(redirect_url) = new_req.uri().get_redirect(location) @@ -190,12 +338,12 @@ impl EpoxyClient { "Host", HeaderValue::from_str(redirect_url_authority.as_str())?, ); - Ok(EpxResponse::Redirect((res, new_req))) + Ok(EpoxyResponse::Redirect((res, new_req))) } else { - Ok(EpxResponse::Success(res)) + Ok(EpoxyResponse::Success(res)) } } - Err(err) => Err(err), + Err(err) => Err(err.into()), } } @@ -203,14 +351,14 @@ impl EpoxyClient { &self, req: http::Request, should_redirect: bool, - ) -> Result<(hyper::Response, Uri, bool), JsError> { + ) -> Result<(hyper::Response, Uri, bool), EpoxyError> { let mut redirected = false; let mut current_url = req.uri().clone(); - let mut current_resp: EpxResponse = self.send_req_inner(req, should_redirect).await?; + let mut current_resp: EpoxyResponse = self.send_req_inner(req, should_redirect).await?; for _ in 0..self.redirect_limit { match current_resp { - EpxResponse::Success(_) => break, - EpxResponse::Redirect((_, req)) => { + EpoxyResponse::Success(_) => break, + EpoxyResponse::Redirect((_, req)) => { redirected = true; current_url = req.uri().clone(); current_resp = self.send_req_inner(req, should_redirect).await? @@ -219,109 +367,75 @@ impl EpoxyClient { } match current_resp { - EpxResponse::Success(resp) => Ok((resp, current_url, redirected)), - EpxResponse::Redirect((resp, _)) => Ok((resp, current_url, redirected)), + EpoxyResponse::Success(resp) => Ok((resp, current_url, redirected)), + EpoxyResponse::Redirect((resp, _)) => Ok((resp, current_url, redirected)), } } - // shut up - #[allow(clippy::too_many_arguments)] - pub async fn connect_ws( + pub async fn fetch( &self, - onopen: Function, - onclose: Function, - onerror: Function, - onmessage: Function, url: String, - protocols: Vec, - origin: String, - ) -> Result { - EpxWebSocket::connect( - self, onopen, onclose, onerror, onmessage, url, protocols, origin, - ) - .await - } + options: Object, + ) -> Result { + let url: Uri = url.try_into()?; + // only valid `Scheme`s are HTTP and HTTPS, which are the ones we support + url.scheme().ok_or(EpoxyError::InvalidUrlScheme)?; - pub async fn connect_tls( - &self, - onopen: Function, - onclose: Function, - onerror: Function, - onmessage: Function, - url: String, - ) -> Result { - EpxTlsStream::connect(self, onopen, onclose, onerror, onmessage, url).await - } + let host = url.host().ok_or(EpoxyError::NoUrlHost)?; - pub async fn connect_udp( - &self, - onopen: Function, - onclose: Function, - onerror: Function, - onmessage: Function, - url: String, - ) -> Result { - EpxUdpStream::connect(self, onopen, onclose, onerror, onmessage, url).await - } + let request_method = object_get(&options, "method") + .and_then(|x| x.as_string()) + .unwrap_or_else(|| "GET".to_string()); + let request_method: Method = Method::from_str(&request_method)?; - pub async fn fetch(&self, url: String, options: Object) -> Result { - let uri = url.parse::().replace_err("Failed to parse URL")?; - let uri_scheme = uri.scheme().replace_err("URL must have a scheme")?; - if *uri_scheme != uri::Scheme::HTTP && *uri_scheme != uri::Scheme::HTTPS { - return Err(jerr!("Scheme must be either `http` or `https`")); - } - let uri_host = uri.host().replace_err("URL must have a host")?; - - let req_method_string: String = match Reflect::get(&options, &jval!("method")) { - Ok(val) => val.as_string().unwrap_or("GET".to_string()), - Err(_) => "GET".to_string(), - }; - let req_method: http::Method = http::Method::try_from(req_method_string.as_str()) - .replace_err("Invalid http method")?; - - let req_should_redirect = match Reflect::get(&options, &jval!("redirect")) { - Ok(val) => !matches!( - val.as_string().unwrap_or_default().as_str(), - "error" | "manual" - ), - Err(_) => true, - }; + let request_redirect = object_get(&options, "redirect") + .map(|x| { + !matches!( + x.as_string().unwrap_or_default().as_str(), + "error" | "manual" + ) + }) + .unwrap_or(true); let mut body_content_type: Option = None; - let body_jsvalue: Option = Reflect::get(&options, &jval!("body")).ok(); - let body_bytes: Bytes = match body_jsvalue { + let body = match object_get(&options, "body") { Some(buf) => { - let (body, req) = utils::jval_to_u8_array_req(buf) + let (body, req) = convert_body(buf) .await - .replace_err("Invalid body")?; + .map_err(|_| EpoxyError::InvalidRequestBody)?; body_content_type = req.headers().get("Content-Type").ok().flatten(); Bytes::from(body.to_vec()) } None => Bytes::new(), }; - let headers = Reflect::get(&options, &jval!("headers")) - .map(|val| { - if web_sys::Headers::instanceof(&val) { - Some(utils::entries_of_object(&Object::from_entries(&val).ok()?)) - } else if val.is_truthy() { - Some(utils::entries_of_object(&Object::from(val))) - } else { - None - } - }) - .unwrap_or(None); + let headers = object_get(&options, "headers").and_then(|val| { + if web_sys::Headers::instanceof(&val) { + Some(entries_of_object(&Object::from_entries(&val).ok()?)) + } else if val.is_truthy() { + Some(entries_of_object(&Object::from(val))) + } else { + None + } + }); - let mut builder = Request::builder().uri(uri.clone()).method(req_method); + let mut request_builder = Request::builder().uri(url.clone()).method(request_method); - let headers_map = builder.headers_mut().replace_err("Failed to get headers")?; - headers_map.insert("Accept-Encoding", HeaderValue::from_static("gzip, br")); + // Generic InvalidRequest because this only returns None if the builder has some error + // which we don't know + let headers_map = request_builder + .headers_mut() + .ok_or(EpoxyError::InvalidRequest)?; + + headers_map.insert("Accept-Encoding", HeaderValue::from_static("identity")); headers_map.insert("Connection", HeaderValue::from_static("keep-alive")); - headers_map.insert("User-Agent", HeaderValue::from_str(&self.useragent)?); - headers_map.insert("Host", HeaderValue::from_str(uri_host)?); - if body_bytes.is_empty() { + headers_map.insert("User-Agent", HeaderValue::from_str(&self.user_agent)?); + headers_map.insert("Host", HeaderValue::from_str(host)?); + + if body.is_empty() { headers_map.insert("Content-Length", HeaderValue::from_static("0")); } + if let Some(content_type) = body_content_type { headers_map.insert("Content-Type", HeaderValue::from_str(&content_type)?); } @@ -329,122 +443,107 @@ impl EpoxyClient { if let Some(headers) = headers { for hdr in headers { headers_map.insert( - HeaderName::from_bytes(hdr[0].as_bytes()) - .replace_err("Failed to get hdr name")?, - HeaderValue::from_bytes(hdr[1].as_bytes()) - .replace_err("Failed to get hdr value")?, + HeaderName::from_str(&hdr[0])?, + HeaderValue::from_str(&hdr[1])?, ); } } - let request = builder - .body(HttpBody::new(body_bytes)) - .replace_err("Failed to make request")?; + let (response, response_uri, redirected) = self + .send_req(request_builder.body(HttpBody::new(body))?, request_redirect) + .await?; - let (resp, resp_uri, req_redirected) = self.send_req(request, req_should_redirect).await?; - - let resp_headers_raw = resp.headers().clone(); - - let resp_headers_jsarray = resp + let response_headers: Array = response .headers() .iter() .filter_map(|val| { Some(Array::of2( - &jval!(val.0.as_str()), - &jval!(val.1.to_str().ok()?), + &val.0.as_str().into(), + &val.1.to_str().ok()?.into(), )) }) - .collect::(); + .collect(); + let response_headers = Object::from_entries(&response_headers) + .map_err(|_| EpoxyError::ResponseHeadersFromEntriesFailed)?; - let resp_headers = Object::from_entries(&resp_headers_jsarray) - .replace_err("Failed to create response headers object")?; + let response_headers_raw = response.headers().clone(); - let mut respinit = web_sys::ResponseInit::new(); - respinit - .headers(&resp_headers) - .status(resp.status().as_u16()) - .status_text(resp.status().canonical_reason().unwrap_or_default()); + let mut response_builder = ResponseInit::new(); + response_builder + .headers(&response_headers) + .status(response.status().as_u16()) + .status_text(response.status().canonical_reason().unwrap_or_default()); - let stream = if !utils::is_null_body(resp.status().as_u16()) { - let compression = match resp + let response_stream = if !is_null_body(response.status().as_u16()) { + let compression = match response .headers() .get("Content-Encoding") .and_then(|val| val.to_str().ok()) .unwrap_or_default() { - "gzip" => Some(EpxCompression::Gzip), - "br" => Some(EpxCompression::Brotli), + "gzip" => Some(EpoxyCompression::Gzip), + "br" => Some(EpoxyCompression::Brotli), _ => None, }; - let incoming_body = IncomingBody::new(resp.into_body()); - + let response_body = IncomingBody::new(response.into_body()).into_async_read(); let decompressed_body = match compression { Some(alg) => match alg { - EpxCompression::Gzip => Either::Left(Either::Left(ReaderStream::new( - async_comp::GzipDecoder::new(StreamReader::new(incoming_body)), - ))), - EpxCompression::Brotli => Either::Left(Either::Right(ReaderStream::new( - async_comp::BrotliDecoder::new(StreamReader::new(incoming_body)), - ))), + EpoxyCompression::Gzip => { + Either::Left(Either::Left(async_comp::GzipDecoder::new(response_body))) + } + EpoxyCompression::Brotli => { + Either::Left(Either::Right(async_comp::BrotliDecoder::new(response_body))) + } }, - None => Either::Right(incoming_body), + None => Either::Right(response_body), }; - Some( - wasm_streams::ReadableStream::from_stream(decompressed_body.map(|x| { - Ok(Uint8Array::from( - x.replace_err_jv("Failed to get frame from response")? - .as_ref(), - ) - .into()) - })) - .into_raw(), - ) + Some(ReadableStream::from_async_read(decompressed_body, 1024).into_raw()) } else { None }; - let resp = - web_sys::Response::new_with_opt_readable_stream_and_init(stream.as_ref(), &respinit) - .replace_err("Failed to make response")?; + let resp = web_sys::Response::new_with_opt_readable_stream_and_init( + response_stream.as_ref(), + &response_builder, + ) + .map_err(|_| EpoxyError::ResponseNewFailed)?; Object::define_property( &resp, - &jval!("url"), - &utils::define_property_obj(jval!(resp_uri.to_string()), false) - .replace_err("Failed to make define_property object for url")?, + &"url".into(), + &utils::define_property_obj(response_uri.to_string().into(), false) + .map_err(|_| EpoxyError::DefinePropertyObjFailed)?, ); Object::define_property( &resp, - &jval!("redirected"), - &utils::define_property_obj(jval!(req_redirected), false) - .replace_err("Failed to make define_property object for redirected")?, + &"redirected".into(), + &utils::define_property_obj(redirected.into(), false) + .map_err(|_| EpoxyError::DefinePropertyObjFailed)?, ); let raw_headers = Object::new(); - for (k, v) in resp_headers_raw.iter() { - let k = jval!(k.to_string()); - let v = jval!(v.to_str()?.to_string()); + for (k, v) in response_headers_raw.iter() { + let k: JsValue = k.to_string().into(); + let v: JsValue = v.to_str()?.to_string().into(); if let Ok(jv) = Reflect::get(&raw_headers, &k) { if jv.is_array() { let arr = Array::from(&jv); - arr.push(&v); - Reflect::set(&raw_headers, &k, &arr).flatten("Failed to set rawHeader")?; + object_set(&raw_headers, &k, &arr)?; } else if jv.is_truthy() { - Reflect::set(&raw_headers, &k, &Array::of2(&jv, &v)) - .flatten("Failed to set rawHeader")?; + object_set(&raw_headers, &k, &Array::of2(&jv, &v))?; } else { - Reflect::set(&raw_headers, &k, &v).flatten("Failed to set rawHeader")?; + object_set(&raw_headers, &k, &v)?; } } } Object::define_property( &resp, - &jval!("rawHeaders"), - &utils::define_property_obj(jval!(&raw_headers), false) - .replace_err("Failed to make define_property object for rawHeaders")?, + &"rawHeaders".into(), + &utils::define_property_obj(raw_headers.into(), false) + .map_err(|_| EpoxyError::DefinePropertyObjFailed)?, ); Ok(resp) diff --git a/client/src/stream_provider.rs b/client/src/stream_provider.rs new file mode 100644 index 0000000..4bde16f --- /dev/null +++ b/client/src/stream_provider.rs @@ -0,0 +1,247 @@ +use std::{pin::Pin, sync::Arc, task::Poll}; + +use futures_rustls::{ + rustls::{ClientConfig, RootCertStore}, + TlsConnector, TlsStream, +}; +use futures_util::{future::Either, lock::Mutex, AsyncRead, AsyncWrite, Future}; +use hyper_util_wasm::client::legacy::connect::{Connected, Connection}; +use js_sys::{Array, Reflect, Uint8Array}; +use pin_project_lite::pin_project; +use rustls_pki_types::{Der, TrustAnchor}; +use tower_service::Service; +use wasm_bindgen::{JsCast, JsValue}; +use wasm_bindgen_futures::spawn_local; +use wisp_mux::{ + extensions::{udp::UdpProtocolExtensionBuilder, ProtocolExtensionBuilder}, + ClientMux, IoStream, MuxStreamIo, StreamType, WispError, +}; + +use crate::{ws_wrapper::WebSocketWrapper, EpoxyClientOptions, EpoxyError}; + +fn object_to_trustanchor(obj: JsValue) -> Result, JsValue> { + let subject: Uint8Array = Reflect::get(&obj, &"subject".into())?.dyn_into()?; + let pub_key_info: Uint8Array = + Reflect::get(&obj, &"subject_public_key_info".into())?.dyn_into()?; + let name_constraints: Option = Reflect::get(&obj, &"name_constraints".into()) + .and_then(|x| x.dyn_into()) + .ok(); + Ok(TrustAnchor { + subject: Der::from(subject.to_vec()), + subject_public_key_info: Der::from(pub_key_info.to_vec()), + name_constraints: name_constraints.map(|x| Der::from(x.to_vec())), + }) +} + +pub struct StreamProvider { + wisp_url: String, + + wisp_v2: bool, + udp_extension: bool, + websocket_protocols: Vec, + + client_config: Arc, + + current_client: Arc>>, +} + +pub type ProviderUnencryptedStream = MuxStreamIo; +pub type ProviderUnencryptedAsyncRW = IoStream>; +pub type ProviderTlsAsyncRW = TlsStream; +pub type ProviderAsyncRW = Either; + +impl StreamProvider { + pub fn new( + wisp_url: String, + certs: Array, + options: &EpoxyClientOptions, + ) -> Result { + let certs: Result, JsValue> = + certs.iter().map(object_to_trustanchor).collect(); + let certstore = RootCertStore::from_iter(certs.map_err(|_| EpoxyError::InvalidCertStore)?); + let client_config = Arc::new( + ClientConfig::builder() + .with_root_certificates(certstore) + .with_no_client_auth(), + ); + + Ok(Self { + wisp_url, + current_client: Arc::new(Mutex::new(None)), + wisp_v2: options.wisp_v2, + udp_extension: options.udp_extension_required, + websocket_protocols: options.websocket_protocols.clone(), + client_config, + }) + } + + async fn create_client(&self) -> Result<(), EpoxyError> { + let extensions_vec: Vec> = + vec![Box::new(UdpProtocolExtensionBuilder())]; + let extensions = if self.wisp_v2 { + Some(extensions_vec.as_slice()) + } else { + None + }; + let (write, read) = WebSocketWrapper::connect(&self.wisp_url, &self.websocket_protocols)?; + if !write.wait_for_open().await { + return Err(EpoxyError::WebSocketConnectFailed); + } + let client = ClientMux::create(read, write, extensions).await?; + let (mux, fut) = if self.udp_extension { + client.with_udp_extension_required().await? + } else { + client.with_no_required_extensions() + }; + self.current_client.lock().await.replace(mux); + let current_client = self.current_client.clone(); + spawn_local(async move { + fut.await; + current_client.lock().await.take(); + }); + Ok(()) + } + + pub async fn get_stream( + &self, + stream_type: StreamType, + host: String, + port: u16, + ) -> Result { + Box::pin(async { + if let Some(mux) = self.current_client.lock().await.as_ref() { + Ok(mux + .client_new_stream(stream_type, host, port) + .await? + .into_io()) + } else { + self.create_client().await?; + self.get_stream(stream_type, host, port).await + } + }) + .await + } + + pub async fn get_asyncread( + &self, + stream_type: StreamType, + host: String, + port: u16, + ) -> Result { + Ok(self + .get_stream(stream_type, host, port) + .await? + .into_asyncrw()) + } + + pub async fn get_tls_stream( + &self, + host: String, + port: u16, + ) -> Result { + let stream = self + .get_asyncread(StreamType::Tcp, host.clone(), port) + .await?; + let connector = TlsConnector::from(self.client_config.clone()); + Ok(connector.connect(host.try_into()?, stream).await?.into()) + } +} + +pin_project! { + pub struct HyperIo { + #[pin] + inner: ProviderAsyncRW, + } +} + +impl hyper::rt::Read for HyperIo { + fn poll_read( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + mut buf: hyper::rt::ReadBufCursor<'_>, + ) -> Poll> { + let buf_slice: &mut [u8] = unsafe { std::mem::transmute(buf.as_mut()) }; + match self.project().inner.poll_read(cx, buf_slice) { + Poll::Ready(bytes_read) => { + let bytes_read = bytes_read?; + unsafe { + buf.advance(bytes_read); + } + Poll::Ready(Ok(())) + } + Poll::Pending => Poll::Pending, + } + } +} + +impl hyper::rt::Write for HyperIo { + fn poll_write( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + buf: &[u8], + ) -> Poll> { + self.project().inner.poll_write(cx, buf) + } + + fn poll_flush( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + self.project().inner.poll_flush(cx) + } + + fn poll_shutdown( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + ) -> Poll> { + self.project().inner.poll_close(cx) + } + + fn poll_write_vectored( + self: std::pin::Pin<&mut Self>, + cx: &mut std::task::Context<'_>, + bufs: &[std::io::IoSlice<'_>], + ) -> Poll> { + self.project().inner.poll_write_vectored(cx, bufs) + } +} + +impl Connection for HyperIo { + fn connected(&self) -> Connected { + Connected::new() + } +} + +#[derive(Clone)] +pub struct StreamProviderService(pub Arc); + +impl Service for StreamProviderService { + type Response = HyperIo; + type Error = EpoxyError; + type Future = Pin>>>; + + fn poll_ready( + &mut self, + _: &mut std::task::Context<'_>, + ) -> std::task::Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: hyper::Uri) -> Self::Future { + let provider = self.0.clone(); + Box::pin(async move { + let scheme = req.scheme_str().ok_or(EpoxyError::InvalidUrlScheme)?; + let host = req.host().ok_or(WispError::UriHasNoHost)?.to_string(); + let port = req.port_u16().ok_or(WispError::UriHasNoPort)?; + Ok(HyperIo { + inner: match scheme { + "https" => Either::Left(provider.get_tls_stream(host, port).await?), + "http" => { + Either::Right(provider.get_asyncread(StreamType::Tcp, host, port).await?) + } + _ => return Err(EpoxyError::InvalidUrlScheme), + }, + }) + }) + } +} diff --git a/client/src/tls_stream.rs b/client/src/tls_stream.rs deleted file mode 100644 index 2071b1c..0000000 --- a/client/src/tls_stream.rs +++ /dev/null @@ -1,92 +0,0 @@ -use crate::*; - -use tokio::io::{split, AsyncWriteExt, WriteHalf}; - -#[wasm_bindgen(inspectable)] -pub struct EpxTlsStream { - tx: WriteHalf, - onerror: Function, - #[wasm_bindgen(readonly, getter_with_clone)] - pub url: String, -} - -#[wasm_bindgen] -impl EpxTlsStream { - #[wasm_bindgen(constructor)] - pub fn new() -> Result { - Err(jerr!("Use EpoxyClient.connect_tls() instead.")) - } - - pub async fn connect( - tcp: &EpoxyClient, - onopen: Function, - onclose: Function, - onerror: Function, - onmessage: Function, - url: String, - ) -> Result { - let onerr = onerror.clone(); - let ret: Result = async move { - let url = Uri::try_from(url).replace_err("Failed to parse URL")?; - let url_host = url.host().replace_err("URL must have a host")?; - let url_port = url.port().replace_err("URL must have a port")?.into(); - - let io = tcp.get_tls_io(url_host, url_port).await?; - let (rx, tx) = split(io); - let mut rx = ReaderStream::new(rx); - - wasm_bindgen_futures::spawn_local(async move { - while let Some(Ok(data)) = rx.next().await { - let _ = onmessage.call1( - &JsValue::null(), - &jval!(Uint8Array::from(data.to_vec().as_slice())), - ); - } - let _ = onclose.call0(&JsValue::null()); - }); - - onopen - .call0(&Object::default()) - .replace_err("Failed to call onopen")?; - - Ok(Self { - tx, - onerror, - url: url.to_string(), - }) - } - .await; - if let Err(ret) = ret { - let _ = onerr.call1(&JsValue::null(), &jval!(ret.clone())); - Err(ret) - } else { - ret - } - } - - #[wasm_bindgen] - pub async fn send(&mut self, payload: JsValue) -> Result<(), JsError> { - let onerr = self.onerror.clone(); - let ret = self - .tx - .write_all( - &utils::jval_to_u8_array(payload) - .await - .replace_err("Invalid payload")? - .to_vec(), - ) - .await; - if let Err(ret) = ret { - let _ = onerr.call1(&JsValue::null(), &jval!(format!("{}", ret))); - Err(ret.into()) - } else { - Ok(ret?) - } - } - - #[wasm_bindgen] - pub async fn close(&mut self) -> Result<(), JsError> { - self.tx.shutdown().await?; - Ok(()) - } -} diff --git a/client/src/tokioio.rs b/client/src/tokioio.rs index 4eb1c89..62cdf5c 100644 --- a/client/src/tokioio.rs +++ b/client/src/tokioio.rs @@ -167,9 +167,3 @@ where hyper::rt::Write::poll_write_vectored(self.project().inner, cx, bufs) } } - -impl hyper_util_wasm::client::legacy::connect::Connection for TokioIo { - fn connected(&self) -> hyper_util_wasm::client::legacy::connect::Connected { - hyper_util_wasm::client::legacy::connect::Connected::new() - } -} diff --git a/client/src/udp_stream.rs b/client/src/udp_stream.rs deleted file mode 100644 index 877bab4..0000000 --- a/client/src/udp_stream.rs +++ /dev/null @@ -1,98 +0,0 @@ -use crate::*; - -use futures_util::{stream::SplitSink, SinkExt}; - -#[wasm_bindgen(inspectable)] -pub struct EpxUdpStream { - tx: SplitSink>, - onerror: Function, - #[wasm_bindgen(readonly, getter_with_clone)] - pub url: String, -} - -#[wasm_bindgen] -impl EpxUdpStream { - #[wasm_bindgen(constructor)] - pub fn new() -> Result { - Err(jerr!("Use EpoxyClient.connect_udp() instead.")) - } - - pub async fn connect( - tcp: &EpoxyClient, - onopen: Function, - onclose: Function, - onerror: Function, - onmessage: Function, - url: String, - ) -> Result { - let onerr = onerror.clone(); - let ret: Result = async move { - let url = Uri::try_from(url).replace_err("Failed to parse URL")?; - let url_host = url.host().replace_err("URL must have a host")?; - let url_port = url.port().replace_err("URL must have a port")?.into(); - - let io = tcp - .mux - .write() - .await - .client_new_stream(StreamType::Udp, url_host.to_string(), url_port) - .await - .replace_err("Failed to open multiplexor channel")? - .into_io(); - let (tx, mut rx) = io.split(); - - wasm_bindgen_futures::spawn_local(async move { - while let Some(Ok(data)) = rx.next().await { - let _ = onmessage.call1( - &JsValue::null(), - &jval!(Uint8Array::from(data.to_vec().as_slice())), - ); - } - let _ = onclose.call0(&JsValue::null()); - }); - - onopen - .call0(&Object::default()) - .replace_err("Failed to call onopen")?; - - Ok(Self { - tx, - onerror, - url: url.to_string(), - }) - } - .await; - if let Err(ret) = ret { - let _ = onerr.call1(&JsValue::null(), &jval!(ret.clone())); - Err(ret) - } else { - ret - } - } - - #[wasm_bindgen] - pub async fn send(&mut self, payload: JsValue) -> Result<(), JsError> { - let onerr = self.onerror.clone(); - let ret = self - .tx - .send( - utils::jval_to_u8_array(payload) - .await - .replace_err("Invalid payload")? - .to_vec(), - ) - .await; - if let Err(ret) = ret { - let _ = onerr.call1(&JsValue::null(), &jval!(format!("{}", ret))); - Err(ret.into()) - } else { - Ok(ret?) - } - } - - #[wasm_bindgen] - pub async fn close(&mut self) -> Result<(), JsError> { - self.tx.close().await?; - Ok(()) - } -} diff --git a/client/src/utils.rs b/client/src/utils.rs index f0279e3..86d9b31 100644 --- a/client/src/utils.rs +++ b/client/src/utils.rs @@ -1,118 +1,25 @@ -use crate::*; +use std::{ + pin::Pin, + task::{Context, Poll}, +}; -use rustls_pki_types::Der; -use wasm_bindgen::prelude::*; +use bytes::Bytes; +use futures_util::{Future, Stream}; +use http::{HeaderValue, Uri}; +use hyper::{body::Body, rt::Executor}; +use js_sys::{Array, ArrayBuffer, Object, Reflect, Uint8Array}; +use pin_project_lite::pin_project; +use wasm_bindgen::{JsCast, JsValue}; use wasm_bindgen_futures::JsFuture; -use hyper::rt::Executor; -use js_sys::ArrayBuffer; -use std::future::Future; -use wisp_mux::{extensions::udp::UdpProtocolExtensionBuilder, WispError}; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(js_namespace = console, js_name = debug)] - pub fn console_debug(s: &str); - #[wasm_bindgen(js_namespace = console, js_name = log)] - pub fn console_log(s: &str); - #[wasm_bindgen(js_namespace = console, js_name = error)] - pub fn console_error(s: &str); -} - -macro_rules! debug { - ($($t:tt)*) => (utils::console_debug(&format_args!($($t)*).to_string())) -} - -macro_rules! log { - ($($t:tt)*) => (utils::console_log(&format_args!($($t)*).to_string())) -} - -#[allow(unused_macros)] -macro_rules! error { - ($($t:tt)*) => (utils::console_error(&format_args!($($t)*).to_string())) -} - -macro_rules! jerr { - ($expr:expr) => { - JsError::new($expr) - }; -} - -macro_rules! jval { - ($expr:expr) => { - JsValue::from($expr) - }; -} - -pub trait ReplaceErr { - type Ok; - - fn replace_err(self, err: &str) -> Result; - fn replace_err_jv(self, err: &str) -> Result; -} - -impl ReplaceErr for Result { - type Ok = T; - - fn replace_err(self, err: &str) -> Result<::Ok, JsError> { - self.map_err(|x| jerr!(&format!("{}, original error: {:?}", err, x))) - } - - fn replace_err_jv(self, err: &str) -> Result<::Ok, JsValue> { - self.map_err(|x| jval!(&format!("{}, original error: {:?}", err, x))) - } -} - -impl ReplaceErr for Option { - type Ok = T; - - fn replace_err(self, err: &str) -> Result<::Ok, JsError> { - self.ok_or_else(|| jerr!(err)) - } - - fn replace_err_jv(self, err: &str) -> Result<::Ok, JsValue> { - self.ok_or_else(|| jval!(err)) - } -} - -// the... BOOLINATOR! -impl ReplaceErr for bool { - type Ok = (); - - fn replace_err(self, err: &str) -> Result<(), JsError> { - if !self { - Err(jerr!(err)) - } else { - Ok(()) - } - } - - fn replace_err_jv(self, err: &str) -> Result<(), JsValue> { - if !self { - Err(jval!(err)) - } else { - Ok(()) - } - } -} - -// the... BOOLINATOR! -pub trait Boolinator { - fn flatten(self, err: &str) -> Result<(), JsError>; -} - -impl Boolinator for Result { - fn flatten(self, err: &str) -> Result<(), JsError> { - self.replace_err(err)?.replace_err(err) - } -} +use crate::EpoxyError; pub trait UriExt { - fn get_redirect(&self, location: &HeaderValue) -> Result; + fn get_redirect(&self, location: &HeaderValue) -> Result; } impl UriExt for Uri { - fn get_redirect(&self, location: &HeaderValue) -> Result { + fn get_redirect(&self, location: &HeaderValue) -> Result { let new_uri = location.to_str()?.parse::()?; let mut new_parts: http::uri::Parts = new_uri.into(); if new_parts.scheme.is_none() { @@ -141,8 +48,75 @@ where } } +pin_project! { + pub struct IncomingBody { + #[pin] + incoming: hyper::body::Incoming, + } +} + +impl IncomingBody { + pub fn new(incoming: hyper::body::Incoming) -> IncomingBody { + IncomingBody { incoming } + } +} + +impl Stream for IncomingBody { + type Item = std::io::Result; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + let ret = this.incoming.poll_frame(cx); + match ret { + Poll::Ready(item) => Poll::>::Ready(match item { + Some(frame) => frame + .map(|x| { + x.into_data() + .map_err(|_| std::io::Error::other("not data frame")) + }) + .ok(), + None => None, + }), + Poll::Pending => Poll::>::Pending, + } + } +} + +pub fn is_redirect(code: u16) -> bool { + [301, 302, 303, 307, 308].contains(&code) +} + +pub fn is_null_body(code: u16) -> bool { + [101, 204, 205, 304].contains(&code) +} + +pub fn object_get(obj: &Object, key: &str) -> Option { + Reflect::get(obj, &key.into()).ok() +} + +pub fn object_set(obj: &Object, key: &JsValue, value: &JsValue) -> Result<(), EpoxyError> { + if Reflect::set(obj, key, value).map_err(|_| EpoxyError::RawHeaderSetFailed)? { + Ok(()) + } else { + Err(EpoxyError::RawHeaderSetFailed) + } +} + +pub async fn convert_body(val: JsValue) -> Result<(Uint8Array, web_sys::Request), JsValue> { + let req = web_sys::Request::new_with_str_and_init( + "/", + web_sys::RequestInit::new().method("POST").body(Some(&val)), + )?; + Ok(( + JsFuture::from(req.array_buffer()?) + .await? + .dyn_into::() + .map(|x| Uint8Array::new(&x))?, + req, + )) +} + pub fn entries_of_object(obj: &Object) -> Vec> { - js_sys::Object::entries(obj) + Object::entries(obj) .to_vec() .iter() .filter_map(|val| { @@ -157,124 +131,10 @@ pub fn entries_of_object(obj: &Object) -> Vec> { pub fn define_property_obj(value: JsValue, writable: bool) -> Result { let entries: Array = [ - Array::of2(&jval!("value"), &value), - Array::of2(&jval!("writable"), &jval!(writable)), + Array::of2(&"value".into(), &value), + Array::of2(&"writable".into(), &writable.into()), ] .iter() .collect::(); Object::from_entries(&entries) } - -pub fn is_redirect(code: u16) -> bool { - [301, 302, 303, 307, 308].contains(&code) -} - -pub fn is_null_body(code: u16) -> bool { - [101, 204, 205, 304].contains(&code) -} - -pub fn get_is_secure(url: &Uri) -> Result { - let url_scheme_str = url.scheme_str().replace_err("URL must have a scheme")?; - match url_scheme_str { - "https" | "wss" => Ok(true), - _ => Ok(false), - } -} - -pub fn get_url_port(url: &Uri) -> Result { - if let Some(port) = url.port() { - Ok(port.as_u16()) - } else if get_is_secure(url)? { - Ok(443) - } else { - Ok(80) - } -} - -pub async fn make_mux( - url: &str, -) -> Result< - ( - ClientMux, - impl Future> + Send, - ), - WispError, -> { - let (wtx, wrx) = - WebSocketWrapper::connect(url, vec![]).map_err(|_| WispError::WsImplSocketClosed)?; - wtx.wait_for_open().await; - Ok( - ClientMux::create(wrx, wtx, Some(&[Box::new(UdpProtocolExtensionBuilder())])) - .await? - .with_no_required_extensions(), - ) -} - -pub fn spawn_mux_fut( - mux: Arc>, - fut: impl Future> + Send + 'static, - url: String, -) { - wasm_bindgen_futures::spawn_local(async move { - debug!("epoxy: mux future started"); - if let Err(e) = fut.await { - log!("epoxy: error in mux future, restarting: {:?}", e); - while let Err(e) = replace_mux(mux.clone(), &url).await { - log!("epoxy: failed to restart mux future: {:?}", e); - wasmtimer::tokio::sleep(std::time::Duration::from_millis(500)).await; - } - } - debug!("epoxy: mux future exited"); - }); -} - -pub async fn replace_mux(mux: Arc>, url: &str) -> Result<(), WispError> { - let (mux_replace, fut) = make_mux(url).await?; - let mut mux_write = mux.write().await; - let _ = mux_write.close().await; - *mux_write = mux_replace; - drop(mux_write); - spawn_mux_fut(mux, fut, url.into()); - Ok(()) -} - -pub async fn jval_to_u8_array(val: JsValue) -> Result { - JsFuture::from( - web_sys::Request::new_with_str_and_init( - "/", - web_sys::RequestInit::new().method("POST").body(Some(&val)), - )? - .array_buffer()?, - ) - .await? - .dyn_into::() - .map(|x| Uint8Array::new(&x)) -} - -pub async fn jval_to_u8_array_req(val: JsValue) -> Result<(Uint8Array, web_sys::Request), JsValue> { - let req = web_sys::Request::new_with_str_and_init( - "/", - web_sys::RequestInit::new().method("POST").body(Some(&val)), - )?; - Ok(( - JsFuture::from(req.array_buffer()?) - .await? - .dyn_into::() - .map(|x| Uint8Array::new(&x))?, - req, - )) -} - -pub fn object_to_trustanchor(obj: JsValue) -> Result, JsValue> { - let subject: Uint8Array = Reflect::get(&obj, &jval!("subject"))?.dyn_into()?; - let pub_key_info: Uint8Array = - Reflect::get(&obj, &jval!("subject_public_key_info"))?.dyn_into()?; - let name_constraints: Option = Reflect::get(&obj, &jval!("name_constraints")) - .and_then(|x| x.dyn_into()) - .ok(); - Ok(TrustAnchor { - subject: Der::from(subject.to_vec()), - subject_public_key_info: Der::from(pub_key_info.to_vec()), - name_constraints: name_constraints.map(|x| Der::from(x.to_vec())), - }) -} diff --git a/client/src/websocket.rs b/client/src/websocket.rs index 414e53e..677a580 100644 --- a/client/src/websocket.rs +++ b/client/src/websocket.rs @@ -1,206 +1,217 @@ -use crate::*; +use std::{str::from_utf8, sync::Arc}; -use base64::{engine::general_purpose::STANDARD, Engine}; +use base64::{prelude::BASE64_STANDARD, Engine}; +use bytes::Bytes; use fastwebsockets::{ CloseCode, FragmentCollectorRead, Frame, OpCode, Payload, Role, WebSocket, WebSocketWrite, }; use futures_util::lock::Mutex; -use http_body_util::Full; -use hyper::{ - header::{CONNECTION, UPGRADE}, - upgrade::Upgraded, - StatusCode, +use getrandom::getrandom; +use http::{ + header::{ + CONNECTION, HOST, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, SEC_WEBSOCKET_VERSION, UPGRADE, + }, + Method, Request, Response, StatusCode, Uri, }; -use std::str::from_utf8; +use hyper::{ + body::Incoming, + upgrade::{self, Upgraded}, +}; +use js_sys::{ArrayBuffer, Function, Uint8Array}; use tokio::io::WriteHalf; +use wasm_bindgen::{prelude::*, JsError, JsValue}; +use wasm_bindgen_futures::spawn_local; -#[wasm_bindgen(inspectable)] -pub struct EpxWebSocket { - tx: Arc>>>>, - onerror: Function, - #[wasm_bindgen(readonly, getter_with_clone)] - pub url: String, - #[wasm_bindgen(readonly, getter_with_clone)] - pub protocols: Vec, - #[wasm_bindgen(readonly, getter_with_clone)] - pub origin: String, -} +use crate::{tokioio::TokioIo, EpoxyClient, EpoxyError, EpoxyHandlers, HttpBody}; #[wasm_bindgen] -impl EpxWebSocket { - #[wasm_bindgen(constructor)] - pub fn new() -> Result { - Err(jerr!("Use EpoxyClient.connect_ws() instead.")) - } +pub struct EpoxyWebSocket { + tx: Arc>>>>, + onerror: Function, +} - // shut up - #[allow(clippy::too_many_arguments)] - pub async fn connect( - tcp: &EpoxyClient, - onopen: Function, - onclose: Function, - onerror: Function, - onmessage: Function, +impl EpoxyWebSocket { + pub(crate) async fn connect( + client: &EpoxyClient, + handlers: EpoxyHandlers, url: String, protocols: Vec, - origin: String, - ) -> Result { - let onerr = onerror.clone(); - let ret: Result = async move { - let url = Uri::try_from(url).replace_err("Failed to parse URL")?; - let host = url.host().replace_err("URL must have a host")?; + ) -> Result { + let EpoxyHandlers { + onopen, + onclose, + onerror, + onmessage, + } = handlers; + let onerror_cloned = onerror.clone(); + let ret: Result = async move { + let url: Uri = url.try_into()?; + let host = url.host().ok_or(EpoxyError::NoUrlHost)?; - let mut rand: [u8; 16] = [0; 16]; - getrandom::getrandom(&mut rand)?; - let key = STANDARD.encode(rand); + let mut rand = [0u8; 16]; + getrandom(&mut rand)?; + let key = BASE64_STANDARD.encode(rand); - let mut builder = Request::builder() - .method("GET") + let mut request = Request::builder() + .method(Method::GET) .uri(url.clone()) - .header("Host", host) - .header("Origin", origin.clone()) - .header(UPGRADE, "websocket") + .header(HOST, host) .header(CONNECTION, "upgrade") - .header("Sec-WebSocket-Key", key) - .header("Sec-WebSocket-Version", "13"); + .header(UPGRADE, "websocket") + .header(SEC_WEBSOCKET_KEY, key) + .header(SEC_WEBSOCKET_VERSION, "13"); if !protocols.is_empty() { - builder = builder.header("Sec-WebSocket-Protocol", protocols.join(", ")); + request = request.header(SEC_WEBSOCKET_PROTOCOL, protocols.join(",")); } - let req = builder.body(Full::::new(Bytes::new()))?; + let request = request.body(HttpBody::new(Bytes::new()))?; - let mut response = tcp.hyper_client.request(req).await?; + let mut response = client.client.request(request).await?; verify(&response)?; - let ws = WebSocket::after_handshake( - TokioIo::new(hyper::upgrade::on(&mut response).await?), + let websocket = WebSocket::after_handshake( + TokioIo::new(upgrade::on(&mut response).await?), Role::Client, ); - let (rx, tx) = ws.split(tokio::io::split); + let (rx, tx) = websocket.split(tokio::io::split); let mut rx = FragmentCollectorRead::new(rx); let tx = Arc::new(Mutex::new(tx)); - let tx_cloned = tx.clone(); - wasm_bindgen_futures::spawn_local(async move { - while let Ok(frame) = rx - .read_frame(&mut |arg| async { tx_cloned.lock().await.write_frame(arg).await }) - .await - { - match frame.opcode { - OpCode::Text => { - if let Ok(str) = from_utf8(&frame.payload) { - let _ = onmessage.call1(&JsValue::null(), &jval!(str)); + let read_tx = tx.clone(); + let onerror_cloned = onerror.clone(); + + spawn_local(async move { + loop { + match rx + .read_frame(&mut |arg| async { + read_tx.lock().await.write_frame(arg).await + }) + .await + { + Ok(frame) => match frame.opcode { + OpCode::Text => { + if let Ok(str) = from_utf8(&frame.payload) { + let _ = onmessage.call1(&JsValue::null(), &str.into()); + } } - } - OpCode::Binary => { - let _ = onmessage.call1( - &JsValue::null(), - &jval!(Uint8Array::from(frame.payload.to_vec().as_slice())), - ); - } - OpCode::Close => { - let _ = onclose.call0(&JsValue::null()); + OpCode::Binary => { + let _ = onmessage.call1( + &JsValue::null(), + &Uint8Array::from(frame.payload.to_vec().as_slice()).into(), + ); + } + OpCode::Close => { + let _ = onclose.call0(&JsValue::null()); + break; + } + // ping/pong/continue + _ => {} + }, + Err(err) => { + let _ = onerror.call1(&JsValue::null(), &JsError::from(err).into()); break; } - // ping/pong/continue - _ => {} } } + let _ = onclose.call0(&JsValue::null()); }); - onopen - .call0(&Object::default()) - .replace_err("Failed to call onopen")?; + let _ = onopen.call0(&JsValue::null()); Ok(Self { tx, - onerror, - origin, - protocols, - url: url.to_string(), + onerror: onerror_cloned, }) } .await; - if let Err(ret) = ret { - let _ = onerr.call1(&JsValue::null(), &jval!(ret.clone())); - Err(ret) - } else { - ret + + match ret { + Ok(ok) => Ok(ok), + Err(err) => { + let _ = onerror_cloned.call1(&JsValue::null(), &err.to_string().into()); + Err(err) + } } } - #[wasm_bindgen] - pub async fn send_text(&self, payload: String) -> Result<(), JsError> { - let onerr = self.onerror.clone(); + pub async fn send(&self, payload: JsValue) -> Result<(), EpoxyError> { + let ret = if let Some(str) = payload.as_string() { + self.tx + .lock() + .await + .write_frame(Frame::text(Payload::Owned(str.as_bytes().to_vec()))) + .await + .map_err(EpoxyError::from) + } else if let Ok(binary) = payload.dyn_into::() { + self.tx + .lock() + .await + .write_frame(Frame::binary(Payload::Owned( + Uint8Array::new(&binary).to_vec(), + ))) + .await + .map_err(EpoxyError::from) + } else { + Err(EpoxyError::WsInvalidPayload) + }; + + match ret { + Ok(ok) => Ok(ok), + Err(err) => { + let _ = self + .onerror + .call1(&JsValue::null(), &err.to_string().into()); + Err(err) + } + } + } + + pub async fn close(&self) -> Result<(), EpoxyError> { let ret = self .tx - .lock() - .await - .write_frame(Frame::text(Payload::Owned(payload.as_bytes().to_vec()))) - .await; - if let Err(ret) = ret { - let _ = onerr.call1(&JsValue::null(), &jval!(ret.to_string())); - Err(ret.into()) - } else { - Ok(ret?) - } - } - - #[wasm_bindgen] - pub async fn send_binary(&self, payload: Uint8Array) -> Result<(), JsError> { - let onerr = self.onerror.clone(); - let ret = self - .tx - .lock() - .await - .write_frame(Frame::binary(Payload::Owned(payload.to_vec()))) - .await; - if let Err(ret) = ret { - let _ = onerr.call1(&JsValue::null(), &jval!(ret.to_string())); - Err(ret.into()) - } else { - Ok(ret?) - } - } - - #[wasm_bindgen] - pub async fn close(&self) -> Result<(), JsError> { - self.tx .lock() .await .write_frame(Frame::close(CloseCode::Normal.into(), b"")) - .await?; - Ok(()) + .await; + match ret { + Ok(ok) => Ok(ok), + Err(err) => { + let _ = self + .onerror + .call1(&JsValue::null(), &err.to_string().into()); + Err(err.into()) + } + } } } // https://github.com/snapview/tungstenite-rs/blob/314feea3055a93e585882fb769854a912a7e6dae/src/handshake/client.rs#L189 -fn verify(response: &Response) -> Result<(), JsError> { +fn verify(response: &Response) -> Result<(), EpoxyError> { if response.status() != StatusCode::SWITCHING_PROTOCOLS { - return Err(jerr!("epoxy ws connect: Invalid status code")); + return Err(EpoxyError::WsInvalidStatusCode); } let headers = response.headers(); if !headers - .get("Upgrade") + .get(UPGRADE) .and_then(|h| h.to_str().ok()) .map(|h| h.eq_ignore_ascii_case("websocket")) .unwrap_or(false) { - return Err(jerr!("epoxy ws connect: Invalid upgrade header")); + return Err(EpoxyError::WsInvalidUpgradeHeader); } if !headers - .get("Connection") + .get(CONNECTION) .and_then(|h| h.to_str().ok()) .map(|h| h.eq_ignore_ascii_case("Upgrade")) .unwrap_or(false) { - return Err(jerr!("epoxy ws connect: Invalid upgrade header")); + return Err(EpoxyError::WsInvalidConnectionHeader); } Ok(()) diff --git a/client/src/wrappers.rs b/client/src/ws_wrapper.rs similarity index 56% rename from client/src/wrappers.rs rename to client/src/ws_wrapper.rs index 02361ff..21ec475 100644 --- a/client/src/wrappers.rs +++ b/client/src/ws_wrapper.rs @@ -1,142 +1,23 @@ -use crate::*; -use std::{ - ops::Deref, pin::Pin, sync::atomic::{AtomicBool, Ordering}, task::{Context, Poll} +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, }; +use async_trait::async_trait; use bytes::BytesMut; use event_listener::Event; -use futures_util::{FutureExt, Stream}; -use hyper::body::Body; -use js_sys::ArrayBuffer; -use pin_project_lite::pin_project; +use flume::Receiver; +use futures_util::FutureExt; +use js_sys::{Array, ArrayBuffer, Uint8Array}; use send_wrapper::SendWrapper; -use std::future::Future; -use tokio::sync::mpsc; +use wasm_bindgen::{closure::Closure, JsCast}; use web_sys::{BinaryType, MessageEvent, WebSocket}; use wisp_mux::{ ws::{Frame, LockedWebSocketWrite, WebSocketRead, WebSocketWrite}, WispError, }; -pin_project! { - pub struct IncomingBody { - #[pin] - incoming: hyper::body::Incoming, - } -} - -impl IncomingBody { - pub fn new(incoming: hyper::body::Incoming) -> IncomingBody { - IncomingBody { incoming } - } -} - -impl Stream for IncomingBody { - type Item = std::io::Result; - fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - let this = self.project(); - let ret = this.incoming.poll_frame(cx); - match ret { - Poll::Ready(item) => Poll::>::Ready(match item { - Some(frame) => frame - .map(|x| { - x.into_data() - .map_err(|_| std::io::Error::other("not data frame")) - }) - .ok(), - None => None, - }), - Poll::Pending => Poll::>::Pending, - } - } -} - -#[derive(Clone)] -pub struct ServiceWrapper(pub Arc>, pub String); - -impl tower_service::Service for ServiceWrapper { - type Response = TokioIo; - type Error = WispError; - type Future = impl Future>; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: hyper::Uri) -> Self::Future { - let mux = self.0.clone(); - let mux_url = self.1.clone(); - async move { - let stream = mux - .write() - .await - .client_new_stream( - StreamType::Tcp, - req.host().ok_or(WispError::UriHasNoHost)?.to_string(), - req.port().ok_or(WispError::UriHasNoPort)?.into(), - ) - .await; - if stream - .as_ref() - .is_err_and(|e| matches!(e, WispError::WsImplSocketClosed)) - { - utils::replace_mux(mux, &mux_url).await?; - } - Ok(TokioIo::new(stream?.into_io().into_asyncrw())) - } - } -} - -#[derive(Clone)] -pub struct TlsWispService { - pub service: ServiceWrapper, - pub rustls_config: Arc, -} - -impl tower_service::Service for TlsWispService { - type Response = TokioIo; - type Error = WispError; - type Future = Pin>>>; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } - - fn call(&mut self, req: http::Uri) -> Self::Future { - let mut service = self.service.clone(); - let rustls_config = self.rustls_config.clone(); - Box::pin(async move { - let uri_host = req - .host() - .ok_or(WispError::UriHasNoHost)? - .to_string() - .clone(); - let uri_parsed = Uri::builder() - .authority(format!( - "{}:{}", - uri_host, - utils::get_url_port(&req).map_err(|_| WispError::UriHasNoPort)? - )) - .build() - .map_err(|x| WispError::Other(Box::new(x)))?; - let stream = service.call(uri_parsed).await?.into_inner(); - if utils::get_is_secure(&req).map_err(|_| WispError::InvalidUri)? { - let connector = TlsConnector::from(rustls_config); - Ok(TokioIo::new(Either::Left( - connector - .connect( - uri_host.try_into().map_err(|_| WispError::InvalidUri)?, - stream, - ) - .await - .map_err(|x| WispError::Other(Box::new(x)))?, - ))) - } else { - Ok(TokioIo::new(Either::Right(stream))) - } - }) - } -} +use crate::EpoxyError; #[derive(Debug)] pub enum WebSocketError { @@ -189,12 +70,12 @@ pub struct WebSocketWrapper { } pub struct WebSocketReader { - read_rx: mpsc::UnboundedReceiver, + read_rx: Receiver, closed: Arc, close_event: Arc, } -#[async_trait::async_trait] +#[async_trait] impl WebSocketRead for WebSocketReader { async fn wisp_read_frame(&mut self, _: &LockedWebSocketWrite) -> Result { use WebSocketMessage::*; @@ -202,11 +83,11 @@ impl WebSocketRead for WebSocketReader { return Err(WispError::WsImplSocketClosed); } let res = futures_util::select! { - data = self.read_rx.recv().fuse() => data, + data = self.read_rx.recv_async() => data.ok(), _ = self.close_event.listen().fuse() => Some(Closed), }; match res.ok_or(WispError::WsImplSocketClosed)? { - Message(bin) => Ok(Frame::binary(BytesMut::from(bin.deref()))), + Message(bin) => Ok(Frame::binary(BytesMut::from(bin.as_slice()))), Error => Err(WebSocketError::Unknown.into()), Closed => Err(WispError::WsImplSocketClosed), } @@ -214,8 +95,8 @@ impl WebSocketRead for WebSocketReader { } impl WebSocketWrapper { - pub fn connect(url: &str, protocols: Vec) -> Result<(Self, WebSocketReader), JsValue> { - let (read_tx, read_rx) = mpsc::unbounded_channel(); + pub fn connect(url: &str, protocols: &[String]) -> Result<(Self, WebSocketReader), EpoxyError> { + let (read_tx, read_rx) = flume::unbounded(); let closed = Arc::new(AtomicBool::new(false)); let open_event = Arc::new(Event::new()); @@ -261,13 +142,13 @@ impl WebSocketWrapper { &protocols .iter() .fold(Array::new(), |acc, x| { - acc.push(&jval!(x)); + acc.push(&x.into()); acc }) .into(), ) } - .replace_err("Failed to make websocket")?; + .map_err(|_| EpoxyError::WebSocketConnectFailed)?; ws.set_binary_type(BinaryType::Arraybuffer); ws.set_onmessage(Some(onmessage.as_ref().unchecked_ref())); ws.set_onopen(Some(onopen.as_ref().unchecked_ref())); @@ -294,15 +175,18 @@ impl WebSocketWrapper { )) } - pub async fn wait_for_open(&self) { + pub async fn wait_for_open(&self) -> bool { + if self.closed.load(Ordering::Acquire) { + return false; + } futures_util::select! { - _ = self.open_event.listen().fuse() => (), - _ = self.error_event.listen().fuse() => (), + _ = self.open_event.listen().fuse() => true, + _ = self.error_event.listen().fuse() => false, } } } -#[async_trait::async_trait] +#[async_trait] impl WebSocketWrite for WebSocketWrapper { async fn wisp_write_frame(&mut self, frame: Frame) -> Result<(), WispError> { use wisp_mux::ws::OpCode::*; diff --git a/client/test.sh b/client/test.sh deleted file mode 100644 index 5e144af..0000000 --- a/client/test.sh +++ /dev/null @@ -1,25 +0,0 @@ -#!/usr/bin/env bash -# https://aweirdimagination.net/2020/06/28/kill-child-jobs-on-script-exit/ -cleanup() { - pkill -P $$ -} - -for sig in INT QUIT HUP TERM; do - trap " - cleanup - trap - $sig EXIT - kill -s $sig "'"$$"' "$sig" -done -trap cleanup EXIT - -set -euo pipefail -shopt -s inherit_errexit - -(cd ..; cargo b --bin epoxy-server) -../target/debug/epoxy-server & -server_pid=$! -sleep 1 -echo "server_pid: $server_pid" - -GECKODRIVER=$(which geckodriver) cargo test --target wasm32-unknown-unknown -CHROMEDRIVER=$(which chromedriver) cargo test --target wasm32-unknown-unknown diff --git a/client/tests/fetch.rs b/client/tests/fetch.rs deleted file mode 100644 index c7307fb..0000000 --- a/client/tests/fetch.rs +++ /dev/null @@ -1,300 +0,0 @@ -use default_env::default_env; -use epoxy_client::EpoxyClient; -use js_sys::{Array, JsString, Object, Reflect, Uint8Array, JSON}; -use rustls_pki_types::TrustAnchor; -use tokio::sync::OnceCell; -use wasm_bindgen::JsValue; -use wasm_bindgen_futures::JsFuture; -use wasm_bindgen_test::*; -use web_sys::{FormData, Headers, Response, UrlSearchParams}; - -wasm_bindgen_test_configure!(run_in_dedicated_worker); - -static USER_AGENT: &str = "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36"; -static EPOXY_CLIENT: OnceCell = OnceCell::const_new(); - -pub fn trustanchor_to_object(cert: &TrustAnchor) -> Result { - let val = Object::new(); - Reflect::set( - &val, - &JsValue::from("subject"), - &Uint8Array::from(cert.subject.as_ref()), - )?; - Reflect::set( - &val, - &JsValue::from("subject_public_key_info"), - &Uint8Array::from(cert.subject_public_key_info.as_ref()), - )?; - Reflect::set( - &val, - &JsValue::from("name_constraints"), - &JsValue::from( - cert.name_constraints - .as_ref() - .map(|x| Uint8Array::from(x.as_ref())), - ), - )?; - Ok(val.into()) -} - -async fn get_client_w_ua(useragent: &str, redirect_limit: usize) -> EpoxyClient { - EpoxyClient::new( - "ws://localhost:4000".into(), - useragent.into(), - redirect_limit, - webpki_roots::TLS_SERVER_ROOTS - .iter() - .map(trustanchor_to_object) - .collect::>() - .expect("Failed to create certs"), - ) - .await - .ok() - .expect("Failed to create client") -} - -macro_rules! fetch { - ($url:expr, $opts:expr) => { - EPOXY_CLIENT - .get_or_init(|| get_client_w_ua(USER_AGENT, 10)) - .await - .fetch($url, $opts) - .await - .ok() - .expect("Failed to fetch") - }; -} -macro_rules! httpbin { - ($url:literal) => { - concat!(default_env!("HTTPBIN_URL", "https://httpbin.org/"), $url) - }; -} - -async fn get_body_json(resp: &Response) -> JsValue { - JsFuture::from(resp.json().unwrap()).await.unwrap() -} -async fn get_body_text(resp: &Response) -> JsValue { - JsFuture::from(resp.text().unwrap()).await.unwrap() -} - -fn get_header(body: &JsValue, header: &str) -> Result { - Reflect::get(body, &JsValue::from("headers")) - .and_then(|x| Reflect::get(&x, &JsValue::from(header))) -} -fn get_resp_body(body: &JsValue) -> Result { - Reflect::get(body, &JsValue::from("data")) -} -fn get_resp_form(body: &JsValue) -> Result { - Reflect::get(body, &JsValue::from("form")) -} - -fn check_resp(resp: &Response, url: &str, status: u16, status_text: &str) { - assert_eq!(resp.url(), url); - assert_eq!(resp.status(), status); - assert_eq!(resp.status_text(), status_text); -} - -#[wasm_bindgen_test] -async fn get() { - let url = httpbin!("get"); - let resp = fetch!(url.into(), Object::new()); - - check_resp(&resp, url, 200, "OK"); - - let body: Object = get_body_json(&resp).await.into(); - assert_eq!( - get_header(&body, "User-Agent"), - Ok(JsValue::from(USER_AGENT)) - ); -} - -#[wasm_bindgen_test] -async fn gzip() { - let url = httpbin!("gzip"); - let resp = fetch!(url.into(), Object::new()); - - check_resp(&resp, url, 200, "OK"); - - let body: Object = get_body_json(&resp).await.into(); - assert_eq!( - get_header(&body, "Accept-Encoding"), - Ok(JsValue::from("gzip, br")) - ); -} - -#[wasm_bindgen_test] -async fn brotli() { - let url = httpbin!("brotli"); - let resp = fetch!(url.into(), Object::new()); - - check_resp(&resp, url, 200, "OK"); - - let body: Object = get_body_json(&resp).await.into(); - assert_eq!( - get_header(&body, "Accept-Encoding"), - Ok(JsValue::from("gzip, br")) - ); -} - -#[wasm_bindgen_test] -async fn redirect() { - let url = httpbin!("redirect/2"); - let resp = fetch!(url.into(), Object::new()); - - check_resp(&resp, httpbin!("get"), 200, "OK"); - - get_body_json(&resp).await; -} - -#[wasm_bindgen_test] -async fn redirect_limit() { - // new client created due to redirect limit difference - let client = get_client_w_ua(USER_AGENT, 2).await; - let url = httpbin!("redirect/3"); - let resp = client - .fetch(url.into(), Object::new()) - .await - .ok() - .expect("Failed to fetch"); - - check_resp(&resp, httpbin!("relative-redirect/1"), 302, "Found"); - - assert_eq!(get_body_text(&resp).await, JsValue::from("")); -} - -#[wasm_bindgen_test] -async fn redirect_manual() { - let url = httpbin!("redirect/2"); - - let obj = Object::new(); - Reflect::set(&obj, &JsValue::from("redirect"), &JsValue::from("manual")).unwrap(); - - let resp = fetch!(url.into(), obj); - - check_resp(&resp, url, 302, "Found"); - - get_body_text(&resp).await; -} - -#[wasm_bindgen_test] -async fn post_string() { - let url = httpbin!("post"); - let obj = Object::new(); - Reflect::set(&obj, &JsValue::from("method"), &JsValue::from("POST")).unwrap(); - Reflect::set(&obj, &JsValue::from("body"), &JsValue::from("epoxy body")).unwrap(); - let resp = fetch!(url.into(), obj); - - check_resp(&resp, url, 200, "OK"); - - let body: Object = get_body_json(&resp).await.into(); - assert_eq!(get_resp_body(&body), Ok(JsValue::from("epoxy body"))); -} - -#[wasm_bindgen_test] -async fn post_arraybuffer() { - let url = httpbin!("post"); - - let obj = Object::new(); - Reflect::set(&obj, &JsValue::from("method"), &JsValue::from("POST")).unwrap(); - let req_body = b"epoxy body"; - let u8array = Uint8Array::new_with_length(req_body.len().try_into().unwrap()); - u8array.copy_from(req_body); - Reflect::set(&obj, &JsValue::from("body"), &u8array).unwrap(); - - let resp = fetch!(url.into(), obj); - - check_resp(&resp, url, 200, "OK"); - - let body: Object = get_body_json(&resp).await.into(); - assert_eq!(get_resp_body(&body), Ok(JsValue::from("epoxy body"))); -} - -#[wasm_bindgen_test] -async fn post_formdata() { - let url = httpbin!("post"); - - let obj = Object::new(); - Reflect::set(&obj, &JsValue::from("method"), &JsValue::from("POST")).unwrap(); - let req_body = FormData::new().unwrap(); - req_body.set_with_str("a", "b").unwrap(); - Reflect::set(&obj, &JsValue::from("body"), &req_body).unwrap(); - - let resp = fetch!(url.into(), obj); - - check_resp(&resp, url, 200, "OK"); - - let body: Object = get_body_json(&resp).await.into(); - assert_eq!( - get_resp_form(&body).and_then(|x| JSON::stringify(&x)), - Ok(JsString::from(r#"{"a":"b"}"#)) - ); - assert!(JsString::from(get_header(&body, "Content-Type").unwrap()) - .includes("multipart/form-data", 0)); -} - -#[wasm_bindgen_test] -async fn post_urlsearchparams() { - let url = httpbin!("post"); - - let obj = Object::new(); - Reflect::set(&obj, &JsValue::from("method"), &JsValue::from("POST")).unwrap(); - let req_body = UrlSearchParams::new_with_str("a=b").unwrap(); - Reflect::set(&obj, &JsValue::from("body"), &req_body).unwrap(); - - let resp = fetch!(url.into(), obj); - - check_resp(&resp, url, 200, "OK"); - - let body: Object = get_body_json(&resp).await.into(); - assert_eq!( - get_resp_form(&body).and_then(|x| JSON::stringify(&x)), - Ok(JsString::from(r#"{"a":"b"}"#)) - ); - assert!(JsString::from(get_header(&body, "Content-Type").unwrap()) - .includes("application/x-www-form-urlencoded", 0)); -} - -#[wasm_bindgen_test] -async fn headers_obj() { - let url = httpbin!("get"); - - let obj = Object::new(); - let headers = Object::new(); - Reflect::set( - &headers, - &JsValue::from("x-header-one"), - &JsValue::from("value"), - ) - .unwrap(); - Reflect::set(&obj, &JsValue::from("headers"), &headers).unwrap(); - - let resp = fetch!(url.into(), obj); - - check_resp(&resp, url, 200, "OK"); - - let body: Object = get_body_json(&resp).await.into(); - assert_eq!( - get_header(&body, "X-Header-One"), - Ok(JsValue::from("value")) - ); -} - -#[wasm_bindgen_test] -async fn headers_headers() { - let url = httpbin!("get"); - - let obj = Object::new(); - let headers = Headers::new().unwrap(); - headers.set("x-header-one", "value").unwrap(); - Reflect::set(&obj, &JsValue::from("headers"), &headers).unwrap(); - - let resp = fetch!(url.into(), obj); - - check_resp(&resp, url, 200, "OK"); - - let body: Object = get_body_json(&resp).await.into(); - assert_eq!( - get_header(&body, "X-Header-One"), - Ok(JsValue::from("value")) - ); -}