From ae0c728b24b30f06a7aa872b5d10ad69ad9fa94e Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 7 Sep 2024 19:55:21 -0400 Subject: [PATCH 01/40] feat: use hyper v1 --- Cargo.lock | 666 ++++++++++++++++++++++++++++++++++++++++++++++------- Cargo.toml | 12 +- 2 files changed, 590 insertions(+), 88 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 449893c6..364b02c3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,7 +69,7 @@ checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -80,15 +80,48 @@ checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "aws-lc-rs" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f95446d919226d587817a7d21379e6eb099b97b45110a7f272a444ca5c54070" +dependencies = [ + "aws-lc-sys", + "mirai-annotations", + "paste", + "zeroize", +] + +[[package]] +name = "aws-lc-sys" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5055edc4a9a1b2a917a818258cdfb86a535947feebd9981adc99667a062c6f85" +dependencies = [ + "bindgen", + "cc", + "cmake", + "dunce", + "fs_extra", + "libc", + "paste", +] + [[package]] name = "backtrace" version = "0.3.65" @@ -116,6 +149,35 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bindgen" +version = "0.69.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +dependencies = [ + "bitflags 2.4.0", + "cexpr", + "clang-sys", + "itertools", + "lazy_static", + "lazycell", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.32", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -169,9 +231,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.1.0" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cast" @@ -181,9 +243,23 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.79" +version = "1.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +dependencies = [ + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cexpr" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] [[package]] name = "cfg-if" @@ -203,7 +279,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -233,6 +309,17 @@ dependencies = [ "half", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "2.34.0" @@ -269,6 +356,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" +[[package]] +name = "cmake" +version = "0.1.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +dependencies = [ + "cc", +] + [[package]] name = "core-foundation" version = "0.9.3" @@ -400,6 +496,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + [[package]] name = "either" version = "1.6.1" @@ -414,23 +516,12 @@ checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" [[package]] name = "errno" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b30f669a7961ef1631673d2766cc92f52d64f7ef354d4fe0ddfd30ed52f0f4f" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ - "cc", "libc", + "windows-sys 0.52.0", ] [[package]] @@ -455,6 +546,12 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fs_extra" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" + [[package]] name = "futures" version = "0.3.30" @@ -511,7 +608,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -553,12 +650,48 @@ dependencies = [ "typenum", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "gimli" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "h2" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "half" version = "1.8.2" @@ -609,6 +742,15 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "http" version = "0.2.11" @@ -620,6 +762,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-auth-basic" version = "0.3.3" @@ -631,12 +784,24 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.4" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body", "pin-project-lite", ] @@ -653,23 +818,25 @@ dependencies = [ "flate2", "futures", "handlebars", - "http", + "http 0.2.11", "http-auth-basic", + "http-body-util", "humansize", "hyper", "hyper-rustls", + "hyper-util", "lazy_static", "local-ip-address", "mime_guess", "percent-encoding", - "rustls", - "rustls-pemfile", + "rustls 0.20.6", + "rustls-pemfile 1.0.4", "serde", "serde_json", "structopt", "termcolor", "tokio", - "tokio-rustls", + "tokio-rustls 0.23.4", "toml", ] @@ -681,9 +848,9 @@ checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.2" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humansize" @@ -696,43 +863,65 @@ dependencies = [ [[package]] name = "hyper" -version = "0.14.27" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffb1cfd654a8219eaef89881fdb3bb3b1cdc5fa75ded05d6933b2b382e395468" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", - "futures-core", "futures-util", - "http", + "h2", + "http 1.1.0", "http-body", "httparse", "httpdate", "itoa", "pin-project-lite", - "socket2", + "smallvec", "tokio", - "tower-service", - "tracing", "want", ] [[package]] name = "hyper-rustls" -version = "0.23.0" +version = "0.27.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ - "http", + "futures-util", + "http 1.1.0", "hyper", + "hyper-util", "log", - "rustls", + "rustls 0.23.12", "rustls-native-certs", + "rustls-pki-types", "tokio", - "tokio-rustls", + "tokio-rustls 0.26.0", + "tower-service", "webpki-roots", ] +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body", + "hyper", + "pin-project-lite", + "socket2 0.5.7", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.57" @@ -792,6 +981,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "jobserver" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.59" @@ -807,11 +1005,27 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "libc" -version = "0.2.147" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libloading" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" +dependencies = [ + "cfg-if", + "windows-targets 0.52.6", +] [[package]] name = "libm" @@ -821,9 +1035,9 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "linux-raw-sys" -version = "0.4.5" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "local-ip-address" @@ -893,6 +1107,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.5.1" @@ -922,6 +1142,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "mirai-annotations" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" + [[package]] name = "neli" version = "0.6.4" @@ -947,6 +1173,16 @@ dependencies = [ "syn 1.0.96", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-traits" version = "0.2.14" @@ -1022,6 +1258,12 @@ dependencies = [ "windows-sys 0.34.0", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "percent-encoding" version = "2.3.0" @@ -1071,11 +1313,31 @@ dependencies = [ "sha-1", ] +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.32", +] + [[package]] name = "pin-project-lite" -version = "0.2.8" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" @@ -1111,6 +1373,16 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.32", +] + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1210,12 +1482,27 @@ dependencies = [ "cc", "libc", "once_cell", - "spin", - "untrusted", + "spin 0.5.2", + "untrusted 0.7.1", "web-sys", "winapi", ] +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin 0.9.8", + "untrusted 0.9.0", + "windows-sys 0.52.0", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -1230,15 +1517,15 @@ checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" -version = "0.38.8" +version = "0.38.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19ed4fa021d81c8392ce04db050a3da9a60299050b7ae1cf482d862b54a7218f" +checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" dependencies = [ "bitflags 2.4.0", "errno", "libc", "linux-raw-sys", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -1248,19 +1535,35 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" dependencies = [ "log", - "ring", + "ring 0.16.20", "sct", "webpki", ] +[[package]] +name = "rustls" +version = "0.23.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" +dependencies = [ + "aws-lc-rs", + "log", + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + [[package]] name = "rustls-native-certs" -version = "0.6.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0167bac7a9f490495f3c33013e7722b53cb087ecbe082fb0c6387c96f634ea50" +checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 2.1.3", + "rustls-pki-types", "schannel", "security-framework", ] @@ -1274,6 +1577,34 @@ dependencies = [ "base64 0.21.2", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" + +[[package]] +name = "rustls-webpki" +version = "0.102.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +dependencies = [ + "aws-lc-rs", + "ring 0.17.8", + "rustls-pki-types", + "untrusted 0.9.0", +] + [[package]] name = "ryu" version = "1.0.9" @@ -1311,8 +1642,8 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] @@ -1355,7 +1686,7 @@ checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -1390,6 +1721,12 @@ dependencies = [ "opaque-debug", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1407,9 +1744,9 @@ checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] name = "smallvec" -version = "1.8.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "socket2" @@ -1421,12 +1758,28 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + [[package]] name = "spin" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "structopt" version = "0.3.26" @@ -1451,6 +1804,12 @@ dependencies = [ "syn 1.0.96", ] +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "1.0.96" @@ -1464,9 +1823,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.29" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c324c494eba9d92503e6f1ef2e6df781e78f6a7705a0202d9801b198807d518a" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", @@ -1542,7 +1901,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2", + "socket2 0.4.9", "tokio-macros", "windows-sys 0.48.0", ] @@ -1555,7 +1914,7 @@ checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.29", + "syn 2.0.32", ] [[package]] @@ -1564,11 +1923,35 @@ version = "0.23.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ - "rustls", + "rustls 0.20.6", "tokio", "webpki", ] +[[package]] +name = "tokio-rustls" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" +dependencies = [ + "rustls 0.23.12", + "rustls-pki-types", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + [[package]] name = "toml" version = "0.7.6" @@ -1603,6 +1986,27 @@ dependencies = [ "winnow", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + [[package]] name = "tower-service" version = "0.3.1" @@ -1611,22 +2015,21 @@ checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" [[package]] name = "tracing" -version = "0.1.34" +version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0ecdcb44a79f0fe9844f0c4f33a342cbcbb5117de8001e6ba0dc2351327d09" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ - "cfg-if", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" -version = "0.1.26" +version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f54c8ca710e81886d498c2fd3331b56c93aa248d49de2222ad2742247c60072f" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ - "lazy_static", + "once_cell", ] [[package]] @@ -1680,6 +2083,12 @@ version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "version_check" version = "0.9.4" @@ -1783,17 +2192,29 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" dependencies = [ - "ring", - "untrusted", + "ring 0.16.20", + "untrusted 0.7.1", ] [[package]] name = "webpki-roots" -version = "0.22.3" +version = "0.26.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" dependencies = [ - "webpki", + "rustls-pki-types", +] + +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix", ] [[package]] @@ -1833,7 +2254,7 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", ] [[package]] @@ -1855,7 +2276,16 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets", + "windows-targets 0.48.1", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", ] [[package]] @@ -1864,21 +2294,43 @@ version = "0.48.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" dependencies = [ - "windows_aarch64_gnullvm", + "windows_aarch64_gnullvm 0.48.0", "windows_aarch64_msvc 0.48.0", "windows_i686_gnu 0.48.0", "windows_i686_msvc 0.48.0", "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm", + "windows_x86_64_gnullvm 0.48.0", "windows_x86_64_msvc 0.48.0", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + [[package]] name = "windows_aarch64_msvc" version = "0.34.0" @@ -1891,6 +2343,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + [[package]] name = "windows_i686_gnu" version = "0.34.0" @@ -1903,6 +2361,18 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + [[package]] name = "windows_i686_msvc" version = "0.34.0" @@ -1915,6 +2385,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + [[package]] name = "windows_x86_64_gnu" version = "0.34.0" @@ -1927,12 +2403,24 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + [[package]] name = "windows_x86_64_msvc" version = "0.34.0" @@ -1945,6 +2433,12 @@ version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "winnow" version = "0.4.9" @@ -1953,3 +2447,9 @@ checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" dependencies = [ "memchr", ] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 17ad829c..dbbc54f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,9 +37,11 @@ futures = "0.3.30" flate2 = "1.0.28" http = "0.2.11" http-auth-basic = "0.3.3" +http-body-util = "0.1" handlebars = "4.3.7" -hyper = { version = "0.14.27", features = ["http1", "server", "stream", "tcp"] } -hyper-rustls = { version = "0.23.0", features = ["webpki-roots"] } +hyper = { version = "1" } +hyper-rustls = { version = "0.27", features = ["webpki-roots"] } +hyper-util = { version = "0.1", features = ["full"] } local-ip-address = "0.6.1" mime_guess = "2.0.4" percent-encoding = "2.2.0" @@ -49,7 +51,7 @@ serde = { version = "1.0.192", features = ["derive"] } serde_json = "1.0.108" structopt = { version = "0.3.26", default-features = false } termcolor = "1.1.3" -tokio = { version = "1.29.1", features = [ +tokio = { version = "1", features = [ "fs", "rt-multi-thread", "signal", @@ -61,8 +63,8 @@ humansize = "2.1.3" [dev-dependencies] criterion = { version = "0.5.1", features = ["async_tokio", "html_reports"] } -hyper = { version = "0.14.27", features = ["client"] } -tokio = { version = "1.29.1", features = ["full"] } +hyper = { version = "1", features = ["client"] } +tokio = { version = "1", features = ["full"] } lazy_static = "1.4.0" [profile.release] From 3e62e97240a86a1cb83597e67000e8623f65af46 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sun, 8 Sep 2024 22:39:39 -0300 Subject: [PATCH 02/40] feat: use `hyper::body::Bytes` over `hyper::body::Body` --- src/addon/compression/gzip.rs | 53 ++++++++++---------- src/addon/file_server/http_utils.rs | 8 +-- src/addon/file_server/mod.rs | 20 ++++---- src/addon/logger.rs | 4 +- src/addon/mod.rs | 2 +- src/server/handler/file_server.rs | 9 ++-- src/server/handler/mod.rs | 32 ++++++------ src/server/handler/proxy.rs | 4 +- src/server/mod.rs | 77 +++++++++++++++-------------- 9 files changed, 105 insertions(+), 104 deletions(-) diff --git a/src/addon/compression/gzip.rs b/src/addon/compression/gzip.rs index cb26b075..88f94f99 100644 --- a/src/addon/compression/gzip.rs +++ b/src/addon/compression/gzip.rs @@ -1,9 +1,7 @@ use anyhow::{Error, Result}; use flate2::write::GzEncoder; use http::{HeaderValue, Request, Response}; -use hyper::body::aggregate; -use hyper::body::Buf; -use hyper::Body; +use hyper::body::Bytes; use std::io::Write; use std::sync::Arc; use tokio::sync::Mutex; @@ -18,7 +16,7 @@ const IGNORED_CONTENT_TYPE: [&str; 6] = [ "video", ]; -pub async fn is_encoding_accepted(request: Arc>>) -> Result { +pub async fn is_encoding_accepted(request: Arc>>) -> Result { if let Some(accept_encoding) = request .lock() .await @@ -36,7 +34,7 @@ pub async fn is_encoding_accepted(request: Arc>>) -> Result< Ok(false) } -pub async fn is_compressable_content_type(response: Arc>>) -> Result { +pub async fn is_compressable_content_type(response: Arc>>) -> Result { if let Some(content_type) = response .lock() .await @@ -56,8 +54,8 @@ pub async fn is_compressable_content_type(response: Arc>>) } pub async fn should_compress( - request: Arc>>, - response: Arc>>, + request: Arc>>, + response: Arc>>, ) -> Result { Ok(is_encoding_accepted(request).await? && is_compressable_content_type(Arc::clone(&response)).await?) @@ -73,8 +71,8 @@ pub fn compress(bytes: &[u8]) -> Result> { } pub async fn compress_http_response( - request: Arc>>, - response: Arc>>, + request: Arc>>, + response: Arc>>, ) -> Result<()> { if let Ok(compressable) = should_compress(Arc::clone(&request), Arc::clone(&response)).await { if compressable { @@ -90,11 +88,11 @@ pub async fn compress_http_response( } let body = response.body_mut(); - let mut buffer_cursor = aggregate(body).await.unwrap(); + // let mut buffer_cursor = aggregate(body).await.unwrap(); - while buffer_cursor.has_remaining() { - buffer.push(buffer_cursor.get_u8()); - } + // while buffer_cursor.has_remaining() { + // buffer.push(buffer_cursor.get_u8()); + // } } let compressed = compress(&buffer)?; @@ -108,7 +106,7 @@ pub async fn compress_http_response( response_headers.remove(http::header::CONTENT_LENGTH); - *response.body_mut() = Body::from(compressed); + *response.body_mut() = Bytes::from(compressed); } } @@ -118,7 +116,8 @@ pub async fn compress_http_response( #[cfg(test)] mod tests { use http::response::Builder as HttpResponseBuilder; - use hyper::{Body, Request}; + use hyper::Request; + use hyper::body::Bytes; use std::sync::Arc; use tokio::sync::Mutex; @@ -130,10 +129,10 @@ mod tests { #[allow(dead_code)] fn make_gzip_request_response( accept_encoding_gzip: bool, - ) -> (middleware::Request, middleware::Response) { + ) -> (middleware::Request, middleware::Response) { let file = std::include_bytes!("../../../assets/test_file.hbs"); let request = if accept_encoding_gzip { - let mut req = Request::new(Body::empty()); + let mut req = Request::new(Bytes::empty()); req.headers_mut().append( http::header::ACCEPT_ENCODING, @@ -142,11 +141,11 @@ mod tests { Arc::new(Mutex::new(req)) } else { - Arc::new(Mutex::new(Request::new(Body::empty()))) + Arc::new(Mutex::new(Request::new(Bytes::empty()))) }; let response_builder = HttpResponseBuilder::new().header(http::header::CONTENT_TYPE, "text/html"); - let response_body = Body::from(file.to_vec()); + let response_body = Bytes::from(file.to_vec()); let response = response_builder.body(response_body).unwrap(); let response = Arc::new(Mutex::new(response)); @@ -195,11 +194,11 @@ mod tests { let mut response = response.lock().await; let body = response.body_mut(); - let mut buffer_cursor = aggregate(body).await.unwrap(); + // let mut buffer_cursor = aggregate(body).await.unwrap(); - while buffer_cursor.has_remaining() { - body_buffer.push(buffer_cursor.get_u8()); - } + // while buffer_cursor.has_remaining() { + // body_buffer.push(buffer_cursor.get_u8()); + // } } compress_http_response(request, Arc::clone(&response)) @@ -210,11 +209,11 @@ mod tests { let mut compressed_response = response.lock().await; let compressed_body = compressed_response.body_mut(); - let mut buffer_cursor = aggregate(compressed_body).await.unwrap(); + // let mut buffer_cursor = aggregate(compressed_body).await.unwrap(); - while buffer_cursor.has_remaining() { - compressed_body_buffer.push(buffer_cursor.get_u8()); - } + // while buffer_cursor.has_remaining() { + // compressed_body_buffer.push(buffer_cursor.get_u8()); + // } } assert_eq!(body_buffer.len(), 6364); diff --git a/src/addon/file_server/http_utils.rs b/src/addon/file_server/http_utils.rs index 5c2c96a5..e0118c70 100644 --- a/src/addon/file_server/http_utils.rs +++ b/src/addon/file_server/http_utils.rs @@ -7,7 +7,6 @@ use anyhow::{Context, Result}; use chrono::{DateTime, Local, Utc}; use futures::Stream; use http::response::Builder as HttpResponseBuilder; -use hyper::body::Body; use hyper::body::Bytes; use tokio::io::{AsyncRead, ReadBuf}; @@ -115,7 +114,7 @@ impl ResponseHeaders { pub async fn make_http_file_response( file: File, cache_control_directive: CacheControlDirective, -) -> Result> { +) -> Result> { let headers = ResponseHeaders::new(&file, cache_control_directive)?; let builder = HttpResponseBuilder::new() .header(http::header::CONTENT_LENGTH, headers.content_length) @@ -132,13 +131,14 @@ pub async fn make_http_file_response( Ok(response) } -pub async fn file_bytes_into_http_body(file: File) -> Body { +pub async fn file_bytes_into_http_body(file: File) -> Bytes { let byte_stream = ByteStream { file: file.file, buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]), }; - Body::wrap_stream(byte_stream) + // Bytes::from(byte_stream) + todo!() } pub struct ByteStream { diff --git a/src/addon/file_server/mod.rs b/src/addon/file_server/mod.rs index fdcf6ec2..2ce3e80a 100644 --- a/src/addon/file_server/mod.rs +++ b/src/addon/file_server/mod.rs @@ -14,8 +14,8 @@ pub use scoped_file_system::{Entry, ScopedFileSystem}; use anyhow::{Context, Result}; use handlebars::{handlebars_helper, Handlebars}; use http::response::Builder as HttpResponseBuilder; -use http::{StatusCode, Uri}; -use hyper::{Body, Response}; +use http::{Response, StatusCode, Uri}; +use hyper::body::Bytes; use percent_encoding::{percent_decode_str, utf8_percent_encode}; use std::fs::read_dir; use std::path::{Component, Path, PathBuf}; @@ -130,7 +130,7 @@ impl<'a> FileServer { /// /// If the matched path resolves to a file, attempts to render it if the /// MIME type is supported, otherwise returns the binary (downloadable file) - pub async fn resolve(&self, req_path: String) -> Result> { + pub async fn resolve(&self, req_path: String) -> Result> { let (path, query_params) = FileServer::parse_path(req_path.as_str())?; match self.scoped_file_system.resolve(path).await { @@ -182,9 +182,9 @@ impl<'a> FileServer { } let status = match err.kind() { - std::io::ErrorKind::NotFound => hyper::StatusCode::NOT_FOUND, - std::io::ErrorKind::PermissionDenied => hyper::StatusCode::FORBIDDEN, - _ => hyper::StatusCode::BAD_REQUEST, + std::io::ErrorKind::NotFound => http::StatusCode::NOT_FOUND, + std::io::ErrorKind::PermissionDenied => http::StatusCode::FORBIDDEN, + _ => http::StatusCode::BAD_REQUEST, }; let code = match err.kind() { @@ -193,10 +193,10 @@ impl<'a> FileServer { _ => "400", }; - let response = hyper::Response::builder() + let response = http::Response::builder() .status(status) .header(http::header::CONTENT_TYPE, "text/html") - .body(hyper::Body::from( + .body(Bytes::from( handlebars::Handlebars::new().render_template( include_str!("./template/error.hbs"), &serde_json::json!({"error": err.to_string(), "code": code}), @@ -215,7 +215,7 @@ impl<'a> FileServer { &self, path: PathBuf, query_params: Option, - ) -> Result> { + ) -> Result> { let directory_index = FileServer::index_directory(self.config.root_dir.clone(), path, query_params)?; let html = self @@ -223,7 +223,7 @@ impl<'a> FileServer { .render(EXPLORER_TEMPLATE, &directory_index) .unwrap(); - let body = Body::from(html); + let body = Bytes::from(html); Ok(HttpResponseBuilder::new() .header(http::header::CONTENT_TYPE, "text/html") diff --git a/src/addon/logger.rs b/src/addon/logger.rs index cdddeeac..62865880 100644 --- a/src/addon/logger.rs +++ b/src/addon/logger.rs @@ -2,7 +2,7 @@ use anyhow::Result; use chrono::{DateTime, Utc}; use http::header::USER_AGENT; use http::Method; -use hyper::Body; +use hyper::body::Bytes; use std::io::Write; use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; @@ -19,7 +19,7 @@ impl Logger { Logger { buffer_writer } } - pub async fn log(&mut self, request: Request, response: Response) -> Result<()> { + pub async fn log(&mut self, request: Request, response: Response) -> Result<()> { let mut buffer = self.buffer_writer.buffer(); let request = request.lock().await; let response = response.lock().await; diff --git a/src/addon/mod.rs b/src/addon/mod.rs index 9987880c..7aca7a31 100644 --- a/src/addon/mod.rs +++ b/src/addon/mod.rs @@ -2,4 +2,4 @@ pub mod compression; pub mod cors; pub mod file_server; pub mod logger; -pub mod proxy; +// pub mod proxy; diff --git a/src/server/handler/file_server.rs b/src/server/handler/file_server.rs index a5d7a01c..ac792f26 100644 --- a/src/server/handler/file_server.rs +++ b/src/server/handler/file_server.rs @@ -1,7 +1,8 @@ use async_trait::async_trait; use http::response::Builder as HttpResponseBuilder; -use http::StatusCode; -use hyper::{Body, Method, Request}; +use http::{Request, StatusCode}; +use hyper::Method; +use hyper::body::Bytes; use std::sync::Arc; use tokio::sync::Mutex; @@ -23,7 +24,7 @@ impl FileServerHandler { #[async_trait] impl RequestHandler for FileServerHandler { - async fn handle(&self, req: Arc>>) -> Arc>> { + async fn handle(&self, req: Arc>>) -> Arc>> { let request_lock = req.lock().await; let req_path = request_lock.uri().to_string(); let req_method = request_lock.method(); @@ -37,7 +38,7 @@ impl RequestHandler for FileServerHandler { Arc::new(Mutex::new( HttpResponseBuilder::new() .status(StatusCode::METHOD_NOT_ALLOWED) - .body(Body::empty()) + .body(Bytes::empty()) .expect("Unable to build response"), )) } diff --git a/src/server/handler/mod.rs b/src/server/handler/mod.rs index 88c4c6fb..b4babe29 100644 --- a/src/server/handler/mod.rs +++ b/src/server/handler/mod.rs @@ -1,28 +1,28 @@ mod file_server; -mod proxy; +// mod proxy; use anyhow::Result; use async_trait::async_trait; use http::{Request, Response}; -use hyper::Body; +use hyper::body::Bytes; use std::convert::TryFrom; use std::sync::Arc; use tokio::sync::Mutex; use crate::addon::file_server::FileServer; -use crate::addon::proxy::Proxy; +// use crate::addon::proxy::Proxy; use crate::Config; use super::middleware::Middleware; use self::file_server::FileServerHandler; -use self::proxy::ProxyHandler; +// use self::proxy::ProxyHandler; /// A trait to implement on addons in order to handle incomming requests and /// generate responses. #[async_trait] pub trait RequestHandler { - async fn handle(&self, req: Arc>>) -> Arc>>; + async fn handle(&self, req: Arc>>) -> Arc>>; } #[derive(Clone)] @@ -32,7 +32,7 @@ pub struct HttpHandler { } impl HttpHandler { - pub async fn handle_request(self, request: Request) -> Result> { + pub async fn handle_request(self, request: Request) -> Result> { let handler = Arc::clone(&self.request_handler); let middleware = Arc::clone(&self.middleware); let response = middleware.handle(request, handler).await; @@ -43,17 +43,17 @@ impl HttpHandler { impl From> for HttpHandler { fn from(config: Arc) -> Self { - if let Some(proxy_config) = config.proxy.clone() { - let proxy = Proxy::new(&proxy_config.url); - let request_handler = Arc::new(ProxyHandler::new(proxy)); - let middleware = Middleware::try_from(config).unwrap(); - let middleware = Arc::new(middleware); + // if let Some(proxy_config) = config.proxy.clone() { + // let proxy = Proxy::new(&proxy_config.url); + // let request_handler = Arc::new(ProxyHandler::new(proxy)); + // let middleware = Middleware::try_from(config).unwrap(); + // let middleware = Arc::new(middleware); - return HttpHandler { - request_handler, - middleware, - }; - } + // return HttpHandler { + // request_handler, + // middleware, + // }; + // } let file_server = FileServer::new(config.clone()); let request_handler = Arc::new(FileServerHandler::new(file_server)); diff --git a/src/server/handler/proxy.rs b/src/server/handler/proxy.rs index 502ae4e2..a79c945a 100644 --- a/src/server/handler/proxy.rs +++ b/src/server/handler/proxy.rs @@ -1,5 +1,5 @@ use async_trait::async_trait; -use hyper::{Body, Request}; +use hyper::{body::Bytes, Request}; use std::sync::Arc; use tokio::sync::Mutex; @@ -21,7 +21,7 @@ impl ProxyHandler { #[async_trait] impl RequestHandler for ProxyHandler { - async fn handle(&self, req: Arc>>) -> Arc>> { + async fn handle(&self, req: Arc>>) -> Arc>> { let proxy = Arc::clone(&self.proxy); let request = Arc::clone(&req); let response = proxy.handle(request).await; diff --git a/src/server/mod.rs b/src/server/mod.rs index 5e0271f3..5b3d43bd 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,5 +1,5 @@ mod handler; -mod https; +// mod https; mod service; pub mod middleware; @@ -117,42 +117,43 @@ impl Server { handler: handler::HttpHandler, https_config: TlsConfig, ) { - let (cert, key) = https_config.parts(); - let https_server_builder = https::Https::new(cert, key); - let server = https_server_builder.make_server(address).await.unwrap(); - let server = server.serve(make_service_fn(|_| { - // Move a clone of `handler` into the `service_fn`. - let handler = handler.clone(); - - async { - Ok::<_, Error>(service_fn(move |req| { - service::main_service(handler.to_owned(), req) - })) - } - })); - - if !self.config.quiet { - println!("Serving HTTPS: http://{}", address); - - if self.config.address.ip() == Ipv4Addr::from_str("0.0.0.0").unwrap() { - if let Ok(ip) = local_ip_address::local_ip() { - println!("Local Network IP: https://{}:{}", ip, self.config.port); - } - } - } - - if self.config.graceful_shutdown { - let graceful = server.with_graceful_shutdown(crate::utils::signal::shutdown_signal()); - - if let Err(e) = graceful.await { - eprint!("Server Error: {}", e); - } - - return; - } - - if let Err(e) = server.await { - eprint!("Server Error: {}", e); - } + // let (cert, key) = https_config.parts(); + // let https_server_builder = https::Https::new(cert, key); + // let server = https_server_builder.make_server(address).await.unwrap(); + // let server = server.serve(make_service_fn(|_| { + // // Move a clone of `handler` into the `service_fn`. + // let handler = handler.clone(); + + // async { + // Ok::<_, Error>(service_fn(move |req| { + // service::main_service(handler.to_owned(), req) + // })) + // } + // })); + + // if !self.config.quiet { + // println!("Serving HTTPS: http://{}", address); + + // if self.config.address.ip() == Ipv4Addr::from_str("0.0.0.0").unwrap() { + // if let Ok(ip) = local_ip_address::local_ip() { + // println!("Local Network IP: https://{}:{}", ip, self.config.port); + // } + // } + // } + + // if self.config.graceful_shutdown { + // let graceful = server.with_graceful_shutdown(crate::utils::signal::shutdown_signal()); + + // if let Err(e) = graceful.await { + // eprint!("Server Error: {}", e); + // } + + // return; + // } + + // if let Err(e) = server.await { + // eprint!("Server Error: {}", e); + // } + todo!() } } From ba4f139f0e4f403a1e816a0a452f07bf242728ec Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Tue, 10 Sep 2024 22:11:07 -0300 Subject: [PATCH 03/40] feat: middleware impl --- Cargo.toml | 2 +- src/bin/main.rs | 33 +++- src/lib.rs | 38 +--- src/{ => old}/addon/compression/gzip.rs | 0 src/{ => old}/addon/compression/mod.rs | 0 src/{ => old}/addon/cors.rs | 0 .../addon/file_server/directory_entry.rs | 0 src/{ => old}/addon/file_server/file.rs | 0 src/{ => old}/addon/file_server/http_utils.rs | 0 src/{ => old}/addon/file_server/mod.rs | 0 .../addon/file_server/query_params.rs | 0 .../addon/file_server/scoped_file_system.rs | 0 .../addon/file_server/template/error.hbs | 0 .../addon/file_server/template/explorer.hbs | 0 src/{ => old}/addon/logger.rs | 0 src/{ => old}/addon/mod.rs | 0 src/{ => old}/addon/proxy.rs | 0 src/{ => old}/server/handler/file_server.rs | 0 src/{ => old}/server/handler/mod.rs | 0 src/{ => old}/server/handler/proxy.rs | 0 src/{ => old}/server/https.rs | 0 src/{ => old}/server/middleware/basic_auth.rs | 0 src/{ => old}/server/middleware/cors.rs | 0 src/{ => old}/server/middleware/gzip.rs | 0 src/{ => old}/server/middleware/logger.rs | 0 src/{ => old}/server/middleware/mod.rs | 0 src/old/server/mod.rs | 159 ++++++++++++++++ src/{ => old}/server/service.rs | 0 src/server/handler.rs | 61 +++++++ src/server/middleware.rs | 66 +++++++ src/server/mod.rs | 170 +++--------------- src/utils/mod.rs | 2 +- 32 files changed, 345 insertions(+), 186 deletions(-) rename src/{ => old}/addon/compression/gzip.rs (100%) rename src/{ => old}/addon/compression/mod.rs (100%) rename src/{ => old}/addon/cors.rs (100%) rename src/{ => old}/addon/file_server/directory_entry.rs (100%) rename src/{ => old}/addon/file_server/file.rs (100%) rename src/{ => old}/addon/file_server/http_utils.rs (100%) rename src/{ => old}/addon/file_server/mod.rs (100%) rename src/{ => old}/addon/file_server/query_params.rs (100%) rename src/{ => old}/addon/file_server/scoped_file_system.rs (100%) rename src/{ => old}/addon/file_server/template/error.hbs (100%) rename src/{ => old}/addon/file_server/template/explorer.hbs (100%) rename src/{ => old}/addon/logger.rs (100%) rename src/{ => old}/addon/mod.rs (100%) rename src/{ => old}/addon/proxy.rs (100%) rename src/{ => old}/server/handler/file_server.rs (100%) rename src/{ => old}/server/handler/mod.rs (100%) rename src/{ => old}/server/handler/proxy.rs (100%) rename src/{ => old}/server/https.rs (100%) rename src/{ => old}/server/middleware/basic_auth.rs (100%) rename src/{ => old}/server/middleware/cors.rs (100%) rename src/{ => old}/server/middleware/gzip.rs (100%) rename src/{ => old}/server/middleware/logger.rs (100%) rename src/{ => old}/server/middleware/mod.rs (100%) create mode 100644 src/old/server/mod.rs rename src/{ => old}/server/service.rs (100%) create mode 100644 src/server/handler.rs create mode 100644 src/server/middleware.rs diff --git a/Cargo.toml b/Cargo.toml index dbbc54f3..dc5f3535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ version = "0.8.9" authors = ["Esteban Borai "] edition = "2021" description = "Simple and configurable command-line HTTP server" -repository = "https://github.com/EstebanBorai/http-server" +repository = "https://github.com/http-server-rs/http-server" categories = ["web-programming", "web-programming::http-server"] keywords = ["configurable", "http", "server", "serve", "static"] license = "MIT OR Apache-2.0" diff --git a/src/bin/main.rs b/src/bin/main.rs index fc195ab8..939b0bd3 100644 --- a/src/bin/main.rs +++ b/src/bin/main.rs @@ -1,21 +1,31 @@ -use http_server_lib::make_server; use std::process::exit; +use anyhow::{Context, Result}; +use http_server_lib::server::Server; +use structopt::StructOpt; + #[cfg(feature = "dhat-profiling")] use dhat::{Dhat, DhatAlloc}; +use http_server_lib::cli::Cli; +use http_server_lib::config::Config; +use http_server_lib::config::file::ConfigFile; + #[cfg(feature = "dhat-profiling")] #[global_allocator] static ALLOCATOR: DhatAlloc = DhatAlloc; #[tokio::main] -async fn main() { +async fn main() -> Result<()> { #[cfg(feature = "dhat-profiling")] let _dhat = Dhat::start_heap_profiling(); + let args = Cli::from_args(); + let config = resolve_config(args)?; - match make_server() { - Ok(server) => { - server.run().await; + match Server::run(config).await { + Ok(_) => { + println!("Server exited successfuly"); + Ok(()) } Err(error) => { eprint!("{:?}", error); @@ -23,3 +33,16 @@ async fn main() { } } } + +fn resolve_config(args: Cli) -> Result { + if let Some(config_path) = args.config { + let config_file = ConfigFile::from_file(config_path)?; + let config = Config::try_from(config_file)?; + + return Ok(config); + } + + // Otherwise configuration is build from CLI arguments + Config::try_from(args) + .with_context(|| anyhow::Error::msg("Failed to parse arguments from stdin")) +} diff --git a/src/lib.rs b/src/lib.rs index c33b90ad..4f43984c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,34 +1,4 @@ -mod addon; -mod cli; -mod config; -mod server; -mod utils; - -use anyhow::{Context, Result}; -use std::convert::TryFrom; -use structopt::StructOpt; - -use crate::config::file::ConfigFile; -use crate::config::Config; -use crate::server::Server; - -fn resolve_config(cli_arguments: cli::Cli) -> Result { - if let Some(config_path) = cli_arguments.config { - let config_file = ConfigFile::from_file(config_path)?; - let config = Config::try_from(config_file)?; - - return Ok(config); - } - - // Otherwise configuration is build from CLI arguments - Config::try_from(cli_arguments) - .with_context(|| anyhow::Error::msg("Failed to parse arguments from stdin")) -} - -pub fn make_server() -> Result { - let cli_arguments = cli::Cli::from_args(); - let config = resolve_config(cli_arguments)?; - let server = Server::new(config); - - Ok(server) -} +pub mod cli; +pub mod config; +pub mod server; +pub mod utils; diff --git a/src/addon/compression/gzip.rs b/src/old/addon/compression/gzip.rs similarity index 100% rename from src/addon/compression/gzip.rs rename to src/old/addon/compression/gzip.rs diff --git a/src/addon/compression/mod.rs b/src/old/addon/compression/mod.rs similarity index 100% rename from src/addon/compression/mod.rs rename to src/old/addon/compression/mod.rs diff --git a/src/addon/cors.rs b/src/old/addon/cors.rs similarity index 100% rename from src/addon/cors.rs rename to src/old/addon/cors.rs diff --git a/src/addon/file_server/directory_entry.rs b/src/old/addon/file_server/directory_entry.rs similarity index 100% rename from src/addon/file_server/directory_entry.rs rename to src/old/addon/file_server/directory_entry.rs diff --git a/src/addon/file_server/file.rs b/src/old/addon/file_server/file.rs similarity index 100% rename from src/addon/file_server/file.rs rename to src/old/addon/file_server/file.rs diff --git a/src/addon/file_server/http_utils.rs b/src/old/addon/file_server/http_utils.rs similarity index 100% rename from src/addon/file_server/http_utils.rs rename to src/old/addon/file_server/http_utils.rs diff --git a/src/addon/file_server/mod.rs b/src/old/addon/file_server/mod.rs similarity index 100% rename from src/addon/file_server/mod.rs rename to src/old/addon/file_server/mod.rs diff --git a/src/addon/file_server/query_params.rs b/src/old/addon/file_server/query_params.rs similarity index 100% rename from src/addon/file_server/query_params.rs rename to src/old/addon/file_server/query_params.rs diff --git a/src/addon/file_server/scoped_file_system.rs b/src/old/addon/file_server/scoped_file_system.rs similarity index 100% rename from src/addon/file_server/scoped_file_system.rs rename to src/old/addon/file_server/scoped_file_system.rs diff --git a/src/addon/file_server/template/error.hbs b/src/old/addon/file_server/template/error.hbs similarity index 100% rename from src/addon/file_server/template/error.hbs rename to src/old/addon/file_server/template/error.hbs diff --git a/src/addon/file_server/template/explorer.hbs b/src/old/addon/file_server/template/explorer.hbs similarity index 100% rename from src/addon/file_server/template/explorer.hbs rename to src/old/addon/file_server/template/explorer.hbs diff --git a/src/addon/logger.rs b/src/old/addon/logger.rs similarity index 100% rename from src/addon/logger.rs rename to src/old/addon/logger.rs diff --git a/src/addon/mod.rs b/src/old/addon/mod.rs similarity index 100% rename from src/addon/mod.rs rename to src/old/addon/mod.rs diff --git a/src/addon/proxy.rs b/src/old/addon/proxy.rs similarity index 100% rename from src/addon/proxy.rs rename to src/old/addon/proxy.rs diff --git a/src/server/handler/file_server.rs b/src/old/server/handler/file_server.rs similarity index 100% rename from src/server/handler/file_server.rs rename to src/old/server/handler/file_server.rs diff --git a/src/server/handler/mod.rs b/src/old/server/handler/mod.rs similarity index 100% rename from src/server/handler/mod.rs rename to src/old/server/handler/mod.rs diff --git a/src/server/handler/proxy.rs b/src/old/server/handler/proxy.rs similarity index 100% rename from src/server/handler/proxy.rs rename to src/old/server/handler/proxy.rs diff --git a/src/server/https.rs b/src/old/server/https.rs similarity index 100% rename from src/server/https.rs rename to src/old/server/https.rs diff --git a/src/server/middleware/basic_auth.rs b/src/old/server/middleware/basic_auth.rs similarity index 100% rename from src/server/middleware/basic_auth.rs rename to src/old/server/middleware/basic_auth.rs diff --git a/src/server/middleware/cors.rs b/src/old/server/middleware/cors.rs similarity index 100% rename from src/server/middleware/cors.rs rename to src/old/server/middleware/cors.rs diff --git a/src/server/middleware/gzip.rs b/src/old/server/middleware/gzip.rs similarity index 100% rename from src/server/middleware/gzip.rs rename to src/old/server/middleware/gzip.rs diff --git a/src/server/middleware/logger.rs b/src/old/server/middleware/logger.rs similarity index 100% rename from src/server/middleware/logger.rs rename to src/old/server/middleware/logger.rs diff --git a/src/server/middleware/mod.rs b/src/old/server/middleware/mod.rs similarity index 100% rename from src/server/middleware/mod.rs rename to src/old/server/middleware/mod.rs diff --git a/src/old/server/mod.rs b/src/old/server/mod.rs new file mode 100644 index 00000000..5b3d43bd --- /dev/null +++ b/src/old/server/mod.rs @@ -0,0 +1,159 @@ +mod handler; +// mod https; +mod service; + +pub mod middleware; + +use anyhow::Error; +use hyper::service::{make_service_fn, service_fn}; +use std::net::{Ipv4Addr, SocketAddr}; +use std::process::exit; +use std::str::FromStr; +use std::sync::Arc; + +use crate::config::tls::TlsConfig; +use crate::config::Config; + +pub struct Server { + config: Arc, +} + +impl Server { + pub fn new(config: Config) -> Server { + let config = Arc::new(config); + + Server { config } + } + + pub async fn run(self) { + let config = Arc::clone(&self.config); + let address = config.address; + let handler = handler::HttpHandler::from(Arc::clone(&config)); + let server = Arc::new(self); + let mut server_instances: Vec> = Vec::new(); + + if config.spa { + let mut index_html = config.root_dir.clone(); + index_html.push("index.html"); + + if !index_html.exists() { + eprintln!( + "SPA flag is enabled, but index.html in root does not exist. Quitting..." + ); + exit(1); + } + } + + if config.tls.is_some() { + let https_config = config.tls.clone().unwrap(); + let handler = handler.clone(); + let host = config.address.ip(); + let port = config.address.port().saturating_add(1); + let address = SocketAddr::new(host, port); + let server = Arc::clone(&server); + let task = tokio::spawn(async move { + let server = Arc::clone(&server); + + server.serve_https(address, handler, https_config).await; + }); + + server_instances.push(task); + } + + let server = Arc::clone(&server); + let task = tokio::spawn(async move { + let server = Arc::clone(&server); + + server.serve(address, handler).await; + }); + + server_instances.push(task); + + for server_task in server_instances { + server_task.await.unwrap(); + } + } + + pub async fn serve(&self, address: SocketAddr, handler: handler::HttpHandler) { + let server = hyper::Server::bind(&address).serve(make_service_fn(|_| { + // Move a clone of `handler` into the `service_fn`. + let handler = handler.clone(); + + async { + Ok::<_, Error>(service_fn(move |req| { + service::main_service(handler.to_owned(), req) + })) + } + })); + + if !self.config.quiet { + println!("Serving HTTP: http://{}", address); + + if self.config.address.ip() == Ipv4Addr::from_str("0.0.0.0").unwrap() { + if let Ok(ip) = local_ip_address::local_ip() { + println!("Local Network IP: http://{}:{}", ip, self.config.port); + } + } + } + + if self.config.graceful_shutdown { + let graceful = server.with_graceful_shutdown(crate::utils::signal::shutdown_signal()); + + if let Err(e) = graceful.await { + eprint!("Server Error: {}", e); + } + + return; + } + + if let Err(e) = server.await { + eprint!("Server Error: {}", e); + } + } + + pub async fn serve_https( + &self, + address: SocketAddr, + handler: handler::HttpHandler, + https_config: TlsConfig, + ) { + // let (cert, key) = https_config.parts(); + // let https_server_builder = https::Https::new(cert, key); + // let server = https_server_builder.make_server(address).await.unwrap(); + // let server = server.serve(make_service_fn(|_| { + // // Move a clone of `handler` into the `service_fn`. + // let handler = handler.clone(); + + // async { + // Ok::<_, Error>(service_fn(move |req| { + // service::main_service(handler.to_owned(), req) + // })) + // } + // })); + + // if !self.config.quiet { + // println!("Serving HTTPS: http://{}", address); + + // if self.config.address.ip() == Ipv4Addr::from_str("0.0.0.0").unwrap() { + // if let Ok(ip) = local_ip_address::local_ip() { + // println!("Local Network IP: https://{}:{}", ip, self.config.port); + // } + // } + // } + + // if self.config.graceful_shutdown { + // let graceful = server.with_graceful_shutdown(crate::utils::signal::shutdown_signal()); + + // if let Err(e) = graceful.await { + // eprint!("Server Error: {}", e); + // } + + // return; + // } + + // if let Err(e) = server.await { + // eprint!("Server Error: {}", e); + // } + todo!() + } +} diff --git a/src/server/service.rs b/src/old/server/service.rs similarity index 100% rename from src/server/service.rs rename to src/old/server/service.rs diff --git a/src/server/handler.rs b/src/server/handler.rs new file mode 100644 index 00000000..1a44bbca --- /dev/null +++ b/src/server/handler.rs @@ -0,0 +1,61 @@ +use std::pin::Pin; +use std::{future::Future, sync::Arc}; + +use http_body_util::Full; +use hyper::{body::Bytes, service::Service}; + +use crate::config::Config; + +use super::{HttpRequest, HttpResponse}; +use super::middleware::Middleware; + +/// Http Request Handler for the Server. +/// +/// This struct is responsible for handling incoming HTTP Requests +/// and returning an HTTP Response. Every request is passed through +/// a series of middleware functions before and after handling the +/// request. +pub struct Handler { + config: Config, + middleware: Arc, +} + +impl From for Handler { + fn from(config: Config) -> Self { + let middleware = Arc::new(Middleware::default()); + + Handler { config, middleware } + } +} + +impl Service for Handler { + type Response = HttpResponse; + type Error = hyper::Error; + type Future = Pin> + Send>>; + + fn call(&self, request: HttpRequest) -> Self::Future { + let middleware = Arc::clone(&self.middleware); + + Box::pin(async move { + match middleware.handle_before(request).await { + Ok(_request) => { + let response = hyper::Response::builder() + .body(Full::new(Bytes::from("Hello, World!"))) + .unwrap(); + + match middleware.handle_after(response).await { + Ok(response) => { + Ok(response) + }, + Err(response) => { + Ok(response) + } + } + }, + Err(response) => { + Ok(response) + } + } + }) + } +} diff --git a/src/server/middleware.rs b/src/server/middleware.rs new file mode 100644 index 00000000..e5b8baab --- /dev/null +++ b/src/server/middleware.rs @@ -0,0 +1,66 @@ +use std::pin::Pin; + +use futures::Future; + +use super::{HttpRequest, HttpResponse}; + +pub type MiddlewareBefore = Box< + dyn Fn(HttpRequest) -> Pin> + Send + Sync>> + + Send + + Sync, +>; + +pub type MiddlewareAfter = Box< + dyn Fn( + HttpResponse, + ) -> Pin> + Send + Sync>> + + Send + + Sync, +>; + +#[derive(Default)] +pub struct Middleware { + before: Vec, + after: Vec, +} + +impl Middleware { + #[allow(dead_code)] + pub fn before(&mut self, middleware: MiddlewareBefore) { + self.before.push(middleware); + } + + pub fn after(&mut self, middleware: MiddlewareAfter) { + self.after.push(middleware); + } + + pub async fn handle_before(&self, request: HttpRequest) -> Result { + let mut next = request; + + for fx in self.before.iter() { + match fx(next).await { + Ok(next_req) => next = next_req, + Err(err) => { + return Err(err); + } + } + } + + Ok(next) + } + + pub async fn handle_after(&self, response: HttpResponse) -> Result { + let mut next = response; + + for fx in self.after.iter() { + match fx(next).await { + Ok(next_res) => next = next_res, + Err(err) => { + return Err(err); + } + } + } + + Ok(next) + } +} diff --git a/src/server/mod.rs b/src/server/mod.rs index 5b3d43bd..1d45d213 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,159 +1,39 @@ mod handler; -// mod https; -mod service; +mod middleware; -pub mod middleware; - -use anyhow::Error; -use hyper::service::{make_service_fn, service_fn}; -use std::net::{Ipv4Addr, SocketAddr}; -use std::process::exit; -use std::str::FromStr; use std::sync::Arc; -use crate::config::tls::TlsConfig; -use crate::config::Config; - -pub struct Server { - config: Arc, -} - -impl Server { - pub fn new(config: Config) -> Server { - let config = Arc::new(config); - - Server { config } - } - - pub async fn run(self) { - let config = Arc::clone(&self.config); - let address = config.address; - let handler = handler::HttpHandler::from(Arc::clone(&config)); - let server = Arc::new(self); - let mut server_instances: Vec> = Vec::new(); - - if config.spa { - let mut index_html = config.root_dir.clone(); - index_html.push("index.html"); +use anyhow::Result; +use http_body_util::Full; +use hyper::{body::Bytes, server::conn::http1}; +use hyper_util::rt::TokioIo; +use tokio::net::TcpListener; - if !index_html.exists() { - eprintln!( - "SPA flag is enabled, but index.html in root does not exist. Quitting..." - ); - exit(1); - } - } - - if config.tls.is_some() { - let https_config = config.tls.clone().unwrap(); - let handler = handler.clone(); - let host = config.address.ip(); - let port = config.address.port().saturating_add(1); - let address = SocketAddr::new(host, port); - let server = Arc::clone(&server); - let task = tokio::spawn(async move { - let server = Arc::clone(&server); - - server.serve_https(address, handler, https_config).await; - }); - - server_instances.push(task); - } - - let server = Arc::clone(&server); - let task = tokio::spawn(async move { - let server = Arc::clone(&server); +use crate::config::Config; - server.serve(address, handler).await; - }); +use self::handler::Handler; - server_instances.push(task); +pub type HttpRequest = hyper::Request; - for server_task in server_instances { - server_task.await.unwrap(); - } - } +pub type HttpResponse = hyper::Response>; - pub async fn serve(&self, address: SocketAddr, handler: handler::HttpHandler) { - let server = hyper::Server::bind(&address).serve(make_service_fn(|_| { - // Move a clone of `handler` into the `service_fn`. - let handler = handler.clone(); +pub struct Server; - async { - Ok::<_, Error>(service_fn(move |req| { - service::main_service(handler.to_owned(), req) - })) - } - })); - - if !self.config.quiet { - println!("Serving HTTP: http://{}", address); - - if self.config.address.ip() == Ipv4Addr::from_str("0.0.0.0").unwrap() { - if let Ok(ip) = local_ip_address::local_ip() { - println!("Local Network IP: http://{}:{}", ip, self.config.port); +impl Server { + pub async fn run(config: Config) -> Result<()> { + let listener = TcpListener::bind(config.address).await?; + let handler = Arc::new(Handler::from(config)); + + loop { + let (stream, _) = listener.accept().await?; + let io = TokioIo::new(stream); + let handler = Arc::clone(&handler); + + tokio::task::spawn(async move { + if let Err(err) = http1::Builder::new().serve_connection(io, handler).await { + eprintln!("Error: {}", err); } - } - } - - if self.config.graceful_shutdown { - let graceful = server.with_graceful_shutdown(crate::utils::signal::shutdown_signal()); - - if let Err(e) = graceful.await { - eprint!("Server Error: {}", e); - } - - return; - } - - if let Err(e) = server.await { - eprint!("Server Error: {}", e); + }); } } - - pub async fn serve_https( - &self, - address: SocketAddr, - handler: handler::HttpHandler, - https_config: TlsConfig, - ) { - // let (cert, key) = https_config.parts(); - // let https_server_builder = https::Https::new(cert, key); - // let server = https_server_builder.make_server(address).await.unwrap(); - // let server = server.serve(make_service_fn(|_| { - // // Move a clone of `handler` into the `service_fn`. - // let handler = handler.clone(); - - // async { - // Ok::<_, Error>(service_fn(move |req| { - // service::main_service(handler.to_owned(), req) - // })) - // } - // })); - - // if !self.config.quiet { - // println!("Serving HTTPS: http://{}", address); - - // if self.config.address.ip() == Ipv4Addr::from_str("0.0.0.0").unwrap() { - // if let Ok(ip) = local_ip_address::local_ip() { - // println!("Local Network IP: https://{}:{}", ip, self.config.port); - // } - // } - // } - - // if self.config.graceful_shutdown { - // let graceful = server.with_graceful_shutdown(crate::utils::signal::shutdown_signal()); - - // if let Err(e) = graceful.await { - // eprint!("Server Error: {}", e); - // } - - // return; - // } - - // if let Err(e) = server.await { - // eprint!("Server Error: {}", e); - // } - todo!() - } } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 46d14a32..48e5ee2f 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,3 @@ -pub mod error; +// pub mod error; pub mod signal; pub mod url_encode; From 5e12528bc2491c6585092592b8166a461f295721 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sun, 15 Sep 2024 16:06:41 -0300 Subject: [PATCH 04/40] feat: move middleware to its own impls --- src/config/basic_auth.rs | 13 -------- src/config/file.rs | 3 +- src/config/mod.rs | 4 +-- src/lib.rs | 1 + src/middleware/basic_auth.rs | 63 ++++++++++++++++++++++++++++++++++++ src/middleware/mod.rs | 1 + src/server/handler.rs | 9 ++++-- src/server/middleware.rs | 5 ++- src/server/mod.rs | 40 +++++++++++++++++++++-- 9 files changed, 117 insertions(+), 22 deletions(-) delete mode 100644 src/config/basic_auth.rs create mode 100644 src/middleware/basic_auth.rs create mode 100644 src/middleware/mod.rs diff --git a/src/config/basic_auth.rs b/src/config/basic_auth.rs deleted file mode 100644 index abd94184..00000000 --- a/src/config/basic_auth.rs +++ /dev/null @@ -1,13 +0,0 @@ -use serde::Deserialize; - -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct BasicAuthConfig { - pub username: String, - pub password: String, -} - -impl BasicAuthConfig { - pub fn new(username: String, password: String) -> Self { - BasicAuthConfig { username, password } - } -} diff --git a/src/config/file.rs b/src/config/file.rs index 659eca96..ef07997d 100644 --- a/src/config/file.rs +++ b/src/config/file.rs @@ -5,7 +5,8 @@ use std::net::IpAddr; use std::path::PathBuf; use std::str::FromStr; -use super::basic_auth::BasicAuthConfig; +use crate::middleware::basic_auth::BasicAuthConfig; + use super::compression::CompressionConfig; use super::cors::CorsConfig; use super::proxy::ProxyConfig; diff --git a/src/config/mod.rs b/src/config/mod.rs index eecc3852..477e522c 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -1,4 +1,3 @@ -pub mod basic_auth; pub mod compression; pub mod cors; pub mod file; @@ -14,7 +13,8 @@ use std::path::PathBuf; use crate::cli::Cli; -use self::basic_auth::BasicAuthConfig; +use crate::middleware::basic_auth::BasicAuthConfig; + use self::compression::CompressionConfig; use self::cors::CorsConfig; use self::file::ConfigFile; diff --git a/src/lib.rs b/src/lib.rs index 4f43984c..4b02c517 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,5 @@ pub mod cli; pub mod config; +pub mod middleware; pub mod server; pub mod utils; diff --git a/src/middleware/basic_auth.rs b/src/middleware/basic_auth.rs new file mode 100644 index 00000000..48301e41 --- /dev/null +++ b/src/middleware/basic_auth.rs @@ -0,0 +1,63 @@ +use std::sync::Arc; + +use http_auth_basic::Credentials; +use hyper::header::AUTHORIZATION; +use hyper::http::StatusCode; + +use crate::server::{middleware::MiddlewareBefore, HttpErrorResponse, HttpRequest}; + +use serde::Deserialize; + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct BasicAuthConfig { + pub username: String, + pub password: String, +} + +impl BasicAuthConfig { + pub fn new(username: String, password: String) -> Self { + BasicAuthConfig { username, password } + } +} + +pub fn make_basic_auth_middleware(basic_auth_config: &BasicAuthConfig) -> MiddlewareBefore { + let credentials = Arc::new(Credentials::new( + basic_auth_config.username.as_str(), + basic_auth_config.password.as_str(), + )); + + Box::new(move |request: HttpRequest| { + let secret = Arc::clone(&credentials); + + Box::pin(async move { + let auth_header = request + .headers() + .get(AUTHORIZATION) + .ok_or( + HttpErrorResponse::new(StatusCode::UNAUTHORIZED) + .with_message("Missing Authorization header"), + ) + .map_err(|err| err.into_response())?; + + let auth_header = auth_header.to_str().map_err(|err| { + HttpErrorResponse::new(StatusCode::BAD_REQUEST) + .with_message("Invalid Authorization Header value") + .into_response() + })?; + + let credentials = Credentials::from_header(auth_header.to_string()).map_err(|err| { + HttpErrorResponse::new(StatusCode::UNAUTHORIZED) + .with_message(err.to_string().as_str()) + .into_response() + })?; + + if credentials == *secret { + return Ok(request); + } + + Err(HttpErrorResponse::new(StatusCode::UNAUTHORIZED) + .with_message("Invalid credentials") + .into_response()) + }) + }) +} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs new file mode 100644 index 00000000..175f81f5 --- /dev/null +++ b/src/middleware/mod.rs @@ -0,0 +1 @@ +pub mod basic_auth; diff --git a/src/server/handler.rs b/src/server/handler.rs index 1a44bbca..4cae614d 100644 --- a/src/server/handler.rs +++ b/src/server/handler.rs @@ -5,6 +5,7 @@ use http_body_util::Full; use hyper::{body::Bytes, service::Service}; use crate::config::Config; +use crate::middleware::basic_auth::make_basic_auth_middleware; use super::{HttpRequest, HttpResponse}; use super::middleware::Middleware; @@ -22,9 +23,13 @@ pub struct Handler { impl From for Handler { fn from(config: Config) -> Self { - let middleware = Arc::new(Middleware::default()); + let mut middleware = Middleware::new(); - Handler { config, middleware } + if let Some(basic_auth) = &config.basic_auth { + middleware.before(make_basic_auth_middleware(basic_auth)); + } + + Handler { config, middleware: Arc::new(middleware) } } } diff --git a/src/server/middleware.rs b/src/server/middleware.rs index e5b8baab..7d1a7f84 100644 --- a/src/server/middleware.rs +++ b/src/server/middleware.rs @@ -25,7 +25,10 @@ pub struct Middleware { } impl Middleware { - #[allow(dead_code)] + pub fn new() -> Self { + Middleware::default() + } + pub fn before(&mut self, middleware: MiddlewareBefore) { self.before.push(middleware); } diff --git a/src/server/mod.rs b/src/server/mod.rs index 1d45d213..5d67e8c4 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,12 +1,14 @@ -mod handler; -mod middleware; +pub mod handler; +pub mod middleware; use std::sync::Arc; use anyhow::Result; use http_body_util::Full; -use hyper::{body::Bytes, server::conn::http1}; +use hyper::{body::Bytes, server::conn::http1, Response}; +use hyper::http::StatusCode; use hyper_util::rt::TokioIo; +use serde::Serialize; use tokio::net::TcpListener; use crate::config::Config; @@ -17,6 +19,38 @@ pub type HttpRequest = hyper::Request; pub type HttpResponse = hyper::Response>; +#[derive(Debug, Serialize)] +pub struct HttpErrorResponse { + status_code: u16, + message: Option, +} + +impl HttpErrorResponse { + pub fn new(status_code: StatusCode) -> Self { + HttpErrorResponse { + status_code: status_code.as_u16(), + message: None, + } + } + + pub fn with_message(self, message: &str) -> Self { + HttpErrorResponse { + message: Some(message.to_string()), + ..self + } + } + + pub fn into_response(self) -> HttpResponse { + let body = serde_json::ser::to_string(&self).unwrap(); + + + Response::builder() + .status(self.status_code) + .body(Full::new(Bytes::from(body))) + .expect("Failed to build error response") + } +} + pub struct Server; impl Server { From 8549a0ee38d9bf6f2b32b0be960210f9b48ef371 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Tue, 1 Oct 2024 21:10:16 -0300 Subject: [PATCH 05/40] feat: cleanup --- Cargo.lock | 23 +- Cargo.toml | 18 +- benches/file_explorer.rs | 43 -- fixtures/config.toml | 35 - src/bin/main.rs | 48 -- src/cli.rs | 322 +------- src/config/compression.rs | 6 - src/config/cors.rs | 39 - src/config/file.rs | 338 -------- src/config/mod.rs | 218 ------ src/config/proxy.rs | 16 - src/config/tls.rs | 44 -- src/config/util/mod.rs | 1 - src/config/util/tls.rs | 61 -- src/lib.rs | 5 - src/main.rs | 22 + src/middleware/basic_auth.rs | 63 -- src/middleware/mod.rs | 1 - src/old/addon/compression/gzip.rs | 222 ------ src/old/addon/compression/mod.rs | 1 - src/old/addon/cors.rs | 377 --------- src/old/addon/file_server/directory_entry.rs | 73 -- src/old/addon/file_server/file.rs | 106 --- src/old/addon/file_server/http_utils.rs | 173 ----- src/old/addon/file_server/mod.rs | 466 ------------ src/old/addon/file_server/query_params.rs | 78 -- .../addon/file_server/scoped_file_system.rs | 238 ------ src/old/addon/file_server/template/error.hbs | 4 - .../addon/file_server/template/explorer.hbs | 249 ------ src/old/addon/logger.rs | 93 --- src/old/addon/mod.rs | 5 - src/old/addon/proxy.rs | 280 ------- src/old/server/handler/file_server.rs | 45 -- src/old/server/handler/mod.rs | 68 -- src/old/server/handler/proxy.rs | 31 - src/old/server/https.rs | 61 -- src/old/server/middleware/basic_auth.rs | 50 -- src/old/server/middleware/cors.rs | 48 -- src/old/server/middleware/gzip.rs | 26 - src/old/server/middleware/logger.rs | 28 - src/old/server/middleware/mod.rs | 131 ---- src/old/server/mod.rs | 159 ---- src/old/server/service.rs | 9 - src/server/handler.rs | 52 +- src/server/middleware.rs | 69 -- src/server/mod.rs | 12 +- src/utils/error.rs | 22 - src/utils/mod.rs | 3 - src/utils/signal.rs | 6 - src/utils/url_encode.rs | 66 -- tests/basic_auth.rs | 52 -- tests/cors.rs | 63 -- tests/defacto.rs | 32 - tests/e2e/basic.bats | 27 - tests/e2e/helpers/assert.bash | 720 ------------------ tests/e2e/helpers/error.bash | 41 - tests/e2e/helpers/lang.bash | 73 -- tests/e2e/helpers/load.bash | 4 - tests/e2e/helpers/output.bash | 279 ------- tests/gzip.rs | 73 -- tests/mod.rs | 3 - 61 files changed, 37 insertions(+), 5884 deletions(-) delete mode 100644 benches/file_explorer.rs delete mode 100644 fixtures/config.toml delete mode 100644 src/bin/main.rs delete mode 100644 src/config/compression.rs delete mode 100644 src/config/cors.rs delete mode 100644 src/config/file.rs delete mode 100644 src/config/mod.rs delete mode 100644 src/config/proxy.rs delete mode 100644 src/config/tls.rs delete mode 100644 src/config/util/mod.rs delete mode 100644 src/config/util/tls.rs delete mode 100644 src/lib.rs create mode 100644 src/main.rs delete mode 100644 src/middleware/basic_auth.rs delete mode 100644 src/middleware/mod.rs delete mode 100644 src/old/addon/compression/gzip.rs delete mode 100644 src/old/addon/compression/mod.rs delete mode 100644 src/old/addon/cors.rs delete mode 100644 src/old/addon/file_server/directory_entry.rs delete mode 100644 src/old/addon/file_server/file.rs delete mode 100644 src/old/addon/file_server/http_utils.rs delete mode 100644 src/old/addon/file_server/mod.rs delete mode 100644 src/old/addon/file_server/query_params.rs delete mode 100644 src/old/addon/file_server/scoped_file_system.rs delete mode 100644 src/old/addon/file_server/template/error.hbs delete mode 100644 src/old/addon/file_server/template/explorer.hbs delete mode 100644 src/old/addon/logger.rs delete mode 100644 src/old/addon/mod.rs delete mode 100644 src/old/addon/proxy.rs delete mode 100644 src/old/server/handler/file_server.rs delete mode 100644 src/old/server/handler/mod.rs delete mode 100644 src/old/server/handler/proxy.rs delete mode 100644 src/old/server/https.rs delete mode 100644 src/old/server/middleware/basic_auth.rs delete mode 100644 src/old/server/middleware/cors.rs delete mode 100644 src/old/server/middleware/gzip.rs delete mode 100644 src/old/server/middleware/logger.rs delete mode 100644 src/old/server/middleware/mod.rs delete mode 100644 src/old/server/mod.rs delete mode 100644 src/old/server/service.rs delete mode 100644 src/server/middleware.rs delete mode 100644 src/utils/error.rs delete mode 100644 src/utils/mod.rs delete mode 100644 src/utils/signal.rs delete mode 100644 src/utils/url_encode.rs delete mode 100644 tests/basic_auth.rs delete mode 100644 tests/cors.rs delete mode 100644 tests/defacto.rs delete mode 100644 tests/e2e/basic.bats delete mode 100644 tests/e2e/helpers/assert.bash delete mode 100644 tests/e2e/helpers/error.bash delete mode 100644 tests/e2e/helpers/lang.bash delete mode 100644 tests/e2e/helpers/load.bash delete mode 100644 tests/e2e/helpers/output.bash delete mode 100644 tests/gzip.rs delete mode 100644 tests/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 364b02c3..b737606d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -473,20 +473,6 @@ dependencies = [ "lazy_static", ] -[[package]] -name = "dhat" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42c9f82890824583b2cfc03d524616ff7119d27b74bd1e74799c122d509df288" -dependencies = [ - "backtrace", - "lazy_static", - "rustc-hash", - "serde", - "serde_json", - "thousands", -] - [[package]] name = "digest" version = "0.8.1" @@ -807,14 +793,13 @@ dependencies = [ [[package]] name = "http-server" -version = "0.8.9" +version = "1.0.0-draft+1" dependencies = [ "anyhow", "async-stream", "async-trait", "chrono", "criterion", - "dhat", "flate2", "futures", "handlebars", @@ -1870,12 +1855,6 @@ dependencies = [ "syn 1.0.96", ] -[[package]] -name = "thousands" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf63baf9f5039dadc247375c29eb13706706cfde997d0330d05aa63a77d8820" - [[package]] name = "tinytemplate" version = "1.2.1" diff --git a/Cargo.toml b/Cargo.toml index dc5f3535..1d52ec51 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "http-server" -version = "0.8.9" +version = "1.0.0-draft+1" authors = ["Esteban Borai "] edition = "2021" description = "Simple and configurable command-line HTTP server" @@ -12,27 +12,11 @@ readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -name = "http_server_lib" -path = "src/lib.rs" - -[[bin]] -name = "http-server" -path = "src/bin/main.rs" - -[[bench]] -name = "file_explorer" -harness = false - -[features] -dhat-profiling = ["dhat"] - [dependencies] anyhow = "1.0.75" async-stream = "0.3.5" async-trait = "0.1.74" chrono = { version = "0.4.31", features = ["serde"] } -dhat = { version = "0.2.4", optional = true } futures = "0.3.30" flate2 = "1.0.28" http = "0.2.11" diff --git a/benches/file_explorer.rs b/benches/file_explorer.rs deleted file mode 100644 index 9170a461..00000000 --- a/benches/file_explorer.rs +++ /dev/null @@ -1,43 +0,0 @@ -use criterion::Criterion; -use criterion::{criterion_group, criterion_main}; -use hyper::client::HttpConnector; -use hyper::Client; -use lazy_static::lazy_static; -use tokio::runtime::Runtime; - -lazy_static! { - static ref HTTP_CLIENT: Client = Client::new(); -} - -async fn http_get(uri: &str) { - HTTP_CLIENT.get(uri.parse().unwrap()).await.unwrap(); -} - -fn get_root(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - - c.bench_function("get_root", |b| { - b.to_async(&rt).iter(|| http_get("http://127.0.0.1:7878")); - }); -} - -fn get_file(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - - c.bench_function("get_file", |b| { - b.to_async(&rt) - .iter(|| http_get("http://127.0.0.1:7878/docs/screenshot.png")); - }); -} - -fn not_found_file(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); - - c.bench_function("not_found_file", |b| { - b.to_async(&rt) - .iter(|| http_get("http://127.0.0.1:7878/thisfiledoesntexists123")); - }); -} - -criterion_group!(benches, get_root, get_file, not_found_file); -criterion_main!(benches); diff --git a/fixtures/config.toml b/fixtures/config.toml deleted file mode 100644 index 7aae8d70..00000000 --- a/fixtures/config.toml +++ /dev/null @@ -1,35 +0,0 @@ -# Server configuration is also provided by specifying a TOML file with the -# following values - -host = "127.0.0.1" -port = 7878 - -# quiet = false -# root_dir = "./" -# graceful_shutdown = false -# index = false -# spa = false - -# [tls] -# cert = "cert.pem" -# key = "key.pem" - -# [cors] -# allow_credentials = false -# allow_headers = ["content-type", "authorization", "content-length"] -# allow_methods = ["GET", "PATCH", "POST", "PUT", "DELETE"] -# allow_origin = "example.com" -# expose_headers = ["*", "authorization"] -# max_age = 600 -# request_headers = ["x-app-version"] -# request_method = "GET" - -# [compression] -# gzip = true - -# [basic_auth] -# username = "John" -# password = "Appleseed" - -# [proxy] -# url = "https://example.com" diff --git a/src/bin/main.rs b/src/bin/main.rs deleted file mode 100644 index 939b0bd3..00000000 --- a/src/bin/main.rs +++ /dev/null @@ -1,48 +0,0 @@ -use std::process::exit; - -use anyhow::{Context, Result}; -use http_server_lib::server::Server; -use structopt::StructOpt; - -#[cfg(feature = "dhat-profiling")] -use dhat::{Dhat, DhatAlloc}; - -use http_server_lib::cli::Cli; -use http_server_lib::config::Config; -use http_server_lib::config::file::ConfigFile; - -#[cfg(feature = "dhat-profiling")] -#[global_allocator] -static ALLOCATOR: DhatAlloc = DhatAlloc; - -#[tokio::main] -async fn main() -> Result<()> { - #[cfg(feature = "dhat-profiling")] - let _dhat = Dhat::start_heap_profiling(); - let args = Cli::from_args(); - let config = resolve_config(args)?; - - match Server::run(config).await { - Ok(_) => { - println!("Server exited successfuly"); - Ok(()) - } - Err(error) => { - eprint!("{:?}", error); - exit(1); - } - } -} - -fn resolve_config(args: Cli) -> Result { - if let Some(config_path) = args.config { - let config_file = ConfigFile::from_file(config_path)?; - let config = Config::try_from(config_file)?; - - return Ok(config); - } - - // Otherwise configuration is build from CLI arguments - Config::try_from(args) - .with_context(|| anyhow::Error::msg("Failed to parse arguments from stdin")) -} diff --git a/src/cli.rs b/src/cli.rs index 7974f0bd..67a575ae 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -1,329 +1,9 @@ -use std::net::IpAddr; -use std::path::PathBuf; -use std::str::FromStr; use structopt::StructOpt; -use crate::config::util::tls::PrivateKeyAlgorithm; - #[derive(Debug, StructOpt, PartialEq, Eq)] #[structopt( name = "http-server", author = "Esteban Borai ", about = "Simple and configurable command-line HTTP server\nSource: https://github.com/EstebanBorai/http-server" )] -pub struct Cli { - /// Path to TOML configuration file. - #[structopt(parse(from_os_str), short = "c", long = "config")] - pub config: Option, - /// Host (IP) to bind the server - #[structopt(short = "h", long = "host", default_value = "127.0.0.1")] - pub host: IpAddr, - /// Port to bind the server - #[structopt(short = "p", long = "port", default_value = "7878")] - pub port: u16, - /// Route directories to index.html if present - #[structopt(short = "i", long = "index")] - pub index: bool, - /// Route non-existent files to /index.html - #[structopt(long = "spa")] - pub spa: bool, - /// Directory to serve files from - #[structopt(parse(from_os_str), default_value = "./")] - pub root_dir: PathBuf, - /// Turns off stdout/stderr logging - #[structopt(short = "q", long = "quiet")] - pub quiet: bool, - /// Enables HTTPS serving using TLS - #[structopt(long = "tls")] - pub tls: bool, - /// Path to the TLS Certificate - #[structopt(long = "tls-cert", parse(from_os_str), default_value = "cert.pem")] - pub tls_cert: PathBuf, - /// Path to the TLS Key - #[structopt(long = "tls-key", parse(from_os_str), default_value = "key.rsa")] - pub tls_key: PathBuf, - /// Algorithm used to generate certificate key - #[structopt(long = "tls-key-algorithm", default_value = "rsa")] - pub tls_key_algorithm: PrivateKeyAlgorithm, - /// Enable Cross-Origin Resource Sharing allowing any origin - #[structopt(long = "cors")] - pub cors: bool, - /// Enable GZip compression for HTTP Responses - #[structopt(long = "gzip")] - pub gzip: bool, - /// Specifies username for basic authentication - #[structopt(long = "username")] - pub username: Option, - /// Specifies password for basic authentication - #[structopt(long = "password")] - pub password: Option, - /// Prints HTTP request and response details to stdout - #[structopt(short = "l", long = "logger")] - pub logger: bool, - /// Proxy requests to the provided URL - #[structopt(long = "proxy")] - pub proxy: Option, - /// Waits for all requests to fulfill before shutting down the server - #[structopt(long = "graceful-shutdown")] - pub graceful_shutdown: bool, -} - -impl Cli { - pub fn from_str_args(args: Vec<&str>) -> Self { - Cli::from_iter_safe(args).unwrap_or_else(|e| e.exit()) - } -} - -impl Default for Cli { - fn default() -> Self { - Cli { - config: None, - host: "127.0.0.1".parse().unwrap(), - port: 7878_u16, - index: false, - spa: false, - root_dir: PathBuf::from_str("./").unwrap(), - quiet: false, - tls: false, - tls_cert: PathBuf::from_str("cert.pem").unwrap(), - tls_key: PathBuf::from_str("key.rsa").unwrap(), - tls_key_algorithm: PrivateKeyAlgorithm::Rsa, - cors: false, - gzip: false, - username: None, - password: None, - logger: false, - proxy: None, - graceful_shutdown: false, - } - } -} - -#[cfg(test)] -mod tests { - #[allow(unused_imports)] - use super::*; - - #[test] - fn no_arguments() { - let from_args = Cli::from_str_args(vec![]); - let expect = Cli::default(); - - assert_eq!(from_args, expect); - } - - #[test] - fn with_host() { - let from_args = Cli::from_str_args(vec!["http-server", "--host", "0.0.0.0"]); - let expect = Cli { - host: "0.0.0.0".parse().unwrap(), - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_host_and_port() { - let from_args = Cli::from_str_args(vec![ - "http-server", - "--host", - "192.168.0.1", - "--port", - "54200", - ]); - let expect = Cli { - host: "192.168.0.1".parse().unwrap(), - port: 54200_u16, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_root_dir() { - let from_args = Cli::from_str_args(vec!["http-server", "~/User/sources/http-server"]); - let expect = Cli { - root_dir: PathBuf::from_str("~/User/sources/http-server").unwrap(), - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_quiet_long() { - let from_args = Cli::from_str_args(vec!["http-server", "--quiet"]); - let expect = Cli { - quiet: true, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_quiet_short() { - let from_args = Cli::from_str_args(vec!["http-server", "-q"]); - let expect = Cli { - quiet: true, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_spa() { - let from_args = Cli::from_str_args(vec!["http-server", "--spa"]); - let expect = Cli { - spa: true, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_index_long() { - let from_args = Cli::from_str_args(vec!["http-server", "--index"]); - let expect = Cli { - index: true, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_index_short() { - let from_args = Cli::from_str_args(vec!["http-server", "-i"]); - let expect = Cli { - index: true, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_tls_no_config() { - let from_args = Cli::from_str_args(vec!["http-server", "--tls"]); - let expect = Cli { - tls: true, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_tls_and_config() { - let from_args = Cli::from_str_args(vec![ - "http-server", - "--tls", - "--tls-cert", - "~/secrets/cert", - "--tls-key", - "~/secrets/key", - "--tls-key-algorithm", - "rsa", - ]); - let expect = Cli { - tls: true, - tls_cert: PathBuf::from_str("~/secrets/cert").unwrap(), - tls_key: PathBuf::from_str("~/secrets/key").unwrap(), - tls_key_algorithm: PrivateKeyAlgorithm::Rsa, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_cors() { - let from_args = Cli::from_str_args(vec!["http-server", "--cors"]); - let expect = Cli { - cors: true, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_gzip() { - let from_args = Cli::from_str_args(vec!["http-server", "--gzip"]); - let expect = Cli { - gzip: true, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_basic_auth() { - let from_args = Cli::from_str_args(vec![ - "http-server", - "--username", - "John", - "--password", - "Appleseed", - ]); - let expect = Cli { - username: Some(String::from("John")), - password: Some(String::from("Appleseed")), - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_username_but_not_password() { - let from_args = Cli::from_str_args(vec!["http-server", "--username", "John"]); - let expect = Cli { - username: Some(String::from("John")), - password: None, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_password_but_not_username() { - let from_args = Cli::from_str_args(vec!["http-server", "--password", "Appleseed"]); - let expect = Cli { - username: None, - password: Some(String::from("Appleseed")), - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_logger() { - let from_args = Cli::from_str_args(vec!["http-server", "--logger"]); - let expect = Cli { - logger: true, - ..Default::default() - }; - - assert_eq!(from_args, expect); - } - - #[test] - fn with_proxy() { - let from_args = Cli::from_str_args(vec!["http-server", "--proxy", "https://example.com"]); - let expect = Cli { - proxy: Some(String::from("https://example.com")), - ..Default::default() - }; - - assert_eq!(from_args, expect); - } -} +pub struct Cli {} diff --git a/src/config/compression.rs b/src/config/compression.rs deleted file mode 100644 index 1e718b09..00000000 --- a/src/config/compression.rs +++ /dev/null @@ -1,6 +0,0 @@ -use serde::Deserialize; - -#[derive(Clone, Debug, Default, Deserialize, PartialEq, Eq)] -pub struct CompressionConfig { - pub gzip: bool, -} diff --git a/src/config/cors.rs b/src/config/cors.rs deleted file mode 100644 index 61452a16..00000000 --- a/src/config/cors.rs +++ /dev/null @@ -1,39 +0,0 @@ -use serde::Deserialize; - -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct CorsConfig { - pub allow_credentials: bool, - pub allow_headers: Option>, - pub allow_methods: Option>, - pub allow_origin: Option, - pub expose_headers: Option>, - pub max_age: Option, - pub request_headers: Option>, - pub request_method: Option, -} - -impl CorsConfig { - pub fn allow_all() -> Self { - CorsConfig { - allow_origin: Some(String::from("*")), - allow_methods: Some(vec![ - "GET".to_string(), - "POST".to_string(), - "PUT".to_string(), - "PATCH".to_string(), - "DELETE".to_string(), - "HEAD".to_string(), - ]), - allow_headers: Some(vec![ - "Origin".to_string(), - "Content-Length".to_string(), - "Content-Type".to_string(), - ]), - allow_credentials: false, - max_age: Some(43200), - expose_headers: None, - request_headers: None, - request_method: None, - } - } -} diff --git a/src/config/file.rs b/src/config/file.rs deleted file mode 100644 index ef07997d..00000000 --- a/src/config/file.rs +++ /dev/null @@ -1,338 +0,0 @@ -use anyhow::{Error, Result}; -use serde::{Deserialize, Deserializer}; -use std::fs; -use std::net::IpAddr; -use std::path::PathBuf; -use std::str::FromStr; - -use crate::middleware::basic_auth::BasicAuthConfig; - -use super::compression::CompressionConfig; -use super::cors::CorsConfig; -use super::proxy::ProxyConfig; -use super::tls::TlsConfigFile; - -#[derive(Debug, Deserialize)] -pub struct ConfigFile { - pub host: IpAddr, - pub port: u16, - pub quiet: Option, - pub index: Option, - pub spa: Option, - #[serde(default = "current_working_dir")] - #[serde(deserialize_with = "canonicalize_some")] - pub root_dir: Option, - pub tls: Option, - pub cors: Option, - pub compression: Option, - pub basic_auth: Option, - pub logger: Option, - pub proxy: Option, - pub graceful_shutdown: Option, -} - -impl ConfigFile { - pub fn from_file(file_path: PathBuf) -> Result { - let file = fs::read_to_string(file_path)?; - let config = ConfigFile::parse_toml(file.as_str())?; - - Ok(config) - } - - fn parse_toml(content: &str) -> Result { - match toml::from_str(content) { - Ok(config) => Ok(config), - Err(err) => Err(Error::msg(format!( - "Failed to parse config from file. {}", - err - ))), - } - } -} - -fn canonicalize_some<'de, D>(deserializer: D) -> Result, D::Error> -where - D: Deserializer<'de>, -{ - let value: &str = Deserialize::deserialize(deserializer)?; - let path = PathBuf::from_str(value).unwrap(); - let canon = fs::canonicalize(path).unwrap(); - - Ok(Some(canon)) -} - -fn current_working_dir() -> Option { - std::env::current_dir().ok() -} - -#[cfg(test)] -mod tests { - use std::net::Ipv4Addr; - use std::str::FromStr; - - use crate::config::util::tls::PrivateKeyAlgorithm; - - use super::*; - - #[test] - fn parses_config_from_file() { - let file_contents = r#" - host = "192.168.0.1" - port = 7878 - quiet = true - root_dir = "./fixtures" - index = true - spa = true - "#; - let host = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)); - let port = 7878; - let config = ConfigFile::parse_toml(file_contents).unwrap(); - let mut root_dir = std::env::current_dir().unwrap(); - - root_dir.push("./fixtures"); - - assert!(config.graceful_shutdown.is_none()); - assert!(config.logger.is_none()); - assert!(config.compression.is_none()); - assert_eq!(config.host, host); - assert_eq!(config.port, port); - assert_eq!(config.quiet, Some(true)); - assert_eq!(config.index, Some(true)); - assert_eq!(config.spa, Some(true)); - assert_eq!(config.root_dir, Some(root_dir)); - } - - #[test] - #[should_panic( - expected = "Failed to parse config from file. missing field `host` at line 1 column 1" - )] - fn checks_invalid_config_from_file() { - let file_contents = r#" - port = 7878 - "#; - ConfigFile::parse_toml(file_contents).unwrap(); - } - - #[test] - fn parses_config_with_tls_using_rsa() { - let file_contents = r#" - host = "192.168.0.1" - port = 7878 - quiet = false - - [tls] - cert = "cert_123.pem" - key = "key_123.pem" - key_algorithm = "rsa" - "#; - let host = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)); - let port = 7878; - let root_dir = Some(std::env::current_dir().unwrap()); - let tls = TlsConfigFile { - cert: PathBuf::from_str("cert_123.pem").unwrap(), - key: PathBuf::from_str("key_123.pem").unwrap(), - key_algorithm: PrivateKeyAlgorithm::Rsa, - }; - let config = ConfigFile::parse_toml(file_contents).unwrap(); - - assert_eq!(config.host, host); - assert_eq!(config.port, port); - assert_eq!(config.root_dir, root_dir); - assert_eq!(config.tls.unwrap(), tls); - assert_eq!(config.quiet, Some(false)); - } - - #[test] - fn parses_config_with_tls_using_pkcs8() { - let file_contents = r#" - host = "192.168.0.1" - port = 7878 - - [tls] - cert = "cert_123.pem" - key = "key_123.pem" - key_algorithm = "pkcs8" - "#; - let host = IpAddr::V4(Ipv4Addr::new(192, 168, 0, 1)); - let port = 7878; - let root_dir = Some(std::env::current_dir().unwrap()); - let tls = TlsConfigFile { - cert: PathBuf::from_str("cert_123.pem").unwrap(), - key: PathBuf::from_str("key_123.pem").unwrap(), - key_algorithm: PrivateKeyAlgorithm::Pkcs8, - }; - let config = ConfigFile::parse_toml(file_contents).unwrap(); - - assert_eq!(config.host, host); - assert_eq!(config.port, port); - assert_eq!(config.root_dir, root_dir); - assert_eq!(config.tls.unwrap(), tls); - } - - #[test] - fn parses_basic_cors_config_from_file() { - let file_contents = r#" - host = "0.0.0.0" - port = 8080 - - [cors] - allow_credentials = true - allow_headers = ["content-type", "authorization", "content-length"] - allow_methods = ["GET", "PATCH", "POST", "PUT", "DELETE"] - allow_origin = "example.com" - "#; - let host = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)); - let port = 8080; - let cors = CorsConfig { - allow_credentials: true, - allow_headers: Some(vec![ - "content-type".to_string(), - "authorization".to_string(), - "content-length".to_string(), - ]), - allow_methods: Some(vec![ - "GET".to_string(), - "PATCH".to_string(), - "POST".to_string(), - "PUT".to_string(), - "DELETE".to_string(), - ]), - allow_origin: Some(String::from("example.com")), - expose_headers: None, - max_age: None, - request_headers: None, - request_method: None, - }; - let config = ConfigFile::parse_toml(file_contents).unwrap(); - let root_dir = Some(std::env::current_dir().unwrap()); - - assert_eq!(config.host, host); - assert_eq!(config.port, port); - assert_eq!(config.root_dir, root_dir); - assert_eq!(config.cors.unwrap(), cors); - } - - #[test] - fn parses_complex_cors_config_from_file() { - let file_contents = r#" - host = "0.0.0.0" - port = 8080 - - [cors] - allow_credentials = true - allow_headers = ["content-type", "authorization", "content-length"] - allow_methods = ["GET", "PATCH", "POST", "PUT", "DELETE"] - allow_origin = "example.com" - expose_headers = ["*", "authorization"] - max_age = 2800 - request_headers = ["x-app-version"] - request_method = "GET" - "#; - let host = IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)); - let port = 8080; - let root_dir = Some(std::env::current_dir().unwrap()); - let cors = CorsConfig { - allow_credentials: true, - allow_headers: Some(vec![ - "content-type".to_string(), - "authorization".to_string(), - "content-length".to_string(), - ]), - allow_methods: Some(vec![ - "GET".to_string(), - "PATCH".to_string(), - "POST".to_string(), - "PUT".to_string(), - "DELETE".to_string(), - ]), - allow_origin: Some(String::from("example.com")), - expose_headers: Some(vec!["*".to_string(), "authorization".to_string()]), - max_age: Some(2800), - request_headers: Some(vec!["x-app-version".to_string()]), - request_method: Some(String::from("GET")), - }; - let config = ConfigFile::parse_toml(file_contents).unwrap(); - - assert_eq!(config.host, host); - assert_eq!(config.port, port); - assert_eq!(config.root_dir, root_dir); - assert_eq!(config.cors.unwrap(), cors); - } - - #[test] - fn parses_config_with_gzip_compression() { - let file_contents = r#" - host = "0.0.0.0" - port = 7878 - - [compression] - gzip = true - "#; - let config = ConfigFile::parse_toml(file_contents).unwrap(); - - assert!(config.compression.unwrap().gzip); - } - - #[test] - fn parses_config_with_basic_auth() { - let file_contents = r#" - host = "0.0.0.0" - port = 7878 - - [basic_auth] - username = "johnappleseed" - password = "john::likes::apples!" - "#; - let config = ConfigFile::parse_toml(file_contents).unwrap(); - - assert!(config.basic_auth.is_some()); - - let basic_auth = config.basic_auth.unwrap(); - - assert_eq!(basic_auth.username, String::from("johnappleseed")); - assert_eq!(basic_auth.password, String::from("john::likes::apples!")); - } - - #[test] - fn parses_config_with_logger() { - let file_contents = r#" - host = "0.0.0.0" - port = 7878 - logger = true - "#; - let config = ConfigFile::parse_toml(file_contents).unwrap(); - - assert_eq!(config.logger, Some(true)); - } - - #[test] - fn parses_config_with_proxy() { - let file_contents = r#" - host = "0.0.0.0" - port = 7878 - - [proxy] - url = "https://example.com" - "#; - let config = ConfigFile::parse_toml(file_contents).unwrap(); - - assert!(config.proxy.is_some()); - - let proxy = config.proxy.unwrap(); - - assert_eq!(proxy.url, "https://example.com"); - } - - #[test] - fn parse_config_with_graceful_shutdown() { - let file_contents = r#" - host = "0.0.0.0" - port = 7878 - graceful_shutdown = true - "#; - let config = ConfigFile::parse_toml(file_contents).unwrap(); - - assert!(config.graceful_shutdown.is_some()); - assert!(config.graceful_shutdown.unwrap()); - } -} diff --git a/src/config/mod.rs b/src/config/mod.rs deleted file mode 100644 index 477e522c..00000000 --- a/src/config/mod.rs +++ /dev/null @@ -1,218 +0,0 @@ -pub mod compression; -pub mod cors; -pub mod file; -pub mod proxy; -pub mod tls; -pub mod util; - -use anyhow::{Error, Result}; -use std::convert::TryFrom; -use std::env::current_dir; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use std::path::PathBuf; - -use crate::cli::Cli; - -use crate::middleware::basic_auth::BasicAuthConfig; - -use self::compression::CompressionConfig; -use self::cors::CorsConfig; -use self::file::ConfigFile; -use self::proxy::ProxyConfig; -use self::tls::TlsConfig; - -/// Server instance configuration used on initialization -pub struct Config { - pub address: SocketAddr, - pub host: IpAddr, - pub port: u16, - pub index: bool, - pub spa: bool, - pub root_dir: PathBuf, - pub quiet: bool, - pub tls: Option, - pub cors: Option, - pub compression: Option, - pub basic_auth: Option, - pub logger: Option, - pub proxy: Option, - pub graceful_shutdown: bool, -} - -impl Default for Config { - fn default() -> Self { - let host = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); - let port = 7878; - let address = SocketAddr::new(host, port); - let root_dir = current_dir().unwrap(); - - Self { - host, - port, - index: false, - spa: false, - address, - root_dir, - quiet: false, - tls: None, - cors: None, - compression: None, - basic_auth: None, - logger: None, - proxy: None, - graceful_shutdown: false, - } - } -} - -impl TryFrom for Config { - type Error = anyhow::Error; - - fn try_from(cli_arguments: Cli) -> Result { - let quiet = cli_arguments.quiet; - let root_dir = if cli_arguments.root_dir.to_str().unwrap() == "./" { - current_dir().unwrap() - } else { - let root_dir = cli_arguments.root_dir.to_str().unwrap(); - - cli_arguments - .root_dir - .canonicalize() - .unwrap_or_else(|_| panic!("Failed to find config on: {}", root_dir)) - }; - - let tls: Option = if cli_arguments.tls { - Some(TlsConfig::new( - cli_arguments.tls_cert, - cli_arguments.tls_key, - cli_arguments.tls_key_algorithm, - )?) - } else { - None - }; - - let cors: Option = if cli_arguments.cors { - // when CORS is specified from CLI the default - // configuration should allow any origin, method and - // headers - Some(CorsConfig::allow_all()) - } else { - None - }; - - let compression: Option = if cli_arguments.gzip { - Some(CompressionConfig { gzip: true }) - } else { - None - }; - - let basic_auth: Option = - if cli_arguments.username.is_some() && cli_arguments.password.is_some() { - Some(BasicAuthConfig::new( - cli_arguments.username.unwrap(), - cli_arguments.password.unwrap(), - )) - } else { - None - }; - - let logger = if cli_arguments.logger { - Some(true) - } else { - None - }; - - let proxy = if cli_arguments.proxy.is_some() { - let proxy_url = cli_arguments.proxy.unwrap(); - - Some(ProxyConfig::url(proxy_url)) - } else { - None - }; - - let spa = cli_arguments.spa; - let index = spa || cli_arguments.index; - - Ok(Config { - host: cli_arguments.host, - port: cli_arguments.port, - address: SocketAddr::new(cli_arguments.host, cli_arguments.port), - index, - spa, - root_dir, - quiet, - tls, - cors, - compression, - basic_auth, - logger, - proxy, - graceful_shutdown: cli_arguments.graceful_shutdown, - }) - } -} - -impl TryFrom for Config { - type Error = Error; - - fn try_from(file: ConfigFile) -> Result { - let root_dir = file.root_dir.unwrap_or_default(); - let quiet = file.quiet.unwrap_or(false); - let tls: Option = if let Some(https_config) = file.tls { - Some(TlsConfig::new( - https_config.cert, - https_config.key, - https_config.key_algorithm, - )?) - } else { - None - }; - - let spa = file.spa.unwrap_or(false); - let index = spa || file.index.unwrap_or(false); - - Ok(Config { - host: file.host, - port: file.port, - address: SocketAddr::new(file.host, file.port), - index, - spa, - quiet, - root_dir, - tls, - cors: file.cors, - compression: file.compression, - basic_auth: file.basic_auth, - logger: file.logger, - proxy: file.proxy, - graceful_shutdown: file.graceful_shutdown.unwrap_or(false), - }) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn creates_default_config() { - let host = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); - let port = 7878; - let address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), 7878); - let config = Config::default(); - - assert_eq!( - config.host, - IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), - "default host: {}", - host - ); - assert_eq!(config.port, 7878, "default port: {}", port); - assert_eq!( - config.address, address, - "default socket address: {}", - address - ); - assert!(!config.quiet, "quiet is off by default"); - } -} diff --git a/src/config/proxy.rs b/src/config/proxy.rs deleted file mode 100644 index b57b1c2f..00000000 --- a/src/config/proxy.rs +++ /dev/null @@ -1,16 +0,0 @@ -use serde::Deserialize; - -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct ProxyConfig { - pub url: String, -} - -impl ProxyConfig { - pub fn new(url: String) -> Self { - ProxyConfig { url } - } - - pub fn url(url: String) -> Self { - ProxyConfig { url } - } -} diff --git a/src/config/tls.rs b/src/config/tls.rs deleted file mode 100644 index 8f900c22..00000000 --- a/src/config/tls.rs +++ /dev/null @@ -1,44 +0,0 @@ -use anyhow::Result; -use rustls::{Certificate, PrivateKey}; -use serde::Deserialize; -use std::path::PathBuf; - -use super::util::tls::{load_cert, load_private_key, PrivateKeyAlgorithm}; - -/// Configuration for TLS protocol serving with its certificate and private key -#[derive(Clone, Debug)] -pub struct TlsConfig { - cert: Vec, - key: PrivateKey, - #[allow(dead_code)] - key_algorithm: PrivateKeyAlgorithm, -} - -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct TlsConfigFile { - pub cert: PathBuf, - pub key: PathBuf, - pub key_algorithm: PrivateKeyAlgorithm, -} - -impl TlsConfig { - pub fn new( - cert_path: PathBuf, - key_path: PathBuf, - key_algorithm: PrivateKeyAlgorithm, - ) -> Result { - let cert = load_cert(&cert_path)?; - let key = load_private_key(&key_path, &key_algorithm)?; - - Ok(TlsConfig { - cert, - key, - key_algorithm, - }) - } - - /// Retrieve certificates and private key loaded on initialization - pub fn parts(&self) -> (Vec, PrivateKey) { - (self.cert.clone(), self.key.clone()) - } -} diff --git a/src/config/util/mod.rs b/src/config/util/mod.rs deleted file mode 100644 index dbdc4f3c..00000000 --- a/src/config/util/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod tls; diff --git a/src/config/util/tls.rs b/src/config/util/tls.rs deleted file mode 100644 index 6b9dd1e3..00000000 --- a/src/config/util/tls.rs +++ /dev/null @@ -1,61 +0,0 @@ -use anyhow::{Context, Error, Result}; -use rustls::{Certificate, PrivateKey}; -use rustls_pemfile::{pkcs8_private_keys, rsa_private_keys}; -use serde::Deserialize; -use std::fs::File; -use std::io::BufReader; -use std::path::Path; -use std::str::FromStr; - -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub enum PrivateKeyAlgorithm { - #[serde(rename = "rsa")] - Rsa, - #[serde(rename = "pkcs8")] - Pkcs8, -} - -impl FromStr for PrivateKeyAlgorithm { - type Err = Error; - - fn from_str(s: &str) -> Result { - match s { - "rsa" => Ok(PrivateKeyAlgorithm::Rsa), - "pkcs8" => Ok(PrivateKeyAlgorithm::Pkcs8), - _ => anyhow::bail!("Invalid algorithm name provided for TLS key. {}", s), - } - } -} - -/// Load certificate on the provided `path` and retrieve it -/// as an instance of `Vec`. -pub fn load_cert(path: &Path) -> Result> { - let file = File::open(path).context(format!( - "Unable to find the TLS certificate on: {}", - path.to_str().unwrap() - ))?; - let mut buf_reader = BufReader::new(file); - let cert_bytes = &rustls_pemfile::certs(&mut buf_reader).unwrap()[0]; - - Ok(vec![Certificate(cert_bytes.to_vec())]) -} - -pub fn load_private_key(path: &Path, kind: &PrivateKeyAlgorithm) -> Result { - let file = File::open(path) - .with_context(|| format!("Unable to find the TLS keys on: {}", path.to_str().unwrap()))?; - let mut reader = BufReader::new(file); - let keys = match kind { - PrivateKeyAlgorithm::Rsa => rsa_private_keys(&mut reader).map_err(|_| { - let path = path.to_str().unwrap(); - - Error::msg(format!("Failed to read private (RSA) keys at {}", path)) - })?, - PrivateKeyAlgorithm::Pkcs8 => pkcs8_private_keys(&mut reader).map_err(|_| { - let path = path.to_str().unwrap(); - - Error::msg(format!("Failed to read private (PKCS8) keys at {}", path)) - })?, - }; - - Ok(PrivateKey(keys[0].clone())) -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 4b02c517..00000000 --- a/src/lib.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod cli; -pub mod config; -pub mod middleware; -pub mod server; -pub mod utils; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 00000000..efe211b2 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,22 @@ +pub mod cli; +pub mod server; + +use std::process::exit; + +use anyhow::Result; + +use crate::server::Server; + +#[tokio::main] +async fn main() -> Result<()> { + match Server::run().await { + Ok(_) => { + println!("Server exited successfuly"); + Ok(()) + } + Err(error) => { + eprint!("{:?}", error); + exit(1); + } + } +} diff --git a/src/middleware/basic_auth.rs b/src/middleware/basic_auth.rs deleted file mode 100644 index 48301e41..00000000 --- a/src/middleware/basic_auth.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::sync::Arc; - -use http_auth_basic::Credentials; -use hyper::header::AUTHORIZATION; -use hyper::http::StatusCode; - -use crate::server::{middleware::MiddlewareBefore, HttpErrorResponse, HttpRequest}; - -use serde::Deserialize; - -#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] -pub struct BasicAuthConfig { - pub username: String, - pub password: String, -} - -impl BasicAuthConfig { - pub fn new(username: String, password: String) -> Self { - BasicAuthConfig { username, password } - } -} - -pub fn make_basic_auth_middleware(basic_auth_config: &BasicAuthConfig) -> MiddlewareBefore { - let credentials = Arc::new(Credentials::new( - basic_auth_config.username.as_str(), - basic_auth_config.password.as_str(), - )); - - Box::new(move |request: HttpRequest| { - let secret = Arc::clone(&credentials); - - Box::pin(async move { - let auth_header = request - .headers() - .get(AUTHORIZATION) - .ok_or( - HttpErrorResponse::new(StatusCode::UNAUTHORIZED) - .with_message("Missing Authorization header"), - ) - .map_err(|err| err.into_response())?; - - let auth_header = auth_header.to_str().map_err(|err| { - HttpErrorResponse::new(StatusCode::BAD_REQUEST) - .with_message("Invalid Authorization Header value") - .into_response() - })?; - - let credentials = Credentials::from_header(auth_header.to_string()).map_err(|err| { - HttpErrorResponse::new(StatusCode::UNAUTHORIZED) - .with_message(err.to_string().as_str()) - .into_response() - })?; - - if credentials == *secret { - return Ok(request); - } - - Err(HttpErrorResponse::new(StatusCode::UNAUTHORIZED) - .with_message("Invalid credentials") - .into_response()) - }) - }) -} diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs deleted file mode 100644 index 175f81f5..00000000 --- a/src/middleware/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod basic_auth; diff --git a/src/old/addon/compression/gzip.rs b/src/old/addon/compression/gzip.rs deleted file mode 100644 index 88f94f99..00000000 --- a/src/old/addon/compression/gzip.rs +++ /dev/null @@ -1,222 +0,0 @@ -use anyhow::{Error, Result}; -use flate2::write::GzEncoder; -use http::{HeaderValue, Request, Response}; -use hyper::body::Bytes; -use std::io::Write; -use std::sync::Arc; -use tokio::sync::Mutex; - -/// Content-Type values that should be ignored by the compression algorithm -const IGNORED_CONTENT_TYPE: [&str; 6] = [ - "application/gzip", - "application/octet-stream", - "application/wasm", - "application/zip", - "image", - "video", -]; - -pub async fn is_encoding_accepted(request: Arc>>) -> Result { - if let Some(accept_encoding) = request - .lock() - .await - .headers() - .get(http::header::ACCEPT_ENCODING) - { - let accept_encoding = accept_encoding.to_str()?; - - return Ok(accept_encoding - .split(", ") - .map(|accepted_encoding| accepted_encoding.trim()) - .any(|accepted_encoding| accepted_encoding == "gzip")); - } - - Ok(false) -} - -pub async fn is_compressable_content_type(response: Arc>>) -> Result { - if let Some(content_type) = response - .lock() - .await - .headers() - .get(http::header::CONTENT_TYPE) - { - let content_type = content_type.to_str()?; - - if IGNORED_CONTENT_TYPE.contains(&content_type) { - return Ok(false); - } - - return Ok(true); - } - - Ok(false) -} - -pub async fn should_compress( - request: Arc>>, - response: Arc>>, -) -> Result { - Ok(is_encoding_accepted(request).await? - && is_compressable_content_type(Arc::clone(&response)).await?) -} - -pub fn compress(bytes: &[u8]) -> Result> { - let buffer: Vec = Vec::with_capacity(bytes.len()); - let mut compressor: GzEncoder> = GzEncoder::new(buffer, flate2::Compression::default()); - - compressor.write_all(bytes)?; - - compressor.finish().map_err(Error::from) -} - -pub async fn compress_http_response( - request: Arc>>, - response: Arc>>, -) -> Result<()> { - if let Ok(compressable) = should_compress(Arc::clone(&request), Arc::clone(&response)).await { - if compressable { - let mut buffer: Vec = Vec::new(); - - { - let mut response = response.lock().await; - - if response.headers().get("Content-Encoding").is_some() { - // if the "Content-Encoding" HTTP header is present in the - // `Response`, skip compression process - return Ok(()); - } - - let body = response.body_mut(); - // let mut buffer_cursor = aggregate(body).await.unwrap(); - - // while buffer_cursor.has_remaining() { - // buffer.push(buffer_cursor.get_u8()); - // } - } - - let compressed = compress(&buffer)?; - let mut response = response.lock().await; - let response_headers = response.headers_mut(); - - response_headers.append( - http::header::CONTENT_ENCODING, - HeaderValue::from_str("gzip").unwrap(), - ); - - response_headers.remove(http::header::CONTENT_LENGTH); - - *response.body_mut() = Bytes::from(compressed); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use http::response::Builder as HttpResponseBuilder; - use hyper::Request; - use hyper::body::Bytes; - use std::sync::Arc; - use tokio::sync::Mutex; - - use crate::server::middleware; - - #[allow(unused_imports)] - use super::*; - - #[allow(dead_code)] - fn make_gzip_request_response( - accept_encoding_gzip: bool, - ) -> (middleware::Request, middleware::Response) { - let file = std::include_bytes!("../../../assets/test_file.hbs"); - let request = if accept_encoding_gzip { - let mut req = Request::new(Bytes::empty()); - - req.headers_mut().append( - http::header::ACCEPT_ENCODING, - HeaderValue::from_str("gzip, deflate").unwrap(), - ); - - Arc::new(Mutex::new(req)) - } else { - Arc::new(Mutex::new(Request::new(Bytes::empty()))) - }; - let response_builder = - HttpResponseBuilder::new().header(http::header::CONTENT_TYPE, "text/html"); - let response_body = Bytes::from(file.to_vec()); - - let response = response_builder.body(response_body).unwrap(); - let response = Arc::new(Mutex::new(response)); - - (request, response) - } - - #[test] - fn gzip_compression_header() { - let raw = b"aabbaabbaabbaabb\n"; - let compressed = compress(raw).unwrap(); - let expect: [u8; 27] = [ - 31, 139, 8, 0, 0, 0, 0, 0, 0, 255, 75, 76, 76, 74, 74, 68, 194, 92, 0, 169, 225, 127, - 69, 17, 0, 0, 0, - ]; - - assert_eq!(compressed, expect); - } - - #[tokio::test] - async fn content_encoding_gzip() { - let (request, response) = make_gzip_request_response(true); - - compress_http_response(request, Arc::clone(&response)) - .await - .unwrap(); - - let compressed_response = response.lock().await; - - assert_eq!( - compressed_response - .headers() - .get(http::header::CONTENT_ENCODING) - .unwrap(), - "gzip" - ); - } - - #[tokio::test] - async fn compresses_body() { - let (request, response) = make_gzip_request_response(true); - let mut body_buffer = Vec::new(); - let mut compressed_body_buffer: Vec = Vec::new(); - - { - let mut response = response.lock().await; - let body = response.body_mut(); - - // let mut buffer_cursor = aggregate(body).await.unwrap(); - - // while buffer_cursor.has_remaining() { - // body_buffer.push(buffer_cursor.get_u8()); - // } - } - - compress_http_response(request, Arc::clone(&response)) - .await - .unwrap(); - - { - let mut compressed_response = response.lock().await; - let compressed_body = compressed_response.body_mut(); - - // let mut buffer_cursor = aggregate(compressed_body).await.unwrap(); - - // while buffer_cursor.has_remaining() { - // compressed_body_buffer.push(buffer_cursor.get_u8()); - // } - } - - assert_eq!(body_buffer.len(), 6364); - assert_eq!(compressed_body_buffer.len(), 20); - } -} diff --git a/src/old/addon/compression/mod.rs b/src/old/addon/compression/mod.rs deleted file mode 100644 index abcab22b..00000000 --- a/src/old/addon/compression/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod gzip; diff --git a/src/old/addon/cors.rs b/src/old/addon/cors.rs deleted file mode 100644 index ea51e816..00000000 --- a/src/old/addon/cors.rs +++ /dev/null @@ -1,377 +0,0 @@ -use anyhow::{Error, Result}; -use hyper::header::{self, HeaderName, HeaderValue}; -use std::convert::TryFrom; - -use crate::config::cors::CorsConfig; - -/// CORS (Cross Origin Resource Sharing) configuration for the HTTP/S -/// server. -/// -/// `CorsConfig` holds the configuration for the CORS headers for a -/// HTTP/S server instance. The following headers are supported: -/// -/// Access-Control-Allow-Credentials header -/// Access-Control-Allow-Headers header -/// Access-Control-Allow-Methods header -/// Access-Control-Expose-Headers header -/// Access-Control-Max-Age header -/// Access-Control-Request-Headers header -/// Access-Control-Request-Method header -/// -/// Refer to CORS here: https://www.w3.org/wiki/CORS -#[derive(Clone, Debug, Default, PartialEq, Eq)] -pub struct Cors { - /// The Access-Control-Allow-Credentials response header tells browsers - /// whether to expose the response to frontend JavaScript code when the - /// request's credentials mode (Request.credentials) is include. - /// - /// The only valid value for this header is true (case-sensitive). If you - /// don't need credentials, omit this header entirely (rather than setting - /// its value to false). - /// - /// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials - pub(crate) allow_credentials: bool, - /// The Access-Control-Allow-Headers response header is used in response to a - /// preflight request which includes the Access-Control-Request-Headers to - /// indicate which HTTP headers can be used during the actual request. - /// - /// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers - pub(crate) allow_headers: Option>, - /// The Access-Control-Allow-Methods response header specifies the method or - /// methods allowed when accessing the resource in response to a preflight - /// request. - /// - /// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods - pub(crate) allow_methods: Option>, - /// The Access-Control-Allow-Origin response header indicates whether the - /// response can be shared with requesting code from the given origin. - /// - /// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin - pub(crate) allow_origin: Option, - /// The Access-Control-Expose-Headers response header allows a server to - /// indicate which response headers should be made available to scripts - /// running in the browser, in response to a cross-origin request. - /// - /// Only the CORS-safelisted response headers are exposed by default. - /// For clients to be able to access other headers, the server must list them - /// using the Access-Control-Expose-Headers header. - /// - /// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers - pub(crate) expose_headers: Option>, - /// The Access-Control-Max-Age response header indicates how long the results - /// of a preflight request (that is the information contained in the - /// Access-Control-Allow-Methods and Access-Control-Allow-Headers headers) - /// can be cached. - /// - /// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age - pub(crate) max_age: Option, - /// The Access-Control-Request-Headers request header is used by browsers - /// when issuing a preflight request, to let the server know which HTTP - /// headers the client might send when the actual request is made (such as - /// with setRequestHeader()). This browser side header will be answered by - /// the complementary server side header of Access-Control-Allow-Headers. - /// - /// Source: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers - pub(crate) request_headers: Option>, - /// The Access-Control-Request-Method request header is used by browsers when - /// issuing a preflight request, to let the server know which HTTP method will - /// be used when the actual request is made. This header is necessary as the - /// preflight request is always an OPTIONS and doesn't use the same method as - /// the actual request. - pub(crate) request_method: Option, -} - -impl Cors { - pub fn builder() -> CorsBuilder { - CorsBuilder { - config: Cors::default(), - } - } - - pub fn make_http_headers(&self) -> Vec<(HeaderName, HeaderValue)> { - let cors = self.clone(); - let mut cors_headers: Vec<(HeaderName, HeaderValue)> = Vec::new(); - - if self.allow_credentials { - cors_headers.push(( - header::ACCESS_CONTROL_ALLOW_CREDENTIALS, - HeaderValue::from_str("true").unwrap(), - )); - } - - if let Some(allow_headers) = cors.allow_headers { - let allow_headers = allow_headers.join(", "); - - cors_headers.push(( - header::ACCESS_CONTROL_ALLOW_HEADERS, - HeaderValue::from_str(allow_headers.as_str()).unwrap(), - )); - } - - if let Some(allow_methods) = cors.allow_methods { - let allow_methods = allow_methods.join(", "); - - cors_headers.push(( - header::ACCESS_CONTROL_ALLOW_METHODS, - HeaderValue::from_str(allow_methods.as_str()).unwrap(), - )); - } - - if let Some(allow_origin) = cors.allow_origin { - cors_headers.push(( - header::ACCESS_CONTROL_ALLOW_ORIGIN, - HeaderValue::from_str(allow_origin.as_str()).unwrap(), - )); - } - - if let Some(expose_headers) = cors.expose_headers { - let expose_headers = expose_headers.join(", "); - - cors_headers.push(( - header::ACCESS_CONTROL_EXPOSE_HEADERS, - HeaderValue::from_str(expose_headers.as_str()).unwrap(), - )); - } - - if let Some(max_age) = cors.max_age { - cors_headers.push(( - header::ACCESS_CONTROL_MAX_AGE, - HeaderValue::from_str(max_age.to_string().as_str()).unwrap(), - )); - } - - if let Some(request_headers) = cors.request_headers { - let request_headers = request_headers.join(", "); - - cors_headers.push(( - header::ACCESS_CONTROL_REQUEST_HEADERS, - HeaderValue::from_str(request_headers.as_str()).unwrap(), - )); - } - - if let Some(request_method) = cors.request_method { - cors_headers.push(( - header::ACCESS_CONTROL_REQUEST_METHOD, - HeaderValue::from_str(request_method.as_str()).unwrap(), - )); - } - - cors_headers - } -} - -/// CorsConfig Builder -pub struct CorsBuilder { - config: Cors, -} - -impl CorsBuilder { - pub fn allow_origin(mut self, origin: String) -> Self { - self.config.allow_origin = Some(origin); - self - } - - pub fn allow_methods(mut self, methods: Vec) -> Self { - self.config.allow_methods = Some(methods); - self - } - - pub fn allow_headers(mut self, headers: Vec) -> Self { - self.config.allow_headers = Some(headers); - self - } - - pub fn allow_credentials(mut self) -> Self { - self.config.allow_credentials = true; - self - } - - pub fn max_age(mut self, duration: u64) -> Self { - self.config.max_age = Some(duration); - self - } - - pub fn expose_headers(mut self, headers: Vec) -> Self { - self.config.expose_headers = Some(headers); - self - } - - pub fn request_headers(mut self, headers: Vec) -> Self { - self.config.request_headers = Some(headers); - self - } - - pub fn request_method(mut self, method: String) -> Self { - self.config.request_method = Some(method); - self - } - - pub fn build(self) -> Cors { - self.config - } -} - -impl TryFrom for Cors { - type Error = Error; - - fn try_from(value: CorsConfig) -> Result { - let mut builder = Cors::builder(); - - if value.allow_credentials { - builder = builder.allow_credentials(); - } - - if let Some(headers) = value.allow_headers { - builder = builder.allow_headers(headers); - } - - if let Some(methods) = value.allow_methods { - builder = builder.allow_methods(methods); - } - - if let Some(origin) = value.allow_origin { - builder = builder.allow_origin(origin); - } - - if let Some(max_age) = value.max_age { - builder = builder.max_age(max_age); - } - - if let Some(expose_headers) = value.expose_headers { - builder = builder.expose_headers(expose_headers); - } - - if let Some(request_headers) = value.request_headers { - builder = builder.request_headers(request_headers); - } - - if let Some(request_method) = value.request_method { - builder = builder.request_method(request_method); - } - - Ok(builder.build()) - } -} - -#[cfg(test)] -mod tests { - #[allow(unused_imports)] - use super::*; - - #[test] - fn creates_cors_config_with_builder() { - let cors_config = Cors::builder() - .allow_origin("http://example.com".to_string()) - .allow_methods(vec![ - "GET".to_string(), - "POST".to_string(), - "PUT".to_string(), - "DELETE".to_string(), - ]) - .allow_headers(vec![ - "Content-Type".to_string(), - "Origin".to_string(), - "Content-Length".to_string(), - ]) - .build(); - - assert_eq!( - cors_config.allow_origin, - Some(String::from("http://example.com")) - ); - assert_eq!( - cors_config.allow_methods, - Some(vec![ - String::from("GET"), - String::from("POST"), - String::from("PUT"), - String::from("DELETE"), - ]) - ); - assert_eq!( - cors_config.allow_headers, - Some(vec![ - String::from("Content-Type"), - String::from("Origin"), - String::from("Content-Length"), - ]) - ); - assert!(!cors_config.allow_credentials); - assert_eq!(cors_config.max_age, None); - assert_eq!(cors_config.expose_headers, None); - assert_eq!(cors_config.request_headers, None); - assert_eq!(cors_config.request_method, None); - } - - #[test] - fn creates_cors_config_which_allows_all_connections() { - let cors_config = CorsConfig::allow_all(); - - assert_eq!(cors_config.allow_origin, Some(String::from("*"))); - assert_eq!( - cors_config.allow_methods, - Some(vec![ - String::from("GET"), - String::from("POST"), - String::from("PUT"), - String::from("PATCH"), - String::from("DELETE"), - String::from("HEAD"), - ]) - ); - assert_eq!( - cors_config.allow_headers, - Some(vec![ - String::from("Origin"), - String::from("Content-Length"), - String::from("Content-Type"), - ]) - ); - assert!(!cors_config.allow_credentials); - assert_eq!(cors_config.max_age, Some(43200)); - assert_eq!(cors_config.expose_headers, None); - assert_eq!(cors_config.request_headers, None); - assert_eq!(cors_config.request_method, None); - } - - #[test] - fn creates_cors_config_from_file() { - let allow_headers = vec![ - "content-type".to_string(), - "content-length".to_string(), - "request-id".to_string(), - ]; - let allow_mehtods = vec!["GET".to_string(), "POST".to_string(), "PUT".to_string()]; - let allow_origin = String::from("github.com"); - let expose_headers = vec!["content-type".to_string(), "request-id".to_string()]; - let max_age = 5400; - let request_headers = vec![ - "content-type".to_string(), - "content-length".to_string(), - "authorization".to_string(), - ]; - let request_method = String::from("GET"); - let config = CorsConfig { - allow_credentials: true, - allow_headers: Some(allow_headers.clone()), - allow_methods: Some(allow_mehtods.clone()), - allow_origin: Some(allow_origin.clone()), - expose_headers: Some(expose_headers.clone()), - max_age: Some(max_age), - request_headers: Some(request_headers.clone()), - request_method: Some(request_method.clone()), - }; - let cors = Cors { - allow_credentials: true, - allow_headers: Some(allow_headers), - allow_methods: Some(allow_mehtods), - allow_origin: Some(allow_origin), - expose_headers: Some(expose_headers), - max_age: Some(max_age), - request_headers: Some(request_headers), - request_method: Some(request_method), - }; - - assert_eq!(cors, Cors::try_from(config).unwrap()); - } -} diff --git a/src/old/addon/file_server/directory_entry.rs b/src/old/addon/file_server/directory_entry.rs deleted file mode 100644 index 472e3045..00000000 --- a/src/old/addon/file_server/directory_entry.rs +++ /dev/null @@ -1,73 +0,0 @@ -use chrono::{DateTime, Local}; -use serde::{Deserialize, Serialize}; -use std::cmp::{Ord, Ordering}; - -/// A Directory entry used to display a File Explorer's entry. -/// This struct is directly related to the Handlebars template used -/// to power the File Explorer's UI -#[derive(Debug, Eq, Serialize)] -pub struct DirectoryEntry { - pub(crate) display_name: String, - pub(crate) is_dir: bool, - pub(crate) size_bytes: u64, - pub(crate) entry_path: String, - pub(crate) date_created: Option>, - pub(crate) date_modified: Option>, -} - -impl Ord for DirectoryEntry { - fn cmp(&self, other: &Self) -> Ordering { - if self.is_dir && other.is_dir { - return self.display_name.cmp(&other.display_name); - } - - if self.is_dir && !other.is_dir { - return Ordering::Less; - } - - Ordering::Greater - } -} - -impl PartialOrd for DirectoryEntry { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for DirectoryEntry { - fn eq(&self, other: &Self) -> bool { - if self.is_dir && other.is_dir { - return self.display_name == other.display_name; - } - - self.display_name == other.display_name - } -} - -/// A Breadcrumb Item used to navigate to previous path components -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] -pub struct BreadcrumbItem { - pub(crate) entry_name: String, - pub(crate) entry_link: String, -} - -/// The value passed to the Handlebars template engine. -/// All references contained in File Explorer's UI are provided -/// via the `DirectoryIndex` struct -#[derive(Debug, Serialize)] -pub struct DirectoryIndex { - /// Directory listing entry - pub(crate) entries: Vec, - pub(crate) breadcrumbs: Vec, - pub(crate) sort: Sort, -} - -#[derive(Serialize, Debug, PartialEq, Deserialize)] -pub enum Sort { - Directory, - Name, - Size, - DateCreated, - DateModified, -} diff --git a/src/old/addon/file_server/file.rs b/src/old/addon/file_server/file.rs deleted file mode 100644 index 8d003f54..00000000 --- a/src/old/addon/file_server/file.rs +++ /dev/null @@ -1,106 +0,0 @@ -use anyhow::{Context, Result}; -use chrono::{DateTime, Local}; -use futures::Stream; -use hyper::body::Bytes; -use mime_guess::{from_path, Mime}; -use std::fs::Metadata; -use std::mem::MaybeUninit; -use std::path::PathBuf; -use std::pin::Pin; -use std::task::{self, Poll}; -use tokio::io::{AsyncRead, ReadBuf}; - -pub const FILE_BUFFER_SIZE: usize = 8 * 1024; - -pub type FileBuffer = Box<[MaybeUninit; FILE_BUFFER_SIZE]>; - -/// Wrapper around `tokio::fs::File` built from a OS ScopedFileSystem file -/// providing `std::fs::Metadata` and the path to such file -#[derive(Debug)] -pub struct File { - pub path: PathBuf, - pub file: tokio::fs::File, - pub metadata: Metadata, -} - -impl File { - pub fn new(path: PathBuf, file: tokio::fs::File, metadata: Metadata) -> Self { - File { - path, - file, - metadata, - } - } - - pub fn mime(&self) -> Mime { - from_path(self.path.clone()).first_or_octet_stream() - } - - pub fn size(&self) -> u64 { - self.metadata.len() - } - - pub fn last_modified(&self) -> Result> { - let modified = self - .metadata - .modified() - .context("Failed to read last modified time for file")?; - let modified: DateTime = modified.into(); - - Ok(modified) - } - - #[allow(dead_code)] - pub fn bytes(self) -> Vec { - let byte_stream = ByteStream { - file: self.file, - buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]), - }; - - byte_stream - .buffer - .iter() - .map(|muint| unsafe { muint.assume_init() }) - .collect::>() - } -} - -pub struct ByteStream { - file: tokio::fs::File, - buffer: FileBuffer, -} - -impl From for ByteStream { - fn from(file: File) -> Self { - ByteStream { - file: file.file, - buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]), - } - } -} - -impl Stream for ByteStream { - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - let ByteStream { - ref mut file, - ref mut buffer, - } = *self; - let mut read_buffer = ReadBuf::uninit(&mut buffer[..]); - - match Pin::new(file).poll_read(cx, &mut read_buffer) { - Poll::Ready(Ok(())) => { - let filled = read_buffer.filled(); - - if filled.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::copy_from_slice(filled)))) - } - } - Poll::Ready(Err(error)) => Poll::Ready(Some(Err(error.into()))), - Poll::Pending => Poll::Pending, - } - } -} diff --git a/src/old/addon/file_server/http_utils.rs b/src/old/addon/file_server/http_utils.rs deleted file mode 100644 index e0118c70..00000000 --- a/src/old/addon/file_server/http_utils.rs +++ /dev/null @@ -1,173 +0,0 @@ -use std::fmt::Display; -use std::mem::MaybeUninit; -use std::pin::Pin; -use std::task::{self, Poll}; - -use anyhow::{Context, Result}; -use chrono::{DateTime, Local, Utc}; -use futures::Stream; -use http::response::Builder as HttpResponseBuilder; -use hyper::body::Bytes; -use tokio::io::{AsyncRead, ReadBuf}; - -use super::file::File; - -const FILE_BUFFER_SIZE: usize = 8 * 1024; - -pub type FileBuffer = Box<[MaybeUninit; FILE_BUFFER_SIZE]>; - -/// HTTP Response `Cache-Control` directive -/// -/// Allow dead code until we have support for cache control configuration -#[allow(dead_code)] - -pub enum CacheControlDirective { - /// Cache-Control: must-revalidate - MustRevalidate, - /// Cache-Control: no-cache - NoCache, - /// Cache-Control: no-store - NoStore, - /// Cache-Control: no-transform - NoTransform, - /// Cache-Control: public - Public, - /// Cache-Control: private - Private, - /// Cache-Control: proxy-revalidate - ProxyRavalidate, - /// Cache-Control: max-age= - MaxAge(u64), - /// Cache-Control: s-maxage= - SMaxAge(u64), -} - -impl Display for CacheControlDirective { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let string = match &self { - Self::MustRevalidate => String::from("must-revalidate"), - Self::NoCache => String::from("no-cache"), - Self::NoStore => String::from("no-store"), - Self::NoTransform => String::from("no-transform"), - Self::Public => String::from("public"), - Self::Private => String::from("private"), - Self::ProxyRavalidate => String::from("proxy-revalidate"), - Self::MaxAge(age) => format!("max-age={}", age), - Self::SMaxAge(age) => format!("s-maxage={}", age), - }; - - write!(f, "{string}") - } -} - -pub struct ResponseHeaders { - cache_control: String, - content_length: u64, - content_type: String, - etag: String, - last_modified: String, -} - -impl ResponseHeaders { - pub fn new( - file: &File, - cache_control_directive: CacheControlDirective, - ) -> Result { - let last_modified = file.last_modified()?; - - Ok(ResponseHeaders { - cache_control: cache_control_directive.to_string(), - content_length: ResponseHeaders::content_length(file), - content_type: ResponseHeaders::content_type(file), - etag: ResponseHeaders::etag(file, &last_modified), - last_modified: ResponseHeaders::last_modified(&last_modified), - }) - } - - fn content_length(file: &File) -> u64 { - file.size() - } - - fn content_type(file: &File) -> String { - file.mime().to_string() - } - - fn etag(file: &File, last_modified: &DateTime) -> String { - format!( - "W/\"{0:x}-{1:x}.{2:x}\"", - file.size(), - last_modified.timestamp(), - last_modified.timestamp_subsec_nanos(), - ) - } - - fn last_modified(last_modified: &DateTime) -> String { - format!( - "{} GMT", - last_modified - .with_timezone(&Utc) - .format("%a, %e %b %Y %H:%M:%S") - ) - } -} - -pub async fn make_http_file_response( - file: File, - cache_control_directive: CacheControlDirective, -) -> Result> { - let headers = ResponseHeaders::new(&file, cache_control_directive)?; - let builder = HttpResponseBuilder::new() - .header(http::header::CONTENT_LENGTH, headers.content_length) - .header(http::header::CACHE_CONTROL, headers.cache_control) - .header(http::header::CONTENT_TYPE, headers.content_type) - .header(http::header::ETAG, headers.etag) - .header(http::header::LAST_MODIFIED, headers.last_modified); - - let body = file_bytes_into_http_body(file).await; - let response = builder - .body(body) - .context("Failed to build HTTP File Response")?; - - Ok(response) -} - -pub async fn file_bytes_into_http_body(file: File) -> Bytes { - let byte_stream = ByteStream { - file: file.file, - buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]), - }; - - // Bytes::from(byte_stream) - todo!() -} - -pub struct ByteStream { - file: tokio::fs::File, - buffer: FileBuffer, -} - -impl Stream for ByteStream { - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - let ByteStream { - ref mut file, - ref mut buffer, - } = *self; - let mut read_buffer = ReadBuf::uninit(&mut buffer[..]); - - match Pin::new(file).poll_read(cx, &mut read_buffer) { - Poll::Ready(Ok(())) => { - let filled = read_buffer.filled(); - - if filled.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::copy_from_slice(filled)))) - } - } - Poll::Ready(Err(error)) => Poll::Ready(Some(Err(error.into()))), - Poll::Pending => Poll::Pending, - } - } -} diff --git a/src/old/addon/file_server/mod.rs b/src/old/addon/file_server/mod.rs deleted file mode 100644 index 2ce3e80a..00000000 --- a/src/old/addon/file_server/mod.rs +++ /dev/null @@ -1,466 +0,0 @@ -mod directory_entry; -mod file; -mod http_utils; -mod query_params; -mod scoped_file_system; - -use chrono::{DateTime, Local}; - -pub use file::File; -use humansize::{format_size, DECIMAL}; - -pub use scoped_file_system::{Entry, ScopedFileSystem}; - -use anyhow::{Context, Result}; -use handlebars::{handlebars_helper, Handlebars}; -use http::response::Builder as HttpResponseBuilder; -use http::{Response, StatusCode, Uri}; -use hyper::body::Bytes; -use percent_encoding::{percent_decode_str, utf8_percent_encode}; -use std::fs::read_dir; -use std::path::{Component, Path, PathBuf}; -use std::str::FromStr; -use std::sync::Arc; - -use crate::config::Config; -use crate::utils::url_encode::{decode_uri, encode_uri, PERCENT_ENCODE_SET}; - -use self::directory_entry::{BreadcrumbItem, DirectoryEntry, DirectoryIndex, Sort}; -use self::http_utils::{make_http_file_response, CacheControlDirective}; -use self::query_params::{QueryParams, SortBy}; - -/// Explorer's Handlebars template filename -const EXPLORER_TEMPLATE: &str = "explorer"; - -pub struct FileServer { - handlebars: Arc>, - scoped_file_system: ScopedFileSystem, - config: Arc, -} - -impl<'a> FileServer { - /// Creates a new instance of the `FileExplorer` with the provided `root_dir` - pub fn new(config: Arc) -> Self { - let handlebars = FileServer::make_handlebars_engine(); - let scoped_file_system = ScopedFileSystem::new(config.root_dir.clone()).unwrap(); - - FileServer { - handlebars, - scoped_file_system, - config, - } - } - - /// Creates a new `Handlebars` instance with templates registered - fn make_handlebars_engine() -> Arc> { - let mut handlebars = Handlebars::new(); - - let template = std::include_bytes!("./template/explorer.hbs"); - let template = std::str::from_utf8(template).unwrap(); - - handlebars - .register_template_string(EXPLORER_TEMPLATE, template) - .unwrap(); - - handlebars_helper!(date: |d: Option>| { - match d { - Some(d) => d.format("%Y/%m/%d %H:%M:%S").to_string(), - None => "-".to_owned(), - } - }); - handlebars.register_helper("date", Box::new(date)); - - handlebars_helper!(size: |bytes: u64| format_size(bytes, DECIMAL)); - handlebars.register_helper("size", Box::new(size)); - - handlebars_helper!(sort_name: |sort: Sort| sort == Sort::Name); - handlebars.register_helper("sort_name", Box::new(sort_name)); - - handlebars_helper!(sort_size: |sort: Sort| sort == Sort::Size); - handlebars.register_helper("sort_size", Box::new(sort_size)); - - handlebars_helper!(sort_date_created: |sort: Sort| sort == Sort::DateCreated); - handlebars.register_helper("sort_date_created", Box::new(sort_date_created)); - - handlebars_helper!(sort_date_modified: |sort: Sort| sort == Sort::DateModified); - handlebars.register_helper("sort_date_modified", Box::new(sort_date_modified)); - - Arc::new(handlebars) - } - - fn parse_path(req_uri: &str) -> Result<(PathBuf, Option)> { - let uri = Uri::from_str(req_uri)?; - let uri_parts = uri.into_parts(); - - if let Some(path_and_query) = uri_parts.path_and_query { - let path = path_and_query.path(); - let query_params = if let Some(query_str) = path_and_query.query() { - Some(QueryParams::from_str(query_str)?) - } else { - None - }; - - return Ok((decode_uri(path), query_params)); - } - - Ok((PathBuf::from_str("/")?, None)) - } - - /// Resolves a HTTP Request to a file or directory. - /// - /// If the method of the HTTP Request is not `GET`, then responds with - /// `Bad Request` - /// - /// If URI doesn't matches a path relative to `root_dir`, then responds - /// with `Bad Reuest` - /// - /// If the HTTP Request URI points to `/` (root), the default behavior - /// would be to respond with `Not Found` but in order to provide `root_dir` - /// indexing, the request is handled and renders `root_dir` directory - /// listing instead. - /// - /// If the HTTP Request doesn't match any file relative to `root_dir` then - /// responds with 'Not Found' - /// - /// If the HTTP Request matches a forbidden file (User doesn't have - /// permissions to read), responds with `Forbidden` - /// - /// If the matched path resolves a directory, responds with the directory - /// listing page - /// - /// If the matched path resolves to a file, attempts to render it if the - /// MIME type is supported, otherwise returns the binary (downloadable file) - pub async fn resolve(&self, req_path: String) -> Result> { - let (path, query_params) = FileServer::parse_path(req_path.as_str())?; - - match self.scoped_file_system.resolve(path).await { - Ok(entry) => match entry { - Entry::Directory(dir) => { - if self.config.index { - let mut filepath = dir.path(); - - filepath.push("index.html"); - if let Ok(file) = tokio::fs::File::open(&filepath).await { - return make_http_file_response( - File { - path: filepath, - metadata: file.metadata().await?, - file, - }, - CacheControlDirective::MaxAge(2500), - ) - .await; - } - } - - self.render_directory_index(dir.path(), query_params).await - } - Entry::File(file) => { - make_http_file_response(*file, CacheControlDirective::MaxAge(2500)).await - } - }, - Err(err) => { - if self.config.spa { - return make_http_file_response( - { - let mut path = self.config.root_dir.clone(); - path.push("index.html"); - - let file = tokio::fs::File::open(&path).await?; - - let metadata = file.metadata().await?; - - File { - path, - metadata, - file, - } - }, - CacheControlDirective::MaxAge(2500), - ) - .await; - } - - let status = match err.kind() { - std::io::ErrorKind::NotFound => http::StatusCode::NOT_FOUND, - std::io::ErrorKind::PermissionDenied => http::StatusCode::FORBIDDEN, - _ => http::StatusCode::BAD_REQUEST, - }; - - let code = match err.kind() { - std::io::ErrorKind::NotFound => "404", - std::io::ErrorKind::PermissionDenied => "403", - _ => "400", - }; - - let response = http::Response::builder() - .status(status) - .header(http::header::CONTENT_TYPE, "text/html") - .body(Bytes::from( - handlebars::Handlebars::new().render_template( - include_str!("./template/error.hbs"), - &serde_json::json!({"error": err.to_string(), "code": code}), - )?, - ))?; - - Ok(response) - } - } - } - - /// Indexes the directory by creating a `DirectoryIndex`. Such `DirectoryIndex` - /// is used to build the Handlebars "Explorer" template using the Handlebars - /// engine and builds an HTTP Response containing such file - async fn render_directory_index( - &self, - path: PathBuf, - query_params: Option, - ) -> Result> { - let directory_index = - FileServer::index_directory(self.config.root_dir.clone(), path, query_params)?; - let html = self - .handlebars - .render(EXPLORER_TEMPLATE, &directory_index) - .unwrap(); - - let body = Bytes::from(html); - - Ok(HttpResponseBuilder::new() - .header(http::header::CONTENT_TYPE, "text/html") - .status(StatusCode::OK) - .body(body) - .expect("Failed to build response")) - } - - /// Encodes a `PathBuf` component using `PercentEncode` with UTF-8 charset. - /// - /// # Panics - /// - /// If the component's `OsStr` representation doesn't belong to valid UTF-8 - /// this function panics. - fn encode_component(comp: Component) -> String { - let component = comp - .as_os_str() - .to_str() - .expect("The provided OsStr doesn't belong to the UTF-8 charset."); - - utf8_percent_encode(component, PERCENT_ENCODE_SET).to_string() - } - - fn breadcrumbs_from_path(root_dir: &Path, path: &Path) -> Result> { - let root_dir_name = root_dir - .components() - .last() - .unwrap() - .as_os_str() - .to_str() - .expect("The first path component is not UTF-8 charset compliant."); - let stripped = path - .strip_prefix(root_dir)? - .components() - .map(FileServer::encode_component) - .collect::>(); - - let mut breadcrumbs = stripped - .iter() - .enumerate() - .map(|(idx, entry_name)| BreadcrumbItem { - entry_name: percent_decode_str(entry_name) - .decode_utf8() - .expect("The path name is not UTF-8 compliant") - .to_string(), - entry_link: format!("/{}", stripped[0..=idx].join("/")), - }) - .collect::>(); - - breadcrumbs.insert( - 0, - BreadcrumbItem { - entry_name: String::from(root_dir_name), - entry_link: String::from("/"), - }, - ); - - Ok(breadcrumbs) - } - - /// Creates a `DirectoryIndex` with the provided `root_dir` and `path` - /// (HTTP Request URI) - fn index_directory( - root_dir: PathBuf, - path: PathBuf, - query_params: Option, - ) -> Result { - let breadcrumbs = FileServer::breadcrumbs_from_path(&root_dir, &path)?; - let entries = read_dir(path).context("Unable to read directory")?; - let mut directory_entries: Vec = Vec::new(); - - for entry in entries { - let entry = entry.context("Unable to read entry")?; - let metadata = entry.metadata()?; - let date_created = if let Ok(time) = metadata.created() { - Some(time.into()) - } else { - None - }; - let date_modified = if let Ok(time) = metadata.modified() { - Some(time.into()) - } else { - None - }; - - directory_entries.push(DirectoryEntry { - display_name: entry - .file_name() - .to_str() - .context("Unable to gather file name into a String")? - .to_string(), - is_dir: metadata.is_dir(), - size_bytes: metadata.len(), - entry_path: FileServer::make_dir_entry_link(&root_dir, &entry.path()), - date_created, - date_modified, - }); - } - - if let Some(query_params) = query_params { - if let Some(sort_by) = query_params.sort_by { - match sort_by { - SortBy::Name => { - directory_entries.sort_by_key(|entry| entry.display_name.clone()); - } - SortBy::Size => directory_entries.sort_by_key(|entry| entry.size_bytes), - SortBy::DateCreated => { - directory_entries.sort_by_key(|entry| entry.date_created) - } - SortBy::DateModified => { - directory_entries.sort_by_key(|entry| entry.date_modified) - } - }; - - let sort_enum = match sort_by { - SortBy::Name => Sort::Name, - SortBy::Size => Sort::Size, - SortBy::DateCreated => Sort::DateCreated, - SortBy::DateModified => Sort::DateModified, - }; - - return Ok(DirectoryIndex { - entries: directory_entries, - breadcrumbs, - sort: sort_enum, - }); - } - } - - directory_entries.sort(); - - Ok(DirectoryIndex { - entries: directory_entries, - breadcrumbs, - sort: Sort::Directory, - }) - } - - /// Creates entry's relative path. Used by Handlebars template engine to - /// provide navigation through `FileExplorer` - /// - /// If the root_dir is: `https-server/src` - /// The entry path is: `https-server/src/server/service/file_explorer.rs` - /// - /// Then the resulting path from this function is the absolute path to - /// the "entry path" in relation to the "root_dir" path. - /// - /// This happens because links should behave relative to the `/` path - /// which in this case is `http-server/src` instead of system's root path. - fn make_dir_entry_link(root_dir: &Path, entry_path: &Path) -> String { - let path = entry_path.strip_prefix(root_dir).unwrap(); - - encode_uri(path) - } -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - use std::str::FromStr; - use std::vec; - - use super::{BreadcrumbItem, FileServer}; - - #[test] - fn makes_dir_entry_link() { - let root_dir = PathBuf::from_str("/Users/bob/sources/http-server").unwrap(); - let entry_path = - PathBuf::from_str("/Users/bob/sources/http-server/src/server/service/testing stuff/filename with spaces.txt") - .unwrap(); - - assert_eq!( - "/src/server/service/testing%20stuff/filename%20with%20spaces.txt", - FileServer::make_dir_entry_link(&root_dir, &entry_path) - ); - } - - #[test] - fn parse_req_uri_path() { - let have = [ - "/index.html", - "/index.html?foo=1234", - "/foo/index.html?bar=baz", - "/foo/bar/baz.html?day=6&month=27&year=2021", - ]; - - let want = [ - "/index.html", - "/index.html", - "/foo/index.html", - "/foo/bar/baz.html", - ]; - - for (idx, req_uri) in have.iter().enumerate() { - let sanitized_path = FileServer::parse_path(req_uri).unwrap().0; - let wanted_path = PathBuf::from_str(want[idx]).unwrap(); - - assert_eq!(sanitized_path, wanted_path); - } - } - - #[test] - fn breadcrumbs_from_paths() { - let root_dir = PathBuf::from_str("/Users/bob/sources/http-server").unwrap(); - let entry_path = - PathBuf::from_str("/Users/bob/sources/http-server/src/server/service/testing stuff/filename with spaces.txt") - .unwrap(); - let breadcrumbs = FileServer::breadcrumbs_from_path(&root_dir, &entry_path).unwrap(); - let expect = vec![ - BreadcrumbItem { - entry_name: String::from("http-server"), - entry_link: String::from("/"), - }, - BreadcrumbItem { - entry_name: String::from("src"), - entry_link: String::from("/src"), - }, - BreadcrumbItem { - entry_name: String::from("server"), - entry_link: String::from("/src/server"), - }, - BreadcrumbItem { - entry_name: String::from("service"), - entry_link: String::from("/src/server/service"), - }, - BreadcrumbItem { - entry_name: String::from("testing stuff"), - entry_link: String::from("/src/server/service/testing%20stuff"), - }, - BreadcrumbItem { - entry_name: String::from("filename with spaces.txt"), - entry_link: String::from( - "/src/server/service/testing%20stuff/filename%20with%20spaces.txt", - ), - }, - ]; - - assert_eq!(breadcrumbs, expect); - } -} diff --git a/src/old/addon/file_server/query_params.rs b/src/old/addon/file_server/query_params.rs deleted file mode 100644 index c548b1a4..00000000 --- a/src/old/addon/file_server/query_params.rs +++ /dev/null @@ -1,78 +0,0 @@ -use anyhow::Error; -use serde::Serialize; -use std::str::FromStr; - -#[derive(Debug, Eq, PartialEq, Serialize)] -pub enum SortBy { - Name, - Size, - DateCreated, - DateModified, -} - -impl FromStr for SortBy { - type Err = Error; - - fn from_str(s: &str) -> Result { - let lower = s.to_ascii_lowercase(); - let lower = lower.as_str(); - - match lower { - "name" => Ok(Self::Name), - "size" => Ok(Self::Size), - "date_created" => Ok(Self::DateCreated), - "date_modified" => Ok(Self::DateModified), - _ => Err(Error::msg("Value doesnt correspond")), - } - } -} - -#[derive(Debug, Default, PartialEq, Eq)] -pub struct QueryParams { - pub(crate) sort_by: Option, -} - -impl FromStr for QueryParams { - type Err = Error; - - fn from_str(s: &str) -> Result { - let mut query_params = QueryParams::default(); - - for set in s.split('&') { - let mut it = set.split('=').take(2); - - if let (Some(key), Some(value)) = (it.next(), it.next()) { - match key { - "sort_by" => { - if let Ok(sort_value) = SortBy::from_str(value) { - query_params.sort_by = Some(sort_value); - } - } - _ => continue, - } - } - - continue; - } - - Ok(query_params) - } -} - -#[cfg(test)] -mod tests { - use std::str::FromStr; - - use super::{QueryParams, SortBy}; - - #[test] - fn builds_query_params_from_str() { - let uri_string = "sort_by=name"; - let have = QueryParams::from_str(uri_string).unwrap(); - let expect = QueryParams { - sort_by: Some(SortBy::Name), - }; - - assert_eq!(have, expect); - } -} diff --git a/src/old/addon/file_server/scoped_file_system.rs b/src/old/addon/file_server/scoped_file_system.rs deleted file mode 100644 index d1145148..00000000 --- a/src/old/addon/file_server/scoped_file_system.rs +++ /dev/null @@ -1,238 +0,0 @@ -//! FileSystem wrapper to navigate safely around the children of a root -//! directory. -//! -//! The `ScopedFileSystem` provides read capabilities on a single directory -//! and its children, either files and directories will be accessed by this -//! `ScopedFileSystem` instance. -//! -//! The `Entry` is a wrapper on OS file system entries such as `File` and -//! `Directory`. Both `File` and `Directory` are primitive types for -//! `ScopedFileSystem` -use anyhow::Result; -use std::path::{Component, Path, PathBuf}; -use tokio::fs::OpenOptions; - -use super::file::File; - -/// The file is being opened or created for a backup or restore operation. -/// The system ensures that the calling process overrides file security -/// checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME privileges. -/// For more information, see Changing Privileges in a Token. -/// You must set this flag to obtain a handle to a directory. -/// A directory handle can be passed to some functions instead of a file handle. -/// -/// Refer: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea -#[cfg(target_os = "windows")] -const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x02000000; - -/// Representation of a OS ScopedFileSystem directory providing the path -/// (`PathBuf`) -#[derive(Debug)] -pub struct Directory { - path: PathBuf, -} - -impl Directory { - pub fn path(&self) -> PathBuf { - self.path.clone() - } -} - -/// Any OS filesystem entry recognized by `ScopedFileSystem` is treated as a -/// `Entry` both `File` and `Directory` are possible values with full support by -/// `ScopedFileSystem` -#[derive(Debug)] -pub enum Entry { - File(Box), - Directory(Directory), -} - -/// `ScopedFileSystem` to resolve and serve static files relative to an specific -/// file system directory -#[derive(Clone)] -pub struct ScopedFileSystem { - pub root: PathBuf, -} - -impl ScopedFileSystem { - /// Creates a new instance of `ScopedFileSystem` using the provided PathBuf - /// as the root directory to serve files from. - /// - /// Provided paths will resolve relartive to the provided `root` directory. - pub fn new(root: PathBuf) -> Result { - Ok(ScopedFileSystem { root }) - } - - /// Resolves the provided path against the root directory of this - /// `ScopedFileSystem` instance. - /// - /// A relative path is built using `build_relative_path` and then is opened - /// to retrieve a `Entry`. - pub async fn resolve(&self, path: PathBuf) -> std::io::Result { - let entry_path = self.build_relative_path(path); - - ScopedFileSystem::open(entry_path).await - } - - /// Builds a path relative to `ScopedFileSystem`'s `root` path with the - /// provided path. - fn build_relative_path(&self, path: PathBuf) -> PathBuf { - let mut root = self.root.clone(); - - root.extend(&ScopedFileSystem::normalize_path(&path)); - - root - } - - /// Normalizes paths - /// - /// ```ignore - /// docs/collegue/cs50/lectures/../code/voting_excecise - /// ``` - /// - /// Will be normalized to be: - /// - /// ```ignore - /// docs/collegue/cs50/code/voting_excecise - /// ``` - fn normalize_path(path: &Path) -> PathBuf { - path.components() - .fold(PathBuf::new(), |mut result, p| match p { - Component::ParentDir => { - result.pop(); - result - } - Component::Normal(os_string) => { - result.push(os_string); - result - } - _ => result, - }) - } - - #[cfg(not(target_os = "windows"))] - async fn open(path: PathBuf) -> std::io::Result { - let mut open_options = OpenOptions::new(); - let entry_path: PathBuf = path.clone(); - let file = open_options.read(true).open(path).await?; - let metadata = file.metadata().await?; - - if metadata.is_dir() { - return Ok(Entry::Directory(Directory { path: entry_path })); - } - - Ok(Entry::File(Box::new(File::new(entry_path, file, metadata)))) - } - - #[cfg(target_os = "windows")] - async fn open(path: PathBuf) -> std::io::Result { - let mut open_options = OpenOptions::new(); - let entry_path: PathBuf = path.clone(); - let file = open_options - .read(true) - .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) - .open(path) - .await?; - let metadata = file.metadata().await?; - - if metadata.is_dir() { - return Ok(Entry::Directory(Directory { path: entry_path })); - } - - Ok(Entry::File(Box::new(File::new(entry_path, file, metadata)))) - } -} - -#[cfg(test)] -mod tests { - #[allow(unused_imports)] - use super::*; - - #[test] - fn builds_a_relative_path_to_root_from_provided_path() { - let sfs = ScopedFileSystem::new(PathBuf::from("")).unwrap(); - let mut root_path = sfs.root.clone(); - let file_path = PathBuf::from(".github/ISSUE_TEMPLATE/feature-request.md"); - - root_path.push(file_path); - - let resolved_path = - sfs.build_relative_path(PathBuf::from(".github/ISSUE_TEMPLATE/feature-request.md")); - - assert_eq!(root_path, resolved_path); - } - - #[test] - fn builds_a_relative_path_to_root_from_forward_slash() { - let sfs = ScopedFileSystem::new(PathBuf::from("")).unwrap(); - let resolved_path = sfs.build_relative_path(PathBuf::from("/")); - - assert_eq!(sfs.root, resolved_path); - } - - #[test] - fn builds_a_relative_path_to_root_from_backwards() { - let sfs = ScopedFileSystem::new(PathBuf::from("")).unwrap(); - let resolved_path = sfs.build_relative_path(PathBuf::from("../../")); - - assert_eq!(sfs.root, resolved_path); - } - - #[test] - fn builds_an_invalid_path_if_an_arbitrary_path_is_provided() { - let sfs = ScopedFileSystem::new(PathBuf::from("")).unwrap(); - let resolved_path = - sfs.build_relative_path(PathBuf::from("unexistent_dir/confidential/recipe.txt")); - - assert_ne!(sfs.root, resolved_path); - } - - #[test] - fn normalizes_an_arbitrary_path() { - let arbitrary_path = PathBuf::from("docs/collegue/cs50/lectures/../code/voting_excecise"); - let normalized = ScopedFileSystem::normalize_path(&arbitrary_path); - - assert_eq!( - normalized.to_str().unwrap(), - "docs/collegue/cs50/code/voting_excecise" - ); - } - - #[tokio::test] - async fn resolves_a_file() { - let sfs = ScopedFileSystem::new(PathBuf::from("")).unwrap(); - let resolved_entry = sfs.resolve(PathBuf::from("assets/logo.svg")).await.unwrap(); - - if let Entry::File(file) = resolved_entry { - assert!(file.metadata.is_file()); - } else { - panic!("Found a directory instead of a file in the provied path"); - } - } - - #[tokio::test] - async fn detect_directory_paths() { - let sfs = ScopedFileSystem::new(PathBuf::from("")).unwrap(); - let resolved_entry = sfs.resolve(PathBuf::from("assets/")).await.unwrap(); - - assert!(matches!(resolved_entry, Entry::Directory(_))); - } - - #[tokio::test] - async fn detect_directory_paths_without_postfixed_slash() { - let sfs = ScopedFileSystem::new(PathBuf::from("")).unwrap(); - let resolved_entry = sfs.resolve(PathBuf::from("assets")).await.unwrap(); - - assert!(matches!(resolved_entry, Entry::Directory(_))); - } - - #[tokio::test] - async fn returns_error_if_file_doesnt_exists() { - let sfs = ScopedFileSystem::new(PathBuf::from("")).unwrap(); - let resolved_entry = sfs - .resolve(PathBuf::from("assets/unexistent_file.doc")) - .await; - - assert!(resolved_entry.is_err()); - } -} diff --git a/src/old/addon/file_server/template/error.hbs b/src/old/addon/file_server/template/error.hbs deleted file mode 100644 index e5e340ed..00000000 --- a/src/old/addon/file_server/template/error.hbs +++ /dev/null @@ -1,4 +0,0 @@ -
-

{{code}}

-

{{error}}

-
\ No newline at end of file diff --git a/src/old/addon/file_server/template/explorer.hbs b/src/old/addon/file_server/template/explorer.hbs deleted file mode 100644 index 75c7939d..00000000 --- a/src/old/addon/file_server/template/explorer.hbs +++ /dev/null @@ -1,249 +0,0 @@ - - - - - File Explorer - - - - -
- -
- -
-
- - diff --git a/src/old/addon/logger.rs b/src/old/addon/logger.rs deleted file mode 100644 index 62865880..00000000 --- a/src/old/addon/logger.rs +++ /dev/null @@ -1,93 +0,0 @@ -use anyhow::Result; -use chrono::{DateTime, Utc}; -use http::header::USER_AGENT; -use http::Method; -use hyper::body::Bytes; -use std::io::Write; -use termcolor::{BufferWriter, Color, ColorChoice, ColorSpec, WriteColor}; - -use crate::server::middleware::{Request, Response}; - -pub struct Logger { - buffer_writer: BufferWriter, -} - -impl Logger { - pub fn new() -> Self { - let buffer_writer = BufferWriter::stdout(ColorChoice::Always); - - Logger { buffer_writer } - } - - pub async fn log(&mut self, request: Request, response: Response) -> Result<()> { - let mut buffer = self.buffer_writer.buffer(); - let request = request.lock().await; - let response = response.lock().await; - - // UTC Time - let moment: DateTime = Utc::now(); - write!(&mut buffer, "[{:?}] \"", moment)?; - - // HTTP Request Method - let method = request.method(); - - match *method { - Method::GET => buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?, - Method::POST | Method::PUT | Method::PATCH => { - buffer.set_color(ColorSpec::new().set_fg(Some(Color::Blue)))? - } - Method::DELETE => buffer.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?, - _ => buffer.set_color(ColorSpec::new().set_fg(Some(Color::Magenta)))?, - }; - - write!(&mut buffer, "{} ", method)?; - buffer.reset()?; - - // HTTP Request URI and Version - write!(&mut buffer, "{} {:?} ", request.uri(), request.version())?; - - // HTTP Response Status Code - match response.status().as_u16() { - 100..=199 => { - // Informational Responses - buffer.set_color(ColorSpec::new().set_fg(Some(Color::Blue)))?; - } - 200..=299 => { - // Successful responses - buffer.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?; - } - 300..=399 => { - // Redirection messages - buffer.set_color(ColorSpec::new().set_fg(Some(Color::Yellow)))?; - } - 400..=499 => { - // Client error responses - buffer.set_color(ColorSpec::new().set_fg(Some(Color::Rgb(255, 140, 0))))?; - } - 500..=599 => { - // Server error responses - buffer.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?; - } - _ => { - // Unknown response codes - buffer.set_color(ColorSpec::new().set_fg(Some(Color::Magenta)))?; - } - } - write!(&mut buffer, "{}", response.status())?; - buffer.reset()?; - - // HTTP Request User Agent - let user_agent = if let Some(value) = request.headers().get(USER_AGENT) { - value.to_str()? - } else { - "N/A" - }; - write!(&mut buffer, "\" \"{}\" ", user_agent)?; - - writeln!(&mut buffer)?; - self.buffer_writer.print(&buffer)?; - buffer.reset()?; - - Ok(()) - } -} diff --git a/src/old/addon/mod.rs b/src/old/addon/mod.rs deleted file mode 100644 index 7aca7a31..00000000 --- a/src/old/addon/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod compression; -pub mod cors; -pub mod file_server; -pub mod logger; -// pub mod proxy; diff --git a/src/old/addon/proxy.rs b/src/old/addon/proxy.rs deleted file mode 100644 index fb841692..00000000 --- a/src/old/addon/proxy.rs +++ /dev/null @@ -1,280 +0,0 @@ -use std::str::FromStr; -use std::sync::Arc; - -use http::header::USER_AGENT; -use http::header::{HeaderName, HeaderValue}; -use hyper::client::HttpConnector; -use hyper::{Body, Client, Response, Uri}; -use hyper_rustls::{HttpsConnector, HttpsConnectorBuilder}; - -use crate::server::middleware::Request; - -pub struct Proxy { - client: Client>, - upstream: Uri, -} - -impl Proxy { - pub fn new(upstream: &str) -> Self { - let https_connector = HttpsConnectorBuilder::new() - .with_webpki_roots() - .https_or_http() - .enable_http1() - .build(); - let client = Client::builder().build::<_, hyper::Body>(https_connector); - let upstream = Uri::from_str(upstream).unwrap(); - - Proxy { client, upstream } - } - - pub async fn handle(&self, request: Request) -> Response { - self.remove_hbh_headers(Arc::clone(&request)).await; - self.add_via_header(Arc::clone(&request)).await; - - let mut outogoing = self.map_incoming_request(Arc::clone(&request)).await; - let outgoing_headers = outogoing.headers_mut(); - - // Host must be the authority from the proxied URL - outgoing_headers.remove(http::header::HOST); - outgoing_headers.append( - http::header::HOST, - HeaderValue::from_str(self.upstream.authority().unwrap().as_str()).unwrap(), - ); - - let client = self.client.clone(); - - tokio::spawn(async move { client.request(outogoing).await.unwrap() }) - .await - .unwrap() - } - - /// Creates a `Via` HTTP header for the provided HTTP Request. - /// - /// The Via general header is added by proxies, both forward and reverse, and - /// can appear in the request or response headers. It is used for tracking - /// message forwards, avoiding request loops, and identifying the protocol - /// capabilities of senders along the request/response chain. - /// - /// Via: [ "/" ] [ ":" ] - /// - /// ## References - /// - /// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Via - async fn add_via_header(&self, request: Request) { - let mut request = request.lock().await; - let via_header_str = format!("{:?} Rust http-server", request.version()); - let via_header = HeaderValue::from_str(&via_header_str).unwrap(); - - if let Some(current_via_header) = request.headers().get("via") { - let current_via_header = current_via_header.to_str().unwrap(); - - if current_via_header.contains(&via_header_str) { - return; - } - - let mut via_set = current_via_header.split(',').collect::>(); - - via_set.push(&via_header_str); - - let proxies_list = via_set.join(", "); - - request.headers_mut().remove(HeaderName::from_static("via")); - request.headers_mut().append( - HeaderName::from_static("via"), - HeaderValue::from_str(proxies_list.as_str()).unwrap(), - ); - return; - } - - request - .headers_mut() - .append(HeaderName::from_static("via"), via_header); - } - - /// Removes Hop-by-Hop headers which are only meaningful for a singles - /// transport-level connection and should not be stored by caches following - /// RFC2616. - /// - /// The following HTTP/1.1 headers are hop-by-hop headers: - /// - /// - Connection - /// - Keep-Alive - /// - Proxy-Authenticate - /// - Proxy-Authorization - /// - TE - /// - Trailers - /// - Transfer-Encoding - /// - Upgrade - /// - /// ## References - /// - /// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html - async fn remove_hbh_headers(&self, request: Request) { - use http::header::{ - CONNECTION, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, TE, TRAILER, TRANSFER_ENCODING, - UPGRADE, - }; - - let mut request = request.lock().await; - let headers = request.headers_mut(); - - headers.remove(CONNECTION); - headers.remove(HeaderName::from_static("keep-alive")); - headers.remove(PROXY_AUTHENTICATE); - headers.remove(PROXY_AUTHORIZATION); - headers.remove(TE); - headers.remove(TRAILER); - headers.remove(TRANSFER_ENCODING); - headers.remove(UPGRADE); - } - - /// Maps a _incoming_ HTTP request into a _outgoing_ HTTP request. - async fn map_incoming_request(&self, incoming: Request) -> hyper::Request { - let incoming = incoming.lock().await; - let upstream_uri = self.map_upstream_uri(incoming.uri()); - let mut request = hyper::Request::builder() - .uri(upstream_uri) - .method(incoming.method()) - .body(Body::empty()) - .unwrap(); - let headers = request.headers_mut(); - - *headers = incoming.headers().clone(); - - // TODO: Instead of append and removing it would be great to support - // some kind of `set` operation which adds if not present or replaces - // if present. - // - // Host must be the authority from the proxied URL - headers.remove(http::header::HOST); - headers.append( - http::header::HOST, - HeaderValue::from_str(self.upstream.authority().unwrap().as_str()).unwrap(), - ); - - // Specify Proxy as User Agent - headers.remove(USER_AGENT).unwrap(); - headers.append(USER_AGENT, HeaderValue::from_static("Rust http-server/1.0")); - - request - } - - fn map_upstream_uri(&self, incoming_uri: &Uri) -> Uri { - let scheme = self.upstream.scheme_str().unwrap(); - let authority = self.upstream.authority().unwrap().as_str(); - let path_and_query = if let Some(paq) = incoming_uri.path_and_query() { - paq.as_str() - } else { - "" - }; - - Uri::builder() - .scheme(scheme) - .authority(authority) - .path_and_query(path_and_query) - .build() - .unwrap() - } -} - -#[cfg(test)] -mod tests { - use http::header::{HeaderName, HeaderValue}; - use http::header::{ - CONNECTION, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, TE, TRAILER, TRANSFER_ENCODING, - UPGRADE, - }; - use hyper::Body; - use std::sync::Arc; - use tokio::sync::Mutex; - - use super::Proxy; - - #[tokio::test] - async fn adds_via_header_if_not_present() { - let proxy = Proxy::new("https://example.com"); - let request = http::Request::new(Body::empty()); - let request = Arc::new(Mutex::new(request)); - - proxy.add_via_header(Arc::clone(&request)).await; - - let request = request.lock().await; - let headers = request.headers(); - - assert!(headers.get(&HeaderName::from_static("via")).is_some()); - - let via_header_value = headers.get(&HeaderName::from_static("via")).unwrap(); - let via_header_value = via_header_value.to_str().unwrap(); - - assert_eq!(via_header_value, "HTTP/1.1 Rust http-server"); - } - - #[tokio::test] - async fn appends_via_header_if_another_is_present() { - let proxy = Proxy::new("https://example.com"); - let mut request = http::Request::new(Body::empty()); - let headers = request.headers_mut(); - - headers.append( - &HeaderName::from_static("via"), - HeaderValue::from_str("HTTP/1.1 GoodProxy").unwrap(), - ); - - let request = Arc::new(Mutex::new(request)); - - proxy.add_via_header(Arc::clone(&request)).await; - - let request = request.lock().await; - let headers = request.headers(); - - assert!(headers.get(&HeaderName::from_static("via")).is_some()); - - let via_header_value = headers.get(&HeaderName::from_static("via")).unwrap(); - let via_header_value = via_header_value.to_str().unwrap(); - - assert_eq!( - via_header_value, - "HTTP/1.1 GoodProxy, HTTP/1.1 Rust http-server" - ); - } - - #[tokio::test] - async fn removes_hbh_headers() { - let proxy = Proxy::new("https://example.com"); - let mut request = http::Request::new(Body::empty()); - let headers = request.headers_mut(); - let headers_to_add = vec![ - (CONNECTION, HeaderValue::from_str("keep-alive").unwrap()), - ( - PROXY_AUTHENTICATE, - HeaderValue::from_static(r#"Basic realm="Access to the internal site""#), - ), - ( - PROXY_AUTHORIZATION, - HeaderValue::from_static("Basic YWxhZGRpbjpvcGVuc2VzYW1l"), - ), - (TE, HeaderValue::from_static("compress")), - (TRAILER, HeaderValue::from_static("Expires")), - (TRANSFER_ENCODING, HeaderValue::from_static("chunked")), - (UPGRADE, HeaderValue::from_static("example/1, foo/2")), - ]; - - for (name, value) in headers_to_add { - headers.append(name, value); - } - - let request = Arc::new(Mutex::new(request)); - - proxy.remove_hbh_headers(Arc::clone(&request)).await; - - let request = request.lock().await; - - assert!(!request.headers().contains_key(CONNECTION)); - assert!(!request.headers().contains_key(PROXY_AUTHENTICATE)); - assert!(!request.headers().contains_key(PROXY_AUTHORIZATION)); - assert!(!request.headers().contains_key(TE)); - assert!(!request.headers().contains_key(TRAILER)); - assert!(!request.headers().contains_key(TRANSFER_ENCODING)); - assert!(!request.headers().contains_key(UPGRADE)); - } -} diff --git a/src/old/server/handler/file_server.rs b/src/old/server/handler/file_server.rs deleted file mode 100644 index ac792f26..00000000 --- a/src/old/server/handler/file_server.rs +++ /dev/null @@ -1,45 +0,0 @@ -use async_trait::async_trait; -use http::response::Builder as HttpResponseBuilder; -use http::{Request, StatusCode}; -use hyper::Method; -use hyper::body::Bytes; -use std::sync::Arc; -use tokio::sync::Mutex; - -use crate::addon::file_server::FileServer; - -use super::RequestHandler; - -pub struct FileServerHandler { - file_server: Arc, -} - -impl FileServerHandler { - pub fn new(file_server: FileServer) -> Self { - let file_server = Arc::new(file_server); - - FileServerHandler { file_server } - } -} - -#[async_trait] -impl RequestHandler for FileServerHandler { - async fn handle(&self, req: Arc>>) -> Arc>> { - let request_lock = req.lock().await; - let req_path = request_lock.uri().to_string(); - let req_method = request_lock.method(); - - if req_method == Method::GET { - let response = self.file_server.resolve(req_path).await.unwrap(); - - return Arc::new(Mutex::new(response)); - } - - Arc::new(Mutex::new( - HttpResponseBuilder::new() - .status(StatusCode::METHOD_NOT_ALLOWED) - .body(Bytes::empty()) - .expect("Unable to build response"), - )) - } -} diff --git a/src/old/server/handler/mod.rs b/src/old/server/handler/mod.rs deleted file mode 100644 index b4babe29..00000000 --- a/src/old/server/handler/mod.rs +++ /dev/null @@ -1,68 +0,0 @@ -mod file_server; -// mod proxy; - -use anyhow::Result; -use async_trait::async_trait; -use http::{Request, Response}; -use hyper::body::Bytes; -use std::convert::TryFrom; -use std::sync::Arc; -use tokio::sync::Mutex; - -use crate::addon::file_server::FileServer; -// use crate::addon::proxy::Proxy; -use crate::Config; - -use super::middleware::Middleware; - -use self::file_server::FileServerHandler; -// use self::proxy::ProxyHandler; - -/// A trait to implement on addons in order to handle incomming requests and -/// generate responses. -#[async_trait] -pub trait RequestHandler { - async fn handle(&self, req: Arc>>) -> Arc>>; -} - -#[derive(Clone)] -pub struct HttpHandler { - request_handler: Arc, - middleware: Arc, -} - -impl HttpHandler { - pub async fn handle_request(self, request: Request) -> Result> { - let handler = Arc::clone(&self.request_handler); - let middleware = Arc::clone(&self.middleware); - let response = middleware.handle(request, handler).await; - - Ok(response) - } -} - -impl From> for HttpHandler { - fn from(config: Arc) -> Self { - // if let Some(proxy_config) = config.proxy.clone() { - // let proxy = Proxy::new(&proxy_config.url); - // let request_handler = Arc::new(ProxyHandler::new(proxy)); - // let middleware = Middleware::try_from(config).unwrap(); - // let middleware = Arc::new(middleware); - - // return HttpHandler { - // request_handler, - // middleware, - // }; - // } - - let file_server = FileServer::new(config.clone()); - let request_handler = Arc::new(FileServerHandler::new(file_server)); - let middleware = Middleware::try_from(config).unwrap(); - let middleware = Arc::new(middleware); - - HttpHandler { - request_handler, - middleware, - } - } -} diff --git a/src/old/server/handler/proxy.rs b/src/old/server/handler/proxy.rs deleted file mode 100644 index a79c945a..00000000 --- a/src/old/server/handler/proxy.rs +++ /dev/null @@ -1,31 +0,0 @@ -use async_trait::async_trait; -use hyper::{body::Bytes, Request}; -use std::sync::Arc; -use tokio::sync::Mutex; - -use crate::addon::proxy::Proxy; - -use super::RequestHandler; - -pub struct ProxyHandler { - proxy: Arc, -} - -impl ProxyHandler { - pub fn new(proxy: Proxy) -> Self { - let proxy = Arc::new(proxy); - - ProxyHandler { proxy } - } -} - -#[async_trait] -impl RequestHandler for ProxyHandler { - async fn handle(&self, req: Arc>>) -> Arc>> { - let proxy = Arc::clone(&self.proxy); - let request = Arc::clone(&req); - let response = proxy.handle(request).await; - - Arc::new(Mutex::new(response)) - } -} diff --git a/src/old/server/https.rs b/src/old/server/https.rs deleted file mode 100644 index f069cf56..00000000 --- a/src/old/server/https.rs +++ /dev/null @@ -1,61 +0,0 @@ -use anyhow::Result; -use async_stream::stream; -use futures::TryFutureExt; -use hyper::server::accept::Accept; -use hyper::server::Builder; -use rustls::{Certificate, PrivateKey, ServerConfig}; -use std::io::Error; -use std::net::SocketAddr; -use std::sync::Arc; -use tokio::net::{TcpListener, TcpStream}; -use tokio_rustls::server::TlsStream; -use tokio_rustls::TlsAcceptor; - -pub struct Https { - cert: Vec, - key: PrivateKey, -} - -impl Https { - pub fn new(cert: Vec, key: PrivateKey) -> Self { - Https { cert, key } - } - - fn make_tls_cfg(&self) -> Result> { - let (certs, private_key) = (self.cert.clone(), self.key.clone()); - let config = ServerConfig::builder() - .with_safe_defaults() - .with_no_client_auth() - .with_single_cert(certs, private_key) - .map_err(anyhow::Error::new)?; - - Ok(Arc::new(config)) - } - - pub async fn make_server( - &self, - addr: SocketAddr, - ) -> Result, Error = Error>>> { - let tcp = TcpListener::bind(addr).await?; - let tls_cfg = self.make_tls_cfg()?; - let tls_acceptor = TlsAcceptor::from(tls_cfg); - - let incoming_tls_stream = stream! { - loop { - let (socket, _) = tcp.accept().await?; - let stream = tls_acceptor.accept(socket).map_err(|error| { - println!("HTTPS Error: {:?}", error); - - error - }); - - yield stream.await; - } - }; - - let acceptor = hyper::server::accept::from_stream(incoming_tls_stream); - let server = hyper::server::Server::builder(acceptor); - - Ok(server) - } -} diff --git a/src/old/server/middleware/basic_auth.rs b/src/old/server/middleware/basic_auth.rs deleted file mode 100644 index 0412fdec..00000000 --- a/src/old/server/middleware/basic_auth.rs +++ /dev/null @@ -1,50 +0,0 @@ -use http::{Request, StatusCode}; -use http_auth_basic::Credentials; -use hyper::Body; -use std::sync::Arc; -use tokio::sync::Mutex; - -use crate::config::basic_auth::BasicAuthConfig; -use crate::utils::error::make_http_error_response; - -use super::MiddlewareBefore; - -pub fn make_basic_auth_middleware(basic_auth_config: BasicAuthConfig) -> MiddlewareBefore { - let credentials = Arc::new(Credentials::new( - basic_auth_config.username.as_str(), - basic_auth_config.password.as_str(), - )); - - Box::new(move |request: Arc>>| { - let credentials = Arc::clone(&credentials); - - Box::pin(async move { - let request = request.lock().await; - - if let Some(auth_header_value) = request.headers().get(http::header::AUTHORIZATION) { - let auth_header_value = auth_header_value.to_str().map_err(|err| { - make_http_error_response(StatusCode::BAD_REQUEST, err.to_string().as_str()) - })?; - - let incoming_credentials = Credentials::from_header(auth_header_value.to_string()) - .map_err(|err| { - make_http_error_response(StatusCode::BAD_REQUEST, err.to_string().as_str()) - })?; - - if incoming_credentials == *credentials { - return Ok(()); - } - - return Err(make_http_error_response( - StatusCode::UNAUTHORIZED, - "Unauthorized", - )); - } - - Err(make_http_error_response( - StatusCode::UNAUTHORIZED, - "Unauthorized", - )) - }) - }) -} diff --git a/src/old/server/middleware/cors.rs b/src/old/server/middleware/cors.rs deleted file mode 100644 index 84d9059e..00000000 --- a/src/old/server/middleware/cors.rs +++ /dev/null @@ -1,48 +0,0 @@ -use http::{Request, Response}; -use hyper::Body; -use std::convert::TryFrom; -use std::sync::Arc; -use tokio::sync::Mutex; - -use crate::addon::cors::Cors; -use crate::config::cors::CorsConfig; - -use super::MiddlewareAfter; - -/// Creates a CORS middleware with the configuration provided and returns it. -/// The configured headers will be appended to every HTTP Response before -/// sending such response back to the client (After Middleware) -/// -/// CORS headers for every response are built on server initialization and -/// then are "appended" to Response headers on every response. -/// -/// # Panics -/// -/// Panics if a CORS config is not defined for the `Config` instance provided. -/// (`Config.cors` is `None`). -/// `make_cors_middlware` should only be called when a `CorsConfig` is defined. -/// -/// Also panics if any CORS header value is not a valid UTF-8 string -pub fn make_cors_middleware(cors_config: CorsConfig) -> MiddlewareAfter { - let cors = Cors::try_from(cors_config).unwrap(); - let cors_headers = cors.make_http_headers(); - - Box::new( - move |_: Arc>>, response: Arc>>| { - let cors_headers = cors_headers.clone(); - let response = Arc::clone(&response); - - Box::pin(async move { - let mut response = response.lock().await; - - let headers = response.headers_mut(); - - cors_headers.iter().for_each(|(header, value)| { - headers.append(header, value.to_owned()); - }); - - Ok(()) - }) - }, - ) -} diff --git a/src/old/server/middleware/gzip.rs b/src/old/server/middleware/gzip.rs deleted file mode 100644 index 6a779730..00000000 --- a/src/old/server/middleware/gzip.rs +++ /dev/null @@ -1,26 +0,0 @@ -use http::{Request, Response, StatusCode}; -use hyper::Body; -use std::sync::Arc; -use tokio::sync::Mutex; - -use crate::addon::compression::gzip::compress_http_response; -use crate::utils::error::make_http_error_response; - -use super::MiddlewareAfter; - -pub fn make_gzip_compression_middleware() -> MiddlewareAfter { - Box::new( - move |request: Arc>>, response: Arc>>| { - Box::pin(async move { - compress_http_response(request, response) - .await - .map_err(|err| { - make_http_error_response( - StatusCode::INTERNAL_SERVER_ERROR, - &err.to_string(), - ) - }) - }) - }, - ) -} diff --git a/src/old/server/middleware/logger.rs b/src/old/server/middleware/logger.rs deleted file mode 100644 index d0af1cfd..00000000 --- a/src/old/server/middleware/logger.rs +++ /dev/null @@ -1,28 +0,0 @@ -use http::{Request, Response}; -use hyper::Body; -use std::sync::Arc; -use tokio::sync::Mutex; - -use crate::addon::logger::Logger; - -use super::MiddlewareAfter; - -pub fn make_logger_middleware() -> MiddlewareAfter { - let logger = Arc::new(Mutex::new(Logger::new())); - - Box::new( - move |request: Arc>>, response: Arc>>| { - let logger = Arc::clone(&logger); - - Box::pin(async move { - let mut logger = logger.lock().await; - - if let Err(error) = logger.log(request, response).await { - eprintln!("{:#?}", error); - } - - Ok(()) - }) - }, - ) -} diff --git a/src/old/server/middleware/mod.rs b/src/old/server/middleware/mod.rs deleted file mode 100644 index 41172258..00000000 --- a/src/old/server/middleware/mod.rs +++ /dev/null @@ -1,131 +0,0 @@ -pub mod basic_auth; -pub mod cors; -pub mod gzip; -pub mod logger; - -use anyhow::Error; -use futures::Future; -use hyper::Body; -use std::convert::TryFrom; -use std::pin::Pin; -use std::sync::Arc; -use tokio::sync::Mutex; - -use super::handler::RequestHandler; -use crate::config::Config; - -use self::basic_auth::make_basic_auth_middleware; -use self::cors::make_cors_middleware; -use self::gzip::make_gzip_compression_middleware; -use self::logger::make_logger_middleware; - -/// Middleware HTTP Response which expands to a `Arc>>` -pub type Request = Arc>>; - -/// Middleware HTTP Response which expands to a `Arc>>` -pub type Response = Arc>>; - -/// Middleware chain `Result` which specifies the `Err` variant as a -/// HTTP response. -pub type Result = std::result::Result<(), http::Response>; - -/// Middleware chain to execute before the main handler digests the -/// HTTP request. No HTTP response is available at this point. -pub type MiddlewareBefore = - Box) -> Pin + Send + Sync>> + Send + Sync>; - -/// Middleware chain to execute after the main handler digests the -/// HTTP request. The HTTP response is created by the handler and -/// consumed by every middleware after chain. -pub type MiddlewareAfter = Box< - dyn Fn(Request, Response) -> Pin + Send + Sync>> - + Send - + Sync, ->; - -#[derive(Default)] -pub struct Middleware { - before: Vec, - after: Vec, -} - -impl Middleware { - /// Appends a middleware function to run before handling the - /// HTTP Request - #[allow(dead_code)] - pub fn before(&mut self, middleware: MiddlewareBefore) { - self.before.push(middleware); - } - - /// Appends a middleware function to run after handling the - /// HTTP Request. Thus, functions appended after will receive - /// the handler's HTTP Response instead - pub fn after(&mut self, middleware: MiddlewareAfter) { - self.after.push(middleware); - } - - /// Runs functions from the chain that must run before - /// executing the handler (applied to the HTTP Request). - /// Then performs the handler operations on the HTTP Request - /// and finally executes the functions on the "after" chain - /// with the HTTP Response from the handler - pub async fn handle( - &self, - request: http::Request, - handler: Arc, - ) -> http::Response { - let request = Arc::new(Mutex::new(request)); - - for fx in self.before.iter() { - if let Err(err) = fx(Arc::clone(&request)).await { - return err; - } - } - - let response = handler.handle(Arc::clone(&request)).await; - - for fx in self.after.iter() { - if let Err(err) = fx(Arc::clone(&request), Arc::clone(&response)).await { - return err; - } - } - - Arc::try_unwrap(response) - .expect("There's one or more reference/s being hold by a middleware chain.") - .into_inner() - } -} - -impl TryFrom> for Middleware { - type Error = Error; - - fn try_from(config: Arc) -> std::result::Result { - let mut middleware = Middleware::default(); - - if let Some(basic_auth_config) = config.basic_auth.clone() { - let basic_auth_middleware = make_basic_auth_middleware(basic_auth_config); - - middleware.before(basic_auth_middleware); - } - - if let Some(cors_config) = config.cors.clone() { - let cors_middleware = make_cors_middleware(cors_config); - - middleware.after(cors_middleware); - } - - if let Some(compression_config) = config.compression.clone() { - if compression_config.gzip { - middleware.after(make_gzip_compression_middleware()); - } - } - - if let Some(should_log) = config.logger { - if should_log { - middleware.after(make_logger_middleware()); - } - } - - Ok(middleware) - } -} diff --git a/src/old/server/mod.rs b/src/old/server/mod.rs deleted file mode 100644 index 5b3d43bd..00000000 --- a/src/old/server/mod.rs +++ /dev/null @@ -1,159 +0,0 @@ -mod handler; -// mod https; -mod service; - -pub mod middleware; - -use anyhow::Error; -use hyper::service::{make_service_fn, service_fn}; -use std::net::{Ipv4Addr, SocketAddr}; -use std::process::exit; -use std::str::FromStr; -use std::sync::Arc; - -use crate::config::tls::TlsConfig; -use crate::config::Config; - -pub struct Server { - config: Arc, -} - -impl Server { - pub fn new(config: Config) -> Server { - let config = Arc::new(config); - - Server { config } - } - - pub async fn run(self) { - let config = Arc::clone(&self.config); - let address = config.address; - let handler = handler::HttpHandler::from(Arc::clone(&config)); - let server = Arc::new(self); - let mut server_instances: Vec> = Vec::new(); - - if config.spa { - let mut index_html = config.root_dir.clone(); - index_html.push("index.html"); - - if !index_html.exists() { - eprintln!( - "SPA flag is enabled, but index.html in root does not exist. Quitting..." - ); - exit(1); - } - } - - if config.tls.is_some() { - let https_config = config.tls.clone().unwrap(); - let handler = handler.clone(); - let host = config.address.ip(); - let port = config.address.port().saturating_add(1); - let address = SocketAddr::new(host, port); - let server = Arc::clone(&server); - let task = tokio::spawn(async move { - let server = Arc::clone(&server); - - server.serve_https(address, handler, https_config).await; - }); - - server_instances.push(task); - } - - let server = Arc::clone(&server); - let task = tokio::spawn(async move { - let server = Arc::clone(&server); - - server.serve(address, handler).await; - }); - - server_instances.push(task); - - for server_task in server_instances { - server_task.await.unwrap(); - } - } - - pub async fn serve(&self, address: SocketAddr, handler: handler::HttpHandler) { - let server = hyper::Server::bind(&address).serve(make_service_fn(|_| { - // Move a clone of `handler` into the `service_fn`. - let handler = handler.clone(); - - async { - Ok::<_, Error>(service_fn(move |req| { - service::main_service(handler.to_owned(), req) - })) - } - })); - - if !self.config.quiet { - println!("Serving HTTP: http://{}", address); - - if self.config.address.ip() == Ipv4Addr::from_str("0.0.0.0").unwrap() { - if let Ok(ip) = local_ip_address::local_ip() { - println!("Local Network IP: http://{}:{}", ip, self.config.port); - } - } - } - - if self.config.graceful_shutdown { - let graceful = server.with_graceful_shutdown(crate::utils::signal::shutdown_signal()); - - if let Err(e) = graceful.await { - eprint!("Server Error: {}", e); - } - - return; - } - - if let Err(e) = server.await { - eprint!("Server Error: {}", e); - } - } - - pub async fn serve_https( - &self, - address: SocketAddr, - handler: handler::HttpHandler, - https_config: TlsConfig, - ) { - // let (cert, key) = https_config.parts(); - // let https_server_builder = https::Https::new(cert, key); - // let server = https_server_builder.make_server(address).await.unwrap(); - // let server = server.serve(make_service_fn(|_| { - // // Move a clone of `handler` into the `service_fn`. - // let handler = handler.clone(); - - // async { - // Ok::<_, Error>(service_fn(move |req| { - // service::main_service(handler.to_owned(), req) - // })) - // } - // })); - - // if !self.config.quiet { - // println!("Serving HTTPS: http://{}", address); - - // if self.config.address.ip() == Ipv4Addr::from_str("0.0.0.0").unwrap() { - // if let Ok(ip) = local_ip_address::local_ip() { - // println!("Local Network IP: https://{}:{}", ip, self.config.port); - // } - // } - // } - - // if self.config.graceful_shutdown { - // let graceful = server.with_graceful_shutdown(crate::utils::signal::shutdown_signal()); - - // if let Err(e) = graceful.await { - // eprint!("Server Error: {}", e); - // } - - // return; - // } - - // if let Err(e) = server.await { - // eprint!("Server Error: {}", e); - // } - todo!() - } -} diff --git a/src/old/server/service.rs b/src/old/server/service.rs deleted file mode 100644 index b894069a..00000000 --- a/src/old/server/service.rs +++ /dev/null @@ -1,9 +0,0 @@ -use anyhow::Result; -use http::{Request, Response}; -use hyper::Body; - -use super::handler::HttpHandler; - -pub async fn main_service(handler: HttpHandler, req: Request) -> Result> { - handler.handle_request(req).await -} diff --git a/src/server/handler.rs b/src/server/handler.rs index 4cae614d..21dd1517 100644 --- a/src/server/handler.rs +++ b/src/server/handler.rs @@ -1,14 +1,11 @@ +use std::future::Future; use std::pin::Pin; -use std::{future::Future, sync::Arc}; use http_body_util::Full; -use hyper::{body::Bytes, service::Service}; - -use crate::config::Config; -use crate::middleware::basic_auth::make_basic_auth_middleware; +use hyper::body::Bytes; +use hyper::service::Service; use super::{HttpRequest, HttpResponse}; -use super::middleware::Middleware; /// Http Request Handler for the Server. /// @@ -16,51 +13,18 @@ use super::middleware::Middleware; /// and returning an HTTP Response. Every request is passed through /// a series of middleware functions before and after handling the /// request. -pub struct Handler { - config: Config, - middleware: Arc, -} - -impl From for Handler { - fn from(config: Config) -> Self { - let mut middleware = Middleware::new(); - - if let Some(basic_auth) = &config.basic_auth { - middleware.before(make_basic_auth_middleware(basic_auth)); - } - - Handler { config, middleware: Arc::new(middleware) } - } -} +pub struct Handler {} impl Service for Handler { type Response = HttpResponse; type Error = hyper::Error; type Future = Pin> + Send>>; - fn call(&self, request: HttpRequest) -> Self::Future { - let middleware = Arc::clone(&self.middleware); - + fn call(&self, _: HttpRequest) -> Self::Future { Box::pin(async move { - match middleware.handle_before(request).await { - Ok(_request) => { - let response = hyper::Response::builder() - .body(Full::new(Bytes::from("Hello, World!"))) - .unwrap(); - - match middleware.handle_after(response).await { - Ok(response) => { - Ok(response) - }, - Err(response) => { - Ok(response) - } - } - }, - Err(response) => { - Ok(response) - } - } + Ok(hyper::Response::builder() + .body(Full::new(Bytes::from("Hello, World!"))) + .unwrap()) }) } } diff --git a/src/server/middleware.rs b/src/server/middleware.rs deleted file mode 100644 index 7d1a7f84..00000000 --- a/src/server/middleware.rs +++ /dev/null @@ -1,69 +0,0 @@ -use std::pin::Pin; - -use futures::Future; - -use super::{HttpRequest, HttpResponse}; - -pub type MiddlewareBefore = Box< - dyn Fn(HttpRequest) -> Pin> + Send + Sync>> - + Send - + Sync, ->; - -pub type MiddlewareAfter = Box< - dyn Fn( - HttpResponse, - ) -> Pin> + Send + Sync>> - + Send - + Sync, ->; - -#[derive(Default)] -pub struct Middleware { - before: Vec, - after: Vec, -} - -impl Middleware { - pub fn new() -> Self { - Middleware::default() - } - - pub fn before(&mut self, middleware: MiddlewareBefore) { - self.before.push(middleware); - } - - pub fn after(&mut self, middleware: MiddlewareAfter) { - self.after.push(middleware); - } - - pub async fn handle_before(&self, request: HttpRequest) -> Result { - let mut next = request; - - for fx in self.before.iter() { - match fx(next).await { - Ok(next_req) => next = next_req, - Err(err) => { - return Err(err); - } - } - } - - Ok(next) - } - - pub async fn handle_after(&self, response: HttpResponse) -> Result { - let mut next = response; - - for fx in self.after.iter() { - match fx(next).await { - Ok(next_res) => next = next_res, - Err(err) => { - return Err(err); - } - } - } - - Ok(next) - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index 5d67e8c4..d457f823 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,18 +1,15 @@ pub mod handler; -pub mod middleware; use std::sync::Arc; use anyhow::Result; use http_body_util::Full; -use hyper::{body::Bytes, server::conn::http1, Response}; use hyper::http::StatusCode; +use hyper::{body::Bytes, server::conn::http1, Response}; use hyper_util::rt::TokioIo; use serde::Serialize; use tokio::net::TcpListener; -use crate::config::Config; - use self::handler::Handler; pub type HttpRequest = hyper::Request; @@ -42,7 +39,6 @@ impl HttpErrorResponse { pub fn into_response(self) -> HttpResponse { let body = serde_json::ser::to_string(&self).unwrap(); - Response::builder() .status(self.status_code) @@ -54,9 +50,9 @@ impl HttpErrorResponse { pub struct Server; impl Server { - pub async fn run(config: Config) -> Result<()> { - let listener = TcpListener::bind(config.address).await?; - let handler = Arc::new(Handler::from(config)); + pub async fn run() -> Result<()> { + let listener = TcpListener::bind("127.0.0.1:7878").await?; + let handler = Arc::new(Handler {}); loop { let (stream, _) = listener.accept().await?; diff --git a/src/utils/error.rs b/src/utils/error.rs deleted file mode 100644 index 88424c0b..00000000 --- a/src/utils/error.rs +++ /dev/null @@ -1,22 +0,0 @@ -use http::{Response, StatusCode}; -use hyper::Body; -use serde::Serialize; - -#[derive(Debug, Serialize)] -struct ErrorResponseBody { - status_code: u16, - message: String, -} - -pub fn make_http_error_response(status: StatusCode, message: &str) -> Response { - Response::builder() - .status(status) - .body(Body::from( - serde_json::ser::to_string(&ErrorResponseBody { - status_code: status.as_u16(), - message: message.to_string(), - }) - .unwrap(), - )) - .unwrap() -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs deleted file mode 100644 index 48e5ee2f..00000000 --- a/src/utils/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -// pub mod error; -pub mod signal; -pub mod url_encode; diff --git a/src/utils/signal.rs b/src/utils/signal.rs deleted file mode 100644 index c251ce4e..00000000 --- a/src/utils/signal.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub async fn shutdown_signal() { - tokio::signal::ctrl_c() - .await - .expect("Failed to hook Ctrl + C signal handler"); - println!("Received Ctrl + C Signal"); -} diff --git a/src/utils/url_encode.rs b/src/utils/url_encode.rs deleted file mode 100644 index e2c56724..00000000 --- a/src/utils/url_encode.rs +++ /dev/null @@ -1,66 +0,0 @@ -use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; -use std::path::{Path, PathBuf}; - -pub const PERCENT_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC - .remove(b'-') - .remove(b'_') - .remove(b'.') - .remove(b'~'); - -pub fn encode_uri(file_path: &Path) -> String { - assert!(!file_path.is_absolute()); - - file_path - .iter() - .flat_map(|component| { - let component = component.to_str().unwrap(); - let segment = utf8_percent_encode(component, PERCENT_ENCODE_SET); - - std::iter::once("/").chain(segment) - }) - .collect::() -} - -pub fn decode_uri(file_path: &str) -> PathBuf { - file_path - .split('/') - .map(|encoded_part| { - let decode = percent_decode(encoded_part.as_bytes()); - let decode = decode.decode_utf8_lossy(); - - decode.to_string() - }) - .collect::() -} - -#[cfg(test)] -mod tests { - use std::path::PathBuf; - use std::str::FromStr; - - use super::{decode_uri, encode_uri}; - - #[test] - fn encodes_uri() { - let file_path = "/these are important files/do_not_delete/file name.txt"; - let file_path = PathBuf::from_str(file_path).unwrap(); - let file_path = encode_uri(&file_path); - - assert_eq!( - file_path, - "/these%20are%20important%20files/do_not_delete/file%20name.txt" - ); - } - - #[test] - fn decodes_uri() { - let file_path = "these%20are%20important%20files/do_not_delete/file%20name.txt"; - let file_path = decode_uri(file_path); - let file_path = file_path.to_str().unwrap(); - - assert_eq!( - file_path, - "these are important files/do_not_delete/file name.txt" - ); - } -} diff --git a/tests/basic_auth.rs b/tests/basic_auth.rs deleted file mode 100644 index 819cb0b8..00000000 --- a/tests/basic_auth.rs +++ /dev/null @@ -1,52 +0,0 @@ -#[cfg(test)] -mod tests { - use http::{HeaderValue, Request, Response, StatusCode}; - use http_auth_basic::Credentials; - use hyper::{Body, Client}; - - async fn http_get(url: &str) -> Response { - let client = Client::default(); - - client.get(url.parse().unwrap()).await.unwrap() - } - - async fn http_get_with_basic_auth(url: &str, username: &str, password: &str) -> Response { - let credentials = Credentials::new(username, password); - let mut request = Request::builder(); - request = request.uri(url); - - let headers = request.headers_mut().unwrap(); - - headers.insert( - http::header::AUTHORIZATION, - HeaderValue::from_str(credentials.as_http_header().as_str()).unwrap(), - ); - - let client = Client::default(); - client - .request(request.body(Body::empty()).unwrap()) - .await - .unwrap() - } - - #[tokio::test] - async fn basic_auth_resolves_request_successfuly() { - let response = http_get_with_basic_auth("http://0.0.0.0:7878", "john", "appleseed").await; - - assert_eq!(response.status(), StatusCode::OK); - } - - #[tokio::test] - async fn basic_auth_validates_wrong_credentials() { - let response = http_get_with_basic_auth("http://0.0.0.0:7878", "somebody", "else").await; - - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - } - - #[tokio::test] - async fn basic_auth_resolves_request_unauthorized_when_header_is_missing() { - let response = http_get("http://0.0.0.0:7878").await; - - assert_eq!(response.status(), StatusCode::UNAUTHORIZED); - } -} diff --git a/tests/cors.rs b/tests/cors.rs deleted file mode 100644 index 3d4ef70a..00000000 --- a/tests/cors.rs +++ /dev/null @@ -1,63 +0,0 @@ -#[cfg(test)] -mod tests { - use http::{Response, StatusCode}; - use hyper::{Body, Client}; - - async fn http_get(url: &str) -> Response { - let client = Client::default(); - - client.get(url.parse().unwrap()).await.unwrap() - } - - #[tokio::test] - async fn cors_get_request_to_root_responds_200() { - let response = http_get("http://0.0.0.0:7878").await; - let headers = response.headers(); - - assert_eq!(response.status(), StatusCode::OK); - assert!(headers - .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN) - .is_some()); - assert!(headers - .get(http::header::ACCESS_CONTROL_ALLOW_METHODS) - .is_some()); - assert!(headers - .get(http::header::ACCESS_CONTROL_ALLOW_HEADERS) - .is_some()); - } - - #[tokio::test] - async fn cors_get_request_retrieve_file() { - let response = http_get("http://0.0.0.0:7878/docs/screenshot.png").await; - let headers = response.headers(); - - println!("{:#?}", response.headers()); - assert_eq!(response.status(), StatusCode::OK); - assert!(headers - .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN) - .is_some()); - assert!(headers - .get(http::header::ACCESS_CONTROL_ALLOW_METHODS) - .is_some()); - assert!(headers - .get(http::header::ACCESS_CONTROL_ALLOW_HEADERS) - .is_some()); - } - - #[tokio::test] - async fn cors_get_request_file_not_found() { - let response = http_get("http://0.0.0.0:7878/xyz/abc.txt").await; - let headers = response.headers(); - - assert_eq!(response.status(), StatusCode::NOT_FOUND); - assert!(headers - .get(http::header::ACCESS_CONTROL_ALLOW_ORIGIN) - .is_some()); - assert!(headers - .get(http::header::ACCESS_CONTROL_ALLOW_METHODS) - .is_some()); - assert!(headers - .get(http::header::ACCESS_CONTROL_ALLOW_HEADERS) - .is_some()); - } -} diff --git a/tests/defacto.rs b/tests/defacto.rs deleted file mode 100644 index 82ddfeb7..00000000 --- a/tests/defacto.rs +++ /dev/null @@ -1,32 +0,0 @@ -#[cfg(test)] -mod tests { - use http::{Response, StatusCode}; - use hyper::{Body, Client}; - - async fn http_get(url: &str) -> Response { - let client = Client::default(); - - client.get(url.parse().unwrap()).await.unwrap() - } - - #[tokio::test] - async fn defacto_get_request_to_root_responds_200() { - let response = http_get("http://0.0.0.0:7878").await; - - assert_eq!(response.status(), StatusCode::OK); - } - - #[tokio::test] - async fn defacto_get_request_retrieve_file() { - let response = http_get("http://0.0.0.0:7878/docs/screenshot.png").await; - - assert_eq!(response.status(), StatusCode::OK); - } - - #[tokio::test] - async fn defacto_get_request_file_not_found() { - let response = http_get("http://0.0.0.0:7878/xyz/abc.txt").await; - - assert_eq!(response.status(), StatusCode::NOT_FOUND); - } -} diff --git a/tests/e2e/basic.bats b/tests/e2e/basic.bats deleted file mode 100644 index 7a7c7cce..00000000 --- a/tests/e2e/basic.bats +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env bats - -load 'helpers/load' - -@test "does not panics on default run" { - run bash -c '$BIN' & - - sleep 1 - - pkill -9 http-server - - sleep 1 - - assert_success -} - -@test "teardowns on graceful shutdown" { - run bash -c '$BIN --graceful-shutdown' & - - sleep 1 - - pkill -SIGINT http-server - - sleep 1 - - assert_success -} diff --git a/tests/e2e/helpers/assert.bash b/tests/e2e/helpers/assert.bash deleted file mode 100644 index b5e24294..00000000 --- a/tests/e2e/helpers/assert.bash +++ /dev/null @@ -1,720 +0,0 @@ -# -# bats-assert - Common assertions for Bats -# -# Written in 2016 by Zoltan Tombol -# -# To the extent possible under law, the author(s) have dedicated all -# copyright and related and neighboring rights to this software to the -# public domain worldwide. This software is distributed without any -# warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication -# along with this software. If not, see -# . -# - -# -# assert.bash -# ----------- -# -# Assertions are functions that perform a test and output relevant -# information on failure to help debugging. They return 1 on failure -# and 0 otherwise. -# -# All output is formatted for readability using the functions of -# `output.bash' and sent to the standard error. -# - -# Fail and display the expression if it evaluates to false. -# -# NOTE: The expression must be a simple command. Compound commands, such -# as `[[', can be used only when executed with `bash -c'. -# -# Globals: -# none -# Arguments: -# $1 - expression -# Returns: -# 0 - expression evaluates to TRUE -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -assert() { - if ! "$@"; then - batslib_print_kv_single 10 'expression' "$*" \ - | batslib_decorate 'assertion failed' \ - | fail - fi -} - -# Fail and display the expression if it evaluates to true. -# -# NOTE: The expression must be a simple command. Compound commands, such -# as `[[', can be used only when executed with `bash -c'. -# -# Globals: -# none -# Arguments: -# $1 - expression -# Returns: -# 0 - expression evaluates to FALSE -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -refute() { - if "$@"; then - batslib_print_kv_single 10 'expression' "$*" \ - | batslib_decorate 'assertion succeeded, but it was expected to fail' \ - | fail - fi -} - -# Fail and display details if the expected and actual values do not -# equal. Details include both values. -# -# Globals: -# none -# Arguments: -# $1 - actual value -# $2 - expected value -# Returns: -# 0 - values equal -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -assert_equal() { - if [[ $1 != "$2" ]]; then - batslib_print_kv_single_or_multi 8 \ - 'expected' "$2" \ - 'actual' "$1" \ - | batslib_decorate 'values do not equal' \ - | fail - fi -} - -# Fail and display details if `$status' is not 0. Details include -# `$status' and `$output'. -# -# Globals: -# status -# output -# Arguments: -# none -# Returns: -# 0 - `$status' is 0 -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -assert_success() { - if (( status != 0 )); then - { local -ir width=6 - batslib_print_kv_single "$width" 'status' "$status" - batslib_print_kv_single_or_multi "$width" 'output' "$output" - } | batslib_decorate 'command failed' \ - | fail - fi -} - -# Fail and display details if `$status' is 0. Details include `$output'. -# -# Optionally, when the expected status is specified, fail when it does -# not equal `$status'. In this case, details include the expected and -# actual status, and `$output'. -# -# Globals: -# status -# output -# Arguments: -# $1 - [opt] expected status -# Returns: -# 0 - `$status' is not 0, or -# `$status' equals the expected status -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -assert_failure() { - (( $# > 0 )) && local -r expected="$1" - if (( status == 0 )); then - batslib_print_kv_single_or_multi 6 'output' "$output" \ - | batslib_decorate 'command succeeded, but it was expected to fail' \ - | fail - elif (( $# > 0 )) && (( status != expected )); then - { local -ir width=8 - batslib_print_kv_single "$width" \ - 'expected' "$expected" \ - 'actual' "$status" - batslib_print_kv_single_or_multi "$width" \ - 'output' "$output" - } | batslib_decorate 'command failed as expected, but status differs' \ - | fail - fi -} - -# Fail and display details if `$output' does not match the expected -# output. The expected output can be specified either by the first -# parameter or on the standard input. -# -# By default, literal matching is performed. The assertion fails if the -# expected output does not equal `$output'. Details include both values. -# -# Option `--partial' enables partial matching. The assertion fails if -# the expected substring cannot be found in `$output'. -# -# Option `--regexp' enables regular expression matching. The assertion -# fails if the extended regular expression does not match `$output'. An -# invalid regular expression causes an error to be displayed. -# -# It is an error to use partial and regular expression matching -# simultaneously. -# -# Globals: -# output -# Options: -# -p, --partial - partial matching -# -e, --regexp - extended regular expression matching -# Arguments: -# $1 - [=STDIN] expected output -# Returns: -# 0 - expected matches the actual output -# 1 - otherwise -# Inputs: -# STDIN - [=$1] expected output -# Outputs: -# STDERR - details, on failure -# error message, on error -assert_output() { - local -i is_mode_partial=0 - local -i is_mode_regexp=0 - - # Handle options. - while (( $# > 0 )); do - case "$1" in - -p|--partial) is_mode_partial=1; shift ;; - -e|--regexp) is_mode_regexp=1; shift ;; - --) shift; break ;; - *) break ;; - esac - done - - if (( is_mode_partial )) && (( is_mode_regexp )); then - echo "\`--partial' and \`--regexp' are mutually exclusive" \ - | batslib_decorate 'ERROR: assert_output' \ - | fail - return $? - fi - - # Arguments. - local expected - (( $# == 0 )) && expected="$(cat -)" || expected="$1" - - # Matching. - if (( is_mode_regexp )); then - if [[ '' =~ $expected ]] || (( $? == 2 )); then - echo "Invalid extended regular expression: \`$expected'" \ - | batslib_decorate 'ERROR: assert_output' \ - | fail - return $? - fi - if ! [[ $output =~ $expected ]]; then - batslib_print_kv_single_or_multi 6 \ - 'regexp' "$expected" \ - 'output' "$output" \ - | batslib_decorate 'regular expression does not match output' \ - | fail - fi - elif (( is_mode_partial )); then - if [[ $output != *"$expected"* ]]; then - batslib_print_kv_single_or_multi 9 \ - 'substring' "$expected" \ - 'output' "$output" \ - | batslib_decorate 'output does not contain substring' \ - | fail - fi - else - if [[ $output != "$expected" ]]; then - batslib_print_kv_single_or_multi 8 \ - 'expected' "$expected" \ - 'actual' "$output" \ - | batslib_decorate 'output differs' \ - | fail - fi - fi -} - -# Fail and display details if `$output' matches the unexpected output. -# The unexpected output can be specified either by the first parameter -# or on the standard input. -# -# By default, literal matching is performed. The assertion fails if the -# unexpected output equals `$output'. Details include `$output'. -# -# Option `--partial' enables partial matching. The assertion fails if -# the unexpected substring is found in `$output'. The unexpected -# substring is added to details. -# -# Option `--regexp' enables regular expression matching. The assertion -# fails if the extended regular expression does matches `$output'. The -# regular expression is added to details. An invalid regular expression -# causes an error to be displayed. -# -# It is an error to use partial and regular expression matching -# simultaneously. -# -# Globals: -# output -# Options: -# -p, --partial - partial matching -# -e, --regexp - extended regular expression matching -# Arguments: -# $1 - [=STDIN] unexpected output -# Returns: -# 0 - unexpected matches the actual output -# 1 - otherwise -# Inputs: -# STDIN - [=$1] unexpected output -# Outputs: -# STDERR - details, on failure -# error message, on error -refute_output() { - local -i is_mode_partial=0 - local -i is_mode_regexp=0 - - # Handle options. - while (( $# > 0 )); do - case "$1" in - -p|--partial) is_mode_partial=1; shift ;; - -e|--regexp) is_mode_regexp=1; shift ;; - --) shift; break ;; - *) break ;; - esac - done - - if (( is_mode_partial )) && (( is_mode_regexp )); then - echo "\`--partial' and \`--regexp' are mutually exclusive" \ - | batslib_decorate 'ERROR: refute_output' \ - | fail - return $? - fi - - # Arguments. - local unexpected - (( $# == 0 )) && unexpected="$(cat -)" || unexpected="$1" - - if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then - echo "Invalid extended regular expression: \`$unexpected'" \ - | batslib_decorate 'ERROR: refute_output' \ - | fail - return $? - fi - - # Matching. - if (( is_mode_regexp )); then - if [[ $output =~ $unexpected ]] || (( $? == 0 )); then - batslib_print_kv_single_or_multi 6 \ - 'regexp' "$unexpected" \ - 'output' "$output" \ - | batslib_decorate 'regular expression should not match output' \ - | fail - fi - elif (( is_mode_partial )); then - if [[ $output == *"$unexpected"* ]]; then - batslib_print_kv_single_or_multi 9 \ - 'substring' "$unexpected" \ - 'output' "$output" \ - | batslib_decorate 'output should not contain substring' \ - | fail - fi - else - if [[ $output == "$unexpected" ]]; then - batslib_print_kv_single_or_multi 6 \ - 'output' "$output" \ - | batslib_decorate 'output equals, but it was expected to differ' \ - | fail - fi - fi -} - -# Fail and display details if the expected line is not found in the -# output (default) or in a specific line of it. -# -# By default, the entire output is searched for the expected line. The -# expected line is matched against every element of `${lines[@]}'. If no -# match is found, the assertion fails. Details include the expected line -# and `${lines[@]}'. -# -# When `--index ' is specified, only the -th line is matched. -# If the expected line does not match `${lines[]}', the assertion -# fails. Details include and the compared lines. -# -# By default, literal matching is performed. A literal match fails if -# the expected string does not equal the matched string. -# -# Option `--partial' enables partial matching. A partial match fails if -# the expected substring is not found in the target string. -# -# Option `--regexp' enables regular expression matching. A regular -# expression match fails if the extended regular expression does not -# match the target string. An invalid regular expression causes an error -# to be displayed. -# -# It is an error to use partial and regular expression matching -# simultaneously. -# -# Mandatory arguments to long options are mandatory for short options -# too. -# -# Globals: -# output -# lines -# Options: -# -n, --index - match the -th line -# -p, --partial - partial matching -# -e, --regexp - extended regular expression matching -# Arguments: -# $1 - expected line -# Returns: -# 0 - match found -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -# error message, on error -# FIXME(ztombol): Display `${lines[@]}' instead of `$output'! -assert_line() { - local -i is_match_line=0 - local -i is_mode_partial=0 - local -i is_mode_regexp=0 - - # Handle options. - while (( $# > 0 )); do - case "$1" in - -n|--index) - if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then - echo "\`--index' requires an integer argument: \`$2'" \ - | batslib_decorate 'ERROR: assert_line' \ - | fail - return $? - fi - is_match_line=1 - local -ri idx="$2" - shift 2 - ;; - -p|--partial) is_mode_partial=1; shift ;; - -e|--regexp) is_mode_regexp=1; shift ;; - --) shift; break ;; - *) break ;; - esac - done - - if (( is_mode_partial )) && (( is_mode_regexp )); then - echo "\`--partial' and \`--regexp' are mutually exclusive" \ - | batslib_decorate 'ERROR: assert_line' \ - | fail - return $? - fi - - # Arguments. - local -r expected="$1" - - if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then - echo "Invalid extended regular expression: \`$expected'" \ - | batslib_decorate 'ERROR: assert_line' \ - | fail - return $? - fi - - # Matching. - if (( is_match_line )); then - # Specific line. - if (( is_mode_regexp )); then - if ! [[ ${lines[$idx]} =~ $expected ]]; then - batslib_print_kv_single 6 \ - 'index' "$idx" \ - 'regexp' "$expected" \ - 'line' "${lines[$idx]}" \ - | batslib_decorate 'regular expression does not match line' \ - | fail - fi - elif (( is_mode_partial )); then - if [[ ${lines[$idx]} != *"$expected"* ]]; then - batslib_print_kv_single 9 \ - 'index' "$idx" \ - 'substring' "$expected" \ - 'line' "${lines[$idx]}" \ - | batslib_decorate 'line does not contain substring' \ - | fail - fi - else - if [[ ${lines[$idx]} != "$expected" ]]; then - batslib_print_kv_single 8 \ - 'index' "$idx" \ - 'expected' "$expected" \ - 'actual' "${lines[$idx]}" \ - | batslib_decorate 'line differs' \ - | fail - fi - fi - else - # Contained in output. - if (( is_mode_regexp )); then - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - [[ ${lines[$idx]} =~ $expected ]] && return 0 - done - { local -ar single=( - 'regexp' "$expected" - ) - local -ar may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" - } | batslib_decorate 'no output line matches regular expression' \ - | fail - elif (( is_mode_partial )); then - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - [[ ${lines[$idx]} == *"$expected"* ]] && return 0 - done - { local -ar single=( - 'substring' "$expected" - ) - local -ar may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" - } | batslib_decorate 'no output line contains substring' \ - | fail - else - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - [[ ${lines[$idx]} == "$expected" ]] && return 0 - done - { local -ar single=( - 'line' "$expected" - ) - local -ar may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" - } | batslib_decorate 'output does not contain line' \ - | fail - fi - fi -} - -# Fail and display details if the unexpected line is found in the output -# (default) or in a specific line of it. -# -# By default, the entire output is searched for the unexpected line. The -# unexpected line is matched against every element of `${lines[@]}'. If -# a match is found, the assertion fails. Details include the unexpected -# line, the index of the first match and `${lines[@]}' with the matching -# line highlighted if `${lines[@]}' is longer than one line. -# -# When `--index ' is specified, only the -th line is matched. -# If the unexpected line matches `${lines[]}', the assertion fails. -# Details include and the unexpected line. -# -# By default, literal matching is performed. A literal match fails if -# the unexpected string does not equal the matched string. -# -# Option `--partial' enables partial matching. A partial match fails if -# the unexpected substring is found in the target string. When used with -# `--index ', the unexpected substring is also displayed on -# failure. -# -# Option `--regexp' enables regular expression matching. A regular -# expression match fails if the extended regular expression matches the -# target string. When used with `--index ', the regular expression -# is also displayed on failure. An invalid regular expression causes an -# error to be displayed. -# -# It is an error to use partial and regular expression matching -# simultaneously. -# -# Mandatory arguments to long options are mandatory for short options -# too. -# -# Globals: -# output -# lines -# Options: -# -n, --index - match the -th line -# -p, --partial - partial matching -# -e, --regexp - extended regular expression matching -# Arguments: -# $1 - unexpected line -# Returns: -# 0 - match not found -# 1 - otherwise -# Outputs: -# STDERR - details, on failure -# error message, on error -# FIXME(ztombol): Display `${lines[@]}' instead of `$output'! -refute_line() { - local -i is_match_line=0 - local -i is_mode_partial=0 - local -i is_mode_regexp=0 - - # Handle options. - while (( $# > 0 )); do - case "$1" in - -n|--index) - if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then - echo "\`--index' requires an integer argument: \`$2'" \ - | batslib_decorate 'ERROR: refute_line' \ - | fail - return $? - fi - is_match_line=1 - local -ri idx="$2" - shift 2 - ;; - -p|--partial) is_mode_partial=1; shift ;; - -e|--regexp) is_mode_regexp=1; shift ;; - --) shift; break ;; - *) break ;; - esac - done - - if (( is_mode_partial )) && (( is_mode_regexp )); then - echo "\`--partial' and \`--regexp' are mutually exclusive" \ - | batslib_decorate 'ERROR: refute_line' \ - | fail - return $? - fi - - # Arguments. - local -r unexpected="$1" - - if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then - echo "Invalid extended regular expression: \`$unexpected'" \ - | batslib_decorate 'ERROR: refute_line' \ - | fail - return $? - fi - - # Matching. - if (( is_match_line )); then - # Specific line. - if (( is_mode_regexp )); then - if [[ ${lines[$idx]} =~ $unexpected ]] || (( $? == 0 )); then - batslib_print_kv_single 6 \ - 'index' "$idx" \ - 'regexp' "$unexpected" \ - 'line' "${lines[$idx]}" \ - | batslib_decorate 'regular expression should not match line' \ - | fail - fi - elif (( is_mode_partial )); then - if [[ ${lines[$idx]} == *"$unexpected"* ]]; then - batslib_print_kv_single 9 \ - 'index' "$idx" \ - 'substring' "$unexpected" \ - 'line' "${lines[$idx]}" \ - | batslib_decorate 'line should not contain substring' \ - | fail - fi - else - if [[ ${lines[$idx]} == "$unexpected" ]]; then - batslib_print_kv_single 5 \ - 'index' "$idx" \ - 'line' "${lines[$idx]}" \ - | batslib_decorate 'line should differ' \ - | fail - fi - fi - else - # Line contained in output. - if (( is_mode_regexp )); then - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - if [[ ${lines[$idx]} =~ $unexpected ]]; then - { local -ar single=( - 'regexp' "$unexpected" - 'index' "$idx" - ) - local -a may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - if batslib_is_single_line "${may_be_multi[1]}"; then - batslib_print_kv_single "$width" "${may_be_multi[@]}" - else - may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \ - | batslib_prefix \ - | batslib_mark '>' "$idx" )" - batslib_print_kv_multi "${may_be_multi[@]}" - fi - } | batslib_decorate 'no line should match the regular expression' \ - | fail - return $? - fi - done - elif (( is_mode_partial )); then - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - if [[ ${lines[$idx]} == *"$unexpected"* ]]; then - { local -ar single=( - 'substring' "$unexpected" - 'index' "$idx" - ) - local -a may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - if batslib_is_single_line "${may_be_multi[1]}"; then - batslib_print_kv_single "$width" "${may_be_multi[@]}" - else - may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \ - | batslib_prefix \ - | batslib_mark '>' "$idx" )" - batslib_print_kv_multi "${may_be_multi[@]}" - fi - } | batslib_decorate 'no line should contain substring' \ - | fail - return $? - fi - done - else - local -i idx - for (( idx = 0; idx < ${#lines[@]}; ++idx )); do - if [[ ${lines[$idx]} == "$unexpected" ]]; then - { local -ar single=( - 'line' "$unexpected" - 'index' "$idx" - ) - local -a may_be_multi=( - 'output' "$output" - ) - local -ir width="$( batslib_get_max_single_line_key_width \ - "${single[@]}" "${may_be_multi[@]}" )" - batslib_print_kv_single "$width" "${single[@]}" - if batslib_is_single_line "${may_be_multi[1]}"; then - batslib_print_kv_single "$width" "${may_be_multi[@]}" - else - may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" \ - | batslib_prefix \ - | batslib_mark '>' "$idx" )" - batslib_print_kv_multi "${may_be_multi[@]}" - fi - } | batslib_decorate 'line should not be in output' \ - | fail - return $? - fi - done - fi - fi -} \ No newline at end of file diff --git a/tests/e2e/helpers/error.bash b/tests/e2e/helpers/error.bash deleted file mode 100644 index e5d97912..00000000 --- a/tests/e2e/helpers/error.bash +++ /dev/null @@ -1,41 +0,0 @@ -# -# bats-support - Supporting library for Bats test helpers -# -# Written in 2016 by Zoltan Tombol -# -# To the extent possible under law, the author(s) have dedicated all -# copyright and related and neighboring rights to this software to the -# public domain worldwide. This software is distributed without any -# warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication -# along with this software. If not, see -# . -# - -# -# error.bash -# ---------- -# -# Functions implementing error reporting. Used by public helper -# functions or test suits directly. -# - -# Fail and display a message. When no parameters are specified, the -# message is read from the standard input. Other functions use this to -# report failure. -# -# Globals: -# none -# Arguments: -# $@ - [=STDIN] message -# Returns: -# 1 - always -# Inputs: -# STDIN - [=$@] message -# Outputs: -# STDERR - message -fail() { - (( $# == 0 )) && batslib_err || batslib_err "$@" - return 1 -} diff --git a/tests/e2e/helpers/lang.bash b/tests/e2e/helpers/lang.bash deleted file mode 100644 index c57e299c..00000000 --- a/tests/e2e/helpers/lang.bash +++ /dev/null @@ -1,73 +0,0 @@ -# -# bats-util - Various auxiliary functions for Bats -# -# Written in 2016 by Zoltan Tombol -# -# To the extent possible under law, the author(s) have dedicated all -# copyright and related and neighboring rights to this software to the -# public domain worldwide. This software is distributed without any -# warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication -# along with this software. If not, see -# . -# - -# -# lang.bash -# --------- -# -# Bash language and execution related functions. Used by public helper -# functions. -# - -# Check whether the calling function was called from a given function. -# -# By default, direct invocation is checked. The function succeeds if the -# calling function was called directly from the given function. In other -# words, if the given function is the next element on the call stack. -# -# When `--indirect' is specified, indirect invocation is checked. The -# function succeeds if the calling function was called from the given -# function with any number of intermediate calls. In other words, if the -# given function can be found somewhere on the call stack. -# -# Direct invocation is a form of indirect invocation with zero -# intermediate calls. -# -# Globals: -# FUNCNAME -# Options: -# -i, --indirect - check indirect invocation -# Arguments: -# $1 - calling function's name -# Returns: -# 0 - current function was called from the given function -# 1 - otherwise -batslib_is_caller() { - local -i is_mode_direct=1 - - # Handle options. - while (( $# > 0 )); do - case "$1" in - -i|--indirect) is_mode_direct=0; shift ;; - --) shift; break ;; - *) break ;; - esac - done - - # Arguments. - local -r func="$1" - - # Check call stack. - if (( is_mode_direct )); then - [[ $func == "${FUNCNAME[2]}" ]] && return 0 - else - local -i depth - for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do - [[ $func == "${FUNCNAME[$depth]}" ]] && return 0 - done - fi - - return 1 -} diff --git a/tests/e2e/helpers/load.bash b/tests/e2e/helpers/load.bash deleted file mode 100644 index 87ee01c8..00000000 --- a/tests/e2e/helpers/load.bash +++ /dev/null @@ -1,4 +0,0 @@ -source "$(dirname "${BASH_SOURCE[0]}")/assert.bash" -source "$(dirname "${BASH_SOURCE[0]}")/error.bash" -source "$(dirname "${BASH_SOURCE[0]}")/lang.bash" -source "$(dirname "${BASH_SOURCE[0]}")/output.bash" diff --git a/tests/e2e/helpers/output.bash b/tests/e2e/helpers/output.bash deleted file mode 100644 index c6cf6a6b..00000000 --- a/tests/e2e/helpers/output.bash +++ /dev/null @@ -1,279 +0,0 @@ -# -# bats-support - Supporting library for Bats test helpers -# -# Written in 2016 by Zoltan Tombol -# -# To the extent possible under law, the author(s) have dedicated all -# copyright and related and neighboring rights to this software to the -# public domain worldwide. This software is distributed without any -# warranty. -# -# You should have received a copy of the CC0 Public Domain Dedication -# along with this software. If not, see -# . -# - -# -# output.bash -# ----------- -# -# Private functions implementing output formatting. Used by public -# helper functions. -# - -# Print a message to the standard error. When no parameters are -# specified, the message is read from the standard input. -# -# Globals: -# none -# Arguments: -# $@ - [=STDIN] message -# Returns: -# none -# Inputs: -# STDIN - [=$@] message -# Outputs: -# STDERR - message -batslib_err() { - { if (( $# > 0 )); then - echo "$@" - else - cat - - fi - } >&2 -} - -# Count the number of lines in the given string. -# -# TODO(ztombol): Fix tests and remove this note after #93 is resolved! -# NOTE: Due to a bug in Bats, `batslib_count_lines "$output"' does not -# give the same result as `${#lines[@]}' when the output contains -# empty lines. -# See PR #93 (https://github.com/sstephenson/bats/pull/93). -# -# Globals: -# none -# Arguments: -# $1 - string -# Returns: -# none -# Outputs: -# STDOUT - number of lines -batslib_count_lines() { - local -i n_lines=0 - local line - while IFS='' read -r line || [[ -n $line ]]; do - (( ++n_lines )) - done < <(printf '%s' "$1") - echo "$n_lines" -} - -# Determine whether all strings are single-line. -# -# Globals: -# none -# Arguments: -# $@ - strings -# Returns: -# 0 - all strings are single-line -# 1 - otherwise -batslib_is_single_line() { - for string in "$@"; do - (( $(batslib_count_lines "$string") > 1 )) && return 1 - done - return 0 -} - -# Determine the length of the longest key that has a single-line value. -# -# This function is useful in determining the correct width of the key -# column in two-column format when some keys may have multi-line values -# and thus should be excluded. -# -# Globals: -# none -# Arguments: -# $odd - key -# $even - value of the previous key -# Returns: -# none -# Outputs: -# STDOUT - length of longest key -batslib_get_max_single_line_key_width() { - local -i max_len=-1 - while (( $# != 0 )); do - local -i key_len="${#1}" - batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len" - shift 2 - done - echo "$max_len" -} - -# Print key-value pairs in two-column format. -# -# Keys are displayed in the first column, and their corresponding values -# in the second. To evenly line up values, the key column is fixed-width -# and its width is specified with the first parameter (possibly computed -# using `batslib_get_max_single_line_key_width'). -# -# Globals: -# none -# Arguments: -# $1 - width of key column -# $even - key -# $odd - value of the previous key -# Returns: -# none -# Outputs: -# STDOUT - formatted key-value pairs -batslib_print_kv_single() { - local -ir col_width="$1"; shift - while (( $# != 0 )); do - printf '%-*s : %s\n' "$col_width" "$1" "$2" - shift 2 - done -} - -# Print key-value pairs in multi-line format. -# -# The key is displayed first with the number of lines of its -# corresponding value in parenthesis. Next, starting on the next line, -# the value is displayed. For better readability, it is recommended to -# indent values using `batslib_prefix'. -# -# Globals: -# none -# Arguments: -# $odd - key -# $even - value of the previous key -# Returns: -# none -# Outputs: -# STDOUT - formatted key-value pairs -batslib_print_kv_multi() { - while (( $# != 0 )); do - printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )" - printf '%s\n' "$2" - shift 2 - done -} - -# Print all key-value pairs in either two-column or multi-line format -# depending on whether all values are single-line. -# -# If all values are single-line, print all pairs in two-column format -# with the specified key column width (identical to using -# `batslib_print_kv_single'). -# -# Otherwise, print all pairs in multi-line format after indenting values -# with two spaces for readability (identical to using `batslib_prefix' -# and `batslib_print_kv_multi') -# -# Globals: -# none -# Arguments: -# $1 - width of key column (for two-column format) -# $even - key -# $odd - value of the previous key -# Returns: -# none -# Outputs: -# STDOUT - formatted key-value pairs -batslib_print_kv_single_or_multi() { - local -ir width="$1"; shift - local -a pairs=( "$@" ) - - local -a values=() - local -i i - for (( i=1; i < ${#pairs[@]}; i+=2 )); do - values+=( "${pairs[$i]}" ) - done - - if batslib_is_single_line "${values[@]}"; then - batslib_print_kv_single "$width" "${pairs[@]}" - else - local -i i - for (( i=1; i < ${#pairs[@]}; i+=2 )); do - pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )" - done - batslib_print_kv_multi "${pairs[@]}" - fi -} - -# Prefix each line read from the standard input with the given string. -# -# Globals: -# none -# Arguments: -# $1 - [= ] prefix string -# Returns: -# none -# Inputs: -# STDIN - lines -# Outputs: -# STDOUT - prefixed lines -batslib_prefix() { - local -r prefix="${1:- }" - local line - while IFS='' read -r line || [[ -n $line ]]; do - printf '%s%s\n' "$prefix" "$line" - done -} - -# Mark select lines of the text read from the standard input by -# overwriting their beginning with the given string. -# -# Usually the input is indented by a few spaces using `batslib_prefix' -# first. -# -# Globals: -# none -# Arguments: -# $1 - marking string -# $@ - indices (zero-based) of lines to mark -# Returns: -# none -# Inputs: -# STDIN - lines -# Outputs: -# STDOUT - lines after marking -batslib_mark() { - local -r symbol="$1"; shift - # Sort line numbers. - set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" ) - - local line - local -i idx=0 - while IFS='' read -r line || [[ -n $line ]]; do - if (( ${1:--1} == idx )); then - printf '%s\n' "${symbol}${line:${#symbol}}" - shift - else - printf '%s\n' "$line" - fi - (( ++idx )) - done -} - -# Enclose the input text in header and footer lines. -# -# The header contains the given string as title. The output is preceded -# and followed by an additional newline to make it stand out more. -# -# Globals: -# none -# Arguments: -# $1 - title -# Returns: -# none -# Inputs: -# STDIN - text -# Outputs: -# STDOUT - decorated text -batslib_decorate() { - echo - echo "-- $1 --" - cat - - echo '--' - echo -} diff --git a/tests/gzip.rs b/tests/gzip.rs deleted file mode 100644 index ca43ae19..00000000 --- a/tests/gzip.rs +++ /dev/null @@ -1,73 +0,0 @@ -#[cfg(test)] -mod tests { - use http::{Request, Response, StatusCode}; - use hyper::{Body, Client}; - - async fn http_get(url: &str, accept_encoding: Option<&str>) -> Response { - let mut request = Request::builder(); - request = request.uri(url); - - let headers = request.headers_mut().unwrap(); - - if let Some(accept_encoding) = accept_encoding { - headers.insert( - http::header::ACCEPT_ENCODING, - http::HeaderValue::from_str(accept_encoding).unwrap(), - ); - } - - let client = Client::default(); - client - .request(request.body(Body::empty()).unwrap()) - .await - .unwrap() - } - - #[tokio::test] - async fn gzip_get_request_to_root_responds_200() { - let response = http_get("http://0.0.0.0:7878", Some("gzip, brotli")).await; - - assert_eq!(response.status(), StatusCode::OK); - assert!(response - .headers() - .get(http::header::CONTENT_ENCODING) - .is_some()); - } - - #[tokio::test] - async fn gzip_get_request_retrieve_image_file_not_present() { - let response = http_get( - "http://0.0.0.0:7878/docs/screenshot.png", - Some("gzip, brotli"), - ) - .await; - - assert_eq!(response.status(), StatusCode::OK); - assert!(response - .headers() - .get(http::header::CONTENT_ENCODING) - .is_some()); - } - - #[tokio::test] - async fn gzip_get_request_file_not_found() { - let response = http_get("http://0.0.0.0:7878/docs/xyz/foo.txt", Some("gzip, brotli")).await; - - assert_eq!(response.status(), StatusCode::NOT_FOUND); - assert!(response - .headers() - .get(http::header::CONTENT_ENCODING) - .is_none()); - } - - #[tokio::test] - async fn gzip_no_compression_if_no_accept_encoding_header_is_provided() { - let response = http_get("http://0.0.0.0:7878/docs/screenshot.png", None).await; - - assert_eq!(response.status(), StatusCode::OK); - assert!(response - .headers() - .get(http::header::CONTENT_ENCODING) - .is_none()); - } -} diff --git a/tests/mod.rs b/tests/mod.rs deleted file mode 100644 index fb9996a6..00000000 --- a/tests/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod basic_auth; -mod cors; -mod defacto; From 0ef1c5c1fd34ceed10a1e469e67e9f7ba3af5d9a Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Tue, 1 Oct 2024 21:26:40 -0300 Subject: [PATCH 06/40] feat: use tower middleware --- Cargo.lock | 42 +++++++++++++++++++++-- Cargo.toml | 2 ++ src/main.rs | 3 +- src/server/handler.rs | 30 ----------------- src/server/mod.rs | 77 ++++++++++++++++--------------------------- 5 files changed, 70 insertions(+), 84 deletions(-) delete mode 100644 src/server/handler.rs diff --git a/Cargo.lock b/Cargo.lock index b737606d..f8b9e0a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -823,6 +823,8 @@ dependencies = [ "tokio", "tokio-rustls 0.23.4", "toml", + "tower 0.5.1", + "tower-http", ] [[package]] @@ -902,7 +904,7 @@ dependencies = [ "pin-project-lite", "socket2 0.5.7", "tokio", - "tower", + "tower 0.4.13", "tower-service", "tracing", ] @@ -1817,6 +1819,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + [[package]] name = "termcolor" version = "1.1.3" @@ -1980,6 +1988,34 @@ dependencies = [ "tower-service", ] +[[package]] +name = "tower" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" +dependencies = [ + "bitflags 2.4.0", + "bytes", + "http 1.1.0", + "pin-project-lite", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -1988,9 +2024,9 @@ checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] name = "tower-service" -version = "0.3.1" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" diff --git a/Cargo.toml b/Cargo.toml index 1d52ec51..17137e77 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,8 @@ tokio = { version = "1", features = [ tokio-rustls = "0.23.4" toml = "0.7.6" humansize = "2.1.3" +tower-http = { version = "0.6.1", features = ["cors"] } +tower = { version = "0.5.1", features = ["util"] } [dev-dependencies] criterion = { version = "0.5.1", features = ["async_tokio", "html_reports"] } diff --git a/src/main.rs b/src/main.rs index efe211b2..cd8c2424 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,7 @@ use std::process::exit; use anyhow::Result; -use crate::server::Server; - +use server::Server; #[tokio::main] async fn main() -> Result<()> { match Server::run().await { diff --git a/src/server/handler.rs b/src/server/handler.rs deleted file mode 100644 index 21dd1517..00000000 --- a/src/server/handler.rs +++ /dev/null @@ -1,30 +0,0 @@ -use std::future::Future; -use std::pin::Pin; - -use http_body_util::Full; -use hyper::body::Bytes; -use hyper::service::Service; - -use super::{HttpRequest, HttpResponse}; - -/// Http Request Handler for the Server. -/// -/// This struct is responsible for handling incoming HTTP Requests -/// and returning an HTTP Response. Every request is passed through -/// a series of middleware functions before and after handling the -/// request. -pub struct Handler {} - -impl Service for Handler { - type Response = HttpResponse; - type Error = hyper::Error; - type Future = Pin> + Send>>; - - fn call(&self, _: HttpRequest) -> Self::Future { - Box::pin(async move { - Ok(hyper::Response::builder() - .body(Full::new(Bytes::from("Hello, World!"))) - .unwrap()) - }) - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index d457f823..a6ed4094 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -1,67 +1,46 @@ -pub mod handler; - -use std::sync::Arc; +use std::{convert::Infallible, net::SocketAddr}; use anyhow::Result; use http_body_util::Full; -use hyper::http::StatusCode; -use hyper::{body::Bytes, server::conn::http1, Response}; -use hyper_util::rt::TokioIo; -use serde::Serialize; +use hyper::{ + body::{Bytes, Incoming}, + server::conn::http1, + Method, Request, Response, +}; +use hyper_util::{rt::TokioIo, service::TowerToHyperService}; use tokio::net::TcpListener; +use tower::ServiceBuilder; +use tower_http::cors::{Any, CorsLayer}; -use self::handler::Handler; - -pub type HttpRequest = hyper::Request; - -pub type HttpResponse = hyper::Response>; - -#[derive(Debug, Serialize)] -pub struct HttpErrorResponse { - status_code: u16, - message: Option, -} - -impl HttpErrorResponse { - pub fn new(status_code: StatusCode) -> Self { - HttpErrorResponse { - status_code: status_code.as_u16(), - message: None, - } - } - - pub fn with_message(self, message: &str) -> Self { - HttpErrorResponse { - message: Some(message.to_string()), - ..self - } - } - - pub fn into_response(self) -> HttpResponse { - let body = serde_json::ser::to_string(&self).unwrap(); - - Response::builder() - .status(self.status_code) - .body(Full::new(Bytes::from(body))) - .expect("Failed to build error response") - } +async fn hello(_: Request) -> Result>, Infallible> { + Ok(Response::new(Full::new(Bytes::from("Hello, World!")))) } -pub struct Server; +pub struct Server {} impl Server { pub async fn run() -> Result<()> { - let listener = TcpListener::bind("127.0.0.1:7878").await?; - let handler = Arc::new(Handler {}); + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + let listener = TcpListener::bind(addr).await?; loop { let (stream, _) = listener.accept().await?; let io = TokioIo::new(stream); - let handler = Arc::clone(&handler); - tokio::task::spawn(async move { - if let Err(err) = http1::Builder::new().serve_connection(io, handler).await { - eprintln!("Error: {}", err); + let cors = CorsLayer::new() + // allow `GET` and `POST` when accessing the resource + .allow_methods([Method::GET, Method::POST]) + // allow requests from any origin + .allow_origin(Any); + + tokio::spawn(async move { + // N.B. should use tower service_fn here, since it's reuqired to be implemented tower Service trait before convert to hyper Service! + let svc = tower::service_fn(hello); + let svc = ServiceBuilder::new().layer(cors).service(svc); + // Convert it to hyper service + let svc = TowerToHyperService::new(svc); + if let Err(err) = http1::Builder::new().serve_connection(io, svc).await { + eprintln!("server error: {}", err); } }); } From 0e9cd15306f63a00fe6503cf93c6ee6d36b8e6f0 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Thu, 10 Oct 2024 20:54:15 -0300 Subject: [PATCH 07/40] feat: load dynlibs --- Cargo.lock | 2294 +++-------------- Cargo.toml | 71 +- crates/file-explorer/Cargo.toml | 12 + crates/file-explorer/src/lib.rs | 22 + crates/http-server-plugin/Cargo.toml | 14 + crates/http-server-plugin/build.rs | 4 + crates/http-server-plugin/src/lib.rs | 41 + crates/http-server/Cargo.toml | 27 + crates/http-server/src/cli.rs | 15 + crates/http-server/src/config.rs | 6 + crates/http-server/src/main.rs | 41 + crates/http-server/src/plugin.rs | 99 + {src => crates/http-server/src}/server/mod.rs | 0 src/cli.rs | 9 - src/main.rs | 21 - 15 files changed, 613 insertions(+), 2063 deletions(-) create mode 100644 crates/file-explorer/Cargo.toml create mode 100644 crates/file-explorer/src/lib.rs create mode 100644 crates/http-server-plugin/Cargo.toml create mode 100644 crates/http-server-plugin/build.rs create mode 100644 crates/http-server-plugin/src/lib.rs create mode 100644 crates/http-server/Cargo.toml create mode 100644 crates/http-server/src/cli.rs create mode 100644 crates/http-server/src/config.rs create mode 100644 crates/http-server/src/main.rs create mode 100644 crates/http-server/src/plugin.rs rename {src => crates/http-server/src}/server/mod.rs (100%) delete mode 100644 src/cli.rs delete mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock index f8b9e0a4..fb96e773 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,70 +18,59 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] -name = "android-tzdata" -version = "0.1.1" +name = "anstream" +version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - -[[package]] -name = "android_system_properties" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ - "libc", + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", ] -[[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - [[package]] name = "anstyle" -version = "1.0.1" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a30da5c5f2d5e72842e00bcb57657162cdabef0931f40e2deb9b4140440cecd" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] -name = "anyhow" -version = "1.0.75" +name = "anstyle-parse" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" +dependencies = [ + "utf8parse", +] [[package]] -name = "async-stream" -version = "0.3.5" +name = "anstyle-query" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd56dd203fef61ac097dd65721a419ddccb106b2d2b70ba60a6b529f03961a51" +checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", + "windows-sys", ] [[package]] -name = "async-stream-impl" -version = "0.3.5" +name = "anstyle-wincon" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e62a023e7c117e27523144c5d2459f4397fcc3cab0085af8e2224f643a0193" +checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.32", + "anstyle", + "windows-sys", ] [[package]] -name = "async-trait" -version = "0.1.74" +name = "anyhow" +version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.32", -] +checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" [[package]] name = "atomic-waker" @@ -89,39 +78,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "aws-lc-rs" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95446d919226d587817a7d21379e6eb099b97b45110a7f272a444ca5c54070" -dependencies = [ - "aws-lc-sys", - "mirai-annotations", - "paste", - "zeroize", -] - -[[package]] -name = "aws-lc-sys" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5055edc4a9a1b2a917a818258cdfb86a535947feebd9981adc99667a062c6f85" -dependencies = [ - "bindgen", - "cc", - "cmake", - "dunce", - "fs_extra", - "libc", - "paste", -] - [[package]] name = "backtrace" version = "0.3.65" @@ -132,7 +88,7 @@ dependencies = [ "cc", "cfg-if", "libc", - "miniz_oxide 0.5.1", + "miniz_oxide", "object", "rustc-demangle", ] @@ -144,1350 +100,415 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] -name = "base64" -version = "0.21.2" +name = "bitflags" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" [[package]] -name = "base64" -version = "0.22.1" +name = "bytes" +version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] -name = "bindgen" -version = "0.69.4" +name = "cc" +version = "1.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a00dc851838a2120612785d195287475a3ac45514741da670b735818822129a0" +checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" dependencies = [ - "bitflags 2.4.0", - "cexpr", - "clang-sys", - "itertools", - "lazy_static", - "lazycell", - "log", - "prettyplease", - "proc-macro2", - "quote", - "regex", - "rustc-hash", "shlex", - "syn 2.0.32", - "which", ] [[package]] -name = "bitflags" -version = "1.3.2" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "bitflags" -version = "2.4.0" +name = "clap" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +dependencies = [ + "clap_builder", + "clap_derive", +] [[package]] -name = "block-buffer" -version = "0.7.3" +name = "clap_builder" +version = "4.5.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" dependencies = [ - "block-padding", - "byte-tools", - "byteorder", - "generic-array", + "anstream", + "anstyle", + "clap_lex", + "strsim", ] [[package]] -name = "block-padding" -version = "0.1.5" +name = "clap_derive" +version = "4.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" dependencies = [ - "byte-tools", + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] -name = "bumpalo" -version = "3.9.1" +name = "clap_lex" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] -name = "byte-tools" -version = "0.3.1" +name = "colorchoice" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" +checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] -name = "byteorder" -version = "1.4.3" +name = "equivalent" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" +checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" [[package]] -name = "bytes" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +name = "file-explorer" +version = "0.0.0" +dependencies = [ + "http-server-plugin", +] [[package]] -name = "cast" -version = "0.3.0" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "cc" -version = "1.1.18" +name = "futures-channel" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ - "jobserver", - "libc", - "shlex", + "futures-core", ] [[package]] -name = "cexpr" -version = "0.6.0" +name = "futures-core" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" -dependencies = [ - "nom", -] +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] -name = "cfg-if" -version = "1.0.0" +name = "futures-sink" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] -name = "chrono" -version = "0.4.31" +name = "futures-task" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38" -dependencies = [ - "android-tzdata", - "iana-time-zone", - "js-sys", - "num-traits", - "serde", - "wasm-bindgen", - "windows-targets 0.48.1", -] +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] -name = "ciborium" -version = "0.2.0" +name = "futures-util" +version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0c137568cc60b904a7724001b35ce2630fd00d5d84805fbb608ab89509d788f" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", ] [[package]] -name = "ciborium-io" -version = "0.2.0" +name = "gimli" +version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "346de753af073cc87b52b2083a506b38ac176a44cfb05497b622e27be899b369" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" [[package]] -name = "ciborium-ll" -version = "0.2.0" +name = "h2" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "213030a2b5a4e0c0892b6652260cf6ccac84827b83a85a534e178e3906c4cf1b" +checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" dependencies = [ - "ciborium-io", - "half", + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", ] [[package]] -name = "clang-sys" -version = "1.8.1" +name = "hashbrown" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" -dependencies = [ - "glob", - "libc", - "libloading", -] +checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" [[package]] -name = "clap" -version = "2.34.0" +name = "heck" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" -dependencies = [ - "bitflags 1.3.2", - "textwrap", - "unicode-width", -] +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] -name = "clap" -version = "4.3.23" +name = "hermit-abi" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03aef18ddf7d879c15ce20f04826ef8418101c7e528014c3eeea13321047dca3" -dependencies = [ - "clap_builder", -] +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] -name = "clap_builder" -version = "4.3.23" +name = "http" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ce6fffb678c9b80a70b6b6de0aad31df727623a70fd9a842c30cd573e2fa98" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ - "anstyle", - "clap_lex", + "bytes", + "fnv", + "itoa", ] [[package]] -name = "clap_lex" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2da6da31387c7e4ef160ffab6d5e7f00c42626fe39aea70a7b0f1773f7dd6c1b" - -[[package]] -name = "cmake" -version = "0.1.51" +name = "http-auth-basic" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb1e43aa7fd152b1f968787f7dbcdeb306d1867ff373c69955211876c053f91a" +checksum = "dd2e17aacf7f4a2428def798e2ff4f4f883c0987bdaf47dd5c8bc027bc9f1ebc" dependencies = [ - "cc", + "base64", ] [[package]] -name = "core-foundation" -version = "0.9.3" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ - "core-foundation-sys", - "libc", + "bytes", + "http", ] [[package]] -name = "core-foundation-sys" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" - -[[package]] -name = "crc32fast" -version = "1.3.2" +name = "http-body-util" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ - "cfg-if", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", ] [[package]] -name = "criterion" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +name = "http-server" +version = "1.0.0-draft+1" dependencies = [ - "anes", - "cast", - "ciborium", - "clap 4.3.23", - "criterion-plot", - "futures", - "is-terminal", - "itertools", - "num-traits", - "once_cell", - "oorandom", - "plotters", - "rayon", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", + "anyhow", + "clap", + "http-auth-basic", + "http-body-util", + "http-server-plugin", + "hyper", + "hyper-util", + "libloading", "tokio", - "walkdir", + "tower", + "tower-http", ] [[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +name = "http-server-plugin" +version = "1.0.0-draft+1" dependencies = [ - "cast", - "itertools", + "rustc_version", ] [[package]] -name = "crossbeam-channel" -version = "0.5.4" +name = "httparse" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] -name = "crossbeam-deque" -version = "0.8.1" +name = "httpdate" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] -name = "crossbeam-epoch" -version = "0.9.8" +name = "hyper" +version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ - "autocfg", - "cfg-if", - "crossbeam-utils", - "lazy_static", - "memoffset", - "scopeguard", + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", ] [[package]] -name = "crossbeam-utils" -version = "0.8.8" +name = "hyper-util" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +checksum = "41296eb09f183ac68eec06e03cdbea2e759633d4067b2f6552fc2e009bcad08b" dependencies = [ - "cfg-if", - "lazy_static", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", ] [[package]] -name = "digest" -version = "0.8.1" +name = "indexmap" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" dependencies = [ - "generic-array", + "equivalent", + "hashbrown", ] [[package]] -name = "dunce" -version = "1.0.5" +name = "is_terminal_polyfill" +version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] -name = "either" -version = "1.6.1" +name = "itoa" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" [[package]] -name = "equivalent" -version = "1.0.0" +name = "libc" +version = "0.2.158" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "fake-simd" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" - -[[package]] -name = "flate2" -version = "1.0.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" -dependencies = [ - "crc32fast", - "miniz_oxide 0.7.1", -] - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "fs_extra" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c" - -[[package]] -name = "futures" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" - -[[package]] -name = "futures-executor" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" - -[[package]] -name = "futures-macro" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.32", -] - -[[package]] -name = "futures-sink" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" - -[[package]] -name = "futures-task" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" - -[[package]] -name = "futures-util" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "generic-array" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" -dependencies = [ - "typenum", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gimli" -version = "0.26.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" - -[[package]] -name = "glob" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" - -[[package]] -name = "h2" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http 1.1.0", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - -[[package]] -name = "half" -version = "1.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" - -[[package]] -name = "handlebars" -version = "4.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c3372087601b532857d332f5957cbae686da52bb7810bf038c3e3c3cc2fa0d" -dependencies = [ - "log", - "pest", - "pest_derive", - "serde", - "serde_json", - "thiserror", -] - -[[package]] -name = "hashbrown" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" - -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hermit-abi" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b" - -[[package]] -name = "home" -version = "0.5.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" -dependencies = [ - "windows-sys 0.52.0", -] - -[[package]] -name = "http" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8947b1a6fad4393052c7ba1f4cd97bed3e953a95c79c92ad9b051a04611d9fbb" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - -[[package]] -name = "http-auth-basic" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd2e17aacf7f4a2428def798e2ff4f4f883c0987bdaf47dd5c8bc027bc9f1ebc" -dependencies = [ - "base64 0.13.0", -] - -[[package]] -name = "http-body" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" -dependencies = [ - "bytes", - "http 1.1.0", -] - -[[package]] -name = "http-body-util" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" -dependencies = [ - "bytes", - "futures-util", - "http 1.1.0", - "http-body", - "pin-project-lite", -] - -[[package]] -name = "http-server" -version = "1.0.0-draft+1" -dependencies = [ - "anyhow", - "async-stream", - "async-trait", - "chrono", - "criterion", - "flate2", - "futures", - "handlebars", - "http 0.2.11", - "http-auth-basic", - "http-body-util", - "humansize", - "hyper", - "hyper-rustls", - "hyper-util", - "lazy_static", - "local-ip-address", - "mime_guess", - "percent-encoding", - "rustls 0.20.6", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "structopt", - "termcolor", - "tokio", - "tokio-rustls 0.23.4", - "toml", - "tower 0.5.1", - "tower-http", -] - -[[package]] -name = "httparse" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" - -[[package]] -name = "httpdate" -version = "1.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -[[package]] -name = "humansize" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" -dependencies = [ - "libm", -] - -[[package]] -name = "hyper" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "h2", - "http 1.1.0", - "http-body", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "smallvec", - "tokio", - "want", -] - -[[package]] -name = "hyper-rustls" -version = "0.27.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" -dependencies = [ - "futures-util", - "http 1.1.0", - "hyper", - "hyper-util", - "log", - "rustls 0.23.12", - "rustls-native-certs", - "rustls-pki-types", - "tokio", - "tokio-rustls 0.26.0", - "tower-service", - "webpki-roots", -] - -[[package]] -name = "hyper-util" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" -dependencies = [ - "bytes", - "futures-channel", - "futures-util", - "http 1.1.0", - "http-body", - "hyper", - "pin-project-lite", - "socket2 0.5.7", - "tokio", - "tower 0.4.13", - "tower-service", - "tracing", -] - -[[package]] -name = "iana-time-zone" -version = "0.1.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fad5b825842d2b38bd206f3e81d6957625fd7f0a361e345c30e01a0ae2dd613" -dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows", -] - -[[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" -dependencies = [ - "cc", -] - -[[package]] -name = "indexmap" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" -dependencies = [ - "equivalent", - "hashbrown", -] - -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi 0.3.2", - "rustix", - "windows-sys 0.48.0", -] - -[[package]] -name = "itertools" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" - -[[package]] -name = "jobserver" -version = "0.1.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0" -dependencies = [ - "libc", -] - -[[package]] -name = "js-sys" -version = "0.3.59" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "258451ab10b34f8af53416d1fdab72c22e805f0c92a1136d59470ec0b11138b2" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "lazycell" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" - -[[package]] -name = "libc" -version = "0.2.158" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" - -[[package]] -name = "libloading" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" -dependencies = [ - "cfg-if", - "windows-targets 0.52.6", -] - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "local-ip-address" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136ef34e18462b17bf39a7826f8f3bbc223341f8e83822beb8b77db9a3d49696" -dependencies = [ - "libc", - "neli", - "thiserror", - "windows-sys 0.48.0", -] - -[[package]] -name = "lock_api" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "maplit" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" - -[[package]] -name = "memchr" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mime" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" - -[[package]] -name = "mime_guess" -version = "2.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" -dependencies = [ - "mime", - "unicase", -] - -[[package]] -name = "minimal-lexical" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" - -[[package]] -name = "miniz_oxide" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" -dependencies = [ - "adler", -] - -[[package]] -name = "miniz_oxide" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" -dependencies = [ - "adler", -] - -[[package]] -name = "mio" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2" -dependencies = [ - "libc", - "wasi", - "windows-sys 0.48.0", -] - -[[package]] -name = "mirai-annotations" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9be0862c1b3f26a88803c4a49de6889c10e608b3ee9344e6ef5b45fb37ad3d1" - -[[package]] -name = "neli" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1100229e06604150b3becd61a4965d5c70f3be1759544ea7274166f4be41ef43" -dependencies = [ - "byteorder", - "libc", - "log", - "neli-proc-macros", -] - -[[package]] -name = "neli-proc-macros" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4" -dependencies = [ - "either", - "proc-macro2", - "quote", - "serde", - "syn 1.0.96", -] - -[[package]] -name = "nom" -version = "7.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" -dependencies = [ - "memchr", - "minimal-lexical", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" -dependencies = [ - "hermit-abi 0.1.19", - "libc", -] - -[[package]] -name = "object" -version = "0.28.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" -dependencies = [ - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" - -[[package]] -name = "oorandom" -version = "11.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" - -[[package]] -name = "opaque-debug" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" - -[[package]] -name = "openssl-probe" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" - -[[package]] -name = "parking_lot" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f5ec2493a61ac0506c0f4199f99070cbe83857b0337006a30f3e6719b8ef58" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "995f667a6c822200b0433ac218e05582f0e2efa1b922a3fd2fbaadc5f87bab37" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-sys 0.34.0", -] - -[[package]] -name = "paste" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" - -[[package]] -name = "percent-encoding" -version = "2.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" - -[[package]] -name = "pest" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" -dependencies = [ - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn 1.0.96", -] - -[[package]] -name = "pest_meta" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d" -dependencies = [ - "maplit", - "pest", - "sha-1", -] - -[[package]] -name = "pin-project" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" -dependencies = [ - "pin-project-internal", -] - -[[package]] -name = "pin-project-internal" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.32", -] - -[[package]] -name = "pin-project-lite" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "plotters" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a3fd9ec30b9749ce28cd91f255d569591cdf937fe280c312143e3c4bad6f2a" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d88417318da0eaf0fdcdb51a0ee6c3bed624333bff8f946733049380be67ac1c" - -[[package]] -name = "plotters-svg" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "521fa9638fa597e1dc53e9412a4f9cefb01187ee1f7413076f9e6749e2885ba9" -dependencies = [ - "plotters-backend", -] - -[[package]] -name = "prettyplease" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" -dependencies = [ - "proc-macro2", - "syn 2.0.32", -] +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "libloading" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn 1.0.96", - "version_check", + "cfg-if", + "windows-targets", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "log" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "cfg-if", ] [[package]] -name = "proc-macro2" -version = "1.0.63" +name = "memchr" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b368fba921b0dce7e60f5e04ec15e565b3303972b42bcfde1d0713b881959eb" -dependencies = [ - "unicode-ident", -] +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] -name = "quote" -version = "1.0.29" +name = "miniz_oxide" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082" dependencies = [ - "proc-macro2", + "adler", ] [[package]] -name = "rayon" -version = "1.5.2" +name = "mio" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd249e82c21598a9a426a4e00dd7adc1d640b22445ec8545feef801d1a74c221" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", + "hermit-abi", + "libc", + "wasi", + "windows-sys", ] [[package]] -name = "rayon-core" -version = "1.9.2" +name = "object" +version = "0.28.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f51245e1e62e1f1629cbfec37b5793bbabcaeb90f30e94d2ba03564687353e4" +checksum = "40bec70ba014595f99f7aa110b84331ffe1ee9aece7fe6f387cc7e3ecda4d456" dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "num_cpus", + "memchr", ] [[package]] -name = "redox_syscall" -version = "0.2.13" +name = "once_cell" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" -dependencies = [ - "bitflags 1.3.2", -] +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] -name = "regex" -version = "1.5.5" +name = "pin-project-lite" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286" -dependencies = [ - "regex-syntax", -] +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] -name = "regex-syntax" -version = "0.6.25" +name = "pin-utils" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] -name = "ring" -version = "0.16.20" +name = "proc-macro2" +version = "1.0.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", + "unicode-ident", ] [[package]] -name = "ring" -version = "0.17.8" +name = "quote" +version = "1.0.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" dependencies = [ - "cc", - "cfg-if", - "getrandom", - "libc", - "spin 0.9.8", - "untrusted 0.9.0", - "windows-sys 0.52.0", + "proc-macro2", ] [[package]] @@ -1497,433 +518,106 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] -name = "rustc-hash" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" - -[[package]] -name = "rustix" -version = "0.38.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" -dependencies = [ - "bitflags 2.4.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustls" -version = "0.20.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aab8ee6c7097ed6057f43c187a62418d0c05a4bd5f18b3571db50ee0f9ce033" -dependencies = [ - "log", - "ring 0.16.20", - "sct", - "webpki", -] - -[[package]] -name = "rustls" -version = "0.23.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" -dependencies = [ - "aws-lc-rs", - "log", - "once_cell", - "rustls-pki-types", - "rustls-webpki", - "subtle", - "zeroize", -] - -[[package]] -name = "rustls-native-certs" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" -dependencies = [ - "openssl-probe", - "rustls-pemfile 2.1.3", - "rustls-pki-types", - "schannel", - "security-framework", -] - -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.2", -] - -[[package]] -name = "rustls-pemfile" -version = "2.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" -dependencies = [ - "base64 0.22.1", - "rustls-pki-types", -] - -[[package]] -name = "rustls-pki-types" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" - -[[package]] -name = "rustls-webpki" -version = "0.102.7" +name = "rustc_version" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84678086bd54edf2b415183ed7a94d0efb049f1b646a33e22a36f3794be6ae56" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ - "aws-lc-rs", - "ring 0.17.8", - "rustls-pki-types", - "untrusted 0.9.0", + "semver", ] [[package]] -name = "ryu" -version = "1.0.9" +name = "semver" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "schannel" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" -dependencies = [ - "lazy_static", - "winapi", -] - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "sct" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" -dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", -] - -[[package]] -name = "security-framework" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-foundation-sys", - "libc", - "security-framework-sys", -] - -[[package]] -name = "security-framework-sys" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "serde" -version = "1.0.192" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bca2a08484b285dcb282d0f67b26cadc0df8b19f8c12502c13d966bf9482f001" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.192" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6c7207fbec9faa48073f3e3074cbe553af6ea512d7c21ba46e434e70ea9fbc1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.32", -] - -[[package]] -name = "serde_json" -version = "1.0.108" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "serde_spanned" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96426c9936fd7a0124915f9185ea1d20aa9445cc9821142f0a73bc9207a2e186" -dependencies = [ - "serde", -] - -[[package]] -name = "sha-1" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" -dependencies = [ - "block-buffer", - "digest", - "fake-simd", - "opaque-debug", -] +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "signal-hook-registry" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" -dependencies = [ - "libc", -] - -[[package]] -name = "slab" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "socket2" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "socket2" -version = "0.5.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "structopt" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap 2.34.0", - "lazy_static", - "structopt-derive", -] - -[[package]] -name = "structopt-derive" -version = "0.4.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2", - "quote", - "syn 1.0.96", -] - -[[package]] -name = "subtle" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" - -[[package]] -name = "syn" -version = "1.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "syn" -version = "2.0.32" +name = "signal-hook-registry" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "libc", ] [[package]] -name = "sync_wrapper" -version = "0.1.2" +name = "slab" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" [[package]] -name = "termcolor" -version = "1.1.3" +name = "smallvec" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" -dependencies = [ - "winapi-util", -] +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] -name = "textwrap" -version = "0.11.0" +name = "socket2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ - "unicode-width", + "libc", + "windows-sys", ] [[package]] -name = "thiserror" -version = "1.0.30" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" -dependencies = [ - "thiserror-impl", -] +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] -name = "thiserror-impl" -version = "1.0.30" +name = "syn" +version = "2.0.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" +checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" dependencies = [ "proc-macro2", "quote", - "syn 1.0.96", + "unicode-ident", ] [[package]] -name = "tinytemplate" -version = "1.2.1" +name = "sync_wrapper" +version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" [[package]] name = "tokio" -version = "1.29.1" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ - "autocfg", "backtrace", "bytes", "libc", "mio", - "num_cpus", - "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.4.9", + "socket2", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] name = "tokio-macros" -version = "2.1.0" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.32", -] - -[[package]] -name = "tokio-rustls" -version = "0.23.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" -dependencies = [ - "rustls 0.20.6", - "tokio", - "webpki", -] - -[[package]] -name = "tokio-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" -dependencies = [ - "rustls 0.23.12", - "rustls-pki-types", - "tokio", + "syn", ] [[package]] @@ -1939,55 +633,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "toml" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c17e963a819c331dcacd7ab957d80bc2b9a9c1e71c804826d2f283dd65306542" -dependencies = [ - "serde", - "serde_spanned", - "toml_datetime", - "toml_edit", -] - -[[package]] -name = "toml_datetime" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" -dependencies = [ - "serde", -] - -[[package]] -name = "toml_edit" -version = "0.19.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c500344a19072298cd05a7224b3c0c629348b78692bf48466c5238656e315a78" -dependencies = [ - "indexmap", - "serde", - "serde_spanned", - "toml_datetime", - "winnow", -] - -[[package]] -name = "tower" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" -dependencies = [ - "futures-core", - "futures-util", - "pin-project", - "pin-project-lite", - "tokio", - "tower-layer", - "tower-service", -] - [[package]] name = "tower" version = "0.5.1" @@ -2008,9 +653,9 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" dependencies = [ - "bitflags 2.4.0", + "bitflags", "bytes", - "http 1.1.0", + "http", "pin-project-lite", "tower-layer", "tower-service", @@ -2053,27 +698,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" -[[package]] -name = "typenum" -version = "1.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" - -[[package]] -name = "ucd-trie" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" - -[[package]] -name = "unicase" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" -dependencies = [ - "version_check", -] - [[package]] name = "unicode-ident" version = "1.0.0" @@ -2081,45 +705,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] -name = "unicode-segmentation" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" - -[[package]] -name = "unicode-width" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" - -[[package]] -name = "untrusted" -version = "0.7.1" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - -[[package]] -name = "untrusted" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" - -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - -[[package]] -name = "walkdir" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" -dependencies = [ - "same-file", - "winapi", - "winapi-util", -] +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "want" @@ -2137,185 +726,13 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wasm-bindgen" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc7652e3f6c4706c8d9cd54832c4a4ccb9b5336e2c3bd154d5cccfbf1c1f5f7d" -dependencies = [ - "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "662cd44805586bd52971b9586b1df85cdbbd9112e4ef4d8f41559c334dc6ac3f" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 1.0.96", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b260f13d3012071dfb1512849c033b1925038373aea48ced3012c09df952c602" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be8e654bdd9b79216c2929ab90721aa82faf65c48cdf08bdc4e7f51357b80da" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.96", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6598dd0bd3c7d51095ff6531a5b23e02acdc81804e30d8f07afb77b7215a140a" - -[[package]] -name = "web-sys" -version = "0.3.57" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b17e741662c70c8bd24ac5c5b18de314a2c26c32bf8346ee1e6f53de919c283" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "webpki" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" -dependencies = [ - "ring 0.16.20", - "untrusted 0.7.1", -] - -[[package]] -name = "webpki-roots" -version = "0.26.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bd24728e5af82c6c4ec1b66ac4844bdf8156257fccda846ec58b42cd0cdbe6a" -dependencies = [ - "rustls-pki-types", -] - -[[package]] -name = "which" -version = "4.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" -dependencies = [ - "either", - "home", - "once_cell", - "rustix", -] - -[[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.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" -dependencies = [ - "winapi", -] - -[[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" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.1", -] - -[[package]] -name = "windows-sys" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5acdd78cb4ba54c0045ac14f62d8f94a03d10047904ae2a40afa1e99d8f70825" -dependencies = [ - "windows_aarch64_msvc 0.34.0", - "windows_i686_gnu 0.34.0", - "windows_i686_msvc 0.34.0", - "windows_x86_64_gnu 0.34.0", - "windows_x86_64_msvc 0.34.0", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.1", -] - [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05d4b17490f70499f20b9e791dcf6a299785ce8af4d709018206dc5b4953e95f" -dependencies = [ - "windows_aarch64_gnullvm 0.48.0", - "windows_aarch64_msvc 0.48.0", - "windows_i686_gnu 0.48.0", - "windows_i686_msvc 0.48.0", - "windows_x86_64_gnu 0.48.0", - "windows_x86_64_gnullvm 0.48.0", - "windows_x86_64_msvc 0.48.0", + "windows-targets", ] [[package]] @@ -2324,58 +741,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17cffbe740121affb56fad0fc0e421804adf0ae00891205213b5cecd30db881d" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2564fde759adb79129d9b4f54be42b32c89970c18ebf93124ca8870a498688ed" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2388,83 +775,26 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cd9d32ba70453522332c14d38814bceeb747d80b3958676007acadd7e166956" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfce6deae227ee8d356d19effc141a509cc503dfd1f850622ec4b0f84428e1f4" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.34.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d19538ccc21819d01deaf88d6a17eae6596a12e9aafdbb97916fb49896d89de9" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winnow" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81a2094c43cc94775293eaa0e499fbc30048a6d824ac82c0351a8c0bf9112529" -dependencies = [ - "memchr", -] - -[[package]] -name = "zeroize" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 17137e77..3602df8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,57 +1,26 @@ -[package] -name = "http-server" -version = "1.0.0-draft+1" -authors = ["Esteban Borai "] -edition = "2021" -description = "Simple and configurable command-line HTTP server" -repository = "https://github.com/http-server-rs/http-server" -categories = ["web-programming", "web-programming::http-server"] -keywords = ["configurable", "http", "server", "serve", "static"] -license = "MIT OR Apache-2.0" -readme = "README.md" +[workspace] +members = [ + "crates/file-explorer", + "crates/http-server", + "crates/http-server-plugin", +] +default-members = ["crates/http-server"] +resolver = "1" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[dependencies] -anyhow = "1.0.75" -async-stream = "0.3.5" -async-trait = "0.1.74" -chrono = { version = "0.4.31", features = ["serde"] } -futures = "0.3.30" -flate2 = "1.0.28" -http = "0.2.11" +[workspace.dependencies] +anyhow = "1.0" +clap = "4.5.20" http-auth-basic = "0.3.3" http-body-util = "0.1" -handlebars = "4.3.7" -hyper = { version = "1" } -hyper-rustls = { version = "0.27", features = ["webpki-roots"] } -hyper-util = { version = "0.1", features = ["full"] } -local-ip-address = "0.6.1" -mime_guess = "2.0.4" -percent-encoding = "2.2.0" -rustls = "0.20.6" -rustls-pemfile = "1.0.4" -serde = { version = "1.0.192", features = ["derive"] } -serde_json = "1.0.108" -structopt = { version = "0.3.26", default-features = false } -termcolor = "1.1.3" -tokio = { version = "1", features = [ - "fs", - "rt-multi-thread", - "signal", - "macros", -] } -tokio-rustls = "0.23.4" -toml = "0.7.6" -humansize = "2.1.3" -tower-http = { version = "0.6.1", features = ["cors"] } -tower = { version = "0.5.1", features = ["util"] } +hyper = "1.4" +hyper-util = "0.1.9" +libloading = "0.8.5" +rustc_version = "0.4.1" +tokio = "1.40" +tower-http = "0.6.1" +tower = "0.5.1" -[dev-dependencies] -criterion = { version = "0.5.1", features = ["async_tokio", "html_reports"] } -hyper = { version = "1", features = ["client"] } -tokio = { version = "1", features = ["full"] } -lazy_static = "1.4.0" - -[profile.release] -debug = 1 +# Workspace Crates +http-server-plugin = { path = "crates/http-server-plugin" } diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml new file mode 100644 index 00000000..db017bbd --- /dev/null +++ b/crates/file-explorer/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "file-explorer" +version = "0.0.0" +authors = ["Esteban Borai "] +edition = "2021" +publish = false + +[lib] +crate-type = ["cdylib"] + +[dependencies] +http-server-plugin = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs new file mode 100644 index 00000000..4434dad4 --- /dev/null +++ b/crates/file-explorer/src/lib.rs @@ -0,0 +1,22 @@ +use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; + +export_plugin!(register); + +extern "C" fn register(registrar: &mut dyn PluginRegistrar) { + registrar.register_function( + "file-explorer", + Box::new(FileExplorer { + path: String::from("/"), + }), + ); +} + +pub struct FileExplorer { + pub path: String, +} + +impl Function for FileExplorer { + fn call(&self, _args: &[f64]) -> Result { + Ok(0.0) + } +} diff --git a/crates/http-server-plugin/Cargo.toml b/crates/http-server-plugin/Cargo.toml new file mode 100644 index 00000000..31a142f7 --- /dev/null +++ b/crates/http-server-plugin/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "http-server-plugin" +version = "1.0.0-draft+1" +authors = ["Esteban Borai "] +edition = "2021" +description = "HTTP Server RS Plugin Crate" +repository = "https://github.com/http-server-rs/http-server" +categories = ["web-programming", "web-programming::http-server"] +keywords = ["sdk", "http", "server", "plugin", "dll"] +license = "MIT OR Apache-2.0" +readme = "README.md" + +[build-dependencies] +rustc_version = { workspace = true } diff --git a/crates/http-server-plugin/build.rs b/crates/http-server-plugin/build.rs new file mode 100644 index 00000000..a38022ff --- /dev/null +++ b/crates/http-server-plugin/build.rs @@ -0,0 +1,4 @@ +fn main() { + let version = rustc_version::version().unwrap(); + println!("cargo:rustc-env=RUSTC_VERSION={}", version); +} diff --git a/crates/http-server-plugin/src/lib.rs b/crates/http-server-plugin/src/lib.rs new file mode 100644 index 00000000..d4a45c62 --- /dev/null +++ b/crates/http-server-plugin/src/lib.rs @@ -0,0 +1,41 @@ +pub static CORE_VERSION: &str = env!("CARGO_PKG_VERSION"); +pub static RUSTC_VERSION: &str = env!("RUSTC_VERSION"); + +pub trait Function { + fn call(&self, args: &[f64]) -> Result; + + /// Help text that may be used to display information about this function. + fn help(&self) -> Option<&str> { + None + } +} + +#[derive(Debug)] +pub enum InvocationError { + InvalidArgumentCount { expected: usize, found: usize }, + Other { msg: String }, +} + +#[allow(improper_ctypes_definitions)] +pub struct PluginDeclaration { + pub rustc_version: &'static str, + pub core_version: &'static str, + pub register: unsafe extern "C" fn(&mut dyn PluginRegistrar), +} + +pub trait PluginRegistrar { + fn register_function(&mut self, name: &str, function: Box); +} + +#[macro_export] +macro_rules! export_plugin { + ($register:expr) => { + #[doc(hidden)] + #[no_mangle] + pub static plugin_declaration: $crate::PluginDeclaration = $crate::PluginDeclaration { + rustc_version: $crate::RUSTC_VERSION, + core_version: $crate::CORE_VERSION, + register: $register, + }; + }; +} diff --git a/crates/http-server/Cargo.toml b/crates/http-server/Cargo.toml new file mode 100644 index 00000000..fcf9b28f --- /dev/null +++ b/crates/http-server/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "http-server" +version = "1.0.0-draft+1" +authors = ["Esteban Borai "] +edition = "2021" +description = "Simple and configurable command-line HTTP server" +repository = "https://github.com/http-server-rs/http-server" +categories = ["web-programming", "web-programming::http-server"] +keywords = ["configurable", "http", "server", "serve", "static"] +license = "MIT OR Apache-2.0" +readme = "README.md" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = { workspace = true } +clap = { workspace = true, features = ["env", "derive", "std"] } +http-auth-basic = { workspace = true } +http-body-util = { workspace = true } +hyper = { workspace = true } +hyper-util = { workspace = true, features = ["full"] } +libloading = { workspace = true } +tokio = { workspace = true, features = ["fs", "rt-multi-thread", "signal", "macros"] } +tower-http = { workspace = true, features = ["cors"] } +tower = { workspace = true, features = ["util"] } + +http-server-plugin = { workspace = true } diff --git a/crates/http-server/src/cli.rs b/crates/http-server/src/cli.rs new file mode 100644 index 00000000..fd05e0dd --- /dev/null +++ b/crates/http-server/src/cli.rs @@ -0,0 +1,15 @@ +use std::net::IpAddr; + +use clap::Parser; + +#[derive(Debug, Parser)] +#[command( + name = "http-server", + author = "Esteban Borai ", + about = "Simple and configurable command-line HTTP server\nSource: https://github.com/EstebanBorai/http-server", + next_line_help = true +)] +pub struct Cli { + pub host: IpAddr, + pub port: u16, +} diff --git a/crates/http-server/src/config.rs b/crates/http-server/src/config.rs new file mode 100644 index 00000000..c06d6cd6 --- /dev/null +++ b/crates/http-server/src/config.rs @@ -0,0 +1,6 @@ +use std::net::IpAddr; + +pub struct Config { + pub host: IpAddr, + pub port: u16, +} diff --git a/crates/http-server/src/main.rs b/crates/http-server/src/main.rs new file mode 100644 index 00000000..3e61cf2f --- /dev/null +++ b/crates/http-server/src/main.rs @@ -0,0 +1,41 @@ +pub mod cli; +pub mod config; +pub mod plugin; +pub mod server; + +use std::{path::PathBuf, process::exit, str::FromStr}; + +use anyhow::Result; + +use crate::plugin::ExternalFunctions; + +use self::server::Server; + +#[tokio::main] +async fn main() -> Result<()> { + let mut functions = ExternalFunctions::new(); + let plugin_library = PathBuf::from_str("./target/debug/libfile_explorer.dylib").unwrap(); + + unsafe { + functions + .load(plugin_library) + .expect("Function loading failed"); + } + + let result = functions + .call("file-explorer", &[]) + .expect("Invocation failed"); + + println!("file-explorer() = {}", result); + + match Server::run().await { + Ok(_) => { + println!("Server exited successfuly"); + Ok(()) + } + Err(error) => { + eprint!("{:?}", error); + exit(1); + } + } +} diff --git a/crates/http-server/src/plugin.rs b/crates/http-server/src/plugin.rs new file mode 100644 index 00000000..8f9c11b3 --- /dev/null +++ b/crates/http-server/src/plugin.rs @@ -0,0 +1,99 @@ +use std::collections::HashMap; +use std::ffi::OsStr; +use std::io; +use std::rc::Rc; + +use libloading::Library; + +use http_server_plugin::{ + Function, InvocationError, PluginDeclaration, CORE_VERSION, RUSTC_VERSION, +}; + +/// A proxy object which wraps a [`Function`] and makes sure it can't outlive +/// the library it came from. +pub struct FunctionProxy { + function: Box, + _lib: Rc, +} + +impl Function for FunctionProxy { + fn call(&self, args: &[f64]) -> Result { + self.function.call(args) + } + + fn help(&self) -> Option<&str> { + self.function.help() + } +} + +#[derive(Default)] +pub struct ExternalFunctions { + functions: HashMap, + libraries: Vec>, +} + +impl ExternalFunctions { + pub fn new() -> ExternalFunctions { + ExternalFunctions::default() + } + + pub unsafe fn load>(&mut self, library_path: P) -> io::Result<()> { + // load the library into memory + let library = Rc::new(Library::new(library_path).unwrap()); + + // get a pointer to the plugin_declaration symbol. + let decl = library + .get::<*mut PluginDeclaration>(b"plugin_declaration\0") + .unwrap() + .read(); + + // version checks to prevent accidental ABI incompatibilities + if decl.rustc_version != RUSTC_VERSION || decl.core_version != CORE_VERSION { + return Err(io::Error::new(io::ErrorKind::Other, "Version mismatch")); + } + + let mut registrar = PluginRegistrar::new(Rc::clone(&library)); + + (decl.register)(&mut registrar); + + // add all loaded plugins to the functions map + self.functions.extend(registrar.functions); + // and make sure ExternalFunctions keeps a reference to the library + self.libraries.push(library); + + Ok(()) + } + + pub fn call(&self, function: &str, arguments: &[f64]) -> Result { + self.functions + .get(function) + .ok_or_else(|| format!("\"{}\" not found", function)) + .unwrap() + .call(arguments) + } +} + +struct PluginRegistrar { + functions: HashMap, + lib: Rc, +} + +impl PluginRegistrar { + fn new(lib: Rc) -> PluginRegistrar { + PluginRegistrar { + lib, + functions: HashMap::default(), + } + } +} + +impl http_server_plugin::PluginRegistrar for PluginRegistrar { + fn register_function(&mut self, name: &str, function: Box) { + let proxy = FunctionProxy { + function, + _lib: Rc::clone(&self.lib), + }; + + self.functions.insert(name.to_string(), proxy); + } +} diff --git a/src/server/mod.rs b/crates/http-server/src/server/mod.rs similarity index 100% rename from src/server/mod.rs rename to crates/http-server/src/server/mod.rs diff --git a/src/cli.rs b/src/cli.rs deleted file mode 100644 index 67a575ae..00000000 --- a/src/cli.rs +++ /dev/null @@ -1,9 +0,0 @@ -use structopt::StructOpt; - -#[derive(Debug, StructOpt, PartialEq, Eq)] -#[structopt( - name = "http-server", - author = "Esteban Borai ", - about = "Simple and configurable command-line HTTP server\nSource: https://github.com/EstebanBorai/http-server" -)] -pub struct Cli {} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index cd8c2424..00000000 --- a/src/main.rs +++ /dev/null @@ -1,21 +0,0 @@ -pub mod cli; -pub mod server; - -use std::process::exit; - -use anyhow::Result; - -use server::Server; -#[tokio::main] -async fn main() -> Result<()> { - match Server::run().await { - Ok(_) => { - println!("Server exited successfuly"); - Ok(()) - } - Err(error) => { - eprint!("{:?}", error); - exit(1); - } - } -} From 39b11bee06bea7d98ef34f1a75d0aea151c6b962 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Thu, 10 Oct 2024 21:21:28 -0300 Subject: [PATCH 08/40] feat: load service as library --- Cargo.lock | 4 ++ crates/file-explorer/Cargo.toml | 3 ++ crates/file-explorer/src/lib.rs | 13 ++++-- crates/http-server-plugin/Cargo.toml | 4 ++ crates/http-server-plugin/src/lib.rs | 19 ++++---- crates/http-server/src/main.rs | 19 +------- crates/http-server/src/plugin.rs | 68 ++++++++++++++++------------ crates/http-server/src/server/mod.rs | 38 ++++++++++++---- 8 files changed, 100 insertions(+), 68 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb96e773..bb623940 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -182,7 +182,9 @@ checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" name = "file-explorer" version = "0.0.0" dependencies = [ + "http-body-util", "http-server-plugin", + "hyper", ] [[package]] @@ -337,6 +339,8 @@ dependencies = [ name = "http-server-plugin" version = "1.0.0-draft+1" dependencies = [ + "http-body-util", + "hyper", "rustc_version", ] diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index db017bbd..6c015151 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -9,4 +9,7 @@ publish = false crate-type = ["cdylib"] [dependencies] +http-body-util = { workspace = true } +hyper = { workspace = true } + http-server-plugin = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 4434dad4..23b23cc1 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -1,11 +1,18 @@ +use std::sync::Arc; + +use http_body_util::Full; +use hyper::body::{Bytes, Incoming}; +use hyper::{Request, Response}; + use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; export_plugin!(register); +#[allow(improper_ctypes_definitions)] extern "C" fn register(registrar: &mut dyn PluginRegistrar) { registrar.register_function( "file-explorer", - Box::new(FileExplorer { + Arc::new(FileExplorer { path: String::from("/"), }), ); @@ -16,7 +23,7 @@ pub struct FileExplorer { } impl Function for FileExplorer { - fn call(&self, _args: &[f64]) -> Result { - Ok(0.0) + fn call(&self, _: Request) -> Result>, InvocationError> { + Ok(Response::new(Full::new(Bytes::from("Hello, World!")))) } } diff --git a/crates/http-server-plugin/Cargo.toml b/crates/http-server-plugin/Cargo.toml index 31a142f7..382a6078 100644 --- a/crates/http-server-plugin/Cargo.toml +++ b/crates/http-server-plugin/Cargo.toml @@ -10,5 +10,9 @@ keywords = ["sdk", "http", "server", "plugin", "dll"] license = "MIT OR Apache-2.0" readme = "README.md" +[dependencies] +http-body-util = { workspace = true } +hyper = { workspace = true } + [build-dependencies] rustc_version = { workspace = true } diff --git a/crates/http-server-plugin/src/lib.rs b/crates/http-server-plugin/src/lib.rs index d4a45c62..5870825b 100644 --- a/crates/http-server-plugin/src/lib.rs +++ b/crates/http-server-plugin/src/lib.rs @@ -1,13 +1,14 @@ +use std::sync::Arc; + +use http_body_util::Full; +use hyper::body::{Bytes, Incoming}; +use hyper::{Request, Response}; + pub static CORE_VERSION: &str = env!("CARGO_PKG_VERSION"); pub static RUSTC_VERSION: &str = env!("RUSTC_VERSION"); -pub trait Function { - fn call(&self, args: &[f64]) -> Result; - - /// Help text that may be used to display information about this function. - fn help(&self) -> Option<&str> { - None - } +pub trait Function: Send + Sync { + fn call(&self, req: Request) -> Result>, InvocationError>; } #[derive(Debug)] @@ -24,7 +25,7 @@ pub struct PluginDeclaration { } pub trait PluginRegistrar { - fn register_function(&mut self, name: &str, function: Box); + fn register_function(&mut self, name: &str, function: Arc); } #[macro_export] @@ -32,7 +33,7 @@ macro_rules! export_plugin { ($register:expr) => { #[doc(hidden)] #[no_mangle] - pub static plugin_declaration: $crate::PluginDeclaration = $crate::PluginDeclaration { + pub static PLUGIN_DECLARATION: $crate::PluginDeclaration = $crate::PluginDeclaration { rustc_version: $crate::RUSTC_VERSION, core_version: $crate::CORE_VERSION, register: $register, diff --git a/crates/http-server/src/main.rs b/crates/http-server/src/main.rs index 3e61cf2f..c6796375 100644 --- a/crates/http-server/src/main.rs +++ b/crates/http-server/src/main.rs @@ -3,31 +3,14 @@ pub mod config; pub mod plugin; pub mod server; -use std::{path::PathBuf, process::exit, str::FromStr}; +use std::process::exit; use anyhow::Result; -use crate::plugin::ExternalFunctions; - use self::server::Server; #[tokio::main] async fn main() -> Result<()> { - let mut functions = ExternalFunctions::new(); - let plugin_library = PathBuf::from_str("./target/debug/libfile_explorer.dylib").unwrap(); - - unsafe { - functions - .load(plugin_library) - .expect("Function loading failed"); - } - - let result = functions - .call("file-explorer", &[]) - .expect("Invocation failed"); - - println!("file-explorer() = {}", result); - match Server::run().await { Ok(_) => { println!("Server exited successfuly"); diff --git a/crates/http-server/src/plugin.rs b/crates/http-server/src/plugin.rs index 8f9c11b3..35f398cb 100644 --- a/crates/http-server/src/plugin.rs +++ b/crates/http-server/src/plugin.rs @@ -1,8 +1,11 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::io; -use std::rc::Rc; +use std::sync::{Arc, Mutex}; +use http_body_util::Full; +use hyper::body::{Bytes, Incoming}; +use hyper::{Request, Response}; use libloading::Library; use http_server_plugin::{ @@ -12,24 +15,20 @@ use http_server_plugin::{ /// A proxy object which wraps a [`Function`] and makes sure it can't outlive /// the library it came from. pub struct FunctionProxy { - function: Box, - _lib: Rc, + function: Arc, + _lib: Arc, } impl Function for FunctionProxy { - fn call(&self, args: &[f64]) -> Result { - self.function.call(args) - } - - fn help(&self) -> Option<&str> { - self.function.help() + fn call(&self, req: Request) -> Result>, InvocationError> { + self.function.call(req) } } #[derive(Default)] pub struct ExternalFunctions { - functions: HashMap, - libraries: Vec>, + functions: Mutex>, + libraries: Mutex>>, } impl ExternalFunctions { @@ -37,49 +36,62 @@ impl ExternalFunctions { ExternalFunctions::default() } - pub unsafe fn load>(&mut self, library_path: P) -> io::Result<()> { - // load the library into memory - let library = Rc::new(Library::new(library_path).unwrap()); - - // get a pointer to the plugin_declaration symbol. + /// Loads a plugin from the given path. + /// + /// # Safety + /// + /// This function is unsafe because it loads a shared library and calls + /// functions from it. + pub unsafe fn load>(&self, library_path: P) -> io::Result<()> { + let library = Arc::new(Library::new(library_path).unwrap()); let decl = library .get::<*mut PluginDeclaration>(b"plugin_declaration\0") .unwrap() .read(); - // version checks to prevent accidental ABI incompatibilities if decl.rustc_version != RUSTC_VERSION || decl.core_version != CORE_VERSION { return Err(io::Error::new(io::ErrorKind::Other, "Version mismatch")); } - let mut registrar = PluginRegistrar::new(Rc::clone(&library)); + let mut registrar = PluginRegistrar::new(Arc::clone(&library)); (decl.register)(&mut registrar); - // add all loaded plugins to the functions map - self.functions.extend(registrar.functions); - // and make sure ExternalFunctions keeps a reference to the library - self.libraries.push(library); + self.functions + .lock() + .expect("Cannot lock Mutex") + .extend(registrar.functions); + + self.libraries + .lock() + .expect("Cannot lock Mutex") + .push(library); Ok(()) } - pub fn call(&self, function: &str, arguments: &[f64]) -> Result { + pub fn call( + &self, + function: &str, + req: Request, + ) -> Result>, InvocationError> { self.functions + .lock() + .expect("Cannot lock Mutex") .get(function) .ok_or_else(|| format!("\"{}\" not found", function)) .unwrap() - .call(arguments) + .call(req) } } struct PluginRegistrar { functions: HashMap, - lib: Rc, + lib: Arc, } impl PluginRegistrar { - fn new(lib: Rc) -> PluginRegistrar { + fn new(lib: Arc) -> PluginRegistrar { PluginRegistrar { lib, functions: HashMap::default(), @@ -88,10 +100,10 @@ impl PluginRegistrar { } impl http_server_plugin::PluginRegistrar for PluginRegistrar { - fn register_function(&mut self, name: &str, function: Box) { + fn register_function(&mut self, name: &str, function: Arc) { let proxy = FunctionProxy { function, - _lib: Rc::clone(&self.lib), + _lib: Arc::clone(&self.lib), }; self.functions.insert(name.to_string(), proxy); diff --git a/crates/http-server/src/server/mod.rs b/crates/http-server/src/server/mod.rs index a6ed4094..b7ed5c6a 100644 --- a/crates/http-server/src/server/mod.rs +++ b/crates/http-server/src/server/mod.rs @@ -1,20 +1,14 @@ -use std::{convert::Infallible, net::SocketAddr}; +use std::{convert::Infallible, net::SocketAddr, path::PathBuf, str::FromStr, sync::Arc}; use anyhow::Result; use http_body_util::Full; -use hyper::{ - body::{Bytes, Incoming}, - server::conn::http1, - Method, Request, Response, -}; +use hyper::{body::Bytes, server::conn::http1, Method, Response}; use hyper_util::{rt::TokioIo, service::TowerToHyperService}; use tokio::net::TcpListener; use tower::ServiceBuilder; use tower_http::cors::{Any, CorsLayer}; -async fn hello(_: Request) -> Result>, Infallible> { - Ok(Response::new(Full::new(Bytes::from("Hello, World!")))) -} +use crate::plugin::ExternalFunctions; pub struct Server {} @@ -22,10 +16,19 @@ impl Server { pub async fn run() -> Result<()> { let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = TcpListener::bind(addr).await?; + let functions = Arc::new(ExternalFunctions::new()); + let plugin_library = PathBuf::from_str("./target/debug/libfile_explorer.dylib").unwrap(); + + unsafe { + functions + .load(plugin_library) + .expect("Function loading failed"); + } loop { let (stream, _) = listener.accept().await?; let io = TokioIo::new(stream); + let functions = Arc::clone(&functions); let cors = CorsLayer::new() // allow `GET` and `POST` when accessing the resource @@ -34,8 +37,23 @@ impl Server { .allow_origin(Any); tokio::spawn(async move { + let functions = Arc::clone(&functions); + // N.B. should use tower service_fn here, since it's reuqired to be implemented tower Service trait before convert to hyper Service! - let svc = tower::service_fn(hello); + let svc = tower::service_fn(|req| async { + match functions.call("file-explorer", req) { + Ok(res) => Ok::< + Response>, + Infallible, + >(res), + Err(err) => { + eprintln!("Error: {:?}", err); + Ok(Response::new(Full::new(Bytes::from( + "Internal Server Error", + )))) + } + } + }); let svc = ServiceBuilder::new().layer(cors).service(svc); // Convert it to hyper service let svc = TowerToHyperService::new(svc); From 1168e0353d01b07232628410df91b1314ea4150b Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 12 Oct 2024 20:56:39 -0300 Subject: [PATCH 09/40] feat: introduce templater --- Cargo.lock | 376 +++++++++++++++++- Cargo.toml | 5 + crates/file-explorer/Cargo.toml | 6 + crates/file-explorer/src/lib.rs | 34 +- .../file-explorer/src/templater/hbs/error.hbs | 4 + .../src/templater/hbs/explorer.hbs | 249 ++++++++++++ crates/file-explorer/src/templater/mod.rs | 49 +++ 7 files changed, 712 insertions(+), 11 deletions(-) create mode 100644 crates/file-explorer/src/templater/hbs/error.hbs create mode 100644 crates/file-explorer/src/templater/hbs/explorer.hbs create mode 100644 crates/file-explorer/src/templater/mod.rs diff --git a/Cargo.lock b/Cargo.lock index bb623940..fc17c72d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,21 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.15" @@ -78,6 +93,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "autocfg" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + [[package]] name = "backtrace" version = "0.3.65" @@ -105,6 +126,21 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + [[package]] name = "bytes" version = "1.7.1" @@ -126,6 +162,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", + "num-traits", + "serde", + "wasm-bindgen", + "windows-targets", +] + [[package]] name = "clap" version = "4.5.20" @@ -172,6 +223,41 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + [[package]] name = "equivalent" version = "1.0.0" @@ -182,9 +268,15 @@ checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" name = "file-explorer" version = "0.0.0" dependencies = [ + "anyhow", + "chrono", + "handlebars", "http-body-util", "http-server-plugin", + "humansize", "hyper", + "serde", + "serde_json", ] [[package]] @@ -232,6 +324,16 @@ dependencies = [ "pin-utils", ] +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + [[package]] name = "gimli" version = "0.26.1" @@ -257,6 +359,20 @@ dependencies = [ "tracing", ] +[[package]] +name = "handlebars" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce25b617d1375ef96eeb920ae717e3da34a02fc979fe632c75128350f9e1f74a" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "hashbrown" version = "0.14.0" @@ -356,6 +472,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "humansize" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7" +dependencies = [ + "libm", +] + [[package]] name = "hyper" version = "1.4.1" @@ -396,6 +521,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "indexmap" version = "2.0.0" @@ -418,6 +566,15 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "js-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.158" @@ -434,6 +591,12 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "log" version = "0.4.16" @@ -470,6 +633,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.28.3" @@ -485,6 +657,51 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "pest" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" +dependencies = [ + "memchr", + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -508,9 +725,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.29" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573015e8ab27661678357f27dc26460738fd2b6c86e46f386fde94cb5d913105" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -530,12 +747,61 @@ dependencies = [ "semver", ] +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + [[package]] name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +[[package]] +name = "serde" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.210" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "shlex" version = "1.3.0" @@ -581,9 +847,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.32" +version = "2.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "239814284fd6f1a4ffe4ca893952cdd93c224b6a1571c9a9eadd670295c0c9e2" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" dependencies = [ "proc-macro2", "quote", @@ -596,6 +862,26 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "thiserror" +version = "1.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e3de26b0965292219b4287ff031fcba86837900fe9cd2b34ea8ad893c0953d2" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tokio" version = "1.40.0" @@ -702,6 +988,18 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + [[package]] name = "unicode-ident" version = "1.0.0" @@ -714,6 +1012,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "want" version = "0.3.0" @@ -730,6 +1034,70 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasm-bindgen" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index 3602df8c..f940776c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,13 +11,18 @@ resolver = "1" [workspace.dependencies] anyhow = "1.0" +chrono = "0.4.38" clap = "4.5.20" +handlebars = "6.1.0" +humansize = "2.1.3" http-auth-basic = "0.3.3" http-body-util = "0.1" hyper = "1.4" hyper-util = "0.1.9" libloading = "0.8.5" rustc_version = "0.4.1" +serde = "1.0.210" +serde_json = "1.0.128" tokio = "1.40" tower-http = "0.6.1" tower = "0.5.1" diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index 6c015151..a07f0dcb 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -9,7 +9,13 @@ publish = false crate-type = ["cdylib"] [dependencies] +anyhow = { workspace = true } +chrono = { workspace = true, features = ["serde"] } +handlebars = { workspace = true } http-body-util = { workspace = true } +humansize = { workspace = true } hyper = { workspace = true } +serde = { workspace = true } +serde_json = { workspace = true } http-server-plugin = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 23b23cc1..36cc2c20 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -1,29 +1,49 @@ +mod templater; + +use std::path::PathBuf; use std::sync::Arc; +use anyhow::Result; use http_body_util::Full; use hyper::body::{Bytes, Incoming}; -use hyper::{Request, Response}; +use hyper::{Method, Request, Response}; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; +use self::templater::Templater; + export_plugin!(register); #[allow(improper_ctypes_definitions)] extern "C" fn register(registrar: &mut dyn PluginRegistrar) { registrar.register_function( "file-explorer", - Arc::new(FileExplorer { - path: String::from("/"), - }), + Arc::new(FileExplorer::new(PathBuf::new()).expect("Failed to create FileExplorer")), ); } pub struct FileExplorer { - pub path: String, + pub path: PathBuf, + pub templater: Templater, } impl Function for FileExplorer { - fn call(&self, _: Request) -> Result>, InvocationError> { - Ok(Response::new(Full::new(Bytes::from("Hello, World!")))) + fn call(&self, req: Request) -> Result>, InvocationError> { + match req.method() { + &Method::GET => Ok(Response::new(Full::new(Bytes::from("File Explorer")))), + &Method::POST => Ok(Response::new(Full::new(Bytes::from("Prepare to upload")))), + _ => Ok(Response::new(Full::new(Bytes::from("Unsupported method")))), + } + } +} + +impl FileExplorer { + pub fn new(path: PathBuf) -> Result { + let templater = Templater::new()?; + + Ok(Self { + path, + templater, + }) } } diff --git a/crates/file-explorer/src/templater/hbs/error.hbs b/crates/file-explorer/src/templater/hbs/error.hbs new file mode 100644 index 00000000..97bf040a --- /dev/null +++ b/crates/file-explorer/src/templater/hbs/error.hbs @@ -0,0 +1,4 @@ +
+

{{code}}

+

{{error}}

+
diff --git a/crates/file-explorer/src/templater/hbs/explorer.hbs b/crates/file-explorer/src/templater/hbs/explorer.hbs new file mode 100644 index 00000000..75c7939d --- /dev/null +++ b/crates/file-explorer/src/templater/hbs/explorer.hbs @@ -0,0 +1,249 @@ + + + + + File Explorer + + + + +
+ +
+ +
+
+ + diff --git a/crates/file-explorer/src/templater/mod.rs b/crates/file-explorer/src/templater/mod.rs new file mode 100644 index 00000000..310f98bf --- /dev/null +++ b/crates/file-explorer/src/templater/mod.rs @@ -0,0 +1,49 @@ +use anyhow::Result; +use chrono::{DateTime, Local}; +use handlebars::{handlebars_helper, Handlebars}; +use humansize::{format_size, DECIMAL}; + +const EXPLORER_KEY: &str = "Explorer"; +const EXPLORER_TPL: &[u8] = include_bytes!("./hbs/explorer.hbs"); + +pub struct Templater { + pub backend: Handlebars<'static>, +} + +impl Templater { + pub fn new() -> Result { + let mut hbs = Handlebars::new(); + + let explorer_tpl = String::from_utf8_lossy(EXPLORER_TPL); + + hbs.register_template_string(EXPLORER_KEY, explorer_tpl)?; + + handlebars_helper!(date: |d: Option>| { + match d { + Some(d) => d.format("%Y/%m/%d %H:%M:%S").to_string(), + None => "-".to_owned(), + } + }); + hbs.register_helper("date", Box::new(date)); + + handlebars_helper!(size: |bytes: u64| format_size(bytes, DECIMAL)); + hbs.register_helper("size", Box::new(size)); + + handlebars_helper!(sort_name: |sort: Sort| sort == Sort::Name); + hbs.register_helper("sort_name", Box::new(sort_name)); + + handlebars_helper!(sort_size: |sort: Sort| sort == Sort::Size); + hbs.register_helper("sort_size", Box::new(sort_size)); + + handlebars_helper!(sort_date_created: |sort: Sort| sort == Sort::DateCreated); + hbs.register_helper("sort_date_created", Box::new(sort_date_created)); + + handlebars_helper!(sort_date_modified: |sort: Sort| sort == Sort::DateModified); + hbs.register_helper("sort_date_modified", Box::new(sort_date_modified)); + + Ok(Self { + path, + handlebars: hbs, + }) + } +} From 55bd5972bc2b904e0b2ceed937d1c66d79f3ac22 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 12 Oct 2024 21:12:01 -0300 Subject: [PATCH 10/40] chore: comment missing impls --- crates/file-explorer/src/templater/mod.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/crates/file-explorer/src/templater/mod.rs b/crates/file-explorer/src/templater/mod.rs index 310f98bf..f3c6ef71 100644 --- a/crates/file-explorer/src/templater/mod.rs +++ b/crates/file-explorer/src/templater/mod.rs @@ -29,21 +29,20 @@ impl Templater { handlebars_helper!(size: |bytes: u64| format_size(bytes, DECIMAL)); hbs.register_helper("size", Box::new(size)); - handlebars_helper!(sort_name: |sort: Sort| sort == Sort::Name); - hbs.register_helper("sort_name", Box::new(sort_name)); + // handlebars_helper!(sort_name: |sort: Sort| sort == Sort::Name); + // hbs.register_helper("sort_name", Box::new(sort_name)); - handlebars_helper!(sort_size: |sort: Sort| sort == Sort::Size); - hbs.register_helper("sort_size", Box::new(sort_size)); + // handlebars_helper!(sort_size: |sort: Sort| sort == Sort::Size); + // hbs.register_helper("sort_size", Box::new(sort_size)); - handlebars_helper!(sort_date_created: |sort: Sort| sort == Sort::DateCreated); - hbs.register_helper("sort_date_created", Box::new(sort_date_created)); + // handlebars_helper!(sort_date_created: |sort: Sort| sort == Sort::DateCreated); + // hbs.register_helper("sort_date_created", Box::new(sort_date_created)); - handlebars_helper!(sort_date_modified: |sort: Sort| sort == Sort::DateModified); - hbs.register_helper("sort_date_modified", Box::new(sort_date_modified)); + // handlebars_helper!(sort_date_modified: |sort: Sort| sort == Sort::DateModified); + // hbs.register_helper("sort_date_modified", Box::new(sort_date_modified)); Ok(Self { - path, - handlebars: hbs, + backend: hbs, }) } } From 79413b206116edbc6d5c7934ebbe43b2e3ce6b2a Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 12 Oct 2024 21:13:59 -0300 Subject: [PATCH 11/40] fix: use same symbol --- crates/http-server/src/plugin.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/http-server/src/plugin.rs b/crates/http-server/src/plugin.rs index 35f398cb..1947fc25 100644 --- a/crates/http-server/src/plugin.rs +++ b/crates/http-server/src/plugin.rs @@ -45,7 +45,7 @@ impl ExternalFunctions { pub unsafe fn load>(&self, library_path: P) -> io::Result<()> { let library = Arc::new(Library::new(library_path).unwrap()); let decl = library - .get::<*mut PluginDeclaration>(b"plugin_declaration\0") + .get::<*mut PluginDeclaration>(b"PLUGIN_DECLARATION\0") .unwrap() .read(); From cfd8ae43c2f0742452dff1c496b3e63981d0e645 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 12 Oct 2024 21:41:25 -0300 Subject: [PATCH 12/40] feat: partially parse toml config --- Cargo.lock | 64 +++++++++++++++++++++-- Cargo.toml | 1 + config.toml | 5 ++ crates/file-explorer/Cargo.toml | 3 +- crates/file-explorer/src/lib.rs | 22 +++++--- crates/file-explorer/src/templater/mod.rs | 4 +- crates/http-server-plugin/Cargo.toml | 3 ++ crates/http-server-plugin/src/config.rs | 18 +++++++ crates/http-server-plugin/src/lib.rs | 5 +- crates/http-server/src/plugin.rs | 9 +++- crates/http-server/src/server/mod.rs | 3 +- 11 files changed, 117 insertions(+), 20 deletions(-) create mode 100644 config.toml create mode 100644 crates/http-server-plugin/src/config.rs diff --git a/Cargo.lock b/Cargo.lock index fc17c72d..7bce3637 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,6 +277,7 @@ dependencies = [ "hyper", "serde", "serde_json", + "toml", ] [[package]] @@ -375,9 +376,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.0" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2c6201b9ff9fd90a5a3bac2e56a830d0caa509576f0e503818ee82c181b3437a" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" [[package]] name = "heck" @@ -455,9 +456,12 @@ dependencies = [ name = "http-server-plugin" version = "1.0.0-draft+1" dependencies = [ + "anyhow", "http-body-util", "hyper", "rustc_version", + "serde", + "toml", ] [[package]] @@ -546,9 +550,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.0.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5477fe2230a79769d8dc68e0eabf5437907c0457a5614a9e8dddb67f65eb65d" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", "hashbrown", @@ -791,6 +795,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "sha2" version = "0.10.8" @@ -923,6 +936,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower" version = "0.5.1" @@ -1170,3 +1217,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index f940776c..0c84a91e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ rustc_version = "0.4.1" serde = "1.0.210" serde_json = "1.0.128" tokio = "1.40" +toml = "0.8.19" tower-http = "0.6.1" tower = "0.5.1" diff --git a/config.toml b/config.toml new file mode 100644 index 00000000..f6c3b5bc --- /dev/null +++ b/config.toml @@ -0,0 +1,5 @@ +host = "127.0.0.1" +port = "7878" + +[file-explorer] +path = "./assets" diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index a07f0dcb..075c8210 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -15,7 +15,8 @@ handlebars = { workspace = true } http-body-util = { workspace = true } humansize = { workspace = true } hyper = { workspace = true } -serde = { workspace = true } +serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +toml = { workspace = true } http-server-plugin = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 36cc2c20..3909476d 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -7,7 +7,9 @@ use anyhow::Result; use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::{Method, Request, Response}; +use serde::Deserialize; +use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; use self::templater::Templater; @@ -15,14 +17,21 @@ use self::templater::Templater; export_plugin!(register); #[allow(improper_ctypes_definitions)] -extern "C" fn register(registrar: &mut dyn PluginRegistrar) { +extern "C" fn register(config_path: PathBuf, registrar: &mut dyn PluginRegistrar) { + let config: FileExplorerConfig = read_from_path(config_path, "file-explorer").unwrap(); + registrar.register_function( "file-explorer", - Arc::new(FileExplorer::new(PathBuf::new()).expect("Failed to create FileExplorer")), + Arc::new(FileExplorer::new(config.path).expect("Failed to create FileExplorer")), ); } -pub struct FileExplorer { +#[derive(Debug, Deserialize)] +struct FileExplorerConfig { + pub path: PathBuf, +} + +struct FileExplorer { pub path: PathBuf, pub templater: Templater, } @@ -38,12 +47,9 @@ impl Function for FileExplorer { } impl FileExplorer { - pub fn new(path: PathBuf) -> Result { + fn new(path: PathBuf) -> Result { let templater = Templater::new()?; - Ok(Self { - path, - templater, - }) + Ok(Self { path, templater }) } } diff --git a/crates/file-explorer/src/templater/mod.rs b/crates/file-explorer/src/templater/mod.rs index f3c6ef71..d4ba9c1d 100644 --- a/crates/file-explorer/src/templater/mod.rs +++ b/crates/file-explorer/src/templater/mod.rs @@ -41,8 +41,6 @@ impl Templater { // handlebars_helper!(sort_date_modified: |sort: Sort| sort == Sort::DateModified); // hbs.register_helper("sort_date_modified", Box::new(sort_date_modified)); - Ok(Self { - backend: hbs, - }) + Ok(Self { backend: hbs }) } } diff --git a/crates/http-server-plugin/Cargo.toml b/crates/http-server-plugin/Cargo.toml index 382a6078..9af33134 100644 --- a/crates/http-server-plugin/Cargo.toml +++ b/crates/http-server-plugin/Cargo.toml @@ -11,8 +11,11 @@ license = "MIT OR Apache-2.0" readme = "README.md" [dependencies] +anyhow = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } +serde = { workspace = true } +toml = { workspace = true } [build-dependencies] rustc_version = { workspace = true } diff --git a/crates/http-server-plugin/src/config.rs b/crates/http-server-plugin/src/config.rs new file mode 100644 index 00000000..9f5e63ec --- /dev/null +++ b/crates/http-server-plugin/src/config.rs @@ -0,0 +1,18 @@ +use std::fs::read_to_string; +use std::path::PathBuf; + +use anyhow::{bail, Result}; +use serde::de::DeserializeOwned; +use toml::Table; + +pub fn read_from_path(path: PathBuf, key: &str) -> Result { + let config_str = read_to_string(&path)?; + let config_tbl: Table = toml::from_str(&config_str)?; + + if let Some(tbl) = config_tbl.get(key) { + let config: T = tbl.to_owned().try_into().unwrap(); + return Ok(config); + } + + bail!("Key not found") +} diff --git a/crates/http-server-plugin/src/lib.rs b/crates/http-server-plugin/src/lib.rs index 5870825b..7802c848 100644 --- a/crates/http-server-plugin/src/lib.rs +++ b/crates/http-server-plugin/src/lib.rs @@ -1,3 +1,6 @@ +pub mod config; + +use std::path::PathBuf; use std::sync::Arc; use http_body_util::Full; @@ -21,7 +24,7 @@ pub enum InvocationError { pub struct PluginDeclaration { pub rustc_version: &'static str, pub core_version: &'static str, - pub register: unsafe extern "C" fn(&mut dyn PluginRegistrar), + pub register: unsafe extern "C" fn(config_path: PathBuf, &mut dyn PluginRegistrar), } pub trait PluginRegistrar { diff --git a/crates/http-server/src/plugin.rs b/crates/http-server/src/plugin.rs index 1947fc25..8c25bb69 100644 --- a/crates/http-server/src/plugin.rs +++ b/crates/http-server/src/plugin.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::io; +use std::path::PathBuf; use std::sync::{Arc, Mutex}; use http_body_util::Full; @@ -42,7 +43,11 @@ impl ExternalFunctions { /// /// This function is unsafe because it loads a shared library and calls /// functions from it. - pub unsafe fn load>(&self, library_path: P) -> io::Result<()> { + pub unsafe fn load>( + &self, + config_path: PathBuf, + library_path: P, + ) -> io::Result<()> { let library = Arc::new(Library::new(library_path).unwrap()); let decl = library .get::<*mut PluginDeclaration>(b"PLUGIN_DECLARATION\0") @@ -55,7 +60,7 @@ impl ExternalFunctions { let mut registrar = PluginRegistrar::new(Arc::clone(&library)); - (decl.register)(&mut registrar); + (decl.register)(config_path, &mut registrar); self.functions .lock() diff --git a/crates/http-server/src/server/mod.rs b/crates/http-server/src/server/mod.rs index b7ed5c6a..cc98ec86 100644 --- a/crates/http-server/src/server/mod.rs +++ b/crates/http-server/src/server/mod.rs @@ -18,10 +18,11 @@ impl Server { let listener = TcpListener::bind(addr).await?; let functions = Arc::new(ExternalFunctions::new()); let plugin_library = PathBuf::from_str("./target/debug/libfile_explorer.dylib").unwrap(); + let config = PathBuf::from_str("./config.toml").unwrap(); unsafe { functions - .load(plugin_library) + .load(config, plugin_library) .expect("Function loading failed"); } From 7c65a019d08cce7f5352658816c1da754f78f559 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 12 Oct 2024 21:55:40 -0300 Subject: [PATCH 13/40] feat: introduce fs parts --- Cargo.lock | 99 +++++++++++++++++--- Cargo.toml | 2 + crates/file-explorer/Cargo.toml | 4 +- crates/file-explorer/src/fs/directory.rs | 14 +++ crates/file-explorer/src/fs/file.rs | 108 ++++++++++++++++++++++ crates/file-explorer/src/fs/mod.rs | 112 +++++++++++++++++++++++ crates/file-explorer/src/lib.rs | 20 +++- 7 files changed, 342 insertions(+), 17 deletions(-) create mode 100644 crates/file-explorer/src/fs/directory.rs create mode 100644 crates/file-explorer/src/fs/file.rs create mode 100644 crates/file-explorer/src/fs/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 7bce3637..1e94cb2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -270,14 +270,16 @@ version = "0.0.0" dependencies = [ "anyhow", "chrono", + "futures", "handlebars", "http-body-util", "http-server-plugin", "humansize", "hyper", + "mime_guess", "serde", "serde_json", - "toml", + "tokio", ] [[package]] @@ -286,43 +288,93 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "futures" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -616,6 +668,22 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "miniz_oxide" version = "0.5.1" @@ -1047,6 +1115,15 @@ version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-ident" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 0c84a91e..894f52ca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ resolver = "1" anyhow = "1.0" chrono = "0.4.38" clap = "4.5.20" +futures = "0.3.31" handlebars = "6.1.0" humansize = "2.1.3" http-auth-basic = "0.3.3" @@ -20,6 +21,7 @@ http-body-util = "0.1" hyper = "1.4" hyper-util = "0.1.9" libloading = "0.8.5" +mime_guess = "2.0.5" rustc_version = "0.4.1" serde = "1.0.210" serde_json = "1.0.128" diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index 075c8210..25c304b4 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -11,12 +11,14 @@ crate-type = ["cdylib"] [dependencies] anyhow = { workspace = true } chrono = { workspace = true, features = ["serde"] } +futures = { workspace = true } handlebars = { workspace = true } http-body-util = { workspace = true } humansize = { workspace = true } hyper = { workspace = true } +mime_guess = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } -toml = { workspace = true } +tokio = { workspace = true, features = ["fs", "rt-multi-thread", "signal", "macros"] } http-server-plugin = { workspace = true } diff --git a/crates/file-explorer/src/fs/directory.rs b/crates/file-explorer/src/fs/directory.rs new file mode 100644 index 00000000..795c98e5 --- /dev/null +++ b/crates/file-explorer/src/fs/directory.rs @@ -0,0 +1,14 @@ +use std::path::PathBuf; + +/// Representation of a OS ScopedFileSystem directory providing the path +/// (`PathBuf`) +#[derive(Debug)] +pub struct Directory { + pub path: PathBuf, +} + +impl Directory { + pub fn path(&self) -> PathBuf { + self.path.clone() + } +} diff --git a/crates/file-explorer/src/fs/file.rs b/crates/file-explorer/src/fs/file.rs new file mode 100644 index 00000000..85aebc13 --- /dev/null +++ b/crates/file-explorer/src/fs/file.rs @@ -0,0 +1,108 @@ +use anyhow::{Context, Result}; +use chrono::{DateTime, Local}; + +use std::fs::Metadata; +use std::mem::MaybeUninit; +use std::path::PathBuf; +use std::pin::Pin; +use std::task::{self, Poll}; + +use futures::Stream; +use hyper::body::Bytes; +use mime_guess::{from_path, Mime}; +use tokio::io::{AsyncRead, ReadBuf}; + +pub const FILE_BUFFER_SIZE: usize = 8 * 1024; + +pub type FileBuffer = Box<[MaybeUninit; FILE_BUFFER_SIZE]>; + +/// Wrapper around `tokio::fs::File` built from a OS ScopedFileSystem file +/// providing `std::fs::Metadata` and the path to such file +#[derive(Debug)] +pub struct File { + pub path: PathBuf, + pub file: tokio::fs::File, + pub metadata: Metadata, +} + +impl File { + pub fn new(path: PathBuf, file: tokio::fs::File, metadata: Metadata) -> Self { + File { + path, + file, + metadata, + } + } + + pub fn mime(&self) -> Mime { + from_path(self.path.clone()).first_or_octet_stream() + } + + pub fn size(&self) -> u64 { + self.metadata.len() + } + + pub fn last_modified(&self) -> Result> { + let modified = self + .metadata + .modified() + .context("Failed to read last modified time for file")?; + let modified: DateTime = modified.into(); + + Ok(modified) + } + + #[allow(dead_code)] + pub fn bytes(self) -> Vec { + let byte_stream = ByteStream { + file: self.file, + buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]), + }; + + byte_stream + .buffer + .iter() + .map(|muint| unsafe { muint.assume_init() }) + .collect::>() + } +} + +pub struct ByteStream { + file: tokio::fs::File, + buffer: FileBuffer, +} + +impl From for ByteStream { + fn from(file: File) -> Self { + ByteStream { + file: file.file, + buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]), + } + } +} + +impl Stream for ByteStream { + type Item = Result; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + let ByteStream { + ref mut file, + ref mut buffer, + } = *self; + let mut read_buffer = ReadBuf::uninit(&mut buffer[..]); + + match Pin::new(file).poll_read(cx, &mut read_buffer) { + Poll::Ready(Ok(())) => { + let filled = read_buffer.filled(); + + if filled.is_empty() { + Poll::Ready(None) + } else { + Poll::Ready(Some(Ok(Bytes::copy_from_slice(filled)))) + } + } + Poll::Ready(Err(error)) => Poll::Ready(Some(Err(error.into()))), + Poll::Pending => Poll::Pending, + } + } +} diff --git a/crates/file-explorer/src/fs/mod.rs b/crates/file-explorer/src/fs/mod.rs new file mode 100644 index 00000000..2959e4cc --- /dev/null +++ b/crates/file-explorer/src/fs/mod.rs @@ -0,0 +1,112 @@ +mod directory; +mod file; + +use std::path::{Component, Path, PathBuf}; + +use anyhow::Result; +use tokio::fs::OpenOptions; + +use self::directory::Directory; +use self::file::File; + +/// Any OS filesystem entry recognized by `ScopedFileSystem` is treated as a +/// `Entry` both `File` and `Directory` are possible values with full support by +/// `ScopedFileSystem` +#[derive(Debug)] +pub enum Entry { + File(Box), + Directory(Directory), +} + +pub struct FileSystem { + pub path: PathBuf, +} + +impl FileSystem { + /// Creates a new instance of `ScopedFileSystem` using the provided PathBuf + /// as the root directory to serve files from. + /// + /// Provided paths will resolve relartive to the provided `root` directory. + pub fn new(path: PathBuf) -> Result { + Ok(Self { path }) + } + + /// Resolves the provided path against the root directory of this + /// `ScopedFileSystem` instance. + /// + /// A relative path is built using `build_relative_path` and then is opened + /// to retrieve a `Entry`. + pub async fn resolve(&self, path: PathBuf) -> std::io::Result { + let entry_path = self.build_relative_path(path); + + Self::open(entry_path).await + } + + /// Builds a path relative to `ScopedFileSystem`'s `root` path with the + /// provided path. + fn build_relative_path(&self, path: PathBuf) -> PathBuf { + let mut root = self.path.clone(); + + root.extend(&Self::normalize_path(&path)); + + root + } + + /// Normalizes paths + /// + /// ```ignore + /// docs/collegue/cs50/lectures/../code/voting_excecise + /// ``` + /// + /// Will be normalized to be: + /// + /// ```ignore + /// docs/collegue/cs50/code/voting_excecise + /// ``` + fn normalize_path(path: &Path) -> PathBuf { + path.components() + .fold(PathBuf::new(), |mut result, p| match p { + Component::ParentDir => { + result.pop(); + result + } + Component::Normal(os_string) => { + result.push(os_string); + result + } + _ => result, + }) + } + + #[cfg(not(target_os = "windows"))] + async fn open(path: PathBuf) -> std::io::Result { + let mut open_options = OpenOptions::new(); + let entry_path: PathBuf = path.clone(); + let file = open_options.read(true).open(path).await?; + let metadata = file.metadata().await?; + + if metadata.is_dir() { + return Ok(Entry::Directory(Directory { path: entry_path })); + } + + Ok(Entry::File(Box::new(File::new(entry_path, file, metadata)))) + } + + #[cfg(target_os = "windows")] + async fn open(path: PathBuf) -> std::io::Result { + let mut open_options = OpenOptions::new(); + let entry_path: PathBuf = path.clone(); + let file = open_options + .read(true) + .custom_flags(FILE_FLAG_BACKUP_SEMANTICS) + .open(path) + .await?; + let metadata = file.metadata().await?; + + if metadata.is_dir() { + return Ok(Entry::Directory(Directory { path: entry_path })); + } + + Ok(Entry::File(Box::new(File::new(entry_path, file, metadata)))) + } +} diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 3909476d..9064bd63 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -1,3 +1,4 @@ +mod fs; mod templater; use std::path::PathBuf; @@ -12,16 +13,19 @@ use serde::Deserialize; use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; +use self::fs::FileSystem; use self::templater::Templater; export_plugin!(register); +const PLUGIN_NAME: &str = "file-explorer"; + #[allow(improper_ctypes_definitions)] extern "C" fn register(config_path: PathBuf, registrar: &mut dyn PluginRegistrar) { - let config: FileExplorerConfig = read_from_path(config_path, "file-explorer").unwrap(); + let config: FileExplorerConfig = read_from_path(config_path, PLUGIN_NAME).unwrap(); registrar.register_function( - "file-explorer", + PLUGIN_NAME, Arc::new(FileExplorer::new(config.path).expect("Failed to create FileExplorer")), ); } @@ -32,8 +36,9 @@ struct FileExplorerConfig { } struct FileExplorer { - pub path: PathBuf, - pub templater: Templater, + fs: FileSystem, + path: PathBuf, + templater: Templater, } impl Function for FileExplorer { @@ -48,8 +53,13 @@ impl Function for FileExplorer { impl FileExplorer { fn new(path: PathBuf) -> Result { + let fs = FileSystem::new(path.clone())?; let templater = Templater::new()?; - Ok(Self { path, templater }) + Ok(Self { + fs, + path, + templater, + }) } } From 7319a7816df1a4d757cb4cd3eec86548cc50d9a5 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 12 Oct 2024 22:03:50 -0300 Subject: [PATCH 14/40] feat: make functions async --- Cargo.lock | 14 ++++++++++++++ Cargo.toml | 1 + crates/file-explorer/Cargo.toml | 1 + crates/file-explorer/src/lib.rs | 4 +++- crates/http-server-plugin/Cargo.toml | 1 + crates/http-server-plugin/src/lib.rs | 4 +++- crates/http-server/Cargo.toml | 1 + crates/http-server/src/plugin.rs | 26 ++++++++++++-------------- crates/http-server/src/server/mod.rs | 3 ++- 9 files changed, 38 insertions(+), 17 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1e94cb2a..f7e7f2a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,6 +87,17 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "async-trait" +version = "0.1.83" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -269,6 +280,7 @@ name = "file-explorer" version = "0.0.0" dependencies = [ "anyhow", + "async-trait", "chrono", "futures", "handlebars", @@ -492,6 +504,7 @@ name = "http-server" version = "1.0.0-draft+1" dependencies = [ "anyhow", + "async-trait", "clap", "http-auth-basic", "http-body-util", @@ -509,6 +522,7 @@ name = "http-server-plugin" version = "1.0.0-draft+1" dependencies = [ "anyhow", + "async-trait", "http-body-util", "hyper", "rustc_version", diff --git a/Cargo.toml b/Cargo.toml index 894f52ca..b252ccd4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ resolver = "1" [workspace.dependencies] anyhow = "1.0" +async-trait = "0.1.83" chrono = "0.4.38" clap = "4.5.20" futures = "0.3.31" diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index 25c304b4..f8bab271 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -10,6 +10,7 @@ crate-type = ["cdylib"] [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } chrono = { workspace = true, features = ["serde"] } futures = { workspace = true } handlebars = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 9064bd63..6e926ab2 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -5,6 +5,7 @@ use std::path::PathBuf; use std::sync::Arc; use anyhow::Result; +use async_trait::async_trait; use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::{Method, Request, Response}; @@ -41,8 +42,9 @@ struct FileExplorer { templater: Templater, } +#[async_trait] impl Function for FileExplorer { - fn call(&self, req: Request) -> Result>, InvocationError> { + async fn call(&self, req: Request) -> Result>, InvocationError> { match req.method() { &Method::GET => Ok(Response::new(Full::new(Bytes::from("File Explorer")))), &Method::POST => Ok(Response::new(Full::new(Bytes::from("Prepare to upload")))), diff --git a/crates/http-server-plugin/Cargo.toml b/crates/http-server-plugin/Cargo.toml index 9af33134..f1208ead 100644 --- a/crates/http-server-plugin/Cargo.toml +++ b/crates/http-server-plugin/Cargo.toml @@ -12,6 +12,7 @@ readme = "README.md" [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } serde = { workspace = true } diff --git a/crates/http-server-plugin/src/lib.rs b/crates/http-server-plugin/src/lib.rs index 7802c848..e8c3a0d7 100644 --- a/crates/http-server-plugin/src/lib.rs +++ b/crates/http-server-plugin/src/lib.rs @@ -3,6 +3,7 @@ pub mod config; use std::path::PathBuf; use std::sync::Arc; +use async_trait::async_trait; use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::{Request, Response}; @@ -10,8 +11,9 @@ use hyper::{Request, Response}; pub static CORE_VERSION: &str = env!("CARGO_PKG_VERSION"); pub static RUSTC_VERSION: &str = env!("RUSTC_VERSION"); +#[async_trait] pub trait Function: Send + Sync { - fn call(&self, req: Request) -> Result>, InvocationError>; + async fn call(&self, req: Request) -> Result>, InvocationError>; } #[derive(Debug)] diff --git a/crates/http-server/Cargo.toml b/crates/http-server/Cargo.toml index fcf9b28f..0097014c 100644 --- a/crates/http-server/Cargo.toml +++ b/crates/http-server/Cargo.toml @@ -14,6 +14,7 @@ readme = "README.md" [dependencies] anyhow = { workspace = true } +async-trait = { workspace = true } clap = { workspace = true, features = ["env", "derive", "std"] } http-auth-basic = { workspace = true } http-body-util = { workspace = true } diff --git a/crates/http-server/src/plugin.rs b/crates/http-server/src/plugin.rs index 8c25bb69..c09b8504 100644 --- a/crates/http-server/src/plugin.rs +++ b/crates/http-server/src/plugin.rs @@ -2,12 +2,14 @@ use std::collections::HashMap; use std::ffi::OsStr; use std::io; use std::path::PathBuf; -use std::sync::{Arc, Mutex}; +use std::sync::Arc; +use async_trait::async_trait; use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::{Request, Response}; use libloading::Library; +use tokio::sync::Mutex; use http_server_plugin::{ Function, InvocationError, PluginDeclaration, CORE_VERSION, RUSTC_VERSION, @@ -20,9 +22,10 @@ pub struct FunctionProxy { _lib: Arc, } +#[async_trait] impl Function for FunctionProxy { - fn call(&self, req: Request) -> Result>, InvocationError> { - self.function.call(req) + async fn call(&self, req: Request) -> Result>, InvocationError> { + self.function.call(req).await } } @@ -43,7 +46,7 @@ impl ExternalFunctions { /// /// This function is unsafe because it loads a shared library and calls /// functions from it. - pub unsafe fn load>( + pub async unsafe fn load>( &self, config_path: PathBuf, library_path: P, @@ -62,31 +65,26 @@ impl ExternalFunctions { (decl.register)(config_path, &mut registrar); - self.functions - .lock() - .expect("Cannot lock Mutex") - .extend(registrar.functions); + self.functions.lock().await.extend(registrar.functions); - self.libraries - .lock() - .expect("Cannot lock Mutex") - .push(library); + self.libraries.lock().await.push(library); Ok(()) } - pub fn call( + pub async fn call( &self, function: &str, req: Request, ) -> Result>, InvocationError> { self.functions .lock() - .expect("Cannot lock Mutex") + .await .get(function) .ok_or_else(|| format!("\"{}\" not found", function)) .unwrap() .call(req) + .await } } diff --git a/crates/http-server/src/server/mod.rs b/crates/http-server/src/server/mod.rs index cc98ec86..b044f7d1 100644 --- a/crates/http-server/src/server/mod.rs +++ b/crates/http-server/src/server/mod.rs @@ -23,6 +23,7 @@ impl Server { unsafe { functions .load(config, plugin_library) + .await .expect("Function loading failed"); } @@ -42,7 +43,7 @@ impl Server { // N.B. should use tower service_fn here, since it's reuqired to be implemented tower Service trait before convert to hyper Service! let svc = tower::service_fn(|req| async { - match functions.call("file-explorer", req) { + match functions.call("file-explorer", req).await { Ok(res) => Ok::< Response>, Infallible, From 8adc51ffdd220daa2754ce909509b576928f9a64 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 12 Oct 2024 22:13:26 -0300 Subject: [PATCH 15/40] feat!: fetches directory but needs runtime --- crates/file-explorer/src/lib.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 6e926ab2..a573305c 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -46,7 +46,16 @@ struct FileExplorer { impl Function for FileExplorer { async fn call(&self, req: Request) -> Result>, InvocationError> { match req.method() { - &Method::GET => Ok(Response::new(Full::new(Bytes::from("File Explorer")))), + &Method::GET => { + match self.fs.resolve(PathBuf::from("./assets")).await.expect("failed to execute") { + fs::Entry::File(file) => { + Ok(Response::new(Full::new(Bytes::from(file.bytes())))) + }, + fs::Entry::Directory(dir) => { + Ok(Response::new(Full::new(Bytes::from(format!("{:?}", dir))))) + } + } + } &Method::POST => Ok(Response::new(Full::new(Bytes::from("Prepare to upload")))), _ => Ok(Response::new(Full::new(Bytes::from("Unsupported method")))), } From b9e9cddf4a526618dba7cbf21c3de33dcbe2e49e Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 12 Oct 2024 22:21:23 -0300 Subject: [PATCH 16/40] feat: pass tokio rt via ffi --- Cargo.lock | 1 + crates/file-explorer/src/lib.rs | 42 ++++++++++++++++++---------- crates/http-server-plugin/Cargo.toml | 1 + crates/http-server-plugin/src/lib.rs | 4 ++- crates/http-server/src/plugin.rs | 7 ++++- 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7e7f2a2..0a1d51de 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -527,6 +527,7 @@ dependencies = [ "hyper", "rustc_version", "serde", + "tokio", "toml", ] diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index a573305c..fd8ba4db 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -10,6 +10,7 @@ use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::{Method, Request, Response}; use serde::Deserialize; +use tokio::runtime::Runtime; use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; @@ -22,12 +23,16 @@ export_plugin!(register); const PLUGIN_NAME: &str = "file-explorer"; #[allow(improper_ctypes_definitions)] -extern "C" fn register(config_path: PathBuf, registrar: &mut dyn PluginRegistrar) { +extern "C" fn register( + config_path: PathBuf, + rt: Arc, + registrar: &mut dyn PluginRegistrar, +) { let config: FileExplorerConfig = read_from_path(config_path, PLUGIN_NAME).unwrap(); registrar.register_function( PLUGIN_NAME, - Arc::new(FileExplorer::new(config.path).expect("Failed to create FileExplorer")), + Arc::new(FileExplorer::new(rt, config.path).expect("Failed to create FileExplorer")), ); } @@ -37,6 +42,7 @@ struct FileExplorerConfig { } struct FileExplorer { + rt: Arc, fs: FileSystem, path: PathBuf, templater: Templater, @@ -45,29 +51,37 @@ struct FileExplorer { #[async_trait] impl Function for FileExplorer { async fn call(&self, req: Request) -> Result>, InvocationError> { - match req.method() { - &Method::GET => { - match self.fs.resolve(PathBuf::from("./assets")).await.expect("failed to execute") { - fs::Entry::File(file) => { - Ok(Response::new(Full::new(Bytes::from(file.bytes())))) - }, - fs::Entry::Directory(dir) => { - Ok(Response::new(Full::new(Bytes::from(format!("{:?}", dir))))) + self.rt.block_on(async move { + match req.method() { + &Method::GET => { + match self + .fs + .resolve(PathBuf::new()) + .await + .expect("failed to execute") + { + fs::Entry::File(file) => { + Ok(Response::new(Full::new(Bytes::from(file.bytes())))) + } + fs::Entry::Directory(dir) => { + Ok(Response::new(Full::new(Bytes::from(format!("{:?}", dir))))) + } } } + &Method::POST => Ok(Response::new(Full::new(Bytes::from("Prepare to upload")))), + _ => Ok(Response::new(Full::new(Bytes::from("Unsupported method")))), } - &Method::POST => Ok(Response::new(Full::new(Bytes::from("Prepare to upload")))), - _ => Ok(Response::new(Full::new(Bytes::from("Unsupported method")))), - } + }) } } impl FileExplorer { - fn new(path: PathBuf) -> Result { + fn new(rt: Arc, path: PathBuf) -> Result { let fs = FileSystem::new(path.clone())?; let templater = Templater::new()?; Ok(Self { + rt, fs, path, templater, diff --git a/crates/http-server-plugin/Cargo.toml b/crates/http-server-plugin/Cargo.toml index f1208ead..5e5cd6b2 100644 --- a/crates/http-server-plugin/Cargo.toml +++ b/crates/http-server-plugin/Cargo.toml @@ -16,6 +16,7 @@ async-trait = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } serde = { workspace = true } +tokio = { workspace = true } toml = { workspace = true } [build-dependencies] diff --git a/crates/http-server-plugin/src/lib.rs b/crates/http-server-plugin/src/lib.rs index e8c3a0d7..33a7190a 100644 --- a/crates/http-server-plugin/src/lib.rs +++ b/crates/http-server-plugin/src/lib.rs @@ -7,6 +7,7 @@ use async_trait::async_trait; use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::{Request, Response}; +use tokio::runtime::Runtime; pub static CORE_VERSION: &str = env!("CARGO_PKG_VERSION"); pub static RUSTC_VERSION: &str = env!("RUSTC_VERSION"); @@ -26,7 +27,8 @@ pub enum InvocationError { pub struct PluginDeclaration { pub rustc_version: &'static str, pub core_version: &'static str, - pub register: unsafe extern "C" fn(config_path: PathBuf, &mut dyn PluginRegistrar), + pub register: + unsafe extern "C" fn(config_path: PathBuf, rt: Arc, &mut dyn PluginRegistrar), } pub trait PluginRegistrar { diff --git a/crates/http-server/src/plugin.rs b/crates/http-server/src/plugin.rs index c09b8504..514730d6 100644 --- a/crates/http-server/src/plugin.rs +++ b/crates/http-server/src/plugin.rs @@ -9,6 +9,7 @@ use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::{Request, Response}; use libloading::Library; +use tokio::runtime::Runtime; use tokio::sync::Mutex; use http_server_plugin::{ @@ -63,7 +64,11 @@ impl ExternalFunctions { let mut registrar = PluginRegistrar::new(Arc::clone(&library)); - (decl.register)(config_path, &mut registrar); + (decl.register)( + config_path, + Arc::new(Runtime::new().unwrap()), + &mut registrar, + ); self.functions.lock().await.extend(registrar.functions); From 4a5bf673e2398feae1b040e6777bfb6d04601d00 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 12 Oct 2024 22:32:19 -0300 Subject: [PATCH 17/40] feat: resolves path --- Cargo.lock | 7 ++++ Cargo.toml | 1 + config.toml | 2 +- crates/file-explorer/Cargo.toml | 1 + crates/file-explorer/src/lib.rs | 47 ++++++++++++++-------- crates/file-explorer/src/utils.rs | 67 +++++++++++++++++++++++++++++++ 6 files changed, 108 insertions(+), 17 deletions(-) create mode 100644 crates/file-explorer/src/utils.rs diff --git a/Cargo.lock b/Cargo.lock index 0a1d51de..09a6e6af 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,6 +289,7 @@ dependencies = [ "humansize", "hyper", "mime_guess", + "percent-encoding", "serde", "serde_json", "tokio", @@ -744,6 +745,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + [[package]] name = "pest" version = "2.7.13" diff --git a/Cargo.toml b/Cargo.toml index b252ccd4..9235897d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,6 +23,7 @@ hyper = "1.4" hyper-util = "0.1.9" libloading = "0.8.5" mime_guess = "2.0.5" +percent-encoding = "2.3.1" rustc_version = "0.4.1" serde = "1.0.210" serde_json = "1.0.128" diff --git a/config.toml b/config.toml index f6c3b5bc..51d4f8e5 100644 --- a/config.toml +++ b/config.toml @@ -2,4 +2,4 @@ host = "127.0.0.1" port = "7878" [file-explorer] -path = "./assets" +path = "./" diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index f8bab271..1a3e4b76 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -20,6 +20,7 @@ hyper = { workspace = true } mime_guess = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } +percent-encoding = { workspace = true } tokio = { workspace = true, features = ["fs", "rt-multi-thread", "signal", "macros"] } http-server-plugin = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index fd8ba4db..300667c3 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -1,14 +1,16 @@ mod fs; mod templater; +mod utils; use std::path::PathBuf; +use std::str::FromStr; use std::sync::Arc; -use anyhow::Result; +use anyhow::{Context, Result}; use async_trait::async_trait; use http_body_util::Full; use hyper::body::{Bytes, Incoming}; -use hyper::{Method, Request, Response}; +use hyper::{Method, Request, Response, Uri}; use serde::Deserialize; use tokio::runtime::Runtime; @@ -17,6 +19,7 @@ use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistr use self::fs::FileSystem; use self::templater::Templater; +use self::utils::decode_uri; export_plugin!(register); @@ -52,22 +55,17 @@ struct FileExplorer { impl Function for FileExplorer { async fn call(&self, req: Request) -> Result>, InvocationError> { self.rt.block_on(async move { + let path = Self::parse_req_uri(req.uri().clone()).unwrap(); + match req.method() { - &Method::GET => { - match self - .fs - .resolve(PathBuf::new()) - .await - .expect("failed to execute") - { - fs::Entry::File(file) => { - Ok(Response::new(Full::new(Bytes::from(file.bytes())))) - } - fs::Entry::Directory(dir) => { - Ok(Response::new(Full::new(Bytes::from(format!("{:?}", dir))))) - } + &Method::GET => match self.fs.resolve(path).await.expect("failed to execute") { + fs::Entry::File(file) => { + Ok(Response::new(Full::new(Bytes::from(file.bytes())))) + } + fs::Entry::Directory(dir) => { + Ok(Response::new(Full::new(Bytes::from(format!("{:?}", dir))))) } - } + }, &Method::POST => Ok(Response::new(Full::new(Bytes::from("Prepare to upload")))), _ => Ok(Response::new(Full::new(Bytes::from("Unsupported method")))), } @@ -87,4 +85,21 @@ impl FileExplorer { templater, }) } + + fn parse_req_uri(uri: Uri) -> Result { + let uri_parts = uri.into_parts(); + + if let Some(path_and_query) = uri_parts.path_and_query { + let path = path_and_query.path(); + // let query_params = if let Some(query_str) = path_and_query.query() { + // Some(QueryParams::from_str(query_str)?) + // } else { + // None + // }; + + return Ok(decode_uri(path)); + } + + PathBuf::from_str("/").context("Failed to parse URI") + } } diff --git a/crates/file-explorer/src/utils.rs b/crates/file-explorer/src/utils.rs new file mode 100644 index 00000000..872d1aea --- /dev/null +++ b/crates/file-explorer/src/utils.rs @@ -0,0 +1,67 @@ +use std::path::{Path, PathBuf}; + +use percent_encoding::{percent_decode, utf8_percent_encode, AsciiSet, NON_ALPHANUMERIC}; + +pub const PERCENT_ENCODE_SET: &AsciiSet = &NON_ALPHANUMERIC + .remove(b'-') + .remove(b'_') + .remove(b'.') + .remove(b'~'); + +pub fn encode_uri(file_path: &Path) -> String { + assert!(!file_path.is_absolute()); + + file_path + .iter() + .flat_map(|component| { + let component = component.to_str().unwrap(); + let segment = utf8_percent_encode(component, PERCENT_ENCODE_SET); + + std::iter::once("/").chain(segment) + }) + .collect::() +} + +pub fn decode_uri(file_path: &str) -> PathBuf { + file_path + .split('/') + .map(|encoded_part| { + let decode = percent_decode(encoded_part.as_bytes()); + let decode = decode.decode_utf8_lossy(); + + decode.to_string() + }) + .collect::() +} + +#[cfg(test)] +mod tests { + use std::path::PathBuf; + use std::str::FromStr; + + use super::{decode_uri, encode_uri}; + + #[test] + fn encodes_uri() { + let file_path = "/these are important files/do_not_delete/file name.txt"; + let file_path = PathBuf::from_str(file_path).unwrap(); + let file_path = encode_uri(&file_path); + + assert_eq!( + file_path, + "/these%20are%20important%20files/do_not_delete/file%20name.txt" + ); + } + + #[test] + fn decodes_uri() { + let file_path = "these%20are%20important%20files/do_not_delete/file%20name.txt"; + let file_path = decode_uri(file_path); + let file_path = file_path.to_str().unwrap(); + + assert_eq!( + file_path, + "these are important files/do_not_delete/file name.txt" + ); + } +} From 8b04190afe9cdcd0d8663a95ee09ea2962b63192 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 12 Oct 2024 23:04:53 -0300 Subject: [PATCH 18/40] feat: naive handling --- crates/file-explorer/src/fs/mod.rs | 4 +- crates/file-explorer/src/lib.rs | 298 +++++++++++++++++++++- crates/file-explorer/src/templater/mod.rs | 23 +- 3 files changed, 304 insertions(+), 21 deletions(-) diff --git a/crates/file-explorer/src/fs/mod.rs b/crates/file-explorer/src/fs/mod.rs index 2959e4cc..6f3d4057 100644 --- a/crates/file-explorer/src/fs/mod.rs +++ b/crates/file-explorer/src/fs/mod.rs @@ -6,8 +6,8 @@ use std::path::{Component, Path, PathBuf}; use anyhow::Result; use tokio::fs::OpenOptions; -use self::directory::Directory; -use self::file::File; +pub use self::directory::Directory; +pub use self::file::File; /// Any OS filesystem entry recognized by `ScopedFileSystem` is treated as a /// `Entry` both `File` and `Directory` are possible values with full support by diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 300667c3..8d6f8213 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -2,24 +2,35 @@ mod fs; mod templater; mod utils; -use std::path::PathBuf; +use std::cmp::{Ord, Ordering}; +use std::fs::read_dir; +use std::mem::MaybeUninit; +use std::path::{Component, Path, PathBuf}; use std::str::FromStr; use std::sync::Arc; use anyhow::{Context, Result}; use async_trait::async_trait; +use chrono::{DateTime, Local}; +use fs::Entry; use http_body_util::Full; use hyper::body::{Bytes, Incoming}; -use hyper::{Method, Request, Response, Uri}; -use serde::Deserialize; +use hyper::header::{CONTENT_TYPE, ETAG, LAST_MODIFIED}; +use hyper::{Method, Request, Response, StatusCode, Uri}; +use percent_encoding::{percent_decode_str, utf8_percent_encode}; +use serde::{Deserialize, Serialize}; use tokio::runtime::Runtime; use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; -use self::fs::FileSystem; +use self::fs::{File, FileSystem}; use self::templater::Templater; -use self::utils::decode_uri; +use self::utils::{decode_uri, encode_uri, PERCENT_ENCODE_SET}; + +const FILE_BUFFER_SIZE: usize = 8 * 1024; + +pub type FileBuffer = Box<[MaybeUninit; FILE_BUFFER_SIZE]>; export_plugin!(register); @@ -58,12 +69,33 @@ impl Function for FileExplorer { let path = Self::parse_req_uri(req.uri().clone()).unwrap(); match req.method() { - &Method::GET => match self.fs.resolve(path).await.expect("failed to execute") { - fs::Entry::File(file) => { - Ok(Response::new(Full::new(Bytes::from(file.bytes())))) - } - fs::Entry::Directory(dir) => { - Ok(Response::new(Full::new(Bytes::from(format!("{:?}", dir))))) + &Method::GET => match self.fs.resolve(path).await { + Ok(entry) => match entry { + Entry::Directory(dir) => { + Ok(self.render_directory_index(dir.path()).await.unwrap()) + } + Entry::File(file) => Ok(Self::make_http_file_response(file).await.unwrap()), + }, + Err(err) => { + let status = match err.kind() { + std::io::ErrorKind::NotFound => hyper::StatusCode::NOT_FOUND, + std::io::ErrorKind::PermissionDenied => hyper::StatusCode::FORBIDDEN, + _ => hyper::StatusCode::BAD_REQUEST, + }; + + let code = match err.kind() { + std::io::ErrorKind::NotFound => "404", + std::io::ErrorKind::PermissionDenied => "403", + _ => "400", + }; + + Ok(Response::new(Full::new(Bytes::from("Unsupported method")))) + // .body(hyper::Body::from( + // handlebars::Handlebars::new().render_template( + // include_str!("./template/error.hbs"), + // &serde_json::json!({"error": err.to_string(), "code": code}), + // )?, + // ))?; } }, &Method::POST => Ok(Response::new(Full::new(Bytes::from("Prepare to upload")))), @@ -102,4 +134,248 @@ impl FileExplorer { PathBuf::from_str("/").context("Failed to parse URI") } + + /// Encodes a `PathBuf` component using `PercentEncode` with UTF-8 charset. + /// + /// # Panics + /// + /// If the component's `OsStr` representation doesn't belong to valid UTF-8 + /// this function panics. + fn encode_component(comp: Component) -> String { + let component = comp + .as_os_str() + .to_str() + .expect("The provided OsStr doesn't belong to the UTF-8 charset."); + + utf8_percent_encode(component, PERCENT_ENCODE_SET).to_string() + } + + fn breadcrumbs_from_path(root_dir: &Path, path: &Path) -> Result> { + let root_dir_name = root_dir + .components() + .last() + .unwrap() + .as_os_str() + .to_str() + .expect("The first path component is not UTF-8 charset compliant."); + let stripped = path + .strip_prefix(root_dir)? + .components() + .map(Self::encode_component) + .collect::>(); + + let mut breadcrumbs = stripped + .iter() + .enumerate() + .map(|(idx, entry_name)| BreadcrumbItem { + entry_name: percent_decode_str(entry_name) + .decode_utf8() + .expect("The path name is not UTF-8 compliant") + .to_string(), + entry_link: format!("/{}", stripped[0..=idx].join("/")), + }) + .collect::>(); + + breadcrumbs.insert( + 0, + BreadcrumbItem { + entry_name: String::from(root_dir_name), + entry_link: String::from("/"), + }, + ); + + Ok(breadcrumbs) + } + + /// Creates entry's relative path. Used by Handlebars template engine to + /// provide navigation through `FileExplorer` + /// + /// If the root_dir is: `https-server/src` + /// The entry path is: `https-server/src/server/service/file_explorer.rs` + /// + /// Then the resulting path from this function is the absolute path to + /// the "entry path" in relation to the "root_dir" path. + /// + /// This happens because links should behave relative to the `/` path + /// which in this case is `http-server/src` instead of system's root path. + fn make_dir_entry_link(root_dir: &Path, entry_path: &Path) -> String { + let path = entry_path.strip_prefix(root_dir).unwrap(); + + encode_uri(path) + } + + /// Creates a `DirectoryIndex` with the provided `root_dir` and `path` + /// (HTTP Request URI) + fn index_directory(root_dir: PathBuf, path: PathBuf) -> Result { + let breadcrumbs = Self::breadcrumbs_from_path(&root_dir, &path)?; + let entries = read_dir(path).context("Unable to read directory")?; + let mut directory_entries: Vec = Vec::new(); + + for entry in entries { + let entry = entry.context("Unable to read entry")?; + let metadata = entry.metadata()?; + let date_created = if let Ok(time) = metadata.created() { + Some(time.into()) + } else { + None + }; + let date_modified = if let Ok(time) = metadata.modified() { + Some(time.into()) + } else { + None + }; + + directory_entries.push(DirectoryEntry { + display_name: entry + .file_name() + .to_str() + .context("Unable to gather file name into a String")? + .to_string(), + is_dir: metadata.is_dir(), + size_bytes: metadata.len(), + entry_path: Self::make_dir_entry_link(&root_dir, &entry.path()), + date_created, + date_modified, + }); + } + + // if let Some(query_params) = query_params { + // if let Some(sort_by) = query_params.sort_by { + // match sort_by { + // SortBy::Name => { + // directory_entries.sort_by_key(|entry| entry.display_name.clone()); + // } + // SortBy::Size => directory_entries.sort_by_key(|entry| entry.size_bytes), + // SortBy::DateCreated => { + // directory_entries.sort_by_key(|entry| entry.date_created) + // } + // SortBy::DateModified => { + // directory_entries.sort_by_key(|entry| entry.date_modified) + // } + // }; + + // let sort_enum = match sort_by { + // SortBy::Name => Sort::Name, + // SortBy::Size => Sort::Size, + // SortBy::DateCreated => Sort::DateCreated, + // SortBy::DateModified => Sort::DateModified, + // }; + + // return Ok(DirectoryIndex { + // entries: directory_entries, + // breadcrumbs, + // sort: sort_enum, + // }); + // } + // } + + directory_entries.sort(); + + Ok(DirectoryIndex { + entries: directory_entries, + breadcrumbs, + sort: Sort::Directory, + }) + } + + /// Indexes the directory by creating a `DirectoryIndex`. Such `DirectoryIndex` + /// is used to build the Handlebars "Explorer" template using the Handlebars + /// engine and builds an HTTP Response containing such file + async fn render_directory_index(&self, path: PathBuf) -> Result>> { + let directory_index = Self::index_directory(self.path.clone(), path)?; + let html = self.templater.render(&directory_index).unwrap(); + + Response::builder() + .header(CONTENT_TYPE, "text/html") + .status(StatusCode::OK) + .body(Full::new(Bytes::from(html))) + .context("Failed to build response") + } + + pub async fn make_http_file_response(file: Box) -> Result>> { + Response::builder() + .header(CONTENT_TYPE, file.mime().to_string()) + .header( + ETAG, + format!( + "W/\"{0:x}-{1:x}.{2:x}\"", + file.size(), + file.last_modified().unwrap().timestamp(), + file.last_modified().unwrap().timestamp_subsec_nanos(), + ), + ) + .header(LAST_MODIFIED, file.last_modified().unwrap().to_rfc2822()) + .body(Full::new(Bytes::from(file.bytes()))) + .context("Failed to build HTTP File Response") + } +} + +/// A Directory entry used to display a File Explorer's entry. +/// This struct is directly related to the Handlebars template used +/// to power the File Explorer's UI +#[derive(Debug, Eq, Serialize)] +pub struct DirectoryEntry { + pub(crate) display_name: String, + pub(crate) is_dir: bool, + pub(crate) size_bytes: u64, + pub(crate) entry_path: String, + pub(crate) date_created: Option>, + pub(crate) date_modified: Option>, +} + +impl Ord for DirectoryEntry { + fn cmp(&self, other: &Self) -> Ordering { + if self.is_dir && other.is_dir { + return self.display_name.cmp(&other.display_name); + } + + if self.is_dir && !other.is_dir { + return Ordering::Less; + } + + Ordering::Greater + } +} + +impl PartialOrd for DirectoryEntry { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for DirectoryEntry { + fn eq(&self, other: &Self) -> bool { + if self.is_dir && other.is_dir { + return self.display_name == other.display_name; + } + + self.display_name == other.display_name + } +} + +/// A Breadcrumb Item used to navigate to previous path components +#[derive(Clone, Debug, Serialize, PartialEq, Eq)] +pub struct BreadcrumbItem { + pub(crate) entry_name: String, + pub(crate) entry_link: String, +} + +/// The value passed to the Handlebars template engine. +/// All references contained in File Explorer's UI are provided +/// via the `DirectoryIndex` struct +#[derive(Debug, Serialize)] +pub struct DirectoryIndex { + /// Directory listing entry + pub(crate) entries: Vec, + pub(crate) breadcrumbs: Vec, + pub(crate) sort: Sort, +} + +#[derive(Serialize, Debug, PartialEq, Deserialize)] +pub enum Sort { + Directory, + Name, + Size, + DateCreated, + DateModified, } diff --git a/crates/file-explorer/src/templater/mod.rs b/crates/file-explorer/src/templater/mod.rs index d4ba9c1d..752b60c4 100644 --- a/crates/file-explorer/src/templater/mod.rs +++ b/crates/file-explorer/src/templater/mod.rs @@ -3,6 +3,8 @@ use chrono::{DateTime, Local}; use handlebars::{handlebars_helper, Handlebars}; use humansize::{format_size, DECIMAL}; +use crate::{DirectoryIndex, Sort}; + const EXPLORER_KEY: &str = "Explorer"; const EXPLORER_TPL: &[u8] = include_bytes!("./hbs/explorer.hbs"); @@ -29,18 +31,23 @@ impl Templater { handlebars_helper!(size: |bytes: u64| format_size(bytes, DECIMAL)); hbs.register_helper("size", Box::new(size)); - // handlebars_helper!(sort_name: |sort: Sort| sort == Sort::Name); - // hbs.register_helper("sort_name", Box::new(sort_name)); + handlebars_helper!(sort_name: |sort: Sort| sort == Sort::Name); + hbs.register_helper("sort_name", Box::new(sort_name)); - // handlebars_helper!(sort_size: |sort: Sort| sort == Sort::Size); - // hbs.register_helper("sort_size", Box::new(sort_size)); + handlebars_helper!(sort_size: |sort: Sort| sort == Sort::Size); + hbs.register_helper("sort_size", Box::new(sort_size)); - // handlebars_helper!(sort_date_created: |sort: Sort| sort == Sort::DateCreated); - // hbs.register_helper("sort_date_created", Box::new(sort_date_created)); + handlebars_helper!(sort_date_created: |sort: Sort| sort == Sort::DateCreated); + hbs.register_helper("sort_date_created", Box::new(sort_date_created)); - // handlebars_helper!(sort_date_modified: |sort: Sort| sort == Sort::DateModified); - // hbs.register_helper("sort_date_modified", Box::new(sort_date_modified)); + handlebars_helper!(sort_date_modified: |sort: Sort| sort == Sort::DateModified); + hbs.register_helper("sort_date_modified", Box::new(sort_date_modified)); Ok(Self { backend: hbs }) } + + pub fn render(&self, di: &DirectoryIndex) -> Result { + let tpl = self.backend.render(EXPLORER_KEY, &di)?; + Ok(tpl) + } } From cdad860cc3754af132749eb9eaadb497b7fae838 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 12 Oct 2024 23:32:14 -0300 Subject: [PATCH 19/40] feat: attempt to pull --- Cargo.lock | 1 + Cargo.toml | 1 + crates/file-explorer/Cargo.toml | 1 + crates/file-explorer/src/lib.rs | 30 ++++++++++++++++++++++++++++-- 4 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 09a6e6af..eb048867 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -293,6 +293,7 @@ dependencies = [ "serde", "serde_json", "tokio", + "tokio-util", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 9235897d..f88989bd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ rustc_version = "0.4.1" serde = "1.0.210" serde_json = "1.0.128" tokio = "1.40" +tokio-util = "0.7.12" toml = "0.8.19" tower-http = "0.6.1" tower = "0.5.1" diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index 1a3e4b76..99a93709 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -22,5 +22,6 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } percent-encoding = { workspace = true } tokio = { workspace = true, features = ["fs", "rt-multi-thread", "signal", "macros"] } +tokio-util = { workspace = true, features = ["io"] } http-server-plugin = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 8d6f8213..7a5d3b63 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -3,7 +3,9 @@ mod templater; mod utils; use std::cmp::{Ord, Ordering}; +use std::env::current_dir; use std::fs::read_dir; +use std::io; use std::mem::MaybeUninit; use std::path::{Component, Path, PathBuf}; use std::str::FromStr; @@ -13,12 +15,14 @@ use anyhow::{Context, Result}; use async_trait::async_trait; use chrono::{DateTime, Local}; use fs::Entry; -use http_body_util::Full; +use futures::TryStreamExt; +use http_body_util::{BodyStream, Full}; use hyper::body::{Bytes, Incoming}; use hyper::header::{CONTENT_TYPE, ETAG, LAST_MODIFIED}; use hyper::{Method, Request, Response, StatusCode, Uri}; use percent_encoding::{percent_decode_str, utf8_percent_encode}; use serde::{Deserialize, Serialize}; +use tokio::io::AsyncWriteExt; use tokio::runtime::Runtime; use http_server_plugin::config::read_from_path; @@ -98,7 +102,29 @@ impl Function for FileExplorer { // ))?; } }, - &Method::POST => Ok(Response::new(Full::new(Bytes::from("Prepare to upload")))), + &Method::POST => { + let body = req.into_body(); + + println!("GOt file upload request!"); + let stream_of_frames = BodyStream::new(body); + let stream_of_bytes = stream_of_frames + .try_filter_map(|frame| async move { Ok(frame.into_data().ok()) }) + .map_err(|err| io::Error::new(io::ErrorKind::Other, err)); + let async_read = tokio_util::io::StreamReader::new(stream_of_bytes); + let mut async_read = std::pin::pin!(async_read); + println!("Got stream!"); + let mut upload_path = current_dir().unwrap(); + upload_path.push("Upload"); + let mut destination = tokio::fs::File::create(upload_path).await.unwrap(); + println!("Creates destination file!"); + + tokio::io::copy_buf(&mut async_read, &mut destination) + .await + .unwrap(); + destination.flush().await.unwrap(); + + Ok(Response::new(Full::new(Bytes::from("Upload Success!")))) + } _ => Ok(Response::new(Full::new(Bytes::from("Unsupported method")))), } }) From a137fe08b27ac95c8216f3a5803935b3d2971cd0 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sun, 13 Oct 2024 17:16:48 -0300 Subject: [PATCH 20/40] feat: setup tracing subscriber --- Cargo.lock | 180 ++++++++++++++++++++++++++- Cargo.toml | 2 + Testing | Bin 0 -> 8015 bytes crates/file-explorer/Cargo.toml | 2 + crates/file-explorer/src/lib.rs | 23 ++-- crates/http-server/Cargo.toml | 2 + crates/http-server/src/main.rs | 4 + crates/http-server/src/server/mod.rs | 3 + 8 files changed, 202 insertions(+), 14 deletions(-) create mode 100644 Testing diff --git a/Cargo.lock b/Cargo.lock index eb048867..f02a9e17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -294,6 +303,8 @@ dependencies = [ "serde_json", "tokio", "tokio-util", + "tracing", + "tracing-subscriber", ] [[package]] @@ -517,6 +528,8 @@ dependencies = [ "tokio", "tower", "tower-http", + "tracing", + "tracing-subscriber", ] [[package]] @@ -648,6 +661,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.158" @@ -672,11 +691,17 @@ checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" [[package]] name = "log" -version = "0.4.16" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "matchers" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" dependencies = [ - "cfg-if", + "regex-automata 0.1.10", ] [[package]] @@ -722,6 +747,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -746,6 +781,12 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -827,6 +868,50 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "regex" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12de2eff854e5fa4b1295edd650e227e9d8fb0c9e90b12e7f36d6a6811791a29" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.3.7", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49530408a136e16e5b486e883fbb6ba058e8e4e8ae6621a77b048b314336e629" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.7.5", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -906,6 +991,15 @@ dependencies = [ "digest", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "1.3.0" @@ -986,6 +1080,16 @@ dependencies = [ "syn", ] +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + [[package]] name = "tokio" version = "1.40.0" @@ -1108,9 +1212,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "tracing-core" version = "0.1.32" @@ -1118,6 +1234,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", ] [[package]] @@ -1159,6 +1305,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + [[package]] name = "version_check" version = "0.9.5" @@ -1236,6 +1388,28 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.52.0" diff --git a/Cargo.toml b/Cargo.toml index f88989bd..6be3d3be 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,6 +32,8 @@ tokio-util = "0.7.12" toml = "0.8.19" tower-http = "0.6.1" tower = "0.5.1" +tracing = "0.1.40" +tracing-subscriber = "0.3.18" # Workspace Crates http-server-plugin = { path = "crates/http-server-plugin" } diff --git a/Testing b/Testing new file mode 100644 index 0000000000000000000000000000000000000000..0cbf1e6519274be0dd9ba22da68dcca604100090 GIT binary patch literal 8015 zcmai32UJtrwhir~SU^Qk5r`-vH3~`(MO36CARSR5^w2`@5wW47(h0ry&^rNCigf7^ znjj!01SBLtNce~Q?tSl$|3AEpadJ-1-fNe&SDAC=wYH}638o87AQ0$;%KbY!AkZI) zAP`OGkw1Vpoitcy;D@%Yl9INHk`h?k)yc}%0R{q{i*)}`uXg(hYfE6%r5plH`SV~U zCFETj;qR#@&k`=_6E2+^M%gA4U2mmcvdM$mFpb4wO*zIm-lS8X4IMF6bzB%!6_$4P z3XqjuaF*l+Oym(31=&ti|rq+{hR7-FAM_uIg%at-)IM($| z+I(2FV?BGVz&R}TMnwqsbGmO=aNKtHu@nfcCRu3*ixEYAVp)Yw6ro>tyKEnz+{V&gq)43zNN^ z1m;GKJLjX;H#C%+jYf_#EU)k@wpPO%3ZB`Y%Y|VBAfq0eszmt?hq*Hs>dmL4B9n#y z73;$cRjf2LKmx$?5fIHwTM#|)L<3wGfC~ho`}6|D2;5Ht*PUeAzgz!EOs4z$nWl5U zqoS^oiVASowQz;O91%87ZkEJ=0AQ$LTRlTJLk)Fl3n#e96HBM3FcB{}aIC;b)=L_A zgu~pPfW6=jjtFTlD9@igq=D!C*AO1?pIzMSp*)5f+F&IoR~T4a8{$$o|0(*9 zrvEMa5CL;la)JYMy2<_LX#NuZXX9UjvXK3)|3el3((|8h0Y%F($wK~pXmU()pB=7% zKnft0JBoTYFy7bk2(^i&|eRO(Jbl3f%FCmHM7(?f1hA>yp;!`WHRM%ioLx{R$CgNymBB z5)nmDo0;w@foSMi6b}3X^U~(&7nIj$~T>Fma?i;V5r3ufe&`J*MVXHrie5>MMEk@f~5G0zQkA9T!bJIR4y(IP1 z%5%;`vQk074#{9HN+Bg4H~*9y{$dA#C(ZDWPG4`S-6&*)CrNzq`~HN z6|`}dX2NMxz18t*%483eG)})9Mo=rY^2ACl#3RxtoMjJLIoMf`q3`CJWmFTS;>nj6 z9<7{v#q5vHSy?A~XF|6v!_s`t1S&6_7qz@jxno~Eq>PPrKUfXGHhoUDUButpA^JK4l(Vn57dgtg~p{onsAB4)=Axxv@fw+wC_jwAOGUu5xOJdS1+m0MbvC^>)~p?;5H`u#@Ma&3CIZ zHBe&>duGF7*eaJa=#CAaNwsUnExUm>$EuVH+<0|A6T67R*igeBXx|$Br;v1v&xbQM zWPi7%sXR)gUH4 zm{fL4oVY;9XH@qgtG~J}Mvb)h!DOgzbxw5(T-qfWTgCC!*xtJU$Sf~Tp4=z8=<7B$pjG6Mljd=&D zBh$+5hmBF<@ZFI-G-iy>irb&?adUB_jIwOL@X|jw!o1Sa4Eky)&j3Q+=#gi}^bej(h$HJfx5&ObhX**Pa4P`9WI!;h8Q+jGizl!4dM-zFy~xDOd&vZ+I8bEmAN z0H3075eyj>mfs-y`G)Jx{xcy!>@k{c(WqN7ulK1OgI7*i2{dGFDN5UNqjS9`dvwJl!)1dix)O^H?F+ng0@%IZw_G}WoptScnzN`Pm>g! z-E{9$8KtysE(c0Pj@6PT^`B@Z1ya4ew3u@F#RP1U0(Nq&;qHrlqhY#W+m^SBPi&hW zH%ueOtBQx}eIRob{n zaS};Mb4hKJ+xYp-!k4&G+(6wCbS<~ui1%6I2QWd4GbI#qvQnP9^(9!p-R{je+e&)% z$D0kGh-xbR?ki*E9d!Dy!$RHPy(X!3lMMa&u__v)4_@!5Z*_TrW9JIKJ?+ozN-Q`F z_x&jid&}>|e%>#1yj%2O*E<6KZ~^JJ{d(ilGi85W5|b1kv#zt4frkzm)VUY$ykT=@+Ic{X3V2#>$o8E$Y-)H|1XP%|Ikqc| z%!(L~P+hnId7%1rg#9qgyX8wyT}NiUWpy<^Ae$NT>#SSsrEaQe`_MbLmhVdWCl_ENPZMhvTsfEmC1~Y1ruHNzP|zkF{w< zlXAOAC_$S8TWq0RmB?_&Y?9Knma?3j20j!MdeJQ-4siz?&+&C`-yb1QJmzDq$HKLU zTPB4s!g&r{igPNkTIui9=KgJpK!+`W{3-${J}6Pxo4=t_!!iKwN4C~y9f&JN;ub9g zrO^jCe~LX?qXg@ix6m<|XF!5)Z!D1T6E*JT0Akw_Z8*fJ!59!SYfH`n`kq15y4#`o z&Dm1-<)Lje*iaAO$lStK>u!e5~Ze$<$RqCRbn$c0ymG^!(mmUMdpyXKq?)0xW&AcYOr(ioHWF$MW|2ZWOEzBEw0*gsWKIqB1X@BPIe%c54D)5wgA zQm%$6w?l-iyW_>Z)>vf7D>E-kl10k>$;8o_=FqUFhX|&Fcl%cW#J zOd{0RIE)MG@Z)oceys<*BW8V%imG(*?n!YEJ=wLOH)S`Fy=WUKt(UO{z(E*ptbDUN zWxhMXK2{V~!r@f+UBAHi?01>aL!5?1E2vRdma~F$n3l2wwJ<8RDhKfGw*R%5GQ`43 zY`t%#`^r|4^&JL@s{PcNa+`rn#Cl`w00mMh8$At2JboNTT0gOX*COayP+{iCNI`?K zFnOvCU?t_9v0_-169yJ->iSv(MXY(4Z+N5AtBy5O)M-UEJ5~4q0OE{UKfiLN?qDYV4o- z+^ur3DFU&Gzz48(3I*k=eBi2phQ=u#h3Q47CkyF^^-|b50~`vKs!@IUU`u3Cn05kq zWqdhM%4p(AI&3_EgvK!2$CqGAK0TG06q>=rwkpcESbM$9NG^EK&6~{Y0eeAHj z6r^Cs0MrBY-_IVVMjHClM}TPE*mU^B$N?sxSHZ>B{}R*ptu`naa`>)u&fYI=)Qu7{ z$_H3{6x5ffXQo#Cv{@*Sx|<^%70jXlhBJI3%Pz+PzG6R?D0{+JX7;U^kVVT)->!@w zk$3R|%I_~m@o6Rg;$SZ5iiRYe70|mjPxRt&UFgQOwvH)NsNs9U7G@I5|G#NWC-Hvp zCas$BV^UwA;Tz-6ihnw6=4s~T05G6@mGG+o2uvDT8q6!W8_EX6MNwbUgy$FkRN(=% z`Ba=Z{KCnTd)o_1Ln%s(CUsseWfMj(LA0$3;Ps98t%zvzfNCJZePd15Ap?bg6%2W` z6kybRlbc2O9yxOUTxV=QW{>JyOB}PuWuhFV9g-f0(2j;*)%YFc0njOjre_RISNDVr z3gb^)l2I>;tqQGDp*RV%HpXJy2>S~#xo?W+wM_|!rY=~NM8}04YT6_f%&_?Wr@@<)J zGtd>fFk4=xJ+fOs8z#4t&4?t7i`x%%YRXgGn60iXq!L#EYU%UKXsXi5d|au<1Gm(~ zf{Tzus7(11em$x6#B25{e^0%Oi!yNp%0XUD!LtCOFWpaC-7LGRc#Xfpk_|cUndKV*$X~Jb<$!*WWef{}2eE zh`47iJC!<7V-{ghHm)5)zm{*H=yHEn*uxy1=3E0bm!mb!0l4Y@YlSy-TAt%BFX0n4 zgL|oug5$rBwSBw{mCR_|hr(&NndO@b`EQ?BP6c3z_$h8H?m9xBnw+FGVqQJZwA$6H zjyls|-IGYI2dGqLXUJ%&HA=KdFIP(=xC0<;1X#eOpvI3!#}PeHKxaaG4_>^BD~TtV z%5VP+r;Y>m+~=_}a>{DZI66TYb#23dE*VF{l5~e)m2zd|E?ukG7OwV!4E~0*;TZW2x z(5QhIt`B^{%#zO$9@4!y998- zogz$xd(QHM>-R_HKWa3crChTq(D4J@n&{aSFNN@{1YD%8C+)H4+WS`?)MPD&N=@jd zUcU>v?)RH$T0lz$^;yw3N%#Qay~AK`BxE^SRQ(vc$O^CK zpt)uLCd9Prx@m@MNQth#9&qS>{@Q(7=KFZ5keT`EfsT-=@P=0FLi&LDl)d&Q;QVV1 zHN)mxf*{t>o5}faUudAjLl7l4YZ8e0y#!4o)}0QZUeYkd1pr8H$w5EJg*!v$tAfet z>I^5ca{;Y&Fq+?bmG|3tNv18JZ`!RW>6syuHUnMz$Khsxtfi|z25O?2L0y1bEgNY9 z4E1=DC+w!cR{rs9AkCs^b?n+rb-)5H$L8U>ncBV2f|OZ0PtH5*ut;eo%5p^?`b)L3>L_TCa!4ja`9_rZc4O6Xpp=5oV{IFj*?4wLA zEmi=lqL|Id<<*GS8nuZ-sVJh`GS!798g`h+@C|#bp>Yt^4BbK2pUiPo&dtQEgHmwL zsV(y>*Ul^3&-j#2qgQX8%aw{d&6t?#oSuG zjoH>PlHwe=^ft}auAbC}6$6!=^Fs#y9M3XrkmC#GJ|5e*-i7_fB(Iok@$XHUeBUc^~qYK zWPxJmJ$0_D;D^9A1R(KXftWJ$5UQjDiOq~aiLO1?oAvVUT_q=I zA2R*s|B#jG`cfd_{y~2x>0JJh+891>`rVu=2uja9t!W+aa!>Ml{1z>14AU5pAi0HJ zB<(B*L}u&6%AR9lCEM+k8gG-U)M9msxb1=4j%@!;^+34b> z!v+ArSM;nlePX$tR~f`v<8_E1Xm8sw{bs$UJza#jrgR?}k#79@G_P!^D*kO7>BZ!k y_KJax!XY4$(VN*m;$?W!Ewyk_(5$BH#Cg%Lm4X&($WptPC1Tc{F(0R6%KiuHq>J4E literal 0 HcmV?d00001 diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index 99a93709..a9cdd81c 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -23,5 +23,7 @@ serde_json = { workspace = true } percent-encoding = { workspace = true } tokio = { workspace = true, features = ["fs", "rt-multi-thread", "signal", "macros"] } tokio-util = { workspace = true, features = ["io"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } http-server-plugin = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 7a5d3b63..2210fa3d 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -3,7 +3,6 @@ mod templater; mod utils; use std::cmp::{Ord, Ordering}; -use std::env::current_dir; use std::fs::read_dir; use std::io; use std::mem::MaybeUninit; @@ -15,8 +14,8 @@ use anyhow::{Context, Result}; use async_trait::async_trait; use chrono::{DateTime, Local}; use fs::Entry; -use futures::TryStreamExt; -use http_body_util::{BodyStream, Full}; +use futures::{StreamExt, TryStreamExt}; +use http_body_util::{BodyExt, BodyStream, Full}; use hyper::body::{Bytes, Incoming}; use hyper::header::{CONTENT_TYPE, ETAG, LAST_MODIFIED}; use hyper::{Method, Request, Response, StatusCode, Uri}; @@ -27,6 +26,7 @@ use tokio::runtime::Runtime; use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; +use tracing::info; use self::fs::{File, FileSystem}; use self::templater::Templater; @@ -46,6 +46,10 @@ extern "C" fn register( rt: Arc, registrar: &mut dyn PluginRegistrar, ) { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .init(); + let config: FileExplorerConfig = read_from_path(config_path, PLUGIN_NAME).unwrap(); registrar.register_function( @@ -104,21 +108,18 @@ impl Function for FileExplorer { }, &Method::POST => { let body = req.into_body(); - - println!("GOt file upload request!"); let stream_of_frames = BodyStream::new(body); let stream_of_bytes = stream_of_frames .try_filter_map(|frame| async move { Ok(frame.into_data().ok()) }) .map_err(|err| io::Error::new(io::ErrorKind::Other, err)); let async_read = tokio_util::io::StreamReader::new(stream_of_bytes); let mut async_read = std::pin::pin!(async_read); - println!("Got stream!"); - let mut upload_path = current_dir().unwrap(); - upload_path.push("Upload"); - let mut destination = tokio::fs::File::create(upload_path).await.unwrap(); - println!("Creates destination file!"); - tokio::io::copy_buf(&mut async_read, &mut destination) + info!("Uploading file"); + + let mut destination = tokio::fs::File::create("Testing").await.unwrap(); + + tokio::io::copy(&mut async_read, &mut destination) .await .unwrap(); destination.flush().await.unwrap(); diff --git a/crates/http-server/Cargo.toml b/crates/http-server/Cargo.toml index 0097014c..e82cf733 100644 --- a/crates/http-server/Cargo.toml +++ b/crates/http-server/Cargo.toml @@ -24,5 +24,7 @@ libloading = { workspace = true } tokio = { workspace = true, features = ["fs", "rt-multi-thread", "signal", "macros"] } tower-http = { workspace = true, features = ["cors"] } tower = { workspace = true, features = ["util"] } +tracing = { workspace = true } +tracing-subscriber = { workspace = true, features = ["env-filter"] } http-server-plugin = { workspace = true } diff --git a/crates/http-server/src/main.rs b/crates/http-server/src/main.rs index c6796375..4c880605 100644 --- a/crates/http-server/src/main.rs +++ b/crates/http-server/src/main.rs @@ -11,6 +11,10 @@ use self::server::Server; #[tokio::main] async fn main() -> Result<()> { + tracing_subscriber::fmt() + .with_max_level(tracing::Level::DEBUG) + .init(); + match Server::run().await { Ok(_) => { println!("Server exited successfuly"); diff --git a/crates/http-server/src/server/mod.rs b/crates/http-server/src/server/mod.rs index b044f7d1..95c47876 100644 --- a/crates/http-server/src/server/mod.rs +++ b/crates/http-server/src/server/mod.rs @@ -7,6 +7,7 @@ use hyper_util::{rt::TokioIo, service::TowerToHyperService}; use tokio::net::TcpListener; use tower::ServiceBuilder; use tower_http::cors::{Any, CorsLayer}; +use tracing::info; use crate::plugin::ExternalFunctions; @@ -14,6 +15,8 @@ pub struct Server {} impl Server { pub async fn run() -> Result<()> { + info!("Initializing server"); + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); let listener = TcpListener::bind(addr).await?; let functions = Arc::new(ExternalFunctions::new()); From 7c7e97b160f2a0561d1e2ec8838f2cf2a061238c Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sun, 13 Oct 2024 18:02:16 -0300 Subject: [PATCH 21/40] feat: more work on file explorer handler --- Testing | Bin 8015 -> 0 bytes crates/file-explorer/src/lib.rs | 46 ++++++++++++++++---------------- 2 files changed, 23 insertions(+), 23 deletions(-) delete mode 100644 Testing diff --git a/Testing b/Testing deleted file mode 100644 index 0cbf1e6519274be0dd9ba22da68dcca604100090..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8015 zcmai32UJtrwhir~SU^Qk5r`-vH3~`(MO36CARSR5^w2`@5wW47(h0ry&^rNCigf7^ znjj!01SBLtNce~Q?tSl$|3AEpadJ-1-fNe&SDAC=wYH}638o87AQ0$;%KbY!AkZI) zAP`OGkw1Vpoitcy;D@%Yl9INHk`h?k)yc}%0R{q{i*)}`uXg(hYfE6%r5plH`SV~U zCFETj;qR#@&k`=_6E2+^M%gA4U2mmcvdM$mFpb4wO*zIm-lS8X4IMF6bzB%!6_$4P z3XqjuaF*l+Oym(31=&ti|rq+{hR7-FAM_uIg%at-)IM($| z+I(2FV?BGVz&R}TMnwqsbGmO=aNKtHu@nfcCRu3*ixEYAVp)Yw6ro>tyKEnz+{V&gq)43zNN^ z1m;GKJLjX;H#C%+jYf_#EU)k@wpPO%3ZB`Y%Y|VBAfq0eszmt?hq*Hs>dmL4B9n#y z73;$cRjf2LKmx$?5fIHwTM#|)L<3wGfC~ho`}6|D2;5Ht*PUeAzgz!EOs4z$nWl5U zqoS^oiVASowQz;O91%87ZkEJ=0AQ$LTRlTJLk)Fl3n#e96HBM3FcB{}aIC;b)=L_A zgu~pPfW6=jjtFTlD9@igq=D!C*AO1?pIzMSp*)5f+F&IoR~T4a8{$$o|0(*9 zrvEMa5CL;la)JYMy2<_LX#NuZXX9UjvXK3)|3el3((|8h0Y%F($wK~pXmU()pB=7% zKnft0JBoTYFy7bk2(^i&|eRO(Jbl3f%FCmHM7(?f1hA>yp;!`WHRM%ioLx{R$CgNymBB z5)nmDo0;w@foSMi6b}3X^U~(&7nIj$~T>Fma?i;V5r3ufe&`J*MVXHrie5>MMEk@f~5G0zQkA9T!bJIR4y(IP1 z%5%;`vQk074#{9HN+Bg4H~*9y{$dA#C(ZDWPG4`S-6&*)CrNzq`~HN z6|`}dX2NMxz18t*%483eG)})9Mo=rY^2ACl#3RxtoMjJLIoMf`q3`CJWmFTS;>nj6 z9<7{v#q5vHSy?A~XF|6v!_s`t1S&6_7qz@jxno~Eq>PPrKUfXGHhoUDUButpA^JK4l(Vn57dgtg~p{onsAB4)=Axxv@fw+wC_jwAOGUu5xOJdS1+m0MbvC^>)~p?;5H`u#@Ma&3CIZ zHBe&>duGF7*eaJa=#CAaNwsUnExUm>$EuVH+<0|A6T67R*igeBXx|$Br;v1v&xbQM zWPi7%sXR)gUH4 zm{fL4oVY;9XH@qgtG~J}Mvb)h!DOgzbxw5(T-qfWTgCC!*xtJU$Sf~Tp4=z8=<7B$pjG6Mljd=&D zBh$+5hmBF<@ZFI-G-iy>irb&?adUB_jIwOL@X|jw!o1Sa4Eky)&j3Q+=#gi}^bej(h$HJfx5&ObhX**Pa4P`9WI!;h8Q+jGizl!4dM-zFy~xDOd&vZ+I8bEmAN z0H3075eyj>mfs-y`G)Jx{xcy!>@k{c(WqN7ulK1OgI7*i2{dGFDN5UNqjS9`dvwJl!)1dix)O^H?F+ng0@%IZw_G}WoptScnzN`Pm>g! z-E{9$8KtysE(c0Pj@6PT^`B@Z1ya4ew3u@F#RP1U0(Nq&;qHrlqhY#W+m^SBPi&hW zH%ueOtBQx}eIRob{n zaS};Mb4hKJ+xYp-!k4&G+(6wCbS<~ui1%6I2QWd4GbI#qvQnP9^(9!p-R{je+e&)% z$D0kGh-xbR?ki*E9d!Dy!$RHPy(X!3lMMa&u__v)4_@!5Z*_TrW9JIKJ?+ozN-Q`F z_x&jid&}>|e%>#1yj%2O*E<6KZ~^JJ{d(ilGi85W5|b1kv#zt4frkzm)VUY$ykT=@+Ic{X3V2#>$o8E$Y-)H|1XP%|Ikqc| z%!(L~P+hnId7%1rg#9qgyX8wyT}NiUWpy<^Ae$NT>#SSsrEaQe`_MbLmhVdWCl_ENPZMhvTsfEmC1~Y1ruHNzP|zkF{w< zlXAOAC_$S8TWq0RmB?_&Y?9Knma?3j20j!MdeJQ-4siz?&+&C`-yb1QJmzDq$HKLU zTPB4s!g&r{igPNkTIui9=KgJpK!+`W{3-${J}6Pxo4=t_!!iKwN4C~y9f&JN;ub9g zrO^jCe~LX?qXg@ix6m<|XF!5)Z!D1T6E*JT0Akw_Z8*fJ!59!SYfH`n`kq15y4#`o z&Dm1-<)Lje*iaAO$lStK>u!e5~Ze$<$RqCRbn$c0ymG^!(mmUMdpyXKq?)0xW&AcYOr(ioHWF$MW|2ZWOEzBEw0*gsWKIqB1X@BPIe%c54D)5wgA zQm%$6w?l-iyW_>Z)>vf7D>E-kl10k>$;8o_=FqUFhX|&Fcl%cW#J zOd{0RIE)MG@Z)oceys<*BW8V%imG(*?n!YEJ=wLOH)S`Fy=WUKt(UO{z(E*ptbDUN zWxhMXK2{V~!r@f+UBAHi?01>aL!5?1E2vRdma~F$n3l2wwJ<8RDhKfGw*R%5GQ`43 zY`t%#`^r|4^&JL@s{PcNa+`rn#Cl`w00mMh8$At2JboNTT0gOX*COayP+{iCNI`?K zFnOvCU?t_9v0_-169yJ->iSv(MXY(4Z+N5AtBy5O)M-UEJ5~4q0OE{UKfiLN?qDYV4o- z+^ur3DFU&Gzz48(3I*k=eBi2phQ=u#h3Q47CkyF^^-|b50~`vKs!@IUU`u3Cn05kq zWqdhM%4p(AI&3_EgvK!2$CqGAK0TG06q>=rwkpcESbM$9NG^EK&6~{Y0eeAHj z6r^Cs0MrBY-_IVVMjHClM}TPE*mU^B$N?sxSHZ>B{}R*ptu`naa`>)u&fYI=)Qu7{ z$_H3{6x5ffXQo#Cv{@*Sx|<^%70jXlhBJI3%Pz+PzG6R?D0{+JX7;U^kVVT)->!@w zk$3R|%I_~m@o6Rg;$SZ5iiRYe70|mjPxRt&UFgQOwvH)NsNs9U7G@I5|G#NWC-Hvp zCas$BV^UwA;Tz-6ihnw6=4s~T05G6@mGG+o2uvDT8q6!W8_EX6MNwbUgy$FkRN(=% z`Ba=Z{KCnTd)o_1Ln%s(CUsseWfMj(LA0$3;Ps98t%zvzfNCJZePd15Ap?bg6%2W` z6kybRlbc2O9yxOUTxV=QW{>JyOB}PuWuhFV9g-f0(2j;*)%YFc0njOjre_RISNDVr z3gb^)l2I>;tqQGDp*RV%HpXJy2>S~#xo?W+wM_|!rY=~NM8}04YT6_f%&_?Wr@@<)J zGtd>fFk4=xJ+fOs8z#4t&4?t7i`x%%YRXgGn60iXq!L#EYU%UKXsXi5d|au<1Gm(~ zf{Tzus7(11em$x6#B25{e^0%Oi!yNp%0XUD!LtCOFWpaC-7LGRc#Xfpk_|cUndKV*$X~Jb<$!*WWef{}2eE zh`47iJC!<7V-{ghHm)5)zm{*H=yHEn*uxy1=3E0bm!mb!0l4Y@YlSy-TAt%BFX0n4 zgL|oug5$rBwSBw{mCR_|hr(&NndO@b`EQ?BP6c3z_$h8H?m9xBnw+FGVqQJZwA$6H zjyls|-IGYI2dGqLXUJ%&HA=KdFIP(=xC0<;1X#eOpvI3!#}PeHKxaaG4_>^BD~TtV z%5VP+r;Y>m+~=_}a>{DZI66TYb#23dE*VF{l5~e)m2zd|E?ukG7OwV!4E~0*;TZW2x z(5QhIt`B^{%#zO$9@4!y998- zogz$xd(QHM>-R_HKWa3crChTq(D4J@n&{aSFNN@{1YD%8C+)H4+WS`?)MPD&N=@jd zUcU>v?)RH$T0lz$^;yw3N%#Qay~AK`BxE^SRQ(vc$O^CK zpt)uLCd9Prx@m@MNQth#9&qS>{@Q(7=KFZ5keT`EfsT-=@P=0FLi&LDl)d&Q;QVV1 zHN)mxf*{t>o5}faUudAjLl7l4YZ8e0y#!4o)}0QZUeYkd1pr8H$w5EJg*!v$tAfet z>I^5ca{;Y&Fq+?bmG|3tNv18JZ`!RW>6syuHUnMz$Khsxtfi|z25O?2L0y1bEgNY9 z4E1=DC+w!cR{rs9AkCs^b?n+rb-)5H$L8U>ncBV2f|OZ0PtH5*ut;eo%5p^?`b)L3>L_TCa!4ja`9_rZc4O6Xpp=5oV{IFj*?4wLA zEmi=lqL|Id<<*GS8nuZ-sVJh`GS!798g`h+@C|#bp>Yt^4BbK2pUiPo&dtQEgHmwL zsV(y>*Ul^3&-j#2qgQX8%aw{d&6t?#oSuG zjoH>PlHwe=^ft}auAbC}6$6!=^Fs#y9M3XrkmC#GJ|5e*-i7_fB(Iok@$XHUeBUc^~qYK zWPxJmJ$0_D;D^9A1R(KXftWJ$5UQjDiOq~aiLO1?oAvVUT_q=I zA2R*s|B#jG`cfd_{y~2x>0JJh+891>`rVu=2uja9t!W+aa!>Ml{1z>14AU5pAi0HJ zB<(B*L}u&6%AR9lCEM+k8gG-U)M9msxb1=4j%@!;^+34b> z!v+ArSM;nlePX$tR~f`v<8_E1Xm8sw{bs$UJza#jrgR?}k#79@G_P!^D*kO7>BZ!k y_KJax!XY4$(VN*m;$?W!Ewyk_(5$BH#Cg%Lm4X&($WptPC1Tc{F(0R6%KiuHq>J4E diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 2210fa3d..2d29756a 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -4,7 +4,7 @@ mod utils; use std::cmp::{Ord, Ordering}; use std::fs::read_dir; -use std::io; +use std::io::{self}; use std::mem::MaybeUninit; use std::path::{Component, Path, PathBuf}; use std::str::FromStr; @@ -14,14 +14,14 @@ use anyhow::{Context, Result}; use async_trait::async_trait; use chrono::{DateTime, Local}; use fs::Entry; -use futures::{StreamExt, TryStreamExt}; -use http_body_util::{BodyExt, BodyStream, Full}; +use futures::TryStreamExt; +use http_body_util::{BodyStream, Full}; use hyper::body::{Bytes, Incoming}; use hyper::header::{CONTENT_TYPE, ETAG, LAST_MODIFIED}; use hyper::{Method, Request, Response, StatusCode, Uri}; use percent_encoding::{percent_decode_str, utf8_percent_encode}; use serde::{Deserialize, Serialize}; -use tokio::io::AsyncWriteExt; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::runtime::Runtime; use http_server_plugin::config::read_from_path; @@ -76,8 +76,8 @@ impl Function for FileExplorer { self.rt.block_on(async move { let path = Self::parse_req_uri(req.uri().clone()).unwrap(); - match req.method() { - &Method::GET => match self.fs.resolve(path).await { + match *req.method() { + Method::GET => match self.fs.resolve(path).await { Ok(entry) => match entry { Entry::Directory(dir) => { Ok(self.render_directory_index(dir.path()).await.unwrap()) @@ -85,19 +85,8 @@ impl Function for FileExplorer { Entry::File(file) => Ok(Self::make_http_file_response(file).await.unwrap()), }, Err(err) => { - let status = match err.kind() { - std::io::ErrorKind::NotFound => hyper::StatusCode::NOT_FOUND, - std::io::ErrorKind::PermissionDenied => hyper::StatusCode::FORBIDDEN, - _ => hyper::StatusCode::BAD_REQUEST, - }; - - let code = match err.kind() { - std::io::ErrorKind::NotFound => "404", - std::io::ErrorKind::PermissionDenied => "403", - _ => "400", - }; - - Ok(Response::new(Full::new(Bytes::from("Unsupported method")))) + let message = format!("Failed to resolve path: {}", err); + Ok(Response::new(Full::new(Bytes::from(message)))) // .body(hyper::Body::from( // handlebars::Handlebars::new().render_template( // include_str!("./template/error.hbs"), @@ -106,19 +95,30 @@ impl Function for FileExplorer { // ))?; } }, - &Method::POST => { + Method::POST => { + let length = req + .headers() + .get("Content-Length") + .and_then(|v| v.to_str().ok()) + .and_then(|v| v.parse().ok()) + .unwrap_or(0); + + info!("Got {} bytes to process", length); + info!("Preparing..."); let body = req.into_body(); let stream_of_frames = BodyStream::new(body); + info!("Got body stream..."); let stream_of_bytes = stream_of_frames .try_filter_map(|frame| async move { Ok(frame.into_data().ok()) }) .map_err(|err| io::Error::new(io::ErrorKind::Other, err)); + info!("Got stream of bytes..."); let async_read = tokio_util::io::StreamReader::new(stream_of_bytes); let mut async_read = std::pin::pin!(async_read); - info!("Uploading file"); + let mut buf = Vec::with_capacity(length as usize); + async_read.read_exact(&mut buf).await.unwrap(); - let mut destination = tokio::fs::File::create("Testing").await.unwrap(); - + let mut destination = tokio::fs::File::create("test.png").await.unwrap(); tokio::io::copy(&mut async_read, &mut destination) .await .unwrap(); From 8e07f489947f63f5708986171a3b12008fbb993b Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Mon, 14 Oct 2024 23:00:30 -0300 Subject: [PATCH 22/40] feat: debugging --- crates/file-explorer/src/lib.rs | 33 ++++++++++----------------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 2d29756a..22103b81 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -21,12 +21,12 @@ use hyper::header::{CONTENT_TYPE, ETAG, LAST_MODIFIED}; use hyper::{Method, Request, Response, StatusCode, Uri}; use percent_encoding::{percent_decode_str, utf8_percent_encode}; use serde::{Deserialize, Serialize}; -use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::io::AsyncWriteExt; use tokio::runtime::Runtime; use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; -use tracing::info; +use tracing::error; use self::fs::{File, FileSystem}; use self::templater::Templater; @@ -96,30 +96,17 @@ impl Function for FileExplorer { } }, Method::POST => { - let length = req - .headers() - .get("Content-Length") - .and_then(|v| v.to_str().ok()) - .and_then(|v| v.parse().ok()) - .unwrap_or(0); - - info!("Got {} bytes to process", length); - info!("Preparing..."); - let body = req.into_body(); - let stream_of_frames = BodyStream::new(body); - info!("Got body stream..."); + let stream_of_frames = BodyStream::new(req.into_body()); let stream_of_bytes = stream_of_frames .try_filter_map(|frame| async move { Ok(frame.into_data().ok()) }) - .map_err(|err| io::Error::new(io::ErrorKind::Other, err)); - info!("Got stream of bytes..."); - let async_read = tokio_util::io::StreamReader::new(stream_of_bytes); - let mut async_read = std::pin::pin!(async_read); - info!("Uploading file"); - let mut buf = Vec::with_capacity(length as usize); - async_read.read_exact(&mut buf).await.unwrap(); - + .map_err(|err| { + error!(?err, "Error while processing stream of frames"); + io::Error::new(io::ErrorKind::Other, err) + }); + let stream_reader = tokio_util::io::StreamReader::new(stream_of_bytes); + let mut stream_reader = std::pin::pin!(stream_reader); let mut destination = tokio::fs::File::create("test.png").await.unwrap(); - tokio::io::copy(&mut async_read, &mut destination) + tokio::io::copy(&mut stream_reader, &mut destination) .await .unwrap(); destination.flush().await.unwrap(); From 7c69a049c775975c65c40d83cf158dc93aad1d97 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Tue, 15 Oct 2024 21:29:52 -0300 Subject: [PATCH 23/40] feat: some cleanup --- crates/file-explorer/src/fs/mod.rs | 1 + crates/file-explorer/src/lib.rs | 48 +++++----------------------- crates/http-server-plugin/src/lib.rs | 4 +-- crates/http-server/src/main.rs | 32 ++++++++++++------- crates/http-server/src/plugin.rs | 48 +++++++++++++++++----------- crates/http-server/src/server/mod.rs | 13 +++++--- 6 files changed, 69 insertions(+), 77 deletions(-) diff --git a/crates/file-explorer/src/fs/mod.rs b/crates/file-explorer/src/fs/mod.rs index 6f3d4057..2d1ba9cf 100644 --- a/crates/file-explorer/src/fs/mod.rs +++ b/crates/file-explorer/src/fs/mod.rs @@ -18,6 +18,7 @@ pub enum Entry { Directory(Directory), } +#[derive(Debug, Clone)] pub struct FileSystem { pub path: PathBuf, } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 22103b81..e2e33946 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -4,7 +4,6 @@ mod utils; use std::cmp::{Ord, Ordering}; use std::fs::read_dir; -use std::io::{self}; use std::mem::MaybeUninit; use std::path::{Component, Path, PathBuf}; use std::str::FromStr; @@ -14,19 +13,16 @@ use anyhow::{Context, Result}; use async_trait::async_trait; use chrono::{DateTime, Local}; use fs::Entry; -use futures::TryStreamExt; -use http_body_util::{BodyStream, Full}; +use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::header::{CONTENT_TYPE, ETAG, LAST_MODIFIED}; use hyper::{Method, Request, Response, StatusCode, Uri}; use percent_encoding::{percent_decode_str, utf8_percent_encode}; use serde::{Deserialize, Serialize}; -use tokio::io::AsyncWriteExt; -use tokio::runtime::Runtime; +use tokio::runtime::Handle; use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; -use tracing::error; use self::fs::{File, FileSystem}; use self::templater::Templater; @@ -41,13 +37,9 @@ export_plugin!(register); const PLUGIN_NAME: &str = "file-explorer"; #[allow(improper_ctypes_definitions)] -extern "C" fn register( - config_path: PathBuf, - rt: Arc, - registrar: &mut dyn PluginRegistrar, -) { +extern "C" fn register(config_path: PathBuf, rt: Arc, registrar: &mut dyn PluginRegistrar) { tracing_subscriber::fmt() - .with_max_level(tracing::Level::DEBUG) + .with_max_level(tracing::Level::INFO) .init(); let config: FileExplorerConfig = read_from_path(config_path, PLUGIN_NAME).unwrap(); @@ -64,7 +56,7 @@ struct FileExplorerConfig { } struct FileExplorer { - rt: Arc, + rt: Arc, fs: FileSystem, path: PathBuf, templater: Templater, @@ -73,9 +65,9 @@ struct FileExplorer { #[async_trait] impl Function for FileExplorer { async fn call(&self, req: Request) -> Result>, InvocationError> { - self.rt.block_on(async move { - let path = Self::parse_req_uri(req.uri().clone()).unwrap(); + let path = Self::parse_req_uri(req.uri().clone()).unwrap(); + self.rt.block_on(async move { match *req.method() { Method::GET => match self.fs.resolve(path).await { Ok(entry) => match entry { @@ -87,32 +79,8 @@ impl Function for FileExplorer { Err(err) => { let message = format!("Failed to resolve path: {}", err); Ok(Response::new(Full::new(Bytes::from(message)))) - // .body(hyper::Body::from( - // handlebars::Handlebars::new().render_template( - // include_str!("./template/error.hbs"), - // &serde_json::json!({"error": err.to_string(), "code": code}), - // )?, - // ))?; } }, - Method::POST => { - let stream_of_frames = BodyStream::new(req.into_body()); - let stream_of_bytes = stream_of_frames - .try_filter_map(|frame| async move { Ok(frame.into_data().ok()) }) - .map_err(|err| { - error!(?err, "Error while processing stream of frames"); - io::Error::new(io::ErrorKind::Other, err) - }); - let stream_reader = tokio_util::io::StreamReader::new(stream_of_bytes); - let mut stream_reader = std::pin::pin!(stream_reader); - let mut destination = tokio::fs::File::create("test.png").await.unwrap(); - tokio::io::copy(&mut stream_reader, &mut destination) - .await - .unwrap(); - destination.flush().await.unwrap(); - - Ok(Response::new(Full::new(Bytes::from("Upload Success!")))) - } _ => Ok(Response::new(Full::new(Bytes::from("Unsupported method")))), } }) @@ -120,7 +88,7 @@ impl Function for FileExplorer { } impl FileExplorer { - fn new(rt: Arc, path: PathBuf) -> Result { + fn new(rt: Arc, path: PathBuf) -> Result { let fs = FileSystem::new(path.clone())?; let templater = Templater::new()?; diff --git a/crates/http-server-plugin/src/lib.rs b/crates/http-server-plugin/src/lib.rs index 33a7190a..c5a8c947 100644 --- a/crates/http-server-plugin/src/lib.rs +++ b/crates/http-server-plugin/src/lib.rs @@ -7,7 +7,7 @@ use async_trait::async_trait; use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::{Request, Response}; -use tokio::runtime::Runtime; +use tokio::runtime::Handle; pub static CORE_VERSION: &str = env!("CARGO_PKG_VERSION"); pub static RUSTC_VERSION: &str = env!("RUSTC_VERSION"); @@ -28,7 +28,7 @@ pub struct PluginDeclaration { pub rustc_version: &'static str, pub core_version: &'static str, pub register: - unsafe extern "C" fn(config_path: PathBuf, rt: Arc, &mut dyn PluginRegistrar), + unsafe extern "C" fn(config_path: PathBuf, rt: Arc, &mut dyn PluginRegistrar), } pub trait PluginRegistrar { diff --git a/crates/http-server/src/main.rs b/crates/http-server/src/main.rs index 4c880605..9b340e1d 100644 --- a/crates/http-server/src/main.rs +++ b/crates/http-server/src/main.rs @@ -3,26 +3,34 @@ pub mod config; pub mod plugin; pub mod server; -use std::process::exit; +use std::{process::exit, sync::Arc}; use anyhow::Result; +use tokio::runtime::Builder; use self::server::Server; -#[tokio::main] -async fn main() -> Result<()> { +fn main() -> Result<()> { + let rt = Builder::new_multi_thread() + .enable_all() + .thread_name("http-server") + .build()?; + let rt = Arc::new(rt); + tracing_subscriber::fmt() .with_max_level(tracing::Level::DEBUG) .init(); - match Server::run().await { - Ok(_) => { - println!("Server exited successfuly"); - Ok(()) - } - Err(error) => { - eprint!("{:?}", error); - exit(1); + rt.block_on(async { + match Server::run(Arc::clone(&rt)).await { + Ok(_) => { + println!("Server exited successfuly"); + Ok(()) + } + Err(error) => { + eprint!("{:?}", error); + exit(1); + } } - } + }) } diff --git a/crates/http-server/src/plugin.rs b/crates/http-server/src/plugin.rs index 514730d6..50f74053 100644 --- a/crates/http-server/src/plugin.rs +++ b/crates/http-server/src/plugin.rs @@ -9,7 +9,7 @@ use http_body_util::Full; use hyper::body::{Bytes, Incoming}; use hyper::{Request, Response}; use libloading::Library; -use tokio::runtime::Runtime; +use tokio::runtime::Handle; use tokio::sync::Mutex; use http_server_plugin::{ @@ -18,6 +18,7 @@ use http_server_plugin::{ /// A proxy object which wraps a [`Function`] and makes sure it can't outlive /// the library it came from. +#[derive(Clone)] pub struct FunctionProxy { function: Arc, _lib: Arc, @@ -30,15 +31,27 @@ impl Function for FunctionProxy { } } -#[derive(Default)] pub struct ExternalFunctions { + handle: Arc, functions: Mutex>, libraries: Mutex>>, } +impl Default for ExternalFunctions { + fn default() -> Self { + Self::new() + } +} + impl ExternalFunctions { pub fn new() -> ExternalFunctions { - ExternalFunctions::default() + let handle = Arc::new(Handle::current()); + + ExternalFunctions { + handle, + functions: Mutex::new(HashMap::default()), + libraries: Mutex::new(Vec::new()), + } } /// Loads a plugin from the given path. @@ -49,6 +62,7 @@ impl ExternalFunctions { /// functions from it. pub async unsafe fn load>( &self, + rt_handle: Arc, config_path: PathBuf, library_path: P, ) -> io::Result<()> { @@ -64,32 +78,30 @@ impl ExternalFunctions { let mut registrar = PluginRegistrar::new(Arc::clone(&library)); - (decl.register)( - config_path, - Arc::new(Runtime::new().unwrap()), - &mut registrar, - ); + (decl.register)(config_path, Arc::clone(&rt_handle), &mut registrar); self.functions.lock().await.extend(registrar.functions); - self.libraries.lock().await.push(library); Ok(()) } + async fn get_function(&self, func: &str) -> Option { + self.functions.lock().await.get(func).cloned() + } + pub async fn call( &self, - function: &str, + func: &str, req: Request, ) -> Result>, InvocationError> { - self.functions - .lock() - .await - .get(function) - .ok_or_else(|| format!("\"{}\" not found", function)) - .unwrap() - .call(req) - .await + let function_proxy = self.get_function(func).await.unwrap(); + let join_handle = self + .handle + .spawn(async move { function_proxy.call(req).await }) + .await; + + join_handle.unwrap() } } diff --git a/crates/http-server/src/server/mod.rs b/crates/http-server/src/server/mod.rs index 95c47876..78242fec 100644 --- a/crates/http-server/src/server/mod.rs +++ b/crates/http-server/src/server/mod.rs @@ -5,6 +5,7 @@ use http_body_util::Full; use hyper::{body::Bytes, server::conn::http1, Method, Response}; use hyper_util::{rt::TokioIo, service::TowerToHyperService}; use tokio::net::TcpListener; +use tokio::runtime::Runtime; use tower::ServiceBuilder; use tower_http::cors::{Any, CorsLayer}; use tracing::info; @@ -14,7 +15,7 @@ use crate::plugin::ExternalFunctions; pub struct Server {} impl Server { - pub async fn run() -> Result<()> { + pub async fn run(rt: Arc) -> Result<()> { info!("Initializing server"); let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); @@ -22,10 +23,11 @@ impl Server { let functions = Arc::new(ExternalFunctions::new()); let plugin_library = PathBuf::from_str("./target/debug/libfile_explorer.dylib").unwrap(); let config = PathBuf::from_str("./config.toml").unwrap(); + let handle = Arc::new(rt.handle().to_owned()); unsafe { functions - .load(config, plugin_library) + .load(Arc::clone(&handle), config, plugin_library) .await .expect("Function loading failed"); } @@ -41,10 +43,11 @@ impl Server { // allow requests from any origin .allow_origin(Any); - tokio::spawn(async move { + handle.spawn(async move { let functions = Arc::clone(&functions); - // N.B. should use tower service_fn here, since it's reuqired to be implemented tower Service trait before convert to hyper Service! + // N.B. should use tower service_fn here, since it's reuqired to be implemented tower + // Service trait before convert to hyper Service! let svc = tower::service_fn(|req| async { match functions.call("file-explorer", req).await { Ok(res) => Ok::< @@ -60,7 +63,7 @@ impl Server { } }); let svc = ServiceBuilder::new().layer(cors).service(svc); - // Convert it to hyper service + let svc = TowerToHyperService::new(svc); if let Err(err) = http1::Builder::new().serve_connection(io, svc).await { eprintln!("server error: {}", err); From 743696c9534e96961a3585ef06c3235ee7fabc51 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 19 Oct 2024 18:38:09 -0300 Subject: [PATCH 24/40] feat: upload works --- Cargo.lock | 6 ++++++ Cargo.toml | 2 ++ crates/file-explorer/Cargo.toml | 2 ++ crates/file-explorer/src/lib.rs | 23 ++++++++++++++++++----- crates/http-server-plugin/Cargo.toml | 2 ++ crates/http-server-plugin/src/lib.rs | 11 ++++++++--- crates/http-server/Cargo.toml | 2 ++ crates/http-server/src/plugin.rs | 18 ++++++++++++------ crates/http-server/src/server/mod.rs | 22 ++++++++++++---------- 9 files changed, 64 insertions(+), 24 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f02a9e17..4cea3ffb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -290,9 +290,11 @@ version = "0.0.0" dependencies = [ "anyhow", "async-trait", + "bytes", "chrono", "futures", "handlebars", + "http", "http-body-util", "http-server-plugin", "humansize", @@ -518,7 +520,9 @@ version = "1.0.0-draft+1" dependencies = [ "anyhow", "async-trait", + "bytes", "clap", + "http", "http-auth-basic", "http-body-util", "http-server-plugin", @@ -538,6 +542,8 @@ version = "1.0.0-draft+1" dependencies = [ "anyhow", "async-trait", + "bytes", + "http", "http-body-util", "hyper", "rustc_version", diff --git a/Cargo.toml b/Cargo.toml index 6be3d3be..c11aaff2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,13 @@ resolver = "1" [workspace.dependencies] anyhow = "1.0" async-trait = "0.1.83" +bytes = "1.7.1" chrono = "0.4.38" clap = "4.5.20" futures = "0.3.31" handlebars = "6.1.0" humansize = "2.1.3" +http = "1.1.0" http-auth-basic = "0.3.3" http-body-util = "0.1" hyper = "1.4" diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index a9cdd81c..417b2d78 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -11,9 +11,11 @@ crate-type = ["cdylib"] [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } +bytes = { workspace = true } chrono = { workspace = true, features = ["serde"] } futures = { workspace = true } handlebars = { workspace = true } +http = { workspace = true } http-body-util = { workspace = true } humansize = { workspace = true } hyper = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index e2e33946..df9a19bf 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -13,12 +13,14 @@ use anyhow::{Context, Result}; use async_trait::async_trait; use chrono::{DateTime, Local}; use fs::Entry; +use http::request::Parts; use http_body_util::Full; -use hyper::body::{Bytes, Incoming}; +use hyper::body::Bytes; use hyper::header::{CONTENT_TYPE, ETAG, LAST_MODIFIED}; -use hyper::{Method, Request, Response, StatusCode, Uri}; +use hyper::{Method, Response, StatusCode, Uri}; use percent_encoding::{percent_decode_str, utf8_percent_encode}; use serde::{Deserialize, Serialize}; +use tokio::io::AsyncWriteExt; use tokio::runtime::Handle; use http_server_plugin::config::read_from_path; @@ -64,11 +66,15 @@ struct FileExplorer { #[async_trait] impl Function for FileExplorer { - async fn call(&self, req: Request) -> Result>, InvocationError> { - let path = Self::parse_req_uri(req.uri().clone()).unwrap(); + async fn call( + &self, + parts: Parts, + body: Bytes, + ) -> Result>, InvocationError> { + let path = Self::parse_req_uri(parts.uri).unwrap(); self.rt.block_on(async move { - match *req.method() { + match parts.method { Method::GET => match self.fs.resolve(path).await { Ok(entry) => match entry { Entry::Directory(dir) => { @@ -81,6 +87,13 @@ impl Function for FileExplorer { Ok(Response::new(Full::new(Bytes::from(message)))) } }, + Method::POST => { + let mut file = tokio::fs::File::create("output.png").await.unwrap(); + file.write_all(&body).await.unwrap(); + Ok(Response::new(Full::new(Bytes::from( + "POST method is not supported", + )))) + } _ => Ok(Response::new(Full::new(Bytes::from("Unsupported method")))), } }) diff --git a/crates/http-server-plugin/Cargo.toml b/crates/http-server-plugin/Cargo.toml index 5e5cd6b2..41c1168b 100644 --- a/crates/http-server-plugin/Cargo.toml +++ b/crates/http-server-plugin/Cargo.toml @@ -13,6 +13,8 @@ readme = "README.md" [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } +bytes = { workspace = true } +http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } serde = { workspace = true } diff --git a/crates/http-server-plugin/src/lib.rs b/crates/http-server-plugin/src/lib.rs index c5a8c947..4ad85f31 100644 --- a/crates/http-server-plugin/src/lib.rs +++ b/crates/http-server-plugin/src/lib.rs @@ -4,9 +4,10 @@ use std::path::PathBuf; use std::sync::Arc; use async_trait::async_trait; +use http::request::Parts; use http_body_util::Full; -use hyper::body::{Bytes, Incoming}; -use hyper::{Request, Response}; +use hyper::body::Bytes; +use hyper::Response; use tokio::runtime::Handle; pub static CORE_VERSION: &str = env!("CARGO_PKG_VERSION"); @@ -14,7 +15,11 @@ pub static RUSTC_VERSION: &str = env!("RUSTC_VERSION"); #[async_trait] pub trait Function: Send + Sync { - async fn call(&self, req: Request) -> Result>, InvocationError>; + async fn call( + &self, + parts: Parts, + body: Bytes, + ) -> Result>, InvocationError>; } #[derive(Debug)] diff --git a/crates/http-server/Cargo.toml b/crates/http-server/Cargo.toml index e82cf733..58a68b50 100644 --- a/crates/http-server/Cargo.toml +++ b/crates/http-server/Cargo.toml @@ -15,7 +15,9 @@ readme = "README.md" [dependencies] anyhow = { workspace = true } async-trait = { workspace = true } +bytes = { workspace = true } clap = { workspace = true, features = ["env", "derive", "std"] } +http = { workspace = true } http-auth-basic = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } diff --git a/crates/http-server/src/plugin.rs b/crates/http-server/src/plugin.rs index 50f74053..f601c287 100644 --- a/crates/http-server/src/plugin.rs +++ b/crates/http-server/src/plugin.rs @@ -5,9 +5,10 @@ use std::path::PathBuf; use std::sync::Arc; use async_trait::async_trait; +use http::request::Parts; use http_body_util::Full; -use hyper::body::{Bytes, Incoming}; -use hyper::{Request, Response}; +use hyper::body::Bytes; +use hyper::Response; use libloading::Library; use tokio::runtime::Handle; use tokio::sync::Mutex; @@ -26,8 +27,12 @@ pub struct FunctionProxy { #[async_trait] impl Function for FunctionProxy { - async fn call(&self, req: Request) -> Result>, InvocationError> { - self.function.call(req).await + async fn call( + &self, + parts: Parts, + bytes: Bytes, + ) -> Result>, InvocationError> { + self.function.call(parts, bytes).await } } @@ -93,12 +98,13 @@ impl ExternalFunctions { pub async fn call( &self, func: &str, - req: Request, + parts: Parts, + bytes: Bytes, ) -> Result>, InvocationError> { let function_proxy = self.get_function(func).await.unwrap(); let join_handle = self .handle - .spawn(async move { function_proxy.call(req).await }) + .spawn(async move { function_proxy.call(parts, bytes).await }) .await; join_handle.unwrap() diff --git a/crates/http-server/src/server/mod.rs b/crates/http-server/src/server/mod.rs index 78242fec..0f274733 100644 --- a/crates/http-server/src/server/mod.rs +++ b/crates/http-server/src/server/mod.rs @@ -1,8 +1,12 @@ use std::{convert::Infallible, net::SocketAddr, path::PathBuf, str::FromStr, sync::Arc}; use anyhow::Result; -use http_body_util::Full; -use hyper::{body::Bytes, server::conn::http1, Method, Response}; +use http_body_util::{BodyExt, Full}; +use hyper::{ + body::{Bytes, Incoming}, + server::conn::http1, + Method, Request, Response, +}; use hyper_util::{rt::TokioIo, service::TowerToHyperService}; use tokio::net::TcpListener; use tokio::runtime::Runtime; @@ -36,20 +40,17 @@ impl Server { let (stream, _) = listener.accept().await?; let io = TokioIo::new(stream); let functions = Arc::clone(&functions); - let cors = CorsLayer::new() - // allow `GET` and `POST` when accessing the resource .allow_methods([Method::GET, Method::POST]) - // allow requests from any origin .allow_origin(Any); handle.spawn(async move { let functions = Arc::clone(&functions); + let svc = tower::service_fn(|req: Request| async { + let (parts, body) = req.into_parts(); + let body = body.collect().await.unwrap().to_bytes(); - // N.B. should use tower service_fn here, since it's reuqired to be implemented tower - // Service trait before convert to hyper Service! - let svc = tower::service_fn(|req| async { - match functions.call("file-explorer", req).await { + match functions.call("file-explorer", parts, body).await { Ok(res) => Ok::< Response>, Infallible, @@ -62,9 +63,10 @@ impl Server { } } }); - let svc = ServiceBuilder::new().layer(cors).service(svc); + let svc = ServiceBuilder::new().layer(cors).service(svc); let svc = TowerToHyperService::new(svc); + if let Err(err) = http1::Builder::new().serve_connection(io, svc).await { eprintln!("server error: {}", err); } From b967e60f4b25377541549ad086894f21e1bbbd46 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 19 Oct 2024 18:56:21 -0300 Subject: [PATCH 25/40] fix: use correct filename --- .github/workflows/test.yml | 33 ------------------- Cargo.lock | 49 ++++++++++++++++++++++++++++ crates/file-explorer/src/lib.rs | 3 +- crates/http-server-plugin/Cargo.toml | 4 +-- 4 files changed, 53 insertions(+), 36 deletions(-) delete mode 100644 .github/workflows/test.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml deleted file mode 100644 index bac22546..00000000 --- a/.github/workflows/test.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: test -on: - merge_group: - pull_request: - push: - branches: - - main - -jobs: - test: - runs-on: ubuntu-latest - - steps: - - name: Setup Bats - uses: mig4/setup-bats@v1 - - - name: Checkout Source Code - uses: actions/checkout@v3 - - - name: Build for Release - uses: actions-rs/cargo@v1 - with: - command: build - args: --release --locked - - - name: Run E2E Tests - env: - BIN: ./target/release/http-server - run: | - bats tests/e2e - - # - name: Run Unit Tests - # run: cargo test \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 4cea3ffb..59e9fbd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -695,6 +695,16 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -793,6 +803,29 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "percent-encoding" version = "2.3.1" @@ -874,6 +907,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.9.4" @@ -939,6 +981,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.23" @@ -1106,6 +1154,7 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index df9a19bf..c0cfe308 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -88,7 +88,8 @@ impl Function for FileExplorer { } }, Method::POST => { - let mut file = tokio::fs::File::create("output.png").await.unwrap(); + let filename = path.file_name().unwrap().to_str().unwrap(); + let mut file = tokio::fs::File::create(filename).await.unwrap(); file.write_all(&body).await.unwrap(); Ok(Response::new(Full::new(Bytes::from( "POST method is not supported", diff --git a/crates/http-server-plugin/Cargo.toml b/crates/http-server-plugin/Cargo.toml index 41c1168b..e632453c 100644 --- a/crates/http-server-plugin/Cargo.toml +++ b/crates/http-server-plugin/Cargo.toml @@ -8,7 +8,7 @@ repository = "https://github.com/http-server-rs/http-server" categories = ["web-programming", "web-programming::http-server"] keywords = ["sdk", "http", "server", "plugin", "dll"] license = "MIT OR Apache-2.0" -readme = "README.md" +readme = "../../README.md" [dependencies] anyhow = { workspace = true } @@ -18,7 +18,7 @@ http = { workspace = true } http-body-util = { workspace = true } hyper = { workspace = true } serde = { workspace = true } -tokio = { workspace = true } +tokio = { workspace = true, features = ["full"] } toml = { workspace = true } [build-dependencies] From f4d6361943b393dcdf09313fff57fdba2611120c Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Tue, 22 Oct 2024 21:19:40 -0300 Subject: [PATCH 26/40] feat: file explorer ui setup --- .gitignore | 4 + Cargo.lock | 1072 +++++++++++++++++++- Cargo.toml | 5 + Justfile | 12 + README.md | 308 ------ crates/file-explorer-ui/Cargo.toml | 26 + crates/file-explorer-ui/Trunk.toml | 9 + crates/file-explorer-ui/assets/.gitkeep | 0 crates/file-explorer-ui/public/index.html | 9 + crates/file-explorer-ui/public/styles.css | 3 + crates/file-explorer-ui/src/bin/main.rs | 9 + crates/file-explorer-ui/src/lib.rs | 13 + crates/file-explorer-ui/tailwind.config.js | 8 + 13 files changed, 1163 insertions(+), 315 deletions(-) create mode 100644 crates/file-explorer-ui/Cargo.toml create mode 100644 crates/file-explorer-ui/Trunk.toml create mode 100644 crates/file-explorer-ui/assets/.gitkeep create mode 100644 crates/file-explorer-ui/public/index.html create mode 100644 crates/file-explorer-ui/public/styles.css create mode 100644 crates/file-explorer-ui/src/bin/main.rs create mode 100644 crates/file-explorer-ui/src/lib.rs create mode 100644 crates/file-explorer-ui/tailwind.config.js diff --git a/.gitignore b/.gitignore index 20556c12..e471ea71 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,7 @@ Thumbs.db # Tests # ######### dhat-heap.json + +# Web # +####### +dist diff --git a/Cargo.lock b/Cargo.lock index 59e9fbd2..813b5eea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -96,6 +96,17 @@ version = "1.0.75" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4668cab20f66d8d020e1fbc0ebe47217433c1b6c8f2040faf858554e394ace6" +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "async-trait" version = "0.1.83" @@ -113,6 +124,36 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "attribute-derive" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1ee502851995027b06f99f5ffbeffa1406b38d0b318a1ebfa469332c6cbafd" +dependencies = [ + "attribute-derive-macro", + "derive-where", + "manyhow", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3601467f634cfe36c4780ca9c75dea9a5b34529c1f2810676a337e7e0997f954" +dependencies = [ + "collection_literals", + "interpolator", + "manyhow", + "proc-macro-utils 0.8.0", + "proc-macro2", + "quote", + "quote-use", + "syn", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -140,6 +181,12 @@ version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.4.0" @@ -167,6 +214,12 @@ version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" + [[package]] name = "cc" version = "1.1.18" @@ -197,6 +250,33 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + [[package]] name = "clap" version = "4.5.20" @@ -237,12 +317,71 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +[[package]] +name = "collection_literals" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" + [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" +[[package]] +name = "config" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +dependencies = [ + "convert_case", + "lazy_static", + "nom", + "pathdiff", + "serde", + "toml", +] + +[[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 = "const_format" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c655d81ff1114fb0dcdea9225ea9f0cc712a6f8d189378e82bdf62a473a64b" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff1a44b93f47b1bac19a27932f5c591e43d1ba357ee4f61526c8a25603f0eb1" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -258,6 +397,12 @@ dependencies = [ "libc", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -268,6 +413,30 @@ dependencies = [ "typenum", ] +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "derive-where" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -278,6 +447,18 @@ dependencies = [ "crypto-common", ] +[[package]] +name = "drain_filter_polyfill" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "equivalent" version = "1.0.0" @@ -309,12 +490,34 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "file-explorer-ui" +version = "0.0.0" +dependencies = [ + "anyhow", + "leptos", + "leptos_meta", + "leptos_router", + "wasm-bindgen", + "wasm-bindgen-test", + "web-sys", +] + [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + [[package]] name = "futures" version = "0.3.31" @@ -414,12 +617,59 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + [[package]] name = "gimli" version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "gloo-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "h2" version = "0.4.6" @@ -439,6 +689,16 @@ dependencies = [ "tracing", ] +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + [[package]] name = "handlebars" version = "6.1.0" @@ -453,6 +713,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "hashbrown" version = "0.15.0" @@ -471,6 +737,15 @@ version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + [[package]] name = "http" version = "1.1.0" @@ -488,7 +763,7 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd2e17aacf7f4a2428def798e2ff4f4f883c0987bdaf47dd5c8bc027bc9f1ebc" dependencies = [ - "base64", + "base64 0.13.0", ] [[package]] @@ -636,6 +911,16 @@ dependencies = [ "cc", ] +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + [[package]] name = "indexmap" version = "2.6.0" @@ -643,15 +928,36 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.15.0", ] +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + +[[package]] +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.1" @@ -673,6 +979,193 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leptos" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cbb3237c274dadf00dcc27db96c52601b40375117178fb24a991cda073624f0" +dependencies = [ + "cfg-if", + "leptos_config", + "leptos_dom", + "leptos_macro", + "leptos_reactive", + "leptos_server", + "server_fn", + "tracing", + "typed-builder", + "typed-builder-macro", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_config" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62ed778611380ddea47568ac6ad6ec5158d39b5bd59e6c4dcd24efc15dc3dc0d" +dependencies = [ + "config", + "regex", + "serde", + "thiserror", + "typed-builder", +] + +[[package]] +name = "leptos_dom" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8401c46c86c1f4c16dcb7881ed319fcdca9cda9b9e78a6088955cb423afcf119" +dependencies = [ + "async-recursion", + "cfg-if", + "drain_filter_polyfill", + "futures", + "getrandom", + "html-escape", + "indexmap", + "itertools", + "js-sys", + "leptos_reactive", + "once_cell", + "pad-adapter", + "paste", + "rustc-hash", + "serde", + "serde_json", + "server_fn", + "smallvec", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_hot_reload" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb53d4794240b684a2f4be224b84bee9e62d2abc498cf2bcd643cd565e01d96" +dependencies = [ + "anyhow", + "camino", + "indexmap", + "parking_lot", + "proc-macro2", + "quote", + "rstml", + "serde", + "syn", + "walkdir", +] + +[[package]] +name = "leptos_macro" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b13bc3db70715cd8218c4535a5af3ae3c0e5fea6f018531fc339377b36bc0e0" +dependencies = [ + "attribute-derive", + "cfg-if", + "convert_case", + "html-escape", + "itertools", + "leptos_hot_reload", + "prettyplease", + "proc-macro-error2", + "proc-macro2", + "quote", + "rstml", + "server_fn_macro", + "syn", + "tracing", + "uuid", +] + +[[package]] +name = "leptos_meta" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25acc2f63cf91932013e400a95bf6e35e5d3dbb44a7b7e25a8e3057d12005b3b" +dependencies = [ + "cfg-if", + "indexmap", + "leptos", + "tracing", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_reactive" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4161acbf80f59219d8d14182371f57302bc7ff81ee41aba8ba1ff7295727f23" +dependencies = [ + "base64 0.22.1", + "cfg-if", + "futures", + "indexmap", + "js-sys", + "oco_ref", + "paste", + "pin-project", + "rustc-hash", + "self_cell", + "serde", + "serde-wasm-bindgen", + "serde_json", + "slotmap", + "thiserror", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_router" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d71dea7d42c0d29c40842750232d3425ed1cf10e313a1f898076d20871dad32" +dependencies = [ + "cfg-if", + "gloo-net", + "itertools", + "js-sys", + "lazy_static", + "leptos", + "linear-map", + "once_cell", + "percent-encoding", + "send_wrapper", + "serde", + "serde_json", + "serde_qs 0.13.0", + "thiserror", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_server" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a97eb90a13f71500b831c7119ddd3bdd0d7ae0a6b0487cade4fddeed3b8c03f" +dependencies = [ + "inventory", + "lazy_static", + "leptos_macro", + "leptos_reactive", + "serde", + "server_fn", + "thiserror", + "tracing", +] + [[package]] name = "libc" version = "0.2.158" @@ -695,6 +1188,16 @@ version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +[[package]] +name = "linear-map" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfae20f6b19ad527b550c223fddc3077a547fc70cda94b9b566575423fd303ee" +dependencies = [ + "serde", + "serde_test", +] + [[package]] name = "lock_api" version = "0.4.12" @@ -711,6 +1214,29 @@ version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +[[package]] +name = "manyhow" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91ea592d76c0b6471965708ccff7e6a5d277f676b90ab31f4d3f3fc77fade64" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "manyhow-macros" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64621e2c08f2576e4194ea8be11daf24ac01249a4f53cd8befcbb7077120ead" +dependencies = [ + "proc-macro-utils 0.8.0", + "proc-macro2", + "quote", +] + [[package]] name = "matchers" version = "0.1.0" @@ -742,6 +1268,22 @@ dependencies = [ "unicase", ] +[[package]] +name = "minicov" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71e683cd655513b99affab7d317deb690528255a0d5f717f1024093c12b169" +dependencies = [ + "cc", + "walkdir", +] + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.5.1" @@ -763,6 +1305,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -791,6 +1343,16 @@ dependencies = [ "memchr", ] +[[package]] +name = "oco_ref" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708" +dependencies = [ + "serde", + "thiserror", +] + [[package]] name = "once_cell" version = "1.18.0" @@ -803,6 +1365,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" +[[package]] +name = "pad-adapter" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" + [[package]] name = "parking_lot" version = "0.12.3" @@ -826,6 +1394,18 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d61c5ce1153ab5b689d0c074c4e7fc613e942dfb7dd9eea5ab202d2ad91fe361" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -877,6 +1457,26 @@ dependencies = [ "sha2", ] +[[package]] +name = "pin-project" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf123a161dde1e524adf36f90bc5d8d3462824a9c43553ad07a8183161189ec" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -884,10 +1484,86 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] -name = "pin-utils" -version = "0.1.0" +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "prettyplease" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "910d41a655dac3b764f1ade94821093d3610248694320cd072303a8eedcf221d" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + +[[package]] +name = "proc-macro-utils" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] [[package]] name = "proc-macro2" @@ -898,6 +1574,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "version_check", + "yansi", +] + [[package]] name = "quote" version = "1.0.37" @@ -907,6 +1596,28 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "quote-use" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9619db1197b497a36178cfc736dc96b271fe918875fbf1344c436a7e93d0321e" +dependencies = [ + "quote", + "quote-use-macros", +] + +[[package]] +name = "quote-use-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35" +dependencies = [ + "proc-macro-utils 0.10.0", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "redox_syscall" version = "0.5.7" @@ -960,12 +1671,32 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "rstml" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe542870b8f59dd45ad11d382e5339c9a1047cde059be136a7016095bbdefa77" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn", + "syn_derive", + "thiserror", +] + [[package]] name = "rustc-demangle" version = "0.1.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.4.1" @@ -981,18 +1712,48 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "self_cell" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" + [[package]] name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] + [[package]] name = "serde" version = "1.0.210" @@ -1002,6 +1763,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" version = "1.0.210" @@ -1025,6 +1797,28 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_qs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + +[[package]] +name = "serde_qs" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd34f36fe4c5ba9654417139a9b3a20d2e1de6012ee678ad14d240c22c78d8d6" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + [[package]] name = "serde_spanned" version = "0.6.8" @@ -1034,6 +1828,68 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_test" +version = "1.0.177" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f901ee573cab6b3060453d2d5f0bae4e6d628c23c0a962ff9b5f1d7c8d4f1ed" +dependencies = [ + "serde", +] + +[[package]] +name = "server_fn" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fae7a3038a32e5a34ba32c6c45eb4852f8affaf8b794ebfcd4b1099e2d62ebe" +dependencies = [ + "bytes", + "ciborium", + "const_format", + "dashmap", + "futures", + "gloo-net", + "http", + "js-sys", + "once_cell", + "send_wrapper", + "serde", + "serde_json", + "serde_qs 0.12.0", + "server_fn_macro_default", + "thiserror", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faaaf648c6967aef78177c0610478abb5a3455811f401f3c62d10ae9bd3901a1" +dependencies = [ + "const_format", + "convert_case", + "proc-macro2", + "quote", + "syn", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f2aa8119b558a17992e0ac1fd07f080099564f24532858811ce04f742542440" +dependencies = [ + "server_fn_macro", + "syn", +] + [[package]] name = "sha2" version = "0.10.8" @@ -1075,6 +1931,16 @@ version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "serde", + "version_check", +] + [[package]] name = "smallvec" version = "1.13.2" @@ -1099,15 +1965,27 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.79" +version = "2.0.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +checksum = "83540f837a8afc019423a8edb95b52a8effe46957ee402287f4292fae35be021" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "sync_wrapper" version = "0.1.2" @@ -1144,6 +2022,21 @@ dependencies = [ "once_cell", ] +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + [[package]] name = "tokio" version = "1.40.0" @@ -1327,6 +2220,26 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642" +[[package]] +name = "typed-builder" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "typenum" version = "1.17.0" @@ -1348,18 +2261,71 @@ dependencies = [ "version_check", ] +[[package]] +name = "unicode-bidi" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ab17db44d7388991a428b2ee655ce0c212e862eff1768a455c58f9aad6e7893" + [[package]] name = "unicode-ident" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" +[[package]] +name = "unicode-normalization" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8c5f0a0af699448548ad1a2fbf920fb4bee257eae39953ba95cb84891a0446a" +dependencies = [ + "getrandom", +] + [[package]] name = "valuable" version = "0.1.0" @@ -1372,6 +2338,16 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + [[package]] name = "want" version = "0.3.0" @@ -1414,6 +2390,18 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7ec4f8827a71586374db3e87abdb5a2bb3a15afed140221307c3ec06b1f63b" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.95" @@ -1443,6 +2431,55 @@ version = "0.2.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" +[[package]] +name = "wasm-bindgen-test" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d381749acb0943d357dcbd8f0b100640679883fcdeeef04def49daf8d33a5426" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "minicov", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "wasm-streams" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -1459,6 +2496,15 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys", +] + [[package]] name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" @@ -1555,3 +2601,15 @@ checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" dependencies = [ "memchr", ] + +[[package]] +name = "xxhash-rust" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" diff --git a/Cargo.toml b/Cargo.toml index c11aaff2..8e66690f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "crates/file-explorer", + "crates/file-explorer-ui", "crates/http-server", "crates/http-server-plugin", ] @@ -23,6 +24,10 @@ http-auth-basic = "0.3.3" http-body-util = "0.1" hyper = "1.4" hyper-util = "0.1.9" +leptos = "0.6" +leptos_meta = "0.6" +leptos_router = "0.6" +leptos-use = "0.10" libloading = "0.8.5" mime_guess = "2.0.5" percent-encoding = "2.3.1" diff --git a/Justfile b/Justfile index 25f3c4f0..60b2e1bb 100644 --- a/Justfile +++ b/Justfile @@ -9,3 +9,15 @@ release: # Runs Bats E2E Tests e2e: release BIN=./target/release/http-server bats tests/e2e + +# Runs formatting tool against Leptos source +ui-fmt: + leptosfmt ./crates/web/src/**/*.rs + +# Runs File Explorer UI for Development +ui-dev: + cd ./crates/file-explorer-ui && trunk serve --config ./Trunk.toml + +# Builds File Explorer UI for Production +ui-build: + cd ./crates/file-explorer-ui && trunk build --release --locked --config ./Trunk.toml diff --git a/README.md b/README.md index 6ec60412..9e86c379 100644 --- a/README.md +++ b/README.md @@ -34,314 +34,6 @@ Verify successful installation. http-server --help ``` -Expect the following output: - -``` -USAGE: - http-server [FLAGS] [OPTIONS] [root-dir] - -FLAGS: - --cors Enable Cross-Origin Resource Sharing allowing any origin - --graceful-shutdown Waits for all requests to fulfill before shutting down the server - --gzip Enable GZip compression for HTTP Responses - --help Prints help information - -l, --logger Prints HTTP request and response details to stdout - -q, --quiet Turns off stdout/stderr logging - --spa Route non-existent files to /index.html - --tls Enables HTTPS serving using TLS - -i, --index Route directories to index.html if present - -V, --version Prints version information - -OPTIONS: - -c, --config Path to TOML configuration file - -h, --host Host (IP) to bind the server [default: 127.0.0.1] - --password Specifies password for basic authentication - -p, --port Port to bind the server [default: 7878] - --proxy Proxy requests to the provided URL - --tls-cert Path to the TLS Certificate [default: cert.pem] - --tls-key Path to the TLS Key [default: key.rsa] - --tls-key-algorithm Algorithm used to generate certificate key [default: rsa] - --username Specifies username for basic authentication - -ARGS: - Directory to serve files from [default: ./] -``` - -> If you find this output is out of date, don't hesitate to open a [PR here][1]. - -## Configuration - -When running the server with no options or flags provided, a set of default -configurations will be used. You can always change this behavior by either -creating your own config with the [Configuration TOML](https://github.com/http-server-rs/http-server/blob/main/fixtures/config.toml) file -or by providing CLI arguments described in the [usage](#usage) section. - -| Name | Description | Default | -| -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| Host | Address to bind the server | `127.0.0.1` | -| Port | Port to bind the server | `7878` | -| Root Directory | The directory to serve files from | `CWD` | -| File Explorer UI | A File Explorer UI for the directory configured as the _Root Directory_ | Enabled | -| Configuration File | Specifies a configuration file. [Example](https://github.com/http-server-rs/http-server/blob/main/fixtures/config.toml) | Disabled | -| HTTPS (TLS) | HTTPS Secure connection configuration. Refer to [TLS (HTTPS)](https://github.com/http-server-rs/http-server#tls-https) reference | Disabled | -| CORS | Cross-Origin-Resource-Sharing headers support. Refer to [CORS](https://github.com/http-server-rs/http-server#cross-origin-resource-sharing-cors) reference | Disabled | -| Compression | GZip compression for HTTP Response Bodies. Refer to [Compression](https://github.com/http-server-rs/http-server#compression) reference | Disabled | -| Quiet | Don't print server details when running. This doesn't include any logging capabilities. | Disabled | -| Index | Route directories to index.html if present | Disabled | -| SPA | Route non-existent files to /index.html | Disabled | -| Basic Authentication | Authorize requests using Basic Authentication. Refer to [Basic Authentication](https://github.com/http-server-rs/http-server#basic-authentication) | Disabled | -| Logger | Prints HTTP request and response details to stdout | Disabled | - -## Usage - -``` -http-server [FLAGS] [OPTIONS] [root-dir] -``` - -### Flags - -Flags are provided without any values. For example: - -``` -http-server --help -``` - -| Name | Short | Long | Description | -| ----------------------------- | ----- | --------------------- | --------------------------------------------------------------------- | -| Cross-Origin Resource Sharing | N/A | `--cors` | Enable Cross-Origin Resource Sharing allowing any origin | -| GZip Compression | N/A | `--gzip` | Enable GZip compression for responses | -| Graceful Shutdown | N/A | `--graceful-shutdown` | Wait for all requests to be fulfilled before shutting down the server | -| Help | N/A | `--help` | Print help information | -| Logger | `-l` | `--logger` | Print HTTP request and response details to stdout | -| Version | `-V` | `--version` | Print version information | -| Quiet | `-q` | `--quiet` | Don't print output to console | -| Index | `-i` | `--index` | Route directories to index.html if present | -| SPA | N/A | `--spa` | Route non-existent files to /index.html | - -### Options - -Options receive a value and support default values as well. - -``` -http-server --host 127.0.0.1 -``` - -| Name | Short | Long | Description | Default Value | -| ------------------ | ----- | --------------------- | ----------------------------------------------------------------------------------------------------------- | ------------- | -| Host | `-h` | `--host` | Address to bind the server | `127.0.0.1` | -| Port | `-p` | `--port` | Port to bind the server | `7878` | -| Configuration File | `-c` | `--config` | Configuration file. [Example](https://github.com/http-server-rs/http-server/blob/main/fixtures/config.toml) | N/A | -| TLS | N/A | `--tls` | Enable TLS for HTTPS connections. Requires a Certificate and Key. [Reference](#tls-reference) | N/A | -| TLS Certificate | N/A | `--tls-cert` | Path to TLS certificate file. **Depends on `--tls`** | `cert.pem` | -| TLS Key | N/A | `--tls-key` | Path to TLS key file. **Depends on `--tls`** | `key.rsa` | -| TLS Key Algorithm | N/A | `--tls-key-algorithm` | Algorithm used to generate certificate key. **Depends on `--tls`** | `rsa` | -| Username | N/A | `--username` | Username to validate using basic authentication | N/A | -| Password | N/A | `--password` | Password to validate using basic authentication. **Depends on `--username`** | N/A | -| Proxy | N/A | `--proxy` | Proxy requests to the provided URL | N/A | - -## Request Handlers - -This HTTP Proxy supports different _Request Handlers_ which determine how each -incoming HTTP request is handled. They can't be combined, you must -choose one based on your needs. - -- [File Server](#file-server-handler) _default_ -- [Proxy](#proxy-handler) - -### File Server Handler - -Serves files from the provided directory. Navigation is scoped to the -specified directory. If no directory is provided the CWD will be used. - -> This is the default behavior for the HTTP server. - -### Proxy Handler - -Proxies requests to the provided URL. The URL provided is used as the base URL -for incoming requests. - -## Reference - -The following are some relevant details on features supported by this HTTP Server -that may be of interest to the user. - -### Compression - -Even though compression is supported, by default the server will not compress any -HTTP response contents. -You must specify the compression configuration you want to use, in the -configuration file or on the command line. - -As of today the server only supports compression with the GZip algorithm, but -`brotli` support is also planned. - -The following MIME types are never compressed: - -- `application/gzip` -- `application/octet-stream` -- `application/wasm` -- `application/zip` -- `image/*` -- `video/*` - -#### The Configuration File's Compression Section - -As future support for other compression algorithms is planned, -the configuration file already supports compression settings. - -```toml -[compression] -gzip = true -``` - -#### The `--gzip` flag - -Provide the `--gzip` argument to the server when executing it. - -```bash -http-server --gzip -``` - -### TLS (HTTPS) - -The TLS solution supported for this HTTP Server is built with the [rustls](https://github.com/ctz/rustls) -crate along with [hyper-rustls](https://github.com/ctz/hyper-rustls). - -When running with TLS support you will need: - -- A certificate -- A matching RSA Private Key for the certificate - -A script to generate certificates and keys is available here [tls-cert.sh](./docs/tls-cert.sh). -This script relies on `openssl`, so make sure you have it installed on your system. - -Run `http-server` as follows: - -```sh -http-server --tls --tls-cert --tls-key --tls-key-algorithm pkcs8 -``` - -### Cross-Origin Resource Sharing (CORS) - -This HTTP Server supports CORS headers _out of the box_. -Based on the headers you want to provide in your HTTP Responses, two -different methods for CORS configuration are available. - -By providing the `--cors` option to `http-server`, CORS headers -will be appended to every HTTP Response, allowing any origin. - -For more complex configurations, like specifying an origin, a set of allowed -HTTP methods and more, you should specify the configuration via the configuration -TOML file. - -The following example shows all the available options. - -```toml -[cors] -allow_credentials = false -allow_headers = ["content-type", "authorization", "content-length"] -allow_methods = ["GET", "PATCH", "POST", "PUT", "DELETE"] -allow_origin = "example.com" -expose_headers = ["*", "authorization"] -max_age = 600 -request_headers = ["x-app-version"] -request_method = "GET" -``` - -### Basic Authentication - -Basic Authentication is supported to deny requests when credentials are invalid. -You must provide the allowed `username` and `password` either by using the CLI -options `--username` along with the desired username and `--password` along with -the desired password, or by specifying such values through the configuration -TOML file. - -```toml -[basic_auth] -username = "John" -password = "Appleseed" -``` - -### Proxy - -The HTTP Server is able to proxy requests to a specified URL. - -When using the proxy, the FileExplorer won't be available, as the proxy is -an alternate _Request Handler_. - -The config TOML file can be used to provide proxy configurations: - -```toml -[proxy] -url = "https://example.com" -``` - -## Roadmap - -The following roadmap list features to provide for the version `v1.0.0`. - -This roadmap is still open for suggestions. If you find that there's a missing -feature in this list, that you would like to work on or expect for the first -stable release, please contact the software editors by opening an issue or a -discussion. - -If you want to contribute to one of these, please make sure -there's an issue tracking the feature and ping me. Otherwise -open an issue to be assigned and track the progress there. - -- [x] Logging - - [x] Request/Response Logging - - [x] Service Config Logins -- [ ] File Explorer - - [x] Modified Date - - [x] File Size - - [ ] Breadcrumb Navigation - - [ ] File Upload - - [ ] Filtering - - [ ] Sorting - - [ ] Sort By: File Name - - [ ] Sort By: File Size - - [ ] Sort By: File Modified Date - - [x] Directories First - - [ ] Files First -- [x] HTTPS/TLS Serving - - [x] HTTPS/TLS Support -- [ ] Compression - - [x] `gzip/deflate` Compression - - [ ] `brotli` Compression -- [ ] CORS - - [x] Cross Origin Resource Sharing - - [x] Allow Credentials - - [x] Allow Headers - - [x] Allow Methods - - [x] Allow Origin - - [x] Expose Headers - - [x] Max Age - - [x] Request Headers - - [x] Request Methods - - [ ] Multiple Origins (#8) -- [ ] Cache Control - - [ ] `Last-Modified` and `ETag` - - [ ] Respond with 304 to `If-Modified-Since` -- [ ] Partial Request - - [ ] `Accept-Ranges` - - [ ] `Content-Range` - - [ ] `If-Range` - - [ ] `If-Match` - - [ ] `Range` -- [x] Standalone Builds - - [x] macOS - - [x] Linux - - [x] Windows -- [ ] Development Server - - [ ] Live Reload -- [x] Proxy - - [x] URL Configuration -- [x] Basic Authentication - - [x] Username - - [x] Password -- [x] Graceful Shutdown - ## Release In order to create a release you must push a Git tag as follows diff --git a/crates/file-explorer-ui/Cargo.toml b/crates/file-explorer-ui/Cargo.toml new file mode 100644 index 00000000..9a8d51b2 --- /dev/null +++ b/crates/file-explorer-ui/Cargo.toml @@ -0,0 +1,26 @@ +[package] +name = "file-explorer-ui" +version = "0.0.0" +edition = "2021" +authors = ["Esteban Borai "] +publish = false +description = "File Explorer UI" + +[lib] +name = "file_explorer_ui" +path = "src/lib.rs" + +[[bin]] +name = "file-explorer-ui" +path = "src/bin/main.rs" + +[dependencies] +anyhow = { workspace = true } +leptos = { workspace = true, features = ["csr"] } +leptos_meta = { workspace = true, features = ["csr"] } +leptos_router = { workspace = true, features = ["csr"] } + +[dev-dependencies] +wasm-bindgen = "0.2" +wasm-bindgen-test = "0.3" +web-sys = "0.3" diff --git a/crates/file-explorer-ui/Trunk.toml b/crates/file-explorer-ui/Trunk.toml new file mode 100644 index 00000000..b9aa0102 --- /dev/null +++ b/crates/file-explorer-ui/Trunk.toml @@ -0,0 +1,9 @@ +[build] +# The index HTML file to drive the bundling process. +target = "./public/index.html" + +[watch] +# Paths to watch. The `build.target`'s parent folder is watched by default. +watch = ["./src"] +# Paths to ignore. +ignore = [] diff --git a/crates/file-explorer-ui/assets/.gitkeep b/crates/file-explorer-ui/assets/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/crates/file-explorer-ui/public/index.html b/crates/file-explorer-ui/public/index.html new file mode 100644 index 00000000..bf50d4b7 --- /dev/null +++ b/crates/file-explorer-ui/public/index.html @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/crates/file-explorer-ui/public/styles.css b/crates/file-explorer-ui/public/styles.css new file mode 100644 index 00000000..b5c61c95 --- /dev/null +++ b/crates/file-explorer-ui/public/styles.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/crates/file-explorer-ui/src/bin/main.rs b/crates/file-explorer-ui/src/bin/main.rs new file mode 100644 index 00000000..1c5d3504 --- /dev/null +++ b/crates/file-explorer-ui/src/bin/main.rs @@ -0,0 +1,9 @@ +use leptos::{mount_to_body, view}; + +use file_explorer_ui::App; + +fn main() { + mount_to_body(|| { + view! { } + }) +} diff --git a/crates/file-explorer-ui/src/lib.rs b/crates/file-explorer-ui/src/lib.rs new file mode 100644 index 00000000..015ce2a5 --- /dev/null +++ b/crates/file-explorer-ui/src/lib.rs @@ -0,0 +1,13 @@ +use leptos::{component, view, IntoView}; +use leptos_meta::provide_meta_context; + +#[component] +pub fn App() -> impl IntoView { + provide_meta_context(); + + view! { +
+

App

+
+ } +} diff --git a/crates/file-explorer-ui/tailwind.config.js b/crates/file-explorer-ui/tailwind.config.js new file mode 100644 index 00000000..3c1e6d82 --- /dev/null +++ b/crates/file-explorer-ui/tailwind.config.js @@ -0,0 +1,8 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./src/**/*.rs'], + theme: { + extend: {} + }, + plugins: [] +}; From 82b5193c939991343ea79c4065014c08ef4f9850 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Tue, 22 Oct 2024 21:40:04 -0300 Subject: [PATCH 27/40] feat: split api and files router --- crates/file-explorer/src/lib.rs | 86 ++++++++++++++++++--------------- 1 file changed, 47 insertions(+), 39 deletions(-) diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index c0cfe308..dabf79d8 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -6,7 +6,6 @@ use std::cmp::{Ord, Ordering}; use std::fs::read_dir; use std::mem::MaybeUninit; use std::path::{Component, Path, PathBuf}; -use std::str::FromStr; use std::sync::Arc; use anyhow::{Context, Result}; @@ -71,33 +70,8 @@ impl Function for FileExplorer { parts: Parts, body: Bytes, ) -> Result>, InvocationError> { - let path = Self::parse_req_uri(parts.uri).unwrap(); - - self.rt.block_on(async move { - match parts.method { - Method::GET => match self.fs.resolve(path).await { - Ok(entry) => match entry { - Entry::Directory(dir) => { - Ok(self.render_directory_index(dir.path()).await.unwrap()) - } - Entry::File(file) => Ok(Self::make_http_file_response(file).await.unwrap()), - }, - Err(err) => { - let message = format!("Failed to resolve path: {}", err); - Ok(Response::new(Full::new(Bytes::from(message)))) - } - }, - Method::POST => { - let filename = path.file_name().unwrap().to_str().unwrap(); - let mut file = tokio::fs::File::create(filename).await.unwrap(); - file.write_all(&body).await.unwrap(); - Ok(Response::new(Full::new(Bytes::from( - "POST method is not supported", - )))) - } - _ => Ok(Response::new(Full::new(Bytes::from("Unsupported method")))), - } - }) + self.rt + .block_on(async move { self.handle(parts, body).await }) } } @@ -114,21 +88,55 @@ impl FileExplorer { }) } - fn parse_req_uri(uri: Uri) -> Result { - let uri_parts = uri.into_parts(); + async fn handle( + &self, + parts: Parts, + body: Bytes, + ) -> Result>, InvocationError> { + if parts.uri.path().starts_with("/api/v1") { + self.handle_api(parts, body).await + } else { + Ok(Response::new(Full::new(Bytes::from("Unsupported method")))) + } + } - if let Some(path_and_query) = uri_parts.path_and_query { - let path = path_and_query.path(); - // let query_params = if let Some(query_str) = path_and_query.query() { - // Some(QueryParams::from_str(query_str)?) - // } else { - // None - // }; + async fn handle_api( + &self, + parts: Parts, + body: Bytes, + ) -> Result>, InvocationError> { + let path = Self::parse_req_uri(parts.uri).unwrap(); - return Ok(decode_uri(path)); + match parts.method { + Method::GET => match self.fs.resolve(path).await { + Ok(entry) => match entry { + Entry::Directory(dir) => { + Ok(self.render_directory_index(dir.path()).await.unwrap()) + } + Entry::File(file) => Ok(Self::make_http_file_response(file).await.unwrap()), + }, + Err(err) => { + let message = format!("Failed to resolve path: {}", err); + Ok(Response::new(Full::new(Bytes::from(message)))) + } + }, + Method::POST => { + let filename = path.file_name().unwrap().to_str().unwrap(); + let mut file = tokio::fs::File::create(filename).await.unwrap(); + file.write_all(&body).await.unwrap(); + Ok(Response::new(Full::new(Bytes::from( + "POST method is not supported", + )))) + } + _ => Ok(Response::new(Full::new(Bytes::from("Unsupported method")))), } + } + + fn parse_req_uri(uri: Uri) -> Result { + let parts: Vec<&str> = uri.path().split('/').collect(); + let path = &parts[3..].join("/"); - PathBuf::from_str("/").context("Failed to parse URI") + Ok(decode_uri(path)) } /// Encodes a `PathBuf` component using `PercentEncode` with UTF-8 charset. From d94c2de43da78dd9ae492e8f006802e050d1e693 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Thu, 24 Oct 2024 19:48:50 -0300 Subject: [PATCH 28/40] feat: serve ui files --- Cargo.lock | 510 ++++++++++++++++++++++++++++- Cargo.toml | 3 + crates/file-explorer-ui/Cargo.toml | 2 + crates/file-explorer-ui/src/lib.rs | 12 +- crates/file-explorer/Cargo.toml | 2 + crates/file-explorer/src/lib.rs | 18 + 6 files changed, 537 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 813b5eea..763335ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -77,7 +77,7 @@ version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -87,7 +87,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -187,6 +187,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.4.0" @@ -382,6 +388,16 @@ dependencies = [ "unicode-segmentation", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" @@ -459,12 +475,37 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "equivalent" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" + [[package]] name = "file-explorer" version = "0.0.0" @@ -473,6 +514,7 @@ dependencies = [ "async-trait", "bytes", "chrono", + "file-explorer-ui", "futures", "handlebars", "http", @@ -482,6 +524,7 @@ dependencies = [ "hyper", "mime_guess", "percent-encoding", + "rust-embed", "serde", "serde_json", "tokio", @@ -498,6 +541,8 @@ dependencies = [ "leptos", "leptos_meta", "leptos_router", + "reqwest", + "rust-embed", "wasm-bindgen", "wasm-bindgen-test", "web-sys", @@ -509,6 +554,21 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "form_urlencoded" version = "1.2.1" @@ -869,6 +929,39 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.9" @@ -943,6 +1036,12 @@ version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" +[[package]] +name = "ipnet" +version = "2.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" + [[package]] name = "is_terminal_polyfill" version = "1.70.1" @@ -1198,6 +1297,12 @@ dependencies = [ "serde_test", ] +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + [[package]] name = "lock_api" version = "0.4.12" @@ -1302,7 +1407,24 @@ dependencies = [ "hermit-abi", "libc", "wasi", - "windows-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -1359,6 +1481,50 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" +[[package]] +name = "openssl" +version = "0.10.68" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" +dependencies = [ + "bitflags 2.4.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "overload" version = "0.1.1" @@ -1489,6 +1655,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + [[package]] name = "prettyplease" version = "0.2.24" @@ -1624,7 +1796,7 @@ version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6dfecf2c74bce2466cabf93f6664d6998a69eb21e39f4207930065b27b771f" dependencies = [ - "bitflags", + "bitflags 2.4.0", ] [[package]] @@ -1671,6 +1843,64 @@ version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" +[[package]] +name = "reqwest" +version = "0.12.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper 1.0.1", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "spin", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rstml" version = "0.11.2" @@ -1685,6 +1915,40 @@ dependencies = [ "thiserror", ] +[[package]] +name = "rust-embed" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa66af4a4fdd5e7ebc276f115e895611a34739a9c1c01028383d612d550953c0" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6125dbc8867951125eec87294137f4e9c2c96566e61bf72c45095a7c77761478" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5347777e9aacb56039b0e1f28785929a8a3b709e87482e7442c72e7c12529d" +dependencies = [ + "sha2", + "walkdir", +] + [[package]] name = "rustc-demangle" version = "0.1.21" @@ -1706,6 +1970,58 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" +dependencies = [ + "bitflags 2.4.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f1201b3c9a7ee8039bcadc17b7e605e2945b27eee7631788c1bd2b0643674b" + +[[package]] +name = "rustls-webpki" +version = "0.102.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "ryu" version = "1.0.18" @@ -1721,6 +2037,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01227be5826fa0690321a2ba6c5cd57a19cf3f6a09e76973b58e61de6ab9d1c1" +dependencies = [ + "windows-sys 0.59.0", +] + [[package]] name = "scoped-tls" version = "1.0.1" @@ -1733,6 +2058,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "770452e37cad93e0a50d5abc3990d2bc351c36d0328f86cefec2f2fb206eaef6" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "self_cell" version = "1.0.4" @@ -1837,6 +2185,18 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "server_fn" version = "0.6.15" @@ -1954,15 +2314,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys", + "windows-sys 0.52.0", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.82" @@ -1992,6 +2364,48 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.4.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "thiserror" version = "1.0.55" @@ -2052,7 +2466,7 @@ dependencies = [ "signal-hook-registry", "socket2", "tokio-macros", - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -2066,6 +2480,27 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "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-util" version = "0.7.12" @@ -2122,7 +2557,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper", + "sync_wrapper 0.1.2", "tower-layer", "tower-service", ] @@ -2133,7 +2568,7 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" dependencies = [ - "bitflags", + "bitflags 2.4.0", "bytes", "http", "pin-project-lite", @@ -2294,6 +2729,12 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.2" @@ -2332,6 +2773,12 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version_check" version = "0.9.5" @@ -2502,7 +2949,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.52.0", ] [[package]] @@ -2520,6 +2967,36 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.52.0" @@ -2529,6 +3006,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -2613,3 +3099,9 @@ name = "yansi" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/Cargo.toml b/Cargo.toml index 8e66690f..a6bf04a6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,8 @@ leptos-use = "0.10" libloading = "0.8.5" mime_guess = "2.0.5" percent-encoding = "2.3.1" +reqwest = "0.12.8" +rust-embed = "8.5.0" rustc_version = "0.4.1" serde = "1.0.210" serde_json = "1.0.128" @@ -43,4 +45,5 @@ tracing = "0.1.40" tracing-subscriber = "0.3.18" # Workspace Crates +file-explorer-ui = { path = "crates/file-explorer-ui" } http-server-plugin = { path = "crates/http-server-plugin" } diff --git a/crates/file-explorer-ui/Cargo.toml b/crates/file-explorer-ui/Cargo.toml index 9a8d51b2..08ba682e 100644 --- a/crates/file-explorer-ui/Cargo.toml +++ b/crates/file-explorer-ui/Cargo.toml @@ -19,6 +19,8 @@ anyhow = { workspace = true } leptos = { workspace = true, features = ["csr"] } leptos_meta = { workspace = true, features = ["csr"] } leptos_router = { workspace = true, features = ["csr"] } +reqwest = { workspace = true } +rust-embed = { workspace = true } [dev-dependencies] wasm-bindgen = "0.2" diff --git a/crates/file-explorer-ui/src/lib.rs b/crates/file-explorer-ui/src/lib.rs index 015ce2a5..b7c67a92 100644 --- a/crates/file-explorer-ui/src/lib.rs +++ b/crates/file-explorer-ui/src/lib.rs @@ -1,13 +1,23 @@ -use leptos::{component, view, IntoView}; +use leptos::{component, spawn_local, view, IntoView}; use leptos_meta::provide_meta_context; +use rust_embed::Embed; #[component] pub fn App() -> impl IntoView { provide_meta_context(); + spawn_local(async move { + leptos::logging::warn!("Performing a request to the server"); + reqwest::get("http://localhost:3000/api/v1").await.unwrap(); + }); + view! {

App

} } + +#[derive(Embed)] +#[folder = "public/dist"] +pub struct Assets; diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index 417b2d78..2a741091 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -20,6 +20,7 @@ http-body-util = { workspace = true } humansize = { workspace = true } hyper = { workspace = true } mime_guess = { workspace = true } +rust-embed = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } percent-encoding = { workspace = true } @@ -29,3 +30,4 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } http-server-plugin = { workspace = true } +file-explorer-ui = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index dabf79d8..fc4d701f 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -13,6 +13,7 @@ use async_trait::async_trait; use chrono::{DateTime, Local}; use fs::Entry; use http::request::Parts; +use http::HeaderValue; use http_body_util::Full; use hyper::body::Bytes; use hyper::header::{CONTENT_TYPE, ETAG, LAST_MODIFIED}; @@ -22,6 +23,7 @@ use serde::{Deserialize, Serialize}; use tokio::io::AsyncWriteExt; use tokio::runtime::Handle; +use file_explorer_ui::Assets; use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; @@ -96,6 +98,22 @@ impl FileExplorer { if parts.uri.path().starts_with("/api/v1") { self.handle_api(parts, body).await } else { + let path = parts.uri.path(); + let path = path.strip_prefix('/').unwrap_or(path); + + if let Some(file) = Assets::get(path) { + let content_type = mime_guess::from_path(path).first_or_octet_stream(); + let content_type = HeaderValue::from_str(content_type.as_ref()).unwrap(); + let body = Full::new(Bytes::from(file.data.to_vec())); + let mut response = Response::new(body); + let mut headers = response.headers().clone(); + + headers.append(CONTENT_TYPE, content_type); + *response.headers_mut() = headers; + + return Ok(response); + } + Ok(Response::new(Full::new(Bytes::from("Unsupported method")))) } } From e33494e9a63f1561cfcea208d2073f60aa84659b Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 26 Oct 2024 17:48:10 -0300 Subject: [PATCH 29/40] feat: file explorer proto --- Cargo.lock | 9 +++ Cargo.toml | 2 + crates/file-explorer-proto/Cargo.toml | 10 +++ crates/file-explorer-proto/src/lib.rs | 73 ++++++++++++++++++++ crates/file-explorer/Cargo.toml | 1 + crates/file-explorer/src/lib.rs | 95 ++++++--------------------- crates/http-server/src/server/mod.rs | 2 + 7 files changed, 117 insertions(+), 75 deletions(-) create mode 100644 crates/file-explorer-proto/Cargo.toml create mode 100644 crates/file-explorer-proto/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 763335ae..6680f88c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -514,6 +514,7 @@ dependencies = [ "async-trait", "bytes", "chrono", + "file-explorer-proto", "file-explorer-ui", "futures", "handlebars", @@ -533,6 +534,14 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "file-explorer-proto" +version = "0.0.0" +dependencies = [ + "chrono", + "serde", +] + [[package]] name = "file-explorer-ui" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index a6bf04a6..2a641fce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "crates/file-explorer", + "crates/file-explorer-proto", "crates/file-explorer-ui", "crates/http-server", "crates/http-server-plugin", @@ -45,5 +46,6 @@ tracing = "0.1.40" tracing-subscriber = "0.3.18" # Workspace Crates +file-explorer-proto = { path = "crates/file-explorer-proto" } file-explorer-ui = { path = "crates/file-explorer-ui" } http-server-plugin = { path = "crates/http-server-plugin" } diff --git a/crates/file-explorer-proto/Cargo.toml b/crates/file-explorer-proto/Cargo.toml new file mode 100644 index 00000000..35d5a72f --- /dev/null +++ b/crates/file-explorer-proto/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "file-explorer-proto" +version = "0.0.0" +authors = ["Esteban Borai "] +edition = "2021" +publish = false + +[dependencies] +chrono = { workspace = true, features = ["serde"] } +serde = { workspace = true, features = ["derive"] } diff --git a/crates/file-explorer-proto/src/lib.rs b/crates/file-explorer-proto/src/lib.rs new file mode 100644 index 00000000..73b0b8b7 --- /dev/null +++ b/crates/file-explorer-proto/src/lib.rs @@ -0,0 +1,73 @@ +use std::cmp::Ordering; + +use chrono::{DateTime, Local}; +use serde::{Deserialize, Serialize}; + +/// A Directory entry used to display a File Explorer's entry. +/// This struct is directly related to the Handlebars template used +/// to power the File Explorer's UI +#[derive(Debug, Eq, Serialize)] +pub struct DirectoryEntry { + pub display_name: String, + pub is_dir: bool, + pub size_bytes: u64, + pub entry_path: String, + pub date_created: Option>, + pub date_modified: Option>, +} + +impl Ord for DirectoryEntry { + fn cmp(&self, other: &Self) -> Ordering { + if self.is_dir && other.is_dir { + return self.display_name.cmp(&other.display_name); + } + + if self.is_dir && !other.is_dir { + return Ordering::Less; + } + + Ordering::Greater + } +} + +impl PartialOrd for DirectoryEntry { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for DirectoryEntry { + fn eq(&self, other: &Self) -> bool { + if self.is_dir && other.is_dir { + return self.display_name == other.display_name; + } + + self.display_name == other.display_name + } +} + +/// A Breadcrumb Item used to navigate to previous path components +#[derive(Clone, Debug, Serialize, PartialEq, Eq)] +pub struct BreadcrumbItem { + pub entry_name: String, + pub entry_link: String, +} + +/// The value passed to the Handlebars template engine. +/// All references contained in File Explorer's UI are provided +/// via the `DirectoryIndex` struct +#[derive(Debug, Serialize)] +pub struct DirectoryIndex { + pub entries: Vec, + pub breadcrumbs: Vec, + pub sort: Sort, +} + +#[derive(Serialize, Debug, PartialEq, Deserialize)] +pub enum Sort { + Directory, + Name, + Size, + DateCreated, + DateModified, +} diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index 2a741091..e23df8fa 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -30,4 +30,5 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } http-server-plugin = { workspace = true } +file-explorer-proto = { workspace = true } file-explorer-ui = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index fc4d701f..832314e9 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -2,7 +2,6 @@ mod fs; mod templater; mod utils; -use std::cmp::{Ord, Ordering}; use std::fs::read_dir; use std::mem::MaybeUninit; use std::path::{Component, Path, PathBuf}; @@ -10,8 +9,8 @@ use std::sync::Arc; use anyhow::{Context, Result}; use async_trait::async_trait; -use chrono::{DateTime, Local}; use fs::Entry; +use http::header::LOCATION; use http::request::Parts; use http::HeaderValue; use http_body_util::Full; @@ -19,10 +18,11 @@ use hyper::body::Bytes; use hyper::header::{CONTENT_TYPE, ETAG, LAST_MODIFIED}; use hyper::{Method, Response, StatusCode, Uri}; use percent_encoding::{percent_decode_str, utf8_percent_encode}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; use tokio::io::AsyncWriteExt; use tokio::runtime::Handle; +use file_explorer_proto::{BreadcrumbItem, DirectoryEntry, DirectoryIndex, Sort}; use file_explorer_ui::Assets; use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; @@ -114,7 +114,14 @@ impl FileExplorer { return Ok(response); } - Ok(Response::new(Full::new(Bytes::from("Unsupported method")))) + let mut response = Response::new(Full::new(Bytes::default())); + let mut headers = response.headers().clone(); + + headers.append(LOCATION, "/index.html".try_into().unwrap()); + *response.headers_mut() = headers; + *response.status_mut() = StatusCode::TEMPORARY_REDIRECT; + + Ok(response) } } @@ -129,7 +136,11 @@ impl FileExplorer { Method::GET => match self.fs.resolve(path).await { Ok(entry) => match entry { Entry::Directory(dir) => { - Ok(self.render_directory_index(dir.path()).await.unwrap()) + let directory_index = + self.marshall_directory_index(dir.path()).await.unwrap(); + let json = serde_json::to_string(&directory_index).unwrap(); + + Ok(Response::new(Full::new(Bytes::from(json)))) } Entry::File(file) => Ok(Self::make_http_file_response(file).await.unwrap()), }, @@ -314,6 +325,10 @@ impl FileExplorer { .context("Failed to build response") } + async fn marshall_directory_index(&self, path: PathBuf) -> Result { + Self::index_directory(self.path.clone(), path) + } + pub async fn make_http_file_response(file: Box) -> Result>> { Response::builder() .header(CONTENT_TYPE, file.mime().to_string()) @@ -331,73 +346,3 @@ impl FileExplorer { .context("Failed to build HTTP File Response") } } - -/// A Directory entry used to display a File Explorer's entry. -/// This struct is directly related to the Handlebars template used -/// to power the File Explorer's UI -#[derive(Debug, Eq, Serialize)] -pub struct DirectoryEntry { - pub(crate) display_name: String, - pub(crate) is_dir: bool, - pub(crate) size_bytes: u64, - pub(crate) entry_path: String, - pub(crate) date_created: Option>, - pub(crate) date_modified: Option>, -} - -impl Ord for DirectoryEntry { - fn cmp(&self, other: &Self) -> Ordering { - if self.is_dir && other.is_dir { - return self.display_name.cmp(&other.display_name); - } - - if self.is_dir && !other.is_dir { - return Ordering::Less; - } - - Ordering::Greater - } -} - -impl PartialOrd for DirectoryEntry { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for DirectoryEntry { - fn eq(&self, other: &Self) -> bool { - if self.is_dir && other.is_dir { - return self.display_name == other.display_name; - } - - self.display_name == other.display_name - } -} - -/// A Breadcrumb Item used to navigate to previous path components -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] -pub struct BreadcrumbItem { - pub(crate) entry_name: String, - pub(crate) entry_link: String, -} - -/// The value passed to the Handlebars template engine. -/// All references contained in File Explorer's UI are provided -/// via the `DirectoryIndex` struct -#[derive(Debug, Serialize)] -pub struct DirectoryIndex { - /// Directory listing entry - pub(crate) entries: Vec, - pub(crate) breadcrumbs: Vec, - pub(crate) sort: Sort, -} - -#[derive(Serialize, Debug, PartialEq, Deserialize)] -pub enum Sort { - Directory, - Name, - Size, - DateCreated, - DateModified, -} diff --git a/crates/http-server/src/server/mod.rs b/crates/http-server/src/server/mod.rs index 0f274733..0518460e 100644 --- a/crates/http-server/src/server/mod.rs +++ b/crates/http-server/src/server/mod.rs @@ -36,6 +36,8 @@ impl Server { .expect("Function loading failed"); } + info!(%addr, "Server Listening"); + loop { let (stream, _) = listener.accept().await?; let io = TokioIo::new(stream); From 3f132a0adf4006982f89f2be4255508a65319505 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 26 Oct 2024 17:49:46 -0300 Subject: [PATCH 30/40] fix: remove handlebars --- Cargo.lock | 66 ----------------------- Cargo.toml | 1 - crates/file-explorer/Cargo.toml | 1 - crates/file-explorer/src/lib.rs | 19 ------- crates/file-explorer/src/templater/mod.rs | 53 ------------------ 5 files changed, 140 deletions(-) delete mode 100644 crates/file-explorer/src/templater/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 6680f88c..2246411f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -517,7 +517,6 @@ dependencies = [ "file-explorer-proto", "file-explorer-ui", "futures", - "handlebars", "http", "http-body-util", "http-server-plugin", @@ -768,20 +767,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "handlebars" -version = "6.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce25b617d1375ef96eeb920ae717e3da34a02fc979fe632c75128350f9e1f74a" -dependencies = [ - "log", - "pest", - "pest_derive", - "serde", - "serde_json", - "thiserror", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -1587,51 +1572,6 @@ version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" -[[package]] -name = "pest" -version = "2.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fdbef9d1d47087a895abd220ed25eb4ad973a5e26f6a4367b038c25e28dfc2d9" -dependencies = [ - "memchr", - "thiserror", - "ucd-trie", -] - -[[package]] -name = "pest_derive" -version = "2.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d3a6e3394ec80feb3b6393c725571754c6188490265c61aaf260810d6b95aa0" -dependencies = [ - "pest", - "pest_generator", -] - -[[package]] -name = "pest_generator" -version = "2.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94429506bde1ca69d1b5601962c73f4172ab4726571a59ea95931218cb0e930e" -dependencies = [ - "pest", - "pest_meta", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "pest_meta" -version = "2.7.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac8a071862e93690b6e34e9a5fb8e33ff3734473ac0245b27232222c4906a33f" -dependencies = [ - "once_cell", - "pest", - "sha2", -] - [[package]] name = "pin-project" version = "1.1.6" @@ -2690,12 +2630,6 @@ version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" -[[package]] -name = "ucd-trie" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" - [[package]] name = "unicase" version = "2.7.0" diff --git a/Cargo.toml b/Cargo.toml index 2a641fce..9d673761 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,6 @@ bytes = "1.7.1" chrono = "0.4.38" clap = "4.5.20" futures = "0.3.31" -handlebars = "6.1.0" humansize = "2.1.3" http = "1.1.0" http-auth-basic = "0.3.3" diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index e23df8fa..2ae5690c 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -14,7 +14,6 @@ async-trait = { workspace = true } bytes = { workspace = true } chrono = { workspace = true, features = ["serde"] } futures = { workspace = true } -handlebars = { workspace = true } http = { workspace = true } http-body-util = { workspace = true } humansize = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 832314e9..7684cc9d 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -1,5 +1,4 @@ mod fs; -mod templater; mod utils; use std::fs::read_dir; @@ -28,7 +27,6 @@ use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; use self::fs::{File, FileSystem}; -use self::templater::Templater; use self::utils::{decode_uri, encode_uri, PERCENT_ENCODE_SET}; const FILE_BUFFER_SIZE: usize = 8 * 1024; @@ -62,7 +60,6 @@ struct FileExplorer { rt: Arc, fs: FileSystem, path: PathBuf, - templater: Templater, } #[async_trait] @@ -80,13 +77,11 @@ impl Function for FileExplorer { impl FileExplorer { fn new(rt: Arc, path: PathBuf) -> Result { let fs = FileSystem::new(path.clone())?; - let templater = Templater::new()?; Ok(Self { rt, fs, path, - templater, }) } @@ -311,20 +306,6 @@ impl FileExplorer { }) } - /// Indexes the directory by creating a `DirectoryIndex`. Such `DirectoryIndex` - /// is used to build the Handlebars "Explorer" template using the Handlebars - /// engine and builds an HTTP Response containing such file - async fn render_directory_index(&self, path: PathBuf) -> Result>> { - let directory_index = Self::index_directory(self.path.clone(), path)?; - let html = self.templater.render(&directory_index).unwrap(); - - Response::builder() - .header(CONTENT_TYPE, "text/html") - .status(StatusCode::OK) - .body(Full::new(Bytes::from(html))) - .context("Failed to build response") - } - async fn marshall_directory_index(&self, path: PathBuf) -> Result { Self::index_directory(self.path.clone(), path) } diff --git a/crates/file-explorer/src/templater/mod.rs b/crates/file-explorer/src/templater/mod.rs deleted file mode 100644 index 752b60c4..00000000 --- a/crates/file-explorer/src/templater/mod.rs +++ /dev/null @@ -1,53 +0,0 @@ -use anyhow::Result; -use chrono::{DateTime, Local}; -use handlebars::{handlebars_helper, Handlebars}; -use humansize::{format_size, DECIMAL}; - -use crate::{DirectoryIndex, Sort}; - -const EXPLORER_KEY: &str = "Explorer"; -const EXPLORER_TPL: &[u8] = include_bytes!("./hbs/explorer.hbs"); - -pub struct Templater { - pub backend: Handlebars<'static>, -} - -impl Templater { - pub fn new() -> Result { - let mut hbs = Handlebars::new(); - - let explorer_tpl = String::from_utf8_lossy(EXPLORER_TPL); - - hbs.register_template_string(EXPLORER_KEY, explorer_tpl)?; - - handlebars_helper!(date: |d: Option>| { - match d { - Some(d) => d.format("%Y/%m/%d %H:%M:%S").to_string(), - None => "-".to_owned(), - } - }); - hbs.register_helper("date", Box::new(date)); - - handlebars_helper!(size: |bytes: u64| format_size(bytes, DECIMAL)); - hbs.register_helper("size", Box::new(size)); - - handlebars_helper!(sort_name: |sort: Sort| sort == Sort::Name); - hbs.register_helper("sort_name", Box::new(sort_name)); - - handlebars_helper!(sort_size: |sort: Sort| sort == Sort::Size); - hbs.register_helper("sort_size", Box::new(sort_size)); - - handlebars_helper!(sort_date_created: |sort: Sort| sort == Sort::DateCreated); - hbs.register_helper("sort_date_created", Box::new(sort_date_created)); - - handlebars_helper!(sort_date_modified: |sort: Sort| sort == Sort::DateModified); - hbs.register_helper("sort_date_modified", Box::new(sort_date_modified)); - - Ok(Self { backend: hbs }) - } - - pub fn render(&self, di: &DirectoryIndex) -> Result { - let tpl = self.backend.render(EXPLORER_KEY, &di)?; - Ok(tpl) - } -} From 23ae136facd908e91f97120c3c21b02789b26f72 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 26 Oct 2024 18:16:13 -0300 Subject: [PATCH 31/40] feat: file explorer core --- Cargo.lock | 11 + Cargo.toml | 2 + crates/file-explorer-core/Cargo.toml | 12 + .../src/fs/directory.rs | 0 crates/file-explorer-core/src/fs/file.rs | 49 ++++ crates/file-explorer-core/src/fs/mod.rs | 24 ++ .../mod.rs => file-explorer-core/src/lib.rs} | 76 +++--- crates/file-explorer/Cargo.toml | 1 + crates/file-explorer/src/fs/file.rs | 108 -------- crates/file-explorer/src/lib.rs | 66 +++-- .../file-explorer/src/templater/hbs/error.hbs | 4 - .../src/templater/hbs/explorer.hbs | 249 ------------------ 12 files changed, 169 insertions(+), 433 deletions(-) create mode 100644 crates/file-explorer-core/Cargo.toml rename crates/{file-explorer => file-explorer-core}/src/fs/directory.rs (100%) create mode 100644 crates/file-explorer-core/src/fs/file.rs create mode 100644 crates/file-explorer-core/src/fs/mod.rs rename crates/{file-explorer/src/fs/mod.rs => file-explorer-core/src/lib.rs} (51%) delete mode 100644 crates/file-explorer/src/fs/file.rs delete mode 100644 crates/file-explorer/src/templater/hbs/error.hbs delete mode 100644 crates/file-explorer/src/templater/hbs/explorer.hbs diff --git a/Cargo.lock b/Cargo.lock index 2246411f..7af33273 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -514,6 +514,7 @@ dependencies = [ "async-trait", "bytes", "chrono", + "file-explorer-core", "file-explorer-proto", "file-explorer-ui", "futures", @@ -533,6 +534,16 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "file-explorer-core" +version = "0.0.0" +dependencies = [ + "anyhow", + "chrono", + "mime_guess", + "tokio", +] + [[package]] name = "file-explorer-proto" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index 9d673761..9e485601 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,7 @@ [workspace] members = [ "crates/file-explorer", + "crates/file-explorer-core", "crates/file-explorer-proto", "crates/file-explorer-ui", "crates/http-server", @@ -45,6 +46,7 @@ tracing = "0.1.40" tracing-subscriber = "0.3.18" # Workspace Crates +file-explorer-core = { path = "crates/file-explorer-core" } file-explorer-proto = { path = "crates/file-explorer-proto" } file-explorer-ui = { path = "crates/file-explorer-ui" } http-server-plugin = { path = "crates/http-server-plugin" } diff --git a/crates/file-explorer-core/Cargo.toml b/crates/file-explorer-core/Cargo.toml new file mode 100644 index 00000000..97f158bc --- /dev/null +++ b/crates/file-explorer-core/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "file-explorer-core" +version = "0.0.0" +authors = ["Esteban Borai "] +edition = "2021" +publish = false + +[dependencies] +anyhow = { workspace = true } +chrono = { workspace = true, features = ["serde"] } +mime_guess = { workspace = true } +tokio = { workspace = true, features = ["fs", "rt-multi-thread", "signal", "macros"] } diff --git a/crates/file-explorer/src/fs/directory.rs b/crates/file-explorer-core/src/fs/directory.rs similarity index 100% rename from crates/file-explorer/src/fs/directory.rs rename to crates/file-explorer-core/src/fs/directory.rs diff --git a/crates/file-explorer-core/src/fs/file.rs b/crates/file-explorer-core/src/fs/file.rs new file mode 100644 index 00000000..22c1cf22 --- /dev/null +++ b/crates/file-explorer-core/src/fs/file.rs @@ -0,0 +1,49 @@ +use anyhow::{Context, Result}; +use chrono::{DateTime, Local}; + +use std::fs::Metadata; +use std::mem::MaybeUninit; +use std::path::PathBuf; + +use mime_guess::{from_path, Mime}; + +pub const FILE_BUFFER_SIZE: usize = 8 * 1024; + +pub type FileBuffer = Box<[MaybeUninit; FILE_BUFFER_SIZE]>; + +/// Wrapper around `tokio::fs::File` built from a OS ScopedFileSystem file +/// providing `std::fs::Metadata` and the path to such file +#[derive(Debug)] +pub struct File { + pub path: PathBuf, + pub file: tokio::fs::File, + pub metadata: Metadata, +} + +impl File { + pub fn new(path: PathBuf, file: tokio::fs::File, metadata: Metadata) -> Self { + File { + path, + file, + metadata, + } + } + + pub fn mime(&self) -> Mime { + from_path(self.path.clone()).first_or_octet_stream() + } + + pub fn size(&self) -> u64 { + self.metadata.len() + } + + pub fn last_modified(&self) -> Result> { + let modified = self + .metadata + .modified() + .context("Failed to read last modified time for file")?; + let modified: DateTime = modified.into(); + + Ok(modified) + } +} diff --git a/crates/file-explorer-core/src/fs/mod.rs b/crates/file-explorer-core/src/fs/mod.rs new file mode 100644 index 00000000..63fc62c8 --- /dev/null +++ b/crates/file-explorer-core/src/fs/mod.rs @@ -0,0 +1,24 @@ +mod directory; +mod file; + +use std::path::PathBuf; + +use anyhow::Result; + +pub use self::directory::Directory; +pub use self::file::File; + +#[derive(Debug, Clone)] +pub struct FileSystem { + pub path: PathBuf, +} + +impl FileSystem { + /// Creates a new instance of `ScopedFileSystem` using the provided PathBuf + /// as the root directory to serve files from. + /// + /// Provided paths will resolve relartive to the provided `root` directory. + pub fn new(path: PathBuf) -> Result { + Ok(Self { path }) + } +} diff --git a/crates/file-explorer/src/fs/mod.rs b/crates/file-explorer-core/src/lib.rs similarity index 51% rename from crates/file-explorer/src/fs/mod.rs rename to crates/file-explorer-core/src/lib.rs index 2d1ba9cf..e007c8b9 100644 --- a/crates/file-explorer/src/fs/mod.rs +++ b/crates/file-explorer-core/src/lib.rs @@ -1,59 +1,44 @@ -mod directory; -mod file; +mod fs; -use std::path::{Component, Path, PathBuf}; +use std::path::{Component, PathBuf}; -use anyhow::Result; use tokio::fs::OpenOptions; -pub use self::directory::Directory; -pub use self::file::File; +pub use self::fs::{Directory, File}; + +use anyhow::Result; -/// Any OS filesystem entry recognized by `ScopedFileSystem` is treated as a -/// `Entry` both `File` and `Directory` are possible values with full support by -/// `ScopedFileSystem` +/// Any OS filesystem entry recognized by [`FileExplorer`] is treated as a +/// `Entry` both `File` and `Directory` are possible values. #[derive(Debug)] pub enum Entry { File(Box), Directory(Directory), } -#[derive(Debug, Clone)] -pub struct FileSystem { - pub path: PathBuf, +pub struct FileExplorer { + root: PathBuf, } -impl FileSystem { - /// Creates a new instance of `ScopedFileSystem` using the provided PathBuf - /// as the root directory to serve files from. - /// - /// Provided paths will resolve relartive to the provided `root` directory. - pub fn new(path: PathBuf) -> Result { - Ok(Self { path }) +impl FileExplorer { + pub fn new(root: PathBuf) -> Self { + Self { root } } - /// Resolves the provided path against the root directory of this - /// `ScopedFileSystem` instance. - /// - /// A relative path is built using `build_relative_path` and then is opened - /// to retrieve a `Entry`. - pub async fn resolve(&self, path: PathBuf) -> std::io::Result { - let entry_path = self.build_relative_path(path); - - Self::open(entry_path).await + /// Peeks on the provided `path` as a "subpath" for this [`FileExplorer`] instance. + pub async fn peek(&self, path: PathBuf) -> Result { + let relative_path = self.build_relative_path(path); + self.open(relative_path).await } - /// Builds a path relative to `ScopedFileSystem`'s `root` path with the - /// provided path. + /// Joins the provided `path` with the `root` path of this [`FileExplorer`] instance. fn build_relative_path(&self, path: PathBuf) -> PathBuf { - let mut root = self.path.clone(); - - root.extend(&Self::normalize_path(&path)); - + let mut root = self.root.clone(); + root.extend(&self.normalize_path(&path)); root } - /// Normalizes paths + /// Normalizes a `Path` to be directory-traversal safe. /// /// ```ignore /// docs/collegue/cs50/lectures/../code/voting_excecise @@ -64,7 +49,11 @@ impl FileSystem { /// ```ignore /// docs/collegue/cs50/code/voting_excecise /// ``` - fn normalize_path(path: &Path) -> PathBuf { + /// + /// # Reference + /// + /// - https://owasp.org/www-community/attacks/Path_Traversal + fn normalize_path(&self, path: &PathBuf) -> PathBuf { path.components() .fold(PathBuf::new(), |mut result, p| match p { Component::ParentDir => { @@ -80,7 +69,7 @@ impl FileSystem { } #[cfg(not(target_os = "windows"))] - async fn open(path: PathBuf) -> std::io::Result { + async fn open(&self, path: PathBuf) -> Result { let mut open_options = OpenOptions::new(); let entry_path: PathBuf = path.clone(); let file = open_options.read(true).open(path).await?; @@ -94,7 +83,18 @@ impl FileSystem { } #[cfg(target_os = "windows")] - async fn open(path: PathBuf) -> std::io::Result { + async fn open(&self, path: PathBuf) -> Result { + /// The file is being opened or created for a backup or restore operation. + /// The system ensures that the calling process overrides file security + /// checks when the process has SE_BACKUP_NAME and SE_RESTORE_NAME privileges. + /// + /// For more information, see Changing Privileges in a Token. + /// You must set this flag to obtain a handle to a directory. + /// A directory handle can be passed to some functions instead of a file handle. + /// + /// Refer: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea + const FILE_FLAG_BACKUP_SEMANTICS: u32 = 0x02000000; + let mut open_options = OpenOptions::new(); let entry_path: PathBuf = path.clone(); let file = open_options diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index 2ae5690c..8c46ab67 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -29,5 +29,6 @@ tracing = { workspace = true } tracing-subscriber = { workspace = true, features = ["env-filter"] } http-server-plugin = { workspace = true } +file-explorer-core = { workspace = true } file-explorer-proto = { workspace = true } file-explorer-ui = { workspace = true } diff --git a/crates/file-explorer/src/fs/file.rs b/crates/file-explorer/src/fs/file.rs deleted file mode 100644 index 85aebc13..00000000 --- a/crates/file-explorer/src/fs/file.rs +++ /dev/null @@ -1,108 +0,0 @@ -use anyhow::{Context, Result}; -use chrono::{DateTime, Local}; - -use std::fs::Metadata; -use std::mem::MaybeUninit; -use std::path::PathBuf; -use std::pin::Pin; -use std::task::{self, Poll}; - -use futures::Stream; -use hyper::body::Bytes; -use mime_guess::{from_path, Mime}; -use tokio::io::{AsyncRead, ReadBuf}; - -pub const FILE_BUFFER_SIZE: usize = 8 * 1024; - -pub type FileBuffer = Box<[MaybeUninit; FILE_BUFFER_SIZE]>; - -/// Wrapper around `tokio::fs::File` built from a OS ScopedFileSystem file -/// providing `std::fs::Metadata` and the path to such file -#[derive(Debug)] -pub struct File { - pub path: PathBuf, - pub file: tokio::fs::File, - pub metadata: Metadata, -} - -impl File { - pub fn new(path: PathBuf, file: tokio::fs::File, metadata: Metadata) -> Self { - File { - path, - file, - metadata, - } - } - - pub fn mime(&self) -> Mime { - from_path(self.path.clone()).first_or_octet_stream() - } - - pub fn size(&self) -> u64 { - self.metadata.len() - } - - pub fn last_modified(&self) -> Result> { - let modified = self - .metadata - .modified() - .context("Failed to read last modified time for file")?; - let modified: DateTime = modified.into(); - - Ok(modified) - } - - #[allow(dead_code)] - pub fn bytes(self) -> Vec { - let byte_stream = ByteStream { - file: self.file, - buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]), - }; - - byte_stream - .buffer - .iter() - .map(|muint| unsafe { muint.assume_init() }) - .collect::>() - } -} - -pub struct ByteStream { - file: tokio::fs::File, - buffer: FileBuffer, -} - -impl From for ByteStream { - fn from(file: File) -> Self { - ByteStream { - file: file.file, - buffer: Box::new([MaybeUninit::uninit(); FILE_BUFFER_SIZE]), - } - } -} - -impl Stream for ByteStream { - type Item = Result; - - fn poll_next(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { - let ByteStream { - ref mut file, - ref mut buffer, - } = *self; - let mut read_buffer = ReadBuf::uninit(&mut buffer[..]); - - match Pin::new(file).poll_read(cx, &mut read_buffer) { - Poll::Ready(Ok(())) => { - let filled = read_buffer.filled(); - - if filled.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::copy_from_slice(filled)))) - } - } - Poll::Ready(Err(error)) => Poll::Ready(Some(Err(error.into()))), - Poll::Pending => Poll::Pending, - } - } -} diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 7684cc9d..30f944c0 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -1,4 +1,3 @@ -mod fs; mod utils; use std::fs::read_dir; @@ -8,25 +7,24 @@ use std::sync::Arc; use anyhow::{Context, Result}; use async_trait::async_trait; -use fs::Entry; use http::header::LOCATION; use http::request::Parts; use http::HeaderValue; use http_body_util::Full; use hyper::body::Bytes; -use hyper::header::{CONTENT_TYPE, ETAG, LAST_MODIFIED}; +use hyper::header::CONTENT_TYPE; use hyper::{Method, Response, StatusCode, Uri}; use percent_encoding::{percent_decode_str, utf8_percent_encode}; use serde::Deserialize; use tokio::io::AsyncWriteExt; use tokio::runtime::Handle; +use file_explorer_core::{Entry, FileExplorer}; use file_explorer_proto::{BreadcrumbItem, DirectoryEntry, DirectoryIndex, Sort}; use file_explorer_ui::Assets; use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; -use self::fs::{File, FileSystem}; use self::utils::{decode_uri, encode_uri, PERCENT_ENCODE_SET}; const FILE_BUFFER_SIZE: usize = 8 * 1024; @@ -47,7 +45,7 @@ extern "C" fn register(config_path: PathBuf, rt: Arc, registrar: &mut dy registrar.register_function( PLUGIN_NAME, - Arc::new(FileExplorer::new(rt, config.path).expect("Failed to create FileExplorer")), + Arc::new(FileExplorerPlugin::new(rt, config.path)), ); } @@ -56,14 +54,14 @@ struct FileExplorerConfig { pub path: PathBuf, } -struct FileExplorer { - rt: Arc, - fs: FileSystem, +struct FileExplorerPlugin { + file_explorer: FileExplorer, path: PathBuf, + rt: Arc, } #[async_trait] -impl Function for FileExplorer { +impl Function for FileExplorerPlugin { async fn call( &self, parts: Parts, @@ -74,15 +72,15 @@ impl Function for FileExplorer { } } -impl FileExplorer { - fn new(rt: Arc, path: PathBuf) -> Result { - let fs = FileSystem::new(path.clone())?; +impl FileExplorerPlugin { + fn new(rt: Arc, path: PathBuf) -> Self { + let file_explorer = FileExplorer::new(path.clone()); - Ok(Self { - rt, - fs, + Self { + file_explorer, path, - }) + rt, + } } async fn handle( @@ -128,7 +126,7 @@ impl FileExplorer { let path = Self::parse_req_uri(parts.uri).unwrap(); match parts.method { - Method::GET => match self.fs.resolve(path).await { + Method::GET => match self.file_explorer.peek(path).await { Ok(entry) => match entry { Entry::Directory(dir) => { let directory_index = @@ -137,7 +135,7 @@ impl FileExplorer { Ok(Response::new(Full::new(Bytes::from(json)))) } - Entry::File(file) => Ok(Self::make_http_file_response(file).await.unwrap()), + Entry::File(_) => Ok(Response::new(Full::new(Bytes::from("Found but WIP")))), }, Err(err) => { let message = format!("Failed to resolve path: {}", err); @@ -310,20 +308,20 @@ impl FileExplorer { Self::index_directory(self.path.clone(), path) } - pub async fn make_http_file_response(file: Box) -> Result>> { - Response::builder() - .header(CONTENT_TYPE, file.mime().to_string()) - .header( - ETAG, - format!( - "W/\"{0:x}-{1:x}.{2:x}\"", - file.size(), - file.last_modified().unwrap().timestamp(), - file.last_modified().unwrap().timestamp_subsec_nanos(), - ), - ) - .header(LAST_MODIFIED, file.last_modified().unwrap().to_rfc2822()) - .body(Full::new(Bytes::from(file.bytes()))) - .context("Failed to build HTTP File Response") - } + // pub async fn make_http_file_response(file: Box) -> Result>> { + // Response::builder() + // .header(CONTENT_TYPE, file.mime().to_string()) + // .header( + // ETAG, + // format!( + // "W/\"{0:x}-{1:x}.{2:x}\"", + // file.size(), + // file.last_modified().unwrap().timestamp(), + // file.last_modified().unwrap().timestamp_subsec_nanos(), + // ), + // ) + // .header(LAST_MODIFIED, file.last_modified().unwrap().to_rfc2822()) + // .body(Full::new(Bytes::from(file.bytes()))) + // .context("Failed to build HTTP File Response") + // } } diff --git a/crates/file-explorer/src/templater/hbs/error.hbs b/crates/file-explorer/src/templater/hbs/error.hbs deleted file mode 100644 index 97bf040a..00000000 --- a/crates/file-explorer/src/templater/hbs/error.hbs +++ /dev/null @@ -1,4 +0,0 @@ -
-

{{code}}

-

{{error}}

-
diff --git a/crates/file-explorer/src/templater/hbs/explorer.hbs b/crates/file-explorer/src/templater/hbs/explorer.hbs deleted file mode 100644 index 75c7939d..00000000 --- a/crates/file-explorer/src/templater/hbs/explorer.hbs +++ /dev/null @@ -1,249 +0,0 @@ - - - - - File Explorer - - - - -
- -
- -
-
- - From 30a092ec262099af284945954dbb2d02127484fa Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 26 Oct 2024 19:09:01 -0300 Subject: [PATCH 32/40] feat: ui integration --- Cargo.lock | 2 + crates/file-explorer-proto/src/lib.rs | 8 ++-- crates/file-explorer-ui/Cargo.toml | 5 ++- crates/file-explorer-ui/src/api.rs | 23 ++++++++++ .../src/components/file_list/entry.rs | 24 ++++++++++ .../src/components/file_list/mod.rs | 45 +++++++++++++++++++ crates/file-explorer-ui/src/components/mod.rs | 1 + crates/file-explorer-ui/src/lib.rs | 34 +++++++++++--- crates/file-explorer/src/lib.rs | 11 ++++- 9 files changed, 140 insertions(+), 13 deletions(-) create mode 100644 crates/file-explorer-ui/src/api.rs create mode 100644 crates/file-explorer-ui/src/components/file_list/entry.rs create mode 100644 crates/file-explorer-ui/src/components/file_list/mod.rs create mode 100644 crates/file-explorer-ui/src/components/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 7af33273..3a3288a0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -557,6 +557,8 @@ name = "file-explorer-ui" version = "0.0.0" dependencies = [ "anyhow", + "chrono", + "file-explorer-proto", "leptos", "leptos_meta", "leptos_router", diff --git a/crates/file-explorer-proto/src/lib.rs b/crates/file-explorer-proto/src/lib.rs index 73b0b8b7..29c639ae 100644 --- a/crates/file-explorer-proto/src/lib.rs +++ b/crates/file-explorer-proto/src/lib.rs @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize}; /// A Directory entry used to display a File Explorer's entry. /// This struct is directly related to the Handlebars template used /// to power the File Explorer's UI -#[derive(Debug, Eq, Serialize)] +#[derive(Clone, Debug, Deserialize, Eq, Serialize)] pub struct DirectoryEntry { pub display_name: String, pub is_dir: bool, @@ -47,7 +47,7 @@ impl PartialEq for DirectoryEntry { } /// A Breadcrumb Item used to navigate to previous path components -#[derive(Clone, Debug, Serialize, PartialEq, Eq)] +#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct BreadcrumbItem { pub entry_name: String, pub entry_link: String, @@ -56,14 +56,14 @@ pub struct BreadcrumbItem { /// The value passed to the Handlebars template engine. /// All references contained in File Explorer's UI are provided /// via the `DirectoryIndex` struct -#[derive(Debug, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] pub struct DirectoryIndex { pub entries: Vec, pub breadcrumbs: Vec, pub sort: Sort, } -#[derive(Serialize, Debug, PartialEq, Deserialize)] +#[derive(Clone, Serialize, Debug, PartialEq, Deserialize)] pub enum Sort { Directory, Name, diff --git a/crates/file-explorer-ui/Cargo.toml b/crates/file-explorer-ui/Cargo.toml index 08ba682e..a98e6288 100644 --- a/crates/file-explorer-ui/Cargo.toml +++ b/crates/file-explorer-ui/Cargo.toml @@ -16,12 +16,15 @@ path = "src/bin/main.rs" [dependencies] anyhow = { workspace = true } +chrono = { workspace = true, features = ["serde"] } leptos = { workspace = true, features = ["csr"] } leptos_meta = { workspace = true, features = ["csr"] } leptos_router = { workspace = true, features = ["csr"] } -reqwest = { workspace = true } +reqwest = { workspace = true, features = ["json"] } rust-embed = { workspace = true } +file-explorer-proto = { workspace = true } + [dev-dependencies] wasm-bindgen = "0.2" wasm-bindgen-test = "0.3" diff --git a/crates/file-explorer-ui/src/api.rs b/crates/file-explorer-ui/src/api.rs new file mode 100644 index 00000000..93dd88a0 --- /dev/null +++ b/crates/file-explorer-ui/src/api.rs @@ -0,0 +1,23 @@ +use anyhow::Result; +use reqwest::Url; + +use file_explorer_proto::DirectoryIndex; + +pub struct Api { + base_url: Url, +} + +impl Api { + pub fn new() -> Self { + let base_url = Url::parse("http://localhost:3000").unwrap(); + + Self { base_url } + } + + pub async fn peek(&self, path: &str) -> Result { + let url = self.base_url.join(&format!("api/v1/{path}"))?; + let index = reqwest::get(url).await?.json::().await?; + + Ok(index) + } +} diff --git a/crates/file-explorer-ui/src/components/file_list/entry.rs b/crates/file-explorer-ui/src/components/file_list/entry.rs new file mode 100644 index 00000000..a104dc9e --- /dev/null +++ b/crates/file-explorer-ui/src/components/file_list/entry.rs @@ -0,0 +1,24 @@ +use leptos::{component, view, IntoView}; + +#[component] +pub fn Entry(#[prop(into)] name: String) -> impl IntoView { + view! { + + + + + + {name} + + + "128.9 MB" + + + "2024-10-11" + + + "2024-10-11" + + + } +} diff --git a/crates/file-explorer-ui/src/components/file_list/mod.rs b/crates/file-explorer-ui/src/components/file_list/mod.rs new file mode 100644 index 00000000..396ebbe5 --- /dev/null +++ b/crates/file-explorer-ui/src/components/file_list/mod.rs @@ -0,0 +1,45 @@ +mod entry; + +use leptos::{component, view, For, IntoView, Signal, SignalGet}; + +use file_explorer_proto::DirectoryEntry; + +use self::entry::Entry; + +#[component] +pub fn FileList(#[prop(into)] entries: Signal>) -> impl IntoView { + view! { +
+ + + + + + + + + + + + } + } + /> + +
+ + "Name" + + "Size" + + "Created" + + "Modified" +
+
+ } +} diff --git a/crates/file-explorer-ui/src/components/mod.rs b/crates/file-explorer-ui/src/components/mod.rs new file mode 100644 index 00000000..e108de48 --- /dev/null +++ b/crates/file-explorer-ui/src/components/mod.rs @@ -0,0 +1 @@ +pub mod file_list; diff --git a/crates/file-explorer-ui/src/lib.rs b/crates/file-explorer-ui/src/lib.rs index b7c67a92..3347fed5 100644 --- a/crates/file-explorer-ui/src/lib.rs +++ b/crates/file-explorer-ui/src/lib.rs @@ -1,23 +1,43 @@ -use leptos::{component, spawn_local, view, IntoView}; +mod api; +mod components; + +use leptos::{ + component, create_memo, create_signal, spawn_local, view, IntoView, SignalGet, SignalSet, +}; use leptos_meta::provide_meta_context; use rust_embed::Embed; +use file_explorer_proto::DirectoryIndex; + +use self::api::Api; +use self::components::file_list::FileList; + +#[derive(Embed)] +#[folder = "public/dist"] +pub struct Assets; + #[component] pub fn App() -> impl IntoView { provide_meta_context(); + let (index_getter, index_setter) = create_signal::>(None); + let entries = create_memo(move |_| { + index_getter + .get() + .map(|index| index.entries.clone()) + .unwrap_or_default() + }); + spawn_local(async move { leptos::logging::warn!("Performing a request to the server"); - reqwest::get("http://localhost:3000/api/v1").await.unwrap(); + let index = Api::new().peek("").await.unwrap(); + + index_setter.set(Some(index)); }); view! {
-

App

+
} } - -#[derive(Embed)] -#[folder = "public/dist"] -pub struct Assets; diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 30f944c0..836ba9df 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -14,6 +14,7 @@ use http_body_util::Full; use hyper::body::Bytes; use hyper::header::CONTENT_TYPE; use hyper::{Method, Response, StatusCode, Uri}; +use mime_guess::mime::APPLICATION_JSON; use percent_encoding::{percent_decode_str, utf8_percent_encode}; use serde::Deserialize; use tokio::io::AsyncWriteExt; @@ -88,6 +89,8 @@ impl FileExplorerPlugin { parts: Parts, body: Bytes, ) -> Result>, InvocationError> { + tracing::info!("Handling request: {:?}", parts); + if parts.uri.path().starts_with("/api/v1") { self.handle_api(parts, body).await } else { @@ -132,8 +135,14 @@ impl FileExplorerPlugin { let directory_index = self.marshall_directory_index(dir.path()).await.unwrap(); let json = serde_json::to_string(&directory_index).unwrap(); + let body = Full::new(Bytes::from(json)); + let mut response = Response::new(body); + let mut headers = response.headers().clone(); + + headers.append(CONTENT_TYPE, "application/json".try_into().unwrap()); + *response.headers_mut() = headers; - Ok(Response::new(Full::new(Bytes::from(json)))) + return Ok(response); } Entry::File(_) => Ok(Response::new(Full::new(Bytes::from("Found but WIP")))), }, From 7d47a08465795b002c07f7965603d4c7100f9b62 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Thu, 31 Oct 2024 20:32:12 -0300 Subject: [PATCH 33/40] feat: file uploads --- Cargo.lock | 409 +++++++++++++++--- Cargo.toml | 4 + crates/file-explorer-ui/Cargo.toml | 5 +- crates/file-explorer-ui/src/api.rs | 3 +- .../src/components/file_upload/mod.rs | 60 +++ crates/file-explorer-ui/src/components/mod.rs | 1 + crates/file-explorer-ui/src/lib.rs | 2 + crates/file-explorer/Cargo.toml | 1 + crates/file-explorer/src/lib.rs | 79 +++- crates/http-server/Cargo.toml | 1 + crates/http-server/src/server/mod.rs | 4 +- 11 files changed, 510 insertions(+), 59 deletions(-) create mode 100644 crates/file-explorer-ui/src/components/file_upload/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 3a3288a0..e04da48d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,7 +104,7 @@ checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -115,7 +115,7 @@ checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -135,7 +135,7 @@ dependencies = [ "manyhow", "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -151,7 +151,7 @@ dependencies = [ "proc-macro2", "quote", "quote-use", - "syn", + "syn 2.0.82", ] [[package]] @@ -187,6 +187,15 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -214,6 +223,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.7.1" @@ -314,7 +329,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -450,7 +465,7 @@ checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -518,12 +533,13 @@ dependencies = [ "file-explorer-proto", "file-explorer-ui", "futures", - "http", + "http 1.1.0", "http-body-util", "http-server-plugin", "humansize", "hyper", "mime_guess", + "multer", "percent-encoding", "rust-embed", "serde", @@ -559,6 +575,7 @@ dependencies = [ "anyhow", "chrono", "file-explorer-proto", + "gloo", "leptos", "leptos_meta", "leptos_router", @@ -655,7 +672,7 @@ checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -717,6 +734,108 @@ version = "0.26.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +[[package]] +name = "gloo" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15282ece24eaf4bd338d73ef580c6714c8615155c4190c781290ee3fa0fd372" +dependencies = [ + "gloo-console", + "gloo-dialogs", + "gloo-events", + "gloo-file", + "gloo-history", + "gloo-net 0.5.0", + "gloo-render", + "gloo-storage", + "gloo-timers", + "gloo-utils", + "gloo-worker", +] + +[[package]] +name = "gloo-console" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a17868f56b4a24f677b17c8cb69958385102fa879418052d60b50bc1727e261" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-dialogs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4748e10122b01435750ff530095b1217cf6546173459448b83913ebe7815df" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-events" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c26fb45f7c385ba980f5fa87ac677e363949e065a083722697ef1b2cc91e41" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-file" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97563d71863fb2824b2e974e754a81d19c4a7ec47b09ced8a0e6656b6d54bd1f" +dependencies = [ + "gloo-events", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-history" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "903f432be5ba34427eac5e16048ef65604a82061fe93789f2212afc73d8617d6" +dependencies = [ + "getrandom", + "gloo-events", + "gloo-utils", + "serde", + "serde-wasm-bindgen", + "serde_urlencoded", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-net" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43aaa242d1239a8822c15c645f02166398da4f8b5c4bae795c1f5b44e9eee173" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http 0.2.12", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "gloo-net" version = "0.6.0" @@ -727,7 +846,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils", - "http", + "http 1.1.0", "js-sys", "pin-project", "serde", @@ -738,6 +857,41 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-render" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56008b6744713a8e8d98ac3dcb7d06543d5662358c9c805b4ce2167ad4649833" +dependencies = [ + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-storage" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc8031e8c92758af912f9bc08fbbadd3c6f3cfcbf6b64cdf3d6a81f0139277a" +dependencies = [ + "gloo-utils", + "js-sys", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "gloo-utils" version = "0.2.0" @@ -751,6 +905,37 @@ dependencies = [ "web-sys", ] +[[package]] +name = "gloo-worker" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "085f262d7604911c8150162529cefab3782e91adb20202e8658f7275d2aefe5d" +dependencies = [ + "bincode", + "futures", + "gloo-utils", + "gloo-worker-macros", + "js-sys", + "pinned", + "serde", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-worker-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "956caa58d4857bc9941749d55e4bd3000032d8212762586fa5705632967140e7" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.82", +] + [[package]] name = "h2" version = "0.4.6" @@ -762,7 +947,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http", + "http 1.1.0", "indexmap", "slab", "tokio", @@ -813,6 +998,17 @@ dependencies = [ "utf8-width", ] +[[package]] +name = "http" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http" version = "1.1.0" @@ -840,7 +1036,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.1.0", ] [[package]] @@ -851,7 +1047,7 @@ checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", - "http", + "http 1.1.0", "http-body", "pin-project-lite", ] @@ -864,13 +1060,14 @@ dependencies = [ "async-trait", "bytes", "clap", - "http", + "http 1.1.0", "http-auth-basic", "http-body-util", "http-server-plugin", "hyper", "hyper-util", "libloading", + "local-ip-address", "tokio", "tower", "tower-http", @@ -885,7 +1082,7 @@ dependencies = [ "anyhow", "async-trait", "bytes", - "http", + "http 1.1.0", "http-body-util", "hyper", "rustc_version", @@ -925,7 +1122,7 @@ dependencies = [ "futures-channel", "futures-util", "h2", - "http", + "http 1.1.0", "http-body", "httparse", "httpdate", @@ -943,7 +1140,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" dependencies = [ "futures-util", - "http", + "http 1.1.0", "hyper", "hyper-util", "rustls", @@ -978,7 +1175,7 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.1.0", "http-body", "hyper", "pin-project-lite", @@ -1162,7 +1359,7 @@ dependencies = [ "quote", "rstml", "serde", - "syn", + "syn 2.0.82", "walkdir", ] @@ -1184,7 +1381,7 @@ dependencies = [ "quote", "rstml", "server_fn_macro", - "syn", + "syn 2.0.82", "tracing", "uuid", ] @@ -1237,7 +1434,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d71dea7d42c0d29c40842750232d3425ed1cf10e313a1f898076d20871dad32" dependencies = [ "cfg-if", - "gloo-net", + "gloo-net 0.6.0", "itertools", "js-sys", "lazy_static", @@ -1310,6 +1507,18 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "local-ip-address" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3669cf5561f8d27e8fc84cc15e58350e70f557d4d65f70e3154e54cd2f8e1782" +dependencies = [ + "libc", + "neli", + "thiserror", + "windows-sys 0.59.0", +] + [[package]] name = "lock_api" version = "0.4.12" @@ -1335,7 +1544,7 @@ dependencies = [ "manyhow-macros", "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -1417,6 +1626,23 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "multer" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" +dependencies = [ + "bytes", + "encoding_rs", + "futures-util", + "http 1.1.0", + "httparse", + "memchr", + "mime", + "spin", + "version_check", +] + [[package]] name = "native-tls" version = "0.2.12" @@ -1434,6 +1660,31 @@ dependencies = [ "tempfile", ] +[[package]] +name = "neli" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1100229e06604150b3becd61a4965d5c70f3be1759544ea7274166f4be41ef43" +dependencies = [ + "byteorder", + "libc", + "log", + "neli-proc-macros", +] + +[[package]] +name = "neli-proc-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c168194d373b1e134786274020dae7fc5513d565ea2ebb9bc9ff17ffb69106d4" +dependencies = [ + "either", + "proc-macro2", + "quote", + "serde", + "syn 1.0.109", +] + [[package]] name = "nom" version = "7.1.3" @@ -1511,7 +1762,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -1602,7 +1853,7 @@ checksum = "a4502d8515ca9f32f1fb543d987f63d95a14934883db45bdb48060b6b69257f8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -1617,6 +1868,17 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pinned" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a829027bd95e54cfe13e3e258a1ae7b645960553fb82b75ff852c29688ee595b" +dependencies = [ + "futures", + "rustversion", + "thiserror", +] + [[package]] name = "pkg-config" version = "0.3.31" @@ -1630,7 +1892,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "910d41a655dac3b764f1ade94821093d3610248694320cd072303a8eedcf221d" dependencies = [ "proc-macro2", - "syn", + "syn 2.0.82", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", ] [[package]] @@ -1716,7 +1988,7 @@ checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", "version_check", "yansi", ] @@ -1749,7 +2021,7 @@ dependencies = [ "proc-macro-utils 0.10.0", "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -1807,9 +2079,9 @@ checksum = "dbb5fb1acd8a1a18b3dd5be62d25485eb770e05afb408a9627d14d451bae12da" [[package]] name = "reqwest" -version = "0.12.8" +version = "0.12.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" +checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" dependencies = [ "base64 0.22.1", "bytes", @@ -1817,7 +2089,7 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", + "http 1.1.0", "http-body", "http-body-util", "hyper", @@ -1872,7 +2144,7 @@ dependencies = [ "proc-macro2", "proc-macro2-diagnostics", "quote", - "syn", + "syn 2.0.82", "syn_derive", "thiserror", ] @@ -1897,7 +2169,7 @@ dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn", + "syn 2.0.82", "walkdir", ] @@ -1947,9 +2219,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.15" +version = "0.23.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fbb44d7acc4e873d613422379f69f237a1b141928c02f6bc6ccfddddc2d7993" +checksum = "eee87ff5d9b36712a58574e12e9f0ea80f915a5b0ac518d322b24a465617925e" dependencies = [ "once_cell", "rustls-pki-types", @@ -1984,6 +2256,12 @@ dependencies = [ "untrusted", ] +[[package]] +name = "rustversion" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" + [[package]] name = "ryu" version = "1.0.18" @@ -2092,7 +2370,7 @@ checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -2170,8 +2448,8 @@ dependencies = [ "const_format", "dashmap", "futures", - "gloo-net", - "http", + "gloo-net 0.6.0", + "http 1.1.0", "js-sys", "once_cell", "send_wrapper", @@ -2198,7 +2476,7 @@ dependencies = [ "convert_case", "proc-macro2", "quote", - "syn", + "syn 2.0.82", "xxhash-rust", ] @@ -2209,7 +2487,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f2aa8119b558a17992e0ac1fd07f080099564f24532858811ce04f742542440" dependencies = [ "server_fn_macro", - "syn", + "syn 2.0.82", ] [[package]] @@ -2297,6 +2575,17 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "syn" version = "2.0.82" @@ -2317,7 +2606,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -2385,7 +2674,7 @@ checksum = "268026685b2be38d7103e9e507c938a1fcb3d7e6eb15e87870b617bf37b6d581" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -2439,7 +2728,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -2485,7 +2774,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "toml_edit", + "toml_edit 0.22.22", ] [[package]] @@ -2497,6 +2786,17 @@ dependencies = [ "serde", ] +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow 0.5.40", +] + [[package]] name = "toml_edit" version = "0.22.22" @@ -2507,7 +2807,7 @@ dependencies = [ "serde", "serde_spanned", "toml_datetime", - "winnow", + "winnow 0.6.20", ] [[package]] @@ -2532,7 +2832,7 @@ checksum = "8437150ab6bbc8c5f0f519e3d5ed4aa883a83dd4cdd3d1b21f9482936046cb97" dependencies = [ "bitflags 2.4.0", "bytes", - "http", + "http 1.1.0", "pin-project-lite", "tower-layer", "tower-service", @@ -2569,7 +2869,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -2634,7 +2934,7 @@ checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -2789,7 +3089,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 2.0.82", "wasm-bindgen-shared", ] @@ -2823,7 +3123,7 @@ checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2857,7 +3157,7 @@ checksum = "c97b2ef2c8d627381e51c071c2ab328eac606d3f69dd82bcbca20a9e389d95f0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.82", ] [[package]] @@ -2905,7 +3205,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -3035,6 +3335,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winnow" version = "0.6.20" diff --git a/Cargo.toml b/Cargo.toml index 9e485601..766c4afa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ bytes = "1.7.1" chrono = "0.4.38" clap = "4.5.20" futures = "0.3.31" +gloo = "0.11.0" humansize = "2.1.3" http = "1.1.0" http-auth-basic = "0.3.3" @@ -30,7 +31,9 @@ leptos_meta = "0.6" leptos_router = "0.6" leptos-use = "0.10" libloading = "0.8.5" +local-ip-address = "0.6.3" mime_guess = "2.0.5" +multer = "3.1.0" percent-encoding = "2.3.1" reqwest = "0.12.8" rust-embed = "8.5.0" @@ -44,6 +47,7 @@ tower-http = "0.6.1" tower = "0.5.1" tracing = "0.1.40" tracing-subscriber = "0.3.18" +web-sys = "0.3.72" # Workspace Crates file-explorer-core = { path = "crates/file-explorer-core" } diff --git a/crates/file-explorer-ui/Cargo.toml b/crates/file-explorer-ui/Cargo.toml index a98e6288..50bf3223 100644 --- a/crates/file-explorer-ui/Cargo.toml +++ b/crates/file-explorer-ui/Cargo.toml @@ -17,15 +17,18 @@ path = "src/bin/main.rs" [dependencies] anyhow = { workspace = true } chrono = { workspace = true, features = ["serde"] } +gloo = { workspace = true } leptos = { workspace = true, features = ["csr"] } leptos_meta = { workspace = true, features = ["csr"] } leptos_router = { workspace = true, features = ["csr"] } reqwest = { workspace = true, features = ["json"] } rust-embed = { workspace = true } +web-sys = { workspace = true, features = ["FileList", "HtmlInputElement"] } file-explorer-proto = { workspace = true } [dev-dependencies] +web-sys = { workspace = true } + wasm-bindgen = "0.2" wasm-bindgen-test = "0.3" -web-sys = "0.3" diff --git a/crates/file-explorer-ui/src/api.rs b/crates/file-explorer-ui/src/api.rs index 93dd88a0..da7a77ed 100644 --- a/crates/file-explorer-ui/src/api.rs +++ b/crates/file-explorer-ui/src/api.rs @@ -1,4 +1,5 @@ use anyhow::Result; +use gloo::utils::window; use reqwest::Url; use file_explorer_proto::DirectoryIndex; @@ -9,7 +10,7 @@ pub struct Api { impl Api { pub fn new() -> Self { - let base_url = Url::parse("http://localhost:3000").unwrap(); + let base_url = Url::parse(&window().location().href().unwrap()).unwrap(); Self { base_url } } diff --git a/crates/file-explorer-ui/src/components/file_upload/mod.rs b/crates/file-explorer-ui/src/components/file_upload/mod.rs new file mode 100644 index 00000000..57693353 --- /dev/null +++ b/crates/file-explorer-ui/src/components/file_upload/mod.rs @@ -0,0 +1,60 @@ +use gloo::utils::window; +use leptos::logging::log; +use leptos::wasm_bindgen::JsCast; +use leptos::{component, create_action, create_node_ref, html, view, IntoView}; +use web_sys::{Event, FormData, HtmlInputElement}; + +#[component] +pub fn FileUpload() -> impl IntoView { + let file_input_el = create_node_ref::(); + let upload_file = create_action(|file: &web_sys::File| { + let file = file.to_owned(); + + let form_data = FormData::new().unwrap(); + form_data.append_with_blob("file", &file).unwrap(); + + async move { + log!("Uploading file..."); + + let response = gloo::net::http::Request::post(&format!( + "{}/api/v1", + &window().location().origin().unwrap() + )) + .body(form_data) + .unwrap() // result can't be error + .send() + .await; + + match response { + Ok(_) => { + log!("File successfully uploaded!"); + } + Err(err) => { + log!("Error uploading file: {}", err); + } + } + } + }); + + view! { +
+ (); + let mb_file_list = el.files(); + + if let Some(file_list) = mb_file_list { + if let Some(file) = file_list.get(0) { + log!("File selected: {:?}", file); + upload_file.dispatch(file); + } else { + log!("No file selected"); + } + } + } + /> +
+ } +} diff --git a/crates/file-explorer-ui/src/components/mod.rs b/crates/file-explorer-ui/src/components/mod.rs index e108de48..b7d1631d 100644 --- a/crates/file-explorer-ui/src/components/mod.rs +++ b/crates/file-explorer-ui/src/components/mod.rs @@ -1 +1,2 @@ pub mod file_list; +pub mod file_upload; diff --git a/crates/file-explorer-ui/src/lib.rs b/crates/file-explorer-ui/src/lib.rs index 3347fed5..f2e48605 100644 --- a/crates/file-explorer-ui/src/lib.rs +++ b/crates/file-explorer-ui/src/lib.rs @@ -1,6 +1,7 @@ mod api; mod components; +use components::file_upload::FileUpload; use leptos::{ component, create_memo, create_signal, spawn_local, view, IntoView, SignalGet, SignalSet, }; @@ -37,6 +38,7 @@ pub fn App() -> impl IntoView { view! {
+
} diff --git a/crates/file-explorer/Cargo.toml b/crates/file-explorer/Cargo.toml index 8c46ab67..729e400f 100644 --- a/crates/file-explorer/Cargo.toml +++ b/crates/file-explorer/Cargo.toml @@ -19,6 +19,7 @@ http-body-util = { workspace = true } humansize = { workspace = true } hyper = { workspace = true } mime_guess = { workspace = true } +multer = { workspace = true } rust-embed = { workspace = true } serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 836ba9df..f4a2084e 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -14,7 +14,7 @@ use http_body_util::Full; use hyper::body::Bytes; use hyper::header::CONTENT_TYPE; use hyper::{Method, Response, StatusCode, Uri}; -use mime_guess::mime::APPLICATION_JSON; +use multer::Multipart; use percent_encoding::{percent_decode_str, utf8_percent_encode}; use serde::Deserialize; use tokio::io::AsyncWriteExt; @@ -126,7 +126,7 @@ impl FileExplorerPlugin { parts: Parts, body: Bytes, ) -> Result>, InvocationError> { - let path = Self::parse_req_uri(parts.uri).unwrap(); + let path = Self::parse_req_uri(parts.uri.clone()).unwrap(); match parts.method { Method::GET => match self.file_explorer.peek(path).await { @@ -142,7 +142,7 @@ impl FileExplorerPlugin { headers.append(CONTENT_TYPE, "application/json".try_into().unwrap()); *response.headers_mut() = headers; - return Ok(response); + Ok(response) } Entry::File(_) => Ok(Response::new(Full::new(Bytes::from("Found but WIP")))), }, @@ -152,9 +152,7 @@ impl FileExplorerPlugin { } }, Method::POST => { - let filename = path.file_name().unwrap().to_str().unwrap(); - let mut file = tokio::fs::File::create(filename).await.unwrap(); - file.write_all(&body).await.unwrap(); + self.handle_file_upload(parts, body).await?; Ok(Response::new(Full::new(Bytes::from( "POST method is not supported", )))) @@ -163,6 +161,75 @@ impl FileExplorerPlugin { } } + async fn handle_file_upload( + &self, + parts: Parts, + body: Bytes, + ) -> Result>, InvocationError> { + // Extract the `multipart/form-data` boundary from the headers. + let boundary = parts + .headers + .get(CONTENT_TYPE) + .and_then(|ct| ct.to_str().ok()) + .and_then(|ct| multer::parse_boundary(ct).ok()); + + // Send `BAD_REQUEST` status if the content-type is not multipart/form-data. + if boundary.is_none() { + return Ok(Response::builder() + .status(StatusCode::BAD_REQUEST) + .body(Full::from("BAD REQUEST")) + .unwrap()); + } + + // Process the multipart e.g. you can store them in files. + if let Err(err) = self.process_multipart(body, boundary.unwrap()).await { + return Ok(Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Full::from(format!("INTERNAL SERVER ERROR: {}", err))) + .unwrap()); + } + + Ok(Response::new(Full::from("Success"))) + } + + async fn process_multipart(&self, bytes: Bytes, boundary: String) -> multer::Result<()> { + let cursor = std::io::Cursor::new(bytes); + let bytes_stream = tokio_util::io::ReaderStream::new(cursor); + let mut multipart = Multipart::new(bytes_stream, boundary); + + // Iterate over the fields, `next_field` method will return the next field if + // available. + while let Some(mut field) = multipart.next_field().await? { + // Get the field name. + let name = field.name(); + + // Get the field's filename if provided in "Content-Disposition" header. + let file_name = field.file_name().to_owned().unwrap_or("default.png"); + + // Get the "Content-Type" header as `mime::Mime` type. + let content_type = field.content_type(); + + let mut file = tokio::fs::File::create(file_name).await.unwrap(); + + println!( + "\n\nName: {:?}, FileName: {:?}, Content-Type: {:?}\n\n", + name, file_name, content_type + ); + + // Process the field data chunks e.g. store them in a file. + let mut field_bytes_len = 0; + while let Some(field_chunk) = field.chunk().await? { + // Do something with field chunk. + field_bytes_len += field_chunk.len(); + file.write_all(&field_chunk).await.unwrap(); + } + + println!("Field Bytes Length: {:?}", field_bytes_len); + } + + Ok(()) + } + fn parse_req_uri(uri: Uri) -> Result { let parts: Vec<&str> = uri.path().split('/').collect(); let path = &parts[3..].join("/"); diff --git a/crates/http-server/Cargo.toml b/crates/http-server/Cargo.toml index 58a68b50..755d7047 100644 --- a/crates/http-server/Cargo.toml +++ b/crates/http-server/Cargo.toml @@ -23,6 +23,7 @@ http-body-util = { workspace = true } hyper = { workspace = true } hyper-util = { workspace = true, features = ["full"] } libloading = { workspace = true } +local-ip-address = { workspace = true } tokio = { workspace = true, features = ["fs", "rt-multi-thread", "signal", "macros"] } tower-http = { workspace = true, features = ["cors"] } tower = { workspace = true, features = ["util"] } diff --git a/crates/http-server/src/server/mod.rs b/crates/http-server/src/server/mod.rs index 0518460e..67866d2a 100644 --- a/crates/http-server/src/server/mod.rs +++ b/crates/http-server/src/server/mod.rs @@ -22,12 +22,13 @@ impl Server { pub async fn run(rt: Arc) -> Result<()> { info!("Initializing server"); - let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + let addr = SocketAddr::from(([0, 0, 0, 0], 3000)); let listener = TcpListener::bind(addr).await?; let functions = Arc::new(ExternalFunctions::new()); let plugin_library = PathBuf::from_str("./target/debug/libfile_explorer.dylib").unwrap(); let config = PathBuf::from_str("./config.toml").unwrap(); let handle = Arc::new(rt.handle().to_owned()); + let local_ip = local_ip_address::local_ip(); unsafe { functions @@ -37,6 +38,7 @@ impl Server { } info!(%addr, "Server Listening"); + info!(?local_ip, "Local Network"); loop { let (stream, _) = listener.accept().await?; From d52a392fe5eab70414d73a4a61d10cb58a00f219 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 2 Nov 2024 00:10:07 -0300 Subject: [PATCH 34/40] feat: ui entry type --- crates/file-explorer-proto/src/lib.rs | 12 ++++++ crates/file-explorer-ui/Trunk.toml | 4 ++ crates/file-explorer-ui/src/api.rs | 12 ++++++ .../src/components/file_list/entry.rs | 38 +++++++++++++------ .../src/components/file_list/entry_icon.rs | 38 +++++++++++++++++++ .../src/components/file_list/mod.rs | 11 +++++- .../src/components/file_upload/mod.rs | 27 ++++++------- .../src/components/icons/file.rs | 10 +++++ .../src/components/icons/folder.rs | 10 +++++ .../src/components/icons/git.rs | 10 +++++ .../src/components/icons/justfile.rs | 10 +++++ .../src/components/icons/markdown.rs | 10 +++++ .../src/components/icons/mod.rs | 15 ++++++++ .../src/components/icons/rust.rs | 11 ++++++ .../src/components/icons/toml.rs | 10 +++++ crates/file-explorer-ui/src/components/mod.rs | 1 + crates/file-explorer/src/lib.rs | 32 +++++++++++++--- 17 files changed, 227 insertions(+), 34 deletions(-) create mode 100644 crates/file-explorer-ui/src/components/file_list/entry_icon.rs create mode 100644 crates/file-explorer-ui/src/components/icons/file.rs create mode 100644 crates/file-explorer-ui/src/components/icons/folder.rs create mode 100644 crates/file-explorer-ui/src/components/icons/git.rs create mode 100644 crates/file-explorer-ui/src/components/icons/justfile.rs create mode 100644 crates/file-explorer-ui/src/components/icons/markdown.rs create mode 100644 crates/file-explorer-ui/src/components/icons/mod.rs create mode 100644 crates/file-explorer-ui/src/components/icons/rust.rs create mode 100644 crates/file-explorer-ui/src/components/icons/toml.rs diff --git a/crates/file-explorer-proto/src/lib.rs b/crates/file-explorer-proto/src/lib.rs index 29c639ae..dc6c99be 100644 --- a/crates/file-explorer-proto/src/lib.rs +++ b/crates/file-explorer-proto/src/lib.rs @@ -3,6 +3,17 @@ use std::cmp::Ordering; use chrono::{DateTime, Local}; use serde::{Deserialize, Serialize}; +#[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] +pub enum EntryType { + Directory, + File, + Git, + Justfile, + Markdown, + Rust, + Toml, +} + /// A Directory entry used to display a File Explorer's entry. /// This struct is directly related to the Handlebars template used /// to power the File Explorer's UI @@ -12,6 +23,7 @@ pub struct DirectoryEntry { pub is_dir: bool, pub size_bytes: u64, pub entry_path: String, + pub entry_type: EntryType, pub date_created: Option>, pub date_modified: Option>, } diff --git a/crates/file-explorer-ui/Trunk.toml b/crates/file-explorer-ui/Trunk.toml index b9aa0102..565ac05e 100644 --- a/crates/file-explorer-ui/Trunk.toml +++ b/crates/file-explorer-ui/Trunk.toml @@ -7,3 +7,7 @@ target = "./public/index.html" watch = ["./src"] # Paths to ignore. ignore = [] + +[[proxy]] +backend = "http://127.0.0.1:3000/api/v1" +rewrite = "/api/v1" diff --git a/crates/file-explorer-ui/src/api.rs b/crates/file-explorer-ui/src/api.rs index da7a77ed..1db7b30b 100644 --- a/crates/file-explorer-ui/src/api.rs +++ b/crates/file-explorer-ui/src/api.rs @@ -3,6 +3,7 @@ use gloo::utils::window; use reqwest::Url; use file_explorer_proto::DirectoryIndex; +use web_sys::FormData; pub struct Api { base_url: Url, @@ -21,4 +22,15 @@ impl Api { Ok(index) } + + pub async fn upload(&self, form_data: FormData) -> Result<()> { + let url = self.base_url.join("api/v1")?; + + gloo::net::http::Request::post(url.as_ref()) + .body(form_data)? + .send() + .await?; + + Ok(()) + } } diff --git a/crates/file-explorer-ui/src/components/file_list/entry.rs b/crates/file-explorer-ui/src/components/file_list/entry.rs index a104dc9e..bdd9f7e9 100644 --- a/crates/file-explorer-ui/src/components/file_list/entry.rs +++ b/crates/file-explorer-ui/src/components/file_list/entry.rs @@ -1,23 +1,39 @@ +use chrono::{DateTime, Local}; use leptos::{component, view, IntoView}; +use file_explorer_proto::EntryType; + +use super::entry_icon::EntryIcon; + #[component] -pub fn Entry(#[prop(into)] name: String) -> impl IntoView { +pub fn Entry( + #[prop(into)] name: String, + #[prop(into)] size: u64, + #[prop(into)] entry_type: EntryType, + #[prop(into)] date_created: Option>, + #[prop(into)] date_modified: Option>, +) -> impl IntoView { + let format_date_or_default = |date: Option>| { + date.map(|d| d.format("%Y-%m-%d %H:%M:%S").to_string()) + .unwrap_or_else(|| "Unknown".to_string()) + }; + view! { - - - + + + - + {name} - - "128.9 MB" + + {size} - - "2024-10-11" + + {format_date_or_default(date_created)} - - "2024-10-11" + + {format_date_or_default(date_modified)} } diff --git a/crates/file-explorer-ui/src/components/file_list/entry_icon.rs b/crates/file-explorer-ui/src/components/file_list/entry_icon.rs new file mode 100644 index 00000000..1664941d --- /dev/null +++ b/crates/file-explorer-ui/src/components/file_list/entry_icon.rs @@ -0,0 +1,38 @@ +use leptos::{component, view, IntoView}; + +use file_explorer_proto::EntryType; + +use crate::components::icons::{File, Folder, Git, Justfile, Markdown, Rust, Toml}; + +#[component] +pub fn EntryIcon(#[prop(into)] entry_type: EntryType) -> impl IntoView { + let icon = match entry_type { + EntryType::Directory => view! { + + }, + EntryType::Git => view! { + + }, + EntryType::Justfile => view! { + + }, + EntryType::Markdown => view! { + + }, + EntryType::Rust => view! { + + }, + EntryType::Toml => view! { + + }, + _ => view! { + + }, + }; + + view! { +
+ {icon} +
+ } +} diff --git a/crates/file-explorer-ui/src/components/file_list/mod.rs b/crates/file-explorer-ui/src/components/file_list/mod.rs index 396ebbe5..ec471c6c 100644 --- a/crates/file-explorer-ui/src/components/file_list/mod.rs +++ b/crates/file-explorer-ui/src/components/file_list/mod.rs @@ -1,4 +1,5 @@ mod entry; +mod entry_icon; use leptos::{component, view, For, IntoView, Signal, SignalGet}; @@ -13,7 +14,7 @@ pub fn FileList(#[prop(into)] entries: Signal>) -> impl Into - @@ -34,7 +35,13 @@ pub fn FileList(#[prop(into)] entries: Signal>) -> impl Into key=|counter| counter.entry_path.clone() children=move |dir_entry: DirectoryEntry| { view! { - + } } /> diff --git a/crates/file-explorer-ui/src/components/file_upload/mod.rs b/crates/file-explorer-ui/src/components/file_upload/mod.rs index 57693353..982ae5c9 100644 --- a/crates/file-explorer-ui/src/components/file_upload/mod.rs +++ b/crates/file-explorer-ui/src/components/file_upload/mod.rs @@ -4,6 +4,8 @@ use leptos::wasm_bindgen::JsCast; use leptos::{component, create_action, create_node_ref, html, view, IntoView}; use web_sys::{Event, FormData, HtmlInputElement}; +use crate::api::Api; + #[component] pub fn FileUpload() -> impl IntoView { let file_input_el = create_node_ref::(); @@ -14,23 +16,18 @@ pub fn FileUpload() -> impl IntoView { form_data.append_with_blob("file", &file).unwrap(); async move { - log!("Uploading file..."); - - let response = gloo::net::http::Request::post(&format!( - "{}/api/v1", - &window().location().origin().unwrap() - )) - .body(form_data) - .unwrap() // result can't be error - .send() - .await; - - match response { + match Api::new().upload(form_data).await { Ok(_) => { - log!("File successfully uploaded!"); + log!("File uploaded successfully"); + window() + .alert_with_message("File uploaded successfully") + .unwrap(); } - Err(err) => { - log!("Error uploading file: {}", err); + Err(e) => { + log!("Failed to upload file: {:?}", e); + window() + .alert_with_message("Failed to upload file") + .unwrap(); } } } diff --git a/crates/file-explorer-ui/src/components/icons/file.rs b/crates/file-explorer-ui/src/components/icons/file.rs new file mode 100644 index 00000000..528bfa8c --- /dev/null +++ b/crates/file-explorer-ui/src/components/icons/file.rs @@ -0,0 +1,10 @@ +use leptos::{component, view, IntoView}; + +#[component] +pub fn File() -> impl IntoView { + view! { + + + + } +} diff --git a/crates/file-explorer-ui/src/components/icons/folder.rs b/crates/file-explorer-ui/src/components/icons/folder.rs new file mode 100644 index 00000000..a16cff48 --- /dev/null +++ b/crates/file-explorer-ui/src/components/icons/folder.rs @@ -0,0 +1,10 @@ +use leptos::{component, view, IntoView}; + +#[component] +pub fn Folder() -> impl IntoView { + view! { + + + + } +} diff --git a/crates/file-explorer-ui/src/components/icons/git.rs b/crates/file-explorer-ui/src/components/icons/git.rs new file mode 100644 index 00000000..abb24830 --- /dev/null +++ b/crates/file-explorer-ui/src/components/icons/git.rs @@ -0,0 +1,10 @@ +use leptos::{component, view, IntoView}; + +#[component] +pub fn Git() -> impl IntoView { + view! { + + + + } +} diff --git a/crates/file-explorer-ui/src/components/icons/justfile.rs b/crates/file-explorer-ui/src/components/icons/justfile.rs new file mode 100644 index 00000000..fc756682 --- /dev/null +++ b/crates/file-explorer-ui/src/components/icons/justfile.rs @@ -0,0 +1,10 @@ +use leptos::{component, view, IntoView}; + +#[component] +pub fn Justfile() -> impl IntoView { + view! { + + + + } +} diff --git a/crates/file-explorer-ui/src/components/icons/markdown.rs b/crates/file-explorer-ui/src/components/icons/markdown.rs new file mode 100644 index 00000000..1f599a29 --- /dev/null +++ b/crates/file-explorer-ui/src/components/icons/markdown.rs @@ -0,0 +1,10 @@ +use leptos::{component, view, IntoView}; + +#[component] +pub fn Markdown() -> impl IntoView { + view! { + + + + } +} diff --git a/crates/file-explorer-ui/src/components/icons/mod.rs b/crates/file-explorer-ui/src/components/icons/mod.rs new file mode 100644 index 00000000..fac2709b --- /dev/null +++ b/crates/file-explorer-ui/src/components/icons/mod.rs @@ -0,0 +1,15 @@ +mod file; +mod folder; +mod git; +mod justfile; +mod markdown; +mod rust; +mod toml; + +pub use file::File; +pub use folder::Folder; +pub use git::Git; +pub use justfile::Justfile; +pub use markdown::Markdown; +pub use rust::Rust; +pub use toml::Toml; diff --git a/crates/file-explorer-ui/src/components/icons/rust.rs b/crates/file-explorer-ui/src/components/icons/rust.rs new file mode 100644 index 00000000..ca56eafe --- /dev/null +++ b/crates/file-explorer-ui/src/components/icons/rust.rs @@ -0,0 +1,11 @@ +use leptos::{component, view, IntoView}; + +#[component] +pub fn Rust() -> impl IntoView { + view! { + + + + + } +} diff --git a/crates/file-explorer-ui/src/components/icons/toml.rs b/crates/file-explorer-ui/src/components/icons/toml.rs new file mode 100644 index 00000000..14cf7fb9 --- /dev/null +++ b/crates/file-explorer-ui/src/components/icons/toml.rs @@ -0,0 +1,10 @@ +use leptos::{component, view, IntoView}; + +#[component] +pub fn Toml() -> impl IntoView { + view! { + + + + } +} diff --git a/crates/file-explorer-ui/src/components/mod.rs b/crates/file-explorer-ui/src/components/mod.rs index b7d1631d..564bbbd2 100644 --- a/crates/file-explorer-ui/src/components/mod.rs +++ b/crates/file-explorer-ui/src/components/mod.rs @@ -1,2 +1,3 @@ pub mod file_list; pub mod file_upload; +pub mod icons; diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index f4a2084e..0c0a38ad 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -21,7 +21,7 @@ use tokio::io::AsyncWriteExt; use tokio::runtime::Handle; use file_explorer_core::{Entry, FileExplorer}; -use file_explorer_proto::{BreadcrumbItem, DirectoryEntry, DirectoryIndex, Sort}; +use file_explorer_proto::{BreadcrumbItem, DirectoryEntry, DirectoryIndex, EntryType, Sort}; use file_explorer_ui::Assets; use http_server_plugin::config::read_from_path; use http_server_plugin::{export_plugin, Function, InvocationError, PluginRegistrar}; @@ -316,26 +316,46 @@ impl FileExplorerPlugin { for entry in entries { let entry = entry.context("Unable to read entry")?; let metadata = entry.metadata()?; + + let display_name = entry + .file_name() + .to_str() + .context("Unable to gather file name into a String")? + .to_string(); + let date_created = if let Ok(time) = metadata.created() { Some(time.into()) } else { None }; + let date_modified = if let Ok(time) = metadata.modified() { Some(time.into()) } else { None }; + let entry_type = if metadata.is_dir() { + EntryType::Directory + } else if let Some(ext) = display_name.split(".").last() { + match ext.to_ascii_lowercase().as_str() { + "gitignore" | "gitkeep" => EntryType::Git, + "justfile" => EntryType::Justfile, + "md" => EntryType::Markdown, + "rs" => EntryType::Rust, + "toml" => EntryType::Toml, + _ => EntryType::File, + } + } else { + EntryType::File + }; + directory_entries.push(DirectoryEntry { - display_name: entry - .file_name() - .to_str() - .context("Unable to gather file name into a String")? - .to_string(), is_dir: metadata.is_dir(), size_bytes: metadata.len(), entry_path: Self::make_dir_entry_link(&root_dir, &entry.path()), + display_name, + entry_type, date_created, date_modified, }); From 790f91a8c9c3ec02fbf1b070cfc9019846b7d5c7 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Tue, 5 Nov 2024 20:00:36 -0300 Subject: [PATCH 35/40] feat: navigation --- Justfile | 3 +++ crates/file-explorer-ui/src/api.rs | 5 +++-- .../src/components/file_list/entry.rs | 5 ++++- .../file-explorer-ui/src/components/file_list/mod.rs | 1 + crates/file-explorer-ui/src/lib.rs | 11 ++++++++++- crates/file-explorer/src/lib.rs | 9 +++++---- 6 files changed, 26 insertions(+), 8 deletions(-) diff --git a/Justfile b/Justfile index 60b2e1bb..e5ee366c 100644 --- a/Justfile +++ b/Justfile @@ -21,3 +21,6 @@ ui-dev: # Builds File Explorer UI for Production ui-build: cd ./crates/file-explorer-ui && trunk build --release --locked --config ./Trunk.toml + +dev: ui-build + cargo b --all && cargo r diff --git a/crates/file-explorer-ui/src/api.rs b/crates/file-explorer-ui/src/api.rs index 1db7b30b..9b8292ca 100644 --- a/crates/file-explorer-ui/src/api.rs +++ b/crates/file-explorer-ui/src/api.rs @@ -1,9 +1,9 @@ use anyhow::Result; use gloo::utils::window; use reqwest::Url; +use web_sys::FormData; use file_explorer_proto::DirectoryIndex; -use web_sys::FormData; pub struct Api { base_url: Url, @@ -17,7 +17,8 @@ impl Api { } pub async fn peek(&self, path: &str) -> Result { - let url = self.base_url.join(&format!("api/v1/{path}"))?; + let path = path.strip_prefix("/").unwrap(); + let url = self.base_url.join(&format!("/api/v1/{path}"))?; let index = reqwest::get(url).await?.json::().await?; Ok(index) diff --git a/crates/file-explorer-ui/src/components/file_list/entry.rs b/crates/file-explorer-ui/src/components/file_list/entry.rs index bdd9f7e9..3026e169 100644 --- a/crates/file-explorer-ui/src/components/file_list/entry.rs +++ b/crates/file-explorer-ui/src/components/file_list/entry.rs @@ -10,6 +10,7 @@ pub fn Entry( #[prop(into)] name: String, #[prop(into)] size: u64, #[prop(into)] entry_type: EntryType, + #[prop(into)] entry_path: String, #[prop(into)] date_created: Option>, #[prop(into)] date_modified: Option>, ) -> impl IntoView { @@ -24,7 +25,9 @@ pub fn Entry( diff --git a/crates/file-explorer-ui/src/components/templates/file_list/mod.rs b/crates/file-explorer-ui/src/components/templates/file_list/mod.rs index fb4a26c1..3a0db79e 100644 --- a/crates/file-explorer-ui/src/components/templates/file_list/mod.rs +++ b/crates/file-explorer-ui/src/components/templates/file_list/mod.rs @@ -37,8 +37,9 @@ pub fn FileList(#[prop(into)] entries: Signal>) -> impl Into children=move |dir_entry: DirectoryEntry| { view! { Date: Thu, 14 Nov 2024 21:37:40 -0300 Subject: [PATCH 40/40] feat: breadcrumb depth --- crates/file-explorer-proto/src/lib.rs | 1 + .../src/components/atoms/icons/download.rs | 6 +-- .../src/components/atoms/icons/house.rs | 10 +++++ .../src/components/atoms/icons/mod.rs | 2 + .../components/organisms/navigation_bar.rs | 27 +++++++++--- .../templates/file_list/download_button.rs | 2 +- .../components/templates/file_list/entry.rs | 42 ++++++++++++++----- crates/file-explorer/src/lib.rs | 2 + 8 files changed, 71 insertions(+), 21 deletions(-) create mode 100644 crates/file-explorer-ui/src/components/atoms/icons/house.rs diff --git a/crates/file-explorer-proto/src/lib.rs b/crates/file-explorer-proto/src/lib.rs index dc6c99be..67ba90f5 100644 --- a/crates/file-explorer-proto/src/lib.rs +++ b/crates/file-explorer-proto/src/lib.rs @@ -61,6 +61,7 @@ impl PartialEq for DirectoryEntry { /// A Breadcrumb Item used to navigate to previous path components #[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)] pub struct BreadcrumbItem { + pub depth: u8, pub entry_name: String, pub entry_link: String, } diff --git a/crates/file-explorer-ui/src/components/atoms/icons/download.rs b/crates/file-explorer-ui/src/components/atoms/icons/download.rs index 16f5e430..3fe17f10 100644 --- a/crates/file-explorer-ui/src/components/atoms/icons/download.rs +++ b/crates/file-explorer-ui/src/components/atoms/icons/download.rs @@ -1,9 +1,9 @@ -use leptos::{component, view, IntoView}; +use leptos::{component, view, IntoView, TextProp}; #[component] -pub fn Download() -> impl IntoView { +pub fn Download(#[prop(into, optional)] class: TextProp) -> impl IntoView { view! { - + } diff --git a/crates/file-explorer-ui/src/components/atoms/icons/house.rs b/crates/file-explorer-ui/src/components/atoms/icons/house.rs new file mode 100644 index 00000000..d44a3642 --- /dev/null +++ b/crates/file-explorer-ui/src/components/atoms/icons/house.rs @@ -0,0 +1,10 @@ +use leptos::{component, view, IntoView, TextProp}; + +#[component] +pub fn House(#[prop(into, optional)] class: TextProp) -> impl IntoView { + view! { + + + + } +} diff --git a/crates/file-explorer-ui/src/components/atoms/icons/mod.rs b/crates/file-explorer-ui/src/components/atoms/icons/mod.rs index c5b1862b..c4fe0d16 100644 --- a/crates/file-explorer-ui/src/components/atoms/icons/mod.rs +++ b/crates/file-explorer-ui/src/components/atoms/icons/mod.rs @@ -2,6 +2,7 @@ mod download; mod file; mod folder; mod git; +mod house; mod justfile; mod markdown; mod rust; @@ -11,6 +12,7 @@ pub use download::Download; pub use file::File; pub use folder::Folder; pub use git::Git; +pub use house::House; pub use justfile::Justfile; pub use markdown::Markdown; pub use rust::Rust; diff --git a/crates/file-explorer-ui/src/components/organisms/navigation_bar.rs b/crates/file-explorer-ui/src/components/organisms/navigation_bar.rs index 8557e357..61d157e4 100644 --- a/crates/file-explorer-ui/src/components/organisms/navigation_bar.rs +++ b/crates/file-explorer-ui/src/components/organisms/navigation_bar.rs @@ -2,21 +2,36 @@ use leptos::{component, view, For, IntoView, Signal, SignalGet}; use file_explorer_proto::BreadcrumbItem; +use crate::components::atoms::icons::House; + #[component] pub fn NavigationBar(#[prop(into)] breadcrumbs: Signal>) -> impl IntoView { view! {
@@ -29,15 +57,7 @@ pub fn Entry(
+ "Name" - {name} + + {name} + {size} diff --git a/crates/file-explorer-ui/src/components/file_list/mod.rs b/crates/file-explorer-ui/src/components/file_list/mod.rs index ec471c6c..35288d3b 100644 --- a/crates/file-explorer-ui/src/components/file_list/mod.rs +++ b/crates/file-explorer-ui/src/components/file_list/mod.rs @@ -39,6 +39,7 @@ pub fn FileList(#[prop(into)] entries: Signal>) -> impl Into name={dir_entry.display_name} size={dir_entry.size_bytes} entry_type={dir_entry.entry_type} + entry_path={dir_entry.entry_path} date_created={dir_entry.date_created} date_modified={dir_entry.date_modified} /> diff --git a/crates/file-explorer-ui/src/lib.rs b/crates/file-explorer-ui/src/lib.rs index f2e48605..f1976eb3 100644 --- a/crates/file-explorer-ui/src/lib.rs +++ b/crates/file-explorer-ui/src/lib.rs @@ -7,6 +7,7 @@ use leptos::{ }; use leptos_meta::provide_meta_context; use rust_embed::Embed; +use gloo::utils::window; use file_explorer_proto::DirectoryIndex; @@ -31,7 +32,15 @@ pub fn App() -> impl IntoView { spawn_local(async move { leptos::logging::warn!("Performing a request to the server"); - let index = Api::new().peek("").await.unwrap(); + let Ok(pathname) = window().location().pathname() else { + leptos::logging::error!("Failed to get the pathname"); + return; + }; + + let Ok(index) = Api::new().peek(&pathname).await else { + leptos::logging::error!("Failed to fetch the directory index"); + return; + }; index_setter.set(Some(index)); }); diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 0c0a38ad..650c6c33 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -110,14 +110,15 @@ impl FileExplorerPlugin { return Ok(response); } - let mut response = Response::new(Full::new(Bytes::default())); + let index = Assets::get("index.html").unwrap(); + let body = Full::new(Bytes::from(index.data.to_vec())); + let mut response = Response::new(body); let mut headers = response.headers().clone(); - headers.append(LOCATION, "/index.html".try_into().unwrap()); + headers.append(CONTENT_TYPE, "text/html".try_into().unwrap()); *response.headers_mut() = headers; - *response.status_mut() = StatusCode::TEMPORARY_REDIRECT; - Ok(response) + return Ok(response); } } From c4be3d9d2ea9c8493fa0808376c1345bb7b226f0 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Wed, 6 Nov 2024 19:57:09 -0300 Subject: [PATCH 36/40] feat: capability to download files --- Cargo.lock | 1 + Cargo.toml | 1 + crates/file-explorer-core/src/fs/file.rs | 7 +++ crates/file-explorer-ui/Cargo.toml | 1 + crates/file-explorer-ui/src/api.rs | 21 ++++++++- .../components/file_list/download_button.rs | 46 +++++++++++++++++++ .../src/components/file_list/entry.rs | 16 +++++-- .../src/components/file_list/mod.rs | 1 + .../src/components/icons/download.rs | 10 ++++ .../src/components/icons/mod.rs | 2 + crates/file-explorer-ui/src/lib.rs | 2 +- crates/file-explorer/src/lib.rs | 14 ++++-- 12 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 crates/file-explorer-ui/src/components/file_list/download_button.rs create mode 100644 crates/file-explorer-ui/src/components/icons/download.rs diff --git a/Cargo.lock b/Cargo.lock index e04da48d..fc55de5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -576,6 +576,7 @@ dependencies = [ "chrono", "file-explorer-proto", "gloo", + "gloo-file", "leptos", "leptos_meta", "leptos_router", diff --git a/Cargo.toml b/Cargo.toml index 766c4afa..6b2781e0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,7 @@ chrono = "0.4.38" clap = "4.5.20" futures = "0.3.31" gloo = "0.11.0" +gloo-file = "0.3.0" humansize = "2.1.3" http = "1.1.0" http-auth-basic = "0.3.3" diff --git a/crates/file-explorer-core/src/fs/file.rs b/crates/file-explorer-core/src/fs/file.rs index 22c1cf22..ce229b14 100644 --- a/crates/file-explorer-core/src/fs/file.rs +++ b/crates/file-explorer-core/src/fs/file.rs @@ -1,5 +1,6 @@ use anyhow::{Context, Result}; use chrono::{DateTime, Local}; +use tokio::io::AsyncReadExt; use std::fs::Metadata; use std::mem::MaybeUninit; @@ -46,4 +47,10 @@ impl File { Ok(modified) } + + pub async fn bytes(&mut self) -> Result> { + let mut buf = Vec::with_capacity(self.size() as usize); + self.file.read_to_end(&mut buf).await?; + Ok(buf) + } } diff --git a/crates/file-explorer-ui/Cargo.toml b/crates/file-explorer-ui/Cargo.toml index 50bf3223..d25b4395 100644 --- a/crates/file-explorer-ui/Cargo.toml +++ b/crates/file-explorer-ui/Cargo.toml @@ -18,6 +18,7 @@ path = "src/bin/main.rs" anyhow = { workspace = true } chrono = { workspace = true, features = ["serde"] } gloo = { workspace = true } +gloo-file = { workspace = true } leptos = { workspace = true, features = ["csr"] } leptos_meta = { workspace = true, features = ["csr"] } leptos_router = { workspace = true, features = ["csr"] } diff --git a/crates/file-explorer-ui/src/api.rs b/crates/file-explorer-ui/src/api.rs index 9b8292ca..4ac2bd21 100644 --- a/crates/file-explorer-ui/src/api.rs +++ b/crates/file-explorer-ui/src/api.rs @@ -1,10 +1,15 @@ use anyhow::Result; use gloo::utils::window; -use reqwest::Url; +use reqwest::{header::CONTENT_TYPE, Url}; use web_sys::FormData; use file_explorer_proto::DirectoryIndex; +pub struct FileDownload { + pub bytes: Vec, + pub mime: String, +} + pub struct Api { base_url: Url, } @@ -34,4 +39,18 @@ impl Api { Ok(()) } + + pub async fn download(&self, path: &String) -> Result { + let path = path.strip_prefix("/").unwrap(); + let url = self.base_url.join(&format!("/api/v1/{path}"))?; + let res = reqwest::get(url).await?; + let headers = res.headers(); + let mime = headers + .get(CONTENT_TYPE) + .map(|hv| hv.to_str().unwrap().to_string()) + .unwrap_or("application/octet-stream".to_string()); + let bytes = res.bytes().await?.to_vec(); + + Ok(FileDownload { bytes, mime }) + } } diff --git a/crates/file-explorer-ui/src/components/file_list/download_button.rs b/crates/file-explorer-ui/src/components/file_list/download_button.rs new file mode 100644 index 00000000..0d68b59e --- /dev/null +++ b/crates/file-explorer-ui/src/components/file_list/download_button.rs @@ -0,0 +1,46 @@ +use gloo_file::{Blob, ObjectUrl}; +use leptos::{component, create_node_ref, html::A, spawn_local, view, IntoView}; + +use crate::{ + api::{Api, FileDownload}, + components::icons::Download, +}; + +#[component] +pub fn DownloadButton(#[prop(into)] entry_path: String) -> impl IntoView { + let anchor_ref = create_node_ref::(); + let download_file = { + move |_: _| { + let entry_path = entry_path.clone(); + spawn_local(async move { + let api = Api::new(); + match api.download(&entry_path).await { + Ok(FileDownload { bytes, mime }) => { + let blob = Blob::new_with_options(bytes.as_slice(), Some(&mime)); + + let object_url = ObjectUrl::from(blob); + + if let Some(anchor_el) = anchor_ref.get_untracked() { + anchor_el.set_href(&object_url); + anchor_el.set_download(&entry_path); + anchor_el.click(); + } + } + Err(err) => { + leptos::logging::error!("Failed to download file: {:?}", err); + } + } + }); + } + }; + + view! { + +
- + - - {name} - + + + {name} + + + + + {size} diff --git a/crates/file-explorer-ui/src/components/file_list/mod.rs b/crates/file-explorer-ui/src/components/file_list/mod.rs index 35288d3b..ad2ca220 100644 --- a/crates/file-explorer-ui/src/components/file_list/mod.rs +++ b/crates/file-explorer-ui/src/components/file_list/mod.rs @@ -1,3 +1,4 @@ +mod download_button; mod entry; mod entry_icon; diff --git a/crates/file-explorer-ui/src/components/icons/download.rs b/crates/file-explorer-ui/src/components/icons/download.rs new file mode 100644 index 00000000..16f5e430 --- /dev/null +++ b/crates/file-explorer-ui/src/components/icons/download.rs @@ -0,0 +1,10 @@ +use leptos::{component, view, IntoView}; + +#[component] +pub fn Download() -> impl IntoView { + view! { + + + + } +} diff --git a/crates/file-explorer-ui/src/components/icons/mod.rs b/crates/file-explorer-ui/src/components/icons/mod.rs index fac2709b..c5b1862b 100644 --- a/crates/file-explorer-ui/src/components/icons/mod.rs +++ b/crates/file-explorer-ui/src/components/icons/mod.rs @@ -1,3 +1,4 @@ +mod download; mod file; mod folder; mod git; @@ -6,6 +7,7 @@ mod markdown; mod rust; mod toml; +pub use download::Download; pub use file::File; pub use folder::Folder; pub use git::Git; diff --git a/crates/file-explorer-ui/src/lib.rs b/crates/file-explorer-ui/src/lib.rs index f1976eb3..bd3eeb18 100644 --- a/crates/file-explorer-ui/src/lib.rs +++ b/crates/file-explorer-ui/src/lib.rs @@ -2,12 +2,12 @@ mod api; mod components; use components::file_upload::FileUpload; +use gloo::utils::window; use leptos::{ component, create_memo, create_signal, spawn_local, view, IntoView, SignalGet, SignalSet, }; use leptos_meta::provide_meta_context; use rust_embed::Embed; -use gloo::utils::window; use file_explorer_proto::DirectoryIndex; diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 650c6c33..4e5e8ae1 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -7,7 +7,6 @@ use std::sync::Arc; use anyhow::{Context, Result}; use async_trait::async_trait; -use http::header::LOCATION; use http::request::Parts; use http::HeaderValue; use http_body_util::Full; @@ -118,7 +117,7 @@ impl FileExplorerPlugin { headers.append(CONTENT_TYPE, "text/html".try_into().unwrap()); *response.headers_mut() = headers; - return Ok(response); + Ok(response) } } @@ -145,7 +144,16 @@ impl FileExplorerPlugin { Ok(response) } - Entry::File(_) => Ok(Response::new(Full::new(Bytes::from("Found but WIP")))), + Entry::File(mut file) => { + let body = Full::new(Bytes::from(file.bytes().await.unwrap())); + let mut response = Response::new(body); + let mut headers = response.headers().clone(); + + headers.append(CONTENT_TYPE, file.mime().to_string().try_into().unwrap()); + *response.headers_mut() = headers; + + Ok(response) + } }, Err(err) => { let message = format!("Failed to resolve path: {}", err); From 3fce43b384733a7ac8dafbc7e3aefe36eaa75178 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sat, 9 Nov 2024 21:38:40 -0300 Subject: [PATCH 37/40] feat: action bar --- .../mod.rs => action_bar/file_upload.rs} | 0 .../src/components/action_bar/mod.rs | 14 ++++++++++++++ .../src/components/file_list/mod.rs | 2 +- crates/file-explorer-ui/src/components/mod.rs | 2 +- crates/file-explorer-ui/src/lib.rs | 4 ++-- 5 files changed, 18 insertions(+), 4 deletions(-) rename crates/file-explorer-ui/src/components/{file_upload/mod.rs => action_bar/file_upload.rs} (100%) create mode 100644 crates/file-explorer-ui/src/components/action_bar/mod.rs diff --git a/crates/file-explorer-ui/src/components/file_upload/mod.rs b/crates/file-explorer-ui/src/components/action_bar/file_upload.rs similarity index 100% rename from crates/file-explorer-ui/src/components/file_upload/mod.rs rename to crates/file-explorer-ui/src/components/action_bar/file_upload.rs diff --git a/crates/file-explorer-ui/src/components/action_bar/mod.rs b/crates/file-explorer-ui/src/components/action_bar/mod.rs new file mode 100644 index 00000000..352a7d2d --- /dev/null +++ b/crates/file-explorer-ui/src/components/action_bar/mod.rs @@ -0,0 +1,14 @@ +mod file_upload; + +use leptos::{component, view, IntoView}; + +use self::file_upload::FileUpload; + +#[component] +pub fn ActionBar() -> impl IntoView { + view! { +
+ +
+ } +} diff --git a/crates/file-explorer-ui/src/components/file_list/mod.rs b/crates/file-explorer-ui/src/components/file_list/mod.rs index ad2ca220..4d565c9b 100644 --- a/crates/file-explorer-ui/src/components/file_list/mod.rs +++ b/crates/file-explorer-ui/src/components/file_list/mod.rs @@ -11,7 +11,7 @@ use self::entry::Entry; #[component] pub fn FileList(#[prop(into)] entries: Signal>) -> impl IntoView { view! { -
+
diff --git a/crates/file-explorer-ui/src/components/mod.rs b/crates/file-explorer-ui/src/components/mod.rs index 564bbbd2..0c012b10 100644 --- a/crates/file-explorer-ui/src/components/mod.rs +++ b/crates/file-explorer-ui/src/components/mod.rs @@ -1,3 +1,3 @@ +pub mod action_bar; pub mod file_list; -pub mod file_upload; pub mod icons; diff --git a/crates/file-explorer-ui/src/lib.rs b/crates/file-explorer-ui/src/lib.rs index bd3eeb18..5d417d44 100644 --- a/crates/file-explorer-ui/src/lib.rs +++ b/crates/file-explorer-ui/src/lib.rs @@ -1,7 +1,6 @@ mod api; mod components; -use components::file_upload::FileUpload; use gloo::utils::window; use leptos::{ component, create_memo, create_signal, spawn_local, view, IntoView, SignalGet, SignalSet, @@ -12,6 +11,7 @@ use rust_embed::Embed; use file_explorer_proto::DirectoryIndex; use self::api::Api; +use self::components::action_bar::ActionBar; use self::components::file_list::FileList; #[derive(Embed)] @@ -47,7 +47,7 @@ pub fn App() -> impl IntoView { view! {
- +
} From a105047c51ba8bec3cf03a7cbd7aedf3c48ed0d1 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Sun, 10 Nov 2024 14:08:11 -0300 Subject: [PATCH 38/40] feat: breadcrumbs --- .../src/components/action_bar/mod.rs | 14 ----- .../src/components/atoms/button.rs | 17 +++++++ .../components/{ => atoms}/icons/download.rs | 0 .../src/components/{ => atoms}/icons/file.rs | 0 .../components/{ => atoms}/icons/folder.rs | 0 .../src/components/{ => atoms}/icons/git.rs | 0 .../components/{ => atoms}/icons/justfile.rs | 0 .../components/{ => atoms}/icons/markdown.rs | 0 .../src/components/{ => atoms}/icons/mod.rs | 0 .../src/components/{ => atoms}/icons/rust.rs | 0 .../src/components/{ => atoms}/icons/toml.rs | 0 .../src/components/atoms/mod.rs | 2 + crates/file-explorer-ui/src/components/mod.rs | 8 +-- .../{action_bar => molecules}/file_upload.rs | 10 ++++ .../src/components/molecules/mod.rs | 1 + .../src/components/organisms/action_bar.rs | 12 +++++ .../src/components/organisms/mod.rs | 2 + .../components/organisms/navigation_bar.rs | 27 ++++++++++ .../src/components/pages/explorer.rs | 51 +++++++++++++++++++ .../src/components/pages/mod.rs | 1 + .../file_list/download_button.rs | 6 +-- .../{ => templates}/file_list/entry.rs | 0 .../{ => templates}/file_list/entry_icon.rs | 2 +- .../{ => templates}/file_list/mod.rs | 30 +++++------ .../src/components/templates/mod.rs | 1 + crates/file-explorer-ui/src/lib.rs | 39 ++------------ 26 files changed, 150 insertions(+), 73 deletions(-) delete mode 100644 crates/file-explorer-ui/src/components/action_bar/mod.rs create mode 100644 crates/file-explorer-ui/src/components/atoms/button.rs rename crates/file-explorer-ui/src/components/{ => atoms}/icons/download.rs (100%) rename crates/file-explorer-ui/src/components/{ => atoms}/icons/file.rs (100%) rename crates/file-explorer-ui/src/components/{ => atoms}/icons/folder.rs (100%) rename crates/file-explorer-ui/src/components/{ => atoms}/icons/git.rs (100%) rename crates/file-explorer-ui/src/components/{ => atoms}/icons/justfile.rs (100%) rename crates/file-explorer-ui/src/components/{ => atoms}/icons/markdown.rs (100%) rename crates/file-explorer-ui/src/components/{ => atoms}/icons/mod.rs (100%) rename crates/file-explorer-ui/src/components/{ => atoms}/icons/rust.rs (100%) rename crates/file-explorer-ui/src/components/{ => atoms}/icons/toml.rs (100%) create mode 100644 crates/file-explorer-ui/src/components/atoms/mod.rs rename crates/file-explorer-ui/src/components/{action_bar => molecules}/file_upload.rs (85%) create mode 100644 crates/file-explorer-ui/src/components/molecules/mod.rs create mode 100644 crates/file-explorer-ui/src/components/organisms/action_bar.rs create mode 100644 crates/file-explorer-ui/src/components/organisms/mod.rs create mode 100644 crates/file-explorer-ui/src/components/organisms/navigation_bar.rs create mode 100644 crates/file-explorer-ui/src/components/pages/explorer.rs create mode 100644 crates/file-explorer-ui/src/components/pages/mod.rs rename crates/file-explorer-ui/src/components/{ => templates}/file_list/download_button.rs (94%) rename crates/file-explorer-ui/src/components/{ => templates}/file_list/entry.rs (100%) rename crates/file-explorer-ui/src/components/{ => templates}/file_list/entry_icon.rs (89%) rename crates/file-explorer-ui/src/components/{ => templates}/file_list/mod.rs (62%) create mode 100644 crates/file-explorer-ui/src/components/templates/mod.rs diff --git a/crates/file-explorer-ui/src/components/action_bar/mod.rs b/crates/file-explorer-ui/src/components/action_bar/mod.rs deleted file mode 100644 index 352a7d2d..00000000 --- a/crates/file-explorer-ui/src/components/action_bar/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod file_upload; - -use leptos::{component, view, IntoView}; - -use self::file_upload::FileUpload; - -#[component] -pub fn ActionBar() -> impl IntoView { - view! { -
- -
- } -} diff --git a/crates/file-explorer-ui/src/components/atoms/button.rs b/crates/file-explorer-ui/src/components/atoms/button.rs new file mode 100644 index 00000000..6b8fbdc4 --- /dev/null +++ b/crates/file-explorer-ui/src/components/atoms/button.rs @@ -0,0 +1,17 @@ +use leptos::{component, view, Children, IntoView}; + +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)] +pub enum ButtonVariant { + Primary, + #[default] + Secondary, +} + +#[component] +pub fn Button(children: Children) -> impl IntoView { + view! { + + } +} diff --git a/crates/file-explorer-ui/src/components/icons/download.rs b/crates/file-explorer-ui/src/components/atoms/icons/download.rs similarity index 100% rename from crates/file-explorer-ui/src/components/icons/download.rs rename to crates/file-explorer-ui/src/components/atoms/icons/download.rs diff --git a/crates/file-explorer-ui/src/components/icons/file.rs b/crates/file-explorer-ui/src/components/atoms/icons/file.rs similarity index 100% rename from crates/file-explorer-ui/src/components/icons/file.rs rename to crates/file-explorer-ui/src/components/atoms/icons/file.rs diff --git a/crates/file-explorer-ui/src/components/icons/folder.rs b/crates/file-explorer-ui/src/components/atoms/icons/folder.rs similarity index 100% rename from crates/file-explorer-ui/src/components/icons/folder.rs rename to crates/file-explorer-ui/src/components/atoms/icons/folder.rs diff --git a/crates/file-explorer-ui/src/components/icons/git.rs b/crates/file-explorer-ui/src/components/atoms/icons/git.rs similarity index 100% rename from crates/file-explorer-ui/src/components/icons/git.rs rename to crates/file-explorer-ui/src/components/atoms/icons/git.rs diff --git a/crates/file-explorer-ui/src/components/icons/justfile.rs b/crates/file-explorer-ui/src/components/atoms/icons/justfile.rs similarity index 100% rename from crates/file-explorer-ui/src/components/icons/justfile.rs rename to crates/file-explorer-ui/src/components/atoms/icons/justfile.rs diff --git a/crates/file-explorer-ui/src/components/icons/markdown.rs b/crates/file-explorer-ui/src/components/atoms/icons/markdown.rs similarity index 100% rename from crates/file-explorer-ui/src/components/icons/markdown.rs rename to crates/file-explorer-ui/src/components/atoms/icons/markdown.rs diff --git a/crates/file-explorer-ui/src/components/icons/mod.rs b/crates/file-explorer-ui/src/components/atoms/icons/mod.rs similarity index 100% rename from crates/file-explorer-ui/src/components/icons/mod.rs rename to crates/file-explorer-ui/src/components/atoms/icons/mod.rs diff --git a/crates/file-explorer-ui/src/components/icons/rust.rs b/crates/file-explorer-ui/src/components/atoms/icons/rust.rs similarity index 100% rename from crates/file-explorer-ui/src/components/icons/rust.rs rename to crates/file-explorer-ui/src/components/atoms/icons/rust.rs diff --git a/crates/file-explorer-ui/src/components/icons/toml.rs b/crates/file-explorer-ui/src/components/atoms/icons/toml.rs similarity index 100% rename from crates/file-explorer-ui/src/components/icons/toml.rs rename to crates/file-explorer-ui/src/components/atoms/icons/toml.rs diff --git a/crates/file-explorer-ui/src/components/atoms/mod.rs b/crates/file-explorer-ui/src/components/atoms/mod.rs new file mode 100644 index 00000000..36207d34 --- /dev/null +++ b/crates/file-explorer-ui/src/components/atoms/mod.rs @@ -0,0 +1,2 @@ +pub mod button; +pub mod icons; diff --git a/crates/file-explorer-ui/src/components/mod.rs b/crates/file-explorer-ui/src/components/mod.rs index 0c012b10..5bcd2fee 100644 --- a/crates/file-explorer-ui/src/components/mod.rs +++ b/crates/file-explorer-ui/src/components/mod.rs @@ -1,3 +1,5 @@ -pub mod action_bar; -pub mod file_list; -pub mod icons; +pub mod atoms; +pub mod molecules; +pub mod organisms; +pub mod templates; +pub mod pages; diff --git a/crates/file-explorer-ui/src/components/action_bar/file_upload.rs b/crates/file-explorer-ui/src/components/molecules/file_upload.rs similarity index 85% rename from crates/file-explorer-ui/src/components/action_bar/file_upload.rs rename to crates/file-explorer-ui/src/components/molecules/file_upload.rs index 982ae5c9..ac3072fc 100644 --- a/crates/file-explorer-ui/src/components/action_bar/file_upload.rs +++ b/crates/file-explorer-ui/src/components/molecules/file_upload.rs @@ -5,6 +5,7 @@ use leptos::{component, create_action, create_node_ref, html, view, IntoView}; use web_sys::{Event, FormData, HtmlInputElement}; use crate::api::Api; +use crate::components::atoms::button::Button; #[component] pub fn FileUpload() -> impl IntoView { @@ -33,10 +34,19 @@ pub fn FileUpload() -> impl IntoView { } }); + let handle_button_click = { + let file_input_el = file_input_el.clone(); + move |_| { + file_input_el.get_untracked().unwrap().click(); + } + }; + view! {
+ (); diff --git a/crates/file-explorer-ui/src/components/molecules/mod.rs b/crates/file-explorer-ui/src/components/molecules/mod.rs new file mode 100644 index 00000000..8737079a --- /dev/null +++ b/crates/file-explorer-ui/src/components/molecules/mod.rs @@ -0,0 +1 @@ +pub mod file_upload; diff --git a/crates/file-explorer-ui/src/components/organisms/action_bar.rs b/crates/file-explorer-ui/src/components/organisms/action_bar.rs new file mode 100644 index 00000000..4641fed6 --- /dev/null +++ b/crates/file-explorer-ui/src/components/organisms/action_bar.rs @@ -0,0 +1,12 @@ +use leptos::{component, view, IntoView}; + +use crate::components::molecules::file_upload::FileUpload; + +#[component] +pub fn ActionBar() -> impl IntoView { + view! { +
+ +
+ } +} diff --git a/crates/file-explorer-ui/src/components/organisms/mod.rs b/crates/file-explorer-ui/src/components/organisms/mod.rs new file mode 100644 index 00000000..7e547cd1 --- /dev/null +++ b/crates/file-explorer-ui/src/components/organisms/mod.rs @@ -0,0 +1,2 @@ +pub mod action_bar; +pub mod navigation_bar; diff --git a/crates/file-explorer-ui/src/components/organisms/navigation_bar.rs b/crates/file-explorer-ui/src/components/organisms/navigation_bar.rs new file mode 100644 index 00000000..8557e357 --- /dev/null +++ b/crates/file-explorer-ui/src/components/organisms/navigation_bar.rs @@ -0,0 +1,27 @@ +use leptos::{component, view, For, IntoView, Signal, SignalGet}; + +use file_explorer_proto::BreadcrumbItem; + +#[component] +pub fn NavigationBar(#[prop(into)] breadcrumbs: Signal>) -> impl IntoView { + view! { +
+ +
+ } +} diff --git a/crates/file-explorer-ui/src/components/pages/explorer.rs b/crates/file-explorer-ui/src/components/pages/explorer.rs new file mode 100644 index 00000000..23768b17 --- /dev/null +++ b/crates/file-explorer-ui/src/components/pages/explorer.rs @@ -0,0 +1,51 @@ +use gloo::utils::window; +use leptos::{ + component, create_memo, create_read_slice, create_signal, create_slice, spawn_local, view, IntoView, SignalGet, SignalSet +}; + +use file_explorer_proto::DirectoryIndex; + +use crate::api::Api; +use crate::components::organisms::action_bar::ActionBar; +use crate::components::organisms::navigation_bar::NavigationBar; +use crate::components::templates::file_list::FileList; + +#[component] +pub fn Explorer() -> impl IntoView { + let (index_getter, index_setter) = create_signal::>(None); + let entries = create_memo(move |_| { + index_getter + .get() + .map(|index| index.entries.clone()) + .unwrap_or_default() + }); + let breadcrumbs = create_memo(move |_| { + index_getter + .get() + .map(|index| index.breadcrumbs.clone()) + .unwrap_or_default() + }); + + spawn_local(async move { + leptos::logging::warn!("Performing a request to the server"); + let Ok(pathname) = window().location().pathname() else { + leptos::logging::error!("Failed to get the pathname"); + return; + }; + + let Ok(index) = Api::new().peek(&pathname).await else { + leptos::logging::error!("Failed to fetch the directory index"); + return; + }; + + index_setter.set(Some(index)); + }); + + view! { +
+ + + +
+ } +} diff --git a/crates/file-explorer-ui/src/components/pages/mod.rs b/crates/file-explorer-ui/src/components/pages/mod.rs new file mode 100644 index 00000000..fcb0baea --- /dev/null +++ b/crates/file-explorer-ui/src/components/pages/mod.rs @@ -0,0 +1 @@ +pub mod explorer; diff --git a/crates/file-explorer-ui/src/components/file_list/download_button.rs b/crates/file-explorer-ui/src/components/templates/file_list/download_button.rs similarity index 94% rename from crates/file-explorer-ui/src/components/file_list/download_button.rs rename to crates/file-explorer-ui/src/components/templates/file_list/download_button.rs index 0d68b59e..603d45ef 100644 --- a/crates/file-explorer-ui/src/components/file_list/download_button.rs +++ b/crates/file-explorer-ui/src/components/templates/file_list/download_button.rs @@ -1,10 +1,8 @@ use gloo_file::{Blob, ObjectUrl}; use leptos::{component, create_node_ref, html::A, spawn_local, view, IntoView}; -use crate::{ - api::{Api, FileDownload}, - components::icons::Download, -}; +use crate::api::{Api, FileDownload}; +use crate::components::atoms::icons::Download; #[component] pub fn DownloadButton(#[prop(into)] entry_path: String) -> impl IntoView { diff --git a/crates/file-explorer-ui/src/components/file_list/entry.rs b/crates/file-explorer-ui/src/components/templates/file_list/entry.rs similarity index 100% rename from crates/file-explorer-ui/src/components/file_list/entry.rs rename to crates/file-explorer-ui/src/components/templates/file_list/entry.rs diff --git a/crates/file-explorer-ui/src/components/file_list/entry_icon.rs b/crates/file-explorer-ui/src/components/templates/file_list/entry_icon.rs similarity index 89% rename from crates/file-explorer-ui/src/components/file_list/entry_icon.rs rename to crates/file-explorer-ui/src/components/templates/file_list/entry_icon.rs index 1664941d..571c6c92 100644 --- a/crates/file-explorer-ui/src/components/file_list/entry_icon.rs +++ b/crates/file-explorer-ui/src/components/templates/file_list/entry_icon.rs @@ -2,7 +2,7 @@ use leptos::{component, view, IntoView}; use file_explorer_proto::EntryType; -use crate::components::icons::{File, Folder, Git, Justfile, Markdown, Rust, Toml}; +use crate::components::atoms::icons::{File, Folder, Git, Justfile, Markdown, Rust, Toml}; #[component] pub fn EntryIcon(#[prop(into)] entry_type: EntryType) -> impl IntoView { diff --git a/crates/file-explorer-ui/src/components/file_list/mod.rs b/crates/file-explorer-ui/src/components/templates/file_list/mod.rs similarity index 62% rename from crates/file-explorer-ui/src/components/file_list/mod.rs rename to crates/file-explorer-ui/src/components/templates/file_list/mod.rs index 4d565c9b..fb4a26c1 100644 --- a/crates/file-explorer-ui/src/components/file_list/mod.rs +++ b/crates/file-explorer-ui/src/components/templates/file_list/mod.rs @@ -31,22 +31,22 @@ pub fn FileList(#[prop(into)] entries: Signal>) -> impl Into
- + + } } - } - /> + />
diff --git a/crates/file-explorer-ui/src/components/templates/mod.rs b/crates/file-explorer-ui/src/components/templates/mod.rs new file mode 100644 index 00000000..e108de48 --- /dev/null +++ b/crates/file-explorer-ui/src/components/templates/mod.rs @@ -0,0 +1 @@ +pub mod file_list; diff --git a/crates/file-explorer-ui/src/lib.rs b/crates/file-explorer-ui/src/lib.rs index 5d417d44..86e87a51 100644 --- a/crates/file-explorer-ui/src/lib.rs +++ b/crates/file-explorer-ui/src/lib.rs @@ -1,18 +1,11 @@ mod api; mod components; -use gloo::utils::window; -use leptos::{ - component, create_memo, create_signal, spawn_local, view, IntoView, SignalGet, SignalSet, -}; +use leptos::{component, view, IntoView, SignalGet}; use leptos_meta::provide_meta_context; use rust_embed::Embed; -use file_explorer_proto::DirectoryIndex; - -use self::api::Api; -use self::components::action_bar::ActionBar; -use self::components::file_list::FileList; +use crate::components::pages::explorer::Explorer; #[derive(Embed)] #[folder = "public/dist"] @@ -22,33 +15,7 @@ pub struct Assets; pub fn App() -> impl IntoView { provide_meta_context(); - let (index_getter, index_setter) = create_signal::>(None); - let entries = create_memo(move |_| { - index_getter - .get() - .map(|index| index.entries.clone()) - .unwrap_or_default() - }); - - spawn_local(async move { - leptos::logging::warn!("Performing a request to the server"); - let Ok(pathname) = window().location().pathname() else { - leptos::logging::error!("Failed to get the pathname"); - return; - }; - - let Ok(index) = Api::new().peek(&pathname).await else { - leptos::logging::error!("Failed to fetch the directory index"); - return; - }; - - index_setter.set(Some(index)); - }); - view! { -
- - -
+ } } From a576093da0315595c1c7daca35c0ae2c141f19a9 Mon Sep 17 00:00:00 2001 From: Esteban Borai Date: Tue, 12 Nov 2024 21:36:00 -0300 Subject: [PATCH 39/40] fix: files being treated as dirs --- crates/file-explorer-ui/src/components/mod.rs | 2 +- .../src/components/molecules/file_upload.rs | 2 +- .../file-explorer-ui/src/components/pages/explorer.rs | 2 +- .../components/templates/file_list/download_button.rs | 10 +++++++--- .../src/components/templates/file_list/entry.rs | 9 +++++++-- .../src/components/templates/file_list/mod.rs | 3 ++- crates/file-explorer/src/lib.rs | 2 +- 7 files changed, 20 insertions(+), 10 deletions(-) diff --git a/crates/file-explorer-ui/src/components/mod.rs b/crates/file-explorer-ui/src/components/mod.rs index 5bcd2fee..8f818fdb 100644 --- a/crates/file-explorer-ui/src/components/mod.rs +++ b/crates/file-explorer-ui/src/components/mod.rs @@ -1,5 +1,5 @@ pub mod atoms; pub mod molecules; pub mod organisms; -pub mod templates; pub mod pages; +pub mod templates; diff --git a/crates/file-explorer-ui/src/components/molecules/file_upload.rs b/crates/file-explorer-ui/src/components/molecules/file_upload.rs index ac3072fc..7f8a2d06 100644 --- a/crates/file-explorer-ui/src/components/molecules/file_upload.rs +++ b/crates/file-explorer-ui/src/components/molecules/file_upload.rs @@ -35,7 +35,7 @@ pub fn FileUpload() -> impl IntoView { }); let handle_button_click = { - let file_input_el = file_input_el.clone(); + let file_input_el = file_input_el; move |_| { file_input_el.get_untracked().unwrap().click(); } diff --git a/crates/file-explorer-ui/src/components/pages/explorer.rs b/crates/file-explorer-ui/src/components/pages/explorer.rs index 23768b17..b239feda 100644 --- a/crates/file-explorer-ui/src/components/pages/explorer.rs +++ b/crates/file-explorer-ui/src/components/pages/explorer.rs @@ -1,6 +1,6 @@ use gloo::utils::window; use leptos::{ - component, create_memo, create_read_slice, create_signal, create_slice, spawn_local, view, IntoView, SignalGet, SignalSet + component, create_memo, create_signal, spawn_local, view, IntoView, SignalGet, SignalSet, }; use file_explorer_proto::DirectoryIndex; diff --git a/crates/file-explorer-ui/src/components/templates/file_list/download_button.rs b/crates/file-explorer-ui/src/components/templates/file_list/download_button.rs index 603d45ef..d54c585d 100644 --- a/crates/file-explorer-ui/src/components/templates/file_list/download_button.rs +++ b/crates/file-explorer-ui/src/components/templates/file_list/download_button.rs @@ -5,22 +5,26 @@ use crate::api::{Api, FileDownload}; use crate::components::atoms::icons::Download; #[component] -pub fn DownloadButton(#[prop(into)] entry_path: String) -> impl IntoView { +pub fn DownloadButton( + #[prop(into)] entry_path: String, + #[prop(into)] download_name: String, +) -> impl IntoView { let anchor_ref = create_node_ref::(); let download_file = { move |_: _| { let entry_path = entry_path.clone(); + let download_name = download_name.clone(); + spawn_local(async move { let api = Api::new(); match api.download(&entry_path).await { Ok(FileDownload { bytes, mime }) => { let blob = Blob::new_with_options(bytes.as_slice(), Some(&mime)); - let object_url = ObjectUrl::from(blob); if let Some(anchor_el) = anchor_ref.get_untracked() { anchor_el.set_href(&object_url); - anchor_el.set_download(&entry_path); + anchor_el.set_download(&download_name); anchor_el.click(); } } diff --git a/crates/file-explorer-ui/src/components/templates/file_list/entry.rs b/crates/file-explorer-ui/src/components/templates/file_list/entry.rs index 105dd15a..a74a5966 100644 --- a/crates/file-explorer-ui/src/components/templates/file_list/entry.rs +++ b/crates/file-explorer-ui/src/components/templates/file_list/entry.rs @@ -10,11 +10,13 @@ use super::entry_icon::EntryIcon; pub fn Entry( #[prop(into)] name: String, #[prop(into)] size: u64, + #[prop(into)] is_dir: bool, #[prop(into)] entry_type: EntryType, #[prop(into)] entry_path: String, #[prop(into)] date_created: Option>, #[prop(into)] date_modified: Option>, ) -> impl IntoView { + let download_name = name.clone(); let format_date_or_default = |date: Option>| { date.map(|d| d.format("%Y-%m-%d %H:%M:%S").to_string()) .unwrap_or_else(|| "Unknown".to_string()) @@ -30,8 +32,11 @@ pub fn Entry( {name} - - + +
- - {name} - - - - + {render_name()} diff --git a/crates/file-explorer/src/lib.rs b/crates/file-explorer/src/lib.rs index 0fed6377..cf05394f 100644 --- a/crates/file-explorer/src/lib.rs +++ b/crates/file-explorer/src/lib.rs @@ -279,6 +279,7 @@ impl FileExplorerPlugin { .iter() .enumerate() .map(|(idx, entry_name)| BreadcrumbItem { + depth: (idx + 1) as u8, entry_name: percent_decode_str(entry_name) .decode_utf8() .expect("The path name is not UTF-8 compliant") @@ -290,6 +291,7 @@ impl FileExplorerPlugin { breadcrumbs.insert( 0, BreadcrumbItem { + depth: 0, entry_name: String::from(root_dir_name), entry_link: String::from("/"), },