├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── README.md ├── changelog.md ├── src ├── builder.rs ├── handlers.rs ├── lib.rs ├── path.rs └── route │ ├── builder.rs │ ├── mod.rs │ └── route_impl.rs ├── test-server └── main.rs └── tests └── basic.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *.iml 3 | 4 | target/ 5 | **/*.rs.bk -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | cache: cargo 7 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "aho-corasick" 5 | version = "0.6.10" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "81ce3d38065e618af2d7b77e10c5ad9a069859b4be3c2250f674af3840d9c8a5" 8 | dependencies = [ 9 | "memchr", 10 | ] 11 | 12 | [[package]] 13 | name = "arrayvec" 14 | version = "0.4.10" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "92c7fb76bc8826a8b33b4ee5bb07a247a81e76764ab4d55e8f73e3a4d8808c71" 17 | dependencies = [ 18 | "nodrop", 19 | ] 20 | 21 | [[package]] 22 | name = "autocfg" 23 | version = "0.1.2" 24 | source = "registry+https://github.com/rust-lang/crates.io-index" 25 | checksum = "a6d640bee2da49f60a4068a7fae53acde8982514ab7bae8b8cea9e88cbcfd799" 26 | 27 | [[package]] 28 | name = "bitflags" 29 | version = "1.0.4" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "228047a76f468627ca71776ecdebd732a3423081fcf5125585bcd7c49886ce12" 32 | 33 | [[package]] 34 | name = "byteorder" 35 | version = "1.3.1" 36 | source = "registry+https://github.com/rust-lang/crates.io-index" 37 | checksum = "a019b10a2a7cdeb292db131fc8113e57ea2a908f6e7894b0c3c671893b65dbeb" 38 | 39 | [[package]] 40 | name = "bytes" 41 | version = "0.4.11" 42 | source = "registry+https://github.com/rust-lang/crates.io-index" 43 | checksum = "40ade3d27603c2cb345eb0912aec461a6dec7e06a4ae48589904e808335c7afa" 44 | dependencies = [ 45 | "byteorder", 46 | "either", 47 | "iovec", 48 | ] 49 | 50 | [[package]] 51 | name = "cfg-if" 52 | version = "0.1.6" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "082bb9b28e00d3c9d39cc03e64ce4cea0f1bb9b3fde493f0cbc008472d22bdf4" 55 | 56 | [[package]] 57 | name = "cloudabi" 58 | version = "0.0.3" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 61 | dependencies = [ 62 | "bitflags", 63 | ] 64 | 65 | [[package]] 66 | name = "crossbeam" 67 | version = "0.6.0" 68 | source = "registry+https://github.com/rust-lang/crates.io-index" 69 | checksum = "ad4c7ea749d9fb09e23c5cb17e3b70650860553a0e2744e38446b1803bf7db94" 70 | dependencies = [ 71 | "cfg-if", 72 | "crossbeam-channel", 73 | "crossbeam-deque", 74 | "crossbeam-epoch", 75 | "crossbeam-utils", 76 | "lazy_static", 77 | "num_cpus", 78 | "parking_lot", 79 | ] 80 | 81 | [[package]] 82 | name = "crossbeam-channel" 83 | version = "0.3.8" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | checksum = "0f0ed1a4de2235cabda8558ff5840bffb97fcb64c97827f354a451307df5f72b" 86 | dependencies = [ 87 | "crossbeam-utils", 88 | "smallvec", 89 | ] 90 | 91 | [[package]] 92 | name = "crossbeam-deque" 93 | version = "0.6.3" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "05e44b8cf3e1a625844d1750e1f7820da46044ff6d28f4d43e455ba3e5bb2c13" 96 | dependencies = [ 97 | "crossbeam-epoch", 98 | "crossbeam-utils", 99 | ] 100 | 101 | [[package]] 102 | name = "crossbeam-epoch" 103 | version = "0.7.1" 104 | source = "registry+https://github.com/rust-lang/crates.io-index" 105 | checksum = "04c9e3102cc2d69cd681412141b390abd55a362afc1540965dad0ad4d34280b4" 106 | dependencies = [ 107 | "arrayvec", 108 | "cfg-if", 109 | "crossbeam-utils", 110 | "lazy_static", 111 | "memoffset", 112 | "scopeguard", 113 | ] 114 | 115 | [[package]] 116 | name = "crossbeam-utils" 117 | version = "0.6.5" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "f8306fcef4a7b563b76b7dd949ca48f52bc1141aa067d2ea09565f3e2652aa5c" 120 | dependencies = [ 121 | "cfg-if", 122 | "lazy_static", 123 | ] 124 | 125 | [[package]] 126 | name = "either" 127 | version = "1.5.3" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 130 | 131 | [[package]] 132 | name = "fnv" 133 | version = "1.0.6" 134 | source = "registry+https://github.com/rust-lang/crates.io-index" 135 | checksum = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3" 136 | 137 | [[package]] 138 | name = "fuchsia-cprng" 139 | version = "0.1.1" 140 | source = "registry+https://github.com/rust-lang/crates.io-index" 141 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 142 | 143 | [[package]] 144 | name = "fuchsia-zircon" 145 | version = "0.3.3" 146 | source = "registry+https://github.com/rust-lang/crates.io-index" 147 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 148 | dependencies = [ 149 | "bitflags", 150 | "fuchsia-zircon-sys", 151 | ] 152 | 153 | [[package]] 154 | name = "fuchsia-zircon-sys" 155 | version = "0.3.3" 156 | source = "registry+https://github.com/rust-lang/crates.io-index" 157 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 158 | 159 | [[package]] 160 | name = "futures" 161 | version = "0.1.25" 162 | source = "registry+https://github.com/rust-lang/crates.io-index" 163 | checksum = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" 164 | 165 | [[package]] 166 | name = "futures-cpupool" 167 | version = "0.1.8" 168 | source = "registry+https://github.com/rust-lang/crates.io-index" 169 | checksum = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" 170 | dependencies = [ 171 | "futures", 172 | "num_cpus", 173 | ] 174 | 175 | [[package]] 176 | name = "h2" 177 | version = "0.1.16" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | checksum = "ddb2b25a33e231484694267af28fec74ac63b5ccf51ee2065a5e313b834d836e" 180 | dependencies = [ 181 | "byteorder", 182 | "bytes", 183 | "fnv", 184 | "futures", 185 | "http", 186 | "indexmap", 187 | "log", 188 | "slab", 189 | "string", 190 | "tokio-io", 191 | ] 192 | 193 | [[package]] 194 | name = "http" 195 | version = "0.1.16" 196 | source = "registry+https://github.com/rust-lang/crates.io-index" 197 | checksum = "fe67e3678f2827030e89cc4b9e7ecd16d52f132c0b940ab5005f88e821500f6a" 198 | dependencies = [ 199 | "bytes", 200 | "fnv", 201 | "itoa", 202 | ] 203 | 204 | [[package]] 205 | name = "http-body" 206 | version = "0.1.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "6741c859c1b2463a423a1dbce98d418e6c3c3fc720fb0d45528657320920292d" 209 | dependencies = [ 210 | "bytes", 211 | "futures", 212 | "http", 213 | "tokio-buf", 214 | ] 215 | 216 | [[package]] 217 | name = "httparse" 218 | version = "1.3.3" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "e8734b0cfd3bc3e101ec59100e101c2eecd19282202e87808b3037b442777a83" 221 | 222 | [[package]] 223 | name = "hyper" 224 | version = "0.12.35" 225 | source = "registry+https://github.com/rust-lang/crates.io-index" 226 | checksum = "9dbe6ed1438e1f8ad955a4701e9a944938e9519f6888d12d8558b645e247d5f6" 227 | dependencies = [ 228 | "bytes", 229 | "futures", 230 | "futures-cpupool", 231 | "h2", 232 | "http", 233 | "http-body", 234 | "httparse", 235 | "iovec", 236 | "itoa", 237 | "log", 238 | "net2", 239 | "rustc_version", 240 | "time", 241 | "tokio", 242 | "tokio-buf", 243 | "tokio-executor", 244 | "tokio-io", 245 | "tokio-reactor", 246 | "tokio-tcp", 247 | "tokio-threadpool", 248 | "tokio-timer", 249 | "want", 250 | ] 251 | 252 | [[package]] 253 | name = "hyper-router" 254 | version = "0.5.0" 255 | dependencies = [ 256 | "futures", 257 | "hyper", 258 | "regex", 259 | ] 260 | 261 | [[package]] 262 | name = "indexmap" 263 | version = "1.0.2" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d" 266 | 267 | [[package]] 268 | name = "iovec" 269 | version = "0.1.2" 270 | source = "registry+https://github.com/rust-lang/crates.io-index" 271 | checksum = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08" 272 | dependencies = [ 273 | "libc", 274 | "winapi 0.2.8", 275 | ] 276 | 277 | [[package]] 278 | name = "itoa" 279 | version = "0.4.3" 280 | source = "registry+https://github.com/rust-lang/crates.io-index" 281 | checksum = "1306f3464951f30e30d12373d31c79fbd52d236e5e896fd92f96ec7babbbe60b" 282 | 283 | [[package]] 284 | name = "kernel32-sys" 285 | version = "0.2.2" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 288 | dependencies = [ 289 | "winapi 0.2.8", 290 | "winapi-build", 291 | ] 292 | 293 | [[package]] 294 | name = "lazy_static" 295 | version = "1.2.0" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "a374c89b9db55895453a74c1e38861d9deec0b01b405a82516e9d5de4820dea1" 298 | 299 | [[package]] 300 | name = "lazycell" 301 | version = "1.2.1" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "b294d6fa9ee409a054354afc4352b0b9ef7ca222c69b8812cbea9e7d2bf3783f" 304 | 305 | [[package]] 306 | name = "libc" 307 | version = "0.2.49" 308 | source = "registry+https://github.com/rust-lang/crates.io-index" 309 | checksum = "413f3dfc802c5dc91dc570b05125b6cda9855edfaa9825c9849807876376e70e" 310 | 311 | [[package]] 312 | name = "lock_api" 313 | version = "0.1.5" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c" 316 | dependencies = [ 317 | "owning_ref", 318 | "scopeguard", 319 | ] 320 | 321 | [[package]] 322 | name = "log" 323 | version = "0.4.6" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" 326 | dependencies = [ 327 | "cfg-if", 328 | ] 329 | 330 | [[package]] 331 | name = "maybe-uninit" 332 | version = "2.0.0" 333 | source = "registry+https://github.com/rust-lang/crates.io-index" 334 | checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00" 335 | 336 | [[package]] 337 | name = "memchr" 338 | version = "2.2.0" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39" 341 | 342 | [[package]] 343 | name = "memoffset" 344 | version = "0.2.1" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" 347 | 348 | [[package]] 349 | name = "mio" 350 | version = "0.6.16" 351 | source = "registry+https://github.com/rust-lang/crates.io-index" 352 | checksum = "71646331f2619b1026cc302f87a2b8b648d5c6dd6937846a16cc8ce0f347f432" 353 | dependencies = [ 354 | "fuchsia-zircon", 355 | "fuchsia-zircon-sys", 356 | "iovec", 357 | "kernel32-sys", 358 | "lazycell", 359 | "libc", 360 | "log", 361 | "miow", 362 | "net2", 363 | "slab", 364 | "winapi 0.2.8", 365 | ] 366 | 367 | [[package]] 368 | name = "miow" 369 | version = "0.2.1" 370 | source = "registry+https://github.com/rust-lang/crates.io-index" 371 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 372 | dependencies = [ 373 | "kernel32-sys", 374 | "net2", 375 | "winapi 0.2.8", 376 | "ws2_32-sys", 377 | ] 378 | 379 | [[package]] 380 | name = "net2" 381 | version = "0.2.33" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" 384 | dependencies = [ 385 | "cfg-if", 386 | "libc", 387 | "winapi 0.3.6", 388 | ] 389 | 390 | [[package]] 391 | name = "nodrop" 392 | version = "0.1.13" 393 | source = "registry+https://github.com/rust-lang/crates.io-index" 394 | checksum = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" 395 | 396 | [[package]] 397 | name = "num_cpus" 398 | version = "1.10.0" 399 | source = "registry+https://github.com/rust-lang/crates.io-index" 400 | checksum = "1a23f0ed30a54abaa0c7e83b1d2d87ada7c3c23078d1d87815af3e3b6385fbba" 401 | dependencies = [ 402 | "libc", 403 | ] 404 | 405 | [[package]] 406 | name = "owning_ref" 407 | version = "0.4.0" 408 | source = "registry+https://github.com/rust-lang/crates.io-index" 409 | checksum = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" 410 | dependencies = [ 411 | "stable_deref_trait", 412 | ] 413 | 414 | [[package]] 415 | name = "parking_lot" 416 | version = "0.7.1" 417 | source = "registry+https://github.com/rust-lang/crates.io-index" 418 | checksum = "ab41b4aed082705d1056416ae4468b6ea99d52599ecf3169b00088d43113e337" 419 | dependencies = [ 420 | "lock_api", 421 | "parking_lot_core", 422 | ] 423 | 424 | [[package]] 425 | name = "parking_lot_core" 426 | version = "0.4.0" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" 429 | dependencies = [ 430 | "libc", 431 | "rand", 432 | "rustc_version", 433 | "smallvec", 434 | "winapi 0.3.6", 435 | ] 436 | 437 | [[package]] 438 | name = "rand" 439 | version = "0.6.5" 440 | source = "registry+https://github.com/rust-lang/crates.io-index" 441 | checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 442 | dependencies = [ 443 | "autocfg", 444 | "libc", 445 | "rand_chacha", 446 | "rand_core 0.4.0", 447 | "rand_hc", 448 | "rand_isaac", 449 | "rand_jitter", 450 | "rand_os", 451 | "rand_pcg", 452 | "rand_xorshift", 453 | "winapi 0.3.6", 454 | ] 455 | 456 | [[package]] 457 | name = "rand_chacha" 458 | version = "0.1.1" 459 | source = "registry+https://github.com/rust-lang/crates.io-index" 460 | checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 461 | dependencies = [ 462 | "autocfg", 463 | "rand_core 0.3.1", 464 | ] 465 | 466 | [[package]] 467 | name = "rand_core" 468 | version = "0.3.1" 469 | source = "registry+https://github.com/rust-lang/crates.io-index" 470 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 471 | dependencies = [ 472 | "rand_core 0.4.0", 473 | ] 474 | 475 | [[package]] 476 | name = "rand_core" 477 | version = "0.4.0" 478 | source = "registry+https://github.com/rust-lang/crates.io-index" 479 | checksum = "d0e7a549d590831370895ab7ba4ea0c1b6b011d106b5ff2da6eee112615e6dc0" 480 | 481 | [[package]] 482 | name = "rand_hc" 483 | version = "0.1.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 486 | dependencies = [ 487 | "rand_core 0.3.1", 488 | ] 489 | 490 | [[package]] 491 | name = "rand_isaac" 492 | version = "0.1.1" 493 | source = "registry+https://github.com/rust-lang/crates.io-index" 494 | checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 495 | dependencies = [ 496 | "rand_core 0.3.1", 497 | ] 498 | 499 | [[package]] 500 | name = "rand_jitter" 501 | version = "0.1.3" 502 | source = "registry+https://github.com/rust-lang/crates.io-index" 503 | checksum = "7b9ea758282efe12823e0d952ddb269d2e1897227e464919a554f2a03ef1b832" 504 | dependencies = [ 505 | "libc", 506 | "rand_core 0.4.0", 507 | "winapi 0.3.6", 508 | ] 509 | 510 | [[package]] 511 | name = "rand_os" 512 | version = "0.1.2" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "b7c690732391ae0abafced5015ffb53656abfaec61b342290e5eb56b286a679d" 515 | dependencies = [ 516 | "cloudabi", 517 | "fuchsia-cprng", 518 | "libc", 519 | "rand_core 0.4.0", 520 | "rdrand", 521 | "winapi 0.3.6", 522 | ] 523 | 524 | [[package]] 525 | name = "rand_pcg" 526 | version = "0.1.2" 527 | source = "registry+https://github.com/rust-lang/crates.io-index" 528 | checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 529 | dependencies = [ 530 | "autocfg", 531 | "rand_core 0.4.0", 532 | ] 533 | 534 | [[package]] 535 | name = "rand_xorshift" 536 | version = "0.1.1" 537 | source = "registry+https://github.com/rust-lang/crates.io-index" 538 | checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 539 | dependencies = [ 540 | "rand_core 0.3.1", 541 | ] 542 | 543 | [[package]] 544 | name = "rdrand" 545 | version = "0.4.0" 546 | source = "registry+https://github.com/rust-lang/crates.io-index" 547 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 548 | dependencies = [ 549 | "rand_core 0.3.1", 550 | ] 551 | 552 | [[package]] 553 | name = "redox_syscall" 554 | version = "0.1.51" 555 | source = "registry+https://github.com/rust-lang/crates.io-index" 556 | checksum = "423e376fffca3dfa06c9e9790a9ccd282fafb3cc6e6397d01dbf64f9bacc6b85" 557 | 558 | [[package]] 559 | name = "regex" 560 | version = "0.2.11" 561 | source = "registry+https://github.com/rust-lang/crates.io-index" 562 | checksum = "9329abc99e39129fcceabd24cf5d85b4671ef7c29c50e972bc5afe32438ec384" 563 | dependencies = [ 564 | "aho-corasick", 565 | "memchr", 566 | "regex-syntax", 567 | "thread_local", 568 | "utf8-ranges", 569 | ] 570 | 571 | [[package]] 572 | name = "regex-syntax" 573 | version = "0.5.6" 574 | source = "registry+https://github.com/rust-lang/crates.io-index" 575 | checksum = "7d707a4fa2637f2dca2ef9fd02225ec7661fe01a53623c1e6515b6916511f7a7" 576 | dependencies = [ 577 | "ucd-util", 578 | ] 579 | 580 | [[package]] 581 | name = "rustc_version" 582 | version = "0.2.3" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" 585 | dependencies = [ 586 | "semver", 587 | ] 588 | 589 | [[package]] 590 | name = "scopeguard" 591 | version = "0.3.3" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "94258f53601af11e6a49f722422f6e3425c52b06245a5cf9bc09908b174f5e27" 594 | 595 | [[package]] 596 | name = "semver" 597 | version = "0.9.0" 598 | source = "registry+https://github.com/rust-lang/crates.io-index" 599 | checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" 600 | dependencies = [ 601 | "semver-parser", 602 | ] 603 | 604 | [[package]] 605 | name = "semver-parser" 606 | version = "0.7.0" 607 | source = "registry+https://github.com/rust-lang/crates.io-index" 608 | checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" 609 | 610 | [[package]] 611 | name = "slab" 612 | version = "0.4.2" 613 | source = "registry+https://github.com/rust-lang/crates.io-index" 614 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 615 | 616 | [[package]] 617 | name = "smallvec" 618 | version = "0.6.14" 619 | source = "registry+https://github.com/rust-lang/crates.io-index" 620 | checksum = "b97fcaeba89edba30f044a10c6a3cc39df9c3f17d7cd829dd1446cab35f890e0" 621 | dependencies = [ 622 | "maybe-uninit", 623 | ] 624 | 625 | [[package]] 626 | name = "stable_deref_trait" 627 | version = "1.1.1" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" 630 | 631 | [[package]] 632 | name = "string" 633 | version = "0.1.3" 634 | source = "registry+https://github.com/rust-lang/crates.io-index" 635 | checksum = "b639411d0b9c738748b5397d5ceba08e648f4f1992231aa859af1a017f31f60b" 636 | 637 | [[package]] 638 | name = "thread_local" 639 | version = "0.3.6" 640 | source = "registry+https://github.com/rust-lang/crates.io-index" 641 | checksum = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b" 642 | dependencies = [ 643 | "lazy_static", 644 | ] 645 | 646 | [[package]] 647 | name = "time" 648 | version = "0.1.42" 649 | source = "registry+https://github.com/rust-lang/crates.io-index" 650 | checksum = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f" 651 | dependencies = [ 652 | "libc", 653 | "redox_syscall", 654 | "winapi 0.3.6", 655 | ] 656 | 657 | [[package]] 658 | name = "tokio" 659 | version = "0.1.15" 660 | source = "registry+https://github.com/rust-lang/crates.io-index" 661 | checksum = "e0500b88064f08bebddd0c0bed39e19f5c567a5f30975bee52b0c0d3e2eeb38c" 662 | dependencies = [ 663 | "bytes", 664 | "futures", 665 | "mio", 666 | "num_cpus", 667 | "tokio-current-thread", 668 | "tokio-executor", 669 | "tokio-io", 670 | "tokio-reactor", 671 | "tokio-threadpool", 672 | "tokio-timer", 673 | ] 674 | 675 | [[package]] 676 | name = "tokio-buf" 677 | version = "0.1.1" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "8fb220f46c53859a4b7ec083e41dec9778ff0b1851c0942b211edb89e0ccdc46" 680 | dependencies = [ 681 | "bytes", 682 | "either", 683 | "futures", 684 | ] 685 | 686 | [[package]] 687 | name = "tokio-current-thread" 688 | version = "0.1.4" 689 | source = "registry+https://github.com/rust-lang/crates.io-index" 690 | checksum = "331c8acc267855ec06eb0c94618dcbbfea45bed2d20b77252940095273fb58f6" 691 | dependencies = [ 692 | "futures", 693 | "tokio-executor", 694 | ] 695 | 696 | [[package]] 697 | name = "tokio-executor" 698 | version = "0.1.6" 699 | source = "registry+https://github.com/rust-lang/crates.io-index" 700 | checksum = "30c6dbf2d1ad1de300b393910e8a3aa272b724a400b6531da03eed99e329fbf0" 701 | dependencies = [ 702 | "crossbeam-utils", 703 | "futures", 704 | ] 705 | 706 | [[package]] 707 | name = "tokio-io" 708 | version = "0.1.11" 709 | source = "registry+https://github.com/rust-lang/crates.io-index" 710 | checksum = "b53aeb9d3f5ccf2ebb29e19788f96987fa1355f8fe45ea193928eaaaf3ae820f" 711 | dependencies = [ 712 | "bytes", 713 | "futures", 714 | "log", 715 | ] 716 | 717 | [[package]] 718 | name = "tokio-reactor" 719 | version = "0.1.8" 720 | source = "registry+https://github.com/rust-lang/crates.io-index" 721 | checksum = "afbcdb0f0d2a1e4c440af82d7bbf0bf91a8a8c0575bcd20c05d15be7e9d3a02f" 722 | dependencies = [ 723 | "crossbeam-utils", 724 | "futures", 725 | "lazy_static", 726 | "log", 727 | "mio", 728 | "num_cpus", 729 | "parking_lot", 730 | "slab", 731 | "tokio-executor", 732 | "tokio-io", 733 | ] 734 | 735 | [[package]] 736 | name = "tokio-tcp" 737 | version = "0.1.3" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "1d14b10654be682ac43efee27401d792507e30fd8d26389e1da3b185de2e4119" 740 | dependencies = [ 741 | "bytes", 742 | "futures", 743 | "iovec", 744 | "mio", 745 | "tokio-io", 746 | "tokio-reactor", 747 | ] 748 | 749 | [[package]] 750 | name = "tokio-threadpool" 751 | version = "0.1.11" 752 | source = "registry+https://github.com/rust-lang/crates.io-index" 753 | checksum = "c3fd86cb15547d02daa2b21aadaf4e37dee3368df38a526178a5afa3c034d2fb" 754 | dependencies = [ 755 | "crossbeam", 756 | "crossbeam-channel", 757 | "crossbeam-deque", 758 | "crossbeam-utils", 759 | "futures", 760 | "log", 761 | "num_cpus", 762 | "rand", 763 | "slab", 764 | "tokio-executor", 765 | ] 766 | 767 | [[package]] 768 | name = "tokio-timer" 769 | version = "0.2.10" 770 | source = "registry+https://github.com/rust-lang/crates.io-index" 771 | checksum = "2910970404ba6fa78c5539126a9ae2045d62e3713041e447f695f41405a120c6" 772 | dependencies = [ 773 | "crossbeam-utils", 774 | "futures", 775 | "slab", 776 | "tokio-executor", 777 | ] 778 | 779 | [[package]] 780 | name = "try-lock" 781 | version = "0.2.2" 782 | source = "registry+https://github.com/rust-lang/crates.io-index" 783 | checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" 784 | 785 | [[package]] 786 | name = "ucd-util" 787 | version = "0.1.3" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "535c204ee4d8434478593480b8f86ab45ec9aae0e83c568ca81abf0fd0e88f86" 790 | 791 | [[package]] 792 | name = "utf8-ranges" 793 | version = "1.0.2" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "796f7e48bef87609f7ade7e06495a87d5cd06c7866e6a5cbfceffc558a243737" 796 | 797 | [[package]] 798 | name = "want" 799 | version = "0.2.0" 800 | source = "registry+https://github.com/rust-lang/crates.io-index" 801 | checksum = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230" 802 | dependencies = [ 803 | "futures", 804 | "log", 805 | "try-lock", 806 | ] 807 | 808 | [[package]] 809 | name = "winapi" 810 | version = "0.2.8" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 813 | 814 | [[package]] 815 | name = "winapi" 816 | version = "0.3.6" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "92c1eb33641e276cfa214a0522acad57be5c56b10cb348b3c5117db75f3ac4b0" 819 | dependencies = [ 820 | "winapi-i686-pc-windows-gnu", 821 | "winapi-x86_64-pc-windows-gnu", 822 | ] 823 | 824 | [[package]] 825 | name = "winapi-build" 826 | version = "0.1.1" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 829 | 830 | [[package]] 831 | name = "winapi-i686-pc-windows-gnu" 832 | version = "0.4.0" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 835 | 836 | [[package]] 837 | name = "winapi-x86_64-pc-windows-gnu" 838 | version = "0.4.0" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 841 | 842 | [[package]] 843 | name = "ws2_32-sys" 844 | version = "0.2.1" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 847 | dependencies = [ 848 | "winapi 0.2.8", 849 | "winapi-build", 850 | ] 851 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyper-router" 3 | version = "0.5.0" 4 | authors = ["Marcin Radoszewski ", "Alexander Mescheryakov "] 5 | description = "Simple routing middleware for Hyper http library." 6 | repository = "https://github.com/marad/hyper-router" 7 | keywords = ["hyper", "router", "routing", "middleware"] 8 | documentation = "https://docs.rs/hyper-router/latest/hyper_router/" 9 | license = "MIT" 10 | edition = "2018" 11 | 12 | [[bin]] 13 | name = "test-server" 14 | path = "test-server/main.rs" 15 | 16 | [lib] 17 | name = "hyper_router" 18 | path = "src/lib.rs" 19 | 20 | [dependencies] 21 | futures = "^0.1" 22 | hyper = "^0.12" 23 | regex = "^0.2" 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Hyper Router [![Build Status](https://travis-ci.com/marad/hyper-router.svg?branch=master)](https://travis-ci.com/marad/hyper-router) 2 | 3 | This cargo is a small extension to the great Hyper HTTP library. It basically is 4 | adds the ability to define routes to request handlers and then query for the handlers 5 | by request path. 6 | 7 | [API Documentation](https://docs.rs/hyper-router/latest/hyper_router/) 8 | 9 | ## Usage 10 | 11 | To use the library just add: 12 | 13 | ```toml 14 | hyper = "^0.12" 15 | hyper-router = "^0.5" 16 | ``` 17 | 18 | to your dependencies. 19 | 20 | ```rust 21 | extern crate hyper; 22 | extern crate hyper_router; 23 | 24 | use hyper::header::{CONTENT_LENGTH, CONTENT_TYPE}; 25 | use hyper::{Request, Response, Body, Method}; 26 | use hyper::server::Server; 27 | use hyper::rt::Future; 28 | use hyper_router::{Route, RouterBuilder, RouterService}; 29 | 30 | fn basic_handler(_: Request) -> Response { 31 | let body = "Hello World"; 32 | Response::builder() 33 | .header(CONTENT_LENGTH, body.len() as u64) 34 | .header(CONTENT_TYPE, "text/plain") 35 | .body(Body::from(body)) 36 | .expect("Failed to construct the response") 37 | } 38 | 39 | fn router_service() -> Result { 40 | let router = RouterBuilder::new() 41 | .add(Route::get("/greet").using(basic_handler)) 42 | .build(); 43 | 44 | Ok(RouterService::new(router)) 45 | } 46 | 47 | fn main() { 48 | let addr = "0.0.0.0:8080".parse().unwrap(); 49 | let server = Server::bind(&addr) 50 | .serve(router_service) 51 | .map_err(|e| eprintln!("server error: {}", e)); 52 | 53 | hyper::rt::run(server) 54 | } 55 | ``` 56 | 57 | This code will start Hyper server and add use router to find handlers for request. 58 | We create the `Route` so that when we visit path `/greet` the `basic_handler` handler 59 | will be called. 60 | 61 | ## Things to note 62 | 63 | * you can specify paths as regular expressions so you can match every path you please. 64 | * If you have request matching multiple paths the one that was first `add`ed will be chosen. 65 | * ~~This library is in an early stage of development so there may be breaking changes comming.~~ - 66 | it seems that the library is quite popular so I'm not going to do compatibility breaking changes. 67 | 68 | # Further Development 69 | 70 | * add the ability to distinguish requests by query parameters. 71 | 72 | # Waiting for your feedback 73 | 74 | I've created this little tool to help myself learn Rust and to avoid using big frameworks 75 | like Iron or rustful. I just want to keep things simple. 76 | 77 | Obviously I could make some errors or bad design choices so I'm waiting for your feedback! 78 | Please contact me at moriturius at GMail. You may also create an issue at [project's bug tracker](https://github.com/marad/hyper-router/issues). 79 | 80 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.5 4 | - updated for hyper 0.12 5 | 6 | ## v0.4 7 | - updated for hyper 0.11 8 | 9 | ## v0.1.3 10 | - updated for hyper 0.9.10 11 | 12 | ## v0.1.2 13 | - [cardoe](https://github.com/cardoe) added support for method OPTIONS, HEAD, TRACE and CONNECT 14 | 15 | ## v0.1.1 16 | - [clonejo](https://github.com/clonejo) updated the code to work with hyper 0.7 17 | 18 | ## v0.1.0 19 | - API changes 20 | 21 | ## v0.0.1 22 | - initial version 23 | 24 | ## v0.0.2 25 | * Added link to documentation at crates.io 26 | * Changed route creation API (introduced route builder) 27 | -------------------------------------------------------------------------------- /src/builder.rs: -------------------------------------------------------------------------------- 1 | use super::Route; 2 | use super::Router; 3 | 4 | /// Builder for a router 5 | /// 6 | /// Example usage: 7 | /// 8 | #[derive(Debug, Default)] 9 | pub struct RouterBuilder { 10 | routes: Vec, 11 | } 12 | 13 | impl RouterBuilder { 14 | pub fn new() -> RouterBuilder { 15 | RouterBuilder { routes: vec![] } 16 | } 17 | 18 | /// Adds new `Route` for `Router` that is being built. 19 | /// 20 | /// Example: 21 | /// 22 | /// ```ignore 23 | /// use hyper::server::{Request, Response}; 24 | /// use hyper_router::{Route, RouterBuilder}; 25 | /// 26 | /// fn some_handler(_: Request) -> Response { 27 | /// // do something 28 | /// } 29 | /// 30 | /// RouterBuilder::new().add(Route::get(r"/person/\d+").using(some_handler)); 31 | /// ``` 32 | #[allow(clippy::should_implement_trait)] 33 | pub fn add(mut self, route: Route) -> RouterBuilder { 34 | self.routes.push(route); 35 | self 36 | } 37 | 38 | pub fn build(self) -> Router { 39 | Router { 40 | routes: self.routes, 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/handlers.rs: -------------------------------------------------------------------------------- 1 | use hyper::header::{CONTENT_LENGTH, CONTENT_TYPE}; 2 | use hyper::{Body, Request, Response, StatusCode}; 3 | 4 | pub fn default_404_handler(_: Request) -> Response { 5 | let body = "page not found"; 6 | make_response(&body, StatusCode::NOT_FOUND) 7 | } 8 | 9 | pub fn method_not_supported_handler(_: Request) -> Response { 10 | let body = "method not supported"; 11 | make_response(&body, StatusCode::METHOD_NOT_ALLOWED) 12 | } 13 | 14 | pub fn internal_server_error_handler(_: Request) -> Response { 15 | let body = "internal server error"; 16 | make_response(&body, StatusCode::INTERNAL_SERVER_ERROR) 17 | } 18 | 19 | pub fn not_implemented_handler(_: Request) -> Response { 20 | let body = "not implemented"; 21 | make_response(&body, StatusCode::NOT_IMPLEMENTED) 22 | } 23 | 24 | fn make_response(body: &'static str, status: StatusCode) -> Response { 25 | Response::builder() 26 | .status(status) 27 | .header(CONTENT_LENGTH, body.len() as u64) 28 | .header(CONTENT_TYPE, "text/plain") 29 | .body(Body::from(body)) 30 | .expect("Failed to construct response") 31 | } 32 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc(html_root_url = "https://marad.github.io/hyper-router/doc/hyper_router")] 2 | 3 | //! # Hyper Router 4 | //! 5 | //! This cargo is a small extension to the great Hyper HTTP library. It basically is 6 | //! adds the ability to define routes to request handlers and then query for the handlers 7 | //! by request path. 8 | //! 9 | //! ## Usage 10 | //! 11 | //! To use the library just add: 12 | //! 13 | //! ```text 14 | //! hyper = "^0.12" 15 | //! hyper-router = "^0.5" 16 | //! ``` 17 | //! 18 | //! to your dependencies. 19 | //! 20 | //! ```no_run 21 | //! extern crate hyper; 22 | //! extern crate hyper_router; 23 | //! 24 | //! use hyper::header::{CONTENT_LENGTH, CONTENT_TYPE}; 25 | //! use hyper::{Request, Response, Body, Method}; 26 | //! use hyper::server::Server; 27 | //! use hyper::rt::Future; 28 | //! use hyper_router::{Route, RouterBuilder, RouterService}; 29 | //! 30 | //! fn basic_handler(_: Request) -> Response { 31 | //! let body = "Hello World"; 32 | //! Response::builder() 33 | //! .header(CONTENT_LENGTH, body.len() as u64) 34 | //! .header(CONTENT_TYPE, "text/plain") 35 | //! .body(Body::from(body)) 36 | //! .expect("Failed to construct the response") 37 | //! } 38 | //! 39 | //! fn router_service() -> Result { 40 | //! let router = RouterBuilder::new() 41 | //! .add(Route::get("/greet").using(basic_handler)) 42 | //! .add(Route::from(Method::PATCH, "/asd").using(basic_handler)) 43 | //! .build(); 44 | //! 45 | //! Ok(RouterService::new(router)) 46 | //! } 47 | //! 48 | //! fn main() { 49 | //! let addr = "0.0.0.0:8080".parse().unwrap(); 50 | //! let server = Server::bind(&addr) 51 | //! .serve(router_service) 52 | //! .map_err(|e| eprintln!("server error: {}", e)); 53 | //! 54 | //! hyper::rt::run(server) 55 | //! } 56 | //! ``` 57 | //! 58 | //! This code will start Hyper server and add use router to find handlers for request. 59 | //! We create the `Route` so that when we visit path `/greet` the `basic_handler` handler 60 | //! will be called. 61 | //! 62 | //! ## Things to note 63 | //! 64 | //! * `Path::new` method accepts regular expressions so you can match every path you please. 65 | //! * If you have request matching multiple paths the one that was first `add`ed will be chosen. 66 | //! * This library is in an early stage of development so there may be breaking changes comming 67 | //! (but I'll try as hard as I can not to break backwards compatibility or break it just a little - 68 | //! I promise I'll try!). 69 | //! 70 | //! # Waiting for your feedback 71 | //! 72 | //! I've created this little tool to help myself learn Rust and to avoid using big frameworks 73 | //! like Iron or rustful. I just want to keep things simple. 74 | //! 75 | //! Obviously I could make some errors or bad design choices so I'm waiting for your feedback! 76 | //! You may create an issue at [project's bug tracker](https://github.com/marad/hyper-router/issues). 77 | 78 | extern crate futures; 79 | extern crate hyper; 80 | 81 | use futures::future::FutureResult; 82 | use hyper::header::CONTENT_LENGTH; 83 | use hyper::service::Service; 84 | use hyper::{Body, Request, Response}; 85 | 86 | use hyper::Method; 87 | use hyper::StatusCode; 88 | 89 | mod builder; 90 | pub mod handlers; 91 | mod path; 92 | pub mod route; 93 | 94 | pub use self::builder::RouterBuilder; 95 | pub use self::path::Path; 96 | pub use self::route::Route; 97 | pub use self::route::RouteBuilder; 98 | 99 | pub type Handler = fn(Request) -> Response; 100 | pub type HttpResult = Result; 101 | 102 | /// This is the one. The router. 103 | #[derive(Debug)] 104 | pub struct Router { 105 | routes: Vec, 106 | } 107 | 108 | impl Router { 109 | /// Finds handler for given Hyper request. 110 | /// 111 | /// This method uses default error handlers. 112 | /// If the request does not match any route than default 404 handler is returned. 113 | /// If the request match some routes but http method does not match (used GET but routes are 114 | /// defined for POST) than default method not supported handler is returned. 115 | pub fn find_handler_with_defaults(&self, request: &Request) -> Handler { 116 | let matching_routes = self.find_matching_routes(request.uri().path()); 117 | match matching_routes.len() { 118 | x if x == 0 => handlers::default_404_handler, 119 | _ => self 120 | .find_for_method(&matching_routes, request.method()) 121 | .unwrap_or(handlers::method_not_supported_handler), 122 | } 123 | } 124 | 125 | /// Finds handler for given Hyper request. 126 | /// 127 | /// It returns handler if it's found or `StatusCode` for error. 128 | /// This method may return `NotFound`, `MethodNotAllowed` or `NotImplemented` 129 | /// status codes. 130 | pub fn find_handler(&self, request: &Request) -> HttpResult { 131 | let matching_routes = self.find_matching_routes(request.uri().path()); 132 | match matching_routes.len() { 133 | x if x == 0 => Err(StatusCode::NOT_FOUND), 134 | _ => self 135 | .find_for_method(&matching_routes, request.method()) 136 | .map(Ok) 137 | .unwrap_or(Err(StatusCode::METHOD_NOT_ALLOWED)), 138 | } 139 | } 140 | 141 | /// Returns vector of `Route`s that match to given path. 142 | pub fn find_matching_routes(&self, request_path: &str) -> Vec<&Route> { 143 | self.routes 144 | .iter() 145 | .filter(|route| route.path.matcher.is_match(&request_path)) 146 | .collect() 147 | } 148 | 149 | fn find_for_method(&self, routes: &[&Route], method: &Method) -> Option { 150 | let method = method.clone(); 151 | routes 152 | .iter() 153 | .find(|route| route.method == method) 154 | .map(|route| route.handler) 155 | } 156 | } 157 | 158 | /// The default simple router service. 159 | #[derive(Debug)] 160 | pub struct RouterService { 161 | pub router: Router, 162 | pub error_handler: fn(StatusCode) -> Response, 163 | } 164 | 165 | impl RouterService { 166 | pub fn new(router: Router) -> RouterService { 167 | RouterService { 168 | router, 169 | error_handler: Self::default_error_handler, 170 | } 171 | } 172 | 173 | fn default_error_handler(status_code: StatusCode) -> Response { 174 | let error = "Routing error: page not found"; 175 | Response::builder() 176 | .header(CONTENT_LENGTH, error.len() as u64) 177 | .status(match status_code { 178 | StatusCode::NOT_FOUND => StatusCode::NOT_FOUND, 179 | _ => StatusCode::INTERNAL_SERVER_ERROR, 180 | }) 181 | .body(Body::from(error)) 182 | .expect("Failed to construct a response") 183 | } 184 | } 185 | 186 | impl Service for RouterService { 187 | type ReqBody = Body; 188 | type ResBody = Body; 189 | type Error = hyper::Error; 190 | type Future = FutureResult, hyper::Error>; 191 | 192 | fn call(&mut self, request: Request) -> Self::Future { 193 | futures::future::ok(match self.router.find_handler(&request) { 194 | Ok(handler) => handler(request), 195 | Err(status_code) => (self.error_handler)(status_code), 196 | }) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/path.rs: -------------------------------------------------------------------------------- 1 | extern crate regex; 2 | use self::regex::Regex; 3 | 4 | /// Represents a path in HTTP sense (starting from `/`) 5 | #[derive(Debug)] 6 | pub struct Path { 7 | pub matcher: Regex, 8 | } 9 | 10 | impl Path { 11 | /// Creates a new path. 12 | /// 13 | /// This method accepts regular expressions so you can 14 | /// write something like this: 15 | /// 16 | /// ```no_run 17 | /// use hyper_router::Path; 18 | /// Path::new(r"/person/\d+"); 19 | /// ``` 20 | /// 21 | /// Note that you don't have to match beggining and end of the 22 | /// path using `^` and `$` - those are inserted for you automatically. 23 | pub fn new(path: &str) -> Path { 24 | let mut regex = "^".to_string(); 25 | regex.push_str(path); 26 | regex.push_str("$"); 27 | Path { 28 | matcher: Regex::new(®ex).unwrap(), 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/route/builder.rs: -------------------------------------------------------------------------------- 1 | use crate::Handler; 2 | use crate::Route; 3 | 4 | pub struct RouteBuilder { 5 | route: Route, 6 | } 7 | 8 | impl RouteBuilder { 9 | pub fn new(route: Route) -> RouteBuilder { 10 | RouteBuilder { route } 11 | } 12 | 13 | /// Completes the building process by taking the handler to process the request. 14 | /// 15 | /// Returns created route. 16 | pub fn using(mut self, handler: Handler) -> Route { 17 | self.route.handler = handler; 18 | self.route 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/route/mod.rs: -------------------------------------------------------------------------------- 1 | mod builder; 2 | mod route_impl; 3 | 4 | pub use builder::RouteBuilder; 5 | pub use route_impl::Route; 6 | -------------------------------------------------------------------------------- /src/route/route_impl.rs: -------------------------------------------------------------------------------- 1 | use crate::handlers; 2 | use hyper::Method; 3 | use std::fmt; 4 | 5 | use super::RouteBuilder; 6 | use crate::Handler; 7 | use crate::Path; 8 | 9 | /// Holds route information 10 | pub struct Route { 11 | /// HTTP method to match 12 | pub method: Method, 13 | 14 | /// Path to match 15 | pub path: Path, 16 | 17 | /// Request handler 18 | /// 19 | /// This should be method that accepts Hyper's Request and Response: 20 | /// 21 | /// ```ignore 22 | /// use hyper::server::{Request, Response}; 23 | /// use hyper::header::{ContentLength, ContentType}; 24 | /// 25 | /// fn hello_handler(_: Request) -> Response { 26 | /// let body = "Hello World"; 27 | /// Response::new() 28 | /// .with_header(ContentLength(body.len() as u64)) 29 | /// .with_header(ContentType::plaintext()) 30 | /// .with_body(body) 31 | /// } 32 | /// ``` 33 | pub handler: Handler, 34 | } 35 | 36 | impl Route { 37 | pub fn options(path: &str) -> RouteBuilder { 38 | Route::from(Method::OPTIONS, path) 39 | } 40 | 41 | pub fn get(path: &str) -> RouteBuilder { 42 | Route::from(Method::GET, path) 43 | } 44 | 45 | pub fn post(path: &str) -> RouteBuilder { 46 | Route::from(Method::POST, path) 47 | } 48 | 49 | pub fn put(path: &str) -> RouteBuilder { 50 | Route::from(Method::PUT, path) 51 | } 52 | 53 | pub fn delete(path: &str) -> RouteBuilder { 54 | Route::from(Method::DELETE, path) 55 | } 56 | 57 | pub fn head(path: &str) -> RouteBuilder { 58 | Route::from(Method::HEAD, path) 59 | } 60 | 61 | pub fn trace(path: &str) -> RouteBuilder { 62 | Route::from(Method::TRACE, path) 63 | } 64 | 65 | pub fn connect(path: &str) -> RouteBuilder { 66 | Route::from(Method::CONNECT, path) 67 | } 68 | 69 | pub fn patch(path: &str) -> RouteBuilder { 70 | Route::from(Method::PATCH, path) 71 | } 72 | 73 | pub fn from(method: Method, path: &str) -> RouteBuilder { 74 | RouteBuilder::new(Route { 75 | method, 76 | path: Path::new(path), 77 | ..Route::default() 78 | }) 79 | } 80 | } 81 | 82 | impl Default for Route { 83 | fn default() -> Route { 84 | Route { 85 | method: Method::GET, 86 | path: Path::new("/"), 87 | handler: handlers::not_implemented_handler, 88 | } 89 | } 90 | } 91 | 92 | impl fmt::Debug for Route { 93 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 94 | write!( 95 | f, 96 | "Route {{method: {:?}, path: {:?}}}", 97 | self.method, self.path 98 | ) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /test-server/main.rs: -------------------------------------------------------------------------------- 1 | extern crate hyper; 2 | extern crate hyper_router; 3 | 4 | use hyper::header::{CONTENT_LENGTH, CONTENT_TYPE}; 5 | use hyper::rt::Future; 6 | use hyper::server::Server; 7 | use hyper::{Body, Method, Request, Response}; 8 | use hyper_router::{Route, RouterBuilder, RouterService}; 9 | 10 | fn request_handler(_: Request) -> Response { 11 | let body = "Hello World"; 12 | Response::builder() 13 | .header(CONTENT_LENGTH, body.len() as u64) 14 | .header(CONTENT_TYPE, "text/plain") 15 | .body(Body::from(body)) 16 | .expect("Failed to construct the response") 17 | } 18 | 19 | fn router_service() -> Result { 20 | let router = RouterBuilder::new() 21 | .add(Route::get("/hello").using(request_handler)) 22 | .add(Route::from(Method::PATCH, "/world").using(request_handler)) 23 | .build(); 24 | 25 | Ok(RouterService::new(router)) 26 | } 27 | 28 | fn main() { 29 | let addr = "0.0.0.0:8080".parse().unwrap(); 30 | let server = Server::bind(&addr) 31 | .serve(router_service) 32 | .map_err(|e| eprintln!("server error: {}", e)); 33 | 34 | hyper::rt::run(server) 35 | } 36 | -------------------------------------------------------------------------------- /tests/basic.rs: -------------------------------------------------------------------------------- 1 | extern crate hyper; 2 | extern crate hyper_router; 3 | 4 | use hyper::{Body, Method, Request, Response, Uri}; 5 | use hyper_router::*; 6 | use std::str::FromStr; 7 | 8 | #[test] 9 | fn test_get_route() { 10 | let request = Request::builder() 11 | .method(Method::GET) 12 | .uri(Uri::from_str("http://www.example.com/hello").unwrap()) 13 | .body(Body::empty()) 14 | .unwrap(); 15 | 16 | fn handle_get_hello(_: Request) -> Response { 17 | unimplemented!() 18 | }; 19 | fn handle_get_root(_: Request) -> Response { 20 | unimplemented!() 21 | }; 22 | fn handle_get_foo(_: Request) -> Response { 23 | unimplemented!() 24 | }; 25 | fn handle_post_hello(_: Request) -> Response { 26 | unimplemented!() 27 | }; 28 | 29 | let router = RouterBuilder::new() 30 | .add(Route::get("/hello").using(handle_get_hello)) 31 | .add(Route::get("/").using(handle_get_root)) 32 | .add(Route::get("/foo").using(handle_get_foo)) 33 | .add(Route::post("/hello").using(handle_post_hello)) 34 | .build(); 35 | 36 | let handler = router.find_handler(&request).unwrap(); 37 | assert!(handler as fn(_) -> _ == handle_get_hello as fn(_) -> _); 38 | } 39 | 40 | #[test] 41 | fn test_post_route() { 42 | let request = Request::builder() 43 | .method(Method::POST) 44 | .uri(Uri::from_str("http://www.example.com/hello").unwrap()) 45 | .body(Body::empty()) 46 | .unwrap(); 47 | 48 | fn handle_post_hello(_: Request) -> Response { 49 | unimplemented!() 50 | }; 51 | fn handle_post_root(_: Request) -> Response { 52 | unimplemented!() 53 | }; 54 | fn handle_post_foo(_: Request) -> Response { 55 | unimplemented!() 56 | }; 57 | fn handle_get_hello(_: Request) -> Response { 58 | unimplemented!() 59 | }; 60 | 61 | let router = RouterBuilder::new() 62 | .add(Route::post("/hello").using(handle_post_hello)) 63 | .add(Route::get("/").using(handle_post_root)) 64 | .add(Route::get("/foo").using(handle_post_foo)) 65 | .add(Route::get("/hello").using(handle_get_hello)) 66 | .build(); 67 | 68 | let handler = router.find_handler(&request).unwrap(); 69 | assert!(handler as fn(_) -> _ == handle_post_hello as fn(_) -> _); 70 | } 71 | 72 | #[test] 73 | fn test_delete_route() { 74 | let request = Request::builder() 75 | .method(Method::DELETE) 76 | .uri(Uri::from_str("http://www.example.com/hello").unwrap()) 77 | .body(Body::empty()) 78 | .unwrap(); 79 | 80 | fn handle_delete_hello(_: Request) -> Response { 81 | unimplemented!() 82 | }; 83 | fn handle_post_hello(_: Request) -> Response { 84 | unimplemented!() 85 | }; 86 | 87 | let router = RouterBuilder::new() 88 | .add(Route::delete("/hello").using(handle_delete_hello)) 89 | .add(Route::post("/hello").using(handle_post_hello)) 90 | .build(); 91 | 92 | let handler = router.find_handler(&request).unwrap(); 93 | assert!(handler as fn(_) -> _ == handle_delete_hello as fn(_) -> _); 94 | } 95 | 96 | #[test] 97 | fn test_options_route() { 98 | let request = Request::builder() 99 | .method(Method::OPTIONS) 100 | .uri(Uri::from_str("http://www.example.com/hello").unwrap()) 101 | .body(Body::empty()) 102 | .unwrap(); 103 | 104 | fn handle_options_hello(_: Request) -> Response { 105 | unimplemented!() 106 | }; 107 | fn handle_post_hello(_: Request) -> Response { 108 | unimplemented!() 109 | }; 110 | 111 | let router = RouterBuilder::new() 112 | .add(Route::options("/hello").using(handle_options_hello)) 113 | .add(Route::post("/hello").using(handle_post_hello)) 114 | .build(); 115 | 116 | let handler = router.find_handler(&request).unwrap(); 117 | assert!(handler as fn(_) -> _ == handle_options_hello as fn(_) -> _); 118 | } 119 | 120 | #[test] 121 | fn test_put_route() { 122 | let request = Request::builder() 123 | .method(Method::PUT) 124 | .uri(Uri::from_str("http://www.example.com/hello").unwrap()) 125 | .body(Body::empty()) 126 | .unwrap(); 127 | 128 | fn handle_put_hello(_: Request) -> Response { 129 | unimplemented!() 130 | }; 131 | fn handle_post_hello(_: Request) -> Response { 132 | unimplemented!() 133 | }; 134 | 135 | let router = RouterBuilder::new() 136 | .add(Route::put("/hello").using(handle_put_hello)) 137 | .add(Route::post("/hello").using(handle_post_hello)) 138 | .build(); 139 | 140 | let handler = router.find_handler(&request).unwrap(); 141 | assert!(handler as fn(_) -> _ == handle_put_hello as fn(_) -> _); 142 | } 143 | 144 | #[test] 145 | fn test_head_route() { 146 | let request = Request::builder() 147 | .method(Method::HEAD) 148 | .uri(Uri::from_str("http://www.example.com/hello").unwrap()) 149 | .body(Body::empty()) 150 | .unwrap(); 151 | 152 | fn handle_head_hello(_: Request) -> Response { 153 | unimplemented!() 154 | }; 155 | fn handle_post_hello(_: Request) -> Response { 156 | unimplemented!() 157 | }; 158 | 159 | let router = RouterBuilder::new() 160 | .add(Route::head("/hello").using(handle_head_hello)) 161 | .add(Route::post("/hello").using(handle_post_hello)) 162 | .build(); 163 | 164 | let handler = router.find_handler(&request).unwrap(); 165 | assert!(handler as fn(_) -> _ == handle_head_hello as fn(_) -> _); 166 | } 167 | 168 | #[test] 169 | fn test_trace_route() { 170 | let request = Request::builder() 171 | .method(Method::TRACE) 172 | .uri(Uri::from_str("http://www.example.com/hello").unwrap()) 173 | .body(Body::empty()) 174 | .unwrap(); 175 | 176 | fn handle_trace_hello(_: Request) -> Response { 177 | unimplemented!() 178 | }; 179 | fn handle_post_hello(_: Request) -> Response { 180 | unimplemented!() 181 | }; 182 | 183 | let router = RouterBuilder::new() 184 | .add(Route::trace("/hello").using(handle_trace_hello)) 185 | .add(Route::post("/hello").using(handle_post_hello)) 186 | .build(); 187 | 188 | let handler = router.find_handler(&request).unwrap(); 189 | assert!(handler as fn(_) -> _ == handle_trace_hello as fn(_) -> _); 190 | } 191 | 192 | #[test] 193 | fn test_patch_route() { 194 | let request = Request::builder() 195 | .method(Method::PATCH) 196 | .uri(Uri::from_str("http://www.example.com/hello").unwrap()) 197 | .body(Body::empty()) 198 | .unwrap(); 199 | 200 | fn handle_patch_hello(_: Request) -> Response { 201 | unimplemented!() 202 | }; 203 | fn handle_post_hello(_: Request) -> Response { 204 | unimplemented!() 205 | }; 206 | 207 | let router = RouterBuilder::new() 208 | .add(Route::patch("/hello").using(handle_patch_hello)) 209 | .add(Route::post("/hello").using(handle_post_hello)) 210 | .build(); 211 | 212 | let handler = router.find_handler(&request).unwrap(); 213 | assert!(handler as fn(_) -> _ == handle_patch_hello as fn(_) -> _); 214 | } 215 | 216 | #[test] 217 | fn test_no_route() { 218 | let request = Request::builder() 219 | .method(Method::GET) 220 | .uri(Uri::from_str("http://www.example.com/notfound").unwrap()) 221 | .body(Body::empty()) 222 | .unwrap(); 223 | 224 | fn handle_get_foo(_: Request) -> Response { 225 | unimplemented!() 226 | }; 227 | fn handle_get_bar(_: Request) -> Response { 228 | unimplemented!() 229 | }; 230 | 231 | let router = RouterBuilder::new() 232 | .add(Route::patch("/foo").using(handle_get_foo)) 233 | .add(Route::patch("/bar").using(handle_get_bar)) 234 | .build(); 235 | 236 | let handler = router.find_handler(&request); 237 | 238 | match handler { 239 | Ok(_) => panic!("Expected an error, but got a handler instead"), 240 | Err(e) => assert_eq!(e, hyper::StatusCode::NOT_FOUND), 241 | } 242 | } 243 | 244 | #[test] 245 | fn test_regex_path() { 246 | let request = Request::builder() 247 | .method(Method::GET) 248 | .uri(Uri::from_str("http://www.example.com/foo/bar").unwrap()) 249 | .body(Body::empty()) 250 | .unwrap(); 251 | 252 | fn handle_regex_foo(_: Request) -> Response { 253 | unimplemented!() 254 | }; 255 | fn handle_regex_bar(_: Request) -> Response { 256 | unimplemented!() 257 | }; 258 | 259 | let router = RouterBuilder::new() 260 | .add(Route::get(r"/foo/.*?").using(handle_regex_foo)) 261 | .add(Route::get(r"/bar/.*?").using(handle_regex_bar)) 262 | .build(); 263 | 264 | let handler = router.find_handler(&request).unwrap(); 265 | assert!(handler as fn(_) -> _ == handle_regex_foo as fn(_) -> _); 266 | } 267 | --------------------------------------------------------------------------------