├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── Deploy.md ├── LICENSE ├── README.md ├── buildspec.yml ├── docker └── Dockerfile ├── examples ├── actix.rs ├── axum.rs ├── rocket.rs └── warp.rs └── src ├── actix4.rs ├── brotli.rs ├── hyper014.rs ├── lib.rs ├── request.rs ├── rocket05.rs └── test_consts.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | /target_lambda/ 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 0.2.1 : 2023-01-08 4 | 5 | - Update lambda\_runtime 0.7.2 6 | 7 | ## 0.2.0 : 2022-07-10 8 | 9 | - Update lambda\_runtime 0.5.1 10 | - Remove "warp03" feature flag. Use "hyper" instead of it. 11 | 12 | ## 0.1.9 : 2022-02-28 13 | 14 | - Actix-Web 4.0.0 15 | 16 | ## 0.1.8 : 2021-11-13 17 | 18 | - Support [axum](target/release/examples/axum) web framework 19 | 20 | ## 0.1.7 : 2021-11-10 21 | 22 | - Support API gateway REST API 23 | 24 | ## 0.1.6 : 2021-08-18 25 | 26 | - Replace lamedh\_runtime with lambda\_runtime 0.4.0 27 | - Update Actix-Web to 4.0.0-beta.8 28 | 29 | ## 0.1.5 : 2021-06-21 30 | 31 | - Update Actix-Web to 4.0.0-beta.7 32 | 33 | ## 0.1.4 : 2021-06-21 34 | 35 | - Support transparent Brotli compression 36 | 37 | ## 0.1.3 : 2021-06-16 38 | 39 | - Fix bug in rawPath handling.\ 40 | Since API Gateway decodes percent encoding of rawPath, path containing %20 did not work correctly. 41 | - Remove unused struct fields 42 | 43 | ## 0.1.2 : 2021-06-15 44 | 45 | - Add Rocket support 46 | - Add examples 47 | 48 | ## 0.1.1 : 2021-06-14 49 | 50 | - Add Warp support 51 | 52 | ## 0.1.0 : 2021-06-14 53 | 54 | - Initial version, run Actix Web on AWS Lambda 55 | -------------------------------------------------------------------------------- /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 = "actix-codec" 7 | version = "0.5.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" 10 | dependencies = [ 11 | "bitflags", 12 | "bytes", 13 | "futures-core", 14 | "futures-sink", 15 | "log", 16 | "memchr", 17 | "pin-project-lite", 18 | "tokio", 19 | "tokio-util", 20 | ] 21 | 22 | [[package]] 23 | name = "actix-http" 24 | version = "3.2.2" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "0c83abf9903e1f0ad9973cc4f7b9767fd5a03a583f51a5b7a339e07987cd2724" 27 | dependencies = [ 28 | "actix-codec", 29 | "actix-rt", 30 | "actix-service", 31 | "actix-utils", 32 | "ahash", 33 | "base64 0.13.1", 34 | "bitflags", 35 | "bytes", 36 | "bytestring", 37 | "derive_more", 38 | "encoding_rs", 39 | "futures-core", 40 | "h2", 41 | "http", 42 | "httparse", 43 | "httpdate", 44 | "itoa", 45 | "language-tags", 46 | "local-channel", 47 | "mime", 48 | "percent-encoding", 49 | "pin-project-lite", 50 | "rand", 51 | "sha1", 52 | "smallvec", 53 | "tracing", 54 | ] 55 | 56 | [[package]] 57 | name = "actix-macros" 58 | version = "0.2.3" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" 61 | dependencies = [ 62 | "quote", 63 | "syn", 64 | ] 65 | 66 | [[package]] 67 | name = "actix-router" 68 | version = "0.5.1" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "d66ff4d247d2b160861fa2866457e85706833527840e4133f8f49aa423a38799" 71 | dependencies = [ 72 | "bytestring", 73 | "http", 74 | "regex", 75 | "serde", 76 | "tracing", 77 | ] 78 | 79 | [[package]] 80 | name = "actix-rt" 81 | version = "2.7.0" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" 84 | dependencies = [ 85 | "futures-core", 86 | "tokio", 87 | ] 88 | 89 | [[package]] 90 | name = "actix-server" 91 | version = "2.1.1" 92 | source = "registry+https://github.com/rust-lang/crates.io-index" 93 | checksum = "0da34f8e659ea1b077bb4637948b815cd3768ad5a188fdcd74ff4d84240cd824" 94 | dependencies = [ 95 | "actix-rt", 96 | "actix-service", 97 | "actix-utils", 98 | "futures-core", 99 | "futures-util", 100 | "mio", 101 | "num_cpus", 102 | "socket2", 103 | "tokio", 104 | "tracing", 105 | ] 106 | 107 | [[package]] 108 | name = "actix-service" 109 | version = "2.0.2" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" 112 | dependencies = [ 113 | "futures-core", 114 | "paste", 115 | "pin-project-lite", 116 | ] 117 | 118 | [[package]] 119 | name = "actix-utils" 120 | version = "3.0.1" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "88a1dcdff1466e3c2488e1cb5c36a71822750ad43839937f85d2f4d9f8b705d8" 123 | dependencies = [ 124 | "local-waker", 125 | "pin-project-lite", 126 | ] 127 | 128 | [[package]] 129 | name = "actix-web" 130 | version = "4.2.1" 131 | source = "registry+https://github.com/rust-lang/crates.io-index" 132 | checksum = "d48f7b6534e06c7bfc72ee91db7917d4af6afe23e7d223b51e68fffbb21e96b9" 133 | dependencies = [ 134 | "actix-codec", 135 | "actix-http", 136 | "actix-macros", 137 | "actix-router", 138 | "actix-rt", 139 | "actix-server", 140 | "actix-service", 141 | "actix-utils", 142 | "actix-web-codegen", 143 | "ahash", 144 | "bytes", 145 | "bytestring", 146 | "cfg-if", 147 | "cookie", 148 | "derive_more", 149 | "encoding_rs", 150 | "futures-core", 151 | "futures-util", 152 | "http", 153 | "itoa", 154 | "language-tags", 155 | "log", 156 | "mime", 157 | "once_cell", 158 | "pin-project-lite", 159 | "regex", 160 | "serde", 161 | "serde_json", 162 | "serde_urlencoded", 163 | "smallvec", 164 | "socket2", 165 | "time", 166 | "url", 167 | ] 168 | 169 | [[package]] 170 | name = "actix-web-codegen" 171 | version = "4.1.0" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "1fa9362663c8643d67b2d5eafba49e4cb2c8a053a29ed00a0bea121f17c76b13" 174 | dependencies = [ 175 | "actix-router", 176 | "proc-macro2", 177 | "quote", 178 | "syn", 179 | ] 180 | 181 | [[package]] 182 | name = "aead" 183 | version = "0.5.1" 184 | source = "registry+https://github.com/rust-lang/crates.io-index" 185 | checksum = "5c192eb8f11fc081b0fe4259ba5af04217d4e0faddd02417310a927911abd7c8" 186 | dependencies = [ 187 | "crypto-common", 188 | "generic-array", 189 | ] 190 | 191 | [[package]] 192 | name = "aes" 193 | version = "0.8.2" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | checksum = "433cfd6710c9986c576a25ca913c39d66a6474107b406f34f91d4a8923395241" 196 | dependencies = [ 197 | "cfg-if", 198 | "cipher", 199 | "cpufeatures", 200 | ] 201 | 202 | [[package]] 203 | name = "aes-gcm" 204 | version = "0.10.1" 205 | source = "registry+https://github.com/rust-lang/crates.io-index" 206 | checksum = "82e1366e0c69c9f927b1fa5ce2c7bf9eafc8f9268c0b9800729e8b267612447c" 207 | dependencies = [ 208 | "aead", 209 | "aes", 210 | "cipher", 211 | "ctr", 212 | "ghash", 213 | "subtle", 214 | ] 215 | 216 | [[package]] 217 | name = "ahash" 218 | version = "0.7.6" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 221 | dependencies = [ 222 | "getrandom", 223 | "once_cell", 224 | "version_check", 225 | ] 226 | 227 | [[package]] 228 | name = "aho-corasick" 229 | version = "0.7.20" 230 | source = "registry+https://github.com/rust-lang/crates.io-index" 231 | checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" 232 | dependencies = [ 233 | "memchr", 234 | ] 235 | 236 | [[package]] 237 | name = "alloc-no-stdlib" 238 | version = "2.0.4" 239 | source = "registry+https://github.com/rust-lang/crates.io-index" 240 | checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" 241 | 242 | [[package]] 243 | name = "alloc-stdlib" 244 | version = "0.2.2" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" 247 | dependencies = [ 248 | "alloc-no-stdlib", 249 | ] 250 | 251 | [[package]] 252 | name = "async-stream" 253 | version = "0.3.3" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "dad5c83079eae9969be7fadefe640a1c566901f05ff91ab221de4b6f68d9507e" 256 | dependencies = [ 257 | "async-stream-impl", 258 | "futures-core", 259 | ] 260 | 261 | [[package]] 262 | name = "async-stream-impl" 263 | version = "0.3.3" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "10f203db73a71dfa2fb6dd22763990fa26f3d2625a6da2da900d23b87d26be27" 266 | dependencies = [ 267 | "proc-macro2", 268 | "quote", 269 | "syn", 270 | ] 271 | 272 | [[package]] 273 | name = "async-trait" 274 | version = "0.1.61" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "705339e0e4a9690e2908d2b3d049d85682cf19fbd5782494498fbf7003a6a282" 277 | dependencies = [ 278 | "proc-macro2", 279 | "quote", 280 | "syn", 281 | ] 282 | 283 | [[package]] 284 | name = "atomic" 285 | version = "0.5.1" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "b88d82667eca772c4aa12f0f1348b3ae643424c8876448f3f7bd5787032e234c" 288 | dependencies = [ 289 | "autocfg", 290 | ] 291 | 292 | [[package]] 293 | name = "atty" 294 | version = "0.2.14" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 297 | dependencies = [ 298 | "hermit-abi 0.1.19", 299 | "libc", 300 | "winapi", 301 | ] 302 | 303 | [[package]] 304 | name = "autocfg" 305 | version = "1.1.0" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 308 | 309 | [[package]] 310 | name = "axum" 311 | version = "0.6.1" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "08b108ad2665fa3f6e6a517c3d80ec3e77d224c47d605167aefaa5d7ef97fa48" 314 | dependencies = [ 315 | "async-trait", 316 | "axum-core", 317 | "bitflags", 318 | "bytes", 319 | "futures-util", 320 | "http", 321 | "http-body", 322 | "hyper", 323 | "itoa", 324 | "matchit", 325 | "memchr", 326 | "mime", 327 | "percent-encoding", 328 | "pin-project-lite", 329 | "rustversion", 330 | "serde", 331 | "sync_wrapper", 332 | "tokio", 333 | "tower", 334 | "tower-http", 335 | "tower-layer", 336 | "tower-service", 337 | ] 338 | 339 | [[package]] 340 | name = "axum-core" 341 | version = "0.3.0" 342 | source = "registry+https://github.com/rust-lang/crates.io-index" 343 | checksum = "79b8558f5a0581152dc94dcd289132a1d377494bdeafcd41869b3258e3e2ad92" 344 | dependencies = [ 345 | "async-trait", 346 | "bytes", 347 | "futures-util", 348 | "http", 349 | "http-body", 350 | "mime", 351 | "rustversion", 352 | "tower-layer", 353 | "tower-service", 354 | ] 355 | 356 | [[package]] 357 | name = "base64" 358 | version = "0.13.1" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" 361 | 362 | [[package]] 363 | name = "base64" 364 | version = "0.20.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" 367 | 368 | [[package]] 369 | name = "binascii" 370 | version = "0.1.4" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" 373 | 374 | [[package]] 375 | name = "bitflags" 376 | version = "1.3.2" 377 | source = "registry+https://github.com/rust-lang/crates.io-index" 378 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 379 | 380 | [[package]] 381 | name = "block-buffer" 382 | version = "0.10.3" 383 | source = "registry+https://github.com/rust-lang/crates.io-index" 384 | checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" 385 | dependencies = [ 386 | "generic-array", 387 | ] 388 | 389 | [[package]] 390 | name = "brotli" 391 | version = "3.3.4" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" 394 | dependencies = [ 395 | "alloc-no-stdlib", 396 | "alloc-stdlib", 397 | "brotli-decompressor", 398 | ] 399 | 400 | [[package]] 401 | name = "brotli-decompressor" 402 | version = "2.3.2" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" 405 | dependencies = [ 406 | "alloc-no-stdlib", 407 | "alloc-stdlib", 408 | ] 409 | 410 | [[package]] 411 | name = "bytes" 412 | version = "1.3.0" 413 | source = "registry+https://github.com/rust-lang/crates.io-index" 414 | checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" 415 | 416 | [[package]] 417 | name = "bytestring" 418 | version = "1.2.0" 419 | source = "registry+https://github.com/rust-lang/crates.io-index" 420 | checksum = "f7f83e57d9154148e355404702e2694463241880b939570d7c97c014da7a69a1" 421 | dependencies = [ 422 | "bytes", 423 | ] 424 | 425 | [[package]] 426 | name = "cc" 427 | version = "1.0.78" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" 430 | 431 | [[package]] 432 | name = "cfg-if" 433 | version = "1.0.0" 434 | source = "registry+https://github.com/rust-lang/crates.io-index" 435 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 436 | 437 | [[package]] 438 | name = "cipher" 439 | version = "0.4.3" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "d1873270f8f7942c191139cb8a40fd228da6c3fd2fc376d7e92d47aa14aeb59e" 442 | dependencies = [ 443 | "crypto-common", 444 | "inout", 445 | ] 446 | 447 | [[package]] 448 | name = "convert_case" 449 | version = "0.4.0" 450 | source = "registry+https://github.com/rust-lang/crates.io-index" 451 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 452 | 453 | [[package]] 454 | name = "cookie" 455 | version = "0.16.2" 456 | source = "registry+https://github.com/rust-lang/crates.io-index" 457 | checksum = "e859cd57d0710d9e06c381b550c06e76992472a8c6d527aecd2fc673dcc231fb" 458 | dependencies = [ 459 | "aes-gcm", 460 | "base64 0.20.0", 461 | "hkdf", 462 | "hmac", 463 | "percent-encoding", 464 | "rand", 465 | "sha2", 466 | "subtle", 467 | "time", 468 | "version_check", 469 | ] 470 | 471 | [[package]] 472 | name = "cpufeatures" 473 | version = "0.2.5" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" 476 | dependencies = [ 477 | "libc", 478 | ] 479 | 480 | [[package]] 481 | name = "crypto-common" 482 | version = "0.1.6" 483 | source = "registry+https://github.com/rust-lang/crates.io-index" 484 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 485 | dependencies = [ 486 | "generic-array", 487 | "rand_core", 488 | "typenum", 489 | ] 490 | 491 | [[package]] 492 | name = "ctr" 493 | version = "0.9.2" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" 496 | dependencies = [ 497 | "cipher", 498 | ] 499 | 500 | [[package]] 501 | name = "derive_more" 502 | version = "0.99.17" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 505 | dependencies = [ 506 | "convert_case", 507 | "proc-macro2", 508 | "quote", 509 | "rustc_version", 510 | "syn", 511 | ] 512 | 513 | [[package]] 514 | name = "devise" 515 | version = "0.3.1" 516 | source = "registry+https://github.com/rust-lang/crates.io-index" 517 | checksum = "50c7580b072f1c8476148f16e0a0d5dedddab787da98d86c5082c5e9ed8ab595" 518 | dependencies = [ 519 | "devise_codegen", 520 | "devise_core", 521 | ] 522 | 523 | [[package]] 524 | name = "devise_codegen" 525 | version = "0.3.1" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "123c73e7a6e51b05c75fe1a1b2f4e241399ea5740ed810b0e3e6cacd9db5e7b2" 528 | dependencies = [ 529 | "devise_core", 530 | "quote", 531 | ] 532 | 533 | [[package]] 534 | name = "devise_core" 535 | version = "0.3.1" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "841ef46f4787d9097405cac4e70fb8644fc037b526e8c14054247c0263c400d0" 538 | dependencies = [ 539 | "bitflags", 540 | "proc-macro2", 541 | "proc-macro2-diagnostics", 542 | "quote", 543 | "syn", 544 | ] 545 | 546 | [[package]] 547 | name = "digest" 548 | version = "0.10.6" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" 551 | dependencies = [ 552 | "block-buffer", 553 | "crypto-common", 554 | "subtle", 555 | ] 556 | 557 | [[package]] 558 | name = "either" 559 | version = "1.8.0" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" 562 | 563 | [[package]] 564 | name = "encoding_rs" 565 | version = "0.8.31" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" 568 | dependencies = [ 569 | "cfg-if", 570 | ] 571 | 572 | [[package]] 573 | name = "fastrand" 574 | version = "1.8.0" 575 | source = "registry+https://github.com/rust-lang/crates.io-index" 576 | checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" 577 | dependencies = [ 578 | "instant", 579 | ] 580 | 581 | [[package]] 582 | name = "figment" 583 | version = "0.10.8" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "4e56602b469b2201400dec66a66aec5a9b8761ee97cd1b8c96ab2483fcc16cc9" 586 | dependencies = [ 587 | "atomic", 588 | "pear", 589 | "serde", 590 | "toml", 591 | "uncased", 592 | "version_check", 593 | ] 594 | 595 | [[package]] 596 | name = "fnv" 597 | version = "1.0.7" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 600 | 601 | [[package]] 602 | name = "form_urlencoded" 603 | version = "1.1.0" 604 | source = "registry+https://github.com/rust-lang/crates.io-index" 605 | checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" 606 | dependencies = [ 607 | "percent-encoding", 608 | ] 609 | 610 | [[package]] 611 | name = "futures" 612 | version = "0.3.25" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" 615 | dependencies = [ 616 | "futures-channel", 617 | "futures-core", 618 | "futures-executor", 619 | "futures-io", 620 | "futures-sink", 621 | "futures-task", 622 | "futures-util", 623 | ] 624 | 625 | [[package]] 626 | name = "futures-channel" 627 | version = "0.3.25" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" 630 | dependencies = [ 631 | "futures-core", 632 | "futures-sink", 633 | ] 634 | 635 | [[package]] 636 | name = "futures-core" 637 | version = "0.3.25" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" 640 | 641 | [[package]] 642 | name = "futures-executor" 643 | version = "0.3.25" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "7acc85df6714c176ab5edf386123fafe217be88c0840ec11f199441134a074e2" 646 | dependencies = [ 647 | "futures-core", 648 | "futures-task", 649 | "futures-util", 650 | ] 651 | 652 | [[package]] 653 | name = "futures-io" 654 | version = "0.3.25" 655 | source = "registry+https://github.com/rust-lang/crates.io-index" 656 | checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" 657 | 658 | [[package]] 659 | name = "futures-macro" 660 | version = "0.3.25" 661 | source = "registry+https://github.com/rust-lang/crates.io-index" 662 | checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" 663 | dependencies = [ 664 | "proc-macro2", 665 | "quote", 666 | "syn", 667 | ] 668 | 669 | [[package]] 670 | name = "futures-sink" 671 | version = "0.3.25" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" 674 | 675 | [[package]] 676 | name = "futures-task" 677 | version = "0.3.25" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" 680 | 681 | [[package]] 682 | name = "futures-util" 683 | version = "0.3.25" 684 | source = "registry+https://github.com/rust-lang/crates.io-index" 685 | checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" 686 | dependencies = [ 687 | "futures-channel", 688 | "futures-core", 689 | "futures-io", 690 | "futures-macro", 691 | "futures-sink", 692 | "futures-task", 693 | "memchr", 694 | "pin-project-lite", 695 | "pin-utils", 696 | "slab", 697 | ] 698 | 699 | [[package]] 700 | name = "generator" 701 | version = "0.7.2" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e" 704 | dependencies = [ 705 | "cc", 706 | "libc", 707 | "log", 708 | "rustversion", 709 | "windows", 710 | ] 711 | 712 | [[package]] 713 | name = "generic-array" 714 | version = "0.14.6" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" 717 | dependencies = [ 718 | "typenum", 719 | "version_check", 720 | ] 721 | 722 | [[package]] 723 | name = "getrandom" 724 | version = "0.2.8" 725 | source = "registry+https://github.com/rust-lang/crates.io-index" 726 | checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" 727 | dependencies = [ 728 | "cfg-if", 729 | "libc", 730 | "wasi", 731 | ] 732 | 733 | [[package]] 734 | name = "ghash" 735 | version = "0.5.0" 736 | source = "registry+https://github.com/rust-lang/crates.io-index" 737 | checksum = "d930750de5717d2dd0b8c0d42c076c0e884c81a73e6cab859bbd2339c71e3e40" 738 | dependencies = [ 739 | "opaque-debug", 740 | "polyval", 741 | ] 742 | 743 | [[package]] 744 | name = "glob" 745 | version = "0.3.1" 746 | source = "registry+https://github.com/rust-lang/crates.io-index" 747 | checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" 748 | 749 | [[package]] 750 | name = "h2" 751 | version = "0.3.15" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "5f9f29bc9dda355256b2916cf526ab02ce0aeaaaf2bad60d65ef3f12f11dd0f4" 754 | dependencies = [ 755 | "bytes", 756 | "fnv", 757 | "futures-core", 758 | "futures-sink", 759 | "futures-util", 760 | "http", 761 | "indexmap", 762 | "slab", 763 | "tokio", 764 | "tokio-util", 765 | "tracing", 766 | ] 767 | 768 | [[package]] 769 | name = "hashbrown" 770 | version = "0.12.3" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 773 | 774 | [[package]] 775 | name = "headers" 776 | version = "0.3.8" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "f3e372db8e5c0d213e0cd0b9be18be2aca3d44cf2fe30a9d46a65581cd454584" 779 | dependencies = [ 780 | "base64 0.13.1", 781 | "bitflags", 782 | "bytes", 783 | "headers-core", 784 | "http", 785 | "httpdate", 786 | "mime", 787 | "sha1", 788 | ] 789 | 790 | [[package]] 791 | name = "headers-core" 792 | version = "0.2.0" 793 | source = "registry+https://github.com/rust-lang/crates.io-index" 794 | checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" 795 | dependencies = [ 796 | "http", 797 | ] 798 | 799 | [[package]] 800 | name = "hermit-abi" 801 | version = "0.1.19" 802 | source = "registry+https://github.com/rust-lang/crates.io-index" 803 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 804 | dependencies = [ 805 | "libc", 806 | ] 807 | 808 | [[package]] 809 | name = "hermit-abi" 810 | version = "0.2.6" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 813 | dependencies = [ 814 | "libc", 815 | ] 816 | 817 | [[package]] 818 | name = "hkdf" 819 | version = "0.12.3" 820 | source = "registry+https://github.com/rust-lang/crates.io-index" 821 | checksum = "791a029f6b9fc27657f6f188ec6e5e43f6911f6f878e0dc5501396e09809d437" 822 | dependencies = [ 823 | "hmac", 824 | ] 825 | 826 | [[package]] 827 | name = "hmac" 828 | version = "0.12.1" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" 831 | dependencies = [ 832 | "digest", 833 | ] 834 | 835 | [[package]] 836 | name = "http" 837 | version = "0.2.8" 838 | source = "registry+https://github.com/rust-lang/crates.io-index" 839 | checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" 840 | dependencies = [ 841 | "bytes", 842 | "fnv", 843 | "itoa", 844 | ] 845 | 846 | [[package]] 847 | name = "http-body" 848 | version = "0.4.5" 849 | source = "registry+https://github.com/rust-lang/crates.io-index" 850 | checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" 851 | dependencies = [ 852 | "bytes", 853 | "http", 854 | "pin-project-lite", 855 | ] 856 | 857 | [[package]] 858 | name = "http-range-header" 859 | version = "0.3.0" 860 | source = "registry+https://github.com/rust-lang/crates.io-index" 861 | checksum = "0bfe8eed0a9285ef776bb792479ea3834e8b94e13d615c2f66d03dd50a435a29" 862 | 863 | [[package]] 864 | name = "httparse" 865 | version = "1.8.0" 866 | source = "registry+https://github.com/rust-lang/crates.io-index" 867 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 868 | 869 | [[package]] 870 | name = "httpdate" 871 | version = "1.0.2" 872 | source = "registry+https://github.com/rust-lang/crates.io-index" 873 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 874 | 875 | [[package]] 876 | name = "hyper" 877 | version = "0.14.23" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "034711faac9d2166cb1baf1a2fb0b60b1f277f8492fd72176c17f3515e1abd3c" 880 | dependencies = [ 881 | "bytes", 882 | "futures-channel", 883 | "futures-core", 884 | "futures-util", 885 | "h2", 886 | "http", 887 | "http-body", 888 | "httparse", 889 | "httpdate", 890 | "itoa", 891 | "pin-project-lite", 892 | "socket2", 893 | "tokio", 894 | "tower-service", 895 | "tracing", 896 | "want", 897 | ] 898 | 899 | [[package]] 900 | name = "idna" 901 | version = "0.3.0" 902 | source = "registry+https://github.com/rust-lang/crates.io-index" 903 | checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" 904 | dependencies = [ 905 | "unicode-bidi", 906 | "unicode-normalization", 907 | ] 908 | 909 | [[package]] 910 | name = "indexmap" 911 | version = "1.9.2" 912 | source = "registry+https://github.com/rust-lang/crates.io-index" 913 | checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" 914 | dependencies = [ 915 | "autocfg", 916 | "hashbrown", 917 | "serde", 918 | ] 919 | 920 | [[package]] 921 | name = "inlinable_string" 922 | version = "0.1.15" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" 925 | 926 | [[package]] 927 | name = "inout" 928 | version = "0.1.3" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" 931 | dependencies = [ 932 | "generic-array", 933 | ] 934 | 935 | [[package]] 936 | name = "instant" 937 | version = "0.1.12" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 940 | dependencies = [ 941 | "cfg-if", 942 | ] 943 | 944 | [[package]] 945 | name = "itoa" 946 | version = "1.0.5" 947 | source = "registry+https://github.com/rust-lang/crates.io-index" 948 | checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" 949 | 950 | [[package]] 951 | name = "lambda-web" 952 | version = "0.2.1" 953 | dependencies = [ 954 | "actix-http", 955 | "actix-service", 956 | "actix-web", 957 | "axum", 958 | "base64 0.13.1", 959 | "brotli", 960 | "hyper", 961 | "lambda_runtime", 962 | "percent-encoding", 963 | "rocket", 964 | "serde", 965 | "serde_json", 966 | "tokio", 967 | "tower", 968 | "warp", 969 | ] 970 | 971 | [[package]] 972 | name = "lambda_runtime" 973 | version = "0.7.2" 974 | source = "registry+https://github.com/rust-lang/crates.io-index" 975 | checksum = "05b0f7b71dc8707ecf8e230aa1ef8ffad0a718742f9195fda4edae1ccf48af1c" 976 | dependencies = [ 977 | "async-stream", 978 | "bytes", 979 | "futures", 980 | "http", 981 | "hyper", 982 | "lambda_runtime_api_client", 983 | "serde", 984 | "serde_json", 985 | "tokio", 986 | "tokio-stream", 987 | "tower", 988 | "tracing", 989 | ] 990 | 991 | [[package]] 992 | name = "lambda_runtime_api_client" 993 | version = "0.7.0" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "7210012be904051520f0dc502140ba599bae3042b65b3737b87727f1aa88a7d6" 996 | dependencies = [ 997 | "http", 998 | "hyper", 999 | "tokio", 1000 | "tower-service", 1001 | ] 1002 | 1003 | [[package]] 1004 | name = "language-tags" 1005 | version = "0.3.2" 1006 | source = "registry+https://github.com/rust-lang/crates.io-index" 1007 | checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" 1008 | 1009 | [[package]] 1010 | name = "lazy_static" 1011 | version = "1.4.0" 1012 | source = "registry+https://github.com/rust-lang/crates.io-index" 1013 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1014 | 1015 | [[package]] 1016 | name = "libc" 1017 | version = "0.2.139" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" 1020 | 1021 | [[package]] 1022 | name = "local-channel" 1023 | version = "0.1.3" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" 1026 | dependencies = [ 1027 | "futures-core", 1028 | "futures-sink", 1029 | "futures-util", 1030 | "local-waker", 1031 | ] 1032 | 1033 | [[package]] 1034 | name = "local-waker" 1035 | version = "0.1.3" 1036 | source = "registry+https://github.com/rust-lang/crates.io-index" 1037 | checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" 1038 | 1039 | [[package]] 1040 | name = "lock_api" 1041 | version = "0.4.9" 1042 | source = "registry+https://github.com/rust-lang/crates.io-index" 1043 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 1044 | dependencies = [ 1045 | "autocfg", 1046 | "scopeguard", 1047 | ] 1048 | 1049 | [[package]] 1050 | name = "log" 1051 | version = "0.4.17" 1052 | source = "registry+https://github.com/rust-lang/crates.io-index" 1053 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 1054 | dependencies = [ 1055 | "cfg-if", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "loom" 1060 | version = "0.5.6" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" 1063 | dependencies = [ 1064 | "cfg-if", 1065 | "generator", 1066 | "scoped-tls", 1067 | "serde", 1068 | "serde_json", 1069 | "tracing", 1070 | "tracing-subscriber", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "matchers" 1075 | version = "0.1.0" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" 1078 | dependencies = [ 1079 | "regex-automata", 1080 | ] 1081 | 1082 | [[package]] 1083 | name = "matchit" 1084 | version = "0.7.0" 1085 | source = "registry+https://github.com/rust-lang/crates.io-index" 1086 | checksum = "b87248edafb776e59e6ee64a79086f65890d3510f2c656c000bf2a7e8a0aea40" 1087 | 1088 | [[package]] 1089 | name = "memchr" 1090 | version = "2.5.0" 1091 | source = "registry+https://github.com/rust-lang/crates.io-index" 1092 | checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" 1093 | 1094 | [[package]] 1095 | name = "mime" 1096 | version = "0.3.16" 1097 | source = "registry+https://github.com/rust-lang/crates.io-index" 1098 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 1099 | 1100 | [[package]] 1101 | name = "mime_guess" 1102 | version = "2.0.4" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" 1105 | dependencies = [ 1106 | "mime", 1107 | "unicase", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "mio" 1112 | version = "0.8.5" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" 1115 | dependencies = [ 1116 | "libc", 1117 | "log", 1118 | "wasi", 1119 | "windows-sys", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "multer" 1124 | version = "2.0.4" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "6ed4198ce7a4cbd2a57af78d28c6fbb57d81ac5f1d6ad79ac6c5587419cbdf22" 1127 | dependencies = [ 1128 | "bytes", 1129 | "encoding_rs", 1130 | "futures-util", 1131 | "http", 1132 | "httparse", 1133 | "log", 1134 | "memchr", 1135 | "mime", 1136 | "spin", 1137 | "tokio", 1138 | "tokio-util", 1139 | "version_check", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "nu-ansi-term" 1144 | version = "0.46.0" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" 1147 | dependencies = [ 1148 | "overload", 1149 | "winapi", 1150 | ] 1151 | 1152 | [[package]] 1153 | name = "num_cpus" 1154 | version = "1.15.0" 1155 | source = "registry+https://github.com/rust-lang/crates.io-index" 1156 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 1157 | dependencies = [ 1158 | "hermit-abi 0.2.6", 1159 | "libc", 1160 | ] 1161 | 1162 | [[package]] 1163 | name = "once_cell" 1164 | version = "1.17.0" 1165 | source = "registry+https://github.com/rust-lang/crates.io-index" 1166 | checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" 1167 | 1168 | [[package]] 1169 | name = "opaque-debug" 1170 | version = "0.3.0" 1171 | source = "registry+https://github.com/rust-lang/crates.io-index" 1172 | checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" 1173 | 1174 | [[package]] 1175 | name = "overload" 1176 | version = "0.1.1" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" 1179 | 1180 | [[package]] 1181 | name = "parking_lot" 1182 | version = "0.12.1" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1185 | dependencies = [ 1186 | "lock_api", 1187 | "parking_lot_core", 1188 | ] 1189 | 1190 | [[package]] 1191 | name = "parking_lot_core" 1192 | version = "0.9.5" 1193 | source = "registry+https://github.com/rust-lang/crates.io-index" 1194 | checksum = "7ff9f3fef3968a3ec5945535ed654cb38ff72d7495a25619e2247fb15a2ed9ba" 1195 | dependencies = [ 1196 | "cfg-if", 1197 | "libc", 1198 | "redox_syscall", 1199 | "smallvec", 1200 | "windows-sys", 1201 | ] 1202 | 1203 | [[package]] 1204 | name = "paste" 1205 | version = "1.0.11" 1206 | source = "registry+https://github.com/rust-lang/crates.io-index" 1207 | checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" 1208 | 1209 | [[package]] 1210 | name = "pear" 1211 | version = "0.2.3" 1212 | source = "registry+https://github.com/rust-lang/crates.io-index" 1213 | checksum = "15e44241c5e4c868e3eaa78b7c1848cadd6344ed4f54d029832d32b415a58702" 1214 | dependencies = [ 1215 | "inlinable_string", 1216 | "pear_codegen", 1217 | "yansi", 1218 | ] 1219 | 1220 | [[package]] 1221 | name = "pear_codegen" 1222 | version = "0.2.3" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "82a5ca643c2303ecb740d506539deba189e16f2754040a42901cd8105d0282d0" 1225 | dependencies = [ 1226 | "proc-macro2", 1227 | "proc-macro2-diagnostics", 1228 | "quote", 1229 | "syn", 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "percent-encoding" 1234 | version = "2.2.0" 1235 | source = "registry+https://github.com/rust-lang/crates.io-index" 1236 | checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" 1237 | 1238 | [[package]] 1239 | name = "pin-project" 1240 | version = "1.0.12" 1241 | source = "registry+https://github.com/rust-lang/crates.io-index" 1242 | checksum = "ad29a609b6bcd67fee905812e544992d216af9d755757c05ed2d0e15a74c6ecc" 1243 | dependencies = [ 1244 | "pin-project-internal", 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "pin-project-internal" 1249 | version = "1.0.12" 1250 | source = "registry+https://github.com/rust-lang/crates.io-index" 1251 | checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" 1252 | dependencies = [ 1253 | "proc-macro2", 1254 | "quote", 1255 | "syn", 1256 | ] 1257 | 1258 | [[package]] 1259 | name = "pin-project-lite" 1260 | version = "0.2.9" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 1263 | 1264 | [[package]] 1265 | name = "pin-utils" 1266 | version = "0.1.0" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1269 | 1270 | [[package]] 1271 | name = "polyval" 1272 | version = "0.6.0" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "7ef234e08c11dfcb2e56f79fd70f6f2eb7f025c0ce2333e82f4f0518ecad30c6" 1275 | dependencies = [ 1276 | "cfg-if", 1277 | "cpufeatures", 1278 | "opaque-debug", 1279 | "universal-hash", 1280 | ] 1281 | 1282 | [[package]] 1283 | name = "ppv-lite86" 1284 | version = "0.2.17" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" 1287 | 1288 | [[package]] 1289 | name = "proc-macro2" 1290 | version = "1.0.49" 1291 | source = "registry+https://github.com/rust-lang/crates.io-index" 1292 | checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" 1293 | dependencies = [ 1294 | "unicode-ident", 1295 | ] 1296 | 1297 | [[package]] 1298 | name = "proc-macro2-diagnostics" 1299 | version = "0.9.1" 1300 | source = "registry+https://github.com/rust-lang/crates.io-index" 1301 | checksum = "4bf29726d67464d49fa6224a1d07936a8c08bb3fba727c7493f6cf1616fdaada" 1302 | dependencies = [ 1303 | "proc-macro2", 1304 | "quote", 1305 | "syn", 1306 | "version_check", 1307 | "yansi", 1308 | ] 1309 | 1310 | [[package]] 1311 | name = "quote" 1312 | version = "1.0.23" 1313 | source = "registry+https://github.com/rust-lang/crates.io-index" 1314 | checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" 1315 | dependencies = [ 1316 | "proc-macro2", 1317 | ] 1318 | 1319 | [[package]] 1320 | name = "rand" 1321 | version = "0.8.5" 1322 | source = "registry+https://github.com/rust-lang/crates.io-index" 1323 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1324 | dependencies = [ 1325 | "libc", 1326 | "rand_chacha", 1327 | "rand_core", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "rand_chacha" 1332 | version = "0.3.1" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1335 | dependencies = [ 1336 | "ppv-lite86", 1337 | "rand_core", 1338 | ] 1339 | 1340 | [[package]] 1341 | name = "rand_core" 1342 | version = "0.6.4" 1343 | source = "registry+https://github.com/rust-lang/crates.io-index" 1344 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1345 | dependencies = [ 1346 | "getrandom", 1347 | ] 1348 | 1349 | [[package]] 1350 | name = "redox_syscall" 1351 | version = "0.2.16" 1352 | source = "registry+https://github.com/rust-lang/crates.io-index" 1353 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 1354 | dependencies = [ 1355 | "bitflags", 1356 | ] 1357 | 1358 | [[package]] 1359 | name = "ref-cast" 1360 | version = "1.0.14" 1361 | source = "registry+https://github.com/rust-lang/crates.io-index" 1362 | checksum = "8c78fb8c9293bcd48ef6fce7b4ca950ceaf21210de6e105a883ee280c0f7b9ed" 1363 | dependencies = [ 1364 | "ref-cast-impl", 1365 | ] 1366 | 1367 | [[package]] 1368 | name = "ref-cast-impl" 1369 | version = "1.0.14" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" 1372 | dependencies = [ 1373 | "proc-macro2", 1374 | "quote", 1375 | "syn", 1376 | ] 1377 | 1378 | [[package]] 1379 | name = "regex" 1380 | version = "1.7.0" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" 1383 | dependencies = [ 1384 | "aho-corasick", 1385 | "memchr", 1386 | "regex-syntax", 1387 | ] 1388 | 1389 | [[package]] 1390 | name = "regex-automata" 1391 | version = "0.1.10" 1392 | source = "registry+https://github.com/rust-lang/crates.io-index" 1393 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1394 | dependencies = [ 1395 | "regex-syntax", 1396 | ] 1397 | 1398 | [[package]] 1399 | name = "regex-syntax" 1400 | version = "0.6.28" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" 1403 | 1404 | [[package]] 1405 | name = "remove_dir_all" 1406 | version = "0.5.3" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1409 | dependencies = [ 1410 | "winapi", 1411 | ] 1412 | 1413 | [[package]] 1414 | name = "rocket" 1415 | version = "0.5.0-rc.2" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "98ead083fce4a405feb349cf09abdf64471c6077f14e0ce59364aa90d4b99317" 1418 | dependencies = [ 1419 | "async-stream", 1420 | "async-trait", 1421 | "atomic", 1422 | "atty", 1423 | "binascii", 1424 | "bytes", 1425 | "either", 1426 | "figment", 1427 | "futures", 1428 | "indexmap", 1429 | "log", 1430 | "memchr", 1431 | "multer", 1432 | "num_cpus", 1433 | "parking_lot", 1434 | "pin-project-lite", 1435 | "rand", 1436 | "ref-cast", 1437 | "rocket_codegen", 1438 | "rocket_http", 1439 | "serde", 1440 | "state", 1441 | "tempfile", 1442 | "time", 1443 | "tokio", 1444 | "tokio-stream", 1445 | "tokio-util", 1446 | "ubyte", 1447 | "version_check", 1448 | "yansi", 1449 | ] 1450 | 1451 | [[package]] 1452 | name = "rocket_codegen" 1453 | version = "0.5.0-rc.2" 1454 | source = "registry+https://github.com/rust-lang/crates.io-index" 1455 | checksum = "d6aeb6bb9c61e9cd2c00d70ea267bf36f76a4cc615e5908b349c2f9d93999b47" 1456 | dependencies = [ 1457 | "devise", 1458 | "glob", 1459 | "indexmap", 1460 | "proc-macro2", 1461 | "quote", 1462 | "rocket_http", 1463 | "syn", 1464 | "unicode-xid", 1465 | ] 1466 | 1467 | [[package]] 1468 | name = "rocket_http" 1469 | version = "0.5.0-rc.2" 1470 | source = "registry+https://github.com/rust-lang/crates.io-index" 1471 | checksum = "2ded65d127954de3c12471630bf4b81a2792f065984461e65b91d0fdaafc17a2" 1472 | dependencies = [ 1473 | "cookie", 1474 | "either", 1475 | "futures", 1476 | "http", 1477 | "hyper", 1478 | "indexmap", 1479 | "log", 1480 | "memchr", 1481 | "pear", 1482 | "percent-encoding", 1483 | "pin-project-lite", 1484 | "ref-cast", 1485 | "serde", 1486 | "smallvec", 1487 | "stable-pattern", 1488 | "state", 1489 | "time", 1490 | "tokio", 1491 | "uncased", 1492 | ] 1493 | 1494 | [[package]] 1495 | name = "rustc_version" 1496 | version = "0.4.0" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1499 | dependencies = [ 1500 | "semver", 1501 | ] 1502 | 1503 | [[package]] 1504 | name = "rustls-pemfile" 1505 | version = "0.2.1" 1506 | source = "registry+https://github.com/rust-lang/crates.io-index" 1507 | checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" 1508 | dependencies = [ 1509 | "base64 0.13.1", 1510 | ] 1511 | 1512 | [[package]] 1513 | name = "rustversion" 1514 | version = "1.0.11" 1515 | source = "registry+https://github.com/rust-lang/crates.io-index" 1516 | checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" 1517 | 1518 | [[package]] 1519 | name = "ryu" 1520 | version = "1.0.12" 1521 | source = "registry+https://github.com/rust-lang/crates.io-index" 1522 | checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" 1523 | 1524 | [[package]] 1525 | name = "scoped-tls" 1526 | version = "1.0.1" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 1529 | 1530 | [[package]] 1531 | name = "scopeguard" 1532 | version = "1.1.0" 1533 | source = "registry+https://github.com/rust-lang/crates.io-index" 1534 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1535 | 1536 | [[package]] 1537 | name = "semver" 1538 | version = "1.0.16" 1539 | source = "registry+https://github.com/rust-lang/crates.io-index" 1540 | checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" 1541 | 1542 | [[package]] 1543 | name = "serde" 1544 | version = "1.0.152" 1545 | source = "registry+https://github.com/rust-lang/crates.io-index" 1546 | checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" 1547 | dependencies = [ 1548 | "serde_derive", 1549 | ] 1550 | 1551 | [[package]] 1552 | name = "serde_derive" 1553 | version = "1.0.152" 1554 | source = "registry+https://github.com/rust-lang/crates.io-index" 1555 | checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" 1556 | dependencies = [ 1557 | "proc-macro2", 1558 | "quote", 1559 | "syn", 1560 | ] 1561 | 1562 | [[package]] 1563 | name = "serde_json" 1564 | version = "1.0.91" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" 1567 | dependencies = [ 1568 | "itoa", 1569 | "ryu", 1570 | "serde", 1571 | ] 1572 | 1573 | [[package]] 1574 | name = "serde_urlencoded" 1575 | version = "0.7.1" 1576 | source = "registry+https://github.com/rust-lang/crates.io-index" 1577 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1578 | dependencies = [ 1579 | "form_urlencoded", 1580 | "itoa", 1581 | "ryu", 1582 | "serde", 1583 | ] 1584 | 1585 | [[package]] 1586 | name = "sha1" 1587 | version = "0.10.5" 1588 | source = "registry+https://github.com/rust-lang/crates.io-index" 1589 | checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" 1590 | dependencies = [ 1591 | "cfg-if", 1592 | "cpufeatures", 1593 | "digest", 1594 | ] 1595 | 1596 | [[package]] 1597 | name = "sha2" 1598 | version = "0.10.6" 1599 | source = "registry+https://github.com/rust-lang/crates.io-index" 1600 | checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" 1601 | dependencies = [ 1602 | "cfg-if", 1603 | "cpufeatures", 1604 | "digest", 1605 | ] 1606 | 1607 | [[package]] 1608 | name = "sharded-slab" 1609 | version = "0.1.4" 1610 | source = "registry+https://github.com/rust-lang/crates.io-index" 1611 | checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" 1612 | dependencies = [ 1613 | "lazy_static", 1614 | ] 1615 | 1616 | [[package]] 1617 | name = "signal-hook-registry" 1618 | version = "1.4.0" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1621 | dependencies = [ 1622 | "libc", 1623 | ] 1624 | 1625 | [[package]] 1626 | name = "slab" 1627 | version = "0.4.7" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" 1630 | dependencies = [ 1631 | "autocfg", 1632 | ] 1633 | 1634 | [[package]] 1635 | name = "smallvec" 1636 | version = "1.10.0" 1637 | source = "registry+https://github.com/rust-lang/crates.io-index" 1638 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 1639 | 1640 | [[package]] 1641 | name = "socket2" 1642 | version = "0.4.7" 1643 | source = "registry+https://github.com/rust-lang/crates.io-index" 1644 | checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" 1645 | dependencies = [ 1646 | "libc", 1647 | "winapi", 1648 | ] 1649 | 1650 | [[package]] 1651 | name = "spin" 1652 | version = "0.9.4" 1653 | source = "registry+https://github.com/rust-lang/crates.io-index" 1654 | checksum = "7f6002a767bff9e83f8eeecf883ecb8011875a21ae8da43bffb817a57e78cc09" 1655 | 1656 | [[package]] 1657 | name = "stable-pattern" 1658 | version = "0.1.0" 1659 | source = "registry+https://github.com/rust-lang/crates.io-index" 1660 | checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" 1661 | dependencies = [ 1662 | "memchr", 1663 | ] 1664 | 1665 | [[package]] 1666 | name = "state" 1667 | version = "0.5.3" 1668 | source = "registry+https://github.com/rust-lang/crates.io-index" 1669 | checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" 1670 | dependencies = [ 1671 | "loom", 1672 | ] 1673 | 1674 | [[package]] 1675 | name = "subtle" 1676 | version = "2.4.1" 1677 | source = "registry+https://github.com/rust-lang/crates.io-index" 1678 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 1679 | 1680 | [[package]] 1681 | name = "syn" 1682 | version = "1.0.107" 1683 | source = "registry+https://github.com/rust-lang/crates.io-index" 1684 | checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" 1685 | dependencies = [ 1686 | "proc-macro2", 1687 | "quote", 1688 | "unicode-ident", 1689 | ] 1690 | 1691 | [[package]] 1692 | name = "sync_wrapper" 1693 | version = "0.1.1" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | checksum = "20518fe4a4c9acf048008599e464deb21beeae3d3578418951a189c235a7a9a8" 1696 | 1697 | [[package]] 1698 | name = "tempfile" 1699 | version = "3.3.0" 1700 | source = "registry+https://github.com/rust-lang/crates.io-index" 1701 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 1702 | dependencies = [ 1703 | "cfg-if", 1704 | "fastrand", 1705 | "libc", 1706 | "redox_syscall", 1707 | "remove_dir_all", 1708 | "winapi", 1709 | ] 1710 | 1711 | [[package]] 1712 | name = "thread_local" 1713 | version = "1.1.4" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" 1716 | dependencies = [ 1717 | "once_cell", 1718 | ] 1719 | 1720 | [[package]] 1721 | name = "time" 1722 | version = "0.3.17" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" 1725 | dependencies = [ 1726 | "itoa", 1727 | "serde", 1728 | "time-core", 1729 | "time-macros", 1730 | ] 1731 | 1732 | [[package]] 1733 | name = "time-core" 1734 | version = "0.1.0" 1735 | source = "registry+https://github.com/rust-lang/crates.io-index" 1736 | checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" 1737 | 1738 | [[package]] 1739 | name = "time-macros" 1740 | version = "0.2.6" 1741 | source = "registry+https://github.com/rust-lang/crates.io-index" 1742 | checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" 1743 | dependencies = [ 1744 | "time-core", 1745 | ] 1746 | 1747 | [[package]] 1748 | name = "tinyvec" 1749 | version = "1.6.0" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" 1752 | dependencies = [ 1753 | "tinyvec_macros", 1754 | ] 1755 | 1756 | [[package]] 1757 | name = "tinyvec_macros" 1758 | version = "0.1.0" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1761 | 1762 | [[package]] 1763 | name = "tokio" 1764 | version = "1.24.1" 1765 | source = "registry+https://github.com/rust-lang/crates.io-index" 1766 | checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae" 1767 | dependencies = [ 1768 | "autocfg", 1769 | "bytes", 1770 | "libc", 1771 | "memchr", 1772 | "mio", 1773 | "num_cpus", 1774 | "parking_lot", 1775 | "pin-project-lite", 1776 | "signal-hook-registry", 1777 | "socket2", 1778 | "tokio-macros", 1779 | "windows-sys", 1780 | ] 1781 | 1782 | [[package]] 1783 | name = "tokio-macros" 1784 | version = "1.8.2" 1785 | source = "registry+https://github.com/rust-lang/crates.io-index" 1786 | checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" 1787 | dependencies = [ 1788 | "proc-macro2", 1789 | "quote", 1790 | "syn", 1791 | ] 1792 | 1793 | [[package]] 1794 | name = "tokio-stream" 1795 | version = "0.1.11" 1796 | source = "registry+https://github.com/rust-lang/crates.io-index" 1797 | checksum = "d660770404473ccd7bc9f8b28494a811bc18542b915c0855c51e8f419d5223ce" 1798 | dependencies = [ 1799 | "futures-core", 1800 | "pin-project-lite", 1801 | "tokio", 1802 | ] 1803 | 1804 | [[package]] 1805 | name = "tokio-util" 1806 | version = "0.7.4" 1807 | source = "registry+https://github.com/rust-lang/crates.io-index" 1808 | checksum = "0bb2e075f03b3d66d8d8785356224ba688d2906a371015e225beeb65ca92c740" 1809 | dependencies = [ 1810 | "bytes", 1811 | "futures-core", 1812 | "futures-sink", 1813 | "pin-project-lite", 1814 | "tokio", 1815 | "tracing", 1816 | ] 1817 | 1818 | [[package]] 1819 | name = "toml" 1820 | version = "0.5.10" 1821 | source = "registry+https://github.com/rust-lang/crates.io-index" 1822 | checksum = "1333c76748e868a4d9d1017b5ab53171dfd095f70c712fdb4653a406547f598f" 1823 | dependencies = [ 1824 | "serde", 1825 | ] 1826 | 1827 | [[package]] 1828 | name = "tower" 1829 | version = "0.4.13" 1830 | source = "registry+https://github.com/rust-lang/crates.io-index" 1831 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1832 | dependencies = [ 1833 | "futures-core", 1834 | "futures-util", 1835 | "pin-project", 1836 | "pin-project-lite", 1837 | "tokio", 1838 | "tower-layer", 1839 | "tower-service", 1840 | "tracing", 1841 | ] 1842 | 1843 | [[package]] 1844 | name = "tower-http" 1845 | version = "0.3.5" 1846 | source = "registry+https://github.com/rust-lang/crates.io-index" 1847 | checksum = "f873044bf02dd1e8239e9c1293ea39dad76dc594ec16185d0a1bf31d8dc8d858" 1848 | dependencies = [ 1849 | "bitflags", 1850 | "bytes", 1851 | "futures-core", 1852 | "futures-util", 1853 | "http", 1854 | "http-body", 1855 | "http-range-header", 1856 | "pin-project-lite", 1857 | "tower", 1858 | "tower-layer", 1859 | "tower-service", 1860 | ] 1861 | 1862 | [[package]] 1863 | name = "tower-layer" 1864 | version = "0.3.2" 1865 | source = "registry+https://github.com/rust-lang/crates.io-index" 1866 | checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" 1867 | 1868 | [[package]] 1869 | name = "tower-service" 1870 | version = "0.3.2" 1871 | source = "registry+https://github.com/rust-lang/crates.io-index" 1872 | checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" 1873 | 1874 | [[package]] 1875 | name = "tracing" 1876 | version = "0.1.37" 1877 | source = "registry+https://github.com/rust-lang/crates.io-index" 1878 | checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" 1879 | dependencies = [ 1880 | "cfg-if", 1881 | "log", 1882 | "pin-project-lite", 1883 | "tracing-attributes", 1884 | "tracing-core", 1885 | ] 1886 | 1887 | [[package]] 1888 | name = "tracing-attributes" 1889 | version = "0.1.23" 1890 | source = "registry+https://github.com/rust-lang/crates.io-index" 1891 | checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" 1892 | dependencies = [ 1893 | "proc-macro2", 1894 | "quote", 1895 | "syn", 1896 | ] 1897 | 1898 | [[package]] 1899 | name = "tracing-core" 1900 | version = "0.1.30" 1901 | source = "registry+https://github.com/rust-lang/crates.io-index" 1902 | checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" 1903 | dependencies = [ 1904 | "once_cell", 1905 | "valuable", 1906 | ] 1907 | 1908 | [[package]] 1909 | name = "tracing-log" 1910 | version = "0.1.3" 1911 | source = "registry+https://github.com/rust-lang/crates.io-index" 1912 | checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" 1913 | dependencies = [ 1914 | "lazy_static", 1915 | "log", 1916 | "tracing-core", 1917 | ] 1918 | 1919 | [[package]] 1920 | name = "tracing-subscriber" 1921 | version = "0.3.16" 1922 | source = "registry+https://github.com/rust-lang/crates.io-index" 1923 | checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" 1924 | dependencies = [ 1925 | "matchers", 1926 | "nu-ansi-term", 1927 | "once_cell", 1928 | "regex", 1929 | "sharded-slab", 1930 | "smallvec", 1931 | "thread_local", 1932 | "tracing", 1933 | "tracing-core", 1934 | "tracing-log", 1935 | ] 1936 | 1937 | [[package]] 1938 | name = "try-lock" 1939 | version = "0.2.4" 1940 | source = "registry+https://github.com/rust-lang/crates.io-index" 1941 | checksum = "3528ecfd12c466c6f163363caf2d02a71161dd5e1cc6ae7b34207ea2d42d81ed" 1942 | 1943 | [[package]] 1944 | name = "typenum" 1945 | version = "1.16.0" 1946 | source = "registry+https://github.com/rust-lang/crates.io-index" 1947 | checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" 1948 | 1949 | [[package]] 1950 | name = "ubyte" 1951 | version = "0.10.3" 1952 | source = "registry+https://github.com/rust-lang/crates.io-index" 1953 | checksum = "c81f0dae7d286ad0d9366d7679a77934cfc3cf3a8d67e82669794412b2368fe6" 1954 | dependencies = [ 1955 | "serde", 1956 | ] 1957 | 1958 | [[package]] 1959 | name = "uncased" 1960 | version = "0.9.7" 1961 | source = "registry+https://github.com/rust-lang/crates.io-index" 1962 | checksum = "09b01702b0fd0b3fadcf98e098780badda8742d4f4a7676615cad90e8ac73622" 1963 | dependencies = [ 1964 | "serde", 1965 | "version_check", 1966 | ] 1967 | 1968 | [[package]] 1969 | name = "unicase" 1970 | version = "2.6.0" 1971 | source = "registry+https://github.com/rust-lang/crates.io-index" 1972 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 1973 | dependencies = [ 1974 | "version_check", 1975 | ] 1976 | 1977 | [[package]] 1978 | name = "unicode-bidi" 1979 | version = "0.3.8" 1980 | source = "registry+https://github.com/rust-lang/crates.io-index" 1981 | checksum = "099b7128301d285f79ddd55b9a83d5e6b9e97c92e0ea0daebee7263e932de992" 1982 | 1983 | [[package]] 1984 | name = "unicode-ident" 1985 | version = "1.0.6" 1986 | source = "registry+https://github.com/rust-lang/crates.io-index" 1987 | checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" 1988 | 1989 | [[package]] 1990 | name = "unicode-normalization" 1991 | version = "0.1.22" 1992 | source = "registry+https://github.com/rust-lang/crates.io-index" 1993 | checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" 1994 | dependencies = [ 1995 | "tinyvec", 1996 | ] 1997 | 1998 | [[package]] 1999 | name = "unicode-xid" 2000 | version = "0.2.4" 2001 | source = "registry+https://github.com/rust-lang/crates.io-index" 2002 | checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" 2003 | 2004 | [[package]] 2005 | name = "universal-hash" 2006 | version = "0.5.0" 2007 | source = "registry+https://github.com/rust-lang/crates.io-index" 2008 | checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" 2009 | dependencies = [ 2010 | "crypto-common", 2011 | "subtle", 2012 | ] 2013 | 2014 | [[package]] 2015 | name = "url" 2016 | version = "2.3.1" 2017 | source = "registry+https://github.com/rust-lang/crates.io-index" 2018 | checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" 2019 | dependencies = [ 2020 | "form_urlencoded", 2021 | "idna", 2022 | "percent-encoding", 2023 | ] 2024 | 2025 | [[package]] 2026 | name = "valuable" 2027 | version = "0.1.0" 2028 | source = "registry+https://github.com/rust-lang/crates.io-index" 2029 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 2030 | 2031 | [[package]] 2032 | name = "version_check" 2033 | version = "0.9.4" 2034 | source = "registry+https://github.com/rust-lang/crates.io-index" 2035 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 2036 | 2037 | [[package]] 2038 | name = "want" 2039 | version = "0.3.0" 2040 | source = "registry+https://github.com/rust-lang/crates.io-index" 2041 | checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" 2042 | dependencies = [ 2043 | "log", 2044 | "try-lock", 2045 | ] 2046 | 2047 | [[package]] 2048 | name = "warp" 2049 | version = "0.3.3" 2050 | source = "registry+https://github.com/rust-lang/crates.io-index" 2051 | checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d" 2052 | dependencies = [ 2053 | "bytes", 2054 | "futures-channel", 2055 | "futures-util", 2056 | "headers", 2057 | "http", 2058 | "hyper", 2059 | "log", 2060 | "mime", 2061 | "mime_guess", 2062 | "percent-encoding", 2063 | "pin-project", 2064 | "rustls-pemfile", 2065 | "scoped-tls", 2066 | "serde", 2067 | "serde_json", 2068 | "serde_urlencoded", 2069 | "tokio", 2070 | "tokio-stream", 2071 | "tokio-util", 2072 | "tower-service", 2073 | "tracing", 2074 | ] 2075 | 2076 | [[package]] 2077 | name = "wasi" 2078 | version = "0.11.0+wasi-snapshot-preview1" 2079 | source = "registry+https://github.com/rust-lang/crates.io-index" 2080 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2081 | 2082 | [[package]] 2083 | name = "winapi" 2084 | version = "0.3.9" 2085 | source = "registry+https://github.com/rust-lang/crates.io-index" 2086 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2087 | dependencies = [ 2088 | "winapi-i686-pc-windows-gnu", 2089 | "winapi-x86_64-pc-windows-gnu", 2090 | ] 2091 | 2092 | [[package]] 2093 | name = "winapi-i686-pc-windows-gnu" 2094 | version = "0.4.0" 2095 | source = "registry+https://github.com/rust-lang/crates.io-index" 2096 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2097 | 2098 | [[package]] 2099 | name = "winapi-x86_64-pc-windows-gnu" 2100 | version = "0.4.0" 2101 | source = "registry+https://github.com/rust-lang/crates.io-index" 2102 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2103 | 2104 | [[package]] 2105 | name = "windows" 2106 | version = "0.39.0" 2107 | source = "registry+https://github.com/rust-lang/crates.io-index" 2108 | checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" 2109 | dependencies = [ 2110 | "windows_aarch64_msvc 0.39.0", 2111 | "windows_i686_gnu 0.39.0", 2112 | "windows_i686_msvc 0.39.0", 2113 | "windows_x86_64_gnu 0.39.0", 2114 | "windows_x86_64_msvc 0.39.0", 2115 | ] 2116 | 2117 | [[package]] 2118 | name = "windows-sys" 2119 | version = "0.42.0" 2120 | source = "registry+https://github.com/rust-lang/crates.io-index" 2121 | checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" 2122 | dependencies = [ 2123 | "windows_aarch64_gnullvm", 2124 | "windows_aarch64_msvc 0.42.0", 2125 | "windows_i686_gnu 0.42.0", 2126 | "windows_i686_msvc 0.42.0", 2127 | "windows_x86_64_gnu 0.42.0", 2128 | "windows_x86_64_gnullvm", 2129 | "windows_x86_64_msvc 0.42.0", 2130 | ] 2131 | 2132 | [[package]] 2133 | name = "windows_aarch64_gnullvm" 2134 | version = "0.42.0" 2135 | source = "registry+https://github.com/rust-lang/crates.io-index" 2136 | checksum = "41d2aa71f6f0cbe00ae5167d90ef3cfe66527d6f613ca78ac8024c3ccab9a19e" 2137 | 2138 | [[package]] 2139 | name = "windows_aarch64_msvc" 2140 | version = "0.39.0" 2141 | source = "registry+https://github.com/rust-lang/crates.io-index" 2142 | checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" 2143 | 2144 | [[package]] 2145 | name = "windows_aarch64_msvc" 2146 | version = "0.42.0" 2147 | source = "registry+https://github.com/rust-lang/crates.io-index" 2148 | checksum = "dd0f252f5a35cac83d6311b2e795981f5ee6e67eb1f9a7f64eb4500fbc4dcdb4" 2149 | 2150 | [[package]] 2151 | name = "windows_i686_gnu" 2152 | version = "0.39.0" 2153 | source = "registry+https://github.com/rust-lang/crates.io-index" 2154 | checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" 2155 | 2156 | [[package]] 2157 | name = "windows_i686_gnu" 2158 | version = "0.42.0" 2159 | source = "registry+https://github.com/rust-lang/crates.io-index" 2160 | checksum = "fbeae19f6716841636c28d695375df17562ca208b2b7d0dc47635a50ae6c5de7" 2161 | 2162 | [[package]] 2163 | name = "windows_i686_msvc" 2164 | version = "0.39.0" 2165 | source = "registry+https://github.com/rust-lang/crates.io-index" 2166 | checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" 2167 | 2168 | [[package]] 2169 | name = "windows_i686_msvc" 2170 | version = "0.42.0" 2171 | source = "registry+https://github.com/rust-lang/crates.io-index" 2172 | checksum = "84c12f65daa39dd2babe6e442988fc329d6243fdce47d7d2d155b8d874862246" 2173 | 2174 | [[package]] 2175 | name = "windows_x86_64_gnu" 2176 | version = "0.39.0" 2177 | source = "registry+https://github.com/rust-lang/crates.io-index" 2178 | checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" 2179 | 2180 | [[package]] 2181 | name = "windows_x86_64_gnu" 2182 | version = "0.42.0" 2183 | source = "registry+https://github.com/rust-lang/crates.io-index" 2184 | checksum = "bf7b1b21b5362cbc318f686150e5bcea75ecedc74dd157d874d754a2ca44b0ed" 2185 | 2186 | [[package]] 2187 | name = "windows_x86_64_gnullvm" 2188 | version = "0.42.0" 2189 | source = "registry+https://github.com/rust-lang/crates.io-index" 2190 | checksum = "09d525d2ba30eeb3297665bd434a54297e4170c7f1a44cad4ef58095b4cd2028" 2191 | 2192 | [[package]] 2193 | name = "windows_x86_64_msvc" 2194 | version = "0.39.0" 2195 | source = "registry+https://github.com/rust-lang/crates.io-index" 2196 | checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" 2197 | 2198 | [[package]] 2199 | name = "windows_x86_64_msvc" 2200 | version = "0.42.0" 2201 | source = "registry+https://github.com/rust-lang/crates.io-index" 2202 | checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5" 2203 | 2204 | [[package]] 2205 | name = "yansi" 2206 | version = "0.5.1" 2207 | source = "registry+https://github.com/rust-lang/crates.io-index" 2208 | checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" 2209 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "lambda-web" 3 | version = "0.2.1" 4 | authors = ["Hanabusa Masahiro"] 5 | description = "Run Rust web frameworks on AWS Lambda" 6 | edition = "2018" 7 | readme = "README.md" 8 | repository = "https://github.com/hanabu/lambda-web" 9 | license = "MIT" 10 | keywords = ["lambda", "actix-web", "axum", "rocket", "warp"] 11 | categories = ["web-programming::http-server"] 12 | exclude = ["/docker/"] 13 | 14 | [package.metadata.docs.rs] 15 | all-features = true 16 | 17 | [features] 18 | default = ["br"] 19 | 20 | # Enable one of your favorite web-framework 21 | actix4 = ["actix-web", "actix-http", "actix-service"] 22 | rocket05 = ["rocket"] 23 | 24 | # Compress output with Brotli 25 | br = ["brotli"] 26 | 27 | [dependencies] 28 | lambda_runtime = "0.7" 29 | serde = { version = "1", features = ["derive"] } 30 | serde_json = "1" 31 | base64 = "0.13" 32 | percent-encoding = "2" 33 | 34 | hyper = { version = "0.14", default-features = false, features = [], optional = true } 35 | actix-web = { version = "4", default-features = false, features = ["cookies", "macros"], optional = true } 36 | actix-http = { version = "3", default-features = false, optional = true } 37 | actix-service = { version = "2", optional = true } 38 | rocket = { version = "0.5.0-rc.2", default-features = false, optional = true } 39 | brotli = { version = "3", features = ["std"], optional = true } 40 | 41 | [dev-dependencies] 42 | tokio = { version = "1", features = ["macros"] } 43 | axum = { version = "0.6", default-features = false, features = ["tokio"] } 44 | warp = { version = "0.3.3", default-features = false } 45 | tower = "0.4" 46 | 47 | [[example]] 48 | name = "actix" 49 | required-features = ["actix4"] 50 | 51 | [[example]] 52 | name = "axum" 53 | required-features = ["hyper"] 54 | 55 | [[example]] 56 | name = "rocket" 57 | required-features = ["rocket05"] 58 | 59 | [[example]] 60 | name = "warp" 61 | required-features = ["hyper"] 62 | 63 | [profile.release] 64 | # Size optimization 65 | opt-level = "s" 66 | lto = "fat" 67 | codegen-units = 1 68 | -------------------------------------------------------------------------------- /Deploy.md: -------------------------------------------------------------------------------- 1 | # Build and deploy Rust binary to Lambda 2 | 3 | ## TL;DR 4 | 5 | - I reccomend ZIP deploy to `provided.al2` than container deploy, because of faster cold start time. 6 | - To avoid shared library version mismatch, run `cargo build` on Amazon Linux 2 environment. 7 | - Using CodeBuild may be the simplest way. You can also build Arm64 binary if you prefer it. 8 | - This repository contains [sample buildspec.yml](./buildspec.yml) for this purpose. 9 | 10 | ## Deploy Rust binary to AWS Lambda 11 | 12 | At the time of writing (Nov, 2021), Amazon AWS supports two ways to deploy Rust binary to AWS Lambda. 13 | 14 | - ZIP deploy to custom runtime `provided.al2` 15 | - Container image deploy 16 | 17 | Both work well, but cold start time is differ. 18 | In my measurement, cold start time of ZIP deploy is around 250ms, while container deploy is around 600ms. 19 | (Measurement is end-to-end, it's time includes network latency, TLS handshake, API gateway processing time, etc.) 20 | 21 | ## How to build binary for `provided.al2` runtime 22 | 23 | If you have a Linux system, you can run `cargo build` to get binary. But sometimes you get error on runtime like 24 | 25 | ```txt 26 | /lib64/libc.so.6: version `GLIBC_2.18` not found 27 | ``` 28 | 29 | This error is occured becasuse your system has newer version of libc than Amazon Linux 2 runtime. 30 | 31 | So, I recommend to run `cargo build` on Amazon Linux 2 environment. 32 | There are a few way to do it, I think [AWS CodeBuild](https://aws.amazon.com/codebuild/) is the simplest way. 33 | Since CodeBuild provides Amazon Linux 2 as the standard build environment, you only need to place `buildspec.yml` file in the repository. 34 | 35 | ## Using AWS CodeBuild 36 | 37 | - Write `buildspec.yml` 38 | - Prepare S3 bucket where built binary will be placed. 39 | - Create CodeBuild project 40 | - Deploy to Lambda 41 | 42 | First, place following `buildspec.yml` in your repository root directory. Make sure, `YOUR_PROJECT_NAME` is for your project. 43 | 44 | ```yml 45 | version: 0.2 46 | 47 | env: 48 | variables: 49 | PATH: "/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/codebuild/user/bin" 50 | phases: 51 | install: 52 | commands: 53 | # Install rust toolchanin 54 | - curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal 55 | build: 56 | commands: 57 | # build Rust binary 58 | - cargo build --release 59 | # binary name must be "bootstrap" for Lambda custom runtime 60 | - mv target/release/YOUR_PROJECT_NAME bootstrap 61 | # strip & check size, dependencies 62 | - strip --strip-all bootstrap 63 | - size bootstrap 64 | - ldd bootstrap 65 | artifacts: 66 | files: 67 | - bootstrap 68 | # - add other resources such as CSS, Javascript assets, etc. 69 | #discard-paths: yes 70 | ``` 71 | 72 | Second, prepare S3 bucket in same region with Lambda and CodeBuild. If you have already some bucket, you can use it. Or make new one. 73 | 74 | Third, create new CodeBuild project. 75 | 76 | - `Source` section: \ 77 | Select source code repository of your project. 78 | CodeBuild can fetch CodeCommit, GitHub, Bitbucket repositories directly, or you should pack all source codes into ZIP and upload it to S3. 79 | - `Environment` section: 80 | - Select `Managed Image` 81 | - Operating system - `Amazon Linux 2` 82 | - Runtime - `Standard` 83 | - Image - `aws/codebuild/amazonlinux2-aarch64-standard:2.0` or `aws/codebuild/amazonlinux2-x86_64-standard:3.0` (as of writing; Newer image will come) 84 | - Environment type - `Linux` 85 | - Privileged - `disable` 86 | - Service role - If you unsure, select `New service role` and name it. If you use existing role, the role needs S3:PutObject permision to the bucket mentioned above. 87 | - `Buildspec` section: \ 88 | `Use a build file` and keep `Buildspec name` as blank, if you place buildspec.yml on your repository root. 89 | In other case, specify where your buildspec.yml is. 90 | - `Batch configuration` section: \ 91 | Disable `Define batch configuration` 92 | - `Artifacts` section: 93 | - Set type as `Amazon S3` and specify `Bucket name` as the S3 bucket mentioned above. 94 | - `Name` is ZIP file name like lambda_deploy.zip 95 | - `Path` is directory of ZIP file. ZIP file will be placed at `s3:////` 96 | - `Namespace` - None 97 | - `Artifacts packaging` - Select `ZIP` 98 | 99 | Congratulations! You can now `Start build` and get ZIPed Rust binary in S3 bucket. 100 | 101 | Finally you can deploy binary to Lambda. Since ZIP is already on S3 bucket, `Upload from` - `Amazon S3 location` in the Lambda console. 102 | 103 | ## Another way to build - run Amazon Linux 2 Docker container on your machine 104 | 105 | If you are familier with Docker, you can build Rust binary with Amazon Linux 2 container image. 106 | 107 | First, build Amazon Linux 2 container image including Rust compiler toolchain with following Dockerfile. 108 | 109 | ```Dockerfile 110 | FROM amazonlinux:2 111 | 112 | # Setup build environment 113 | RUN mkdir -p /build/src && \ 114 | yum update -y && \ 115 | # Add required packages 116 | yum install -y awscli gcc openssl-devel tree zip && \ 117 | # Install rust with rustup 118 | curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal 119 | 120 | # Build environment setting 121 | WORKDIR /build 122 | ENV PATH=/root/.cargo/bin:/usr/sbin:/usr/bin:/sbin:/bin 123 | # Default build command 124 | CMD \ 125 | cargo build --release --target-dir target_al2 && \ 126 | mv target_al2/release/YOUR_PROJECT_NAME bootstrap && \ 127 | strip --strip-all bootstrap && \ 128 | size bootstrap && \ 129 | ldd bootstrap && \ 130 | zip -9 -j deploy.zip bootstrap 131 | ``` 132 | 133 | Then, run container with mounting source code directory. 134 | 135 | ```console 136 | $ cd /your/cargo-project/top 137 | $ ls 138 | Cargo.lock Cargo.toml src 139 | $ docker run -it --rm \ 140 | -v ~/.cargo/registry:/root/.cargo/registry:z \ 141 | -v .:/build:z \ 142 | BUILD_CONTAINER_NAME 143 | ... 144 | $ ls 145 | Cargo.lock Cargo.toml bootstrap deploy.zip src target_al2 146 | ``` 147 | 148 | ## Arm64 build 149 | 150 | [AWS released Graviton2 processor (Arm64) for Lambda](https://aws.amazon.com/blogs/aws/aws-lambda-functions-powered-by-aws-graviton2-processor-run-your-functions-on-arm-and-get-up-to-34-better-price-performance/) 151 | on Sep, 2021. 152 | Graviton2 Lambda function is cost effective option, you may interested in it. 153 | 154 | If you use CodeBuild, all pieces for Arm64 support are available! 155 | 156 | - CodeBuild already [supports Graviton2](https://aws.amazon.com/about-aws/whats-new/2021/02/aws-codebuild-supports-arm-based-workloads-using-aws-graviton2/) 157 | - Rust compiler supports [aarch64-unknown-linux-gnu as tier 1](https://doc.rust-lang.org/rustc/platform-support.html#tier-1-with-host-tools) 158 | - Lambda function [powered by Graviton2 prcessor](https://aws.amazon.com/about-aws/whats-new/2021/09/better-price-performance-aws-lambda-functions-aws-graviton2-processor/) 159 | 160 | You only switch CodeBuild image as 161 | `aws/codebuild/amazonlinux2-aarch64-standard` and Lambda function architecture as `arm64`. 162 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2021 Hanabusa Masahiro 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 8 | the Software, and to permit persons to whom the Software is furnished to do so, 9 | subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 16 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 17 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 18 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lambda-web 2 | 3 | Run Rust web server frameworks on AWS Lambda. 4 | Currently, it supports Actix web, axum, Rocket, warp. 5 | 6 | [![crates.io](https://img.shields.io/crates/v/lambda-web?label=latest)](https://crates.io/crates/lambda-web) 7 | [![API docs](https://docs.rs/lambda-web/badge.svg)](https://docs.rs/lambda-web) 8 | 9 | ## Features 10 | 11 | ### Supported web frameworks 12 | 13 | - [Actix Web](https://crates.io/crates/actix-web) 4.0 14 | - [axum](https://crates.io/crates/axum) 0.6 15 | - [Rocket](https://crates.io/crates/rocket/0.5.0-rc.2) 0.5.0-rc.2 16 | - [warp](https://crates.io/crates/warp) 0.3 17 | 18 | ### Supported AWS infrastructure 19 | 20 | - [API Gateway HTTP API](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api.html) with [payload format version 2.0](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html#2.0) 21 | - [API Gateway REST API](https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-rest-api.html) 22 | - [Lambda function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html) 23 | 24 | ### Not supported 25 | 26 | - API Gateway HTTP API with payload format version **1.0** 27 | - [Application Load Balancer (ALB)](https://docs.aws.amazon.com/elasticloadbalancing/latest/application/lambda-functions.html) 28 | 29 | ## Example 30 | 31 | ### Actix Web 32 | 33 | Cargo.toml 34 | 35 | ```toml 36 | [[bin]] 37 | name = "bootstrap" 38 | path = "src/main.rs" 39 | 40 | [dependencies] 41 | lambda-web = { version = "0.2.0", features=["actix4"] } 42 | ``` 43 | 44 | main.rs 45 | 46 | ```rust 47 | use lambda_web::actix_web::{self, get, App, HttpServer, Responder}; 48 | use lambda_web::{is_running_on_lambda, run_actix_on_lambda, LambdaError}; 49 | 50 | #[get("/")] 51 | async fn hello() -> impl Responder { 52 | format!("Hello") 53 | } 54 | 55 | #[actix_web::main] 56 | async fn main() -> Result<(),LambdaError> { 57 | let factory = move || { 58 | App::new().service(hello) 59 | }; 60 | 61 | if is_running_on_lambda() { 62 | // Run on AWS Lambda 63 | run_actix_on_lambda(factory).await?; 64 | } else { 65 | // Local server 66 | HttpServer::new(factory) 67 | .bind("127.0.0.2.0080")? 68 | .run() 69 | .await?; 70 | } 71 | Ok(()) 72 | } 73 | ``` 74 | 75 | ### axum 76 | 77 | Cargo.toml 78 | 79 | ```toml 80 | [[bin]] 81 | name = "bootstrap" 82 | path = "src/main.rs" 83 | 84 | [dependencies] 85 | lambda-web = { version = "0.2.0", features=["hyper"] } 86 | axum = "0.6" 87 | tokio = { version = "1" } 88 | ``` 89 | 90 | main.rs 91 | 92 | ```rust 93 | use axum::{routing::get, Router}; 94 | use lambda_web::{is_running_on_lambda, run_hyper_on_lambda, LambdaError}; 95 | use std::net::SocketAddr; 96 | 97 | // basic handler that responds with a static string 98 | async fn root() -> &'static str { 99 | "Hello, World!" 100 | } 101 | 102 | #[tokio::main] 103 | async fn main() -> Result<(), LambdaError> { 104 | // build our application with a route 105 | let app = Router::new().route("/", get(root)); 106 | 107 | if is_running_on_lambda() { 108 | // Run app on AWS Lambda 109 | run_hyper_on_lambda(app).await?; 110 | } else { 111 | // Run app on local server 112 | let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); 113 | axum::Server::bind(&addr).serve(app.into_make_service()).await?; 114 | } 115 | Ok(()) 116 | } 117 | ``` 118 | 119 | ### Rocket 120 | 121 | Cargo.toml 122 | 123 | ```toml 124 | [[bin]] 125 | name = "bootstrap" 126 | path = "src/main.rs" 127 | 128 | [dependencies] 129 | lambda-web = { version = "0.2.0", features=["rocket05"] } 130 | rocket = "0.5.0-rc.2" 131 | ``` 132 | 133 | main.rs 134 | 135 | ```rust 136 | use rocket::{self, get, routes}; 137 | use lambda_web::{is_running_on_lambda, launch_rocket_on_lambda, LambdaError}; 138 | 139 | #[get("/hello//")] 140 | fn hello(name: &str, age: u8) -> String { 141 | format!("Hello, {} year old named {}!", age, name) 142 | } 143 | 144 | #[rocket::main] 145 | async fn main() -> Result<(), LambdaError> { 146 | let rocket = rocket::build().mount("/", routes![hello]); 147 | if is_running_on_lambda() { 148 | // Launch on AWS Lambda 149 | launch_rocket_on_lambda(rocket).await?; 150 | } else { 151 | // Launch local server 152 | let _ = rocket.launch().await?; 153 | } 154 | Ok(()) 155 | } 156 | ``` 157 | 158 | ### warp 159 | 160 | Cargo.toml 161 | 162 | ```toml 163 | [[bin]] 164 | name = "bootstrap" 165 | path = "src/main.rs" 166 | 167 | [dependencies] 168 | lambda-web = { version = "0.2.0", features=["hyper"] } 169 | warp = "0.3" 170 | tokio = { version = "1" } 171 | ``` 172 | 173 | main.rs 174 | 175 | ```rust 176 | use lambda_web::{is_running_on_lambda, run_hyper_on_lambda, LambdaError}; 177 | use warp::Filter; 178 | 179 | #[tokio::main] 180 | async fn main() -> Result<(), LambdaError> { 181 | // GET /hello/warp => 200 OK with body "Hello, warp!" 182 | let hello = warp::path!("hello" / String).map(|name| format!("Hello, {}", name)); 183 | 184 | if is_running_on_lambda() { 185 | // Run on AWS Lambda 186 | run_hyper_on_lambda(warp::service(hello)).await?; 187 | } else { 188 | // Run local server 189 | warp::serve(hello).run(([127, 0, 0, 1], 8080)).await; 190 | } 191 | Ok(()) 192 | } 193 | ``` 194 | 195 | ## Create deploy ZIP file 196 | 197 | As of writing (Nov, 2021), we have two options to run Rust on AWS Lambda: [Amazon Linux 2 custom runtime](https://docs.aws.amazon.com/lambda/latest/dg/runtimes-custom.html) or Docker container image. 198 | 199 | I recommend ZIP deploy to Amazon Linux 2 custom runtime (`provided.al2`) because it's faster cold start time than container image. 200 | 201 | To build Amazon Linux 2 compatible binary, see [Deploy.md](./Deploy.md) for more details. 202 | 203 | ## Setup AWS Lambda & function URLs 204 | 205 | - Create lambda function with `provided.al2` custom runtime. Choose "Provide your own bootstrap on Amazon Linux 2" . 206 | - Upload ZIP file described above. 207 | - IAM role, memory settings, etc. are as your demands. \ 208 | As sample code above consumes only 30MB of memory, many simple Rust app can fit in 128MB setting. 209 | - Create function URL, then you can call your Lambda function with `https://.lambda-url..on.aws` 210 | - You can use CloudFront for custom domain. 211 | 212 | ## Setup AWS Lambda & API gateway 213 | 214 | ### Lambda 215 | 216 | - Create lambda function with `provided.al2` custom runtime. Choose "Provide your own bootstrap on Amazon Linux 2" . 217 | - Upload ZIP file described above. 218 | - IAM role, memory settings, etc. are as your demands. \ 219 | As sample code above consumes only 30MB of memory, many simple Rust app can fit in 128MB setting. 220 | 221 | ### API Gateway (HTTP) 222 | 223 | - Create HTTP API 224 | - Create single route "$default" and attach Lambda integration. Make sure, payload format version is "2.0" 225 | 226 | ### API Gateway (REST) 227 | 228 | - Create REST API 229 | - Create two resources: 230 | - ANY method on route `/` and attach Lambda proxy integration. 231 | - ANY method on route `/{proxy+}` and attach Lambda proxy integration. 232 | - In settings tab, add `*/*` binary media type. 233 | - Then, deploy API to stage. 234 | -------------------------------------------------------------------------------- /buildspec.yml: -------------------------------------------------------------------------------- 1 | version: 0.2 2 | 3 | env: 4 | variables: 5 | PATH: "/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/codebuild/user/bin" 6 | # key: "value" 7 | #parameter-store: 8 | # key: "value" 9 | # key: "value" 10 | #secrets-manager: 11 | # key: secret-id:json-key:version-stage:version-id 12 | # key: secret-id:json-key:version-stage:version-id 13 | #exported-variables: 14 | # - variable 15 | # - variable 16 | #git-credential-helper: yes 17 | #batch: 18 | #fast-fail: true 19 | #build-list: 20 | #build-matrix: 21 | #build-graph: 22 | phases: 23 | install: 24 | commands: 25 | # Install rust toolchanin 26 | - curl -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal 27 | pre_build: 28 | commands: 29 | # Uncomment if you require custom build setting 30 | #- mkdir -p .cargo 31 | #- | 32 | # cat > .cargo/config.toml < impl Responder { 6 | format!("Hello") 7 | } 8 | 9 | #[actix_web::main] 10 | async fn main() -> Result<(), LambdaError> { 11 | let factory = move || App::new().service(hello); 12 | if is_running_on_lambda() { 13 | // Run on AWS Lambda 14 | run_actix_on_lambda(factory).await?; 15 | } else { 16 | // Run local server 17 | HttpServer::new(factory) 18 | .bind("127.0.0.1:8080")? 19 | .run() 20 | .await?; 21 | } 22 | Ok(()) 23 | } 24 | -------------------------------------------------------------------------------- /examples/axum.rs: -------------------------------------------------------------------------------- 1 | use axum::{routing::get, Router}; 2 | use lambda_web::{is_running_on_lambda, run_hyper_on_lambda, LambdaError}; 3 | use std::net::SocketAddr; 4 | 5 | // basic handler that responds with a static string 6 | async fn root() -> &'static str { 7 | "Hello, World!" 8 | } 9 | 10 | #[tokio::main] 11 | async fn main() -> Result<(), LambdaError> { 12 | // build our application with a route 13 | let app = Router::new() 14 | // `GET /` goes to `root` 15 | .route("/", get(root)); 16 | 17 | if is_running_on_lambda() { 18 | // Run app on AWS Lambda 19 | run_hyper_on_lambda(app).await?; 20 | } else { 21 | // Run app on local server 22 | let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); 23 | axum::Server::bind(&addr) 24 | .serve(app.into_make_service()) 25 | .await?; 26 | } 27 | Ok(()) 28 | } 29 | -------------------------------------------------------------------------------- /examples/rocket.rs: -------------------------------------------------------------------------------- 1 | use lambda_web::{is_running_on_lambda, launch_rocket_on_lambda, LambdaError}; 2 | use rocket::{self, get, routes}; 3 | 4 | #[get("/hello//")] 5 | fn hello(name: &str, age: u8) -> String { 6 | format!("Hello, {} year old named {}!", age, name) 7 | } 8 | 9 | #[rocket::main] 10 | async fn main() -> Result<(), LambdaError> { 11 | let rocket = rocket::build().mount("/", routes![hello]); 12 | if is_running_on_lambda() { 13 | // Launch on AWS Lambda 14 | launch_rocket_on_lambda(rocket).await?; 15 | } else { 16 | // Launch local server 17 | let _ = rocket.launch().await?; 18 | } 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /examples/warp.rs: -------------------------------------------------------------------------------- 1 | use lambda_web::{is_running_on_lambda, run_hyper_on_lambda, LambdaError}; 2 | use warp::Filter; 3 | 4 | #[tokio::main] 5 | async fn main() -> Result<(), LambdaError> { 6 | // GET /hello/warp => 200 OK with body "Hello, warp!" 7 | let hello = warp::path!("hello" / String).map(|name| format!("Hello, {}!", name)); 8 | 9 | if is_running_on_lambda() { 10 | // Run on AWS Lambda 11 | run_hyper_on_lambda(warp::service(hello)).await?; 12 | } else { 13 | // Run local server 14 | warp::serve(hello).run(([127, 0, 0, 1], 8080)).await; 15 | } 16 | Ok(()) 17 | } 18 | -------------------------------------------------------------------------------- /src/actix4.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | //! Run Actix Web on AWS Lambda 3 | //! 4 | //! 5 | use crate::request::LambdaHttpEvent; 6 | use core::convert::TryFrom; 7 | use core::future::Future; 8 | use lambda_runtime::{Error as LambdaError, LambdaEvent, Service as LambdaService}; 9 | use std::pin::Pin; 10 | 11 | /// Run Actix web application on AWS Lambda 12 | /// 13 | /// ```no_run 14 | /// use lambda_web::actix_web::{self, get, App, HttpServer, Responder}; 15 | /// use lambda_web::{is_running_on_lambda, run_actix_on_lambda, LambdaError}; 16 | /// 17 | /// #[get("/")] 18 | /// async fn hello() -> impl Responder { 19 | /// format!("Hello") 20 | /// } 21 | /// 22 | /// #[actix_web::main] 23 | /// async fn main() -> Result<(),LambdaError> { 24 | /// let factory = move || { 25 | /// App::new().service(hello) 26 | /// }; 27 | /// if is_running_on_lambda() { 28 | /// // Run on AWS Lambda 29 | /// run_actix_on_lambda(factory).await?; 30 | /// } else { 31 | /// // Run local server 32 | /// HttpServer::new(factory).bind("127.0.0.1:8080")?.run().await?; 33 | /// } 34 | /// Ok(()) 35 | /// } 36 | /// ``` 37 | /// 38 | pub async fn run_actix_on_lambda(factory: F) -> Result<(), LambdaError> 39 | where 40 | F: Fn() -> I + Send + Clone + 'static, 41 | I: actix_service::IntoServiceFactory, 42 | S: actix_service::ServiceFactory< 43 | actix_http::Request, 44 | Config = actix_web::dev::AppConfig, 45 | Response = actix_web::dev::ServiceResponse, 46 | Error = actix_web::Error, 47 | > + 'static, 48 | S::InitError: std::fmt::Debug, 49 | B: actix_web::body::MessageBody, 50 | B::Error: std::fmt::Display, 51 | ::Error: std::fmt::Debug, 52 | { 53 | // Prepare actix_service::Service 54 | let srv = factory().into_factory(); 55 | let new_svc = srv 56 | .new_service(actix_web::dev::AppConfig::default()) 57 | .await 58 | .unwrap(); 59 | 60 | lambda_runtime::run(ActixHandler(new_svc)).await?; 61 | 62 | Ok(()) 63 | } 64 | 65 | /// Lambda_runtime handler for Actix Web 66 | struct ActixHandler(S) 67 | where 68 | S: actix_service::Service< 69 | actix_http::Request, 70 | Response = actix_web::dev::ServiceResponse, 71 | Error = actix_web::Error, 72 | > + 'static, 73 | B: actix_web::body::MessageBody, 74 | B::Error: std::fmt::Display, 75 | ::Error: std::fmt::Debug; 76 | 77 | impl LambdaService>> for ActixHandler 78 | where 79 | S: actix_service::Service< 80 | actix_http::Request, 81 | Response = actix_web::dev::ServiceResponse, 82 | Error = actix_web::Error, 83 | > + 'static, 84 | B: actix_web::body::MessageBody, 85 | B::Error: std::fmt::Display, 86 | ::Error: std::fmt::Debug, 87 | { 88 | type Response = serde_json::Value; 89 | type Error = actix_web::Error; 90 | type Future = Pin>>>; 91 | 92 | /// Returns Poll::Ready when servie can process more requrests. 93 | fn poll_ready( 94 | &mut self, 95 | cx: &mut core::task::Context<'_>, 96 | ) -> core::task::Poll> { 97 | self.0.poll_ready(cx) 98 | } 99 | 100 | /// Lambda handler function 101 | /// Parse Lambda event as Actix-web request, 102 | /// serialize Actix-web response to Lambda JSON response 103 | fn call(&mut self, req: LambdaEvent>) -> Self::Future { 104 | use serde_json::json; 105 | 106 | let event = req.payload; 107 | let _context = req.context; 108 | 109 | // check if web client supports content-encoding: br 110 | let client_br = event.client_supports_brotli(); 111 | // multi-value-headers response format 112 | let multi_value = event.multi_value(); 113 | 114 | // Parse request 115 | let actix_request = actix_http::Request::try_from(event); 116 | 117 | // Call Actix service when request parsing succeeded 118 | let svc_call = actix_request.map(|req| self.0.call(req)); 119 | 120 | let fut = async move { 121 | match svc_call { 122 | Ok(svc_fut) => { 123 | // Request parsing succeeded 124 | if let Ok(response) = svc_fut.await { 125 | // Returns as API Gateway response 126 | api_gateway_response_from_actix_web(response, client_br, multi_value) 127 | .await 128 | .or_else(|_err| { 129 | Ok(json!({ 130 | "isBase64Encoded": false, 131 | "statusCode": 500u16, 132 | "headers": { "content-type": "text/plain"}, 133 | "body": "Internal Server Error" 134 | })) 135 | }) 136 | } else { 137 | // Some Actix web error -> 500 Internal Server Error 138 | Ok(json!({ 139 | "isBase64Encoded": false, 140 | "statusCode": 500u16, 141 | "headers": { "content-type": "text/plain"}, 142 | "body": "Internal Server Error" 143 | })) 144 | } 145 | } 146 | Err(_request_err) => { 147 | // Request parsing error 148 | Ok(json!({ 149 | "isBase64Encoded": false, 150 | "statusCode": 400u16, 151 | "headers": { "content-type": "text/plain"}, 152 | "body": "Bad Request" 153 | })) 154 | } 155 | } 156 | }; 157 | Box::pin(fut) 158 | } 159 | } 160 | 161 | impl TryFrom> for actix_http::Request { 162 | type Error = LambdaError; 163 | 164 | /// Actix-web Request from API Gateway event 165 | fn try_from(event: LambdaHttpEvent) -> Result { 166 | use actix_web::http::Method; 167 | 168 | // Construct actix_web request 169 | let method = Method::try_from(event.method())?; 170 | let req = actix_web::test::TestRequest::with_uri(&event.path_query()).method(method); 171 | 172 | // Source IP 173 | let req = if let Some(source_ip) = event.source_ip() { 174 | req.peer_addr(std::net::SocketAddr::from((source_ip, 0u16))) 175 | } else { 176 | req 177 | }; 178 | 179 | // Headers 180 | let req = event 181 | .headers() 182 | .into_iter() 183 | .fold(req, |req, (k, v)| req.insert_header((k, &v as &str))); 184 | 185 | // Body 186 | let req = req.set_payload(event.body()?); 187 | 188 | Ok(req.to_request()) 189 | } 190 | } 191 | 192 | impl crate::brotli::ResponseCompression for actix_web::dev::ServiceResponse { 193 | /// Content-Encoding header value 194 | fn content_encoding<'a>(&'a self) -> Option<&'a str> { 195 | self.headers() 196 | .get(actix_web::http::header::CONTENT_ENCODING) 197 | .and_then(|val| val.to_str().ok()) 198 | } 199 | 200 | /// Content-Type header value 201 | fn content_type<'a>(&'a self) -> Option<&'a str> { 202 | self.headers() 203 | .get(actix_web::http::header::CONTENT_TYPE) 204 | .and_then(|val| val.to_str().ok()) 205 | } 206 | } 207 | 208 | /// API Gateway response from Actix-web response 209 | async fn api_gateway_response_from_actix_web( 210 | response: actix_web::dev::ServiceResponse, 211 | client_support_br: bool, 212 | multi_value: bool, 213 | ) -> Result { 214 | use crate::brotli::ResponseCompression; 215 | use actix_web::http::header::SET_COOKIE; 216 | use serde_json::json; 217 | 218 | // HTTP status 219 | let status_code = response.status().as_u16(); 220 | 221 | // Convert header to JSON map 222 | let mut cookies = Vec::::new(); 223 | let mut headers = serde_json::Map::new(); 224 | for (k, v) in response.headers() { 225 | if let Ok(value_str) = v.to_str() { 226 | if multi_value { 227 | // REST API format, returns multiValueHeaders 228 | if let Some(values) = headers.get_mut(k.as_str()) { 229 | if let Some(value_ary) = values.as_array_mut() { 230 | value_ary.push(json!(value_str)); 231 | } 232 | } else { 233 | headers.insert(k.as_str().to_string(), json!([value_str])); 234 | } 235 | } else { 236 | // HTTP API v2 format, returns headers 237 | if k == SET_COOKIE { 238 | cookies.push(value_str.to_string()); 239 | } else { 240 | headers.insert(k.as_str().to_string(), json!(value_str)); 241 | } 242 | } 243 | } 244 | } 245 | 246 | // check if response should be compressed 247 | let compress = client_support_br && response.can_brotli_compress(); 248 | let body_bytes = actix_web::body::to_bytes(response.into_body()).await?; 249 | let body_base64 = if compress { 250 | if multi_value { 251 | headers.insert("content-encoding".to_string(), json!(["br"])); 252 | } else { 253 | headers.insert("content-encoding".to_string(), json!("br")); 254 | } 255 | crate::brotli::compress_response_body(&body_bytes) 256 | } else { 257 | base64::encode(body_bytes) 258 | }; 259 | 260 | if multi_value { 261 | Ok(json!({ 262 | "isBase64Encoded": true, 263 | "statusCode": status_code, 264 | "multiValueHeaders": headers, 265 | "body": body_base64 266 | })) 267 | } else { 268 | Ok(json!({ 269 | "isBase64Encoded": true, 270 | "statusCode": status_code, 271 | "cookies": cookies, 272 | "headers": headers, 273 | "body": body_base64 274 | })) 275 | } 276 | } 277 | 278 | #[cfg(test)] 279 | mod tests { 280 | use super::*; 281 | use crate::{request::LambdaHttpEvent, test_consts::*}; 282 | 283 | // Request JSON to actix_http::Request 284 | fn prepare_request(event_str: &str) -> actix_http::Request { 285 | let reqjson: LambdaHttpEvent = serde_json::from_str(event_str).unwrap(); 286 | actix_http::Request::try_from(reqjson).unwrap() 287 | } 288 | 289 | #[test] 290 | fn test_path_decode() { 291 | let req = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY); 292 | assert_eq!(req.uri().path(), "/"); 293 | let req = prepare_request(API_GATEWAY_REST_GET_ROOT_NOQUERY); 294 | assert_eq!(req.uri().path(), "/stage/"); 295 | 296 | let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_NOQUERY); 297 | assert_eq!(req.uri().path(), "/somewhere"); 298 | let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_NOQUERY); 299 | assert_eq!(req.uri().path(), "/stage/somewhere"); 300 | 301 | let req = prepare_request(API_GATEWAY_V2_GET_SPACEPATH_NOQUERY); 302 | assert_eq!(req.uri().path(), "/path%20with/space"); 303 | let req = prepare_request(API_GATEWAY_REST_GET_SPACEPATH_NOQUERY); 304 | assert_eq!(req.uri().path(), "/stage/path%20with/space"); 305 | 306 | let req = prepare_request(API_GATEWAY_V2_GET_PERCENTPATH_NOQUERY); 307 | assert_eq!(req.uri().path(), "/path%25with/percent"); 308 | let req = prepare_request(API_GATEWAY_REST_GET_PERCENTPATH_NOQUERY); 309 | assert_eq!(req.uri().path(), "/stage/path%25with/percent"); 310 | 311 | let req = prepare_request(API_GATEWAY_V2_GET_UTF8PATH_NOQUERY); 312 | assert_eq!( 313 | req.uri().path(), 314 | "/%E6%97%A5%E6%9C%AC%E8%AA%9E/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D" 315 | ); 316 | let req = prepare_request(API_GATEWAY_REST_GET_UTF8PATH_NOQUERY); 317 | assert_eq!( 318 | req.uri().path(), 319 | "/stage/%E6%97%A5%E6%9C%AC%E8%AA%9E/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D" 320 | ); 321 | } 322 | 323 | #[test] 324 | fn test_query_decode() { 325 | let req = prepare_request(API_GATEWAY_V2_GET_ROOT_ONEQUERY); 326 | assert_eq!(req.uri().query(), Some("key=value")); 327 | let req = prepare_request(API_GATEWAY_REST_GET_ROOT_ONEQUERY); 328 | assert_eq!(req.uri().query(), Some("key=value")); 329 | 330 | let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_ONEQUERY); 331 | assert_eq!(req.uri().query(), Some("key=value")); 332 | let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_ONEQUERY); 333 | assert_eq!(req.uri().query(), Some("key=value")); 334 | 335 | let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_TWOQUERY); 336 | assert_eq!(req.uri().query(), Some("key1=value1&key2=value2")); 337 | let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_TWOQUERY); 338 | assert!( 339 | req.uri().query() == Some("key1=value1&key2=value2") 340 | || req.uri().query() == Some("key2=value2&key1=value1") 341 | ); 342 | 343 | let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_SPACEQUERY); 344 | assert_eq!(req.uri().query(), Some("key=value1+value2")); 345 | let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_SPACEQUERY); 346 | assert_eq!(req.uri().query(), Some("key=value1%20value2")); 347 | 348 | let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_UTF8QUERY); 349 | assert_eq!(req.uri().query(), Some("key=%E6%97%A5%E6%9C%AC%E8%AA%9E")); 350 | let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_UTF8QUERY); 351 | assert_eq!(req.uri().query(), Some("key=%E6%97%A5%E6%9C%AC%E8%AA%9E")); 352 | } 353 | 354 | #[test] 355 | fn test_remote_ip_decode() { 356 | use std::net::IpAddr; 357 | use std::str::FromStr; 358 | 359 | let req = prepare_request(API_GATEWAY_V2_GET_ROOT_ONEQUERY); 360 | assert_eq!( 361 | req.peer_addr().unwrap().ip(), 362 | IpAddr::from_str("1.2.3.4").unwrap() 363 | ); 364 | let req = prepare_request(API_GATEWAY_REST_GET_ROOT_ONEQUERY); 365 | assert_eq!( 366 | req.peer_addr().unwrap().ip(), 367 | IpAddr::from_str("1.2.3.4").unwrap() 368 | ); 369 | 370 | let req = prepare_request(API_GATEWAY_V2_GET_REMOTE_IPV6); 371 | assert_eq!( 372 | req.peer_addr().unwrap().ip(), 373 | IpAddr::from_str("2404:6800:400a:80c::2004").unwrap() 374 | ); 375 | let req = prepare_request(API_GATEWAY_REST_GET_REMOTE_IPV6); 376 | assert_eq!( 377 | req.peer_addr().unwrap().ip(), 378 | IpAddr::from_str("2404:6800:400a:80c::2004").unwrap() 379 | ); 380 | } 381 | 382 | #[tokio::test] 383 | async fn test_form_post() { 384 | use actix_web::http::Method; 385 | 386 | let req = prepare_request(API_GATEWAY_V2_POST_FORM_URLENCODED); 387 | assert_eq!(req.method(), Method::POST); 388 | let req = prepare_request(API_GATEWAY_REST_POST_FORM_URLENCODED); 389 | assert_eq!(req.method(), Method::POST); 390 | 391 | // Base64 encoded 392 | let req = prepare_request(API_GATEWAY_V2_POST_FORM_URLENCODED_B64); 393 | assert_eq!(req.method(), Method::POST); 394 | let req = prepare_request(API_GATEWAY_REST_POST_FORM_URLENCODED_B64); 395 | assert_eq!(req.method(), Method::POST); 396 | } 397 | 398 | #[test] 399 | fn test_parse_header() { 400 | let req = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY); 401 | assert_eq!(req.head().headers.get("x-forwarded-port").unwrap(), &"443"); 402 | assert_eq!( 403 | req.head().headers.get("x-forwarded-proto").unwrap(), 404 | &"https" 405 | ); 406 | let req = prepare_request(API_GATEWAY_REST_GET_ROOT_NOQUERY); 407 | assert_eq!(req.head().headers.get("x-forwarded-port").unwrap(), &"443"); 408 | assert_eq!( 409 | req.head().headers.get("x-forwarded-proto").unwrap(), 410 | &"https" 411 | ); 412 | } 413 | 414 | #[test] 415 | fn test_parse_cookies() { 416 | let req = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY); 417 | assert_eq!(req.head().headers.get("cookie"), None); 418 | 419 | let req = prepare_request(API_GATEWAY_V2_GET_ONE_COOKIE); 420 | assert_eq!(req.head().headers.get("cookie").unwrap(), &"cookie1=value1"); 421 | let req = prepare_request(API_GATEWAY_REST_GET_ONE_COOKIE); 422 | assert_eq!(req.head().headers.get("cookie").unwrap(), &"cookie1=value1"); 423 | 424 | let req = prepare_request(API_GATEWAY_V2_GET_TWO_COOKIES); 425 | assert!( 426 | req.head().headers.get("cookie").unwrap() == &"cookie2=value2; cookie1=value1" 427 | || req.head().headers.get("cookie").unwrap() == &"cookie1=value1; cookie2=value2" 428 | ); 429 | let req = prepare_request(API_GATEWAY_REST_GET_TWO_COOKIES); 430 | assert!( 431 | req.head().headers.get("cookie").unwrap() == &"cookie2=value2; cookie1=value1" 432 | || req.head().headers.get("cookie").unwrap() == &"cookie1=value1; cookie2=value2" 433 | ); 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /src/brotli.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | //! 3 | //! Brotli ransparent compression 4 | //! Supports 5 | //! Content-Encoding: br 6 | //! 7 | 8 | /// 9 | /// Trait to check if reponse should be compressed 10 | /// 11 | pub(crate) trait ResponseCompression { 12 | /// Content-Encoding header value 13 | fn content_encoding<'a>(&'a self) -> Option<&'a str>; 14 | 15 | /// Content-Type header value 16 | fn content_type<'a>(&'a self) -> Option<&'a str>; 17 | 18 | /// Can this response be compressed? 19 | #[cfg(feature = "br")] 20 | fn can_brotli_compress(&self) -> bool { 21 | // Check already compressed 22 | if self.content_encoding().is_some() { 23 | // Already compressed 24 | return false; 25 | } 26 | 27 | // Get Content-type header value 28 | if let Some(header_val) = self.content_type() { 29 | let ctype = header_val.trim().to_ascii_lowercase(); 30 | 31 | // Compress when text types 32 | ctype.starts_with("text/") 33 | || ctype.starts_with("application/json") 34 | || ctype.starts_with("application/xhtml") 35 | || ctype.starts_with("application/xml") 36 | || ctype.starts_with("application/wasm") 37 | || ctype.starts_with("image/svg") 38 | } else { 39 | // No content-type 40 | false 41 | } 42 | } 43 | 44 | // Without Brotli support, always returns false 45 | #[cfg(not(feature = "br"))] 46 | fn can_brotli_compress(&self) -> bool { 47 | false 48 | } 49 | } 50 | 51 | /// Compress response using Brotli, base64 encode it, and return encoded string. 52 | #[cfg(feature = "br")] 53 | pub(crate) fn compress_response_body<'a>(body: &[u8]) -> String { 54 | // Compress parameter 55 | let cfg = brotli::enc::BrotliEncoderParams { 56 | quality: 4, 57 | ..Default::default() 58 | }; 59 | 60 | // Do Brotli compression 61 | let mut body_reader = std::io::Cursor::new(body); 62 | let mut compressed_base64 = base64::write::EncoderStringWriter::new(base64::STANDARD); 63 | let _sz = brotli::BrotliCompress(&mut body_reader, &mut compressed_base64, &cfg); 64 | 65 | compressed_base64.into_inner() 66 | } 67 | 68 | // No Brotli compression, only base64 encoding 69 | #[cfg(not(feature = "br"))] 70 | pub(crate) fn compress_response_body<'a>(body: &[u8]) -> String { 71 | base64::encode(body) 72 | } 73 | -------------------------------------------------------------------------------- /src/hyper014.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | //! 3 | //! Run hyper based web framework on AWS Lambda 4 | //! 5 | use crate::request::LambdaHttpEvent; 6 | use core::convert::TryFrom; 7 | use core::future::Future; 8 | use lambda_runtime::{Error as LambdaError, LambdaEvent, Service as LambdaService}; 9 | use std::convert::Infallible; 10 | use std::pin::Pin; 11 | 12 | type HyperRequest = hyper::Request; 13 | type HyperResponse = hyper::Response; 14 | 15 | /// Run hyper based web framework on AWS Lambda 16 | /// 17 | /// axum 0.3 example: 18 | /// 19 | /// ```no_run 20 | /// use axum::{routing::get, Router}; 21 | /// use lambda_web::{is_running_on_lambda, run_hyper_on_lambda, LambdaError}; 22 | /// use std::net::SocketAddr; 23 | /// 24 | /// // basic handler that responds with a static string 25 | /// async fn root() -> &'static str { 26 | /// "Hello, World!" 27 | /// } 28 | /// 29 | /// #[tokio::main] 30 | /// async fn main() -> Result<(), LambdaError> { 31 | /// // build our application with a route 32 | /// let app = Router::new() 33 | /// // `GET /` goes to `root` 34 | /// .route("/", get(root)); 35 | /// 36 | /// if is_running_on_lambda() { 37 | /// // Run app on AWS Lambda 38 | /// run_hyper_on_lambda(app).await?; 39 | /// } else { 40 | /// // Run app on local server 41 | /// let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); 42 | /// axum::Server::bind(&addr).serve(app.into_make_service()).await?; 43 | /// } 44 | /// Ok(()) 45 | /// } 46 | /// ``` 47 | /// 48 | /// warp 0.3 example: 49 | /// 50 | /// ```no_run 51 | /// use warp::Filter; 52 | /// use lambda_web::{is_running_on_lambda, run_hyper_on_lambda, LambdaError}; 53 | /// 54 | /// #[tokio::main] 55 | /// async fn main() -> Result<(),LambdaError> { 56 | /// // GET /hello/warp => 200 OK with body "Hello, warp!" 57 | /// let hello = warp::path!("hello" / String) 58 | /// .map(|name| format!("Hello, {}!", name)); 59 | /// 60 | /// if is_running_on_lambda() { 61 | /// // Run app on AWS Lambda 62 | /// run_hyper_on_lambda(warp::service(hello)).await?; 63 | /// } else { 64 | /// // Run app on local server 65 | /// warp::serve(hello).run(([127, 0, 0, 1], 8080)).await; 66 | /// } 67 | /// Ok(()) 68 | /// } 69 | /// ``` 70 | pub async fn run_hyper_on_lambda(svc: S) -> Result<(), LambdaError> 71 | where 72 | S: hyper::service::Service, Error = Infallible> 73 | + 'static, 74 | B: hyper::body::HttpBody, 75 | ::Error: std::error::Error + Send + Sync + 'static, 76 | { 77 | lambda_runtime::run(HyperHandler(svc)).await?; 78 | Ok(()) 79 | } 80 | 81 | /// Lambda_runtime handler for hyper 82 | struct HyperHandler(S) 83 | where 84 | S: hyper::service::Service, Error = Infallible> 85 | + 'static, 86 | B: hyper::body::HttpBody, 87 | ::Error: std::error::Error + Send + Sync + 'static; 88 | 89 | impl LambdaService>> for HyperHandler 90 | where 91 | S: hyper::service::Service, Error = Infallible> 92 | + 'static, 93 | B: hyper::body::HttpBody, 94 | ::Error: std::error::Error + Send + Sync + 'static, 95 | { 96 | type Response = serde_json::Value; 97 | type Error = Infallible; 98 | type Future = Pin>>>; 99 | 100 | /// Returns Poll::Ready when servie can process more requrests. 101 | fn poll_ready( 102 | &mut self, 103 | cx: &mut core::task::Context<'_>, 104 | ) -> core::task::Poll> { 105 | self.0.poll_ready(cx) 106 | } 107 | 108 | /// Lambda handler function 109 | /// Parse Lambda event as hyper request, 110 | /// serialize hyper response to Lambda JSON response 111 | fn call(&mut self, req: LambdaEvent>) -> Self::Future { 112 | use serde_json::json; 113 | 114 | let event = req.payload; 115 | let _context = req.context; 116 | 117 | // check if web client supports content-encoding: br 118 | let client_br = event.client_supports_brotli(); 119 | // multi-value-headers response format 120 | let multi_value = event.multi_value(); 121 | 122 | // Parse request 123 | let hyper_request = HyperRequest::try_from(event); 124 | 125 | // Call hyper service when request parsing succeeded 126 | let svc_call = hyper_request.map(|req| self.0.call(req)); 127 | 128 | let fut = async move { 129 | match svc_call { 130 | Ok(svc_fut) => { 131 | // Request parsing succeeded 132 | if let Ok(response) = svc_fut.await { 133 | // Returns as API Gateway response 134 | api_gateway_response_from_hyper(response, client_br, multi_value) 135 | .await 136 | .or_else(|_err| { 137 | Ok(json!({ 138 | "isBase64Encoded": false, 139 | "statusCode": 500u16, 140 | "headers": { "content-type": "text/plain"}, 141 | "body": "Internal Server Error" 142 | })) 143 | }) 144 | } else { 145 | // Some hyper error -> 500 Internal Server Error 146 | Ok(json!({ 147 | "isBase64Encoded": false, 148 | "statusCode": 500u16, 149 | "headers": { "content-type": "text/plain"}, 150 | "body": "Internal Server Error" 151 | })) 152 | } 153 | } 154 | Err(_request_err) => { 155 | // Request parsing error 156 | Ok(json!({ 157 | "isBase64Encoded": false, 158 | "statusCode": 400u16, 159 | "headers": { "content-type": "text/plain"}, 160 | "body": "Bad Request" 161 | })) 162 | } 163 | } 164 | }; 165 | Box::pin(fut) 166 | } 167 | } 168 | 169 | impl TryFrom> for HyperRequest { 170 | type Error = LambdaError; 171 | 172 | /// hyper Request from API Gateway event 173 | fn try_from(event: LambdaHttpEvent) -> Result { 174 | use hyper::header::{HeaderName, HeaderValue}; 175 | use hyper::Method; 176 | use std::str::FromStr; 177 | 178 | // URI 179 | let uri = format!( 180 | "https://{}{}", 181 | event.hostname().unwrap_or("localhost"), 182 | event.path_query() 183 | ); 184 | 185 | // Method 186 | let method = Method::try_from(event.method())?; 187 | 188 | // Construct hyper request 189 | let mut reqbuilder = hyper::Request::builder().method(method).uri(&uri); 190 | 191 | // headers 192 | if let Some(headers_mut) = reqbuilder.headers_mut() { 193 | for (k, v) in event.headers() { 194 | if let (Ok(k), Ok(v)) = ( 195 | HeaderName::from_str(k as &str), 196 | HeaderValue::from_str(&v as &str), 197 | ) { 198 | headers_mut.insert(k, v); 199 | } 200 | } 201 | } 202 | 203 | // Body 204 | let req = reqbuilder.body(hyper::Body::from(event.body()?))?; 205 | 206 | Ok(req) 207 | } 208 | } 209 | 210 | impl crate::brotli::ResponseCompression for HyperResponse { 211 | /// Content-Encoding header value 212 | fn content_encoding<'a>(&'a self) -> Option<&'a str> { 213 | self.headers() 214 | .get(hyper::header::CONTENT_ENCODING) 215 | .and_then(|val| val.to_str().ok()) 216 | } 217 | 218 | /// Content-Type header value 219 | fn content_type<'a>(&'a self) -> Option<&'a str> { 220 | self.headers() 221 | .get(hyper::header::CONTENT_TYPE) 222 | .and_then(|val| val.to_str().ok()) 223 | } 224 | } 225 | 226 | /// API Gateway response from hyper response 227 | async fn api_gateway_response_from_hyper( 228 | response: HyperResponse, 229 | client_support_br: bool, 230 | multi_value: bool, 231 | ) -> Result 232 | where 233 | B: hyper::body::HttpBody, 234 | ::Error: std::error::Error + Send + Sync + 'static, 235 | { 236 | use crate::brotli::ResponseCompression; 237 | use hyper::header::SET_COOKIE; 238 | use serde_json::json; 239 | 240 | // Check if response should be compressed 241 | let compress = client_support_br && response.can_brotli_compress(); 242 | 243 | // Divide resonse into headers and body 244 | let (parts, res_body) = response.into_parts(); 245 | 246 | // HTTP status 247 | let status_code = parts.status.as_u16(); 248 | 249 | // Convert header to JSON map 250 | let mut cookies = Vec::::new(); 251 | let mut headers = serde_json::Map::new(); 252 | for (k, v) in parts.headers.iter() { 253 | if let Ok(value_str) = v.to_str() { 254 | if multi_value { 255 | // REST API format, returns multiValueHeaders 256 | if let Some(values) = headers.get_mut(k.as_str()) { 257 | if let Some(value_ary) = values.as_array_mut() { 258 | value_ary.push(json!(value_str)); 259 | } 260 | } else { 261 | headers.insert(k.as_str().to_string(), json!([value_str])); 262 | } 263 | } else { 264 | // HTTP API v2 format, returns headers 265 | if k == SET_COOKIE { 266 | cookies.push(value_str.to_string()); 267 | } else { 268 | headers.insert(k.as_str().to_string(), json!(value_str)); 269 | } 270 | } 271 | } 272 | } 273 | 274 | // Compress, base64 encode the response body 275 | let body_bytes = hyper::body::to_bytes(res_body).await?; 276 | let body_base64 = if compress { 277 | if multi_value { 278 | headers.insert("content-encoding".to_string(), json!(["br"])); 279 | } else { 280 | headers.insert("content-encoding".to_string(), json!("br")); 281 | } 282 | crate::brotli::compress_response_body(&body_bytes) 283 | } else { 284 | base64::encode(body_bytes) 285 | }; 286 | 287 | if multi_value { 288 | Ok(json!({ 289 | "isBase64Encoded": true, 290 | "statusCode": status_code, 291 | "multiValueHeaders": headers, 292 | "body": body_base64 293 | })) 294 | } else { 295 | Ok(json!({ 296 | "isBase64Encoded": true, 297 | "statusCode": status_code, 298 | "cookies": cookies, 299 | "headers": headers, 300 | "body": body_base64 301 | })) 302 | } 303 | } 304 | 305 | #[cfg(test)] 306 | mod tests { 307 | use super::*; 308 | use crate::{request::LambdaHttpEvent, test_consts::*}; 309 | 310 | // Request JSON string to http::Request 311 | fn prepare_request(event_str: &str) -> HyperRequest { 312 | let reqjson: LambdaHttpEvent = serde_json::from_str(event_str).unwrap(); 313 | let req = HyperRequest::try_from(reqjson).unwrap(); 314 | req 315 | } 316 | 317 | #[test] 318 | fn test_path_decode() { 319 | let req = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY); 320 | assert_eq!(req.uri().path(), "/"); 321 | let req = prepare_request(API_GATEWAY_REST_GET_ROOT_NOQUERY); 322 | assert_eq!(req.uri().path(), "/stage/"); 323 | 324 | let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_NOQUERY); 325 | assert_eq!(req.uri().path(), "/somewhere"); 326 | let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_NOQUERY); 327 | assert_eq!(req.uri().path(), "/stage/somewhere"); 328 | 329 | let req = prepare_request(API_GATEWAY_V2_GET_SPACEPATH_NOQUERY); 330 | assert_eq!(req.uri().path(), "/path%20with/space"); 331 | let req = prepare_request(API_GATEWAY_REST_GET_SPACEPATH_NOQUERY); 332 | assert_eq!(req.uri().path(), "/stage/path%20with/space"); 333 | 334 | let req = prepare_request(API_GATEWAY_V2_GET_PERCENTPATH_NOQUERY); 335 | assert_eq!(req.uri().path(), "/path%25with/percent"); 336 | let req = prepare_request(API_GATEWAY_REST_GET_PERCENTPATH_NOQUERY); 337 | assert_eq!(req.uri().path(), "/stage/path%25with/percent"); 338 | 339 | let req = prepare_request(API_GATEWAY_V2_GET_UTF8PATH_NOQUERY); 340 | assert_eq!( 341 | req.uri().path(), 342 | "/%E6%97%A5%E6%9C%AC%E8%AA%9E/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D" 343 | ); 344 | let req = prepare_request(API_GATEWAY_REST_GET_UTF8PATH_NOQUERY); 345 | assert_eq!( 346 | req.uri().path(), 347 | "/stage/%E6%97%A5%E6%9C%AC%E8%AA%9E/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D" 348 | ); 349 | } 350 | 351 | #[test] 352 | fn test_query_decode() { 353 | let req = prepare_request(API_GATEWAY_V2_GET_ROOT_ONEQUERY); 354 | assert_eq!(req.uri().query(), Some("key=value")); 355 | let req = prepare_request(API_GATEWAY_REST_GET_ROOT_ONEQUERY); 356 | assert_eq!(req.uri().query(), Some("key=value")); 357 | 358 | let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_ONEQUERY); 359 | assert_eq!(req.uri().query(), Some("key=value")); 360 | let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_ONEQUERY); 361 | assert_eq!(req.uri().query(), Some("key=value")); 362 | 363 | let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_TWOQUERY); 364 | assert_eq!(req.uri().query(), Some("key1=value1&key2=value2")); 365 | let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_TWOQUERY); 366 | assert!( 367 | req.uri().query() == Some("key1=value1&key2=value2") 368 | || req.uri().query() == Some("key2=value2&key1=value1") 369 | ); 370 | 371 | let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_SPACEQUERY); 372 | assert_eq!(req.uri().query(), Some("key=value1+value2")); 373 | let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_SPACEQUERY); 374 | assert_eq!(req.uri().query(), Some("key=value1%20value2")); 375 | 376 | let req = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_UTF8QUERY); 377 | assert_eq!(req.uri().query(), Some("key=%E6%97%A5%E6%9C%AC%E8%AA%9E")); 378 | let req = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_UTF8QUERY); 379 | assert_eq!(req.uri().query(), Some("key=%E6%97%A5%E6%9C%AC%E8%AA%9E")); 380 | } 381 | 382 | #[tokio::test] 383 | async fn test_form_post() { 384 | use hyper::body::to_bytes; 385 | use hyper::Method; 386 | 387 | let req = prepare_request(API_GATEWAY_V2_POST_FORM_URLENCODED); 388 | assert_eq!(req.method(), Method::POST); 389 | assert_eq!( 390 | to_bytes(req.into_body()).await.unwrap().as_ref(), 391 | b"key1=value1&key2=value2&Ok=Ok" 392 | ); 393 | let req = prepare_request(API_GATEWAY_REST_POST_FORM_URLENCODED); 394 | assert_eq!(req.method(), Method::POST); 395 | assert_eq!( 396 | to_bytes(req.into_body()).await.unwrap().as_ref(), 397 | b"key1=value1&key2=value2&Ok=Ok" 398 | ); 399 | 400 | // Base64 encoded 401 | let req = prepare_request(API_GATEWAY_V2_POST_FORM_URLENCODED_B64); 402 | assert_eq!(req.method(), Method::POST); 403 | assert_eq!( 404 | to_bytes(req.into_body()).await.unwrap().as_ref(), 405 | b"key1=value1&key2=value2&Ok=Ok" 406 | ); 407 | let req = prepare_request(API_GATEWAY_REST_POST_FORM_URLENCODED_B64); 408 | assert_eq!(req.method(), Method::POST); 409 | assert_eq!( 410 | to_bytes(req.into_body()).await.unwrap().as_ref(), 411 | b"key1=value1&key2=value2&Ok=Ok" 412 | ); 413 | } 414 | 415 | #[test] 416 | fn test_parse_header() { 417 | let req = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY); 418 | assert_eq!(req.headers().get("x-forwarded-port").unwrap(), &"443"); 419 | assert_eq!(req.headers().get("x-forwarded-proto").unwrap(), &"https"); 420 | let req = prepare_request(API_GATEWAY_REST_GET_ROOT_NOQUERY); 421 | assert_eq!(req.headers().get("x-forwarded-port").unwrap(), &"443"); 422 | assert_eq!(req.headers().get("x-forwarded-proto").unwrap(), &"https"); 423 | } 424 | 425 | #[test] 426 | fn test_parse_cookies() { 427 | let req = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY); 428 | assert_eq!(req.headers().get("cookie"), None); 429 | let req = prepare_request(API_GATEWAY_REST_GET_ROOT_NOQUERY); 430 | assert_eq!(req.headers().get("cookie"), None); 431 | 432 | let req = prepare_request(API_GATEWAY_V2_GET_ONE_COOKIE); 433 | assert_eq!(req.headers().get("cookie").unwrap(), &"cookie1=value1"); 434 | let req = prepare_request(API_GATEWAY_REST_GET_ONE_COOKIE); 435 | assert_eq!(req.headers().get("cookie").unwrap(), &"cookie1=value1"); 436 | 437 | let req = prepare_request(API_GATEWAY_V2_GET_TWO_COOKIES); 438 | assert_eq!( 439 | req.headers().get("cookie").unwrap(), 440 | &"cookie1=value1; cookie2=value2" 441 | ); 442 | let req = prepare_request(API_GATEWAY_REST_GET_TWO_COOKIES); 443 | assert_eq!( 444 | req.headers().get("cookie").unwrap(), 445 | &"cookie1=value1; cookie2=value2" 446 | ); 447 | } 448 | } 449 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pub use lambda_runtime::Error as LambdaError; 3 | 4 | #[cfg(test)] 5 | #[cfg(any(feature = "actix4", feature = "rocket05", feature = "hyper"))] 6 | mod test_consts; 7 | 8 | #[cfg(any(feature = "actix4", feature = "rocket05", feature = "hyper"))] 9 | pub(crate) mod brotli; 10 | #[cfg(any(feature = "actix4", feature = "rocket05", feature = "hyper"))] 11 | mod request; 12 | 13 | #[cfg(feature = "actix4")] 14 | mod actix4; 15 | #[cfg(feature = "actix4")] 16 | pub use actix4::run_actix_on_lambda; 17 | #[cfg(feature = "actix4")] 18 | pub use actix_web; 19 | 20 | #[cfg(feature = "rocket05")] 21 | mod rocket05; 22 | #[cfg(feature = "rocket05")] 23 | pub use rocket; 24 | #[cfg(feature = "rocket05")] 25 | pub use rocket05::launch_rocket_on_lambda; 26 | 27 | #[cfg(feature = "hyper")] 28 | mod hyper014; 29 | #[cfg(feature = "hyper")] 30 | pub use hyper014::run_hyper_on_lambda; 31 | 32 | /// Returns true if it is running on AWS Lambda 33 | pub fn is_running_on_lambda() -> bool { 34 | std::env::var("AWS_LAMBDA_RUNTIME_API").is_ok() 35 | } 36 | -------------------------------------------------------------------------------- /src/request.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | //! 3 | //! Lambda event deserialize 4 | //! 5 | use serde::Deserialize; 6 | use std::borrow::Cow; 7 | use std::collections::HashMap; 8 | 9 | #[derive(Deserialize, Debug)] 10 | #[serde(untagged)] 11 | pub(crate) enum LambdaHttpEvent<'a> { 12 | ApiGatewayHttpV2(ApiGatewayHttpV2Event<'a>), 13 | ApiGatewayRestOrAlb(ApiGatewayRestEvent<'a>), 14 | } 15 | 16 | impl LambdaHttpEvent<'_> { 17 | /// HTTP request method 18 | pub fn method<'a>(&'a self) -> &'a str { 19 | match self { 20 | Self::ApiGatewayHttpV2(event) => &event.request_context.http.method, 21 | Self::ApiGatewayRestOrAlb(event) => &event.http_method, 22 | } 23 | } 24 | 25 | /// Host name 26 | #[allow(dead_code)] 27 | pub fn hostname<'a>(&'a self) -> Option<&'a str> { 28 | match self { 29 | Self::ApiGatewayHttpV2(event) => Some(&event.request_context.domain_name), 30 | Self::ApiGatewayRestOrAlb(event) => { 31 | if let RestOrAlbRequestContext::Rest(context) = &event.request_context { 32 | Some(&context.domain_name) 33 | } else if let Some(host_headers) = event.multi_value_headers.get("host") { 34 | host_headers.first().map(|h| h as &str) 35 | } else { 36 | None 37 | } 38 | } 39 | } 40 | } 41 | 42 | /// URL encoded path?query 43 | pub fn path_query(&self) -> String { 44 | match self { 45 | Self::ApiGatewayHttpV2(event) => { 46 | let path = encode_path_query(&event.raw_path); 47 | let query = &event.raw_query_string as &str; 48 | if query.is_empty() { 49 | // No query string 50 | path.into_owned() 51 | } else { 52 | // With query string 53 | format!("{}?{}", path, query) 54 | } 55 | } 56 | Self::ApiGatewayRestOrAlb(event) => { 57 | let path = if let RestOrAlbRequestContext::Rest(context) = &event.request_context { 58 | // API Gateway REST, request_contest.path contains stage prefix 59 | &context.path 60 | } else { 61 | // ALB 62 | &event.path 63 | }; 64 | if let Some(query_string_parameters) = &event.multi_value_query_string_parameters { 65 | // With query string 66 | let querystr = query_string_parameters 67 | .iter() 68 | .flat_map(|(k, vec)| { 69 | let k_enc = encode_path_query(&k); 70 | vec.iter() 71 | .map(move |v| format!("{}={}", k_enc, encode_path_query(&v))) 72 | }) 73 | .collect::>() 74 | .join("&"); 75 | format!("{}?{}", path, querystr) 76 | } else { 77 | // No query string 78 | path.clone() 79 | } 80 | } 81 | } 82 | } 83 | 84 | /// HTTP headers 85 | pub fn headers<'a>(&'a self) -> Vec<(&'a str, Cow<'a, str>)> { 86 | match self { 87 | Self::ApiGatewayHttpV2(event) => { 88 | let mut headers: Vec<(&'a str, Cow<'a, str>)> = event 89 | .headers 90 | .iter() 91 | .map(|(k, v)| (k as &str, Cow::from(v as &str))) 92 | .collect(); 93 | 94 | // Add cookie header 95 | if let Some(cookies) = &event.cookies { 96 | let cookie_value = cookies.join("; "); 97 | headers.push(("cookie", Cow::from(cookie_value))); 98 | } 99 | 100 | headers 101 | } 102 | Self::ApiGatewayRestOrAlb(event) => event 103 | .multi_value_headers 104 | .iter() 105 | .flat_map(|(k, vec)| vec.iter().map(move |v| (k as &str, Cow::from(v as &str)))) 106 | .collect(), 107 | } 108 | } 109 | 110 | /// Cookies 111 | /// percent encoded "key=val" 112 | #[allow(dead_code)] 113 | pub fn cookies<'a>(&'a self) -> Vec<&'a str> { 114 | match self { 115 | Self::ApiGatewayHttpV2(event) => { 116 | if let Some(cookies) = &event.cookies { 117 | cookies.iter().map(|c| c.as_str()).collect() 118 | } else { 119 | Vec::new() 120 | } 121 | } 122 | Self::ApiGatewayRestOrAlb(event) => { 123 | if let Some(cookie_headers) = event.multi_value_headers.get("cookie") { 124 | cookie_headers 125 | .iter() 126 | .flat_map(|v| v.split(";")) 127 | .map(|c| c.trim()) 128 | .collect() 129 | } else { 130 | Vec::new() 131 | } 132 | } 133 | } 134 | } 135 | 136 | /// Check if HTTP client supports Brotli compression. 137 | /// ( Accept-Encoding contains "br" ) 138 | #[cfg(feature = "br")] 139 | pub fn client_supports_brotli(&self) -> bool { 140 | match self { 141 | Self::ApiGatewayHttpV2(event) => { 142 | if let Some(header_val) = event.headers.get("accept-encoding") { 143 | for elm in header_val.to_ascii_lowercase().split(',') { 144 | if let Some(algo_name) = elm.split(';').next() { 145 | // first part of elm, contains 'br', 'gzip', etc. 146 | if algo_name.trim() == "br" { 147 | // HTTP client support Brotli compression 148 | return true; 149 | } 150 | } 151 | } 152 | // No "br" in accept-encoding header 153 | false 154 | } else { 155 | // No accept-encoding header 156 | false 157 | } 158 | } 159 | Self::ApiGatewayRestOrAlb(event) => { 160 | if let Some(header_vals) = event.multi_value_headers.get("accept-encoding") { 161 | for header_val in header_vals { 162 | for elm in header_val.to_ascii_lowercase().split(',') { 163 | if let Some(algo_name) = elm.split(';').next() { 164 | // first part of elm, contains 'br', 'gzip', etc. 165 | if algo_name.trim() == "br" { 166 | // HTTP client support Brotli compression 167 | return true; 168 | } 169 | } 170 | } 171 | } 172 | // No "br" in accept-encoding header 173 | false 174 | } else { 175 | // No accept-encoding header 176 | false 177 | } 178 | } 179 | } 180 | } 181 | 182 | // Without Brotli support, always returns false 183 | #[cfg(not(feature = "br"))] 184 | pub fn client_supports_brotli(&self) -> bool { 185 | false 186 | } 187 | 188 | /// Is request & response use multi-value-header 189 | pub fn multi_value(&self) -> bool { 190 | match self { 191 | Self::ApiGatewayHttpV2(_) => false, 192 | Self::ApiGatewayRestOrAlb(_) => true, 193 | } 194 | } 195 | 196 | /// Request body 197 | pub fn body(self) -> Result, base64::DecodeError> { 198 | let (body, b64_encoded) = match self { 199 | Self::ApiGatewayHttpV2(event) => (event.body, event.is_base64_encoded), 200 | Self::ApiGatewayRestOrAlb(event) => (event.body, event.is_base64_encoded), 201 | }; 202 | 203 | if let Some(body) = body { 204 | if b64_encoded { 205 | // base64 decode 206 | base64::decode(&body as &str) 207 | } else { 208 | // string 209 | Ok(body.into_owned().into_bytes()) 210 | } 211 | } else { 212 | // empty body (GET, OPTION, etc. methods) 213 | Ok(Vec::new()) 214 | } 215 | } 216 | 217 | /// Source IP address 218 | #[allow(dead_code)] 219 | pub fn source_ip(&self) -> Option { 220 | use std::net::IpAddr; 221 | use std::str::FromStr; 222 | match self { 223 | Self::ApiGatewayHttpV2(event) => { 224 | IpAddr::from_str(&event.request_context.http.source_ip).ok() 225 | } 226 | Self::ApiGatewayRestOrAlb(event) => { 227 | if let RestOrAlbRequestContext::Rest(context) = &event.request_context { 228 | IpAddr::from_str(&context.identity.source_ip).ok() 229 | } else { 230 | None 231 | } 232 | } 233 | } 234 | } 235 | } 236 | 237 | /// API Gateway HTTP API payload format version 2.0 238 | /// https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html 239 | #[derive(Deserialize, Debug)] 240 | #[serde(rename_all = "camelCase")] 241 | pub(crate) struct ApiGatewayHttpV2Event<'a> { 242 | #[allow(dead_code)] 243 | version: String, 244 | raw_path: String, 245 | raw_query_string: String, 246 | cookies: Option>, 247 | headers: HashMap, 248 | //#[serde(borrow)] 249 | body: Option>, 250 | #[serde(default)] 251 | is_base64_encoded: bool, 252 | request_context: ApiGatewayV2RequestContext, 253 | // route_key: Cow<'a, str>, 254 | // #[serde(default)] 255 | // query_string_parameters: StrMap, 256 | // #[serde(default)] 257 | // path_parameters: StrMap, 258 | // #[serde(default)] 259 | // stage_variables: StrMap, 260 | } 261 | 262 | #[derive(Deserialize, Debug, Clone)] 263 | #[serde(rename_all = "camelCase")] 264 | struct ApiGatewayV2RequestContext { 265 | /// The full domain name used to invoke the API. This should be the same as the incoming Host header. 266 | domain_name: String, 267 | /// The HTTP method used. 268 | http: Http, 269 | // The API owner's AWS account ID. 270 | // pub account_id: String, 271 | // The identifier API Gateway assigns to your API. 272 | // pub api_id: String, 273 | // The stringified value of the specified key-value pair of the context map returned from an API Gateway Lambda authorizer function. 274 | // #[serde(default)] 275 | // pub authorizer: HashMap, 276 | // The first label of the $context.domainName. This is often used as a caller/customer identifier. 277 | // pub domain_prefix: String, 278 | // The ID that API Gateway assigns to the API request. 279 | // pub request_id: String, 280 | // Undocumented, could be resourcePath 281 | // pub route_key: String, 282 | // The deployment stage of the API request (for example, Beta or Prod). 283 | // pub stage: String, 284 | // Undocumented, could be requestTime 285 | // pub time: String, 286 | // Undocumented, could be requestTimeEpoch 287 | // pub time_epoch: usize, 288 | } 289 | 290 | #[derive(Deserialize, Debug, Default, Clone)] 291 | #[serde(rename_all = "camelCase")] 292 | struct Http { 293 | /// The HTTP method used. Valid values include: DELETE, GET, HEAD, OPTIONS, PATCH, POST, and PUT. 294 | method: String, 295 | /// The source IP address of the TCP connection making the request to API Gateway. 296 | source_ip: String, 297 | // The request path. For example, for a non-proxy request URL of 298 | // `https://{rest-api-id.execute-api.{region}.amazonaws.com/{stage}/root/child`, 299 | // the $context.path value is `/{stage}/root/child`. 300 | // pub path: String, 301 | // The request protocol, for example, HTTP/1.1. 302 | // pub protocol: String, 303 | // The User-Agent header of the API caller. 304 | // pub user_agent: String, 305 | } 306 | 307 | /// API Gateway REST API, ALB payload format 308 | /// https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format 309 | /// 310 | /// In case of ALB, you must explicitly enable multi-value headers setting. 311 | /// 312 | #[derive(Deserialize, Debug)] 313 | #[serde(rename_all = "camelCase")] 314 | pub(crate) struct ApiGatewayRestEvent<'a> { 315 | // path without stage 316 | path: String, 317 | http_method: String, 318 | //#[serde(borrow)] 319 | body: Option>, 320 | #[serde(default)] 321 | is_base64_encoded: bool, 322 | multi_value_headers: HashMap>, 323 | #[serde(default)] 324 | multi_value_query_string_parameters: Option>>, 325 | // request_context = None when called from ALB 326 | request_context: RestOrAlbRequestContext, 327 | // headers: HashMap, 328 | // path_parameters: HashMap, 329 | // query_string_parameters: HashMap, 330 | // stage_variables: HashMap, 331 | } 332 | 333 | #[derive(Deserialize, Debug)] 334 | #[serde(untagged)] 335 | enum RestOrAlbRequestContext { 336 | Rest(ApiGatewayRestRequestContext), 337 | Alb(AlbRequestContext), 338 | } 339 | 340 | /// API Gateway REST API request context 341 | #[derive(Deserialize, Debug)] 342 | #[serde(rename_all = "camelCase")] 343 | struct ApiGatewayRestRequestContext { 344 | domain_name: String, 345 | identity: ApiGatewayRestIdentity, 346 | // Path with stage 347 | path: String, 348 | // account_id: String, 349 | // api_id: String, 350 | // authorizer: HashMap, 351 | // domain_prefix: String, 352 | // http_method: String, 353 | // protocol: String, 354 | // request_id: String, 355 | // request_time: String, 356 | // request_time_epoch: i64, 357 | // resource_id: String, 358 | // resource_path: String, 359 | // stage: String, 360 | } 361 | 362 | /// API Gateway REST API identity 363 | #[derive(Deserialize, Debug)] 364 | #[serde(rename_all = "camelCase")] 365 | struct ApiGatewayRestIdentity { 366 | #[allow(dead_code)] 367 | access_key: Option, 368 | source_ip: String, 369 | } 370 | 371 | /// ALB Request context 372 | #[derive(Deserialize, Debug)] 373 | #[serde(rename_all = "camelCase")] 374 | struct AlbRequestContext {} 375 | 376 | // raw_path in API Gateway HTTP API V2 payload is percent decoded. 377 | // Path containing space or UTF-8 char is 378 | // required to percent encoded again before passed to web frameworks 379 | // See RFC3986 3.3 Path for valid chars. 380 | const RFC3986_PATH_ESCAPE_SET: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS 381 | .add(b' ') 382 | .add(b'"') 383 | .add(b'#') 384 | .add(b'%') 385 | .add(b'+') 386 | .add(b':') 387 | .add(b'<') 388 | .add(b'>') 389 | .add(b'?') 390 | .add(b'@') 391 | .add(b'[') 392 | .add(b'\\') 393 | .add(b']') 394 | .add(b'^') 395 | .add(b'`') 396 | .add(b'{') 397 | .add(b'|') 398 | .add(b'}'); 399 | 400 | fn encode_path_query<'a>(pathstr: &'a str) -> Cow<'a, str> { 401 | Cow::from(percent_encoding::utf8_percent_encode( 402 | pathstr, 403 | &RFC3986_PATH_ESCAPE_SET, 404 | )) 405 | } 406 | 407 | #[cfg(test)] 408 | mod tests { 409 | use super::*; 410 | use crate::test_consts::*; 411 | 412 | #[test] 413 | fn test_decode() { 414 | let _: ApiGatewayHttpV2Event = 415 | serde_json::from_str(API_GATEWAY_V2_GET_ROOT_NOQUERY).unwrap(); 416 | let _: LambdaHttpEvent = serde_json::from_str(API_GATEWAY_V2_GET_ROOT_NOQUERY).unwrap(); 417 | let _: ApiGatewayRestEvent = 418 | serde_json::from_str(API_GATEWAY_REST_GET_ROOT_NOQUERY).unwrap(); 419 | let _: LambdaHttpEvent = serde_json::from_str(API_GATEWAY_REST_GET_ROOT_NOQUERY).unwrap(); 420 | } 421 | 422 | #[test] 423 | fn test_cookie() { 424 | let event: LambdaHttpEvent = serde_json::from_str(API_GATEWAY_V2_GET_TWO_COOKIES).unwrap(); 425 | assert_eq!( 426 | event.cookies(), 427 | vec!["cookie1=value1".to_string(), "cookie2=value2".to_string()] 428 | ); 429 | let event: LambdaHttpEvent = 430 | serde_json::from_str(API_GATEWAY_REST_GET_TWO_COOKIES).unwrap(); 431 | assert_eq!( 432 | event.cookies(), 433 | vec!["cookie1=value1".to_string(), "cookie2=value2".to_string()] 434 | ); 435 | } 436 | } 437 | -------------------------------------------------------------------------------- /src/rocket05.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | //! 3 | //! Run Rocket on AWS Lambda 4 | //! 5 | //! 6 | use crate::request::LambdaHttpEvent; 7 | use core::convert::TryFrom; 8 | use core::future::Future; 9 | use lambda_runtime::{Error as LambdaError, LambdaEvent, Service as LambdaService}; 10 | use std::pin::Pin; 11 | use std::sync::Arc; 12 | 13 | /// Launch Rocket application on AWS Lambda 14 | /// 15 | /// ```no_run 16 | /// use rocket::{self, get, routes}; 17 | /// use lambda_web::{is_running_on_lambda, launch_rocket_on_lambda, LambdaError}; 18 | /// 19 | /// #[get("/hello//")] 20 | /// fn hello(name: &str, age: u8) -> String { 21 | /// format!("Hello, {} year old named {}!", age, name) 22 | /// } 23 | /// 24 | /// #[rocket::main] 25 | /// async fn main() -> Result<(), LambdaError> { 26 | /// let rocket = rocket::build().mount("/", routes![hello]); 27 | /// if is_running_on_lambda() { 28 | /// // Launch on AWS Lambda 29 | /// launch_rocket_on_lambda(rocket).await?; 30 | /// } else { 31 | /// // Launch local server 32 | /// rocket.launch().await?; 33 | /// } 34 | /// Ok(()) 35 | /// } 36 | /// ``` 37 | /// 38 | pub async fn launch_rocket_on_lambda( 39 | r: rocket::Rocket

, 40 | ) -> Result<(), LambdaError> { 41 | lambda_runtime::run(RocketHandler(Arc::new( 42 | rocket::local::asynchronous::Client::untracked(r).await?, 43 | ))) 44 | .await?; 45 | 46 | Ok(()) 47 | } 48 | 49 | /// Lambda_runtime handler for Rocket 50 | struct RocketHandler(Arc); 51 | 52 | impl LambdaService>> for RocketHandler { 53 | type Response = serde_json::Value; 54 | type Error = rocket::Error; 55 | type Future = Pin> + Send>>; 56 | 57 | /// Always ready in case of Rocket local client 58 | fn poll_ready( 59 | &mut self, 60 | _cx: &mut core::task::Context<'_>, 61 | ) -> core::task::Poll> { 62 | core::task::Poll::Ready(Ok(())) 63 | } 64 | 65 | /// Lambda handler function 66 | /// Parse Lambda event as Rocket LocalRequest, 67 | /// serialize Rocket LocalResponse to Lambda JSON response 68 | fn call(&mut self, req: LambdaEvent>) -> Self::Future { 69 | use serde_json::json; 70 | 71 | let event = req.payload; 72 | let _context = req.context; 73 | 74 | // check if web client supports content-encoding: br 75 | let client_br = event.client_supports_brotli(); 76 | // multi-value-headers response format 77 | let multi_value = event.multi_value(); 78 | 79 | // Parse request 80 | let decode_result = RequestDecode::try_from(event); 81 | let client = self.0.clone(); 82 | let fut = async move { 83 | match decode_result { 84 | Ok(req_decode) => { 85 | // Request parsing succeeded, make Rocket LocalRequest 86 | let local_request = req_decode.make_request(&client); 87 | 88 | // Dispatch request and get response 89 | let response = local_request.dispatch().await; 90 | 91 | // Return response as API Gateway JSON 92 | api_gateway_response_from_rocket(response, client_br, multi_value).await 93 | } 94 | Err(_request_err) => { 95 | // Request parsing error 96 | Ok(json!({ 97 | "isBase64Encoded": false, 98 | "statusCode": 400u16, 99 | "headers": { "content-type": "text/plain"}, 100 | "body": "Bad Request" // No details for security 101 | })) 102 | } 103 | } 104 | }; 105 | Box::pin(fut) 106 | } 107 | } 108 | 109 | // Request decoded from API gateway JSON. 110 | // To move async boundary in call() function, 111 | // all elements must be owned 112 | struct RequestDecode { 113 | path_and_query: String, 114 | method: rocket::http::Method, 115 | source_ip: std::net::IpAddr, 116 | cookies: Vec, 117 | headers: Vec>, 118 | body: Vec, 119 | } 120 | 121 | impl TryFrom> for RequestDecode { 122 | type Error = LambdaError; 123 | 124 | /// Request from API Gateway event 125 | fn try_from(event: LambdaHttpEvent) -> Result { 126 | use rocket::http::{Header, Method}; 127 | use std::net::IpAddr; 128 | use std::str::FromStr; 129 | 130 | // path ? query_string 131 | let path_and_query = event.path_query(); 132 | 133 | // Method, Source IP 134 | let method = Method::from_str(&event.method()).map_err(|_| "InvalidMethod")?; 135 | let source_ip = event 136 | .source_ip() 137 | .unwrap_or(IpAddr::from([0u8, 0u8, 0u8, 0u8])); 138 | 139 | // Parse cookies 140 | let cookies = event.cookies().iter().map(|c| c.to_string()).collect(); 141 | 142 | // Headers 143 | let headers = event 144 | .headers() 145 | .iter() 146 | .map(|(k, v)| Header::new(k.to_string(), v.to_string())) 147 | .collect::>(); 148 | 149 | // Body 150 | let body = event.body()?; 151 | 152 | Ok(Self { 153 | path_and_query, 154 | method, 155 | source_ip, 156 | cookies, 157 | headers, 158 | body, 159 | }) 160 | } 161 | } 162 | 163 | impl RequestDecode { 164 | /// Make Rocket LocalRequest 165 | fn make_request<'c, 's: 'c>( 166 | &'s self, 167 | client: &'c rocket::local::asynchronous::Client, 168 | ) -> rocket::local::asynchronous::LocalRequest<'c> { 169 | use rocket::http::Cookie; 170 | 171 | // path, method, remote address, body 172 | let req = client 173 | .req(self.method, &self.path_and_query) 174 | .remote(std::net::SocketAddr::from((self.source_ip, 0u16))) 175 | .body(&self.body); 176 | 177 | // Copy cookies 178 | let req = self.cookies.iter().fold(req, |req, cookie_name_val| { 179 | if let Ok(cookie) = Cookie::parse_encoded(cookie_name_val) { 180 | req.cookie(cookie) 181 | } else { 182 | req 183 | } 184 | }); 185 | 186 | // Copy headers 187 | let req = self 188 | .headers 189 | .iter() 190 | .fold(req, |req, header| req.header(header.clone())); 191 | 192 | req 193 | } 194 | } 195 | 196 | impl crate::brotli::ResponseCompression for rocket::local::asynchronous::LocalResponse<'_> { 197 | /// Content-Encoding header value 198 | fn content_encoding<'a>(&'a self) -> Option<&'a str> { 199 | self.headers().get_one("content-encoding") 200 | } 201 | 202 | /// Content-Type header value 203 | fn content_type<'a>(&'a self) -> Option<&'a str> { 204 | self.headers().get_one("content-type") 205 | } 206 | } 207 | 208 | /// API Gateway response from Rocket response 209 | async fn api_gateway_response_from_rocket( 210 | response: rocket::local::asynchronous::LocalResponse<'_>, 211 | client_support_br: bool, 212 | multi_value: bool, 213 | ) -> Result { 214 | use crate::brotli::ResponseCompression; 215 | use serde_json::json; 216 | 217 | // HTTP status 218 | let status_code = response.status().code; 219 | 220 | // Convert header to JSON map 221 | let mut cookies = Vec::::new(); 222 | let mut headers = serde_json::Map::new(); 223 | for header in response.headers().iter() { 224 | let header_name = header.name.into_string(); 225 | let header_value = header.value.into_owned(); 226 | if multi_value { 227 | // REST API format, returns multiValueHeaders 228 | if let Some(values) = headers.get_mut(&header_name) { 229 | if let Some(value_ary) = values.as_array_mut() { 230 | value_ary.push(json!(header_value)); 231 | } 232 | } else { 233 | headers.insert(header_name, json!([header_value])); 234 | } 235 | } else { 236 | // HTTP API v2 format, returns headers 237 | if &header_name == "set-cookie" { 238 | cookies.push(header_value); 239 | } else { 240 | headers.insert(header_name, json!(header_value)); 241 | } 242 | } 243 | } 244 | 245 | // check if response should be compressed 246 | let compress = client_support_br && response.can_brotli_compress(); 247 | let body_bytes = response.into_bytes().await.unwrap_or_default(); 248 | let body_base64 = if compress { 249 | if multi_value { 250 | headers.insert("content-encoding".to_string(), json!(["br"])); 251 | } else { 252 | headers.insert("content-encoding".to_string(), json!("br")); 253 | } 254 | crate::brotli::compress_response_body(&body_bytes) 255 | } else { 256 | base64::encode(body_bytes) 257 | }; 258 | 259 | if multi_value { 260 | Ok(json!({ 261 | "isBase64Encoded": true, 262 | "statusCode": status_code, 263 | "multiValueHeaders": headers, 264 | "body": body_base64 265 | })) 266 | } else { 267 | Ok(json!({ 268 | "isBase64Encoded": true, 269 | "statusCode": status_code, 270 | "cookies": cookies, 271 | "headers": headers, 272 | "body": body_base64 273 | })) 274 | } 275 | } 276 | 277 | #[cfg(test)] 278 | mod tests { 279 | use super::*; 280 | use crate::{request::LambdaHttpEvent, test_consts::*}; 281 | use rocket::{async_test, local::asynchronous::Client}; 282 | use std::path::PathBuf; 283 | 284 | // Request JSON to actix_http::Request 285 | fn prepare_request(event_str: &str) -> RequestDecode { 286 | let reqjson: LambdaHttpEvent = serde_json::from_str(event_str).unwrap(); 287 | let decode = RequestDecode::try_from(reqjson).unwrap(); 288 | decode 289 | } 290 | 291 | #[async_test] 292 | async fn test_path_decode() { 293 | let rocket = rocket::build(); 294 | let client = Client::untracked(rocket).await.unwrap(); 295 | 296 | let decode = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY); 297 | let req = decode.make_request(&client); 298 | assert_eq!(&decode.path_and_query, "/"); 299 | assert_eq!(req.inner().segments(0..), Ok(PathBuf::new())); 300 | let decode = prepare_request(API_GATEWAY_REST_GET_ROOT_NOQUERY); 301 | let req = decode.make_request(&client); 302 | assert_eq!(&decode.path_and_query, "/stage/"); 303 | assert_eq!(req.inner().segments(0..), Ok(PathBuf::from("stage"))); 304 | 305 | let decode = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_NOQUERY); 306 | let req = decode.make_request(&client); 307 | assert_eq!(&decode.path_and_query, "/somewhere"); 308 | assert_eq!(req.inner().segments(0..), Ok(PathBuf::from("somewhere"))); 309 | let decode = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_NOQUERY); 310 | let req = decode.make_request(&client); 311 | assert_eq!(&decode.path_and_query, "/stage/somewhere"); 312 | assert_eq!( 313 | req.inner().segments(0..), 314 | Ok(PathBuf::from("stage/somewhere")) 315 | ); 316 | 317 | let decode = prepare_request(API_GATEWAY_V2_GET_SPACEPATH_NOQUERY); 318 | let req = decode.make_request(&client); 319 | assert_eq!(&decode.path_and_query, "/path%20with/space"); 320 | assert_eq!( 321 | req.inner().segments(0..), 322 | Ok(PathBuf::from("path with/space")) 323 | ); 324 | let decode = prepare_request(API_GATEWAY_REST_GET_SPACEPATH_NOQUERY); 325 | let req = decode.make_request(&client); 326 | assert_eq!(&decode.path_and_query, "/stage/path%20with/space"); 327 | assert_eq!( 328 | req.inner().segments(0..), 329 | Ok(PathBuf::from("stage/path with/space")) 330 | ); 331 | 332 | let decode = prepare_request(API_GATEWAY_V2_GET_PERCENTPATH_NOQUERY); 333 | let req = decode.make_request(&client); 334 | assert_eq!(&decode.path_and_query, "/path%25with/percent"); 335 | assert_eq!( 336 | req.inner().segments(0..), 337 | Ok(PathBuf::from("path%with/percent")) 338 | ); 339 | let decode = prepare_request(API_GATEWAY_REST_GET_PERCENTPATH_NOQUERY); 340 | let req = decode.make_request(&client); 341 | assert_eq!(&decode.path_and_query, "/stage/path%25with/percent"); 342 | assert_eq!( 343 | req.inner().segments(0..), 344 | Ok(PathBuf::from("stage/path%with/percent")) 345 | ); 346 | 347 | let decode = prepare_request(API_GATEWAY_V2_GET_UTF8PATH_NOQUERY); 348 | let req = decode.make_request(&client); 349 | assert_eq!( 350 | &decode.path_and_query, 351 | "/%E6%97%A5%E6%9C%AC%E8%AA%9E/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D" 352 | ); 353 | assert_eq!( 354 | req.inner().segments(0..), 355 | Ok(PathBuf::from("日本語/ファイル名")) 356 | ); 357 | let decode = prepare_request(API_GATEWAY_REST_GET_UTF8PATH_NOQUERY); 358 | let req = decode.make_request(&client); 359 | assert_eq!( 360 | &decode.path_and_query, 361 | "/stage/%E6%97%A5%E6%9C%AC%E8%AA%9E/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D" 362 | ); 363 | assert_eq!( 364 | req.inner().segments(0..), 365 | Ok(PathBuf::from("stage/日本語/ファイル名")) 366 | ); 367 | } 368 | 369 | #[async_test] 370 | async fn test_query_decode() { 371 | let rocket = rocket::build(); 372 | let client = Client::untracked(rocket).await.unwrap(); 373 | 374 | let decode = prepare_request(API_GATEWAY_V2_GET_ROOT_ONEQUERY); 375 | let req = decode.make_request(&client); 376 | assert_eq!(&decode.path_and_query, "/?key=value"); 377 | assert_eq!(req.inner().segments(0..), Ok(PathBuf::new())); 378 | assert_eq!(req.inner().query_value::<&str>("key").unwrap(), Ok("value")); 379 | let decode = prepare_request(API_GATEWAY_REST_GET_ROOT_ONEQUERY); 380 | let req = decode.make_request(&client); 381 | assert_eq!(&decode.path_and_query, "/stage/?key=value"); 382 | assert_eq!(req.inner().segments(0..), Ok(PathBuf::from("stage"))); 383 | assert_eq!(req.inner().query_value::<&str>("key").unwrap(), Ok("value")); 384 | 385 | let decode = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_ONEQUERY); 386 | let req = decode.make_request(&client); 387 | assert_eq!(&decode.path_and_query, "/somewhere?key=value"); 388 | assert_eq!(req.inner().segments(0..), Ok(PathBuf::from("somewhere"))); 389 | assert_eq!(req.inner().query_value::<&str>("key").unwrap(), Ok("value")); 390 | let decode = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_ONEQUERY); 391 | let req = decode.make_request(&client); 392 | assert_eq!(&decode.path_and_query, "/stage/somewhere?key=value"); 393 | assert_eq!( 394 | req.inner().segments(0..), 395 | Ok(PathBuf::from("stage/somewhere")) 396 | ); 397 | assert_eq!(req.inner().query_value::<&str>("key").unwrap(), Ok("value")); 398 | 399 | let decode = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_TWOQUERY); 400 | let req = decode.make_request(&client); 401 | assert_eq!( 402 | req.inner().query_value::<&str>("key1").unwrap(), 403 | Ok("value1") 404 | ); 405 | assert_eq!( 406 | req.inner().query_value::<&str>("key2").unwrap(), 407 | Ok("value2") 408 | ); 409 | let decode = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_TWOQUERY); 410 | let req = decode.make_request(&client); 411 | assert_eq!( 412 | req.inner().query_value::<&str>("key1").unwrap(), 413 | Ok("value1") 414 | ); 415 | assert_eq!( 416 | req.inner().query_value::<&str>("key2").unwrap(), 417 | Ok("value2") 418 | ); 419 | 420 | let decode = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_SPACEQUERY); 421 | let req = decode.make_request(&client); 422 | assert_eq!( 423 | req.inner().query_value::<&str>("key").unwrap(), 424 | Ok("value1 value2") 425 | ); 426 | let decode = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_SPACEQUERY); 427 | let req = decode.make_request(&client); 428 | assert_eq!( 429 | req.inner().query_value::<&str>("key").unwrap(), 430 | Ok("value1 value2") 431 | ); 432 | 433 | let decode = prepare_request(API_GATEWAY_V2_GET_SOMEWHERE_UTF8QUERY); 434 | let req = decode.make_request(&client); 435 | assert_eq!( 436 | req.inner().query_value::<&str>("key").unwrap(), 437 | Ok("日本語") 438 | ); 439 | let decode = prepare_request(API_GATEWAY_REST_GET_SOMEWHERE_UTF8QUERY); 440 | let req = decode.make_request(&client); 441 | assert_eq!( 442 | req.inner().query_value::<&str>("key").unwrap(), 443 | Ok("日本語") 444 | ); 445 | } 446 | 447 | #[async_test] 448 | async fn test_remote_ip_decode() { 449 | use std::net::IpAddr; 450 | use std::str::FromStr; 451 | 452 | let rocket = rocket::build(); 453 | let client = Client::untracked(rocket).await.unwrap(); 454 | 455 | let decode = prepare_request(API_GATEWAY_V2_GET_ROOT_ONEQUERY); 456 | let req = decode.make_request(&client); 457 | assert_eq!(decode.source_ip, IpAddr::from_str("1.2.3.4").unwrap()); 458 | assert_eq!( 459 | req.inner().client_ip(), 460 | Some(IpAddr::from_str("1.2.3.4").unwrap()) 461 | ); 462 | let decode = prepare_request(API_GATEWAY_REST_GET_ROOT_ONEQUERY); 463 | let req = decode.make_request(&client); 464 | assert_eq!(decode.source_ip, IpAddr::from_str("1.2.3.4").unwrap()); 465 | assert_eq!( 466 | req.inner().client_ip(), 467 | Some(IpAddr::from_str("1.2.3.4").unwrap()) 468 | ); 469 | 470 | let decode = prepare_request(API_GATEWAY_V2_GET_REMOTE_IPV6); 471 | let req = decode.make_request(&client); 472 | assert_eq!( 473 | decode.source_ip, 474 | IpAddr::from_str("2404:6800:400a:80c::2004").unwrap() 475 | ); 476 | assert_eq!( 477 | req.inner().client_ip(), 478 | Some(IpAddr::from_str("2404:6800:400a:80c::2004").unwrap()) 479 | ); 480 | let decode = prepare_request(API_GATEWAY_REST_GET_REMOTE_IPV6); 481 | let req = decode.make_request(&client); 482 | assert_eq!( 483 | decode.source_ip, 484 | IpAddr::from_str("2404:6800:400a:80c::2004").unwrap() 485 | ); 486 | assert_eq!( 487 | req.inner().client_ip(), 488 | Some(IpAddr::from_str("2404:6800:400a:80c::2004").unwrap()) 489 | ); 490 | } 491 | 492 | #[async_test] 493 | async fn test_form_post() { 494 | use rocket::http::ContentType; 495 | use rocket::http::Method; 496 | let rocket = rocket::build(); 497 | let client = Client::untracked(rocket).await.unwrap(); 498 | 499 | let decode = prepare_request(API_GATEWAY_V2_POST_FORM_URLENCODED); 500 | let req = decode.make_request(&client); 501 | assert_eq!(&decode.body, b"key1=value1&key2=value2&Ok=Ok"); 502 | assert_eq!(req.inner().method(), Method::Post); 503 | assert_eq!(req.inner().content_type(), Some(&ContentType::Form)); 504 | let decode = prepare_request(API_GATEWAY_REST_POST_FORM_URLENCODED); 505 | let req = decode.make_request(&client); 506 | assert_eq!(&decode.body, b"key1=value1&key2=value2&Ok=Ok"); 507 | assert_eq!(req.inner().method(), Method::Post); 508 | assert_eq!(req.inner().content_type(), Some(&ContentType::Form)); 509 | 510 | // Base64 encoded 511 | let decode = prepare_request(API_GATEWAY_V2_POST_FORM_URLENCODED_B64); 512 | let req = decode.make_request(&client); 513 | assert_eq!(&decode.body, b"key1=value1&key2=value2&Ok=Ok"); 514 | assert_eq!(req.inner().method(), Method::Post); 515 | assert_eq!(req.inner().content_type(), Some(&ContentType::Form)); 516 | let decode = prepare_request(API_GATEWAY_REST_POST_FORM_URLENCODED_B64); 517 | let req = decode.make_request(&client); 518 | assert_eq!(&decode.body, b"key1=value1&key2=value2&Ok=Ok"); 519 | assert_eq!(req.inner().method(), Method::Post); 520 | assert_eq!(req.inner().content_type(), Some(&ContentType::Form)); 521 | } 522 | 523 | #[async_test] 524 | async fn test_parse_header() { 525 | let rocket = rocket::build(); 526 | let client = Client::untracked(rocket).await.unwrap(); 527 | 528 | let decode = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY); 529 | let req = decode.make_request(&client); 530 | assert_eq!( 531 | req.inner().headers().get_one("x-forwarded-port"), 532 | Some("443") 533 | ); 534 | assert_eq!( 535 | req.inner().headers().get_one("x-forwarded-proto"), 536 | Some("https") 537 | ); 538 | let decode = prepare_request(API_GATEWAY_REST_GET_ROOT_NOQUERY); 539 | let req = decode.make_request(&client); 540 | assert_eq!( 541 | req.inner().headers().get_one("x-forwarded-port"), 542 | Some("443") 543 | ); 544 | assert_eq!( 545 | req.inner().headers().get_one("x-forwarded-proto"), 546 | Some("https") 547 | ); 548 | } 549 | 550 | #[async_test] 551 | async fn test_parse_cookies() { 552 | let rocket = rocket::build(); 553 | let client = Client::untracked(rocket).await.unwrap(); 554 | 555 | let decode = prepare_request(API_GATEWAY_V2_GET_ROOT_NOQUERY); 556 | let req = decode.make_request(&client); 557 | assert_eq!(req.inner().cookies().iter().count(), 0); 558 | let decode = prepare_request(API_GATEWAY_REST_GET_ROOT_NOQUERY); 559 | let req = decode.make_request(&client); 560 | assert_eq!(req.inner().cookies().iter().count(), 0); 561 | 562 | let decode = prepare_request(API_GATEWAY_V2_GET_ONE_COOKIE); 563 | let req = decode.make_request(&client); 564 | assert_eq!( 565 | req.inner().cookies().get("cookie1").unwrap().value(), 566 | "value1" 567 | ); 568 | let decode = prepare_request(API_GATEWAY_REST_GET_ONE_COOKIE); 569 | let req = decode.make_request(&client); 570 | assert_eq!( 571 | req.inner().cookies().get("cookie1").unwrap().value(), 572 | "value1" 573 | ); 574 | 575 | let decode = prepare_request(API_GATEWAY_V2_GET_TWO_COOKIES); 576 | let req = decode.make_request(&client); 577 | assert_eq!( 578 | req.inner().cookies().get("cookie1").unwrap().value(), 579 | "value1" 580 | ); 581 | assert_eq!( 582 | req.inner().cookies().get("cookie2").unwrap().value(), 583 | "value2" 584 | ); 585 | let decode = prepare_request(API_GATEWAY_REST_GET_TWO_COOKIES); 586 | let req = decode.make_request(&client); 587 | assert_eq!( 588 | req.inner().cookies().get("cookie1").unwrap().value(), 589 | "value1" 590 | ); 591 | assert_eq!( 592 | req.inner().cookies().get("cookie2").unwrap().value(), 593 | "value2" 594 | ); 595 | } 596 | } 597 | -------------------------------------------------------------------------------- /src/test_consts.rs: -------------------------------------------------------------------------------- 1 | // 2 | // Path test 3 | // 4 | 5 | // GET / 6 | pub(crate) const API_GATEWAY_V2_GET_ROOT_NOQUERY: &str = r###"{ 7 | "headers":{ 8 | "x-forwarded-for":"1.2.3.4", 9 | "x-forwarded-port":"443", 10 | "x-forwarded-proto":"https" 11 | }, 12 | "isBase64Encoded":false, 13 | "rawPath":"/", 14 | "rawQueryString":"", 15 | "requestContext":{ 16 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 17 | "http":{ 18 | "method":"GET", 19 | "sourceIp":"1.2.3.4" 20 | } 21 | }, 22 | "version":"2.0" 23 | }"###; 24 | pub(crate) const API_GATEWAY_REST_GET_ROOT_NOQUERY: &str = r###"{ 25 | "body":null, 26 | "path":"/", 27 | "httpMethod":"GET", 28 | "headers":{ 29 | "x-forwarded-for":"1.2.3.4", 30 | "x-forwarded-port":"443", 31 | "x-forwarded-proto":"https" 32 | }, 33 | "multiValueHeaders":{ 34 | "x-forwarded-for":["1.2.3.4"], 35 | "x-forwarded-port":["443"], 36 | "x-forwarded-proto":["https"] 37 | }, 38 | "queryStringParameters":null, 39 | "multiValueQueryStringParameters":null, 40 | "requestContext":{ 41 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 42 | "path":"/stage/", 43 | "identity":{ 44 | "sourceIp": "1.2.3.4" 45 | } 46 | } 47 | }"###; 48 | 49 | // GET /somewhere 50 | pub(crate) const API_GATEWAY_V2_GET_SOMEWHERE_NOQUERY: &str = r###"{ 51 | "headers":{ 52 | "x-forwarded-for":"1.2.3.4", 53 | "x-forwarded-port":"443", 54 | "x-forwarded-proto":"https" 55 | }, 56 | "isBase64Encoded":false, 57 | "rawPath":"/somewhere", 58 | "rawQueryString":"", 59 | "requestContext":{ 60 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 61 | "http":{ 62 | "method":"GET", 63 | "sourceIp":"1.2.3.4" 64 | } 65 | }, 66 | "version":"2.0" 67 | }"###; 68 | pub(crate) const API_GATEWAY_REST_GET_SOMEWHERE_NOQUERY: &str = r###"{ 69 | "body":null, 70 | "path":"/somewhere", 71 | "httpMethod":"GET", 72 | "headers":{ 73 | "x-forwarded-for":"1.2.3.4", 74 | "x-forwarded-port":"443", 75 | "x-forwarded-proto":"https" 76 | }, 77 | "multiValueHeaders":{ 78 | "x-forwarded-for":["1.2.3.4"], 79 | "x-forwarded-port":["443"], 80 | "x-forwarded-proto":["https"] 81 | }, 82 | "queryStringParameters":null, 83 | "multiValueQueryStringParameters":null, 84 | "requestContext":{ 85 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 86 | "path":"/stage/somewhere", 87 | "identity":{ 88 | "sourceIp": "1.2.3.4" 89 | } 90 | } 91 | }"###; 92 | 93 | // GET /path%20with/space 94 | pub(crate) const API_GATEWAY_V2_GET_SPACEPATH_NOQUERY: &str = r###"{ 95 | "headers":{ 96 | "x-forwarded-for":"1.2.3.4", 97 | "x-forwarded-port":"443", 98 | "x-forwarded-proto":"https" 99 | }, 100 | "isBase64Encoded":false, 101 | "rawPath":"/path with/space", 102 | "rawQueryString":"", 103 | "requestContext":{ 104 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 105 | "http":{ 106 | "method":"GET", 107 | "sourceIp":"1.2.3.4" 108 | } 109 | }, 110 | "version":"2.0" 111 | }"###; 112 | pub(crate) const API_GATEWAY_REST_GET_SPACEPATH_NOQUERY: &str = r###"{ 113 | "body":null, 114 | "path":"/path%20with/space", 115 | "httpMethod":"GET", 116 | "headers":{ 117 | "x-forwarded-for":"1.2.3.4", 118 | "x-forwarded-port":"443", 119 | "x-forwarded-proto":"https" 120 | }, 121 | "multiValueHeaders":{ 122 | "x-forwarded-for":["1.2.3.4"], 123 | "x-forwarded-port":["443"], 124 | "x-forwarded-proto":["https"] 125 | }, 126 | "queryStringParameters":null, 127 | "multiValueQueryStringParameters":null, 128 | "requestContext":{ 129 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 130 | "path":"/stage/path%20with/space", 131 | "identity":{ 132 | "sourceIp": "1.2.3.4" 133 | } 134 | } 135 | }"###; 136 | 137 | // GET /path%25with/percent 138 | pub(crate) const API_GATEWAY_V2_GET_PERCENTPATH_NOQUERY: &str = r###"{ 139 | "headers":{ 140 | "x-forwarded-for":"1.2.3.4", 141 | "x-forwarded-port":"443", 142 | "x-forwarded-proto":"https" 143 | }, 144 | "isBase64Encoded":false, 145 | "rawPath":"/path%with/percent", 146 | "rawQueryString":"", 147 | "requestContext":{ 148 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 149 | "http":{ 150 | "method":"GET", 151 | "sourceIp":"1.2.3.4" 152 | } 153 | }, 154 | "version":"2.0" 155 | }"###; 156 | pub(crate) const API_GATEWAY_REST_GET_PERCENTPATH_NOQUERY: &str = r###"{ 157 | "body":null, 158 | "path":"/path%25with/percent", 159 | "httpMethod":"GET", 160 | "headers":{ 161 | "x-forwarded-for":"1.2.3.4", 162 | "x-forwarded-port":"443", 163 | "x-forwarded-proto":"https" 164 | }, 165 | "multiValueHeaders":{ 166 | "x-forwarded-for":["1.2.3.4"], 167 | "x-forwarded-port":["443"], 168 | "x-forwarded-proto":["https"] 169 | }, 170 | "queryStringParameters":null, 171 | "multiValueQueryStringParameters":null, 172 | "requestContext":{ 173 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 174 | "path":"/stage/path%25with/percent", 175 | "identity":{ 176 | "sourceIp": "1.2.3.4" 177 | } 178 | } 179 | }"###; 180 | 181 | // GET /%E6%97%A5%E6%9C%AC%E8%AA%9E/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D 182 | pub(crate) const API_GATEWAY_V2_GET_UTF8PATH_NOQUERY: &str = r###"{ 183 | "headers":{ 184 | "x-forwarded-for":"1.2.3.4", 185 | "x-forwarded-port":"443", 186 | "x-forwarded-proto":"https" 187 | }, 188 | "isBase64Encoded":false, 189 | "rawPath":"/日本語/ファイル名", 190 | "rawQueryString":"", 191 | "requestContext":{ 192 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 193 | "http":{ 194 | "method":"GET", 195 | "sourceIp":"1.2.3.4" 196 | } 197 | }, 198 | "version":"2.0" 199 | }"###; 200 | pub(crate) const API_GATEWAY_REST_GET_UTF8PATH_NOQUERY: &str = r###"{ 201 | "body":null, 202 | "path":"/%E6%97%A5%E6%9C%AC%E8%AA%9E/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D", 203 | "httpMethod":"GET", 204 | "headers":{ 205 | "x-forwarded-for":"1.2.3.4", 206 | "x-forwarded-port":"443", 207 | "x-forwarded-proto":"https" 208 | }, 209 | "multiValueHeaders":{ 210 | "x-forwarded-for":["1.2.3.4"], 211 | "x-forwarded-port":["443"], 212 | "x-forwarded-proto":["https"] 213 | }, 214 | "queryStringParameters":null, 215 | "multiValueQueryStringParameters":null, 216 | "requestContext":{ 217 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 218 | "path":"/stage/%E6%97%A5%E6%9C%AC%E8%AA%9E/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%90%8D", 219 | "identity":{ 220 | "sourceIp": "1.2.3.4" 221 | } 222 | } 223 | }"###; 224 | 225 | // 226 | // Query test 227 | // 228 | 229 | // GET /?key=value 230 | pub(crate) const API_GATEWAY_V2_GET_ROOT_ONEQUERY: &str = r###"{ 231 | "headers":{ 232 | "x-forwarded-for":"1.2.3.4", 233 | "x-forwarded-port":"443", 234 | "x-forwarded-proto":"https" 235 | }, 236 | "isBase64Encoded":false, 237 | "rawPath":"/", 238 | "rawQueryString":"key=value", 239 | "requestContext":{ 240 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 241 | "http":{ 242 | "method":"GET", 243 | "sourceIp":"1.2.3.4" 244 | } 245 | }, 246 | "version":"2.0" 247 | }"###; 248 | pub(crate) const API_GATEWAY_REST_GET_ROOT_ONEQUERY: &str = r###"{ 249 | "body":null, 250 | "path":"/", 251 | "httpMethod":"GET", 252 | "headers":{ 253 | "x-forwarded-for":"1.2.3.4", 254 | "x-forwarded-port":"443", 255 | "x-forwarded-proto":"https" 256 | }, 257 | "multiValueHeaders":{ 258 | "x-forwarded-for":["1.2.3.4"], 259 | "x-forwarded-port":["443"], 260 | "x-forwarded-proto":["https"] 261 | }, 262 | "queryStringParameters":{ 263 | "key":"value" 264 | }, 265 | "multiValueQueryStringParameters":{ 266 | "key":["value"] 267 | }, 268 | "requestContext":{ 269 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 270 | "path":"/stage/", 271 | "identity":{ 272 | "sourceIp": "1.2.3.4" 273 | } 274 | } 275 | }"###; 276 | 277 | // GET /somewhere?key=value 278 | pub(crate) const API_GATEWAY_V2_GET_SOMEWHERE_ONEQUERY: &str = r###"{ 279 | "headers":{ 280 | "x-forwarded-for":"1.2.3.4", 281 | "x-forwarded-port":"443", 282 | "x-forwarded-proto":"https" 283 | }, 284 | "isBase64Encoded":false, 285 | "rawPath":"/somewhere", 286 | "rawQueryString":"key=value", 287 | "requestContext":{ 288 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 289 | "http":{ 290 | "method":"GET", 291 | "sourceIp":"1.2.3.4" 292 | } 293 | }, 294 | "version":"2.0" 295 | }"###; 296 | pub(crate) const API_GATEWAY_REST_GET_SOMEWHERE_ONEQUERY: &str = r###"{ 297 | "body":null, 298 | "path":"/somewhere", 299 | "httpMethod":"GET", 300 | "headers":{ 301 | "x-forwarded-for":"1.2.3.4", 302 | "x-forwarded-port":"443", 303 | "x-forwarded-proto":"https" 304 | }, 305 | "multiValueHeaders":{ 306 | "x-forwarded-for":["1.2.3.4"], 307 | "x-forwarded-port":["443"], 308 | "x-forwarded-proto":["https"] 309 | }, 310 | "queryStringParameters":{ 311 | "key":"value" 312 | }, 313 | "multiValueQueryStringParameters":{ 314 | "key":["value"] 315 | }, 316 | "requestContext":{ 317 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 318 | "path":"/stage/somewhere", 319 | "identity":{ 320 | "sourceIp": "1.2.3.4" 321 | } 322 | } 323 | }"###; 324 | 325 | // GET /somewhere?key1=value1&key2=value2 326 | pub(crate) const API_GATEWAY_V2_GET_SOMEWHERE_TWOQUERY: &str = r###"{ 327 | "headers":{ 328 | "x-forwarded-for":"1.2.3.4", 329 | "x-forwarded-port":"443", 330 | "x-forwarded-proto":"https" 331 | }, 332 | "isBase64Encoded":false, 333 | "rawPath":"/somewhere", 334 | "rawQueryString":"key1=value1&key2=value2", 335 | "requestContext":{ 336 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 337 | "http":{ 338 | "method":"GET", 339 | "sourceIp":"1.2.3.4" 340 | } 341 | }, 342 | "version":"2.0" 343 | }"###; 344 | pub(crate) const API_GATEWAY_REST_GET_SOMEWHERE_TWOQUERY: &str = r###"{ 345 | "body":null, 346 | "path":"/somewhere", 347 | "httpMethod":"GET", 348 | "headers":{ 349 | "x-forwarded-for":"1.2.3.4", 350 | "x-forwarded-port":"443", 351 | "x-forwarded-proto":"https" 352 | }, 353 | "multiValueHeaders":{ 354 | "x-forwarded-for":["1.2.3.4"], 355 | "x-forwarded-port":["443"], 356 | "x-forwarded-proto":["https"] 357 | }, 358 | "queryStringParameters":{ 359 | "key1":"value1", 360 | "key2":"value2" 361 | }, 362 | "multiValueQueryStringParameters":{ 363 | "key1":["value1"], 364 | "key2":["value2"] 365 | }, 366 | "requestContext":{ 367 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 368 | "path":"/stage/somewhere", 369 | "identity":{ 370 | "sourceIp": "1.2.3.4" 371 | } 372 | } 373 | }"###; 374 | 375 | // GET /somewhere?key=value1+value2 376 | pub(crate) const API_GATEWAY_V2_GET_SOMEWHERE_SPACEQUERY: &str = r###"{ 377 | "headers":{ 378 | "x-forwarded-for":"1.2.3.4", 379 | "x-forwarded-port":"443", 380 | "x-forwarded-proto":"https" 381 | }, 382 | "isBase64Encoded":false, 383 | "rawPath":"/somewhere", 384 | "rawQueryString":"key=value1+value2", 385 | "requestContext":{ 386 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 387 | "http":{ 388 | "method":"GET", 389 | "sourceIp":"1.2.3.4" 390 | } 391 | }, 392 | "version":"2.0" 393 | }"###; 394 | pub(crate) const API_GATEWAY_REST_GET_SOMEWHERE_SPACEQUERY: &str = r###"{ 395 | "body":null, 396 | "path":"/somewhere", 397 | "httpMethod":"GET", 398 | "headers":{ 399 | "x-forwarded-for":"1.2.3.4", 400 | "x-forwarded-port":"443", 401 | "x-forwarded-proto":"https" 402 | }, 403 | "multiValueHeaders":{ 404 | "x-forwarded-for":["1.2.3.4"], 405 | "x-forwarded-port":["443"], 406 | "x-forwarded-proto":["https"] 407 | }, 408 | "queryStringParameters":{ 409 | "key":"value1 value2" 410 | }, 411 | "multiValueQueryStringParameters":{ 412 | "key":["value1 value2"] 413 | }, 414 | "requestContext":{ 415 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 416 | "path":"/stage/somewhere", 417 | "identity":{ 418 | "sourceIp": "1.2.3.4" 419 | } 420 | } 421 | }"###; 422 | 423 | // GET /somewhere?key=%E6%97%A5%E6%9C%AC%E8%AA%9E 424 | pub(crate) const API_GATEWAY_V2_GET_SOMEWHERE_UTF8QUERY: &str = r###"{ 425 | "headers":{ 426 | "x-forwarded-for":"1.2.3.4", 427 | "x-forwarded-port":"443", 428 | "x-forwarded-proto":"https" 429 | }, 430 | "isBase64Encoded":false, 431 | "rawPath":"/somewhere", 432 | "rawQueryString":"key=%E6%97%A5%E6%9C%AC%E8%AA%9E", 433 | "requestContext":{ 434 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 435 | "http":{ 436 | "method":"GET", 437 | "sourceIp":"1.2.3.4" 438 | } 439 | }, 440 | "version":"2.0" 441 | }"###; 442 | pub(crate) const API_GATEWAY_REST_GET_SOMEWHERE_UTF8QUERY: &str = r###"{ 443 | "body":null, 444 | "path":"/somewhere", 445 | "httpMethod":"GET", 446 | "headers":{ 447 | "x-forwarded-for":"1.2.3.4", 448 | "x-forwarded-port":"443", 449 | "x-forwarded-proto":"https" 450 | }, 451 | "multiValueHeaders":{ 452 | "x-forwarded-for":["1.2.3.4"], 453 | "x-forwarded-port":["443"], 454 | "x-forwarded-proto":["https"] 455 | }, 456 | "queryStringParameters":{ 457 | "key":"日本語" 458 | }, 459 | "multiValueQueryStringParameters":{ 460 | "key":["日本語"] 461 | }, 462 | "requestContext":{ 463 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 464 | "path":"/stage/somewhere", 465 | "identity":{ 466 | "sourceIp": "1.2.3.4" 467 | } 468 | } 469 | }"###; 470 | 471 | // 472 | // IPv6 source IP 473 | // 474 | #[allow(dead_code)] 475 | pub(crate) const API_GATEWAY_V2_GET_REMOTE_IPV6: &str = r###"{ 476 | "headers":{ 477 | "x-forwarded-for":"2404:6800:400a:80c::2004", 478 | "x-forwarded-port":"443", 479 | "x-forwarded-proto":"https" 480 | }, 481 | "isBase64Encoded":false, 482 | "rawPath":"/", 483 | "rawQueryString":"", 484 | "requestContext":{ 485 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 486 | "http":{ 487 | "method":"GET", 488 | "sourceIp":"2404:6800:400a:80c::2004" 489 | } 490 | }, 491 | "version":"2.0" 492 | }"###; 493 | pub(crate) const API_GATEWAY_REST_GET_REMOTE_IPV6: &str = r###"{ 494 | "body":null, 495 | "path":"/", 496 | "httpMethod":"GET", 497 | "headers":{ 498 | "x-forwarded-for":"2404:6800:400a:80c::2004", 499 | "x-forwarded-port":"443", 500 | "x-forwarded-proto":"https" 501 | }, 502 | "multiValueHeaders":{ 503 | "x-forwarded-for":["2404:6800:400a:80c::2004"], 504 | "x-forwarded-port":["443"], 505 | "x-forwarded-proto":["https"] 506 | }, 507 | "queryStringParameters":null, 508 | "multiValueQueryStringParameters":null, 509 | "requestContext":{ 510 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 511 | "path":"/stage/", 512 | "identity":{ 513 | "sourceIp": "2404:6800:400a:80c::2004" 514 | } 515 | } 516 | }"###; 517 | 518 | // 519 | // Cookie test 520 | // 521 | 522 | // Cookie: cookie1=value1 523 | pub(crate) const API_GATEWAY_V2_GET_ONE_COOKIE: &str = r###"{ 524 | "cookies":["cookie1=value1"], 525 | "headers":{ 526 | "x-forwarded-for":"1.2.3.4", 527 | "x-forwarded-port":"443", 528 | "x-forwarded-proto":"https" 529 | }, 530 | "isBase64Encoded":false, 531 | "rawPath":"/", 532 | "rawQueryString":"", 533 | "requestContext":{ 534 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 535 | "http":{ 536 | "method":"GET", 537 | "sourceIp":"1.2.3.4" 538 | } 539 | }, 540 | "version":"2.0" 541 | }"###; 542 | pub(crate) const API_GATEWAY_REST_GET_ONE_COOKIE: &str = r###"{ 543 | "body":null, 544 | "path":"/somewhere", 545 | "httpMethod":"GET", 546 | "headers":{ 547 | "cookie":"cookie1=value1", 548 | "x-forwarded-for":"1.2.3.4", 549 | "x-forwarded-port":"443", 550 | "x-forwarded-proto":"https" 551 | }, 552 | "multiValueHeaders":{ 553 | "cookie":["cookie1=value1"], 554 | "x-forwarded-for":["1.2.3.4"], 555 | "x-forwarded-port":["443"], 556 | "x-forwarded-proto":["https"] 557 | }, 558 | "queryStringParameters":null, 559 | "multiValueQueryStringParameters":null, 560 | "requestContext":{ 561 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 562 | "path":"/stage/somewhere", 563 | "identity":{ 564 | "sourceIp": "1.2.3.4" 565 | } 566 | } 567 | }"###; 568 | 569 | // Cookie: cookie1=value1; cookie2=value2 570 | pub(crate) const API_GATEWAY_V2_GET_TWO_COOKIES: &str = r###"{ 571 | "cookies":["cookie1=value1","cookie2=value2"], 572 | "headers":{ 573 | "x-forwarded-for":"1.2.3.4", 574 | "x-forwarded-port":"443", 575 | "x-forwarded-proto":"https" 576 | }, 577 | "isBase64Encoded":false, 578 | "rawPath":"/", 579 | "rawQueryString":"", 580 | "requestContext":{ 581 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 582 | "http":{ 583 | "method":"GET", 584 | "sourceIp":"1.2.3.4" 585 | } 586 | }, 587 | "version":"2.0" 588 | }"###; 589 | pub(crate) const API_GATEWAY_REST_GET_TWO_COOKIES: &str = r###"{ 590 | "body":null, 591 | "path":"/somewhere", 592 | "httpMethod":"GET", 593 | "headers":{ 594 | "cookie":"cookie1=value1; cookie2=value2", 595 | "x-forwarded-for":"1.2.3.4", 596 | "x-forwarded-port":"443", 597 | "x-forwarded-proto":"https" 598 | }, 599 | "multiValueHeaders":{ 600 | "cookie":["cookie1=value1; cookie2=value2"], 601 | "x-forwarded-for":["1.2.3.4"], 602 | "x-forwarded-port":["443"], 603 | "x-forwarded-proto":["https"] 604 | }, 605 | "queryStringParameters":null, 606 | "multiValueQueryStringParameters":null, 607 | "requestContext":{ 608 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 609 | "path":"/stage/somewhere", 610 | "identity":{ 611 | "sourceIp": "1.2.3.4" 612 | } 613 | } 614 | }"###; 615 | 616 | // 617 | // Form POST 618 | // 619 | 620 | // POST /somewhere with key1=value1&key2=value2&Ok=Ok 621 | pub(crate) const API_GATEWAY_V2_POST_FORM_URLENCODED: &str = r###"{ 622 | "body":"key1=value1&key2=value2&Ok=Ok", 623 | "headers":{ 624 | "content-length":"29", 625 | "content-type":"application/x-www-form-urlencoded", 626 | "x-forwarded-for":"1.2.3.4", 627 | "x-forwarded-port":"443", 628 | "x-forwarded-proto":"https" 629 | }, 630 | "isBase64Encoded":false, 631 | "rawPath":"/somewhere", 632 | "rawQueryString":"", 633 | "requestContext":{ 634 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 635 | "http":{ 636 | "method":"POST", 637 | "sourceIp":"1.2.3.4" 638 | } 639 | }, 640 | "version":"2.0" 641 | }"###; 642 | pub(crate) const API_GATEWAY_REST_POST_FORM_URLENCODED: &str = r###"{ 643 | "body":"key1=value1&key2=value2&Ok=Ok", 644 | "isBase64Encoded":false, 645 | "path":"/somewhere", 646 | "httpMethod":"POST", 647 | "headers":{ 648 | "content-length":"29", 649 | "content-type":"application/x-www-form-urlencoded", 650 | "x-forwarded-for":"1.2.3.4", 651 | "x-forwarded-port":"443", 652 | "x-forwarded-proto":"https" 653 | }, 654 | "multiValueHeaders":{ 655 | "content-length":["29"], 656 | "content-type":["application/x-www-form-urlencoded"], 657 | "x-forwarded-for":["1.2.3.4"], 658 | "x-forwarded-port":["443"], 659 | "x-forwarded-proto":["https"] 660 | }, 661 | "queryStringParameters":null, 662 | "multiValueQueryStringParameters":null, 663 | "requestContext":{ 664 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 665 | "path":"/stage/somewhere", 666 | "identity":{ 667 | "sourceIp": "1.2.3.4" 668 | } 669 | } 670 | }"###; 671 | 672 | // POST /somewhere with key1=value1&key2=value2&Ok=Ok, base64 encoded in API gateway 673 | pub(crate) const API_GATEWAY_V2_POST_FORM_URLENCODED_B64: &str = r###"{ 674 | "body":"a2V5MT12YWx1ZTEma2V5Mj12YWx1ZTImT2s9T2s=", 675 | "headers":{ 676 | "content-length":"29", 677 | "content-type":"application/x-www-form-urlencoded", 678 | "x-forwarded-for":"1.2.3.4", 679 | "x-forwarded-port":"443", 680 | "x-forwarded-proto":"https" 681 | }, 682 | "isBase64Encoded":true, 683 | "rawPath":"/somewhere", 684 | "rawQueryString":"", 685 | "requestContext":{ 686 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 687 | "http":{ 688 | "method":"POST", 689 | "sourceIp":"1.2.3.4" 690 | } 691 | }, 692 | "version":"2.0" 693 | }"###; 694 | pub(crate) const API_GATEWAY_REST_POST_FORM_URLENCODED_B64: &str = r###"{ 695 | "body":"a2V5MT12YWx1ZTEma2V5Mj12YWx1ZTImT2s9T2s=", 696 | "isBase64Encoded":true, 697 | "path":"/somewhere", 698 | "httpMethod":"POST", 699 | "headers":{ 700 | "content-length":"29", 701 | "content-type":"application/x-www-form-urlencoded", 702 | "x-forwarded-for":"1.2.3.4", 703 | "x-forwarded-port":"443", 704 | "x-forwarded-proto":"https" 705 | }, 706 | "multiValueHeaders":{ 707 | "content-length":["29"], 708 | "content-type":["application/x-www-form-urlencoded"], 709 | "x-forwarded-for":["1.2.3.4"], 710 | "x-forwarded-port":["443"], 711 | "x-forwarded-proto":["https"] 712 | }, 713 | "queryStringParameters":null, 714 | "multiValueQueryStringParameters":null, 715 | "requestContext":{ 716 | "domainName":"yyyyyyyyyy.execute-api.ap-northeast-1.amazonaws.com", 717 | "path":"/stage/somewhere", 718 | "identity":{ 719 | "sourceIp": "1.2.3.4" 720 | } 721 | } 722 | }"###; 723 | --------------------------------------------------------------------------------