├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── http-core ├── Cargo.toml ├── index.css ├── index.html └── src │ └── main.rs └── http-lib ├── Cargo.toml └── src ├── http.rs ├── lib.rs ├── macros.rs ├── middleware ├── logger.rs ├── middleware.rs └── mod.rs ├── request.rs ├── response.rs └── server.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target -------------------------------------------------------------------------------- /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 = "autocfg" 7 | version = "1.1.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 10 | 11 | [[package]] 12 | name = "bitflags" 13 | version = "1.3.2" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 16 | 17 | [[package]] 18 | name = "bytes" 19 | version = "1.4.0" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" 22 | 23 | [[package]] 24 | name = "cfg-if" 25 | version = "1.0.0" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 28 | 29 | [[package]] 30 | name = "hermit-abi" 31 | version = "0.2.6" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" 34 | dependencies = [ 35 | "libc", 36 | ] 37 | 38 | [[package]] 39 | name = "http-core" 40 | version = "0.1.0" 41 | dependencies = [ 42 | "http-lib", 43 | "tokio", 44 | ] 45 | 46 | [[package]] 47 | name = "http-lib" 48 | version = "0.1.0" 49 | dependencies = [ 50 | "httparse", 51 | "quote", 52 | "serde", 53 | "serde_json", 54 | "syn", 55 | "tokio", 56 | ] 57 | 58 | [[package]] 59 | name = "httparse" 60 | version = "1.8.0" 61 | source = "registry+https://github.com/rust-lang/crates.io-index" 62 | checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" 63 | 64 | [[package]] 65 | name = "itoa" 66 | version = "1.0.6" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" 69 | 70 | [[package]] 71 | name = "libc" 72 | version = "0.2.144" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" 75 | 76 | [[package]] 77 | name = "lock_api" 78 | version = "0.4.9" 79 | source = "registry+https://github.com/rust-lang/crates.io-index" 80 | checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" 81 | dependencies = [ 82 | "autocfg", 83 | "scopeguard", 84 | ] 85 | 86 | [[package]] 87 | name = "log" 88 | version = "0.4.17" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 91 | dependencies = [ 92 | "cfg-if", 93 | ] 94 | 95 | [[package]] 96 | name = "mio" 97 | version = "0.8.6" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" 100 | dependencies = [ 101 | "libc", 102 | "log", 103 | "wasi", 104 | "windows-sys 0.45.0", 105 | ] 106 | 107 | [[package]] 108 | name = "num_cpus" 109 | version = "1.15.0" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" 112 | dependencies = [ 113 | "hermit-abi", 114 | "libc", 115 | ] 116 | 117 | [[package]] 118 | name = "parking_lot" 119 | version = "0.12.1" 120 | source = "registry+https://github.com/rust-lang/crates.io-index" 121 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 122 | dependencies = [ 123 | "lock_api", 124 | "parking_lot_core", 125 | ] 126 | 127 | [[package]] 128 | name = "parking_lot_core" 129 | version = "0.9.7" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" 132 | dependencies = [ 133 | "cfg-if", 134 | "libc", 135 | "redox_syscall", 136 | "smallvec", 137 | "windows-sys 0.45.0", 138 | ] 139 | 140 | [[package]] 141 | name = "pin-project-lite" 142 | version = "0.2.9" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" 145 | 146 | [[package]] 147 | name = "proc-macro2" 148 | version = "1.0.56" 149 | source = "registry+https://github.com/rust-lang/crates.io-index" 150 | checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" 151 | dependencies = [ 152 | "unicode-ident", 153 | ] 154 | 155 | [[package]] 156 | name = "quote" 157 | version = "1.0.27" 158 | source = "registry+https://github.com/rust-lang/crates.io-index" 159 | checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500" 160 | dependencies = [ 161 | "proc-macro2", 162 | ] 163 | 164 | [[package]] 165 | name = "redox_syscall" 166 | version = "0.2.16" 167 | source = "registry+https://github.com/rust-lang/crates.io-index" 168 | checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" 169 | dependencies = [ 170 | "bitflags", 171 | ] 172 | 173 | [[package]] 174 | name = "ryu" 175 | version = "1.0.13" 176 | source = "registry+https://github.com/rust-lang/crates.io-index" 177 | checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" 178 | 179 | [[package]] 180 | name = "scopeguard" 181 | version = "1.1.0" 182 | source = "registry+https://github.com/rust-lang/crates.io-index" 183 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 184 | 185 | [[package]] 186 | name = "serde" 187 | version = "1.0.162" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "71b2f6e1ab5c2b98c05f0f35b236b22e8df7ead6ffbf51d7808da7f8817e7ab6" 190 | dependencies = [ 191 | "serde_derive", 192 | ] 193 | 194 | [[package]] 195 | name = "serde_derive" 196 | version = "1.0.162" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "a2a0814352fd64b58489904a44ea8d90cb1a91dcb6b4f5ebabc32c8318e93cb6" 199 | dependencies = [ 200 | "proc-macro2", 201 | "quote", 202 | "syn", 203 | ] 204 | 205 | [[package]] 206 | name = "serde_json" 207 | version = "1.0.96" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "057d394a50403bcac12672b2b18fb387ab6d289d957dab67dd201875391e52f1" 210 | dependencies = [ 211 | "itoa", 212 | "ryu", 213 | "serde", 214 | ] 215 | 216 | [[package]] 217 | name = "signal-hook-registry" 218 | version = "1.4.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" 221 | dependencies = [ 222 | "libc", 223 | ] 224 | 225 | [[package]] 226 | name = "smallvec" 227 | version = "1.10.0" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" 230 | 231 | [[package]] 232 | name = "socket2" 233 | version = "0.4.9" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" 236 | dependencies = [ 237 | "libc", 238 | "winapi", 239 | ] 240 | 241 | [[package]] 242 | name = "syn" 243 | version = "2.0.15" 244 | source = "registry+https://github.com/rust-lang/crates.io-index" 245 | checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822" 246 | dependencies = [ 247 | "proc-macro2", 248 | "quote", 249 | "unicode-ident", 250 | ] 251 | 252 | [[package]] 253 | name = "tokio" 254 | version = "1.28.0" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "c3c786bf8134e5a3a166db9b29ab8f48134739014a3eca7bc6bfa95d673b136f" 257 | dependencies = [ 258 | "autocfg", 259 | "bytes", 260 | "libc", 261 | "mio", 262 | "num_cpus", 263 | "parking_lot", 264 | "pin-project-lite", 265 | "signal-hook-registry", 266 | "socket2", 267 | "tokio-macros", 268 | "windows-sys 0.48.0", 269 | ] 270 | 271 | [[package]] 272 | name = "tokio-macros" 273 | version = "2.1.0" 274 | source = "registry+https://github.com/rust-lang/crates.io-index" 275 | checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" 276 | dependencies = [ 277 | "proc-macro2", 278 | "quote", 279 | "syn", 280 | ] 281 | 282 | [[package]] 283 | name = "unicode-ident" 284 | version = "1.0.8" 285 | source = "registry+https://github.com/rust-lang/crates.io-index" 286 | checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" 287 | 288 | [[package]] 289 | name = "wasi" 290 | version = "0.11.0+wasi-snapshot-preview1" 291 | source = "registry+https://github.com/rust-lang/crates.io-index" 292 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 293 | 294 | [[package]] 295 | name = "winapi" 296 | version = "0.3.9" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 299 | dependencies = [ 300 | "winapi-i686-pc-windows-gnu", 301 | "winapi-x86_64-pc-windows-gnu", 302 | ] 303 | 304 | [[package]] 305 | name = "winapi-i686-pc-windows-gnu" 306 | version = "0.4.0" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 309 | 310 | [[package]] 311 | name = "winapi-x86_64-pc-windows-gnu" 312 | version = "0.4.0" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 315 | 316 | [[package]] 317 | name = "windows-sys" 318 | version = "0.45.0" 319 | source = "registry+https://github.com/rust-lang/crates.io-index" 320 | checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" 321 | dependencies = [ 322 | "windows-targets 0.42.2", 323 | ] 324 | 325 | [[package]] 326 | name = "windows-sys" 327 | version = "0.48.0" 328 | source = "registry+https://github.com/rust-lang/crates.io-index" 329 | checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" 330 | dependencies = [ 331 | "windows-targets 0.48.0", 332 | ] 333 | 334 | [[package]] 335 | name = "windows-targets" 336 | version = "0.42.2" 337 | source = "registry+https://github.com/rust-lang/crates.io-index" 338 | checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" 339 | dependencies = [ 340 | "windows_aarch64_gnullvm 0.42.2", 341 | "windows_aarch64_msvc 0.42.2", 342 | "windows_i686_gnu 0.42.2", 343 | "windows_i686_msvc 0.42.2", 344 | "windows_x86_64_gnu 0.42.2", 345 | "windows_x86_64_gnullvm 0.42.2", 346 | "windows_x86_64_msvc 0.42.2", 347 | ] 348 | 349 | [[package]] 350 | name = "windows-targets" 351 | version = "0.48.0" 352 | source = "registry+https://github.com/rust-lang/crates.io-index" 353 | checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" 354 | dependencies = [ 355 | "windows_aarch64_gnullvm 0.48.0", 356 | "windows_aarch64_msvc 0.48.0", 357 | "windows_i686_gnu 0.48.0", 358 | "windows_i686_msvc 0.48.0", 359 | "windows_x86_64_gnu 0.48.0", 360 | "windows_x86_64_gnullvm 0.48.0", 361 | "windows_x86_64_msvc 0.48.0", 362 | ] 363 | 364 | [[package]] 365 | name = "windows_aarch64_gnullvm" 366 | version = "0.42.2" 367 | source = "registry+https://github.com/rust-lang/crates.io-index" 368 | checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" 369 | 370 | [[package]] 371 | name = "windows_aarch64_gnullvm" 372 | version = "0.48.0" 373 | source = "registry+https://github.com/rust-lang/crates.io-index" 374 | checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" 375 | 376 | [[package]] 377 | name = "windows_aarch64_msvc" 378 | version = "0.42.2" 379 | source = "registry+https://github.com/rust-lang/crates.io-index" 380 | checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" 381 | 382 | [[package]] 383 | name = "windows_aarch64_msvc" 384 | version = "0.48.0" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" 387 | 388 | [[package]] 389 | name = "windows_i686_gnu" 390 | version = "0.42.2" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" 393 | 394 | [[package]] 395 | name = "windows_i686_gnu" 396 | version = "0.48.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" 399 | 400 | [[package]] 401 | name = "windows_i686_msvc" 402 | version = "0.42.2" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" 405 | 406 | [[package]] 407 | name = "windows_i686_msvc" 408 | version = "0.48.0" 409 | source = "registry+https://github.com/rust-lang/crates.io-index" 410 | checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" 411 | 412 | [[package]] 413 | name = "windows_x86_64_gnu" 414 | version = "0.42.2" 415 | source = "registry+https://github.com/rust-lang/crates.io-index" 416 | checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" 417 | 418 | [[package]] 419 | name = "windows_x86_64_gnu" 420 | version = "0.48.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" 423 | 424 | [[package]] 425 | name = "windows_x86_64_gnullvm" 426 | version = "0.42.2" 427 | source = "registry+https://github.com/rust-lang/crates.io-index" 428 | checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" 429 | 430 | [[package]] 431 | name = "windows_x86_64_gnullvm" 432 | version = "0.48.0" 433 | source = "registry+https://github.com/rust-lang/crates.io-index" 434 | checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" 435 | 436 | [[package]] 437 | name = "windows_x86_64_msvc" 438 | version = "0.42.2" 439 | source = "registry+https://github.com/rust-lang/crates.io-index" 440 | checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" 441 | 442 | [[package]] 443 | name = "windows_x86_64_msvc" 444 | version = "0.48.0" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" 447 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | 3 | members = [ 4 | "http-lib", 5 | "http-core" 6 | ] 7 | 8 | [toolchain] 9 | channel = "nightly" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Toy-HTTP-rs: A Modern, Lightweight HTTP Learning Tool in Rust 2 | 3 | ![Rust](https://github.com/user/toy-http/workflows/Rust/badge.svg) 4 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) 5 | 6 | Welcome to `toy-http-rs`! This is a hands-on, educational project designed to provide an accessible, practical introduction to the workings of the HTTP protocol, leveraging the power and safety of Rust. 7 | 8 | ## Introduction 9 | 10 | With `toy-http-rs`, we aim to demystify the intricacies of HTTP and provide a solid foundation for anyone looking to delve deeper into web development or networking. The project maintains a clean, modern codebase that is easy to navigate and understand. 11 | 12 | ## Features 13 | 14 | - **HTTP protocol understanding**: A step-by-step, code-based exploration of the HTTP protocol. 15 | - **Rust**: Leverages the safety and speed of Rust. 16 | - **Modern and clean codebase**: An easily understandable and well-commented codebase, perfect for those eager to learn. 17 | 18 | ## Getting Started 19 | 20 | 1. Clone the repository: 21 | 22 | ```bash 23 | git clone https://github.com/username/toy-http.git 24 | -------------------------------------------------------------------------------- /http-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-core" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | tokio = { version = "1.28.0", features = ["full"] } 10 | http-lib = {path="../http-lib"} -------------------------------------------------------------------------------- /http-core/index.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/codemoonsxyz/toy-http-rs/f63509ed8cc7b726b74bb9658490d863d2ab6b4e/http-core/index.css -------------------------------------------------------------------------------- /http-core/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello, World! 5 | 6 | 7 |

Hello, World!

8 |

Welcome to my page.

9 | 10 | 11 | -------------------------------------------------------------------------------- /http-core/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::net::SocketAddr; 2 | 3 | use std::collections::HashMap; 4 | 5 | use http_lib::http::{HttpMethod, HttpStatusCode}; 6 | use http_lib::middleware::logger::LoggerMiddleware; 7 | use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader}; 8 | use tokio::net::TcpListener; 9 | use tokio::sync::broadcast; 10 | 11 | 12 | use http_lib::request::*; 13 | 14 | use http_lib::response::*; 15 | 16 | use http_lib::server::*; 17 | 18 | fn hello_handler(_req: Request) -> FutureResponse<'static> { 19 | let html = "

Hello, world!

"; 20 | let response = Response { 21 | version: "HTTP/1.1".to_string(), 22 | status_code: 200, 23 | status_text: "OK".to_string(), 24 | headers: { 25 | let mut headers = HashMap::new(); 26 | headers.insert("Content-Type".to_string(), "text/html".to_string()); 27 | headers 28 | }, 29 | body: Some(html.to_string()), 30 | }; 31 | Box::pin(async move { Ok(response) }) 32 | } 33 | 34 | 35 | 36 | #[tokio::main] 37 | async fn main() -> Result<(), Box> { 38 | 39 | let addr = SocketAddr::from(([127, 0, 0, 1], 8080)); 40 | 41 | let server = ServerBuilder::new() 42 | .bind(addr) 43 | .route("/",HttpMethod::GET, hello_handler).accept(LoggerMiddleware) 44 | .build()? 45 | .run() 46 | .await?; 47 | 48 | println!("Hello, world!"); 49 | Ok(()) 50 | } 51 | -------------------------------------------------------------------------------- /http-lib/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "http-lib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | [lib] 8 | #proc-macro = true 9 | 10 | [dependencies] 11 | tokio = { version = "1.28.0", features = ["full"] } 12 | httparse = "1.8.0" 13 | syn = "2.0.15" 14 | 15 | quote = "1.0.27" 16 | 17 | # The core APIs, including the Serialize and Deserialize traits. Always 18 | # required when using Serde. The "derive" feature is only required when 19 | # using #[derive(Serialize, Deserialize)] to make Serde work with structs 20 | # and enums defined in your crate. 21 | serde = { version = "1.0", features = ["derive"] } 22 | 23 | # Each data format lives in its own crate; the sample code below uses JSON 24 | # but you may be using a different one. 25 | serde_json = "1.0" -------------------------------------------------------------------------------- /http-lib/src/http.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use std::fmt; 4 | #[derive(Debug, Serialize, Deserialize, Clone, Default,PartialEq,Eq, Hash)] 5 | pub enum HttpMethod { 6 | #[default] 7 | GET, 8 | POST, 9 | PUT, 10 | DELETE, 11 | HEAD, 12 | OPTIONS, 13 | CONNECT, 14 | TRACE, 15 | PATCH, 16 | OTHER(String), 17 | } 18 | 19 | #[derive(Debug, Clone, Copy, Default,PartialEq)] 20 | #[repr(u16)] 21 | pub enum HttpStatusCode { 22 | #[default] 23 | Success = 200, 24 | BadRequest = 400, 25 | Unauthorized = 401, 26 | Forbidden = 403, 27 | NotFound = 404, 28 | MethodNotAllowed = 405, 29 | NotAcceptable = 406, 30 | Conflict = 409, 31 | InternalServerError = 500, 32 | NotImplemented = 501, 33 | BadGateway = 502, 34 | ServiceUnavailable = 503, 35 | } 36 | 37 | #[derive(Debug, Clone)] 38 | pub enum HttpError { 39 | BadRequest(HttpStatusCode,&'static str), 40 | Unauthorized(HttpStatusCode,&'static str), 41 | Forbidden(HttpStatusCode,&'static str), 42 | NotFound(HttpStatusCode,&'static str), 43 | MethodNotAllowed(HttpStatusCode,&'static str), 44 | NotAcceptable(HttpStatusCode,&'static str), 45 | Conflict(HttpStatusCode,&'static str), 46 | InternalServerError(HttpStatusCode,&'static str), 47 | NotImplemented(HttpStatusCode,&'static str), 48 | BadGateway(HttpStatusCode,&'static str), 49 | ServiceUnavailable(HttpStatusCode,&'static str), 50 | } 51 | 52 | impl fmt::Display for HttpError { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | match self { 55 | HttpError::BadRequest(err_code,msg) => write!(f, "Error {}: Bad Request {}", *err_code as u16,msg), 56 | HttpError::Unauthorized(err_code,msg) => { 57 | write!(f, "Error {}: Unauthorized {}", *err_code as u16, msg) 58 | } 59 | HttpError::Forbidden(err_code,msg) => write!(f, "Error {}: Forbidden {}", *err_code as u16,msg), 60 | HttpError::NotFound(err_code,msg) => write!(f, "Error {}: Not Found {}", *err_code as u16,msg), 61 | HttpError::MethodNotAllowed(err_code,msg) => { 62 | write!(f, "Error {}: Method Not Allowed", *err_code as u16) 63 | } 64 | HttpError::NotAcceptable(err_code,msg) => { 65 | write!(f, "Error {}: Not Acceptable {}", *err_code as u16,msg) 66 | } 67 | HttpError::Conflict(err_code,msg) => write!(f, "Error {}: Conflict", *err_code as u16), 68 | HttpError::InternalServerError(err_code,msg) => { 69 | write!(f, "Error {}: Internal Server Error {}", *err_code as u16,msg) 70 | } 71 | HttpError::NotImplemented(err_code,msg) => { 72 | write!(f, "Error {}: Not Implemented {}", *err_code as u16,msg) 73 | } 74 | HttpError::BadGateway(err_code,msg) => write!(f, "Error {}: Bad Gateway", *err_code as u16), 75 | HttpError::ServiceUnavailable(err_code,msg) => { 76 | write!(f, "Error {}: Service Unavailable {}", *err_code as u16,msg) 77 | } 78 | } 79 | } 80 | } 81 | 82 | impl std::error::Error for HttpError {} 83 | -------------------------------------------------------------------------------- /http-lib/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(async_fn_in_trait)] 2 | pub mod http; 3 | pub mod request; 4 | pub mod response; 5 | pub mod server; 6 | 7 | pub mod middleware; 8 | -------------------------------------------------------------------------------- /http-lib/src/macros.rs: -------------------------------------------------------------------------------- 1 | macro_rules! make_route { 2 | ($method:ident, $handler:expr) => { 3 | fn $method() -> Route { 4 | let method = match stringify!($method).to_uppercase().as_str() { 5 | "GET" => HttpMethod::GET, 6 | "POST" => HttpMethod::POST, 7 | "PUT" => HttpMethod::PUT, 8 | "DELETE" => HttpMethod::DELETE, 9 | "HEAD" => HttpMethod::HEAD, 10 | "OPTIONS" => HttpMethod::OPTIONS, 11 | "CONNECT" => HttpMethod::CONNECT, 12 | "TRACE" => HttpMethod::TRACE, 13 | "PATCH" => HttpMethod::PATCH, 14 | _ => HttpMethod::OTHER(stringify!($method).to_string()), 15 | }; 16 | Route { 17 | method, 18 | handler: $handler, 19 | } 20 | } 21 | }; 22 | } -------------------------------------------------------------------------------- /http-lib/src/middleware/logger.rs: -------------------------------------------------------------------------------- 1 | use crate::middleware::middleware::Middleware; 2 | use crate::request::Request; 3 | use crate::response::Response; 4 | use crate::server::FutureResponse; 5 | use crate::middleware::middleware::FutureRequest; 6 | use std::error::Error; 7 | use std::pin::Pin; 8 | use std::future::Future; 9 | 10 | 11 | #[derive(Debug, Clone)] 12 | pub struct LoggerMiddleware; 13 | 14 | impl Middleware for LoggerMiddleware { 15 | 16 | 17 | fn on_request<'a>(&self, request: Request) -> FutureRequest<'a> { 18 | // Log the incoming request 19 | println!("Request HERE: {:?}", request); 20 | 21 | Box::pin(async move { 22 | // Modify the request or perform asynchronous operations 23 | Ok(request) 24 | // Modify as per your implementation 25 | }) 26 | } 27 | 28 | 29 | fn on_response<'a>(&self, response: Response) -> FutureResponse<'a> { 30 | // Log the outgoing response 31 | println!("Response: {:?}", response); 32 | Box::pin(async move { 33 | Ok(response) // Wrap response.clone() in Ok variant 34 | }) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /http-lib/src/middleware/middleware.rs: -------------------------------------------------------------------------------- 1 | 2 | use crate::response::Response; 3 | use crate::request::Request; 4 | use crate::server::FutureResponse; 5 | use std::future::Future; 6 | use std::pin::Pin; 7 | use std::ops::Deref; 8 | use std::clone::Clone; 9 | 10 | pub type FutureRequest<'a> = Pin>> + Send + 'a>>; 11 | 12 | 13 | pub trait Middleware: Send + Sync{ 14 | fn on_request<'a>(&self, request: Request) -> FutureRequest<'a>; 15 | fn on_response<'a>(&self, response:Response) -> FutureResponse<'a>; 16 | 17 | } 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /http-lib/src/middleware/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod logger; 2 | 3 | pub mod middleware; 4 | -------------------------------------------------------------------------------- /http-lib/src/request.rs: -------------------------------------------------------------------------------- 1 | 2 | use std::collections::HashMap; 3 | 4 | use crate::http::HttpMethod; 5 | 6 | 7 | use serde::{Deserialize, Serialize}; 8 | #[derive(Debug,Clone,Serialize, Deserialize)] 9 | pub struct Request { 10 | pub method: HttpMethod, 11 | pub uri: String, 12 | pub version: String, 13 | pub headers: HashMap, 14 | pub body: Option, 15 | } -------------------------------------------------------------------------------- /http-lib/src/response.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::collections::HashMap; 3 | #[derive(Debug,Default, Serialize, Deserialize,Clone)] 4 | pub struct Response { 5 | pub version: String, 6 | pub status_code: u16, 7 | pub status_text: String, 8 | pub headers: HashMap, 9 | pub body: Option, 10 | } 11 | -------------------------------------------------------------------------------- /http-lib/src/server.rs: -------------------------------------------------------------------------------- 1 | use crate::request::Request; 2 | use crate::response::Response; 3 | use crate::middleware::middleware::Middleware; 4 | use crate::middleware::logger::LoggerMiddleware; 5 | 6 | use crate::http::*; 7 | 8 | 9 | use std::sync::Mutex; 10 | use std::sync::Arc; 11 | use std::cell::RefCell; 12 | use httparse; 13 | use serde::{Deserialize, Serialize}; 14 | use serde_json; 15 | use std::collections::HashMap; 16 | use std::fmt; 17 | use std::future::Future; 18 | use std::net::SocketAddr; 19 | use std::pin::Pin; 20 | use std::{fs, io::prelude::*, thread, time::Duration}; 21 | use tokio::io::{AsyncReadExt, AsyncWriteExt, BufReader}; 22 | use tokio::net::{TcpListener, TcpStream}; 23 | use tokio::sync::mpsc::{channel, Sender}; 24 | 25 | use std::rc::Rc; 26 | 27 | pub type FutureResponse<'a> = Pin> + Send + 'a>>; 28 | 29 | pub type RouteHandler = fn(Request) -> FutureResponse<'static>; 30 | 31 | 32 | #[derive(Clone)] 33 | pub struct Server { 34 | address: SocketAddr, 35 | routes: HashMap, 36 | middleware: Arc>>, 37 | 38 | } 39 | 40 | 41 | 42 | 43 | 44 | #[derive(Eq, Hash, PartialEq,Clone)] 45 | struct Route { 46 | method: HttpMethod, 47 | path: String, 48 | } 49 | impl Server { 50 | pub async fn run(&self) -> std::io::Result<()> { 51 | let address = self.address; 52 | let listener = TcpListener::bind(address).await?; 53 | println!("Server listening on {}", address.to_string()); 54 | 55 | 56 | loop { 57 | let (mut stream, _) = listener.accept().await?; 58 | let routes = self.routes.clone(); 59 | let middleware = Arc::clone(&self.middleware); 60 | 61 | tokio::spawn(async move { 62 | let mut buffer = [0; 1024]; 63 | let _ = stream.read(&mut buffer).await.unwrap(); 64 | 65 | let request = parse_request(&buffer).unwrap(); 66 | 67 | let future_response = handle_route(request, &routes, &middleware).await; 68 | 69 | match future_response.await { 70 | Ok(response) => { 71 | 72 | let response_string = format!( 73 | "HTTP/1.1 {} {}\r\n{}\r\n\r\n{}", 74 | response.status_code, 75 | response.status_text, 76 | response.headers 77 | .iter() 78 | .map(|(key, value)| format!("{}: {}", key, value)) 79 | .collect::>() 80 | .join("\r\n"), 81 | response.body.unwrap_or_default() 82 | ); 83 | 84 | 85 | stream.write(response_string.as_bytes()).await.unwrap(); 86 | stream.flush().await.unwrap(); 87 | // Do something with the response 88 | } 89 | 90 | Err(e) => {} 91 | } 92 | }); 93 | } 94 | } 95 | } 96 | 97 | fn parse_request(buffer: &[u8]) -> Result> { 98 | let mut headers = [httparse::EMPTY_HEADER; 16]; 99 | let mut req = httparse::Request::new(&mut headers); 100 | 101 | let res = match req.parse(&buffer)? { 102 | httparse::Status::Complete(amt) => amt, 103 | httparse::Status::Partial => { 104 | return Err("Request is incomplete".into()); 105 | } 106 | }; 107 | 108 | let method = match req.method.ok_or("Method not found")? { 109 | "GET" => HttpMethod::GET, 110 | "POST" => HttpMethod::POST, 111 | "PUT" => HttpMethod::PUT, 112 | "DELETE" => HttpMethod::DELETE, 113 | _ => HttpMethod::OTHER(req.method.unwrap().to_string()), 114 | }; 115 | let uri = req.path.ok_or("URI not found")?.to_string(); 116 | let version = req.version.ok_or("Version not found")?.to_string(); 117 | 118 | let mut headers_map = HashMap::new(); 119 | for header in req.headers.iter() { 120 | let name = header.name.to_string(); 121 | let value = std::str::from_utf8(header.value)?.to_string(); 122 | headers_map.insert(name, value); 123 | } 124 | 125 | let body = if res < buffer.len() { 126 | Some(String::from_utf8(buffer[res..].to_vec())?) 127 | } else { 128 | None 129 | }; 130 | 131 | 132 | Ok(Request { 133 | method, 134 | uri, 135 | version, 136 | headers: headers_map, 137 | body, 138 | }) 139 | } 140 | 141 | async fn handle_route<'a>(request:Request, routes: &'a HashMap, middleware: &'a Vec>) -> FutureResponse<'a>{ 142 | // Find the route handler based on the request path and call it 143 | if let Some(handler) = routes.get(&Route { method: request.method.clone(), path: request.uri.clone() }) { 144 | 145 | 146 | 147 | for mw in middleware { 148 | 149 | mw.on_request(request.clone()); 150 | 151 | } 152 | 153 | handler(request) 154 | } else { 155 | Box::pin(async move { 156 | 157 | Err(HttpError::InternalServerError( 158 | HttpStatusCode::InternalServerError, 159 | "Internal Server Error" 160 | )) 161 | 162 | }) 163 | 164 | } 165 | } 166 | 167 | pub struct ServerBuilder { 168 | address: Option, 169 | routes: Option>, 170 | middleware: Option>>>, 171 | } 172 | 173 | impl ServerBuilder { 174 | pub fn new() -> Self { 175 | Self { 176 | address: None, 177 | routes: Some(HashMap::new()), 178 | middleware: Some(Arc::new(Vec::new())) 179 | } 180 | } 181 | 182 | pub fn bind(mut self, socket: SocketAddr) -> Self { 183 | self.address = Some(socket); 184 | self 185 | } 186 | 187 | pub fn route(mut self, path: &str, method:HttpMethod, handler: RouteHandler) -> Self { 188 | if let Some(routes) = self.routes.as_mut() { 189 | routes.insert(Route{path:String::from(path),method}, handler); 190 | } else { 191 | let mut map = HashMap::new(); 192 | map.insert(Route{path:String::from(path),method}, handler); 193 | self.routes = Some(map); 194 | } 195 | self 196 | } 197 | pub fn accept(mut self, middleware: M) -> Self { 198 | if let Some(middleware_vec) = self.middleware.as_mut() { 199 | Arc::get_mut(middleware_vec) 200 | .expect("Cannot add middleware because there are other references to this Arc") 201 | .push(Box::new(middleware)); 202 | } else { 203 | self.middleware = Some(Arc::new(vec![Box::new(middleware)])); 204 | } 205 | 206 | self 207 | } 208 | 209 | 210 | 211 | // String should be error 212 | pub fn build(self) -> Result { 213 | let address = self.address.ok_or("Address not set")?; 214 | let routes = self.routes.ok_or("Routes Uninitalized")?; 215 | 216 | let middleware = self.middleware.ok_or("MIddleware Error").unwrap(); 217 | Ok(Server { address, routes,middleware }) 218 | } 219 | } --------------------------------------------------------------------------------