diff --git a/.dockerignore b/.dockerignore index c59e6784..19b1345e 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,16 +3,17 @@ # Include project code and build files !PluralKit.*/ -!gateway/ -!myriad_rs/ +!services/ +!lib/ !Myriad/ -!PluralKit.sln -!nuget.config !.git !proto -!scripts/run-clustered.sh !dashboard -!scheduled_tasks + +!Cargo.toml +!Cargo.lock +!PluralKit.sln +!nuget.config # Re-exclude host build artifact directories **/bin diff --git a/.github/workflows/api.yml b/.github/workflows/api.yml new file mode 100644 index 00000000..5b6746cd --- /dev/null +++ b/.github/workflows/api.yml @@ -0,0 +1,36 @@ +name: Build and push API Docker image +on: + push: + branches: + - main + - 'rust-api' + paths: + - 'lib/pklib/**' + - 'services/api/**' + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + packages: write + if: github.repository == 'PluralKit/PluralKit' + steps: + - uses: docker/login-action@v1 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.CR_PAT }} + - uses: actions/checkout@v2 + - run: echo "BRANCH_NAME=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV + - uses: docker/build-push-action@v2 + with: + # https://github.com/docker/build-push-action/issues/378 + context: . + file: services/api/Dockerfile + push: true + tags: | + ghcr.io/pluralkit/api:${{ env.BRANCH_NAME }} + ghcr.io/pluralkit/api:${{ github.sha }} + ghcr.io/pluralkit/api:latest + cache-from: type=registry,ref=ghcr.io/pluralkit/pluralkit:${{ env.BRANCH_NAME }} + cache-to: type=inline diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 00000000..5e23e382 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,1741 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + +[[package]] +name = "api" +version = "0.1.0" +dependencies = [ + "anyhow", + "axum", + "fred", + "http", + "hyper-reverse-proxy", + "lazy_static", + "libpk", + "tokio", + "tower", + "tracing", +] + +[[package]] +name = "arc-swap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bddcadddf5e9015d310179a59bb28c4d4b9920ad0f11e8e14dbadf654890c9a6" + +[[package]] +name = "arcstr" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f907281554a3d0312bb7aab855a8e0ef6cbf1614d06de54105039ca8b34460e" + +[[package]] +name = "async-trait" +version = "0.1.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "axum" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5694b64066a2459918d8074c2ce0d5a88f409431994c2356617c8ae0c4721fc" +dependencies = [ + "async-trait", + "axum-core", + "bitflags", + "bytes", + "futures-util", + "http", + "http-body", + "hyper", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cae3e661676ffbacb30f1a824089a8c9150e71017f7e1e38f2aa32009188d34" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "mime", + "rustversion", + "tower-layer", + "tower-service", +] + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "bytes-utils" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e47d3a8076e283f3acd27400535992edb3ba4b5bb72f8891ad8fbe7932a7d4b9" +dependencies = [ + "bytes", + "either", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "config" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d379af7f68bfc21714c6c7dea883544201741d2ce8274bb12fa54f89507f52a7" +dependencies = [ + "async-trait", + "json5", + "lazy_static", + "nom", + "pathdiff", + "ron", + "rust-ini", + "serde", + "serde_json", + "toml", + "yaml-rust", +] + +[[package]] +name = "cookie-factory" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396de984970346b0d9e93d1415082923c679e5ae5c3ee3dcbd104f5610af126b" + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crc16" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "338089f42c427b86394a5ee60ff321da23a5c89c9d89514c829687b26359fcff" + +[[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.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer 0.10.3", + "crypto-common", +] + +[[package]] +name = "dlv-list" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0688c2a7f92e427f44895cd63841bff7b29f8d7a1648b9e7e07a4a365b2e1257" + +[[package]] +name = "either" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "float-cmp" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1267f4ac4f343772758f7b1bdcbe767c218bbab93bb432acbf5162bbf85a6c4" +dependencies = [ + "num-traits", +] + +[[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.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "fred" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6be0137d9045288f9c0a0659da3b74c196ad0263d2eafa0f5a73785a907bad14" +dependencies = [ + "arc-swap", + "arcstr", + "async-trait", + "bytes", + "bytes-utils", + "cfg-if", + "float-cmp", + "futures", + "lazy_static", + "log", + "parking_lot 0.11.2", + "pretty_env_logger", + "rand", + "redis-protocol", + "semver", + "sha-1", + "tokio", + "tokio-stream", + "tokio-util 0.6.10", + "tracing", + "url", +] + +[[package]] +name = "futures" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13e2792b0ff0340399d58445b88fd9770e3489eff258a4cbc1523418f12abf84" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-executor" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" + +[[package]] +name = "futures-macro" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f310820bb3e8cfd46c80db4d7fb8353e15dfff853a127158425f31e0be6c8364" + +[[package]] +name = "futures-task" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" + +[[package]] +name = "futures-util" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a329e22866dd78b35d2c639a4a23d7b950aeae300dfd79f4fb19f74055c2404" +dependencies = [ + "libc", + "windows", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "h2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "futures-sink", + "futures-util", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util 0.7.6", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] + +[[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.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" +dependencies = [ + "bytes", + "http", + "pin-project-lite", +] + +[[package]] +name = "http-range-header" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" + +[[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.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "hyper" +version = "0.14.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c" +dependencies = [ + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", + "want", +] + +[[package]] +name = "hyper-reverse-proxy" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1af9b1b483fb9f33bd1cda26b35eacf902f0d116fcf0d56075ea5e5923b935" +dependencies = [ + "hyper", + "lazy_static", + "unicase", +] + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "json5" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96b0db21af676c1ce64250b5f40f3ce2cf27e4e47cb91ed91eb6fe9350b430c1" +dependencies = [ + "pest", + "pest_derive", + "serde", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "libpk" +version = "0.1.0" +dependencies = [ + "anyhow", + "config", + "gethostname", + "lazy_static", + "serde", + "tokio", + "tracing", + "tracing-gelf", + "tracing-subscriber", +] + +[[package]] +name = "linked-hash-map" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0717cef1bc8b636c6e1c1bbdefc09e6322da8a9321966e8928ef80d20f7f770f" + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.42.0", +] + +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi 0.2.6", + "libc", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "ordered-multimap" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccd746e37177e1711c20dd619a1620f34f5c8b569c53590a72dedd5344d8924a" +dependencies = [ + "dlv-list", + "hashbrown", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "parking_lot" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" +dependencies = [ + "instant", + "lock_api", + "parking_lot_core 0.8.6", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core 0.9.7", +] + +[[package]] +name = "parking_lot_core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc" +dependencies = [ + "cfg-if", + "instant", + "libc", + "redox_syscall", + "smallvec", + "winapi", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pest" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac3922aac69a40733080f53c1ce7f91dcf57e1a5f6c52f421fadec7fbdc4b69" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d06646e185566b5961b4058dd107e0a7f56e77c3f484549fb119867773c0f202" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6f60b2ba541577e2a0c307c8f39d1439108120eb7903adeb6497fa880c59616" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "pin-project" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro2" +version = "1.0.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d727cae5b39d21da60fa540906919ad737832fe0b1c165da3a34d6548c849d6" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "redis-protocol" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c31deddf734dc0a39d3112e73490e88b61a05e83e074d211f348404cee4d2c6" +dependencies = [ + "bytes", + "bytes-utils", + "cookie-factory", + "crc16", + "log", + "nom", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "ron" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88073939a61e5b7680558e6be56b419e208420c2adb92be54921fa6b72283f1a" +dependencies = [ + "base64", + "bitflags", + "serde", +] + +[[package]] +name = "rust-ini" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6d5f2436026b4f6e79dc829837d467cc7e9a55ee40e750d716713540715a2df" +dependencies = [ + "cfg-if", + "ordered-multimap", +] + +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cad406b69c91885b5107daf2c29572f6c8cdb3c66826821e286c533490c0bc76" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b04f22b563c91331a10074bda3dd5492e3cc39d56bd557e91c0af42b6c7341" +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 = "sha-1" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.6", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot 0.12.1", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.42.0", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.6.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36943ee01a6d67977dd3f84a5a1d2efeb4ada3a1ae771cadfaa535d9d9fc6507" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "log", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc6a3b08b64e6dfad376fa2432c7b1f01522e37a623c3050bc95db2d3ff21583" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", + "tracing", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[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", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" +dependencies = [ + "bitflags", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-range-header", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-gelf" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c0170f1bf67b749d4377c2da1d99d6e722600051ee53870cfb6f618611e29e" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "hostname", + "serde_json", + "thiserror", + "tokio", + "tokio-util 0.7.6", + "tracing-core", + "tracing-futures", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "try-lock" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[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-bidi" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "want" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" +dependencies = [ + "log", + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[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.43.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04662ed0e3e5630dfa9b26e4cb823b817f1a9addda855d973a9458c236556244" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "yaml-rust" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85" +dependencies = [ + "linked-hash-map", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 00000000..676f5e4e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,7 @@ +[workspace] +members = [ + "./lib/libpk", + "./services/api" +] + +# todo: add workspace dependencies here \ No newline at end of file diff --git a/dashboard/main.go b/dashboard/main.go index c8a8cab2..294fe6dc 100644 --- a/dashboard/main.go +++ b/dashboard/main.go @@ -3,9 +3,11 @@ package main import ( "embed" "encoding/json" + "errors" "fmt" "html" "io" + _fs "io/fs" "net/http" "strings" @@ -78,12 +80,21 @@ func notFoundHandler(rw http.ResponseWriter, r *http.Request) { data = []byte(strings.Replace(string(data), ``, defaultEmbed+versionJS, 1)) } - if err != nil { + if errors.Is(err, _fs.ErrNotExist) { + rw.WriteHeader(http.StatusNotFound) + } else if err != nil { rw.WriteHeader(http.StatusInternalServerError) - return - } + } else { + // cache built+hashed dashboard js/css files forever + is_dash_static_asset := strings.HasPrefix(r.URL.Path, "/assets/") && + (strings.HasSuffix(r.URL.Path, ".js") || strings.HasSuffix(r.URL.Path, ".css") || strings.HasSuffix(r.URL.Path, ".map")) - rw.Write(data) + if is_dash_static_asset { + rw.Header().Add("Cache-Control", "max-age=31536000, s-maxage=31536000, immutable") + } + + rw.Write(data) + } } // explanation for createEmbed: diff --git a/lib/libpk/Cargo.toml b/lib/libpk/Cargo.toml new file mode 100644 index 00000000..d0926993 --- /dev/null +++ b/lib/libpk/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "libpk" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.69" +config = "0.13.3" +gethostname = "0.4.1" +lazy_static = "1.4.0" +serde = "1.0.152" +tokio = { version = "1.25.0", features = ["full"] } +tracing = "0.1.37" +tracing-gelf = "0.7.1" +tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } diff --git a/lib/libpk/src/_config.rs b/lib/libpk/src/_config.rs new file mode 100644 index 00000000..ee2b48e1 --- /dev/null +++ b/lib/libpk/src/_config.rs @@ -0,0 +1,50 @@ +use config::Config; +use lazy_static::lazy_static; +use serde::Deserialize; +use std::sync::Arc; + +#[derive(Deserialize, Debug)] +pub struct DiscordConfig { + pub client_id: u32, + pub bot_token: String, + pub client_secret: String, +} + +#[derive(Deserialize, Debug)] +pub struct DatabaseConfig { + pub(crate) _data_db_uri: String, + pub(crate) _messages_db_uri: String, + pub(crate) _db_password: Option, + pub data_redis_addr: String, +} + +fn _default_api_addr() -> String { + "0.0.0.0:5000".to_string() +} + +#[derive(Deserialize, Debug)] +pub struct ApiConfig { + #[serde(default = "_default_api_addr")] + pub addr: String, + + #[serde(default)] + pub ratelimit_redis_addr: Option, + + pub remote_url: String, +} + +#[derive(Deserialize, Debug)] +pub struct PKConfig { + pub discord: DiscordConfig, + pub api: ApiConfig, + + pub(crate) gelf_log_url: Option, +} + +lazy_static! { + #[derive(Debug)] + pub static ref CONFIG: Arc = Arc::new(Config::builder() + .add_source(config::Environment::with_prefix("pluralkit").separator("__")) + .build().unwrap() + .try_deserialize::().unwrap()); +} diff --git a/lib/libpk/src/lib.rs b/lib/libpk/src/lib.rs new file mode 100644 index 00000000..785dedb4 --- /dev/null +++ b/lib/libpk/src/lib.rs @@ -0,0 +1,27 @@ +use gethostname::gethostname; +use tracing_subscriber::{prelude::__tracing_subscriber_SubscriberExt, EnvFilter, Registry}; + +mod _config; +pub use crate::_config::CONFIG as config; + +pub fn init_logging(component: &str) -> anyhow::Result<()> { + let subscriber = Registry::default() + .with(EnvFilter::from_default_env()) + .with(tracing_subscriber::fmt::layer()); + + if let Some(gelf_url) = &config.gelf_log_url { + let gelf_logger = tracing_gelf::Logger::builder() + .additional_field("component", component) + .additional_field("hostname", gethostname().to_str()); + let mut conn_handle = gelf_logger + .init_udp_with_subscriber(gelf_url, subscriber) + .unwrap(); + tokio::spawn(async move { conn_handle.connect().await }); + } else { + // gelf_logger internally sets the global subscriber + tracing::subscriber::set_global_default(subscriber) + .expect("unable to set global subscriber"); + } + + Ok(()) +} diff --git a/services/api/Cargo.toml b/services/api/Cargo.toml new file mode 100644 index 00000000..6aa6beff --- /dev/null +++ b/services/api/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "api" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1.0.69" +axum = "0.6.4" +fred = { version = "5.2.0", default-features = false, features = ["tracing", "pool-prefer-active"] } +http = "0.2.8" +hyper-reverse-proxy = "0.5.1" +lazy_static = "1.4.0" +libpk = { path = "../../lib/libpk" } +tokio = { version = "1.25.0", features = ["full"] } +tower = "0.4.13" +tracing = "0.1.37" diff --git a/services/api/Dockerfile b/services/api/Dockerfile new file mode 100644 index 00000000..461c9733 --- /dev/null +++ b/services/api/Dockerfile @@ -0,0 +1,27 @@ +FROM alpine:latest AS builder + +WORKDIR /build + +RUN apk add rustup build-base +# todo: arm64 target +RUN rustup-init --default-host x86_64-unknown-linux-musl --default-toolchain stable --profile default -y + +COPY Cargo.toml /build/ +COPY Cargo.lock /build/ + +# todo: fetch dependencies first to cache +# RUN cargo fetch + +COPY lib/libpk /build/lib/libpk +COPY services/api/ /build/services/api + +RUN source "$HOME/.cargo/env" && RUSTFLAGS='-C link-arg=-s' cargo build --bin api --release --target x86_64-unknown-linux-musl + +RUN ls /build/target +RUN ls /build/target/release + +FROM alpine:latest + +COPY --from=builder /build/target/x86_64-unknown-linux-musl/release/api /bin/api + +ENTRYPOINT [ "/bin/api" ] diff --git a/services/api/src/main.rs b/services/api/src/main.rs new file mode 100644 index 00000000..5298c2d2 --- /dev/null +++ b/services/api/src/main.rs @@ -0,0 +1,85 @@ +use axum::{ + routing::{delete, get, patch, post}, + Router, +}; +use tracing::info; + +mod middleware; +mod util; + +// this function is manually formatted for easier legibility of routes +#[rustfmt::skip] +#[tokio::main] +async fn main() -> anyhow::Result<()> { + libpk::init_logging("api")?; + info!("hello world"); + + // processed upside down (???) so we have to put middleware at the end + let app = Router::new() + .route("/v2/systems/:system_id", get(util::rproxy)) + .route("/v2/systems/:system_id", patch(util::rproxy)) + .route("/v2/systems/:system_id/settings", get(util::rproxy)) + .route("/v2/systems/:system_id/settings", patch(util::rproxy)) + + .route("/v2/systems/:system_id/members", get(util::rproxy)) + .route("/v2/members", post(util::rproxy)) + .route("/v2/members/:member_id", get(util::rproxy)) + .route("/v2/members/:member_id", patch(util::rproxy)) + .route("/v2/members/:member_id", delete(util::rproxy)) + + .route("/v2/systems/:system_id/groups", get(util::rproxy)) + .route("/v2/groups", post(util::rproxy)) + .route("/v2/groups/:group_id", get(util::rproxy)) + .route("/v2/groups/:group_id", patch(util::rproxy)) + .route("/v2/groups/:group_id", delete(util::rproxy)) + + .route("/v2/groups/:group_id/members", get(util::rproxy)) + .route("/v2/groups/:group_id/members/add", post(util::rproxy)) + .route("/v2/groups/:group_id/members/remove", post(util::rproxy)) + .route("/v2/groups/:group_id/members/overwrite", post(util::rproxy)) + + .route("/v2/members/:member_id/groups", get(util::rproxy)) + .route("/v2/members/:member_id/groups/add", post(util::rproxy)) + .route("/v2/members/:member_id/groups/remove", post(util::rproxy)) + .route("/v2/members/:member_id/groups/overwrite", post(util::rproxy)) + + .route("/v2/systems/:system_id/switches", get(util::rproxy)) + .route("/v2/systems/:system_id/switches", post(util::rproxy)) + .route("/v2/systems/:system_id/fronters", get(util::rproxy)) + + .route("/v2/systems/:system_id/switches/:switch_id", get(util::rproxy)) + .route("/v2/systems/:system_id/switches/:switch_id", patch(util::rproxy)) + .route("/v2/systems/:system_id/switches/:switch_id/members", patch(util::rproxy)) + .route("/v2/systems/:system_id/switches/:switch_id", delete(util::rproxy)) + + .route("/v2/systems/:system_id/guilds/:guild_id", get(util::rproxy)) + .route("/v2/systems/:system_id/guilds/:guild_id", patch(util::rproxy)) + + .route("/v2/members/:member_id/guilds/:guild_id", get(util::rproxy)) + .route("/v2/members/:member_id/guilds/:guild_id", patch(util::rproxy)) + + .route("/v2/messages/:message_id", get(util::rproxy)) + + .route("/private/meta", get(util::rproxy)) + .route("/private/bulk_privacy/member", post(util::rproxy)) + .route("/private/bulk_privacy/group", post(util::rproxy)) + .route("/private/discord/callback", post(util::rproxy)) + + .route("/v2/systems/:system_id/oembed.json", get(util::rproxy)) + .route("/v2/members/:member_id/oembed.json", get(util::rproxy)) + .route("/v2/groups/:group_id/oembed.json", get(util::rproxy)) + + .layer(middleware::ratelimit::ratelimiter(middleware::ratelimit::do_request_ratelimited)) // this sucks + .layer(axum::middleware::from_fn(middleware::logger)) + .layer(axum::middleware::from_fn(middleware::ignore_invalid_routes)) + .layer(axum::middleware::from_fn(middleware::cors)) + + .route("/", get(|| async { axum::response::Redirect::to("https://pluralkit.me/api") })); + + let addr: &str = libpk::config.api.addr.as_ref(); + axum::Server::bind(&addr.parse()?) + .serve(app.into_make_service()) + .await?; + + Ok(()) +} diff --git a/services/api/src/middleware/cors.rs b/services/api/src/middleware/cors.rs new file mode 100644 index 00000000..e439cd9d --- /dev/null +++ b/services/api/src/middleware/cors.rs @@ -0,0 +1,26 @@ +use axum::{ + http::{HeaderMap, HeaderValue, Method, Request, StatusCode}, + middleware::Next, + response::{IntoResponse, Response}, +}; + +#[rustfmt::skip] +fn add_cors_headers(headers: &mut HeaderMap) { + headers.append("Access-Control-Allow-Origin", HeaderValue::from_static("*")); + headers.append("Access-Control-Allow-Methods", HeaderValue::from_static("*")); + headers.append("Access-Control-Allow-Credentials", HeaderValue::from_static("true")); + headers.append("Access-Control-Allow-Headers", HeaderValue::from_static("Content-Type, Authorization, sentry-trace, User-Agent")); + headers.append("Access-Control-Max-Age", HeaderValue::from_static("86400")); +} + +pub async fn cors(request: Request, next: Next) -> Response { + let mut response = if request.method() == Method::OPTIONS { + StatusCode::OK.into_response() + } else { + next.run(request).await + }; + + add_cors_headers(response.headers_mut()); + + response +} diff --git a/services/api/src/middleware/ignore_invalid_routes.rs b/services/api/src/middleware/ignore_invalid_routes.rs new file mode 100644 index 00000000..ec864a52 --- /dev/null +++ b/services/api/src/middleware/ignore_invalid_routes.rs @@ -0,0 +1,61 @@ +use axum::{ + extract::MatchedPath, + http::{Request, StatusCode}, + middleware::Next, + response::{IntoResponse, Response}, +}; + +use crate::util::header_or_unknown; + +fn is_trying_to_use_v1_path_on_v2(path: &str) -> bool { + path.starts_with("/v2/s/") + || path.starts_with("/v2/m/") + || path.starts_with("/v2/a/") + || path.starts_with("/v2/msg/") + || path == "/v2/s" + || path == "/v2/m" +} + +pub async fn ignore_invalid_routes(request: Request, next: Next) -> Response { + let path = request + .extensions() + .get::() + .cloned() + .map(|v| v.as_str().to_string()) + .unwrap_or("unknown".to_string()); + let user_agent = header_or_unknown(request.headers().get("User-Agent")); + + if request.uri().path().starts_with("/v1") { + ( + StatusCode::GONE, + r#"{"message":"Unsupported API version","code":0}"#, + ) + .into_response() + } else if is_trying_to_use_v1_path_on_v2(request.uri().path()) { + ( + StatusCode::BAD_REQUEST, + r#"{"message":"Invalid path for API version","code":0}"#, + ) + .into_response() + } + // we ignored v1 routes earlier, now let's ignore all non-v2 routes + else if !request.uri().clone().path().starts_with("/v2") { + return ( + StatusCode::BAD_REQUEST, + r#"{"message":"Unsupported API version","code":0}"#, + ) + .into_response(); + } else if path == "unknown" { + // current prod api responds with 404 with empty body to invalid endpoints + // just doing that here as well but i'm not sure if it's the correct behaviour + return StatusCode::NOT_FOUND.into_response(); + } + // yes, technically because of how we parse headers this will break for user-agents literally set to "unknown" + // but "unknown" isn't really a valid user-agent + else if user_agent == "unknown" { + // please set a valid user-agent + return StatusCode::FORBIDDEN.into_response(); + } else { + next.run(request).await + } +} diff --git a/services/api/src/middleware/logger.rs b/services/api/src/middleware/logger.rs new file mode 100644 index 00000000..09d2136b --- /dev/null +++ b/services/api/src/middleware/logger.rs @@ -0,0 +1,41 @@ +use std::time::Instant; + +use axum::{extract::MatchedPath, http::Request, middleware::Next, response::Response}; +use tracing::{info, span, Instrument, Level}; + +use crate::util::header_or_unknown; + +pub async fn logger(request: Request, next: Next) -> Response { + let method = request.method().clone(); + + let request_id = header_or_unknown(request.headers().get("Fly-Request-Id")); + let remote_ip = header_or_unknown(request.headers().get("Fly-Client-IP")); + let user_agent = header_or_unknown(request.headers().get("User-Agent")); + + let path = request + .extensions() + .get::() + .cloned() + .map(|v| v.as_str().to_string()) + .unwrap_or("unknown".to_string()); + + // todo: prometheus metrics + + let request_id_span = span!( + Level::INFO, + "request", + request_id, + remote_ip, + method = method.as_str(), + path, + user_agent + ); + + let start = Instant::now(); + let response = next.run(request).instrument(request_id_span).await; + let elapsed = start.elapsed().as_millis(); + + info!("handled request for {} {} in {}ms", method, path, elapsed); + + response +} diff --git a/services/api/src/middleware/mod.rs b/services/api/src/middleware/mod.rs new file mode 100644 index 00000000..7746157c --- /dev/null +++ b/services/api/src/middleware/mod.rs @@ -0,0 +1,11 @@ +mod cors; + +pub use cors::cors; + +mod logger; +pub use logger::logger; + +mod ignore_invalid_routes; +pub use ignore_invalid_routes::ignore_invalid_routes; + +pub mod ratelimit; diff --git a/services/api/src/middleware/ratelimit.lua b/services/api/src/middleware/ratelimit.lua new file mode 100644 index 00000000..b7e58ec9 --- /dev/null +++ b/services/api/src/middleware/ratelimit.lua @@ -0,0 +1,57 @@ +-- Copyright (c) 2017 Pavel Pravosud +-- https://github.com/rwz/redis-gcra/blob/master/vendor/perform_gcra_ratelimit.lua + +local rate_limit_key = KEYS[1] +local burst = ARGV[1] +local rate = ARGV[2] +local period = ARGV[3] +-- local cost = tonumber(ARGV[4]) +local cost = 1 + +local emission_interval = period / rate +local increment = emission_interval * cost +local burst_offset = emission_interval * burst + +-- redis returns time as an array containing two integers: seconds of the epoch +-- time (10 digits) and microseconds (6 digits). for convenience we need to +-- convert them to a floating point number. the resulting number is 16 digits, +-- bordering on the limits of a 64-bit double-precision floating point number. +-- adjust the epoch to be relative to Jan 1, 2017 00:00:00 GMT to avoid floating +-- point problems. this approach is good until "now" is 2,483,228,799 (Wed, 09 +-- Sep 2048 01:46:39 GMT), when the adjusted value is 16 digits. +local jan_1_2017 = 1483228800 +local now = redis.call("TIME") +now = (now[1] - jan_1_2017) + (now[2] / 1000000) + +local tat = redis.call("GET", rate_limit_key) + +if not tat then + tat = now +else + tat = tonumber(tat) +end + +tat = math.max(tat, now) + +local new_tat = tat + increment +local allow_at = new_tat - burst_offset + +local diff = now - allow_at +local remaining = diff / emission_interval + +if remaining < 0 then + local reset_after = tat - now + local retry_after = diff * -1 + return { + 0, -- remaining + retry_after, + reset_after, + } +end + +local reset_after = new_tat - now +if reset_after > 0 then + redis.call("SET", rate_limit_key, new_tat, "EX", math.ceil(reset_after)) +end +local retry_after = -1 +return {remaining, retry_after, reset_after} \ No newline at end of file diff --git a/services/api/src/middleware/ratelimit.rs b/services/api/src/middleware/ratelimit.rs new file mode 100644 index 00000000..36c3e618 --- /dev/null +++ b/services/api/src/middleware/ratelimit.rs @@ -0,0 +1,154 @@ +use std::time::{Duration, SystemTime}; + +use axum::{ + extract::State, + http::Request, + middleware::{FromFnLayer, Next}, + response::Response, +}; +use fred::{pool::RedisPool, prelude::LuaInterface, types::ReconnectPolicy, util::sha1_hash}; +use http::{HeaderValue, StatusCode}; +use tracing::{error, info, warn}; + +use crate::util::{header_or_unknown, json_err}; + +const LUA_SCRIPT: &str = include_str!("ratelimit.lua"); + +lazy_static::lazy_static! { + static ref LUA_SCRIPT_SHA: String = sha1_hash(LUA_SCRIPT); +} + +// todo lol +const TOKEN2: &'static str = "h"; + +// this is awful but it works +pub fn ratelimiter(f: F) -> FromFnLayer, T> { + let redis = libpk::config.api.ratelimit_redis_addr.as_ref().map(|val| { + let r = fred::pool::RedisPool::new( + fred::types::RedisConfig::from_url_centralized(val.as_ref()) + .expect("redis url is invalid"), + 10, + ) + .expect("failed to connect to redis"); + + let handle = r.connect(Some(ReconnectPolicy::default())); + + tokio::spawn(async move { handle }); + + let rscript = r.clone(); + tokio::spawn(async move { + if let Ok(()) = rscript.wait_for_connect().await { + match rscript.script_load(LUA_SCRIPT).await { + Ok(_) => info!("connected to redis for request rate limiting"), + Err(err) => error!("could not load redis script: {}", err), + } + } else { + error!("could not wait for connection to load redis script!"); + } + }); + + r + }); + + if redis.is_none() { + warn!("running without request rate limiting!"); + } + + axum::middleware::from_fn_with_state(redis, f) +} + +pub async fn do_request_ratelimited( + State(redis): State>, + request: Request, + next: Next, +) -> Response { + if let Some(redis) = redis { + let headers = request.headers().clone(); + let source_ip = header_or_unknown(headers.get("Fly-Client-IP")); + + let (rl_key, rate) = if let Some(header) = request.headers().clone().get("X-PluralKit-App") + { + if header == TOKEN2 { + ("token2", 20) + } else { + (source_ip, 2) + } + } else { + (source_ip, 2) + }; + + let burst = 5; + let period = 1; // seconds + + // todo: make this static + // though even if it's not static, it's probably cheaper than sending the entire script to redis every time + let scriptsha = sha1_hash(&LUA_SCRIPT); + + // local rate_limit_key = KEYS[1] + // local burst = ARGV[1] + // local rate = ARGV[2] + // local period = ARGV[3] + // return {remaining, retry_after, reset_after} + let resp = redis + .evalsha::<(i32, String, u64), String, Vec<&str>, Vec>( + scriptsha, + vec![rl_key], + vec![burst, rate, period], + ) + .await; + + match resp { + Ok((mut remaining, retry_after, reset_after)) => { + let mut response = if remaining > 0 { + next.run(request).await + } else { + json_err( + StatusCode::TOO_MANY_REQUESTS, + format!( + // todo: the retry_after is horribly wrong + r#"{{"message":"429: too many requests","retry_after":{retry_after}}}"# + ), + ) + }; + + // the redis script puts burst in remaining for ??? some reason + remaining -= burst - rate; + + let reset_time = SystemTime::now() + .checked_add(Duration::from_secs(reset_after)) + .expect("invalid timestamp") + .duration_since(std::time::UNIX_EPOCH) + .expect("invalid duration") + .as_secs(); + + let headers = response.headers_mut(); + headers.insert( + "X-RateLimit-Limit", + HeaderValue::from_str(format!("{}", rate).as_str()) + .expect("invalid header value"), + ); + headers.insert( + "X-RateLimit-Remaining", + HeaderValue::from_str(format!("{}", remaining).as_str()) + .expect("invalid header value"), + ); + headers.insert( + "X-RateLimit-Reset", + HeaderValue::from_str(format!("{}", reset_time).as_str()) + .expect("invalid header value"), + ); + + return response; + } + Err(err) => { + tracing::error!("error getting ratelimit info: {}", err); + return json_err( + StatusCode::INTERNAL_SERVER_ERROR, + r#"{"message": "500: internal server error", "code": 0}"#.to_string(), + ); + } + } + } + + next.run(request).await +} diff --git a/services/api/src/util.rs b/services/api/src/util.rs new file mode 100644 index 00000000..8128845f --- /dev/null +++ b/services/api/src/util.rs @@ -0,0 +1,42 @@ +use axum::{ + body::Body, + http::{HeaderValue, Request, Response, StatusCode, Uri}, + response::IntoResponse, +}; +use tracing::error; + +pub fn header_or_unknown(header: Option<&HeaderValue>) -> &str { + if let Some(value) = header { + match value.to_str() { + Ok(v) => v, + Err(err) => { + error!("failed to parse header value {:#?}: {:#?}", value, err); + "failed to parse" + } + } + } else { + "unknown" + } +} + +pub async fn rproxy(req: Request) -> Response { + let uri = Uri::from_static(&libpk::config.api.remote_url).to_string(); + + match hyper_reverse_proxy::call("0.0.0.0".parse().unwrap(), &uri[..uri.len() - 1], req).await { + Ok(response) => response, + Err(error) => { + error!("error proxying request: {:?}", error); + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::empty()) + .unwrap() + } + } +} + +pub fn json_err(code: StatusCode, text: String) -> axum::response::Response { + let mut response = (code, text).into_response(); + let headers = response.headers_mut(); + headers.insert("content-type", HeaderValue::from_static("application/json")); + response +} diff --git a/services/web-proxy/main.go b/services/web-proxy/main.go index 2ff9c74d..7ffc091a 100644 --- a/services/web-proxy/main.go +++ b/services/web-proxy/main.go @@ -2,25 +2,26 @@ package main import ( "context" - "encoding/json" - "fmt" + "log" "net/http" "net/http/httputil" + "net/url" "strconv" - "strings" "time" - "github.com/go-redis/redis/v8" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" - - "web-proxy/redis_rate" ) -var limiter *redis_rate.Limiter - -// todo: be able to raise ratelimits for >1 consumers -var token2 string +func proxyTo(host string) *httputil.ReverseProxy { + rp := httputil.NewSingleHostReverseProxy(&url.URL{ + Scheme: "http", + Host: host, + RawQuery: "", + }) + rp.ModifyResponse = logTimeElapsed + return rp +} // todo: this shouldn't be in this repo var remotes = map[string]*httputil.ReverseProxy{ @@ -29,22 +30,6 @@ var remotes = map[string]*httputil.ReverseProxy{ "sentry.pluralkit.me": proxyTo("[fdaa:0:ae33:a7b:8dd7:0:a:202]:9000"), } -func init() { - redisHost := requireEnv("REDIS_HOST") - redisPassword := requireEnv("REDIS_PASSWORD") - - rdb := redis.NewClient(&redis.Options{ - Addr: redisHost, - Username: "default", - Password: redisPassword, - }) - limiter = redis_rate.NewLimiter(rdb) - - token2 = requireEnv("TOKEN2") - - remotes["dash.pluralkit.me"].ModifyResponse = modifyDashResponse -} - type ProxyHandler struct{} func (p ProxyHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { @@ -61,48 +46,6 @@ func (p ProxyHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) { return } - if r.Host == "api.pluralkit.me" { - // root - if r.URL.Path == "" { - // api root path redirects to docs - http.Redirect(rw, r, "https://pluralkit.me/api/", http.StatusFound) - return - } - - // CORS headers - rw.Header().Add("Access-Control-Allow-Origin", "*") - rw.Header().Add("Access-Control-Allow-Methods", "*") - rw.Header().Add("Access-Control-Allow-Credentials", "true") - rw.Header().Add("Access-Control-Allow-Headers", "Content-Type, Authorization, sentry-trace, User-Agent") - rw.Header().Add("Access-Control-Max-Age", "86400") - - if r.Method == http.MethodOptions { - rw.WriteHeader(200) - return - } - - if r.URL.Path == "/" { - http.Redirect(rw, r, "https://pluralkit.me/api", http.StatusFound) - return - } - - if strings.HasPrefix(r.URL.Path, "/v1") { - rw.Header().Set("content-type", "application/json") - rw.WriteHeader(410) - rw.Write([]byte(`{"message":"Unsupported API version","code":0}`)) - } - - if is_trying_to_use_v1_path_on_v2(r.URL.Path) { - rw.WriteHeader(400) - rw.Write([]byte(`{"message":"Invalid path for API version","code":0}`)) - return - } - - if is_api_ratelimited(rw, r) { - return - } - } - startTime := time.Now() r = r.WithContext(context.WithValue(r.Context(), "req-time", startTime)) @@ -119,40 +62,14 @@ func logTimeElapsed(resp *http.Response) error { "domain": r.Host, "method": r.Method, "status": strconv.Itoa(resp.StatusCode), - "route": cleanPath(r.Host, r.URL.Path), + "route": r.URL.Path, }).Observe(elapsed.Seconds()) - log, _ := json.Marshal(map[string]interface{}{ - "remote_ip": r.Header.Get("Fly-Client-IP"), - "method": r.Method, - "host": r.Host, - "route": r.URL.Path, - "route_clean": cleanPath(r.Host, r.URL.Path), - "status": resp.StatusCode, - "elapsed": elapsed.Milliseconds(), - "user_agent": r.Header.Get("User-Agent"), - }) - fmt.Println(string(log)) - - // log.Printf("[%s] \"%s %s%s\" %d - %vms %s\n", r.Header.Get("Fly-Client-IP"), r.Method, r.Host, r.URL.Path, resp.StatusCode, elapsed.Milliseconds(), r.Header.Get("User-Agent")) + log.Printf("[%s] \"%s %s%s\" %d - %vms %s\n", r.Header.Get("Fly-Client-IP"), r.Method, r.Host, r.URL.Path, resp.StatusCode, elapsed.Milliseconds(), r.Header.Get("User-Agent")) return nil } -func modifyDashResponse(resp *http.Response) error { - r := resp.Request - - // cache built+hashed dashboard js/css files forever - is_dash_static_asset := strings.HasPrefix(r.URL.Path, "/assets/") && - (strings.HasSuffix(r.URL.Path, ".js") || strings.HasSuffix(r.URL.Path, ".css") || strings.HasSuffix(r.URL.Path, ".map")) - - if is_dash_static_asset && resp.StatusCode == 200 { - resp.Header.Add("Cache-Control", "max-age=31536000, s-maxage=31536000, immutable") - } - - return logTimeElapsed(resp) -} - func main() { prometheus.MustRegister(metric) @@ -161,3 +78,11 @@ func main() { http.ListenAndServe(":8080", ProxyHandler{}) } + +var metric = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "pk_http_requests", + Buckets: []float64{.1, .25, 1, 2.5, 5, 20}, + }, + []string{"domain", "method", "status", "route"}, +) diff --git a/services/web-proxy/rate_limit.go b/services/web-proxy/rate_limit.go deleted file mode 100644 index 3d32119d..00000000 --- a/services/web-proxy/rate_limit.go +++ /dev/null @@ -1,43 +0,0 @@ -package main - -import ( - "fmt" - "net/http" - "time" - - "web-proxy/redis_rate" -) - -func is_api_ratelimited(rw http.ResponseWriter, r *http.Request) bool { - var limit int - var key string - - if r.Header.Get("X-PluralKit-App") == token2 { - limit = 20 - key = "token2" - } else { - limit = 2 - key = r.Header.Get("Fly-Client-IP") - } - - res, err := limiter.Allow(r.Context(), "ratelimit:"+key, redis_rate.Limit{ - Period: time.Second, - Rate: limit, - Burst: 5, - }) - if err != nil { - panic(err) - } - - rw.Header().Set("X-RateLimit-Limit", fmt.Sprint(limit)) - rw.Header().Set("X-RateLimit-Remaining", fmt.Sprint(res.Remaining)) - rw.Header().Set("X-RateLimit-Reset", fmt.Sprint(time.Now().Add(res.ResetAfter).UnixNano()/1_000_000)) - - if res.Allowed < 1 { - rw.WriteHeader(429) - rw.Write([]byte(`{"message":"429: too many requests","retry_after":` + fmt.Sprint(res.RetryAfter.Milliseconds()) + `,"code":0}`)) - return true - } - - return false -} diff --git a/services/web-proxy/redis_rate/LICENSE b/services/web-proxy/redis_rate/LICENSE deleted file mode 100644 index aaf73aa9..00000000 --- a/services/web-proxy/redis_rate/LICENSE +++ /dev/null @@ -1,25 +0,0 @@ -Copyright (c) 2013 The github.com/go-redis/redis_rate Authors. -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/services/web-proxy/redis_rate/lua.go b/services/web-proxy/redis_rate/lua.go deleted file mode 100644 index cab02ca3..00000000 --- a/services/web-proxy/redis_rate/lua.go +++ /dev/null @@ -1,140 +0,0 @@ -package redis_rate - -import "github.com/go-redis/redis/v8" - -// pluralkit changes: -// fly's hosted redis doesn't support replicate commands -// we can remove it since it's a single host - -// Copyright (c) 2017 Pavel Pravosud -// https://github.com/rwz/redis-gcra/blob/master/vendor/perform_gcra_ratelimit.lua -var allowN = redis.NewScript(` --- this script has side-effects, so it requires replicate commands mode --- redis.replicate_commands() - -local rate_limit_key = KEYS[1] -local burst = ARGV[1] -local rate = ARGV[2] -local period = ARGV[3] -local cost = tonumber(ARGV[4]) - -local emission_interval = period / rate -local increment = emission_interval * cost -local burst_offset = emission_interval * burst - --- redis returns time as an array containing two integers: seconds of the epoch --- time (10 digits) and microseconds (6 digits). for convenience we need to --- convert them to a floating point number. the resulting number is 16 digits, --- bordering on the limits of a 64-bit double-precision floating point number. --- adjust the epoch to be relative to Jan 1, 2017 00:00:00 GMT to avoid floating --- point problems. this approach is good until "now" is 2,483,228,799 (Wed, 09 --- Sep 2048 01:46:39 GMT), when the adjusted value is 16 digits. -local jan_1_2017 = 1483228800 -local now = redis.call("TIME") -now = (now[1] - jan_1_2017) + (now[2] / 1000000) - -local tat = redis.call("GET", rate_limit_key) - -if not tat then - tat = now -else - tat = tonumber(tat) -end - -tat = math.max(tat, now) - -local new_tat = tat + increment -local allow_at = new_tat - burst_offset - -local diff = now - allow_at -local remaining = diff / emission_interval - -if remaining < 0 then - local reset_after = tat - now - local retry_after = diff * -1 - return { - 0, -- allowed - 0, -- remaining - tostring(retry_after), - tostring(reset_after), - } -end - -local reset_after = new_tat - now -if reset_after > 0 then - redis.call("SET", rate_limit_key, new_tat, "EX", math.ceil(reset_after)) -end -local retry_after = -1 -return {cost, remaining, tostring(retry_after), tostring(reset_after)} -`) - -var allowAtMost = redis.NewScript(` --- this script has side-effects, so it requires replicate commands mode --- redis.replicate_commands() - -local rate_limit_key = KEYS[1] -local burst = ARGV[1] -local rate = ARGV[2] -local period = ARGV[3] -local cost = tonumber(ARGV[4]) - -local emission_interval = period / rate -local burst_offset = emission_interval * burst - --- redis returns time as an array containing two integers: seconds of the epoch --- time (10 digits) and microseconds (6 digits). for convenience we need to --- convert them to a floating point number. the resulting number is 16 digits, --- bordering on the limits of a 64-bit double-precision floating point number. --- adjust the epoch to be relative to Jan 1, 2017 00:00:00 GMT to avoid floating --- point problems. this approach is good until "now" is 2,483,228,799 (Wed, 09 --- Sep 2048 01:46:39 GMT), when the adjusted value is 16 digits. -local jan_1_2017 = 1483228800 -local now = redis.call("TIME") -now = (now[1] - jan_1_2017) + (now[2] / 1000000) - -local tat = redis.call("GET", rate_limit_key) - -if not tat then - tat = now -else - tat = tonumber(tat) -end - -tat = math.max(tat, now) - -local diff = now - (tat - burst_offset) -local remaining = diff / emission_interval - -if remaining < 1 then - local reset_after = tat - now - local retry_after = emission_interval - diff - return { - 0, -- allowed - 0, -- remaining - tostring(retry_after), - tostring(reset_after), - } -end - -if remaining < cost then - cost = remaining - remaining = 0 -else - remaining = remaining - cost -end - -local increment = emission_interval * cost -local new_tat = tat + increment - -local reset_after = new_tat - now -if reset_after > 0 then - redis.call("SET", rate_limit_key, new_tat, "EX", math.ceil(reset_after)) -end - -return { - cost, - remaining, - tostring(-1), - tostring(reset_after), -} -`) diff --git a/services/web-proxy/redis_rate/rate.go b/services/web-proxy/redis_rate/rate.go deleted file mode 100644 index 058a8225..00000000 --- a/services/web-proxy/redis_rate/rate.go +++ /dev/null @@ -1,198 +0,0 @@ -package redis_rate - -import ( - "context" - "fmt" - "strconv" - "time" - - "github.com/go-redis/redis/v8" -) - -const redisPrefix = "rate:" - -type rediser interface { - Eval(ctx context.Context, script string, keys []string, args ...interface{}) *redis.Cmd - EvalSha(ctx context.Context, sha1 string, keys []string, args ...interface{}) *redis.Cmd - ScriptExists(ctx context.Context, hashes ...string) *redis.BoolSliceCmd - ScriptLoad(ctx context.Context, script string) *redis.StringCmd - Del(ctx context.Context, keys ...string) *redis.IntCmd -} - -type Limit struct { - Rate int - Burst int - Period time.Duration -} - -func (l Limit) String() string { - return fmt.Sprintf("%d req/%s (burst %d)", l.Rate, fmtDur(l.Period), l.Burst) -} - -func (l Limit) IsZero() bool { - return l == Limit{} -} - -func fmtDur(d time.Duration) string { - switch d { - case time.Second: - return "s" - case time.Minute: - return "m" - case time.Hour: - return "h" - } - return d.String() -} - -func PerSecond(rate int) Limit { - return Limit{ - Rate: rate, - Period: time.Second, - Burst: rate, - } -} - -func PerMinute(rate int) Limit { - return Limit{ - Rate: rate, - Period: time.Minute, - Burst: rate, - } -} - -func PerHour(rate int) Limit { - return Limit{ - Rate: rate, - Period: time.Hour, - Burst: rate, - } -} - -//------------------------------------------------------------------------------ - -// Limiter controls how frequently events are allowed to happen. -type Limiter struct { - rdb rediser -} - -// NewLimiter returns a new Limiter. -func NewLimiter(rdb rediser) *Limiter { - return &Limiter{ - rdb: rdb, - } -} - -// Allow is a shortcut for AllowN(ctx, key, limit, 1). -func (l Limiter) Allow(ctx context.Context, key string, limit Limit) (*Result, error) { - return l.AllowN(ctx, key, limit, 1) -} - -// AllowN reports whether n events may happen at time now. -func (l Limiter) AllowN( - ctx context.Context, - key string, - limit Limit, - n int, -) (*Result, error) { - values := []interface{}{limit.Burst, limit.Rate, limit.Period.Seconds(), n} - v, err := allowN.Run(ctx, l.rdb, []string{redisPrefix + key}, values...).Result() - if err != nil { - return nil, err - } - - values = v.([]interface{}) - - retryAfter, err := strconv.ParseFloat(values[2].(string), 64) - if err != nil { - return nil, err - } - - resetAfter, err := strconv.ParseFloat(values[3].(string), 64) - if err != nil { - return nil, err - } - - res := &Result{ - Limit: limit, - Allowed: int(values[0].(int64)), - Remaining: int(values[1].(int64)), - RetryAfter: dur(retryAfter), - ResetAfter: dur(resetAfter), - } - return res, nil -} - -// AllowAtMost reports whether at most n events may happen at time now. -// It returns number of allowed events that is less than or equal to n. -func (l Limiter) AllowAtMost( - ctx context.Context, - key string, - limit Limit, - n int, -) (*Result, error) { - values := []interface{}{limit.Burst, limit.Rate, limit.Period.Seconds(), n} - v, err := allowAtMost.Run(ctx, l.rdb, []string{redisPrefix + key}, values...).Result() - if err != nil { - return nil, err - } - - values = v.([]interface{}) - - retryAfter, err := strconv.ParseFloat(values[2].(string), 64) - if err != nil { - return nil, err - } - - resetAfter, err := strconv.ParseFloat(values[3].(string), 64) - if err != nil { - return nil, err - } - - res := &Result{ - Limit: limit, - Allowed: int(values[0].(int64)), - Remaining: int(values[1].(int64)), - RetryAfter: dur(retryAfter), - ResetAfter: dur(resetAfter), - } - return res, nil -} - -// Reset gets a key and reset all limitations and previous usages -func (l *Limiter) Reset(ctx context.Context, key string) error { - return l.rdb.Del(ctx, redisPrefix+key).Err() -} - -func dur(f float64) time.Duration { - if f == -1 { - return -1 - } - return time.Duration(f * float64(time.Second)) -} - -type Result struct { - // Limit is the limit that was used to obtain this result. - Limit Limit - - // Allowed is the number of events that may happen at time now. - Allowed int - - // Remaining is the maximum number of requests that could be - // permitted instantaneously for this key given the current - // state. For example, if a rate limiter allows 10 requests per - // second and has already received 6 requests for this key this - // second, Remaining would be 4. - Remaining int - - // RetryAfter is the time until the next request will be permitted. - // It should be -1 unless the rate limit has been exceeded. - RetryAfter time.Duration - - // ResetAfter is the time until the RateLimiter returns to its - // initial state for a given key. For example, if a rate limiter - // manages requests per second and received one request 200ms ago, - // Reset would return 800ms. You can also think of this as the time - // until Limit and Remaining will be equal. - ResetAfter time.Duration -} diff --git a/services/web-proxy/util.go b/services/web-proxy/util.go deleted file mode 100644 index 552cef49..00000000 --- a/services/web-proxy/util.go +++ /dev/null @@ -1,74 +0,0 @@ -package main - -import ( - "net/http/httputil" - "net/url" - "os" - "regexp" - "strings" - - "github.com/prometheus/client_golang/prometheus" -) - -var metric = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Name: "pk_http_requests", - Buckets: []float64{.1, .25, 1, 2.5, 5, 20}, - }, - []string{"domain", "method", "status", "route"}, -) - -func proxyTo(host string) *httputil.ReverseProxy { - rp := httputil.NewSingleHostReverseProxy(&url.URL{ - Scheme: "http", - Host: host, - RawQuery: "", - }) - rp.ModifyResponse = logTimeElapsed - return rp -} - -var systemsRegex = regexp.MustCompile("systems/[^/{}]+") -var membersRegex = regexp.MustCompile("members/[^/{}]+") -var groupsRegex = regexp.MustCompile("groups/[^/{}]+") -var switchesRegex = regexp.MustCompile("switches/[^/{}]+") -var guildsRegex = regexp.MustCompile("guilds/[^/{}]+") -var messagesRegex = regexp.MustCompile("messages/[^/{}]+") - -func cleanPath(host, path string) string { - if host != "api.pluralkit.me" { - return "" - } - - path = strings.ToLower(path) - - if !(strings.HasPrefix(path, "/v2") || strings.HasPrefix(path, "/private")) { - return "" - } - - path = systemsRegex.ReplaceAllString(path, "systems/{systemRef}") - path = membersRegex.ReplaceAllString(path, "members/{memberRef}") - path = groupsRegex.ReplaceAllString(path, "groups/{groupRef}") - path = switchesRegex.ReplaceAllString(path, "switches/{switchRef}") - path = guildsRegex.ReplaceAllString(path, "guilds/{guild_id}") - path = messagesRegex.ReplaceAllString(path, "messages/{message_id}") - - return path -} - -func requireEnv(key string) string { - if val, ok := os.LookupEnv(key); !ok { - panic("missing `" + key + "` in environment") - } else { - return val - } -} - -func is_trying_to_use_v1_path_on_v2(path string) bool { - return strings.HasPrefix(path, "/v2/s/") || - strings.HasPrefix(path, "/v2/m/") || - strings.HasPrefix(path, "/v2/a/") || - strings.HasPrefix(path, "/v2/msg/") || - path == "/v2/s" || - path == "/v2/m" -}