├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── examples ├── Cargo.lock ├── Cargo.toml └── src │ ├── basic.rs │ └── custom_key_bearer.rs ├── flake.lock ├── flake.nix └── src ├── errors.rs ├── governor.rs ├── key_extractor.rs ├── lib.rs └── tests.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | .DS_Store 3 | .direnv 4 | .envrc 5 | .idea 6 | Cargo.lock -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.24.2" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "autocfg" 31 | version = "1.4.0" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 34 | 35 | [[package]] 36 | name = "axum" 37 | version = "0.8.1" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" 40 | dependencies = [ 41 | "axum-core", 42 | "bytes", 43 | "form_urlencoded", 44 | "futures-util", 45 | "http", 46 | "http-body", 47 | "http-body-util", 48 | "hyper", 49 | "hyper-util", 50 | "itoa", 51 | "matchit", 52 | "memchr", 53 | "mime", 54 | "percent-encoding", 55 | "pin-project-lite", 56 | "rustversion", 57 | "serde", 58 | "serde_json", 59 | "serde_path_to_error", 60 | "serde_urlencoded", 61 | "sync_wrapper", 62 | "tokio", 63 | "tower", 64 | "tower-layer", 65 | "tower-service", 66 | "tracing", 67 | ] 68 | 69 | [[package]] 70 | name = "axum-core" 71 | version = "0.5.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" 74 | dependencies = [ 75 | "bytes", 76 | "futures-util", 77 | "http", 78 | "http-body", 79 | "http-body-util", 80 | "mime", 81 | "pin-project-lite", 82 | "rustversion", 83 | "sync_wrapper", 84 | "tower-layer", 85 | "tower-service", 86 | "tracing", 87 | ] 88 | 89 | [[package]] 90 | name = "backtrace" 91 | version = "0.3.74" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 94 | dependencies = [ 95 | "addr2line", 96 | "cfg-if", 97 | "libc", 98 | "miniz_oxide", 99 | "object", 100 | "rustc-demangle", 101 | "windows-targets", 102 | ] 103 | 104 | [[package]] 105 | name = "base64" 106 | version = "0.22.1" 107 | source = "registry+https://github.com/rust-lang/crates.io-index" 108 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 109 | 110 | [[package]] 111 | name = "bitflags" 112 | version = "2.6.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 115 | 116 | [[package]] 117 | name = "bumpalo" 118 | version = "3.16.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 121 | 122 | [[package]] 123 | name = "byteorder" 124 | version = "1.5.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 127 | 128 | [[package]] 129 | name = "bytes" 130 | version = "1.9.0" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" 133 | 134 | [[package]] 135 | name = "cfg-if" 136 | version = "1.0.0" 137 | source = "registry+https://github.com/rust-lang/crates.io-index" 138 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 139 | 140 | [[package]] 141 | name = "crossbeam-utils" 142 | version = "0.8.21" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" 145 | 146 | [[package]] 147 | name = "dashmap" 148 | version = "6.1.0" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" 151 | dependencies = [ 152 | "cfg-if", 153 | "crossbeam-utils", 154 | "hashbrown", 155 | "lock_api", 156 | "once_cell", 157 | "parking_lot_core", 158 | ] 159 | 160 | [[package]] 161 | name = "displaydoc" 162 | version = "0.2.5" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" 165 | dependencies = [ 166 | "proc-macro2", 167 | "quote", 168 | "syn", 169 | ] 170 | 171 | [[package]] 172 | name = "fnv" 173 | version = "1.0.7" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 176 | 177 | [[package]] 178 | name = "form_urlencoded" 179 | version = "1.2.1" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 182 | dependencies = [ 183 | "percent-encoding", 184 | ] 185 | 186 | [[package]] 187 | name = "forwarded-header-value" 188 | version = "0.1.1" 189 | source = "registry+https://github.com/rust-lang/crates.io-index" 190 | checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" 191 | dependencies = [ 192 | "nonempty", 193 | "thiserror 1.0.69", 194 | ] 195 | 196 | [[package]] 197 | name = "futures-channel" 198 | version = "0.3.31" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" 201 | dependencies = [ 202 | "futures-core", 203 | ] 204 | 205 | [[package]] 206 | name = "futures-core" 207 | version = "0.3.31" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" 210 | 211 | [[package]] 212 | name = "futures-sink" 213 | version = "0.3.31" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" 216 | 217 | [[package]] 218 | name = "futures-task" 219 | version = "0.3.31" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" 222 | 223 | [[package]] 224 | name = "futures-timer" 225 | version = "3.0.3" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" 228 | 229 | [[package]] 230 | name = "futures-util" 231 | version = "0.3.31" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" 234 | dependencies = [ 235 | "futures-core", 236 | "futures-sink", 237 | "futures-task", 238 | "pin-project-lite", 239 | "pin-utils", 240 | "slab", 241 | ] 242 | 243 | [[package]] 244 | name = "getrandom" 245 | version = "0.2.15" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 248 | dependencies = [ 249 | "cfg-if", 250 | "libc", 251 | "wasi", 252 | ] 253 | 254 | [[package]] 255 | name = "gimli" 256 | version = "0.31.1" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" 259 | 260 | [[package]] 261 | name = "governor" 262 | version = "0.8.0" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "842dc78579ce01e6a1576ad896edc92fca002dd60c9c3746b7fc2bec6fb429d0" 265 | dependencies = [ 266 | "cfg-if", 267 | "dashmap", 268 | "futures-sink", 269 | "futures-timer", 270 | "futures-util", 271 | "no-std-compat", 272 | "nonzero_ext", 273 | "parking_lot", 274 | "portable-atomic", 275 | "quanta", 276 | "rand", 277 | "smallvec", 278 | "spinning_top", 279 | ] 280 | 281 | [[package]] 282 | name = "hashbrown" 283 | version = "0.14.5" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 286 | 287 | [[package]] 288 | name = "http" 289 | version = "1.2.0" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "f16ca2af56261c99fba8bac40a10251ce8188205a4c448fbb745a2e4daa76fea" 292 | dependencies = [ 293 | "bytes", 294 | "fnv", 295 | "itoa", 296 | ] 297 | 298 | [[package]] 299 | name = "http-body" 300 | version = "1.0.1" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 303 | dependencies = [ 304 | "bytes", 305 | "http", 306 | ] 307 | 308 | [[package]] 309 | name = "http-body-util" 310 | version = "0.1.2" 311 | source = "registry+https://github.com/rust-lang/crates.io-index" 312 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 313 | dependencies = [ 314 | "bytes", 315 | "futures-util", 316 | "http", 317 | "http-body", 318 | "pin-project-lite", 319 | ] 320 | 321 | [[package]] 322 | name = "httparse" 323 | version = "1.9.5" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" 326 | 327 | [[package]] 328 | name = "httpdate" 329 | version = "1.0.3" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 332 | 333 | [[package]] 334 | name = "hyper" 335 | version = "1.5.2" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "256fb8d4bd6413123cc9d91832d78325c48ff41677595be797d90f42969beae0" 338 | dependencies = [ 339 | "bytes", 340 | "futures-channel", 341 | "futures-util", 342 | "http", 343 | "http-body", 344 | "httparse", 345 | "httpdate", 346 | "itoa", 347 | "pin-project-lite", 348 | "smallvec", 349 | "tokio", 350 | "want", 351 | ] 352 | 353 | [[package]] 354 | name = "hyper-util" 355 | version = "0.1.10" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "df2dcfbe0677734ab2f3ffa7fa7bfd4706bfdc1ef393f2ee30184aed67e631b4" 358 | dependencies = [ 359 | "bytes", 360 | "futures-channel", 361 | "futures-util", 362 | "http", 363 | "http-body", 364 | "hyper", 365 | "pin-project-lite", 366 | "socket2", 367 | "tokio", 368 | "tower-service", 369 | "tracing", 370 | ] 371 | 372 | [[package]] 373 | name = "icu_collections" 374 | version = "1.5.0" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" 377 | dependencies = [ 378 | "displaydoc", 379 | "yoke", 380 | "zerofrom", 381 | "zerovec", 382 | ] 383 | 384 | [[package]] 385 | name = "icu_locid" 386 | version = "1.5.0" 387 | source = "registry+https://github.com/rust-lang/crates.io-index" 388 | checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" 389 | dependencies = [ 390 | "displaydoc", 391 | "litemap", 392 | "tinystr", 393 | "writeable", 394 | "zerovec", 395 | ] 396 | 397 | [[package]] 398 | name = "icu_locid_transform" 399 | version = "1.5.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" 402 | dependencies = [ 403 | "displaydoc", 404 | "icu_locid", 405 | "icu_locid_transform_data", 406 | "icu_provider", 407 | "tinystr", 408 | "zerovec", 409 | ] 410 | 411 | [[package]] 412 | name = "icu_locid_transform_data" 413 | version = "1.5.0" 414 | source = "registry+https://github.com/rust-lang/crates.io-index" 415 | checksum = "fdc8ff3388f852bede6b579ad4e978ab004f139284d7b28715f773507b946f6e" 416 | 417 | [[package]] 418 | name = "icu_normalizer" 419 | version = "1.5.0" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" 422 | dependencies = [ 423 | "displaydoc", 424 | "icu_collections", 425 | "icu_normalizer_data", 426 | "icu_properties", 427 | "icu_provider", 428 | "smallvec", 429 | "utf16_iter", 430 | "utf8_iter", 431 | "write16", 432 | "zerovec", 433 | ] 434 | 435 | [[package]] 436 | name = "icu_normalizer_data" 437 | version = "1.5.0" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "f8cafbf7aa791e9b22bec55a167906f9e1215fd475cd22adfcf660e03e989516" 440 | 441 | [[package]] 442 | name = "icu_properties" 443 | version = "1.5.1" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" 446 | dependencies = [ 447 | "displaydoc", 448 | "icu_collections", 449 | "icu_locid_transform", 450 | "icu_properties_data", 451 | "icu_provider", 452 | "tinystr", 453 | "zerovec", 454 | ] 455 | 456 | [[package]] 457 | name = "icu_properties_data" 458 | version = "1.5.0" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "67a8effbc3dd3e4ba1afa8ad918d5684b8868b3b26500753effea8d2eed19569" 461 | 462 | [[package]] 463 | name = "icu_provider" 464 | version = "1.5.0" 465 | source = "registry+https://github.com/rust-lang/crates.io-index" 466 | checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" 467 | dependencies = [ 468 | "displaydoc", 469 | "icu_locid", 470 | "icu_provider_macros", 471 | "stable_deref_trait", 472 | "tinystr", 473 | "writeable", 474 | "yoke", 475 | "zerofrom", 476 | "zerovec", 477 | ] 478 | 479 | [[package]] 480 | name = "icu_provider_macros" 481 | version = "1.5.0" 482 | source = "registry+https://github.com/rust-lang/crates.io-index" 483 | checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" 484 | dependencies = [ 485 | "proc-macro2", 486 | "quote", 487 | "syn", 488 | ] 489 | 490 | [[package]] 491 | name = "idna" 492 | version = "1.0.3" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" 495 | dependencies = [ 496 | "idna_adapter", 497 | "smallvec", 498 | "utf8_iter", 499 | ] 500 | 501 | [[package]] 502 | name = "idna_adapter" 503 | version = "1.2.0" 504 | source = "registry+https://github.com/rust-lang/crates.io-index" 505 | checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" 506 | dependencies = [ 507 | "icu_normalizer", 508 | "icu_properties", 509 | ] 510 | 511 | [[package]] 512 | name = "ipnet" 513 | version = "2.10.1" 514 | source = "registry+https://github.com/rust-lang/crates.io-index" 515 | checksum = "ddc24109865250148c2e0f3d25d4f0f479571723792d3802153c60922a4fb708" 516 | 517 | [[package]] 518 | name = "itoa" 519 | version = "1.0.14" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" 522 | 523 | [[package]] 524 | name = "js-sys" 525 | version = "0.3.76" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "6717b6b5b077764fb5966237269cb3c64edddde4b14ce42647430a78ced9e7b7" 528 | dependencies = [ 529 | "once_cell", 530 | "wasm-bindgen", 531 | ] 532 | 533 | [[package]] 534 | name = "lazy_static" 535 | version = "1.5.0" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 538 | 539 | [[package]] 540 | name = "libc" 541 | version = "0.2.168" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" 544 | 545 | [[package]] 546 | name = "litemap" 547 | version = "0.7.4" 548 | source = "registry+https://github.com/rust-lang/crates.io-index" 549 | checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" 550 | 551 | [[package]] 552 | name = "lock_api" 553 | version = "0.4.12" 554 | source = "registry+https://github.com/rust-lang/crates.io-index" 555 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 556 | dependencies = [ 557 | "autocfg", 558 | "scopeguard", 559 | ] 560 | 561 | [[package]] 562 | name = "log" 563 | version = "0.4.22" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 566 | 567 | [[package]] 568 | name = "matchers" 569 | version = "0.1.0" 570 | source = "registry+https://github.com/rust-lang/crates.io-index" 571 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 572 | dependencies = [ 573 | "regex-automata 0.1.10", 574 | ] 575 | 576 | [[package]] 577 | name = "matchit" 578 | version = "0.8.4" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" 581 | 582 | [[package]] 583 | name = "memchr" 584 | version = "2.7.4" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 587 | 588 | [[package]] 589 | name = "mime" 590 | version = "0.3.17" 591 | source = "registry+https://github.com/rust-lang/crates.io-index" 592 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 593 | 594 | [[package]] 595 | name = "miniz_oxide" 596 | version = "0.8.2" 597 | source = "registry+https://github.com/rust-lang/crates.io-index" 598 | checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394" 599 | dependencies = [ 600 | "adler2", 601 | ] 602 | 603 | [[package]] 604 | name = "mio" 605 | version = "1.0.3" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" 608 | dependencies = [ 609 | "libc", 610 | "wasi", 611 | "windows-sys", 612 | ] 613 | 614 | [[package]] 615 | name = "no-std-compat" 616 | version = "0.4.1" 617 | source = "registry+https://github.com/rust-lang/crates.io-index" 618 | checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" 619 | 620 | [[package]] 621 | name = "nonempty" 622 | version = "0.7.0" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" 625 | 626 | [[package]] 627 | name = "nonzero_ext" 628 | version = "0.3.0" 629 | source = "registry+https://github.com/rust-lang/crates.io-index" 630 | checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" 631 | 632 | [[package]] 633 | name = "nu-ansi-term" 634 | version = "0.46.0" 635 | source = "registry+https://github.com/rust-lang/crates.io-index" 636 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 637 | dependencies = [ 638 | "overload", 639 | "winapi", 640 | ] 641 | 642 | [[package]] 643 | name = "object" 644 | version = "0.36.5" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "aedf0a2d09c573ed1d8d85b30c119153926a2b36dce0ab28322c09a117a4683e" 647 | dependencies = [ 648 | "memchr", 649 | ] 650 | 651 | [[package]] 652 | name = "once_cell" 653 | version = "1.20.2" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 656 | 657 | [[package]] 658 | name = "overload" 659 | version = "0.1.1" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 662 | 663 | [[package]] 664 | name = "parking_lot" 665 | version = "0.12.3" 666 | source = "registry+https://github.com/rust-lang/crates.io-index" 667 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 668 | dependencies = [ 669 | "lock_api", 670 | "parking_lot_core", 671 | ] 672 | 673 | [[package]] 674 | name = "parking_lot_core" 675 | version = "0.9.10" 676 | source = "registry+https://github.com/rust-lang/crates.io-index" 677 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 678 | dependencies = [ 679 | "cfg-if", 680 | "libc", 681 | "redox_syscall", 682 | "smallvec", 683 | "windows-targets", 684 | ] 685 | 686 | [[package]] 687 | name = "percent-encoding" 688 | version = "2.3.1" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 691 | 692 | [[package]] 693 | name = "pin-project" 694 | version = "1.1.7" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "be57f64e946e500c8ee36ef6331845d40a93055567ec57e8fae13efd33759b95" 697 | dependencies = [ 698 | "pin-project-internal", 699 | ] 700 | 701 | [[package]] 702 | name = "pin-project-internal" 703 | version = "1.1.7" 704 | source = "registry+https://github.com/rust-lang/crates.io-index" 705 | checksum = "3c0f5fad0874fc7abcd4d750e76917eaebbecaa2c20bde22e1dbeeba8beb758c" 706 | dependencies = [ 707 | "proc-macro2", 708 | "quote", 709 | "syn", 710 | ] 711 | 712 | [[package]] 713 | name = "pin-project-lite" 714 | version = "0.2.15" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "915a1e146535de9163f3987b8944ed8cf49a18bb0056bcebcdcece385cece4ff" 717 | 718 | [[package]] 719 | name = "pin-utils" 720 | version = "0.1.0" 721 | source = "registry+https://github.com/rust-lang/crates.io-index" 722 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 723 | 724 | [[package]] 725 | name = "portable-atomic" 726 | version = "1.10.0" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" 729 | 730 | [[package]] 731 | name = "ppv-lite86" 732 | version = "0.2.20" 733 | source = "registry+https://github.com/rust-lang/crates.io-index" 734 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 735 | dependencies = [ 736 | "zerocopy", 737 | ] 738 | 739 | [[package]] 740 | name = "proc-macro2" 741 | version = "1.0.92" 742 | source = "registry+https://github.com/rust-lang/crates.io-index" 743 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 744 | dependencies = [ 745 | "unicode-ident", 746 | ] 747 | 748 | [[package]] 749 | name = "quanta" 750 | version = "0.12.4" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "773ce68d0bb9bc7ef20be3536ffe94e223e1f365bd374108b2659fac0c65cfe6" 753 | dependencies = [ 754 | "crossbeam-utils", 755 | "libc", 756 | "once_cell", 757 | "raw-cpuid", 758 | "wasi", 759 | "web-sys", 760 | "winapi", 761 | ] 762 | 763 | [[package]] 764 | name = "quote" 765 | version = "1.0.37" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 768 | dependencies = [ 769 | "proc-macro2", 770 | ] 771 | 772 | [[package]] 773 | name = "rand" 774 | version = "0.8.5" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 777 | dependencies = [ 778 | "libc", 779 | "rand_chacha", 780 | "rand_core", 781 | ] 782 | 783 | [[package]] 784 | name = "rand_chacha" 785 | version = "0.3.1" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 788 | dependencies = [ 789 | "ppv-lite86", 790 | "rand_core", 791 | ] 792 | 793 | [[package]] 794 | name = "rand_core" 795 | version = "0.6.4" 796 | source = "registry+https://github.com/rust-lang/crates.io-index" 797 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 798 | dependencies = [ 799 | "getrandom", 800 | ] 801 | 802 | [[package]] 803 | name = "raw-cpuid" 804 | version = "11.2.0" 805 | source = "registry+https://github.com/rust-lang/crates.io-index" 806 | checksum = "1ab240315c661615f2ee9f0f2cd32d5a7343a84d5ebcccb99d46e6637565e7b0" 807 | dependencies = [ 808 | "bitflags", 809 | ] 810 | 811 | [[package]] 812 | name = "redox_syscall" 813 | version = "0.5.8" 814 | source = "registry+https://github.com/rust-lang/crates.io-index" 815 | checksum = "03a862b389f93e68874fbf580b9de08dd02facb9a788ebadaf4a3fd33cf58834" 816 | dependencies = [ 817 | "bitflags", 818 | ] 819 | 820 | [[package]] 821 | name = "regex" 822 | version = "1.11.1" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 825 | dependencies = [ 826 | "aho-corasick", 827 | "memchr", 828 | "regex-automata 0.4.9", 829 | "regex-syntax 0.8.5", 830 | ] 831 | 832 | [[package]] 833 | name = "regex-automata" 834 | version = "0.1.10" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 837 | dependencies = [ 838 | "regex-syntax 0.6.29", 839 | ] 840 | 841 | [[package]] 842 | name = "regex-automata" 843 | version = "0.4.9" 844 | source = "registry+https://github.com/rust-lang/crates.io-index" 845 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 846 | dependencies = [ 847 | "aho-corasick", 848 | "memchr", 849 | "regex-syntax 0.8.5", 850 | ] 851 | 852 | [[package]] 853 | name = "regex-syntax" 854 | version = "0.6.29" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" 857 | 858 | [[package]] 859 | name = "regex-syntax" 860 | version = "0.8.5" 861 | source = "registry+https://github.com/rust-lang/crates.io-index" 862 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 863 | 864 | [[package]] 865 | name = "reqwest" 866 | version = "0.12.9" 867 | source = "registry+https://github.com/rust-lang/crates.io-index" 868 | checksum = "a77c62af46e79de0a562e1a9849205ffcb7fc1238876e9bd743357570e04046f" 869 | dependencies = [ 870 | "base64", 871 | "bytes", 872 | "futures-core", 873 | "futures-util", 874 | "http", 875 | "http-body", 876 | "http-body-util", 877 | "hyper", 878 | "hyper-util", 879 | "ipnet", 880 | "js-sys", 881 | "log", 882 | "mime", 883 | "once_cell", 884 | "percent-encoding", 885 | "pin-project-lite", 886 | "serde", 887 | "serde_json", 888 | "serde_urlencoded", 889 | "sync_wrapper", 890 | "tokio", 891 | "tower-service", 892 | "url", 893 | "wasm-bindgen", 894 | "wasm-bindgen-futures", 895 | "web-sys", 896 | "windows-registry", 897 | ] 898 | 899 | [[package]] 900 | name = "rustc-demangle" 901 | version = "0.1.24" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 904 | 905 | [[package]] 906 | name = "rustversion" 907 | version = "1.0.18" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "0e819f2bc632f285be6d7cd36e25940d45b2391dd6d9b939e79de557f7014248" 910 | 911 | [[package]] 912 | name = "ryu" 913 | version = "1.0.18" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 916 | 917 | [[package]] 918 | name = "scopeguard" 919 | version = "1.2.0" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 922 | 923 | [[package]] 924 | name = "serde" 925 | version = "1.0.216" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" 928 | dependencies = [ 929 | "serde_derive", 930 | ] 931 | 932 | [[package]] 933 | name = "serde_derive" 934 | version = "1.0.216" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" 937 | dependencies = [ 938 | "proc-macro2", 939 | "quote", 940 | "syn", 941 | ] 942 | 943 | [[package]] 944 | name = "serde_json" 945 | version = "1.0.133" 946 | source = "registry+https://github.com/rust-lang/crates.io-index" 947 | checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" 948 | dependencies = [ 949 | "itoa", 950 | "memchr", 951 | "ryu", 952 | "serde", 953 | ] 954 | 955 | [[package]] 956 | name = "serde_path_to_error" 957 | version = "0.1.16" 958 | source = "registry+https://github.com/rust-lang/crates.io-index" 959 | checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6" 960 | dependencies = [ 961 | "itoa", 962 | "serde", 963 | ] 964 | 965 | [[package]] 966 | name = "serde_urlencoded" 967 | version = "0.7.1" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 970 | dependencies = [ 971 | "form_urlencoded", 972 | "itoa", 973 | "ryu", 974 | "serde", 975 | ] 976 | 977 | [[package]] 978 | name = "sharded-slab" 979 | version = "0.1.7" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" 982 | dependencies = [ 983 | "lazy_static", 984 | ] 985 | 986 | [[package]] 987 | name = "slab" 988 | version = "0.4.9" 989 | source = "registry+https://github.com/rust-lang/crates.io-index" 990 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 991 | dependencies = [ 992 | "autocfg", 993 | ] 994 | 995 | [[package]] 996 | name = "smallvec" 997 | version = "1.13.2" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1000 | 1001 | [[package]] 1002 | name = "socket2" 1003 | version = "0.5.8" 1004 | source = "registry+https://github.com/rust-lang/crates.io-index" 1005 | checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" 1006 | dependencies = [ 1007 | "libc", 1008 | "windows-sys", 1009 | ] 1010 | 1011 | [[package]] 1012 | name = "spinning_top" 1013 | version = "0.3.0" 1014 | source = "registry+https://github.com/rust-lang/crates.io-index" 1015 | checksum = "d96d2d1d716fb500937168cc09353ffdc7a012be8475ac7308e1bdf0e3923300" 1016 | dependencies = [ 1017 | "lock_api", 1018 | ] 1019 | 1020 | [[package]] 1021 | name = "stable_deref_trait" 1022 | version = "1.2.0" 1023 | source = "registry+https://github.com/rust-lang/crates.io-index" 1024 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1025 | 1026 | [[package]] 1027 | name = "syn" 1028 | version = "2.0.90" 1029 | source = "registry+https://github.com/rust-lang/crates.io-index" 1030 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 1031 | dependencies = [ 1032 | "proc-macro2", 1033 | "quote", 1034 | "unicode-ident", 1035 | ] 1036 | 1037 | [[package]] 1038 | name = "sync_wrapper" 1039 | version = "1.0.2" 1040 | source = "registry+https://github.com/rust-lang/crates.io-index" 1041 | checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" 1042 | dependencies = [ 1043 | "futures-core", 1044 | ] 1045 | 1046 | [[package]] 1047 | name = "synstructure" 1048 | version = "0.13.1" 1049 | source = "registry+https://github.com/rust-lang/crates.io-index" 1050 | checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" 1051 | dependencies = [ 1052 | "proc-macro2", 1053 | "quote", 1054 | "syn", 1055 | ] 1056 | 1057 | [[package]] 1058 | name = "thiserror" 1059 | version = "1.0.69" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" 1062 | dependencies = [ 1063 | "thiserror-impl 1.0.69", 1064 | ] 1065 | 1066 | [[package]] 1067 | name = "thiserror" 1068 | version = "2.0.7" 1069 | source = "registry+https://github.com/rust-lang/crates.io-index" 1070 | checksum = "93605438cbd668185516ab499d589afb7ee1859ea3d5fc8f6b0755e1c7443767" 1071 | dependencies = [ 1072 | "thiserror-impl 2.0.7", 1073 | ] 1074 | 1075 | [[package]] 1076 | name = "thiserror-impl" 1077 | version = "1.0.69" 1078 | source = "registry+https://github.com/rust-lang/crates.io-index" 1079 | checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" 1080 | dependencies = [ 1081 | "proc-macro2", 1082 | "quote", 1083 | "syn", 1084 | ] 1085 | 1086 | [[package]] 1087 | name = "thiserror-impl" 1088 | version = "2.0.7" 1089 | source = "registry+https://github.com/rust-lang/crates.io-index" 1090 | checksum = "e1d8749b4531af2117677a5fcd12b1348a3fe2b81e36e61ffeac5c4aa3273e36" 1091 | dependencies = [ 1092 | "proc-macro2", 1093 | "quote", 1094 | "syn", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "thread_local" 1099 | version = "1.1.8" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" 1102 | dependencies = [ 1103 | "cfg-if", 1104 | "once_cell", 1105 | ] 1106 | 1107 | [[package]] 1108 | name = "tinystr" 1109 | version = "0.7.6" 1110 | source = "registry+https://github.com/rust-lang/crates.io-index" 1111 | checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" 1112 | dependencies = [ 1113 | "displaydoc", 1114 | "zerovec", 1115 | ] 1116 | 1117 | [[package]] 1118 | name = "tokio" 1119 | version = "1.42.0" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "5cec9b21b0450273377fc97bd4c33a8acffc8c996c987a7c5b319a0083707551" 1122 | dependencies = [ 1123 | "backtrace", 1124 | "libc", 1125 | "mio", 1126 | "pin-project-lite", 1127 | "socket2", 1128 | "tokio-macros", 1129 | "windows-sys", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "tokio-macros" 1134 | version = "2.4.0" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1137 | dependencies = [ 1138 | "proc-macro2", 1139 | "quote", 1140 | "syn", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "tower" 1145 | version = "0.5.2" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" 1148 | dependencies = [ 1149 | "futures-core", 1150 | "futures-util", 1151 | "pin-project-lite", 1152 | "sync_wrapper", 1153 | "tokio", 1154 | "tower-layer", 1155 | "tower-service", 1156 | "tracing", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "tower-http" 1161 | version = "0.6.2" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "403fa3b783d4b626a8ad51d766ab03cb6d2dbfc46b1c5d4448395e6628dc9697" 1164 | dependencies = [ 1165 | "bitflags", 1166 | "bytes", 1167 | "http", 1168 | "http-body", 1169 | "pin-project-lite", 1170 | "tower-layer", 1171 | "tower-service", 1172 | "tracing", 1173 | ] 1174 | 1175 | [[package]] 1176 | name = "tower-layer" 1177 | version = "0.3.3" 1178 | source = "registry+https://github.com/rust-lang/crates.io-index" 1179 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1180 | 1181 | [[package]] 1182 | name = "tower-service" 1183 | version = "0.3.3" 1184 | source = "registry+https://github.com/rust-lang/crates.io-index" 1185 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1186 | 1187 | [[package]] 1188 | name = "tower_governor" 1189 | version = "0.7.0" 1190 | dependencies = [ 1191 | "axum", 1192 | "forwarded-header-value", 1193 | "governor", 1194 | "http", 1195 | "hyper", 1196 | "pin-project", 1197 | "reqwest", 1198 | "serde_json", 1199 | "thiserror 2.0.7", 1200 | "tokio", 1201 | "tower", 1202 | "tower-http", 1203 | "tracing", 1204 | "tracing-subscriber", 1205 | ] 1206 | 1207 | [[package]] 1208 | name = "tracing" 1209 | version = "0.1.41" 1210 | source = "registry+https://github.com/rust-lang/crates.io-index" 1211 | checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" 1212 | dependencies = [ 1213 | "log", 1214 | "pin-project-lite", 1215 | "tracing-attributes", 1216 | "tracing-core", 1217 | ] 1218 | 1219 | [[package]] 1220 | name = "tracing-attributes" 1221 | version = "0.1.28" 1222 | source = "registry+https://github.com/rust-lang/crates.io-index" 1223 | checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d" 1224 | dependencies = [ 1225 | "proc-macro2", 1226 | "quote", 1227 | "syn", 1228 | ] 1229 | 1230 | [[package]] 1231 | name = "tracing-core" 1232 | version = "0.1.33" 1233 | source = "registry+https://github.com/rust-lang/crates.io-index" 1234 | checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" 1235 | dependencies = [ 1236 | "once_cell", 1237 | "valuable", 1238 | ] 1239 | 1240 | [[package]] 1241 | name = "tracing-log" 1242 | version = "0.2.0" 1243 | source = "registry+https://github.com/rust-lang/crates.io-index" 1244 | checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" 1245 | dependencies = [ 1246 | "log", 1247 | "once_cell", 1248 | "tracing-core", 1249 | ] 1250 | 1251 | [[package]] 1252 | name = "tracing-subscriber" 1253 | version = "0.3.19" 1254 | source = "registry+https://github.com/rust-lang/crates.io-index" 1255 | checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" 1256 | dependencies = [ 1257 | "matchers", 1258 | "nu-ansi-term", 1259 | "once_cell", 1260 | "regex", 1261 | "sharded-slab", 1262 | "smallvec", 1263 | "thread_local", 1264 | "tracing", 1265 | "tracing-core", 1266 | "tracing-log", 1267 | ] 1268 | 1269 | [[package]] 1270 | name = "try-lock" 1271 | version = "0.2.5" 1272 | source = "registry+https://github.com/rust-lang/crates.io-index" 1273 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1274 | 1275 | [[package]] 1276 | name = "unicode-ident" 1277 | version = "1.0.14" 1278 | source = "registry+https://github.com/rust-lang/crates.io-index" 1279 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 1280 | 1281 | [[package]] 1282 | name = "url" 1283 | version = "2.5.4" 1284 | source = "registry+https://github.com/rust-lang/crates.io-index" 1285 | checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" 1286 | dependencies = [ 1287 | "form_urlencoded", 1288 | "idna", 1289 | "percent-encoding", 1290 | ] 1291 | 1292 | [[package]] 1293 | name = "utf16_iter" 1294 | version = "1.0.5" 1295 | source = "registry+https://github.com/rust-lang/crates.io-index" 1296 | checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" 1297 | 1298 | [[package]] 1299 | name = "utf8_iter" 1300 | version = "1.0.4" 1301 | source = "registry+https://github.com/rust-lang/crates.io-index" 1302 | checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" 1303 | 1304 | [[package]] 1305 | name = "valuable" 1306 | version = "0.1.0" 1307 | source = "registry+https://github.com/rust-lang/crates.io-index" 1308 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1309 | 1310 | [[package]] 1311 | name = "want" 1312 | version = "0.3.1" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1315 | dependencies = [ 1316 | "try-lock", 1317 | ] 1318 | 1319 | [[package]] 1320 | name = "wasi" 1321 | version = "0.11.0+wasi-snapshot-preview1" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1324 | 1325 | [[package]] 1326 | name = "wasm-bindgen" 1327 | version = "0.2.99" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396" 1330 | dependencies = [ 1331 | "cfg-if", 1332 | "once_cell", 1333 | "wasm-bindgen-macro", 1334 | ] 1335 | 1336 | [[package]] 1337 | name = "wasm-bindgen-backend" 1338 | version = "0.2.99" 1339 | source = "registry+https://github.com/rust-lang/crates.io-index" 1340 | checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79" 1341 | dependencies = [ 1342 | "bumpalo", 1343 | "log", 1344 | "proc-macro2", 1345 | "quote", 1346 | "syn", 1347 | "wasm-bindgen-shared", 1348 | ] 1349 | 1350 | [[package]] 1351 | name = "wasm-bindgen-futures" 1352 | version = "0.4.49" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "38176d9b44ea84e9184eff0bc34cc167ed044f816accfe5922e54d84cf48eca2" 1355 | dependencies = [ 1356 | "cfg-if", 1357 | "js-sys", 1358 | "once_cell", 1359 | "wasm-bindgen", 1360 | "web-sys", 1361 | ] 1362 | 1363 | [[package]] 1364 | name = "wasm-bindgen-macro" 1365 | version = "0.2.99" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe" 1368 | dependencies = [ 1369 | "quote", 1370 | "wasm-bindgen-macro-support", 1371 | ] 1372 | 1373 | [[package]] 1374 | name = "wasm-bindgen-macro-support" 1375 | version = "0.2.99" 1376 | source = "registry+https://github.com/rust-lang/crates.io-index" 1377 | checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2" 1378 | dependencies = [ 1379 | "proc-macro2", 1380 | "quote", 1381 | "syn", 1382 | "wasm-bindgen-backend", 1383 | "wasm-bindgen-shared", 1384 | ] 1385 | 1386 | [[package]] 1387 | name = "wasm-bindgen-shared" 1388 | version = "0.2.99" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6" 1391 | 1392 | [[package]] 1393 | name = "web-sys" 1394 | version = "0.3.76" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "04dd7223427d52553d3702c004d3b2fe07c148165faa56313cb00211e31c12bc" 1397 | dependencies = [ 1398 | "js-sys", 1399 | "wasm-bindgen", 1400 | ] 1401 | 1402 | [[package]] 1403 | name = "winapi" 1404 | version = "0.3.9" 1405 | source = "registry+https://github.com/rust-lang/crates.io-index" 1406 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1407 | dependencies = [ 1408 | "winapi-i686-pc-windows-gnu", 1409 | "winapi-x86_64-pc-windows-gnu", 1410 | ] 1411 | 1412 | [[package]] 1413 | name = "winapi-i686-pc-windows-gnu" 1414 | version = "0.4.0" 1415 | source = "registry+https://github.com/rust-lang/crates.io-index" 1416 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1417 | 1418 | [[package]] 1419 | name = "winapi-x86_64-pc-windows-gnu" 1420 | version = "0.4.0" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1423 | 1424 | [[package]] 1425 | name = "windows-registry" 1426 | version = "0.2.0" 1427 | source = "registry+https://github.com/rust-lang/crates.io-index" 1428 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 1429 | dependencies = [ 1430 | "windows-result", 1431 | "windows-strings", 1432 | "windows-targets", 1433 | ] 1434 | 1435 | [[package]] 1436 | name = "windows-result" 1437 | version = "0.2.0" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 1440 | dependencies = [ 1441 | "windows-targets", 1442 | ] 1443 | 1444 | [[package]] 1445 | name = "windows-strings" 1446 | version = "0.1.0" 1447 | source = "registry+https://github.com/rust-lang/crates.io-index" 1448 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 1449 | dependencies = [ 1450 | "windows-result", 1451 | "windows-targets", 1452 | ] 1453 | 1454 | [[package]] 1455 | name = "windows-sys" 1456 | version = "0.52.0" 1457 | source = "registry+https://github.com/rust-lang/crates.io-index" 1458 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1459 | dependencies = [ 1460 | "windows-targets", 1461 | ] 1462 | 1463 | [[package]] 1464 | name = "windows-targets" 1465 | version = "0.52.6" 1466 | source = "registry+https://github.com/rust-lang/crates.io-index" 1467 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1468 | dependencies = [ 1469 | "windows_aarch64_gnullvm", 1470 | "windows_aarch64_msvc", 1471 | "windows_i686_gnu", 1472 | "windows_i686_gnullvm", 1473 | "windows_i686_msvc", 1474 | "windows_x86_64_gnu", 1475 | "windows_x86_64_gnullvm", 1476 | "windows_x86_64_msvc", 1477 | ] 1478 | 1479 | [[package]] 1480 | name = "windows_aarch64_gnullvm" 1481 | version = "0.52.6" 1482 | source = "registry+https://github.com/rust-lang/crates.io-index" 1483 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1484 | 1485 | [[package]] 1486 | name = "windows_aarch64_msvc" 1487 | version = "0.52.6" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1490 | 1491 | [[package]] 1492 | name = "windows_i686_gnu" 1493 | version = "0.52.6" 1494 | source = "registry+https://github.com/rust-lang/crates.io-index" 1495 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1496 | 1497 | [[package]] 1498 | name = "windows_i686_gnullvm" 1499 | version = "0.52.6" 1500 | source = "registry+https://github.com/rust-lang/crates.io-index" 1501 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1502 | 1503 | [[package]] 1504 | name = "windows_i686_msvc" 1505 | version = "0.52.6" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1508 | 1509 | [[package]] 1510 | name = "windows_x86_64_gnu" 1511 | version = "0.52.6" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1514 | 1515 | [[package]] 1516 | name = "windows_x86_64_gnullvm" 1517 | version = "0.52.6" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1520 | 1521 | [[package]] 1522 | name = "windows_x86_64_msvc" 1523 | version = "0.52.6" 1524 | source = "registry+https://github.com/rust-lang/crates.io-index" 1525 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1526 | 1527 | [[package]] 1528 | name = "write16" 1529 | version = "1.0.0" 1530 | source = "registry+https://github.com/rust-lang/crates.io-index" 1531 | checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" 1532 | 1533 | [[package]] 1534 | name = "writeable" 1535 | version = "0.5.5" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" 1538 | 1539 | [[package]] 1540 | name = "yoke" 1541 | version = "0.7.5" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" 1544 | dependencies = [ 1545 | "serde", 1546 | "stable_deref_trait", 1547 | "yoke-derive", 1548 | "zerofrom", 1549 | ] 1550 | 1551 | [[package]] 1552 | name = "yoke-derive" 1553 | version = "0.7.5" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" 1556 | dependencies = [ 1557 | "proc-macro2", 1558 | "quote", 1559 | "syn", 1560 | "synstructure", 1561 | ] 1562 | 1563 | [[package]] 1564 | name = "zerocopy" 1565 | version = "0.7.35" 1566 | source = "registry+https://github.com/rust-lang/crates.io-index" 1567 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1568 | dependencies = [ 1569 | "byteorder", 1570 | "zerocopy-derive", 1571 | ] 1572 | 1573 | [[package]] 1574 | name = "zerocopy-derive" 1575 | version = "0.7.35" 1576 | source = "registry+https://github.com/rust-lang/crates.io-index" 1577 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 1578 | dependencies = [ 1579 | "proc-macro2", 1580 | "quote", 1581 | "syn", 1582 | ] 1583 | 1584 | [[package]] 1585 | name = "zerofrom" 1586 | version = "0.1.5" 1587 | source = "registry+https://github.com/rust-lang/crates.io-index" 1588 | checksum = "cff3ee08c995dee1859d998dea82f7374f2826091dd9cd47def953cae446cd2e" 1589 | dependencies = [ 1590 | "zerofrom-derive", 1591 | ] 1592 | 1593 | [[package]] 1594 | name = "zerofrom-derive" 1595 | version = "0.1.5" 1596 | source = "registry+https://github.com/rust-lang/crates.io-index" 1597 | checksum = "595eed982f7d355beb85837f651fa22e90b3c044842dc7f2c2842c086f295808" 1598 | dependencies = [ 1599 | "proc-macro2", 1600 | "quote", 1601 | "syn", 1602 | "synstructure", 1603 | ] 1604 | 1605 | [[package]] 1606 | name = "zerovec" 1607 | version = "0.10.4" 1608 | source = "registry+https://github.com/rust-lang/crates.io-index" 1609 | checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" 1610 | dependencies = [ 1611 | "yoke", 1612 | "zerofrom", 1613 | "zerovec-derive", 1614 | ] 1615 | 1616 | [[package]] 1617 | name = "zerovec-derive" 1618 | version = "0.10.3" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" 1621 | dependencies = [ 1622 | "proc-macro2", 1623 | "quote", 1624 | "syn", 1625 | ] 1626 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tower_governor" 3 | authors = ["Ben Wishovich "] 4 | description = "A rate-limiting middleware for Tower backed by the governor crate that allows configurable key based and global limits" 5 | repository = "https://github.com/benwis/tower-governor" 6 | license = "MIT OR Apache-2.0" 7 | readme = "README.md" 8 | version = "0.7.0" 9 | edition = "2021" 10 | keywords = ["axum", "tower", "tonic", "rate-limit", "governor"] 11 | categories = ["web-programming::http-server"] 12 | 13 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 14 | 15 | [dependencies] 16 | forwarded-header-value = "0.1.1" 17 | governor = "0.8.0" 18 | http = "1.0.0" 19 | pin-project = "1.0.12" 20 | thiserror = "2.0.0" 21 | tower = "0.5.1" 22 | tracing = { version = "0.1.37", features = ["attributes"] } 23 | 24 | axum = { version = "0.8", optional = true } 25 | 26 | [dev-dependencies] 27 | tokio = { version = "1", features = ["macros", "rt-multi-thread"] } 28 | hyper = "1" 29 | reqwest = { version = "0.12", default-features = false, features = ["json"] } 30 | serde_json = "1.0.89" 31 | tower = { version = "0.5", features = ["util"] } 32 | tower-http = { version = "0.6", features = ["trace"] } 33 | tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } 34 | 35 | [features] 36 | default = ["axum"] 37 | # Enables support for axum web framework 38 | axum = ["dep:axum"] 39 | # Enables tracing output for this middleware 40 | tracing = [] 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ben Wishovich 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | A Tower service and layer that provides a rate-limiting backend by [governor](https://github.com/antifuchs/governor). Based heavily on the work done for [actix-governor](https://github.com/AaronErhardt/actix-governor). Works with Axum, Hyper, Tonic, and anything else based on Tower! 2 | 3 | # Features: 4 | 5 | + Rate limit requests based on peer IP address, IP address headers, globally, or via custom keys 6 | + Custom traffic limiting criteria per second, or to certain bursts 7 | + Simple to use 8 | + High customizability 9 | + High performance 10 | + Robust yet flexible API 11 | 12 | 13 | # How does it work? 14 | 15 | Each governor middleware has a configuration that stores a quota. 16 | The quota specifies how many requests can be sent from an IP address 17 | before the middleware starts blocking further requests. 18 | 19 | For example if the quota allowed ten requests a client could send a burst of 20 | ten requests in short time before the middleware starts blocking. 21 | 22 | Once at least one element of the quota was used the elements of the quota 23 | will be replenished after a specified period. 24 | 25 | For example if this period was 2 seconds and the quota was empty 26 | it would take 2 seconds to replenish one element of the quota. 27 | This means you could send one request every two seconds on average. 28 | 29 | If there was a quota that allowed ten requests with the same period 30 | a client could again send a burst of ten requests and then had to wait 31 | two seconds before sending further requests or 20 seconds before the full 32 | quota would be replenished and he could send another burst. 33 | 34 | # Example 35 | ```rust,no_run 36 | use axum::{error_handling::HandleErrorLayer, routing::get, BoxError, Router}; 37 | use std::net::SocketAddr; 38 | use std::sync::Arc; 39 | use std::time::Duration; 40 | use tokio::net::TcpListener; 41 | use tower::ServiceBuilder; 42 | use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer}; 43 | 44 | async fn hello() -> &'static str { 45 | "Hello world" 46 | } 47 | 48 | #[tokio::main] 49 | async fn main() { 50 | // Configure tracing if desired 51 | // construct a subscriber that prints formatted traces to stdout 52 | let subscriber = tracing_subscriber::FmtSubscriber::new(); 53 | // use that subscriber to process traces emitted after this point 54 | tracing::subscriber::set_global_default(subscriber).unwrap(); 55 | 56 | // Allow bursts with up to five requests per IP address 57 | // and replenishes one element every two seconds 58 | // We Box it because Axum 0.6 requires all Layers to be Clone 59 | // and thus we need a static reference to it 60 | let governor_conf = Arc::new( 61 | GovernorConfigBuilder::default() 62 | .per_second(2) 63 | .burst_size(5) 64 | .finish() 65 | .unwrap(), 66 | ); 67 | 68 | let governor_limiter = governor_conf.limiter().clone(); 69 | let interval = Duration::from_secs(60); 70 | // a separate background task to clean up 71 | std::thread::spawn(move || { 72 | loop { 73 | std::thread::sleep(interval); 74 | tracing::info!("rate limiting storage size: {}", governor_limiter.len()); 75 | governor_limiter.retain_recent(); 76 | } 77 | }); 78 | 79 | // build our application with a route 80 | let app = Router::new() 81 | // `GET /` goes to `root` 82 | .route("/", get(hello)) 83 | .layer(GovernorLayer { 84 | config: governor_conf, 85 | }); 86 | 87 | // run our app with hyper 88 | // `axum::Server` is a re-export of `hyper::Server` 89 | let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); 90 | tracing::debug!("listening on {}", addr); 91 | let listener = TcpListener::bind(addr).await.unwrap(); 92 | axum::serve(listener, app.into_make_service_with_connect_info::()) 93 | .await 94 | .unwrap(); 95 | } 96 | ``` 97 | 98 | # Configuration presets 99 | 100 | Instead of using the configuration builder you can use predefined presets. 101 | 102 | + [`GovernorConfig::default()`](https://docs.rs/tower_governor/latest/tower_governor/governor/struct.GovernorConfig.html#method.default): The default configuration which is suitable for most services. Allows bursts with up to eight requests and replenishes one element after 500ms, based on peer IP. 103 | 104 | + [`GovernorConfig::secure()`](https://docs.rs/tower_governor/latest/tower_governor/governor/struct.GovernorConfig.html#method.secure): A default configuration for security related services. 105 | Allows bursts with up to two requests and replenishes one element after four seconds, based on peer IP. 106 | 107 | For example the secure configuration can be used as a short version of this code: 108 | 109 | ```rust 110 | use tower_governor::governor::GovernorConfigBuilder; 111 | 112 | let config = GovernorConfigBuilder::default() 113 | .per_second(4) 114 | .burst_size(2) 115 | .finish() 116 | .unwrap(); 117 | ``` 118 | 119 | # Customize rate limiting key 120 | 121 | By default, rate limiting is done using the peer IP address (i.e. the IP address of the HTTP client that requested your app: either your user or a reverse proxy, depending on your deployment setup). 122 | You can configure a different behavior which: 123 | 1. can be useful in itself 124 | 2. allows you to setup multiple instances of this middleware based on different keys (for example, if you want to apply rate limiting with different rates on IP and API keys at the same time) 125 | 126 | This is achieved by defining a [KeyExtractor] and giving it to a [Governor] instance. 127 | Three ready-to-use key extractors are provided: 128 | - [PeerIpKeyExtractor]: this is the default, it uses the peer IP address of the request. 129 | - [SmartIpKeyExtractor]: Looks for common IP identification headers usually provided by reverse proxies in order(x-forwarded-for,x-real-ip, forwarded) and falls back to the peer IP address. 130 | - [GlobalKeyExtractor]: uses the same key for all incoming requests 131 | 132 | Check out the [custom_key_bearer](https://github.com/benwis/tower-governor/blob/main/examples/src/custom_key_bearer.rs) example for more information. 133 | 134 | # Crate feature flags 135 | 136 | tower-governor uses [feature flags](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section) to reduce the amount of compiled code and it is possible to enable certain features over others. Below is a list of the available feature flags: 137 | - `axum`: Enables support for axum web framework 138 | - `tracing`: Enables tracing output for this middleware 139 | 140 | ### Example for no-default-features 141 | 142 | - Disabling [`default` feature](https://doc.rust-lang.org/cargo/reference/features.html#the-default-feature) will change behavior of [PeerIpKeyExtractor] and [SmartIpKeyExtractor]: These two key extractors will expect [SocketAddr] type from [Request]'s [Extensions]. 143 | - Fail to provide valid `SocketAddr` could result in [GovernorError::UnableToExtractKey]. 144 | 145 | Cargo.toml 146 | ```toml 147 | [dependencies] 148 | tower-governor = { version = "0.3", default-features = false } 149 | ``` 150 | main.rs 151 | ```rust 152 | use std::{convert::Infallible, net::SocketAddr}; 153 | use std::sync::Arc; 154 | 155 | use http::{Request, Response}; 156 | use tower::{service_fn, ServiceBuilder, ServiceExt}; 157 | use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer}; 158 | # async fn service() -> Result<(), Box> { 159 | 160 | // service function expecting rate limiting by governor. 161 | let service = service_fn(|_: Request<()>| async { 162 | Ok::<_, Infallible>(Response::new(axum::body::Body::from("mock response"))) 163 | }); 164 | 165 | let config = Arc::new(GovernorConfigBuilder::default().finish().unwrap()); 166 | 167 | // build service with governor layer 168 | let service = ServiceBuilder::new() 169 | // the caller of service must provide SocketAddr to governor layer middleware 170 | .map_request(|(mut req, addr): (Request<()>, SocketAddr)| { 171 | // insert SocketAddr to request's extensions and governor is expecting it. 172 | req.extensions_mut().insert(addr); 173 | req 174 | }) 175 | .layer(GovernorLayer { config }) 176 | .service(service); 177 | 178 | // mock client socket addr and http request. 179 | let addr = "127.0.0.1:12345".parse().unwrap(); 180 | let req = Request::default(); 181 | 182 | // execute service 183 | service.oneshot((req, addr)).await?; 184 | # Ok(()) 185 | # } 186 | ``` 187 | 188 | [SocketAddr]: std::net::SocketAddr 189 | [Request]: http::Request 190 | [Extensions]: http::Extensions 191 | 192 | 193 | # Add x-ratelimit headers 194 | 195 | By default, `x-ratelimit-after` and `retry-after` headers are being sent. If you want to add `x-ratelimit-limit`, `x-ratelimit-whitelisted` and `x-ratelimit-remaining` use the [`.use_headers()`](https://docs.rs/tower_governor/latest/tower_governor/governor/struct.GovernorConfigBuilder.html#method.use_headers) method on your GovernorConfig. 196 | 197 | 198 | # Error Handling 199 | 200 | This crate surfaces a GovernorError with suggested headers, and includes [`GovernorConfigBuilder::error_handler`] method that will turn those errors into a Response. Feel free to provide your own error handler that takes in [`GovernorError`] and returns a [`Response`](https://docs.rs/http/latest/http/response/struct.Response.html). 201 | 202 | [`GovernorConfigBuilder::error_handler`]: crate::governor::GovernorConfigBuilder::error_handler 203 | 204 | # Common pitfalls 205 | 206 | 1. Do not construct the same configuration multiple times, unless explicitly wanted! 207 | This will create an independent rate limiter for each configuration! Instead pass the same configuration reference into [`Governor::new()`](https://docs.rs/tower_governor/latest/tower_governor/governor/struct.Governor.html#method.new), like it is described in the example. 208 | 209 | 2. Be careful to create your server with [`.into_make_service_with_connect_info::`](https://docs.rs/axum/latest/axum/struct.Router.html#method.into_make_service_with_connect_info) instead of `.into_make_service()` if you are using the default PeerIpKeyExtractor. Otherwise there will be no peer ip address for Tower to find! 210 | -------------------------------------------------------------------------------- /examples/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.21.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "async-trait" 22 | version = "0.1.74" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "a66537f1bb974b254c98ed142ff995236e81b9d0fe4db0575f46612cb15eb0f9" 25 | dependencies = [ 26 | "proc-macro2", 27 | "quote", 28 | "syn 2.0.39", 29 | ] 30 | 31 | [[package]] 32 | name = "autocfg" 33 | version = "1.1.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 36 | 37 | [[package]] 38 | name = "axum" 39 | version = "0.7.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "202651474fe73c62d9e0a56c6133f7a0ff1dc1c8cf7a5b03381af2a26553ac9d" 42 | dependencies = [ 43 | "async-trait", 44 | "axum-core", 45 | "bytes", 46 | "futures-util", 47 | "http", 48 | "http-body", 49 | "http-body-util", 50 | "hyper", 51 | "hyper-util", 52 | "itoa", 53 | "matchit", 54 | "memchr", 55 | "mime", 56 | "percent-encoding", 57 | "pin-project-lite", 58 | "rustversion", 59 | "serde", 60 | "serde_json", 61 | "serde_path_to_error", 62 | "serde_urlencoded", 63 | "sync_wrapper", 64 | "tokio", 65 | "tower", 66 | "tower-layer", 67 | "tower-service", 68 | ] 69 | 70 | [[package]] 71 | name = "axum-core" 72 | version = "0.4.1" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "77cb22c689c44d4c07b0ab44ebc25d69d8ae601a2f28fb8d672d344178fa17aa" 75 | dependencies = [ 76 | "async-trait", 77 | "bytes", 78 | "futures-util", 79 | "http", 80 | "http-body", 81 | "http-body-util", 82 | "mime", 83 | "pin-project-lite", 84 | "rustversion", 85 | "sync_wrapper", 86 | "tower-layer", 87 | "tower-service", 88 | ] 89 | 90 | [[package]] 91 | name = "backtrace" 92 | version = "0.3.69" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "2089b7e3f35b9dd2d0ed921ead4f6d318c27680d4a5bd167b3ee120edb105837" 95 | dependencies = [ 96 | "addr2line", 97 | "cc", 98 | "cfg-if", 99 | "libc", 100 | "miniz_oxide", 101 | "object", 102 | "rustc-demangle", 103 | ] 104 | 105 | [[package]] 106 | name = "bitflags" 107 | version = "1.3.2" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 110 | 111 | [[package]] 112 | name = "bumpalo" 113 | version = "3.11.1" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "572f695136211188308f16ad2ca5c851a712c464060ae6974944458eb83880ba" 116 | 117 | [[package]] 118 | name = "bytes" 119 | version = "1.3.0" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" 122 | 123 | [[package]] 124 | name = "cc" 125 | version = "1.0.83" 126 | source = "registry+https://github.com/rust-lang/crates.io-index" 127 | checksum = "f1174fb0b6ec23863f8b971027804a42614e347eafb0a95bf0b12cdae21fc4d0" 128 | dependencies = [ 129 | "libc", 130 | ] 131 | 132 | [[package]] 133 | name = "cfg-if" 134 | version = "1.0.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 137 | 138 | [[package]] 139 | name = "crossbeam-utils" 140 | version = "0.8.14" 141 | source = "registry+https://github.com/rust-lang/crates.io-index" 142 | checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" 143 | dependencies = [ 144 | "cfg-if", 145 | ] 146 | 147 | [[package]] 148 | name = "dashmap" 149 | version = "5.4.0" 150 | source = "registry+https://github.com/rust-lang/crates.io-index" 151 | checksum = "907076dfda823b0b36d2a1bb5f90c96660a5bbcd7729e10727f07858f22c4edc" 152 | dependencies = [ 153 | "cfg-if", 154 | "hashbrown 0.12.3", 155 | "lock_api", 156 | "once_cell", 157 | "parking_lot_core", 158 | ] 159 | 160 | [[package]] 161 | name = "equivalent" 162 | version = "1.0.1" 163 | source = "registry+https://github.com/rust-lang/crates.io-index" 164 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 165 | 166 | [[package]] 167 | name = "examples" 168 | version = "0.1.0" 169 | dependencies = [ 170 | "axum", 171 | "governor", 172 | "http", 173 | "serde", 174 | "tokio", 175 | "tower", 176 | "tower_governor", 177 | "tracing", 178 | "tracing-subscriber", 179 | ] 180 | 181 | [[package]] 182 | name = "fnv" 183 | version = "1.0.7" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 186 | 187 | [[package]] 188 | name = "form_urlencoded" 189 | version = "1.1.0" 190 | source = "registry+https://github.com/rust-lang/crates.io-index" 191 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 192 | dependencies = [ 193 | "percent-encoding", 194 | ] 195 | 196 | [[package]] 197 | name = "forwarded-header-value" 198 | version = "0.1.1" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | checksum = "8835f84f38484cc86f110a805655697908257fb9a7af005234060891557198e9" 201 | dependencies = [ 202 | "nonempty", 203 | "thiserror", 204 | ] 205 | 206 | [[package]] 207 | name = "futures" 208 | version = "0.3.25" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" 211 | dependencies = [ 212 | "futures-channel", 213 | "futures-core", 214 | "futures-executor", 215 | "futures-io", 216 | "futures-sink", 217 | "futures-task", 218 | "futures-util", 219 | ] 220 | 221 | [[package]] 222 | name = "futures-channel" 223 | version = "0.3.25" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" 226 | dependencies = [ 227 | "futures-core", 228 | "futures-sink", 229 | ] 230 | 231 | [[package]] 232 | name = "futures-core" 233 | version = "0.3.25" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" 236 | 237 | [[package]] 238 | name = "futures-executor" 239 | version = "0.3.25" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" 242 | dependencies = [ 243 | "futures-core", 244 | "futures-task", 245 | "futures-util", 246 | ] 247 | 248 | [[package]] 249 | name = "futures-io" 250 | version = "0.3.25" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" 253 | 254 | [[package]] 255 | name = "futures-macro" 256 | version = "0.3.25" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" 259 | dependencies = [ 260 | "proc-macro2", 261 | "quote", 262 | "syn 1.0.105", 263 | ] 264 | 265 | [[package]] 266 | name = "futures-sink" 267 | version = "0.3.25" 268 | source = "registry+https://github.com/rust-lang/crates.io-index" 269 | checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" 270 | 271 | [[package]] 272 | name = "futures-task" 273 | version = "0.3.25" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" 276 | 277 | [[package]] 278 | name = "futures-timer" 279 | version = "3.0.2" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "e64b03909df88034c26dc1547e8970b91f98bdb65165d6a4e9110d94263dbb2c" 282 | 283 | [[package]] 284 | name = "futures-util" 285 | version = "0.3.25" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" 288 | dependencies = [ 289 | "futures-channel", 290 | "futures-core", 291 | "futures-io", 292 | "futures-macro", 293 | "futures-sink", 294 | "futures-task", 295 | "memchr", 296 | "pin-project-lite", 297 | "pin-utils", 298 | "slab", 299 | ] 300 | 301 | [[package]] 302 | name = "getrandom" 303 | version = "0.2.8" 304 | source = "registry+https://github.com/rust-lang/crates.io-index" 305 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 306 | dependencies = [ 307 | "cfg-if", 308 | "libc", 309 | "wasi", 310 | ] 311 | 312 | [[package]] 313 | name = "gimli" 314 | version = "0.28.1" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" 317 | 318 | [[package]] 319 | name = "governor" 320 | version = "0.6.0" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "821239e5672ff23e2a7060901fa622950bbd80b649cdaadd78d1c1767ed14eb4" 323 | dependencies = [ 324 | "cfg-if", 325 | "dashmap", 326 | "futures", 327 | "futures-timer", 328 | "no-std-compat", 329 | "nonzero_ext", 330 | "parking_lot", 331 | "quanta", 332 | "rand", 333 | "smallvec", 334 | ] 335 | 336 | [[package]] 337 | name = "h2" 338 | version = "0.4.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "e1d308f63daf4181410c242d34c11f928dcb3aa105852019e043c9d1f4e4368a" 341 | dependencies = [ 342 | "bytes", 343 | "fnv", 344 | "futures-core", 345 | "futures-sink", 346 | "futures-util", 347 | "http", 348 | "indexmap", 349 | "slab", 350 | "tokio", 351 | "tokio-util", 352 | "tracing", 353 | ] 354 | 355 | [[package]] 356 | name = "hashbrown" 357 | version = "0.12.3" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 360 | 361 | [[package]] 362 | name = "hashbrown" 363 | version = "0.14.3" 364 | source = "registry+https://github.com/rust-lang/crates.io-index" 365 | checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" 366 | 367 | [[package]] 368 | name = "hermit-abi" 369 | version = "0.1.19" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 372 | dependencies = [ 373 | "libc", 374 | ] 375 | 376 | [[package]] 377 | name = "http" 378 | version = "1.0.0" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "b32afd38673a8016f7c9ae69e5af41a58f81b1d31689040f2f1959594ce194ea" 381 | dependencies = [ 382 | "bytes", 383 | "fnv", 384 | "itoa", 385 | ] 386 | 387 | [[package]] 388 | name = "http-body" 389 | version = "1.0.0" 390 | source = "registry+https://github.com/rust-lang/crates.io-index" 391 | checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" 392 | dependencies = [ 393 | "bytes", 394 | "http", 395 | ] 396 | 397 | [[package]] 398 | name = "http-body-util" 399 | version = "0.1.0" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "41cb79eb393015dadd30fc252023adb0b2400a0caee0fa2a077e6e21a551e840" 402 | dependencies = [ 403 | "bytes", 404 | "futures-util", 405 | "http", 406 | "http-body", 407 | "pin-project-lite", 408 | ] 409 | 410 | [[package]] 411 | name = "httparse" 412 | version = "1.8.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 415 | 416 | [[package]] 417 | name = "httpdate" 418 | version = "1.0.2" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 421 | 422 | [[package]] 423 | name = "hyper" 424 | version = "1.0.1" 425 | source = "registry+https://github.com/rust-lang/crates.io-index" 426 | checksum = "403f9214f3e703236b221f1a9cd88ec8b4adfa5296de01ab96216361f4692f56" 427 | dependencies = [ 428 | "bytes", 429 | "futures-channel", 430 | "futures-util", 431 | "h2", 432 | "http", 433 | "http-body", 434 | "httparse", 435 | "httpdate", 436 | "itoa", 437 | "pin-project-lite", 438 | "tokio", 439 | ] 440 | 441 | [[package]] 442 | name = "hyper-util" 443 | version = "0.1.1" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "9ca339002caeb0d159cc6e023dff48e199f081e42fa039895c7c6f38b37f2e9d" 446 | dependencies = [ 447 | "bytes", 448 | "futures-channel", 449 | "futures-util", 450 | "http", 451 | "http-body", 452 | "hyper", 453 | "pin-project-lite", 454 | "socket2 0.5.5", 455 | "tokio", 456 | "tower", 457 | "tower-service", 458 | "tracing", 459 | ] 460 | 461 | [[package]] 462 | name = "indexmap" 463 | version = "2.1.0" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "d530e1a18b1cb4c484e6e34556a0d948706958449fca0cab753d649f2bce3d1f" 466 | dependencies = [ 467 | "equivalent", 468 | "hashbrown 0.14.3", 469 | ] 470 | 471 | [[package]] 472 | name = "itoa" 473 | version = "1.0.9" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" 476 | 477 | [[package]] 478 | name = "js-sys" 479 | version = "0.3.60" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "49409df3e3bf0856b916e2ceaca09ee28e6871cf7d9ce97a692cacfdb2a25a47" 482 | dependencies = [ 483 | "wasm-bindgen", 484 | ] 485 | 486 | [[package]] 487 | name = "lazy_static" 488 | version = "1.4.0" 489 | source = "registry+https://github.com/rust-lang/crates.io-index" 490 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 491 | 492 | [[package]] 493 | name = "libc" 494 | version = "0.2.150" 495 | source = "registry+https://github.com/rust-lang/crates.io-index" 496 | checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" 497 | 498 | [[package]] 499 | name = "lock_api" 500 | version = "0.4.9" 501 | source = "registry+https://github.com/rust-lang/crates.io-index" 502 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 503 | dependencies = [ 504 | "autocfg", 505 | "scopeguard", 506 | ] 507 | 508 | [[package]] 509 | name = "log" 510 | version = "0.4.17" 511 | source = "registry+https://github.com/rust-lang/crates.io-index" 512 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 513 | dependencies = [ 514 | "cfg-if", 515 | ] 516 | 517 | [[package]] 518 | name = "mach2" 519 | version = "0.4.1" 520 | source = "registry+https://github.com/rust-lang/crates.io-index" 521 | checksum = "6d0d1830bcd151a6fc4aea1369af235b36c1528fe976b8ff678683c9995eade8" 522 | dependencies = [ 523 | "libc", 524 | ] 525 | 526 | [[package]] 527 | name = "matchit" 528 | version = "0.7.0" 529 | source = "registry+https://github.com/rust-lang/crates.io-index" 530 | checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" 531 | 532 | [[package]] 533 | name = "memchr" 534 | version = "2.5.0" 535 | source = "registry+https://github.com/rust-lang/crates.io-index" 536 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 537 | 538 | [[package]] 539 | name = "mime" 540 | version = "0.3.16" 541 | source = "registry+https://github.com/rust-lang/crates.io-index" 542 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 543 | 544 | [[package]] 545 | name = "miniz_oxide" 546 | version = "0.7.1" 547 | source = "registry+https://github.com/rust-lang/crates.io-index" 548 | checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" 549 | dependencies = [ 550 | "adler", 551 | ] 552 | 553 | [[package]] 554 | name = "mio" 555 | version = "0.8.9" 556 | source = "registry+https://github.com/rust-lang/crates.io-index" 557 | checksum = "3dce281c5e46beae905d4de1870d8b1509a9142b62eedf18b443b011ca8343d0" 558 | dependencies = [ 559 | "libc", 560 | "wasi", 561 | "windows-sys 0.48.0", 562 | ] 563 | 564 | [[package]] 565 | name = "no-std-compat" 566 | version = "0.4.1" 567 | source = "registry+https://github.com/rust-lang/crates.io-index" 568 | checksum = "b93853da6d84c2e3c7d730d6473e8817692dd89be387eb01b94d7f108ecb5b8c" 569 | 570 | [[package]] 571 | name = "nonempty" 572 | version = "0.7.0" 573 | source = "registry+https://github.com/rust-lang/crates.io-index" 574 | checksum = "e9e591e719385e6ebaeb5ce5d3887f7d5676fceca6411d1925ccc95745f3d6f7" 575 | 576 | [[package]] 577 | name = "nonzero_ext" 578 | version = "0.3.0" 579 | source = "registry+https://github.com/rust-lang/crates.io-index" 580 | checksum = "38bf9645c8b145698bb0b18a4637dcacbc421ea49bef2317e4fd8065a387cf21" 581 | 582 | [[package]] 583 | name = "nu-ansi-term" 584 | version = "0.46.0" 585 | source = "registry+https://github.com/rust-lang/crates.io-index" 586 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 587 | dependencies = [ 588 | "overload", 589 | "winapi", 590 | ] 591 | 592 | [[package]] 593 | name = "num_cpus" 594 | version = "1.14.0" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "f6058e64324c71e02bc2b150e4f3bc8286db6c83092132ffa3f6b1eab0f9def5" 597 | dependencies = [ 598 | "hermit-abi", 599 | "libc", 600 | ] 601 | 602 | [[package]] 603 | name = "object" 604 | version = "0.32.1" 605 | source = "registry+https://github.com/rust-lang/crates.io-index" 606 | checksum = "9cf5f9dd3933bd50a9e1f149ec995f39ae2c496d31fd772c1fd45ebc27e902b0" 607 | dependencies = [ 608 | "memchr", 609 | ] 610 | 611 | [[package]] 612 | name = "once_cell" 613 | version = "1.16.0" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "86f0b0d4bf799edbc74508c1e8bf170ff5f41238e5f8225603ca7caaae2b7860" 616 | 617 | [[package]] 618 | name = "overload" 619 | version = "0.1.1" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 622 | 623 | [[package]] 624 | name = "parking_lot" 625 | version = "0.12.1" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 628 | dependencies = [ 629 | "lock_api", 630 | "parking_lot_core", 631 | ] 632 | 633 | [[package]] 634 | name = "parking_lot_core" 635 | version = "0.9.5" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" 638 | dependencies = [ 639 | "cfg-if", 640 | "libc", 641 | "redox_syscall", 642 | "smallvec", 643 | "windows-sys 0.42.0", 644 | ] 645 | 646 | [[package]] 647 | name = "percent-encoding" 648 | version = "2.2.0" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 651 | 652 | [[package]] 653 | name = "pin-project" 654 | version = "1.0.12" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" 657 | dependencies = [ 658 | "pin-project-internal", 659 | ] 660 | 661 | [[package]] 662 | name = "pin-project-internal" 663 | version = "1.0.12" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" 666 | dependencies = [ 667 | "proc-macro2", 668 | "quote", 669 | "syn 1.0.105", 670 | ] 671 | 672 | [[package]] 673 | name = "pin-project-lite" 674 | version = "0.2.9" 675 | source = "registry+https://github.com/rust-lang/crates.io-index" 676 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 677 | 678 | [[package]] 679 | name = "pin-utils" 680 | version = "0.1.0" 681 | source = "registry+https://github.com/rust-lang/crates.io-index" 682 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 683 | 684 | [[package]] 685 | name = "ppv-lite86" 686 | version = "0.2.17" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 689 | 690 | [[package]] 691 | name = "proc-macro2" 692 | version = "1.0.70" 693 | source = "registry+https://github.com/rust-lang/crates.io-index" 694 | checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" 695 | dependencies = [ 696 | "unicode-ident", 697 | ] 698 | 699 | [[package]] 700 | name = "quanta" 701 | version = "0.11.1" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "a17e662a7a8291a865152364c20c7abc5e60486ab2001e8ec10b24862de0b9ab" 704 | dependencies = [ 705 | "crossbeam-utils", 706 | "libc", 707 | "mach2", 708 | "once_cell", 709 | "raw-cpuid", 710 | "wasi", 711 | "web-sys", 712 | "winapi", 713 | ] 714 | 715 | [[package]] 716 | name = "quote" 717 | version = "1.0.33" 718 | source = "registry+https://github.com/rust-lang/crates.io-index" 719 | checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" 720 | dependencies = [ 721 | "proc-macro2", 722 | ] 723 | 724 | [[package]] 725 | name = "rand" 726 | version = "0.8.5" 727 | source = "registry+https://github.com/rust-lang/crates.io-index" 728 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 729 | dependencies = [ 730 | "libc", 731 | "rand_chacha", 732 | "rand_core", 733 | ] 734 | 735 | [[package]] 736 | name = "rand_chacha" 737 | version = "0.3.1" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 740 | dependencies = [ 741 | "ppv-lite86", 742 | "rand_core", 743 | ] 744 | 745 | [[package]] 746 | name = "rand_core" 747 | version = "0.6.4" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 750 | dependencies = [ 751 | "getrandom", 752 | ] 753 | 754 | [[package]] 755 | name = "raw-cpuid" 756 | version = "10.6.0" 757 | source = "registry+https://github.com/rust-lang/crates.io-index" 758 | checksum = "a6823ea29436221176fe662da99998ad3b4db2c7f31e7b6f5fe43adccd6320bb" 759 | dependencies = [ 760 | "bitflags", 761 | ] 762 | 763 | [[package]] 764 | name = "redox_syscall" 765 | version = "0.2.16" 766 | source = "registry+https://github.com/rust-lang/crates.io-index" 767 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 768 | dependencies = [ 769 | "bitflags", 770 | ] 771 | 772 | [[package]] 773 | name = "rustc-demangle" 774 | version = "0.1.23" 775 | source = "registry+https://github.com/rust-lang/crates.io-index" 776 | checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 777 | 778 | [[package]] 779 | name = "rustversion" 780 | version = "1.0.9" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "97477e48b4cf8603ad5f7aaf897467cf42ab4218a38ef76fb14c2d6773a6d6a8" 783 | 784 | [[package]] 785 | name = "ryu" 786 | version = "1.0.11" 787 | source = "registry+https://github.com/rust-lang/crates.io-index" 788 | checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" 789 | 790 | [[package]] 791 | name = "scopeguard" 792 | version = "1.1.0" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 795 | 796 | [[package]] 797 | name = "serde" 798 | version = "1.0.149" 799 | source = "registry+https://github.com/rust-lang/crates.io-index" 800 | checksum = "256b9932320c590e707b94576e3cc1f7c9024d0ee6612dfbcf1cb106cbe8e055" 801 | dependencies = [ 802 | "serde_derive", 803 | ] 804 | 805 | [[package]] 806 | name = "serde_derive" 807 | version = "1.0.149" 808 | source = "registry+https://github.com/rust-lang/crates.io-index" 809 | checksum = "b4eae9b04cbffdfd550eb462ed33bc6a1b68c935127d008b27444d08380f94e4" 810 | dependencies = [ 811 | "proc-macro2", 812 | "quote", 813 | "syn 1.0.105", 814 | ] 815 | 816 | [[package]] 817 | name = "serde_json" 818 | version = "1.0.89" 819 | source = "registry+https://github.com/rust-lang/crates.io-index" 820 | checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" 821 | dependencies = [ 822 | "itoa", 823 | "ryu", 824 | "serde", 825 | ] 826 | 827 | [[package]] 828 | name = "serde_path_to_error" 829 | version = "0.1.8" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "184c643044780f7ceb59104cef98a5a6f12cb2288a7bc701ab93a362b49fd47d" 832 | dependencies = [ 833 | "serde", 834 | ] 835 | 836 | [[package]] 837 | name = "serde_urlencoded" 838 | version = "0.7.1" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 841 | dependencies = [ 842 | "form_urlencoded", 843 | "itoa", 844 | "ryu", 845 | "serde", 846 | ] 847 | 848 | [[package]] 849 | name = "sharded-slab" 850 | version = "0.1.4" 851 | source = "registry+https://github.com/rust-lang/crates.io-index" 852 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 853 | dependencies = [ 854 | "lazy_static", 855 | ] 856 | 857 | [[package]] 858 | name = "signal-hook-registry" 859 | version = "1.4.0" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 862 | dependencies = [ 863 | "libc", 864 | ] 865 | 866 | [[package]] 867 | name = "slab" 868 | version = "0.4.7" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 871 | dependencies = [ 872 | "autocfg", 873 | ] 874 | 875 | [[package]] 876 | name = "smallvec" 877 | version = "1.10.0" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 880 | 881 | [[package]] 882 | name = "socket2" 883 | version = "0.4.10" 884 | source = "registry+https://github.com/rust-lang/crates.io-index" 885 | checksum = "9f7916fc008ca5542385b89a3d3ce689953c143e9304a9bf8beec1de48994c0d" 886 | dependencies = [ 887 | "libc", 888 | "winapi", 889 | ] 890 | 891 | [[package]] 892 | name = "socket2" 893 | version = "0.5.5" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "7b5fac59a5cb5dd637972e5fca70daf0523c9067fcdc4842f053dae04a18f8e9" 896 | dependencies = [ 897 | "libc", 898 | "windows-sys 0.48.0", 899 | ] 900 | 901 | [[package]] 902 | name = "syn" 903 | version = "1.0.105" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" 906 | dependencies = [ 907 | "proc-macro2", 908 | "quote", 909 | "unicode-ident", 910 | ] 911 | 912 | [[package]] 913 | name = "syn" 914 | version = "2.0.39" 915 | source = "registry+https://github.com/rust-lang/crates.io-index" 916 | checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" 917 | dependencies = [ 918 | "proc-macro2", 919 | "quote", 920 | "unicode-ident", 921 | ] 922 | 923 | [[package]] 924 | name = "sync_wrapper" 925 | version = "0.1.1" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" 928 | 929 | [[package]] 930 | name = "thiserror" 931 | version = "1.0.37" 932 | source = "registry+https://github.com/rust-lang/crates.io-index" 933 | checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" 934 | dependencies = [ 935 | "thiserror-impl", 936 | ] 937 | 938 | [[package]] 939 | name = "thiserror-impl" 940 | version = "1.0.37" 941 | source = "registry+https://github.com/rust-lang/crates.io-index" 942 | checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" 943 | dependencies = [ 944 | "proc-macro2", 945 | "quote", 946 | "syn 1.0.105", 947 | ] 948 | 949 | [[package]] 950 | name = "thread_local" 951 | version = "1.1.4" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 954 | dependencies = [ 955 | "once_cell", 956 | ] 957 | 958 | [[package]] 959 | name = "tokio" 960 | version = "1.29.1" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "532826ff75199d5833b9d2c5fe410f29235e25704ee5f0ef599fb51c21f4a4da" 963 | dependencies = [ 964 | "autocfg", 965 | "backtrace", 966 | "bytes", 967 | "libc", 968 | "mio", 969 | "num_cpus", 970 | "parking_lot", 971 | "pin-project-lite", 972 | "signal-hook-registry", 973 | "socket2 0.4.10", 974 | "tokio-macros", 975 | "windows-sys 0.48.0", 976 | ] 977 | 978 | [[package]] 979 | name = "tokio-macros" 980 | version = "2.1.0" 981 | source = "registry+https://github.com/rust-lang/crates.io-index" 982 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 983 | dependencies = [ 984 | "proc-macro2", 985 | "quote", 986 | "syn 2.0.39", 987 | ] 988 | 989 | [[package]] 990 | name = "tokio-util" 991 | version = "0.7.8" 992 | source = "registry+https://github.com/rust-lang/crates.io-index" 993 | checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" 994 | dependencies = [ 995 | "bytes", 996 | "futures-core", 997 | "futures-sink", 998 | "pin-project-lite", 999 | "tokio", 1000 | "tracing", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "tower" 1005 | version = "0.4.13" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1008 | dependencies = [ 1009 | "futures-core", 1010 | "futures-util", 1011 | "pin-project", 1012 | "pin-project-lite", 1013 | "tokio", 1014 | "tower-layer", 1015 | "tower-service", 1016 | "tracing", 1017 | ] 1018 | 1019 | [[package]] 1020 | name = "tower-layer" 1021 | version = "0.3.2" 1022 | source = "registry+https://github.com/rust-lang/crates.io-index" 1023 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1024 | 1025 | [[package]] 1026 | name = "tower-service" 1027 | version = "0.3.2" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1030 | 1031 | [[package]] 1032 | name = "tower_governor" 1033 | version = "0.3.0" 1034 | dependencies = [ 1035 | "axum", 1036 | "forwarded-header-value", 1037 | "governor", 1038 | "http", 1039 | "pin-project", 1040 | "thiserror", 1041 | "tower", 1042 | "tracing", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "tracing" 1047 | version = "0.1.37" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1050 | dependencies = [ 1051 | "cfg-if", 1052 | "log", 1053 | "pin-project-lite", 1054 | "tracing-attributes", 1055 | "tracing-core", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "tracing-attributes" 1060 | version = "0.1.23" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 1063 | dependencies = [ 1064 | "proc-macro2", 1065 | "quote", 1066 | "syn 1.0.105", 1067 | ] 1068 | 1069 | [[package]] 1070 | name = "tracing-core" 1071 | version = "0.1.30" 1072 | source = "registry+https://github.com/rust-lang/crates.io-index" 1073 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 1074 | dependencies = [ 1075 | "once_cell", 1076 | "valuable", 1077 | ] 1078 | 1079 | [[package]] 1080 | name = "tracing-log" 1081 | version = "0.1.3" 1082 | source = "registry+https://github.com/rust-lang/crates.io-index" 1083 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 1084 | dependencies = [ 1085 | "lazy_static", 1086 | "log", 1087 | "tracing-core", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "tracing-subscriber" 1092 | version = "0.3.16" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" 1095 | dependencies = [ 1096 | "nu-ansi-term", 1097 | "sharded-slab", 1098 | "smallvec", 1099 | "thread_local", 1100 | "tracing-core", 1101 | "tracing-log", 1102 | ] 1103 | 1104 | [[package]] 1105 | name = "unicode-ident" 1106 | version = "1.0.5" 1107 | source = "registry+https://github.com/rust-lang/crates.io-index" 1108 | checksum = "6ceab39d59e4c9499d4e5a8ee0e2735b891bb7308ac83dfb4e80cad195c9f6f3" 1109 | 1110 | [[package]] 1111 | name = "valuable" 1112 | version = "0.1.0" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1115 | 1116 | [[package]] 1117 | name = "wasi" 1118 | version = "0.11.0+wasi-snapshot-preview1" 1119 | source = "registry+https://github.com/rust-lang/crates.io-index" 1120 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1121 | 1122 | [[package]] 1123 | name = "wasm-bindgen" 1124 | version = "0.2.83" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "eaf9f5aceeec8be17c128b2e93e031fb8a4d469bb9c4ae2d7dc1888b26887268" 1127 | dependencies = [ 1128 | "cfg-if", 1129 | "wasm-bindgen-macro", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "wasm-bindgen-backend" 1134 | version = "0.2.83" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "4c8ffb332579b0557b52d268b91feab8df3615f265d5270fec2a8c95b17c1142" 1137 | dependencies = [ 1138 | "bumpalo", 1139 | "log", 1140 | "once_cell", 1141 | "proc-macro2", 1142 | "quote", 1143 | "syn 1.0.105", 1144 | "wasm-bindgen-shared", 1145 | ] 1146 | 1147 | [[package]] 1148 | name = "wasm-bindgen-macro" 1149 | version = "0.2.83" 1150 | source = "registry+https://github.com/rust-lang/crates.io-index" 1151 | checksum = "052be0f94026e6cbc75cdefc9bae13fd6052cdcaf532fa6c45e7ae33a1e6c810" 1152 | dependencies = [ 1153 | "quote", 1154 | "wasm-bindgen-macro-support", 1155 | ] 1156 | 1157 | [[package]] 1158 | name = "wasm-bindgen-macro-support" 1159 | version = "0.2.83" 1160 | source = "registry+https://github.com/rust-lang/crates.io-index" 1161 | checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" 1162 | dependencies = [ 1163 | "proc-macro2", 1164 | "quote", 1165 | "syn 1.0.105", 1166 | "wasm-bindgen-backend", 1167 | "wasm-bindgen-shared", 1168 | ] 1169 | 1170 | [[package]] 1171 | name = "wasm-bindgen-shared" 1172 | version = "0.2.83" 1173 | source = "registry+https://github.com/rust-lang/crates.io-index" 1174 | checksum = "1c38c045535d93ec4f0b4defec448e4291638ee608530863b1e2ba115d4fff7f" 1175 | 1176 | [[package]] 1177 | name = "web-sys" 1178 | version = "0.3.60" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "bcda906d8be16e728fd5adc5b729afad4e444e106ab28cd1c7256e54fa61510f" 1181 | dependencies = [ 1182 | "js-sys", 1183 | "wasm-bindgen", 1184 | ] 1185 | 1186 | [[package]] 1187 | name = "winapi" 1188 | version = "0.3.9" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1191 | dependencies = [ 1192 | "winapi-i686-pc-windows-gnu", 1193 | "winapi-x86_64-pc-windows-gnu", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "winapi-i686-pc-windows-gnu" 1198 | version = "0.4.0" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1201 | 1202 | [[package]] 1203 | name = "winapi-x86_64-pc-windows-gnu" 1204 | version = "0.4.0" 1205 | source = "registry+https://github.com/rust-lang/crates.io-index" 1206 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1207 | 1208 | [[package]] 1209 | name = "windows-sys" 1210 | version = "0.42.0" 1211 | source = "registry+https://github.com/rust-lang/crates.io-index" 1212 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 1213 | dependencies = [ 1214 | "windows_aarch64_gnullvm 0.42.0", 1215 | "windows_aarch64_msvc 0.42.0", 1216 | "windows_i686_gnu 0.42.0", 1217 | "windows_i686_msvc 0.42.0", 1218 | "windows_x86_64_gnu 0.42.0", 1219 | "windows_x86_64_gnullvm 0.42.0", 1220 | "windows_x86_64_msvc 0.42.0", 1221 | ] 1222 | 1223 | [[package]] 1224 | name = "windows-sys" 1225 | version = "0.48.0" 1226 | source = "registry+https://github.com/rust-lang/crates.io-index" 1227 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 1228 | dependencies = [ 1229 | "windows-targets", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "windows-targets" 1234 | version = "0.48.5" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" 1237 | dependencies = [ 1238 | "windows_aarch64_gnullvm 0.48.5", 1239 | "windows_aarch64_msvc 0.48.5", 1240 | "windows_i686_gnu 0.48.5", 1241 | "windows_i686_msvc 0.48.5", 1242 | "windows_x86_64_gnu 0.48.5", 1243 | "windows_x86_64_gnullvm 0.48.5", 1244 | "windows_x86_64_msvc 0.48.5", 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "windows_aarch64_gnullvm" 1249 | version = "0.42.0" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 1252 | 1253 | [[package]] 1254 | name = "windows_aarch64_gnullvm" 1255 | version = "0.48.5" 1256 | source = "registry+https://github.com/rust-lang/crates.io-index" 1257 | checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" 1258 | 1259 | [[package]] 1260 | name = "windows_aarch64_msvc" 1261 | version = "0.42.0" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 1264 | 1265 | [[package]] 1266 | name = "windows_aarch64_msvc" 1267 | version = "0.48.5" 1268 | source = "registry+https://github.com/rust-lang/crates.io-index" 1269 | checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" 1270 | 1271 | [[package]] 1272 | name = "windows_i686_gnu" 1273 | version = "0.42.0" 1274 | source = "registry+https://github.com/rust-lang/crates.io-index" 1275 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 1276 | 1277 | [[package]] 1278 | name = "windows_i686_gnu" 1279 | version = "0.48.5" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" 1282 | 1283 | [[package]] 1284 | name = "windows_i686_msvc" 1285 | version = "0.42.0" 1286 | source = "registry+https://github.com/rust-lang/crates.io-index" 1287 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 1288 | 1289 | [[package]] 1290 | name = "windows_i686_msvc" 1291 | version = "0.48.5" 1292 | source = "registry+https://github.com/rust-lang/crates.io-index" 1293 | checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" 1294 | 1295 | [[package]] 1296 | name = "windows_x86_64_gnu" 1297 | version = "0.42.0" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 1300 | 1301 | [[package]] 1302 | name = "windows_x86_64_gnu" 1303 | version = "0.48.5" 1304 | source = "registry+https://github.com/rust-lang/crates.io-index" 1305 | checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" 1306 | 1307 | [[package]] 1308 | name = "windows_x86_64_gnullvm" 1309 | version = "0.42.0" 1310 | source = "registry+https://github.com/rust-lang/crates.io-index" 1311 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 1312 | 1313 | [[package]] 1314 | name = "windows_x86_64_gnullvm" 1315 | version = "0.48.5" 1316 | source = "registry+https://github.com/rust-lang/crates.io-index" 1317 | checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" 1318 | 1319 | [[package]] 1320 | name = "windows_x86_64_msvc" 1321 | version = "0.42.0" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 1324 | 1325 | [[package]] 1326 | name = "windows_x86_64_msvc" 1327 | version = "0.48.5" 1328 | source = "registry+https://github.com/rust-lang/crates.io-index" 1329 | checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" 1330 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Ben Wishovich "] 3 | edition = "2021" 4 | license = "MIT" 5 | name = "examples" 6 | publish = false 7 | version = "0.1.0" 8 | 9 | [[bin]] 10 | name = "basic" 11 | path = "src/basic.rs" 12 | 13 | [[bin]] 14 | name = "custom_key_bearer" 15 | path = "src/custom_key_bearer.rs" 16 | 17 | 18 | 19 | [dependencies] 20 | axum = "0.7" 21 | tower_governor={path="../", features=["tracing"]} 22 | tokio = { version = "1.23.0", features = ["full"] } 23 | tracing = {version="0.1.37", features=["attributes"]} 24 | tracing-subscriber = "0.3" 25 | tower = "0.5.1" 26 | serde = { version = "1.0.149", features = ["derive"] } 27 | http = "1.0.0" 28 | governor = "0.7.0" 29 | -------------------------------------------------------------------------------- /examples/src/basic.rs: -------------------------------------------------------------------------------- 1 | use axum::{routing::get, Router}; 2 | use std::net::SocketAddr; 3 | use std::sync::Arc; 4 | use std::time::Duration; 5 | use tokio::net::TcpListener; 6 | use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer}; 7 | 8 | async fn hello() -> &'static str { 9 | "Hello world" 10 | } 11 | 12 | #[tokio::main] 13 | async fn main() { 14 | // Configure tracing if desired 15 | // construct a subscriber that prints formatted traces to stdout 16 | let subscriber = tracing_subscriber::FmtSubscriber::new(); 17 | // use that subscriber to process traces emitted after this point 18 | tracing::subscriber::set_global_default(subscriber).unwrap(); 19 | 20 | // Allow bursts with up to five requests per IP address 21 | // and replenishes one element every two seconds 22 | // We Box it because Axum 0.6 requires all Layers to be Clone 23 | // and thus we need a static reference to it 24 | let governor_conf = Arc::new( 25 | GovernorConfigBuilder::default() 26 | .per_second(2) 27 | .burst_size(5) 28 | .finish() 29 | .unwrap(), 30 | ); 31 | 32 | let governor_limiter = governor_conf.limiter().clone(); 33 | let interval = Duration::from_secs(60); 34 | // a separate background task to clean up 35 | std::thread::spawn(move || loop { 36 | std::thread::sleep(interval); 37 | tracing::info!("rate limiting storage size: {}", governor_limiter.len()); 38 | governor_limiter.retain_recent(); 39 | }); 40 | 41 | // build our application with a route 42 | let app = Router::new() 43 | // `GET /` goes to `root` 44 | .route("/", get(hello)) 45 | .layer(GovernorLayer { 46 | config: governor_conf, 47 | }); 48 | 49 | // run our app with hyper 50 | // `axum::Server` is a re-export of `hyper::Server` 51 | let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); 52 | tracing::debug!("listening on {}", addr); 53 | let listener = TcpListener::bind(addr).await.unwrap(); 54 | axum::serve( 55 | listener, 56 | app.into_make_service_with_connect_info::(), 57 | ) 58 | .await 59 | .unwrap(); 60 | } 61 | -------------------------------------------------------------------------------- /examples/src/custom_key_bearer.rs: -------------------------------------------------------------------------------- 1 | use axum::{routing::get, Router}; 2 | use http::{request::Request, StatusCode}; 3 | use serde::{Deserialize, Serialize}; 4 | use std::net::SocketAddr; 5 | use std::sync::Arc; 6 | use tokio::net::TcpListener; 7 | use tower_governor::{ 8 | errors::GovernorError, governor::GovernorConfigBuilder, key_extractor::KeyExtractor, 9 | GovernorLayer, 10 | }; 11 | 12 | #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq)] 13 | struct UserToken; 14 | 15 | impl KeyExtractor for UserToken { 16 | type Key = String; 17 | 18 | fn extract(&self, req: &Request) -> Result { 19 | req.headers() 20 | .get("Authorization") 21 | .and_then(|token| token.to_str().ok()) 22 | .and_then(|token| token.strip_prefix("Bearer ")) 23 | .and_then(|token| Some(token.trim().to_owned())) 24 | .ok_or(GovernorError::Other { 25 | code: StatusCode::UNAUTHORIZED, 26 | msg: Some("You don't have permission to access".to_string()), 27 | headers: None, 28 | }) 29 | } 30 | fn key_name(&self, key: &Self::Key) -> Option { 31 | Some(format!("{}", key)) 32 | } 33 | fn name(&self) -> &'static str { 34 | "UserToken" 35 | } 36 | } 37 | 38 | async fn hello() -> &'static str { 39 | "Hello world" 40 | } 41 | 42 | #[tokio::main] 43 | async fn main() { 44 | // Configure tracing if desired 45 | // construct a subscriber that prints formatted traces to stdout 46 | let subscriber = tracing_subscriber::FmtSubscriber::new(); 47 | // use that subscriber to process traces emitted after this point 48 | tracing::subscriber::set_global_default(subscriber).unwrap(); 49 | 50 | // Allow bursts with up to five requests per IP address 51 | // and replenishes one element every twenty seconds 52 | let governor_conf = Arc::new( 53 | GovernorConfigBuilder::default() 54 | .per_second(20) 55 | .burst_size(5) 56 | .key_extractor(UserToken) 57 | .use_headers() 58 | .finish() 59 | .unwrap(), 60 | ); 61 | 62 | // build our application with a route 63 | let app = Router::new() 64 | // `GET /` goes to `root` 65 | .route("/", get(hello)) 66 | .layer(GovernorLayer { 67 | config: governor_conf, 68 | }); 69 | 70 | // run our app with hyper 71 | // `axum::Server` is a re-export of `hyper::Server` 72 | let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); 73 | tracing::debug!("listening on {}", addr); 74 | let listener = TcpListener::bind(addr).await.unwrap(); 75 | axum::serve( 76 | listener, 77 | app.into_make_service_with_connect_info::(), 78 | ) 79 | .await 80 | .unwrap(); 81 | } 82 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1701680307, 9 | "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "flake-utils_2": { 22 | "inputs": { 23 | "systems": "systems_2" 24 | }, 25 | "locked": { 26 | "lastModified": 1681202837, 27 | "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", 28 | "owner": "numtide", 29 | "repo": "flake-utils", 30 | "rev": "cfacdce06f30d2b68473a46042957675eebb3401", 31 | "type": "github" 32 | }, 33 | "original": { 34 | "owner": "numtide", 35 | "repo": "flake-utils", 36 | "type": "github" 37 | } 38 | }, 39 | "nixpkgs": { 40 | "locked": { 41 | "lastModified": 1701436327, 42 | "narHash": "sha256-tRHbnoNI8SIM5O5xuxOmtSLnswEByzmnQcGGyNRjxsE=", 43 | "owner": "NixOS", 44 | "repo": "nixpkgs", 45 | "rev": "91050ea1e57e50388fa87a3302ba12d188ef723a", 46 | "type": "github" 47 | }, 48 | "original": { 49 | "owner": "NixOS", 50 | "ref": "nixos-unstable", 51 | "repo": "nixpkgs", 52 | "type": "github" 53 | } 54 | }, 55 | "nixpkgs_2": { 56 | "locked": { 57 | "lastModified": 1681358109, 58 | "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", 59 | "owner": "NixOS", 60 | "repo": "nixpkgs", 61 | "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", 62 | "type": "github" 63 | }, 64 | "original": { 65 | "owner": "NixOS", 66 | "ref": "nixpkgs-unstable", 67 | "repo": "nixpkgs", 68 | "type": "github" 69 | } 70 | }, 71 | "root": { 72 | "inputs": { 73 | "flake-utils": "flake-utils", 74 | "nixpkgs": "nixpkgs", 75 | "rust-overlay": "rust-overlay" 76 | } 77 | }, 78 | "rust-overlay": { 79 | "inputs": { 80 | "flake-utils": "flake-utils_2", 81 | "nixpkgs": "nixpkgs_2" 82 | }, 83 | "locked": { 84 | "lastModified": 1701742626, 85 | "narHash": "sha256-ASuWURoeuV7xKZEVSCJsdHidrgprJexNkFWU/cfZ5LE=", 86 | "owner": "oxalica", 87 | "repo": "rust-overlay", 88 | "rev": "1f48c08cae1b2c4d5f201a77abfe31fc3b95a4cf", 89 | "type": "github" 90 | }, 91 | "original": { 92 | "owner": "oxalica", 93 | "repo": "rust-overlay", 94 | "type": "github" 95 | } 96 | }, 97 | "systems": { 98 | "locked": { 99 | "lastModified": 1681028828, 100 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 101 | "owner": "nix-systems", 102 | "repo": "default", 103 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 104 | "type": "github" 105 | }, 106 | "original": { 107 | "owner": "nix-systems", 108 | "repo": "default", 109 | "type": "github" 110 | } 111 | }, 112 | "systems_2": { 113 | "locked": { 114 | "lastModified": 1681028828, 115 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 116 | "owner": "nix-systems", 117 | "repo": "default", 118 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 119 | "type": "github" 120 | }, 121 | "original": { 122 | "owner": "nix-systems", 123 | "repo": "default", 124 | "type": "github" 125 | } 126 | } 127 | }, 128 | "root": "root", 129 | "version": 7 130 | } 131 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | 2 | { 3 | description = "A basic Rust devshell"; 4 | 5 | inputs = { 6 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 7 | rust-overlay.url = "github:oxalica/rust-overlay"; 8 | flake-utils.url = "github:numtide/flake-utils"; 9 | }; 10 | 11 | outputs = { self, nixpkgs, rust-overlay, flake-utils, ... }: 12 | flake-utils.lib.eachDefaultSystem (system: 13 | let 14 | overlays = [ (import rust-overlay) ]; 15 | pkgs = import nixpkgs { 16 | inherit system overlays; 17 | }; 18 | in 19 | with pkgs; 20 | { 21 | devShells.default = mkShell { 22 | buildInputs = [ 23 | openssl 24 | pkg-config 25 | (rust-bin.stable.latest.default.override { 26 | extensions= [ "rust-src" "rust-analyzer" ]; 27 | targets = [ "wasm32-unknown-unknown" ]; 28 | }) 29 | ]; 30 | 31 | shellHook = '' 32 | ''; 33 | }; 34 | } 35 | ); 36 | } 37 | -------------------------------------------------------------------------------- /src/errors.rs: -------------------------------------------------------------------------------- 1 | use http::{HeaderMap, Response, StatusCode}; 2 | use std::mem; 3 | use thiserror::Error; 4 | 5 | /// The error type returned by tower-governor. 6 | #[derive(Debug, Error, Clone)] 7 | pub enum GovernorError { 8 | #[error("Too Many Requests! Wait for {wait_time}s")] 9 | TooManyRequests { 10 | wait_time: u64, 11 | headers: Option, 12 | }, 13 | #[error("Unable to extract key!")] 14 | UnableToExtractKey, 15 | #[error("Other Error")] 16 | /// Used for custom key extractors to return their own errors 17 | Other { 18 | code: StatusCode, 19 | msg: Option, 20 | headers: Option, 21 | }, 22 | } 23 | 24 | impl GovernorError { 25 | /// Convert self into a "default response", as if no error handler was set using 26 | /// [`GovernorConfigBuilder::error_handler`]. 27 | pub fn as_response(&mut self) -> Response 28 | where 29 | ResB: From, 30 | { 31 | match mem::replace(self, Self::UnableToExtractKey) { 32 | GovernorError::TooManyRequests { wait_time, headers } => { 33 | let response = Response::new(format!("Too Many Requests! Wait for {}s", wait_time)); 34 | let (mut parts, body) = response.into_parts(); 35 | parts.status = StatusCode::TOO_MANY_REQUESTS; 36 | if let Some(headers) = headers { 37 | parts.headers = headers; 38 | } 39 | Response::from_parts(parts, ResB::from(body)) 40 | } 41 | GovernorError::UnableToExtractKey => { 42 | let response = Response::new("Unable To Extract Key!".to_string()); 43 | let (mut parts, body) = response.into_parts(); 44 | parts.status = StatusCode::INTERNAL_SERVER_ERROR; 45 | 46 | Response::from_parts(parts, ResB::from(body)) 47 | } 48 | GovernorError::Other { msg, code, headers } => { 49 | let response = Response::new("Other Error!".to_string()); 50 | let (mut parts, mut body) = response.into_parts(); 51 | parts.status = code; 52 | if let Some(headers) = headers { 53 | parts.headers = headers; 54 | } 55 | if let Some(msg) = msg { 56 | body = msg; 57 | } 58 | 59 | Response::from_parts(parts, ResB::from(body)) 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/governor.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | key_extractor::{KeyExtractor, PeerIpKeyExtractor}, 3 | GovernorError, 4 | }; 5 | use axum::body::Body; 6 | use governor::{ 7 | clock::{DefaultClock, QuantaInstant}, 8 | middleware::{NoOpMiddleware, RateLimitingMiddleware, StateInformationMiddleware}, 9 | state::keyed::DefaultKeyedStateStore, 10 | Quota, RateLimiter, 11 | }; 12 | use http::{Method, Response}; 13 | use std::{fmt, marker::PhantomData, num::NonZeroU32, sync::Arc, time::Duration}; 14 | 15 | pub const DEFAULT_PERIOD: Duration = Duration::from_millis(500); 16 | pub const DEFAULT_BURST_SIZE: u32 = 8; 17 | 18 | // Required by Governor's RateLimiter to share it across threads 19 | // See Governor User Guide: https://docs.rs/governor/0.6.0/governor/_guide/index.html 20 | pub type SharedRateLimiter = 21 | Arc, DefaultClock, M>>; 22 | 23 | /// Helper struct for building a configuration for the governor middleware. 24 | /// 25 | /// # Example 26 | /// 27 | /// Create a configuration with a quota of ten requests per IP address 28 | /// that replenishes one element every minute. 29 | /// 30 | /// ```rust 31 | /// use tower_governor::governor::GovernorConfigBuilder; 32 | /// 33 | /// let config = GovernorConfigBuilder::default() 34 | /// .per_second(60) 35 | /// .burst_size(10) 36 | /// .finish() 37 | /// .unwrap(); 38 | /// ``` 39 | /// 40 | /// with x-ratelimit headers 41 | /// 42 | /// ```rust 43 | /// use tower_governor::governor::GovernorConfigBuilder; 44 | /// 45 | /// let config = GovernorConfigBuilder::default() 46 | /// .per_second(60) 47 | /// .burst_size(10) 48 | /// .use_headers() // Add this 49 | /// .finish() 50 | /// .unwrap(); 51 | /// ``` 52 | #[derive(Debug, Eq, Clone, PartialEq)] 53 | pub struct GovernorConfigBuilder> { 54 | period: Duration, 55 | burst_size: u32, 56 | methods: Option>, 57 | key_extractor: K, 58 | error_handler: ErrorHandler, 59 | middleware: PhantomData, 60 | } 61 | 62 | // function for handling GovernorError and produce valid http Response type. 63 | #[derive(Clone)] 64 | struct ErrorHandler(Arc Response + Send + Sync>); 65 | 66 | impl Default for ErrorHandler { 67 | fn default() -> Self { 68 | Self(Arc::new(|mut e| e.as_response())) 69 | } 70 | } 71 | 72 | impl fmt::Debug for ErrorHandler { 73 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 74 | f.debug_struct("ErrorHandler").finish() 75 | } 76 | } 77 | 78 | impl PartialEq for ErrorHandler { 79 | fn eq(&self, _: &Self) -> bool { 80 | // there is no easy way to tell two object equals. 81 | true 82 | } 83 | } 84 | 85 | impl Eq for ErrorHandler {} 86 | 87 | impl Default for GovernorConfigBuilder { 88 | /// The default configuration which is suitable for most services. 89 | /// Allows burst with up to eight requests and replenishes one element after 500ms, based on peer IP. 90 | /// The values can be modified by calling other methods on this struct. 91 | fn default() -> Self { 92 | Self::const_default() 93 | } 94 | } 95 | 96 | impl> GovernorConfigBuilder { 97 | /// Set handler function for handling [GovernorError] 98 | /// # Example 99 | /// ```rust 100 | /// # use http::Response; 101 | /// # use tower_governor::governor::GovernorConfigBuilder; 102 | /// GovernorConfigBuilder::default() 103 | /// .error_handler(|mut error| { 104 | /// // match against GovernorError and produce customized Response type. 105 | /// match error { 106 | /// _ => Response::new("some error".into()) 107 | /// } 108 | /// }); 109 | /// ``` 110 | pub fn error_handler(&mut self, func: F) -> &mut Self 111 | where 112 | F: Fn(GovernorError) -> Response + Send + Sync + 'static, 113 | { 114 | self.error_handler = ErrorHandler(Arc::new(func)); 115 | self 116 | } 117 | } 118 | 119 | /// Sets the default Governor Config and defines all the different configuration functions 120 | /// This one is used when the default PeerIpKeyExtractor is used 121 | impl> GovernorConfigBuilder { 122 | pub fn const_default() -> Self { 123 | GovernorConfigBuilder { 124 | period: DEFAULT_PERIOD, 125 | burst_size: DEFAULT_BURST_SIZE, 126 | methods: None, 127 | key_extractor: PeerIpKeyExtractor, 128 | error_handler: ErrorHandler::default(), 129 | middleware: PhantomData, 130 | } 131 | } 132 | /// Set the interval after which one element of the quota is replenished. 133 | /// 134 | /// **The interval must not be zero.** 135 | pub fn const_period(mut self, duration: Duration) -> Self { 136 | self.period = duration; 137 | self 138 | } 139 | /// Set the interval after which one element of the quota is replenished in seconds. 140 | /// 141 | /// **The interval must not be zero.** 142 | pub fn const_per_second(mut self, seconds: u64) -> Self { 143 | self.period = Duration::from_secs(seconds); 144 | self 145 | } 146 | /// Set the interval after which one element of the quota is replenished in milliseconds. 147 | /// 148 | /// **The interval must not be zero.** 149 | pub fn const_per_millisecond(mut self, milliseconds: u64) -> Self { 150 | self.period = Duration::from_millis(milliseconds); 151 | self 152 | } 153 | /// Set the interval after which one element of the quota is replenished in nanoseconds. 154 | /// 155 | /// **The interval must not be zero.** 156 | pub fn const_per_nanosecond(mut self, nanoseconds: u64) -> Self { 157 | self.period = Duration::from_nanos(nanoseconds); 158 | self 159 | } 160 | /// Set quota size that defines how many requests can occur 161 | /// before the governor middleware starts blocking requests from an IP address and 162 | /// clients have to wait until the elements of the quota are replenished. 163 | /// 164 | /// **The burst_size must not be zero.** 165 | pub fn const_burst_size(mut self, burst_size: u32) -> Self { 166 | self.burst_size = burst_size; 167 | self 168 | } 169 | } 170 | 171 | /// Sets configuration options when any Key Extractor is provided 172 | impl> GovernorConfigBuilder { 173 | /// Set the interval after which one element of the quota is replenished. 174 | /// 175 | /// **The interval must not be zero.** 176 | pub fn period(&mut self, duration: Duration) -> &mut Self { 177 | self.period = duration; 178 | self 179 | } 180 | /// Set the interval after which one element of the quota is replenished in seconds. 181 | /// 182 | /// **The interval must not be zero.** 183 | pub fn per_second(&mut self, seconds: u64) -> &mut Self { 184 | self.period = Duration::from_secs(seconds); 185 | self 186 | } 187 | /// Set the interval after which one element of the quota is replenished in milliseconds. 188 | /// 189 | /// **The interval must not be zero.** 190 | pub fn per_millisecond(&mut self, milliseconds: u64) -> &mut Self { 191 | self.period = Duration::from_millis(milliseconds); 192 | self 193 | } 194 | /// Set the interval after which one element of the quota is replenished in nanoseconds. 195 | /// 196 | /// **The interval must not be zero.** 197 | pub fn per_nanosecond(&mut self, nanoseconds: u64) -> &mut Self { 198 | self.period = Duration::from_nanos(nanoseconds); 199 | self 200 | } 201 | /// Set quota size that defines how many requests can occur 202 | /// before the governor middleware starts blocking requests from an IP address and 203 | /// clients have to wait until the elements of the quota are replenished. 204 | /// 205 | /// **The burst_size must not be zero.** 206 | pub fn burst_size(&mut self, burst_size: u32) -> &mut Self { 207 | self.burst_size = burst_size; 208 | self 209 | } 210 | 211 | /// Set the HTTP methods this configuration should apply to. 212 | /// By default this is all methods. 213 | pub fn methods(&mut self, methods: Vec) -> &mut Self { 214 | self.methods = Some(methods); 215 | self 216 | } 217 | 218 | /// Set the key extractor this configuration should use. 219 | /// By default this is using the [PeerIpKeyExtractor]. 220 | pub fn key_extractor( 221 | &mut self, 222 | key_extractor: K2, 223 | ) -> GovernorConfigBuilder { 224 | GovernorConfigBuilder { 225 | period: self.period, 226 | burst_size: self.burst_size, 227 | methods: self.methods.to_owned(), 228 | key_extractor, 229 | error_handler: self.error_handler.clone(), 230 | middleware: PhantomData, 231 | } 232 | } 233 | /// Set ratelimit headers to response, the headers is 234 | /// - `x-ratelimit-limit` - Request limit 235 | /// - `x-ratelimit-remaining` - The number of requests left for the time window 236 | /// - `x-ratelimit-after` - Number of seconds in which the API will become available after its rate limit has been exceeded 237 | /// - `retry-after` - Same value as `x-ratelimit-after` 238 | /// - `x-ratelimit-whitelisted` - If the request method not in methods, this header will be add it, use [`methods`] to add methods 239 | /// 240 | /// By default `x-ratelimit-after` and `retry-after` are enabled, with [`use_headers`] will enable `x-ratelimit-limit`, `x-ratelimit-whitelisted` and `x-ratelimit-remaining` 241 | /// 242 | /// [`methods`]: crate::GovernorConfigBuilder::methods() 243 | /// [`use_headers`]: Self::use_headers 244 | pub fn use_headers(&mut self) -> GovernorConfigBuilder { 245 | GovernorConfigBuilder { 246 | period: self.period, 247 | burst_size: self.burst_size, 248 | methods: self.methods.to_owned(), 249 | key_extractor: self.key_extractor.clone(), 250 | error_handler: self.error_handler.clone(), 251 | middleware: PhantomData, 252 | } 253 | } 254 | 255 | /// Finish building the configuration and return the configuration for the middleware. 256 | /// Returns `None` if either burst size or period interval are zero. 257 | pub fn finish(&mut self) -> Option> { 258 | if self.burst_size != 0 && self.period.as_nanos() != 0 { 259 | Some(GovernorConfig { 260 | key_extractor: self.key_extractor.clone(), 261 | limiter: Arc::new( 262 | RateLimiter::keyed( 263 | Quota::with_period(self.period) 264 | .unwrap() 265 | .allow_burst(NonZeroU32::new(self.burst_size).unwrap()), 266 | ) 267 | .with_middleware::(), 268 | ), 269 | methods: self.methods.clone(), 270 | error_handler: self.error_handler.clone(), 271 | }) 272 | } else { 273 | None 274 | } 275 | } 276 | } 277 | 278 | #[derive(Debug, Clone)] 279 | /// Configuration for the Governor middleware. 280 | pub struct GovernorConfig> { 281 | key_extractor: K, 282 | limiter: SharedRateLimiter, 283 | methods: Option>, 284 | error_handler: ErrorHandler, 285 | } 286 | 287 | impl> GovernorConfig { 288 | pub fn limiter(&self) -> &SharedRateLimiter { 289 | &self.limiter 290 | } 291 | } 292 | 293 | impl Default for GovernorConfig { 294 | /// The default configuration which is suitable for most services. 295 | /// Allows bursts with up to eight requests and replenishes one element after 500ms, based on peer IP. 296 | fn default() -> Self { 297 | GovernorConfigBuilder::default().finish().unwrap() 298 | } 299 | } 300 | 301 | impl> GovernorConfig { 302 | /// A default configuration for security related services. 303 | /// Allows bursts with up to two requests and replenishes one element after four seconds, based on peer IP. 304 | /// 305 | /// This prevents brute-forcing passwords or security tokens 306 | /// yet allows to quickly retype a wrong password once before the quota is exceeded. 307 | pub fn secure() -> Self { 308 | GovernorConfigBuilder { 309 | period: Duration::from_secs(4), 310 | burst_size: 2, 311 | methods: None, 312 | key_extractor: PeerIpKeyExtractor, 313 | error_handler: ErrorHandler::default(), 314 | middleware: PhantomData, 315 | } 316 | .finish() 317 | .unwrap() 318 | } 319 | } 320 | 321 | /// Governor middleware factory. Hand this a GovernorConfig and it'll create this struct, which 322 | /// contains everything needed to implement a middleware 323 | /// https://stegosaurusdormant.com/understanding-derive-clone/ 324 | #[derive(Debug)] 325 | pub struct Governor, S> { 326 | pub key_extractor: K, 327 | pub limiter: SharedRateLimiter, 328 | pub methods: Option>, 329 | pub inner: S, 330 | error_handler: ErrorHandler, 331 | } 332 | 333 | impl, S: Clone> Clone 334 | for Governor 335 | { 336 | fn clone(&self) -> Self { 337 | Self { 338 | key_extractor: self.key_extractor.clone(), 339 | limiter: self.limiter.clone(), 340 | methods: self.methods.clone(), 341 | inner: self.inner.clone(), 342 | error_handler: self.error_handler.clone(), 343 | } 344 | } 345 | } 346 | 347 | impl, S> Governor { 348 | /// Create new governor middleware factory from configuration. 349 | pub fn new(inner: S, config: &GovernorConfig) -> Self { 350 | Governor { 351 | key_extractor: config.key_extractor.clone(), 352 | limiter: config.limiter.clone(), 353 | methods: config.methods.clone(), 354 | inner, 355 | error_handler: config.error_handler.clone(), 356 | } 357 | } 358 | 359 | pub(crate) fn error_handler(&self) -> &(dyn Fn(GovernorError) -> Response + Send + Sync) { 360 | &*self.error_handler.0 361 | } 362 | } 363 | -------------------------------------------------------------------------------- /src/key_extractor.rs: -------------------------------------------------------------------------------- 1 | use crate::errors::GovernorError; 2 | use forwarded_header_value::{ForwardedHeaderValue, Identifier}; 3 | use http::request::Request; 4 | use http::{header::FORWARDED, HeaderMap}; 5 | use std::fmt::Debug; 6 | use std::net::SocketAddr; 7 | use std::{hash::Hash, net::IpAddr}; 8 | 9 | /// Generic structure of what is needed to extract a rate-limiting key from an incoming request. 10 | pub trait KeyExtractor: Clone { 11 | /// The type of the key. 12 | type Key: Clone + Hash + Eq + Debug; 13 | 14 | /// The type of the error that can occur if key extraction from the request fails. 15 | /// Should be one of the predefined GovernorErrors, or GovernorError::Other 16 | #[cfg(feature = "tracing")] 17 | /// Name of this extractor (only used for tracing). 18 | fn name(&self) -> &'static str; 19 | 20 | /// Extraction method, will return [`GovernorError`] response when the extract failed 21 | fn extract(&self, req: &Request) -> Result; 22 | 23 | #[cfg(feature = "tracing")] 24 | /// Value of the extracted key (only used in tracing). 25 | fn key_name(&self, _key: &Self::Key) -> Option { 26 | None 27 | } 28 | } 29 | 30 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 31 | /// A [KeyExtractor] that allow to do rate limiting for all incoming requests. This is useful if you want to hard-limit the HTTP load your app can handle. 32 | pub struct GlobalKeyExtractor; 33 | 34 | impl KeyExtractor for GlobalKeyExtractor { 35 | type Key = (); 36 | 37 | #[cfg(feature = "tracing")] 38 | fn name(&self) -> &'static str { 39 | "global" 40 | } 41 | 42 | fn extract(&self, _req: &Request) -> Result { 43 | Ok(()) 44 | } 45 | 46 | #[cfg(feature = "tracing")] 47 | fn key_name(&self, _key: &Self::Key) -> Option { 48 | None 49 | } 50 | } 51 | 52 | /// A [KeyExtractor] that uses peer IP as key. **This is the default key extractor and [it may no do want you want](PeerIpKeyExtractor).** 53 | /// 54 | /// **Warning:** this key extractor enforces rate limiting based on the **_peer_ IP address**. 55 | /// 56 | /// This means that if your app is deployed behind a reverse proxy, the peer IP address will _always_ be the proxy's IP address. 57 | /// In this case, rate limiting will be applied to _all_ incoming requests as if they were from the same user. 58 | /// 59 | /// If this is not the behavior you want, you may: 60 | /// - Use the SmartIpKeyExtractor to get the IP from the `Forwarded` or `X-Forwarded-For` headers that most proxies set 61 | /// - implement your own [KeyExtractor] that tries to get IP from the `Forwarded` or `X-Forwarded-For` headers that most reverse proxies set 62 | /// - make absolutely sure that you only trust these headers when the peer IP is the IP of your reverse proxy (otherwise any user could set them to fake its IP) 63 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 64 | pub struct PeerIpKeyExtractor; 65 | 66 | impl KeyExtractor for PeerIpKeyExtractor { 67 | type Key = IpAddr; 68 | 69 | #[cfg(feature = "tracing")] 70 | fn name(&self) -> &'static str { 71 | "peer IP" 72 | } 73 | 74 | //type Key: Clone + Hash + Eq; 75 | fn extract(&self, req: &Request) -> Result { 76 | maybe_connect_info(req).ok_or(GovernorError::UnableToExtractKey) 77 | } 78 | 79 | #[cfg(feature = "tracing")] 80 | fn key_name(&self, key: &Self::Key) -> Option { 81 | Some(key.to_string()) 82 | } 83 | } 84 | 85 | /// A [KeyExtractor] that tries to get the client IP address from the x-forwarded-for, x-real-ip, and forwarded headers in that order. Falls back to the peer IP address. 86 | /// 87 | /// **Warning:** Only use this key extractor if you can ensure these headers are being set by a trusted provider.**. 88 | /// 89 | /// This is a sane default for an app running behind a reverse proxy, with the caveat that one must be careful of ths source of the headers. 90 | /// It will fall back to the peer IP address if the headers are not present, which would set a global rate limit if behind a reverse proxy. 91 | /// If it fails to find any of the headers or the peer IP, it will error out. 92 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 93 | pub struct SmartIpKeyExtractor; 94 | 95 | impl KeyExtractor for SmartIpKeyExtractor { 96 | type Key = IpAddr; 97 | 98 | #[cfg(feature = "tracing")] 99 | fn name(&self) -> &'static str { 100 | "smart IP" 101 | } 102 | 103 | //type Key: Clone + Hash + Eq; 104 | //type Boxerror: pub type BoxError = Box; 105 | fn extract(&self, req: &Request) -> Result { 106 | let headers = req.headers(); 107 | 108 | maybe_x_forwarded_for(headers) 109 | .or_else(|| maybe_x_real_ip(headers)) 110 | .or_else(|| maybe_forwarded(headers)) 111 | .or_else(|| maybe_connect_info(req)) 112 | .ok_or(GovernorError::UnableToExtractKey) 113 | } 114 | 115 | #[cfg(feature = "tracing")] 116 | fn key_name(&self, key: &Self::Key) -> Option { 117 | Some(key.to_string()) 118 | } 119 | } 120 | 121 | // Utility functions for the SmartIpExtractor 122 | // Shamelessly snatched from the axum-client-ip crate here: 123 | // https://crates.io/crates/axum-client-ip 124 | 125 | const X_REAL_IP: &str = "x-real-ip"; 126 | const X_FORWARDED_FOR: &str = "x-forwarded-for"; 127 | 128 | /// Tries to parse the `x-forwarded-for` header 129 | fn maybe_x_forwarded_for(headers: &HeaderMap) -> Option { 130 | headers 131 | .get(X_FORWARDED_FOR) 132 | .and_then(|hv| hv.to_str().ok()) 133 | .and_then(|s| s.split(',').find_map(|s| s.trim().parse::().ok())) 134 | } 135 | 136 | /// Tries to parse the `x-real-ip` header 137 | fn maybe_x_real_ip(headers: &HeaderMap) -> Option { 138 | headers 139 | .get(X_REAL_IP) 140 | .and_then(|hv| hv.to_str().ok()) 141 | .and_then(|s| s.parse::().ok()) 142 | } 143 | 144 | /// Tries to parse `forwarded` headers 145 | fn maybe_forwarded(headers: &HeaderMap) -> Option { 146 | headers.get_all(FORWARDED).iter().find_map(|hv| { 147 | hv.to_str() 148 | .ok() 149 | .and_then(|s| ForwardedHeaderValue::from_forwarded(s).ok()) 150 | .and_then(|f| { 151 | f.iter() 152 | .filter_map(|fs| fs.forwarded_for.as_ref()) 153 | .find_map(|ff| match ff { 154 | Identifier::SocketAddr(a) => Some(a.ip()), 155 | Identifier::IpAddr(ip) => Some(*ip), 156 | _ => None, 157 | }) 158 | }) 159 | }) 160 | } 161 | 162 | #[cfg(feature = "axum")] 163 | /// Looks in `ConnectInfo` extension 164 | fn maybe_connect_info(req: &Request) -> Option { 165 | req.extensions() 166 | .get::>() 167 | .map(|addr| addr.ip()) 168 | } 169 | 170 | #[cfg(not(feature = "axum"))] 171 | /// Looks in `ConnectInfo` extension 172 | fn maybe_connect_info(req: &Request) -> Option { 173 | req.extensions().get::().map(|addr| addr.ip()) 174 | } 175 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | 3 | #[cfg(test)] 4 | mod tests; 5 | 6 | pub mod errors; 7 | pub mod governor; 8 | pub mod key_extractor; 9 | use crate::governor::{Governor, GovernorConfig}; 10 | use ::governor::clock::{Clock, DefaultClock, QuantaInstant}; 11 | use ::governor::middleware::{NoOpMiddleware, RateLimitingMiddleware, StateInformationMiddleware}; 12 | use axum::body::Body; 13 | pub use errors::GovernorError; 14 | use http::response::Response; 15 | 16 | use http::header::{HeaderName, HeaderValue}; 17 | use http::request::Request; 18 | use http::HeaderMap; 19 | use key_extractor::KeyExtractor; 20 | use pin_project::pin_project; 21 | use std::sync::Arc; 22 | use std::task::{Context, Poll}; 23 | use std::{future::Future, pin::Pin, task::ready}; 24 | use tower::{Layer, Service}; 25 | 26 | /// The Layer type that implements tower::Layer and is passed into `.layer()` 27 | pub struct GovernorLayer 28 | where 29 | K: KeyExtractor, 30 | M: RateLimitingMiddleware, 31 | { 32 | pub config: Arc>, 33 | } 34 | 35 | impl Layer for GovernorLayer 36 | where 37 | K: KeyExtractor, 38 | M: RateLimitingMiddleware, 39 | { 40 | type Service = Governor; 41 | 42 | fn layer(&self, inner: S) -> Self::Service { 43 | Governor::new(inner, &self.config) 44 | } 45 | } 46 | 47 | /// https://stegosaurusdormant.com/understanding-derive-clone/ 48 | impl> Clone for GovernorLayer { 49 | fn clone(&self) -> Self { 50 | Self { 51 | config: self.config.clone(), 52 | } 53 | } 54 | } 55 | // Implement tower::Service for Governor 56 | impl Service> for Governor 57 | where 58 | K: KeyExtractor, 59 | S: Service, Response = Response>, 60 | { 61 | type Response = S::Response; 62 | type Error = S::Error; 63 | type Future = ResponseFuture; 64 | 65 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 66 | self.inner.poll_ready(cx) 67 | } 68 | 69 | fn call(&mut self, req: Request) -> Self::Future { 70 | if let Some(configured_methods) = &self.methods { 71 | if !configured_methods.contains(req.method()) { 72 | // The request method is not configured, we're ignoring this one. 73 | let future = self.inner.call(req); 74 | return ResponseFuture { 75 | inner: Kind::Passthrough { future }, 76 | }; 77 | } 78 | } 79 | // Use the provided key extractor to extract the rate limiting key from the request. 80 | match self.key_extractor.extract(&req) { 81 | // Extraction worked, let's check if rate limiting is needed. 82 | Ok(key) => match self.limiter.check_key(&key) { 83 | Ok(_) => { 84 | let future = self.inner.call(req); 85 | ResponseFuture { 86 | inner: Kind::Passthrough { future }, 87 | } 88 | } 89 | 90 | Err(negative) => { 91 | let wait_time = negative 92 | .wait_time_from(DefaultClock::default().now()) 93 | .as_secs(); 94 | 95 | #[cfg(feature = "tracing")] 96 | { 97 | let key_name = match self.key_extractor.key_name(&key) { 98 | Some(n) => format!(" [{}]", &n), 99 | None => "".to_owned(), 100 | }; 101 | tracing::info!( 102 | "Rate limit exceeded for {}{}, quota reset in {}s", 103 | self.key_extractor.name(), 104 | key_name, 105 | &wait_time 106 | ); 107 | } 108 | let mut headers = HeaderMap::new(); 109 | headers.insert("x-ratelimit-after", wait_time.into()); 110 | headers.insert("retry-after", wait_time.into()); 111 | 112 | let error_response = self.error_handler()(GovernorError::TooManyRequests { 113 | wait_time, 114 | headers: Some(headers), 115 | }); 116 | 117 | ResponseFuture { 118 | inner: Kind::Error { 119 | error_response: Some(error_response), 120 | }, 121 | } 122 | } 123 | }, 124 | 125 | Err(e) => { 126 | let error_response = self.error_handler()(e); 127 | ResponseFuture { 128 | inner: Kind::Error { 129 | error_response: Some(error_response), 130 | }, 131 | } 132 | } 133 | } 134 | } 135 | } 136 | 137 | #[derive(Debug)] 138 | #[pin_project] 139 | /// Response future for [`Governor`]. 140 | pub struct ResponseFuture { 141 | #[pin] 142 | inner: Kind, 143 | } 144 | 145 | #[derive(Debug)] 146 | #[pin_project(project = KindProj)] 147 | enum Kind { 148 | Passthrough { 149 | #[pin] 150 | future: F, 151 | }, 152 | RateLimitHeader { 153 | #[pin] 154 | future: F, 155 | #[pin] 156 | burst_size: u32, 157 | #[pin] 158 | remaining_burst_capacity: u32, 159 | }, 160 | WhitelistedHeader { 161 | #[pin] 162 | future: F, 163 | }, 164 | Error { 165 | error_response: Option>, 166 | }, 167 | } 168 | 169 | impl Future for ResponseFuture 170 | where 171 | F: Future, E>>, 172 | { 173 | type Output = Result, E>; 174 | 175 | fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 176 | match self.project().inner.project() { 177 | KindProj::Passthrough { future } => future.poll(cx), 178 | KindProj::RateLimitHeader { 179 | future, 180 | burst_size, 181 | remaining_burst_capacity, 182 | } => { 183 | let mut response = ready!(future.poll(cx))?; 184 | 185 | let mut headers = HeaderMap::new(); 186 | headers.insert( 187 | HeaderName::from_static("x-ratelimit-limit"), 188 | HeaderValue::from(*burst_size), 189 | ); 190 | headers.insert( 191 | HeaderName::from_static("x-ratelimit-remaining"), 192 | HeaderValue::from(*remaining_burst_capacity), 193 | ); 194 | response.headers_mut().extend(headers.drain()); 195 | 196 | Poll::Ready(Ok(response)) 197 | } 198 | KindProj::WhitelistedHeader { future } => { 199 | let mut response = ready!(future.poll(cx))?; 200 | 201 | let headers = response.headers_mut(); 202 | headers.insert( 203 | HeaderName::from_static("x-ratelimit-whitelisted"), 204 | HeaderValue::from_static("true"), 205 | ); 206 | 207 | Poll::Ready(Ok(response)) 208 | } 209 | KindProj::Error { error_response } => Poll::Ready(Ok(error_response.take().expect(" 210 | >>::call must produce Response when GovernorError occurs. 211 | "))), 212 | } 213 | } 214 | } 215 | 216 | // Implementation of Service for Governor using the StateInformationMiddleware. 217 | impl Service> for Governor 218 | where 219 | K: KeyExtractor, 220 | S: Service, Response = Response>, 221 | // Body type of response must impl From trait to convert potential error 222 | // produced by governor to re 223 | { 224 | type Response = S::Response; 225 | type Error = S::Error; 226 | type Future = ResponseFuture; 227 | 228 | fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { 229 | // Our middleware doesn't care about backpressure so its ready as long 230 | // as the inner service is ready. 231 | self.inner.poll_ready(cx) 232 | } 233 | 234 | fn call(&mut self, req: Request) -> Self::Future { 235 | if let Some(configured_methods) = &self.methods { 236 | if !configured_methods.contains(req.method()) { 237 | // The request method is not configured, we're ignoring this one. 238 | let fut = self.inner.call(req); 239 | return ResponseFuture { 240 | inner: Kind::WhitelistedHeader { future: fut }, 241 | }; 242 | } 243 | } 244 | // Use the provided key extractor to extract the rate limiting key from the request. 245 | match self.key_extractor.extract(&req) { 246 | // Extraction worked, let's check if rate limiting is needed. 247 | Ok(key) => match self.limiter.check_key(&key) { 248 | Ok(snapshot) => { 249 | let fut = self.inner.call(req); 250 | ResponseFuture { 251 | inner: Kind::RateLimitHeader { 252 | future: fut, 253 | burst_size: snapshot.quota().burst_size().get(), 254 | remaining_burst_capacity: snapshot.remaining_burst_capacity(), 255 | }, 256 | } 257 | } 258 | 259 | Err(negative) => { 260 | let wait_time = negative 261 | .wait_time_from(DefaultClock::default().now()) 262 | .as_secs(); 263 | 264 | #[cfg(feature = "tracing")] 265 | { 266 | let key_name = match self.key_extractor.key_name(&key) { 267 | Some(n) => format!(" [{}]", &n), 268 | None => "".to_owned(), 269 | }; 270 | tracing::info!( 271 | "Rate limit exceeded for {}{}, quota reset in {}s", 272 | self.key_extractor.name(), 273 | key_name, 274 | &wait_time 275 | ); 276 | } 277 | 278 | let mut headers = HeaderMap::new(); 279 | headers.insert("x-ratelimit-after", wait_time.into()); 280 | headers.insert("retry-after", wait_time.into()); 281 | headers.insert( 282 | "x-ratelimit-limit", 283 | negative.quota().burst_size().get().into(), 284 | ); 285 | headers.insert("x-ratelimit-remaining", 0.into()); 286 | 287 | let error_response = self.error_handler()(GovernorError::TooManyRequests { 288 | wait_time, 289 | headers: Some(headers), 290 | }); 291 | 292 | ResponseFuture { 293 | inner: Kind::Error { 294 | error_response: Some(error_response), 295 | }, 296 | } 297 | } 298 | }, 299 | 300 | // Extraction failed, stop right now. 301 | Err(e) => { 302 | let error_response = self.error_handler()(e); 303 | ResponseFuture { 304 | inner: Kind::Error { 305 | error_response: Some(error_response), 306 | }, 307 | } 308 | } 309 | } 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /src/tests.rs: -------------------------------------------------------------------------------- 1 | use axum::{routing::get, Router}; 2 | use std::sync::Arc; 3 | use tower_http::trace::TraceLayer; 4 | use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; 5 | 6 | use crate::{governor::GovernorConfigBuilder, GovernorLayer}; 7 | 8 | #[tokio::main] 9 | async fn _main() { 10 | tracing_subscriber::registry() 11 | .with(tracing_subscriber::EnvFilter::new( 12 | std::env::var("RUST_LOG") 13 | .unwrap_or_else(|_| "example_testing=debug,tower_http=debug".into()), 14 | )) 15 | .with(tracing_subscriber::fmt::layer()) 16 | .init(); 17 | 18 | let addr = std::net::SocketAddr::from(([127, 0, 0, 1], 3000)); 19 | 20 | tracing::debug!("listening on {}", addr); 21 | 22 | let listener = tokio::net::TcpListener::bind(addr).await.unwrap(); 23 | 24 | axum::serve(listener, app().into_make_service()) 25 | .await 26 | .unwrap(); 27 | } 28 | 29 | /// Having a function that produces our app makes it easy to call it from tests 30 | /// without having to create an HTTP server. 31 | #[allow(dead_code)] 32 | fn app() -> Router { 33 | let config = Arc::new( 34 | GovernorConfigBuilder::default() 35 | .per_millisecond(90) 36 | .burst_size(2) 37 | .finish() 38 | .unwrap(), 39 | ); 40 | 41 | Router::new() 42 | // `GET /` goes to `root` 43 | .route( 44 | "/", 45 | get(|| async { "Hello, World!" }).post(|| async { "Hello, Post World!" }), 46 | ) 47 | .layer(GovernorLayer { config }) 48 | .layer(TraceLayer::new_for_http()) 49 | } 50 | 51 | #[cfg(test)] 52 | mod governor_tests { 53 | use super::*; 54 | use axum::{body, http}; 55 | use reqwest::header::HeaderName; 56 | use reqwest::StatusCode; 57 | use std::net::SocketAddr; 58 | use tokio::net::TcpListener; 59 | use tower::ServiceExt; 60 | 61 | #[tokio::test] 62 | async fn hello_world() { 63 | let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 64 | let addr = listener.local_addr().unwrap(); 65 | 66 | let (tx, rx) = tokio::sync::oneshot::channel(); 67 | tokio::spawn(async move { 68 | let app = app(); 69 | tx.send(()).unwrap(); 70 | axum::serve( 71 | listener, 72 | app.into_make_service_with_connect_info::(), 73 | ) 74 | .await 75 | .unwrap(); 76 | }); 77 | rx.await.unwrap(); 78 | 79 | let client = reqwest::Client::new(); 80 | 81 | let res = client.get(format!("http://{}", addr)).send().await.unwrap(); 82 | let res2 = client.get(format!("http://{}", addr)).send().await.unwrap(); 83 | 84 | let body = res.text().await.unwrap(); 85 | let body2 = res2.text().await.unwrap(); 86 | 87 | assert!(body.starts_with("Hello, World!")); 88 | assert!(body2.starts_with("Hello, World!")); 89 | } 90 | 91 | // #[test] 92 | // fn builder_test() { 93 | // use crate::governor::GovernorConfigBuilder; 94 | 95 | // let mut builder = GovernorConfigBuilder::default(); 96 | // builder 97 | // .period(crate::governor::DEFAULT_PERIOD) 98 | // .burst_size(crate::governor::DEFAULT_BURST_SIZE); 99 | 100 | // assert_eq!(GovernorConfigBuilder::default(), builder); 101 | 102 | // let mut builder1 = builder.clone(); 103 | // builder1.per_millisecond(5000); 104 | // let builder2 = builder.per_second(5); 105 | 106 | // assert_eq!(&builder1, builder2); 107 | // } 108 | 109 | #[tokio::test] 110 | async fn test_server() { 111 | let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 112 | let addr = listener.local_addr().unwrap(); 113 | let url = format!("http://{}", addr); 114 | 115 | let (tx, rx) = tokio::sync::oneshot::channel(); 116 | tokio::spawn(async move { 117 | let app = app(); 118 | tx.send(()).unwrap(); 119 | axum::serve( 120 | listener, 121 | app.into_make_service_with_connect_info::(), 122 | ) 123 | .await 124 | .unwrap(); 125 | }); 126 | rx.await.unwrap(); 127 | 128 | let client = reqwest::Client::new(); 129 | 130 | // First request 131 | let res = client.get(&url).send().await.unwrap(); 132 | assert_eq!(res.status(), StatusCode::OK); 133 | 134 | // Second request 135 | let res = client.get(&url).send().await.unwrap(); 136 | assert_eq!(res.status(), StatusCode::OK); 137 | 138 | // Third request -> Over limit, returns Error 139 | let res = client.get(&url).send().await.unwrap(); 140 | assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); 141 | assert_eq!( 142 | res.headers() 143 | .get(HeaderName::from_static("x-ratelimit-after")) 144 | .unwrap(), 145 | "0" 146 | ); 147 | 148 | // Replenish one element by waiting for >90ms 149 | let sleep_time = std::time::Duration::from_millis(100); 150 | std::thread::sleep(sleep_time); 151 | 152 | // First request after reset 153 | let res = client.get(&url).send().await.unwrap(); 154 | assert_eq!(res.status(), StatusCode::OK); 155 | 156 | // Second request after reset -> Again over limit, returns Error 157 | let res = client.get(&url).send().await.unwrap(); 158 | assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); 159 | assert_eq!( 160 | res.headers() 161 | .get(HeaderName::from_static("x-ratelimit-after")) 162 | .unwrap(), 163 | "0" 164 | ); 165 | let body = res.text().await.unwrap(); 166 | assert_eq!(&body, "Too Many Requests! Wait for 0s"); 167 | } 168 | #[tokio::test] 169 | async fn test_method_filter() { 170 | use crate::governor::GovernorConfigBuilder; 171 | use http::Method; 172 | 173 | let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 174 | let addr = listener.local_addr().unwrap(); 175 | let url = format!("http://{}", addr); 176 | 177 | let (tx, rx) = tokio::sync::oneshot::channel(); 178 | tokio::spawn(async move { 179 | let config = Arc::new( 180 | GovernorConfigBuilder::default() 181 | .per_millisecond(90) 182 | .burst_size(2) 183 | .methods(vec![Method::GET]) 184 | .finish() 185 | .unwrap(), 186 | ); 187 | 188 | let app = Router::new() 189 | // `GET /` goes to `root` 190 | .route( 191 | "/", 192 | get(|| async { "Hello, World!" }).post(|| async { "Hello, Post World!" }), 193 | ) 194 | .layer(GovernorLayer { config }) 195 | .layer(TraceLayer::new_for_http()); 196 | tx.send(()).unwrap(); 197 | axum::serve( 198 | listener, 199 | app.into_make_service_with_connect_info::(), 200 | ) 201 | .await 202 | .unwrap(); 203 | }); 204 | rx.await.unwrap(); 205 | 206 | let client = reqwest::Client::new(); 207 | 208 | // First request 209 | let res = client.get(&url).send().await.unwrap(); 210 | assert_eq!(res.status(), StatusCode::OK); 211 | 212 | // Second request 213 | let res = client.get(&url).send().await.unwrap(); 214 | assert_eq!(res.status(), StatusCode::OK); 215 | 216 | // Third request -> Over limit, returns Error 217 | let res = client.get(&url).send().await.unwrap(); 218 | assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); 219 | assert_eq!( 220 | res.headers() 221 | .get(HeaderName::from_static("x-ratelimit-after")) 222 | .unwrap(), 223 | "0" 224 | ); 225 | 226 | // Fourth request. POST should be ignored by the method filter 227 | let res = client.post(&url).send().await.unwrap(); 228 | assert_eq!(res.status(), StatusCode::OK); 229 | } 230 | 231 | #[tokio::test] 232 | async fn test_server_use_headers() { 233 | use crate::governor::GovernorConfigBuilder; 234 | 235 | let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 236 | let addr = listener.local_addr().unwrap(); 237 | let url = format!("http://{}", addr); 238 | 239 | let (tx, rx) = tokio::sync::oneshot::channel(); 240 | tokio::spawn(async move { 241 | let config = Arc::new( 242 | GovernorConfigBuilder::default() 243 | .per_millisecond(90) 244 | .burst_size(2) 245 | .use_headers() 246 | .finish() 247 | .unwrap(), 248 | ); 249 | 250 | let app = Router::new() 251 | // `GET /` goes to `root` 252 | .route( 253 | "/", 254 | get(|| async { "Hello, World!" }).post(|| async { "Hello, Post World!" }), 255 | ) 256 | .layer(GovernorLayer { config }) 257 | .layer(TraceLayer::new_for_http()); 258 | tx.send(()).unwrap(); 259 | axum::serve( 260 | listener, 261 | app.into_make_service_with_connect_info::(), 262 | ) 263 | .await 264 | .unwrap(); 265 | }); 266 | rx.await.unwrap(); 267 | 268 | let client = reqwest::Client::new(); 269 | 270 | // First request 271 | let res = client.get(&url).send().await.unwrap(); 272 | assert_eq!(res.status(), StatusCode::OK); 273 | 274 | assert_eq!( 275 | res.headers() 276 | .get(HeaderName::from_static("x-ratelimit-limit")) 277 | .unwrap(), 278 | "2" 279 | ); 280 | assert_eq!( 281 | res.headers() 282 | .get(HeaderName::from_static("x-ratelimit-remaining")) 283 | .unwrap(), 284 | "1" 285 | ); 286 | assert!(res 287 | .headers() 288 | .get(HeaderName::from_static("x-ratelimit-after")) 289 | .is_none()); 290 | assert!(res 291 | .headers() 292 | .get(HeaderName::from_static("x-ratelimit-whitelisted")) 293 | .is_none()); 294 | 295 | // Second request 296 | let res = client.get(&url).send().await.unwrap(); 297 | assert_eq!(res.status(), StatusCode::OK); 298 | 299 | assert_eq!( 300 | res.headers() 301 | .get(HeaderName::from_static("x-ratelimit-limit")) 302 | .unwrap(), 303 | "2" 304 | ); 305 | assert_eq!( 306 | res.headers() 307 | .get(HeaderName::from_static("x-ratelimit-remaining")) 308 | .unwrap(), 309 | "0" 310 | ); 311 | assert!(res 312 | .headers() 313 | .get(HeaderName::from_static("x-ratelimit-after")) 314 | .is_none()); 315 | assert!(res 316 | .headers() 317 | .get(HeaderName::from_static("x-ratelimit-whitelisted")) 318 | .is_none()); 319 | 320 | // Third request -> Over limit, returns Error 321 | let res = client.get(&url).send().await.unwrap(); 322 | assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); 323 | assert_eq!( 324 | res.headers() 325 | .get(HeaderName::from_static("x-ratelimit-after")) 326 | .unwrap(), 327 | "0" 328 | ); 329 | assert_eq!( 330 | res.headers() 331 | .get(HeaderName::from_static("x-ratelimit-limit")) 332 | .unwrap(), 333 | "2" 334 | ); 335 | assert_eq!( 336 | res.headers() 337 | .get(HeaderName::from_static("x-ratelimit-remaining")) 338 | .unwrap(), 339 | "0" 340 | ); 341 | assert!(res 342 | .headers() 343 | .get(HeaderName::from_static("x-ratelimit-whitelisted")) 344 | .is_none()); 345 | 346 | // Replenish one element by waiting for >90ms 347 | let sleep_time = std::time::Duration::from_millis(100); 348 | std::thread::sleep(sleep_time); 349 | 350 | // First request after reset 351 | let res = client.get(&url).send().await.unwrap(); 352 | assert_eq!(res.status(), StatusCode::OK); 353 | 354 | assert_eq!( 355 | res.headers() 356 | .get(HeaderName::from_static("x-ratelimit-limit")) 357 | .unwrap(), 358 | "2" 359 | ); 360 | assert_eq!( 361 | res.headers() 362 | .get(HeaderName::from_static("x-ratelimit-remaining")) 363 | .unwrap(), 364 | "0" 365 | ); 366 | assert!(res 367 | .headers() 368 | .get(HeaderName::from_static("x-ratelimit-after")) 369 | .is_none()); 370 | assert!(res 371 | .headers() 372 | .get(HeaderName::from_static("x-ratelimit-whitelisted")) 373 | .is_none()); 374 | 375 | // Second request after reset -> Again over limit, returns Error 376 | let res = client.get(&url).send().await.unwrap(); 377 | assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); 378 | assert_eq!( 379 | res.headers() 380 | .get(HeaderName::from_static("x-ratelimit-after")) 381 | .unwrap(), 382 | "0" 383 | ); 384 | assert_eq!( 385 | res.headers() 386 | .get(HeaderName::from_static("x-ratelimit-limit")) 387 | .unwrap(), 388 | "2" 389 | ); 390 | assert_eq!( 391 | res.headers() 392 | .get(HeaderName::from_static("x-ratelimit-remaining")) 393 | .unwrap(), 394 | "0" 395 | ); 396 | assert!(res 397 | .headers() 398 | .get(HeaderName::from_static("x-ratelimit-whitelisted")) 399 | .is_none()); 400 | 401 | let body = res.text().await.unwrap(); 402 | assert_eq!(&body, "Too Many Requests! Wait for 0s"); 403 | } 404 | 405 | #[tokio::test] 406 | async fn test_method_filter_use_headers() { 407 | use crate::governor::GovernorConfigBuilder; 408 | use http::Method; 409 | 410 | let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); 411 | let addr = listener.local_addr().unwrap(); 412 | let url = format!("http://{}", addr); 413 | 414 | let (tx, rx) = tokio::sync::oneshot::channel(); 415 | tokio::spawn(async move { 416 | let config = Arc::new( 417 | GovernorConfigBuilder::default() 418 | .per_millisecond(90) 419 | .burst_size(2) 420 | .methods(vec![Method::GET]) 421 | .use_headers() 422 | .finish() 423 | .unwrap(), 424 | ); 425 | 426 | let app = Router::new() 427 | // `GET /` goes to `root` 428 | .route( 429 | "/", 430 | get(|| async { "Hello, World!" }).post(|| async { "Hello, Post World!" }), 431 | ) 432 | .layer(GovernorLayer { config }) 433 | .layer(TraceLayer::new_for_http()); 434 | tx.send(()).unwrap(); 435 | axum::serve( 436 | listener, 437 | app.into_make_service_with_connect_info::(), 438 | ) 439 | .await 440 | .unwrap(); 441 | }); 442 | rx.await.unwrap(); 443 | 444 | let client = reqwest::Client::new(); 445 | 446 | // First request 447 | let res = client.get(&url).send().await.unwrap(); 448 | assert_eq!(res.status(), StatusCode::OK); 449 | 450 | assert_eq!( 451 | res.headers() 452 | .get(HeaderName::from_static("x-ratelimit-limit")) 453 | .unwrap(), 454 | "2" 455 | ); 456 | assert_eq!( 457 | res.headers() 458 | .get(HeaderName::from_static("x-ratelimit-remaining")) 459 | .unwrap(), 460 | "1" 461 | ); 462 | assert!(res 463 | .headers() 464 | .get(HeaderName::from_static("x-ratelimit-after")) 465 | .is_none()); 466 | assert!(res 467 | .headers() 468 | .get(HeaderName::from_static("x-ratelimit-whitelisted")) 469 | .is_none()); 470 | 471 | // Second request 472 | let res = client.get(&url).send().await.unwrap(); 473 | assert_eq!(res.status(), StatusCode::OK); 474 | 475 | assert_eq!( 476 | res.headers() 477 | .get(HeaderName::from_static("x-ratelimit-limit")) 478 | .unwrap(), 479 | "2" 480 | ); 481 | assert_eq!( 482 | res.headers() 483 | .get(HeaderName::from_static("x-ratelimit-remaining")) 484 | .unwrap(), 485 | "0" 486 | ); 487 | assert!(res 488 | .headers() 489 | .get(HeaderName::from_static("x-ratelimit-after")) 490 | .is_none()); 491 | assert!(res 492 | .headers() 493 | .get(HeaderName::from_static("x-ratelimit-whitelisted")) 494 | .is_none()); 495 | 496 | // Third request -> Over limit, returns Error 497 | let res = client.get(&url).send().await.unwrap(); 498 | assert_eq!(res.status(), StatusCode::TOO_MANY_REQUESTS); 499 | assert_eq!( 500 | res.headers() 501 | .get(HeaderName::from_static("x-ratelimit-after")) 502 | .unwrap(), 503 | "0" 504 | ); 505 | assert_eq!( 506 | res.headers() 507 | .get(HeaderName::from_static("x-ratelimit-limit")) 508 | .unwrap(), 509 | "2" 510 | ); 511 | assert_eq!( 512 | res.headers() 513 | .get(HeaderName::from_static("x-ratelimit-remaining")) 514 | .unwrap(), 515 | "0" 516 | ); 517 | assert!(res 518 | .headers() 519 | .get(HeaderName::from_static("x-ratelimit-whitelisted")) 520 | .is_none()); 521 | 522 | // Fourth request, ignored because POST 523 | let res = client.post(&url).send().await.unwrap(); 524 | assert_eq!(res.status(), StatusCode::OK); 525 | 526 | assert_eq!( 527 | res.headers() 528 | .get(HeaderName::from_static("x-ratelimit-whitelisted")) 529 | .unwrap(), 530 | "true" 531 | ); 532 | assert!(res 533 | .headers() 534 | .get(HeaderName::from_static("x-ratelimit-limit")) 535 | .is_none()); 536 | assert!(res 537 | .headers() 538 | .get(HeaderName::from_static("x-ratelimit-remaining")) 539 | .is_none()); 540 | assert!(res 541 | .headers() 542 | .get(HeaderName::from_static("x-ratelimit-after")) 543 | .is_none()); 544 | 545 | let body = res.text().await.unwrap(); 546 | assert_eq!(&body, "Hello, Post World!"); 547 | } 548 | 549 | #[tokio::test] 550 | async fn test_error_handler() { 551 | let config = Arc::new( 552 | crate::governor::GovernorConfigBuilder::default() 553 | .per_second(10) 554 | .burst_size(1) 555 | .error_handler(|_| { 556 | http::Response::builder() 557 | .status(http::StatusCode::IM_A_TEAPOT) 558 | .body(axum::body::Body::from("a custom error string")) 559 | .unwrap() 560 | }) 561 | .finish() 562 | .unwrap(), 563 | ); 564 | 565 | let app = Router::new() 566 | .route("/", get(|| async { "Hello, World!" })) 567 | .layer(GovernorLayer { config }) 568 | .layer(TraceLayer::new_for_http()); 569 | 570 | let req = || http::Request::new(body::Body::empty()); 571 | 572 | let _ = app.clone().oneshot(req()).await.unwrap(); 573 | 574 | let res = app.oneshot(req()).await.unwrap(); 575 | 576 | // second response should match the response produced by GovernorConfigBuilder::error_handler 577 | 578 | assert_eq!(res.status(), http::StatusCode::IM_A_TEAPOT); 579 | let body = axum::body::to_bytes(res.into_body(), usize::MAX) 580 | .await 581 | .unwrap(); 582 | assert_eq!(body.as_ref(), b"a custom error string"); 583 | } 584 | } 585 | --------------------------------------------------------------------------------