diff --git a/Cargo.lock b/Cargo.lock index c9065a5..87805ea 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,144 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "async-channel" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" +dependencies = [ + "concurrent-queue", + "event-listener", + "futures-core", +] + +[[package]] +name = "async-executor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871f9bb5e0a22eeb7e8cf16641feb87c9dc67032ccf8ff49e772eb9941d3a965" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "once_cell", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c290043c9a95b05d45e952fb6383c67bcb61471f60cfa21e890dba6654234f43" +dependencies = [ + "async-channel", + "async-executor", + "async-io", + "async-mutex", + "blocking", + "futures-lite", + "num_cpus", + "once_cell", +] + +[[package]] +name = "async-io" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a811e6a479f2439f0c04038796b5cfb3d2ad56c230e0f2d3f7b04d68cfee607b" +dependencies = [ + "concurrent-queue", + "futures-lite", + "libc", + "log", + "once_cell", + "parking", + "polling", + "slab", + "socket2", + "waker-fn", + "winapi", +] + +[[package]] +name = "async-lock" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e97a171d191782fba31bb902b14ad94e24a68145032b7eedf871ab0bc0d077b6" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-mutex" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479db852db25d9dbf6204e6cb6253698f175c15726470f78af0d918e99d6156e" +dependencies = [ + "event-listener", +] + +[[package]] +name = "async-object-pool" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aeb901c30ebc2fc4ab46395bbfbdba9542c16559d853645d75190c3056caf3bc" +dependencies = [ + "async-std", +] + +[[package]] +name = "async-process" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83137067e3a2a6a06d67168e49e68a0957d215410473a740cea95a2425c0b7c6" +dependencies = [ + "async-io", + "blocking", + "cfg-if", + "event-listener", + "futures-lite", + "libc", + "once_cell", + "signal-hook", + "winapi", +] + +[[package]] +name = "async-std" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52580991739c5cdb36cde8b2a516371c0a3b70dda36d916cc08b82372916808c" +dependencies = [ + "async-channel", + "async-global-executor", + "async-io", + "async-lock", + "async-process", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "num_cpus", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30696a84d817107fc028e049980e09d5e140e8da8f1caeb17e8e950658a3cea9" + [[package]] name = "async-trait" version = "0.1.53" @@ -57,6 +195,12 @@ dependencies = [ "syn", ] +[[package]] +name = "atomic-waker" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "065374052e7df7ee4047b1160cca5e1467a12351a40b3da123c870ba0b8eda2a" + [[package]] name = "autocfg" version = "1.1.0" @@ -102,6 +246,20 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6ccb65d468978a086b69884437ded69a90faab3bbe6e67f242173ea728acccc" +dependencies = [ + "async-channel", + "async-task", + "atomic-waker", + "fastrand", + "futures-lite", + "once_cell", +] + [[package]] name = "buf_redux" version = "0.8.4" @@ -112,6 +270,12 @@ dependencies = [ "safemem", ] +[[package]] +name = "bumpalo" +version = "3.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" + [[package]] name = "byteorder" version = "1.4.3" @@ -124,6 +288,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" +[[package]] +name = "cache-padded" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1db59621ec70f09c5e9b597b220c7a2b43611f4710dc03ceb8748637775692c" + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" + [[package]] name = "cfg-if" version = "1.0.0" @@ -137,13 +313,34 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50b727aacc797f9fc28e355d21f34709ac4fc9adecfe470ad07b8f4464f53062" dependencies = [ "bytes", - "futures-core", "memchr", - "pin-project-lite", - "tokio", - "tokio-util", ] +[[package]] +name = "concurrent-queue" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" +dependencies = [ + "cache-padded", +] + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + [[package]] name = "cow-utils" version = "0.1.2" @@ -159,6 +356,16 @@ dependencies = [ "libc", ] +[[package]] +name = "crossbeam-utils" +version = "0.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" +dependencies = [ + "cfg-if", + "lazy_static", +] + [[package]] name = "crypto-common" version = "0.1.3" @@ -169,6 +376,16 @@ dependencies = [ "typenum", ] +[[package]] +name = "ctor" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "data-encoding" version = "2.3.2" @@ -232,6 +449,12 @@ dependencies = [ "syn", ] +[[package]] +name = "event-listener" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77f3309417938f28bf8228fcff79a4a37103981e3e186d2ccd19c74b38f4eb71" + [[package]] name = "fastrand" version = "1.7.0" @@ -247,6 +470,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.0.1" @@ -273,6 +511,27 @@ version = "0.3.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" +[[package]] +name = "futures-io" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" + +[[package]] +name = "futures-lite" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "memchr", + "parking", + "pin-project-lite", + "waker-fn", +] + [[package]] name = "futures-sink" version = "0.3.21" @@ -320,6 +579,18 @@ dependencies = [ "wasi 0.10.2+wasi-snapshot-preview1", ] +[[package]] +name = "gloo-timers" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fb7d06c1c8cc2a29bee7ec961009a0b2caa0793ee4900c2ffb348734ba1c8f9" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "h2" version = "0.3.12" @@ -497,6 +768,24 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" +[[package]] +name = "js-sys" +version = "0.3.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "671a26f820db17c2a2750743f1dd03bafd15b98c9f30c7c2628c024c05d73397" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -544,6 +833,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" dependencies = [ "cfg-if", + "value-bag", ] [[package]] @@ -551,7 +841,9 @@ name = "lonk" version = "0.1.0" dependencies = [ "argh", + "async-object-pool", "base64", + "rand", "redis", "serde", "serde_json", @@ -638,6 +930,24 @@ dependencies = [ "twoway", ] +[[package]] +name = "native-tls" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "nom" version = "5.1.2" @@ -716,6 +1026,45 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" +[[package]] +name = "openssl" +version = "0.10.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-sys", +] + +[[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.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" + [[package]] name = "parking_lot" version = "0.12.0" @@ -797,6 +1146,25 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +[[package]] +name = "pkg-config" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df8c4ec4b0627e53bdf214615ad287367e482558cf84b109250b37464dc03ae" + +[[package]] +name = "polling" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685404d509889fade3e86fe3a5803bca2ec09b0c0778d5ada6ec8bf7a8de5259" +dependencies = [ + "cfg-if", + "libc", + "log", + "wepoll-ffi", + "winapi", +] + [[package]] name = "ppv-lite86" version = "0.2.16" @@ -873,16 +1241,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1a80b5f38d7f5a020856a0e16e40a9cfabf88ae8f0e4c2dcd8a3114c1e470852" dependencies = [ "async-trait", - "bytes", "combine", "dtoa", - "futures-util", "itoa 0.4.8", + "native-tls", "percent-encoding", - "pin-project-lite", "sha1", - "tokio", - "tokio-util", + "tokio-native-tls", "url", ] @@ -954,6 +1319,16 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" +[[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 = "scoped-tls" version = "1.0.0" @@ -966,6 +1341,29 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "security-framework" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc" +dependencies = [ + "bitflags", + "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 = "semver" version = "1.0.6" @@ -1054,6 +1452,16 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" +[[package]] +name = "signal-hook" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "647c97df271007dcea485bb74ffdb57f2e683f1306c854f468a0c244badabf2d" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -1192,6 +1600,16 @@ dependencies = [ "syn", ] +[[package]] +name = "tokio-native-tls" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" +dependencies = [ + "native-tls", + "tokio", +] + [[package]] name = "tokio-stream" version = "0.1.8" @@ -1396,12 +1814,34 @@ dependencies = [ "enum-ordinalize", ] +[[package]] +name = "value-bag" +version = "1.0.0-alpha.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" +dependencies = [ + "ctor", + "version_check", +] + +[[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.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "waker-fn" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" + [[package]] name = "want" version = "0.3.0" @@ -1454,6 +1894,91 @@ 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.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27370197c907c55e3f1a9fbe26f44e937fe6451368324e009cba39e139dc08ad" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53e04185bfa3a779273da532f5025e33398409573f348985af9a1cbf3774d3f4" +dependencies = [ + "bumpalo", + "lazy_static", + "log", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f741de44b75e14c35df886aff5f1eb73aa114fa5d4d00dcd37b5e01259bf3b2" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17cae7ff784d7e83a2fe7611cfe766ecf034111b49deb850a3dc7699c08251f5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99ec0dc7a4756fffc231aab1b9f2f578d23cd391390ab27f952ae0c9b3ece20b" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d554b7f530dee5964d9a9468d95c1f8b8acae4f282807e7d27d4b03099a46744" + +[[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 = "wepoll-ffi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" +dependencies = [ + "cc", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/Cargo.toml b/Cargo.toml index cbadf58..c217ed9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,11 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -argh = "0.1.7" -base64 = "0.13.0" -redis = { version = "~0.21.5", features = ["tokio-comp"] } +argh = "~0.1.7" +async-object-pool = "~0.1.4" +base64 = "~0.13.0" +rand = "~0.8.5" +redis = { version = "~0.21.5", features = ["tokio-native-tls-comp"] } serde = { version = "~1.0.136", features = ["derive"] } serde_json = "1.0.79" tokio = { version = "~1.17.0", features = ["full"] } validators = { version = "~0.24.1", features = ["url-dep"] } -warp = "0.3.2" +warp = "~0.3.2" diff --git a/data/config.json b/data/config.json index 4448c16..166ac11 100644 --- a/data/config.json +++ b/data/config.json @@ -1,7 +1,7 @@ { "db": { "address": "redis://redis:6379", - "worker_threads": 4 + "expire_seconds": 259200 }, "slug_rules": { "length": 5, diff --git a/src/main.rs b/src/main.rs index bca6f87..9b7b5bc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,8 @@ use argh::FromArgs; +use async_object_pool::Pool; use core::panic; +use rand::prelude::*; +use redis::Commands; use serde::{Deserialize, Serialize}; use std::{ collections::BTreeSet, io::BufRead, net::IpAddr, path::PathBuf, str::FromStr, sync::Arc, @@ -8,28 +11,6 @@ use tokio::sync; use validators::prelude::*; use warp::{filters::BoxedFilter, hyper::StatusCode, Filter}; -macro_rules! unwrap_or_unwrap_err { - ($x:expr) => { - match $x { - Ok(x) => x, - Err(y) => y, - } - }; -} - -macro_rules! clone_to_move { - ($($y:ident),+$x:ident) => { - clone_to_move!($x); - clone_to_move!($y) - }; - (mut $x:ident) => { - let mut $x = $x.clone(); - }; - ($x:ident) => { - let $x = $x.clone(); - }; -} - #[derive(Serialize, Deserialize, Debug, Validator, Clone)] #[validator(domain(ipv4(Allow), local(Allow), at_least_two_labels(Allow), port(Allow)))] struct Url { @@ -37,17 +18,26 @@ struct Url { port: Option, } +impl ToString for Url { + fn to_string(&self) -> String { + match self.port { + Some(port) => format!("{}:{}", self.domain, port), + None => self.domain.clone(), + } + } +} + #[derive(Deserialize, Serialize, Debug, Clone)] struct DbConfig { address: String, - worker_threads: usize, + expire_seconds: usize, } impl Default for DbConfig { fn default() -> Self { Self { address: "redis://127.0.0.1:6379".to_string(), - worker_threads: 4, + expire_seconds: 259200, // 3 days } } } @@ -154,49 +144,158 @@ struct SlugDatabase { } #[derive(Clone, Debug)] +enum AddResult { + Success(Slug), + Fail, +} + +#[derive(Clone, Debug)] +enum GetResult { + Found(Url), + NotFound, + InternalError, +} + enum SlugDbMessage { - Add(Slug, Url), + Add(Slug, Url, sync::oneshot::Sender), + Get(Slug, sync::oneshot::Sender), +} + +impl core::fmt::Debug for SlugDbMessage { + fn fmt(&self, f: &mut validators_prelude::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Add(arg0, arg1, _) => f + .debug_tuple("Add") + .field(arg0) + .field(arg1) + .field(&"oneshot::Sender") + .finish(), + SlugDbMessage::Get(arg0, _) => f + .debug_tuple("Get") + .field(arg0) + .field(&"oneshot::Sender") + .finish(), + } + } } impl SlugDatabase { - fn from_client(client: redis::Client, worker_threads: usize) -> Self { - let (tx, rx) = sync::mpsc::unbounded_channel::(); + fn from_client(client: redis::Client, expire_seconds: usize) -> Self { + let (tx, mut rx) = sync::mpsc::unbounded_channel::(); - // I want a FILO queue with a spin lock for consumption. - // I'm not sure this is the best way to implement this. - // (Alternatively: is there a better architecture?) - let rx = Arc::new(sync::Mutex::new(rx)); + tokio::spawn(async move { + let pool = Arc::new(sync::Mutex::new(Pool::new(100))); - for _ in 0..worker_threads { - let mut connection = client - .get_connection() - .expect("Could not open connection to Redis server."); - clone_to_move!(rx); - tokio::spawn(async move { - while let Some(msg) = { (*rx.lock().await).recv().await } { + while let Some(msg) = { rx.recv().await } { + let mut connection = { + (*pool.lock().await) + .take_or_create(|| { + client + .get_connection() + .expect("Could not open connection to Redis server.") + }) + .await + }; + + let pool = pool.clone(); + tokio::spawn(async move { match msg { - SlugDbMessage::Add(slug, url) => { - todo!() + SlugDbMessage::Add(requested_slug, url, response_channel) => { + let url_str = url.to_string(); + // Check that the URL is not already present in the DB + // This is, to some extent, a protection against collision attacks. + match connection + .get::>(format!("url:{}", url_str)) + { + Ok(Some(slug)) => { + // The URL was already present, just return that. + response_channel.send(AddResult::Success(Slug(slug))).ok(); + return; + } + Err(_) => { + response_channel.send(AddResult::Fail).ok(); + return; + } + _ => {} // Ok(None); continue with insertion + }; + + // The URL is not present in the database; insert it. + let add_result = connection.set_ex::( + format!("slug:{}", requested_slug.0), + url_str.clone(), + expire_seconds, + ); + if add_result.is_ok() { + connection + .set_ex::( + format!("url:{}", url_str), + requested_slug.0.clone(), + expire_seconds, + ) + .ok(); // If this failed we have no way of correcting for it. + } + response_channel + .send(match add_result { + Ok(_) => AddResult::Success(requested_slug), + Err(_) => AddResult::Fail, + }) + .ok(); // If the receiver has hung up there's nothing we can do. + } + SlugDbMessage::Get(slug, response_channel) => { + let result: Result, _> = + connection.get(format!("slug:{}", slug.0)); + match result { + Ok(Some(url)) => response_channel.send(GetResult::Found( + Url::parse_string(url) + .expect("Mismatched URL in the database."), + )), + Ok(None) => response_channel.send(GetResult::NotFound), + Err(_) => response_channel.send(GetResult::InternalError), + } + .ok(); // If the receiver has hung up there's nothing we can do. } } - } - }); - } + + (*pool.lock().await).put(connection).await; + }); + } + }); SlugDatabase { tx } } - fn insert_slug(&self, slug: Slug, url: Url) -> Result<(), ()> { + fn insert_slug( + &self, + requested_slug: Slug, + url: Url, + ) -> Result, ()> { + let (tx, rx) = sync::oneshot::channel(); self.tx - .send(SlugDbMessage::Add(slug, url)) - .expect("Could not send message."); - Ok(()) + .send(SlugDbMessage::Add(requested_slug, url, tx)) + .expect("The SlugDbMessage channel is unexpectedly closed."); + Ok(rx) + } + + async fn get_slug(&self, slug: Slug) -> Result, ()> { + let (tx, rx) = sync::oneshot::channel(); + self.tx + .send(SlugDbMessage::Get(slug, tx)) + .expect("The SlugDbMessage channel is unexpectedly closed."); + match rx + .await + .expect("The query channel was unexpectedly dropped.") + { + GetResult::Found(url) => Ok(Some(url)), + GetResult::NotFound => Ok(None), + GetResult::InternalError => Err(()), + } } } struct SlugFactory { slug_length: usize, slug_chars: BTreeSet, + slug_chars_indexable: Vec, } #[derive(Clone, Debug)] @@ -215,6 +314,7 @@ impl SlugFactory { SlugFactory { slug_length: rules.length, slug_chars, + slug_chars_indexable: rules.chars.chars().collect(), } } @@ -233,15 +333,22 @@ impl SlugFactory { } fn generate(&self) -> Slug { - todo!() + // Generate indices then map + let distribution = rand::distributions::Uniform::new(0, self.slug_chars_indexable.len()); + let slug_str = distribution + .sample_iter(rand::thread_rng()) + .take(self.slug_length) + .map(|i| self.slug_chars_indexable[i]) + .collect::(); + Slug(slug_str) } } -fn shorten( +async fn shorten( slug_factory: &SlugFactory, db: &SlugDatabase, b64url: &str, -) -> Result { +) -> Result { let url = { let raw = base64::decode_config(b64url, base64::URL_SAFE_NO_PAD) .map_err(|_| warp::http::StatusCode::BAD_REQUEST)?; @@ -250,8 +357,17 @@ fn shorten( }; let new_slug = slug_factory.generate(); - - Ok(warp::http::StatusCode::OK) + let insert_result = db.insert_slug(new_slug, url); + match insert_result { + Ok(receiver) => match receiver.await { + Ok(result) => match result { + AddResult::Success(slug) => Ok(slug), + AddResult::Fail => Err(warp::http::StatusCode::INTERNAL_SERVER_ERROR), + }, + Err(_) => Err(warp::http::StatusCode::INTERNAL_SERVER_ERROR), + }, + Err(_) => Err(warp::http::StatusCode::INTERNAL_SERVER_ERROR), + } } #[tokio::main] @@ -303,7 +419,7 @@ async fn serve() { // Initialize database let db = { let client = redis::Client::open(config.db.address).expect("Error opening Redis database."); - Arc::new(SlugDatabase::from_client(client, config.db.worker_threads)) + Arc::new(SlugDatabase::from_client(client, config.db.expire_seconds)) }; // GET / @@ -311,11 +427,11 @@ async fn serve() { // GET /shorten/:Base64WithoutPaddingUrl let shorten = warp::path!("shorten" / Base64WithoutPaddingUrl).map({ - move |link: Base64WithoutPaddingUrl| { - warp::reply::with_status( - warp::reply(), - unwrap_or_unwrap_err!(shorten(&slug_factory, &db, &link.0)), - ) + move |link: Base64WithoutPaddingUrl| async { + match shorten(&slug_factory, &db, &link.0).await { + Ok(slug) => warp::redirect::found("/"), + Err(status) => warp::reply::with_status(warp::reply::reply(), status), + } } });