Archived
3
0

Create build system (#1)

This commit is contained in:
Matěj Žucha
2025-03-09 18:22:14 +00:00
committed by Ondřej Mekina
parent 5bb655b7de
commit 77c8deabc8
36 changed files with 1860 additions and 478 deletions

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
target/
dst/

848
Cargo.lock generated Normal file
View File

@@ -0,0 +1,848 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "askama"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b79091df18a97caea757e28cd2d5fda49c6cd4bd01ddffd7ff01ace0c0ad2c28"
dependencies = [
"askama_derive",
"askama_escape",
"humansize",
"num-traits",
"percent-encoding",
]
[[package]]
name = "askama_derive"
version = "0.12.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19fe8d6cb13c4714962c072ea496f3392015f0989b1a2847bb4b2d9effd71d83"
dependencies = [
"askama_parser",
"basic-toml",
"mime",
"mime_guess",
"proc-macro2",
"quote",
"serde",
"syn",
]
[[package]]
name = "askama_escape"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "619743e34b5ba4e9703bba34deac3427c72507c7159f5fd030aea8cac0cfe341"
[[package]]
name = "askama_parser"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acb1161c6b64d1c3d83108213c2a2533a342ac225aabd0bda218278c2ddb00c0"
dependencies = [
"nom",
]
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets",
]
[[package]]
name = "basic-toml"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a"
dependencies = [
"serde",
]
[[package]]
name = "bincode"
version = "1.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad"
dependencies = [
"serde",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
[[package]]
name = "bumpalo"
version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
version = "1.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be714c154be609ec7f5dad223a33bf1482fff90472de28f7362806e6d4832b8c"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-link",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "crc32fast"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
dependencies = [
"cfg-if",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
dependencies = [
"byteorder",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "humansize"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6cb51c9a029ddc91b07a787f1d86b53ccfa49b0e86688c946ebe8d3555685dd7"
dependencies = [
"libm",
]
[[package]]
name = "iana-time-zone"
version = "0.1.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "inferium"
version = "0.1.0"
dependencies = [
"proc",
"tokio",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "itoa"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
[[package]]
name = "js-sys"
version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
"once_cell",
"wasm-bindgen",
]
[[package]]
name = "libc"
version = "0.2.170"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "875b3680cb2f8f71bdcf9a30f38d48282f5d3c95cbf9b3fa57269bb5d5c06828"
[[package]]
name = "libm"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "mime"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "mime_guess"
version = "2.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e"
dependencies = [
"mime",
"unicase",
]
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e3e04debbb59698c15bacbb6d93584a8c0ca9cc3213cb423d31f760d8843ce5"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"wasi",
"windows-sys",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core 0.8.6",
]
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core 0.9.10",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"winapi",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.5.10",
"smallvec",
"windows-targets",
]
[[package]]
name = "percent-encoding"
version = "2.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "proc"
version = "0.1.0"
[[package]]
name = "proc-macro2"
version = "1.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.39"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1f1914ce909e1658d9907913b4b91947430c7d9be598b15a1912935b8c04801"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.5.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8c0c260b63a8219631167be35e6a988e9554dbd323f8bd08439c8ed1302bd1"
dependencies = [
"bitflags 2.9.0",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustversion"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
[[package]]
name = "ryu"
version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.218"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "sled"
version = "0.34.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f96b4737c2ce5987354855aed3797279def4ebf734436c6aa4552cf8e169935"
dependencies = [
"crc32fast",
"crossbeam-epoch",
"crossbeam-utils",
"fs2",
"fxhash",
"libc",
"log",
"parking_lot 0.11.2",
]
[[package]]
name = "smallvec"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd"
[[package]]
name = "socket2"
version = "0.5.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "syn"
version = "2.0.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e02e925281e18ffd9d640e234264753c43edc62d64b2d4cf898f1bc5e75f3fc2"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tokio"
version = "1.43.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d61fa4ffa3de412bfea335c6ecff681de2b609ba3c77ef3e00e521813a9ed9e"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"parking_lot 0.12.3",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicase"
version = "2.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539"
[[package]]
name = "unicode-ident"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
dependencies = [
"unicode-ident",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-link"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zmp24"
version = "0.1.0"
dependencies = [
"askama",
"bincode",
"chrono",
"inferium",
"serde",
"serde_json",
"sled",
"tokio",
]

21
Cargo.toml Normal file
View File

@@ -0,0 +1,21 @@
[package]
name = "zmp24"
version = "0.1.0"
edition = "2021"
[profile.release]
strip = true
lto = true
[dependencies]
askama = "0.12.1"
bincode = "1.3.3"
chrono = "0.4.40"
inferium = { path = "./lib/inferium", features = ["async", "tokio-net", "tokio-unixsocks"] }
serde = { version = "1.0.218", features = ["derive"] }
serde_json = "1.0.140"
sled = "0.34.7"
tokio = { version = "1.43.0", features = ["full"] }
[features]
dev = []

42
Makefile Normal file
View File

@@ -0,0 +1,42 @@
.PHONY: build
build: \
target/release/zmp24
ARCH := $(shell uname -m)
MAIN_RS_SRCS := $(shell find src -type f -regex '^.*\.rs$$') Cargo.toml \
client/dst/index.html client/dst/script.js client/dst/style.css \
config.json
SEARCH_REPLACE := lib/search_and_replace/target/release/search_and_replace
.PHONY: run
run: $(MAIN_RS_SRCS)
DB_PATH="$$(jq -r .dev.db config.json)" \
BIND_TO="$$(jq -r .dev.bind_to config.json)" \
cargo run --features dev
.PHONY: clean
clean: client_clean
cargo clean
cd lib/search_and_replace && cargo clean
rm -rf dst
dst:
mkdir dst
include client/client.mk
include image/image.mk
target/release/zmp24: $(MAIN_RS_SRCS)
DB_PATH="$$(jq -r .prod.db config.json)" \
BIND_TO="$$(jq -r .prod.sock_path config.json)" \
cargo build --release
target/$(ARCH)-unknown-linux-musl/release/zmp24: $(MAIN_RS_SRCS)
DB_PATH="$$(jq -r .prod.db config.json)" \
BIND_TO="$$(jq -r .prod.sock_path config.json)" \
cargo build --target $(ARCH)-unknown-linux-musl --release
$(SEARCH_REPLACE): lib/search_and_replace/src/main.rs lib/search_and_replace/Cargo.toml
cd lib/search_and_replace && cargo build --release

2
askama.toml Normal file
View File

@@ -0,0 +1,2 @@
[general]
dirs = ["client/dst/"]

2
client/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
node_modules/
dst/

BIN
client/bun.lockb Executable file

Binary file not shown.

33
client/client.mk Normal file
View File

@@ -0,0 +1,33 @@
client/node_modules:
bun install --cwd client
client/dst:
mkdir client/dst
client/dst/%.html: \
client/src/%.html \
client/node_modules \
client/dst \
$(SEARCH_REPLACE)
cat $< | bun run --cwd client html-minifier \
--collapse-inline-tag-whitespace \
--collapse-boolean-attributes \
--collapse-whitespace \
--remove-attribute-quotes \
--remove-comments \
--remove-redundant-attributes | \
$(SEARCH_REPLACE) \
'##LT##' '<' \
'##GT##' '>' \
> $@
client/dst/%.js: client/src/%.ts client/node_modules client/dst
bun build $< --minify --outfile $@
client/dst/%.css: client/src/%.scss client/node_modules client/dst
cat $< | bun run --cwd client sass --stdin --style compressed > $@
.PHONY: client_clean
client_clean:
rm -rf client/dst
rm -rf client/node_modules

13
client/package.json Normal file
View File

@@ -0,0 +1,13 @@
{
"type": "module",
"devDependencies": {
"@types/bun": "latest"
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
"html-minifier": "^4.0.0",
"sass": "^1.85.1"
}
}

31
client/src/index.html Normal file
View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Test page</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<h1>This is a test page</h1>
<p>zmp24 rizzin 2b tru σ pookie fr gooner skibidi</p>
<p>This page was visited {{ visit_count }} times before you showed up.</p>
<p>
Here is {{ visit_count }} dot{% if visit_count != 1 %}s{% endif %}:
<!--
Tohle je skibidi sigma můj úžasný build systém.
Ať neděláme gulášek v syntax highlightingu.
Ty replacementy jsou v `client/client.mk`.
-->
{% if visit_count ##LT## 100 %}
{% for _ in 0..visit_count %}
.
{% endfor %}
{% else %}
That's too much dots to display. So f u.
{% endif %}
</p>
<script src="script.js"></script>
</body>
</html>

16
client/src/script.ts Normal file
View File

@@ -0,0 +1,16 @@
type Sigma = string;
type ToMew = "Σ" | Sigma;
async function skibidi(): Promise<ToMew> {
return "fr fr";
}
async function boomer(): Promise<never> {
return new Promise(() => {});
}
async function alpha(): Promise<void> {
console.log(await Promise.any([skibidi(), boomer()]) + " nocap");
}
alpha();

10
client/src/style.scss Normal file
View File

@@ -0,0 +1,10 @@
:root {
color-scheme: light dark;
}
$color-me-daddy: light-dark(black, white);
body {
font-family: Arial, Helvetica, sans-serif;
color: $color-me-daddy;
}

23
client/tsconfig.json Normal file
View File

@@ -0,0 +1,23 @@
{
"compilerOptions": {
"lib": ["ESNext", "DOM"],
"target": "ESNext",
"module": "ESNext",
"moduleDetection": "force",
"jsx": "react-jsx",
"allowJs": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": false,
"noEmit": true,
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false
}
}

16
config.json Normal file
View File

@@ -0,0 +1,16 @@
{
"prod": {
"sock_path": "/run/zmp24.sock",
"db": "/data/db"
},
"dev": {
"bind_to": "[::1]:8080",
"db": "/tmp/zmp24db"
},
"image": {
"name": "zmp24",
"version": "0.1"
}
}

7
image/image.dockerfile Normal file
View File

@@ -0,0 +1,7 @@
FROM docker.io/alpine:latest
COPY ./zmp24 /usr/bin/zmp24
COPY ./run.sh /run.sh
RUN apk update && apk add gcompat && mkdir /data
CMD [ "/run.sh" ]

17
image/image.mk Normal file
View File

@@ -0,0 +1,17 @@
.PHONY: image
image: dst/image/zmp24 dst/image/Containerfile dst/image/run.sh config.json
cd dst/image && podman build . -t \
"$$(jq -r .image.name ../../config.json):$$(jq -r .image.version ../../config.json)"
dst/image: dst
mkdir dst/image
dst/image/Containerfile: dst/image image/image.dockerfile
ln -sf $$(pwd)/image/image.dockerfile dst/image/Containerfile
dst/image/run.sh: dst/image image/run.sh
ln -f image/run.sh dst/image/run.sh
chmod +x dst/image/run.sh
dst/image/zmp24: dst/image target/$(ARCH)-unknown-linux-musl/release/zmp24
ln -f target/$(ARCH)-unknown-linux-musl/release/zmp24 dst/image/zmp24

10
image/run.sh Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/sh
_term() {
kill -TERM "$server_pid" 2>/dev/null
}
trap _term SIGTERM
/usr/bin/zmp24 &
server_pid=$!
wait "$server_pid"

View File

@@ -1,8 +0,0 @@
/target
# Added by cargo
#
# already existing elements were commented out
#/target

View File

@@ -1,60 +0,0 @@
#![feature(test)]
extern crate test;
use std::collections::HashMap;
use test::Bencher;
extern crate inferium;
use inferium::h1::{SyncClient, Response, ResponseHead, ProtocolVariant};
use inferium::{Status, HeaderValue};
use inferium::TestSyncStream;
fn parse_response_sync_inner() {
let src = "HTTP/1.1 200 OK\r\nserver: inferium\r\n\r\n".as_bytes().to_vec();
let stream = TestSyncStream::<4>::new(&src);
let mut client = SyncClient::<TestSyncStream<4>>::new(stream);
let target = Response::HeadersOnly(ResponseHead::new(
Status::Ok,
ProtocolVariant::HTTP1_1,
HashMap::from([
("server".into(), HeaderValue::new(vec!["inferium".to_string()]))
])
));
assert_eq!(client.receive_response().unwrap(), target);
}
fn parse_response_sync_inner_body() {
let mut src = "HTTP/1.1 200 OK\r\ncontent-length: 50\r\n\r\n".as_bytes().to_vec();
src.extend_from_slice(b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
let stream = TestSyncStream::<4>::new(&src);
let mut client = SyncClient::<TestSyncStream<4>>::new(stream);
let target_head = ResponseHead::new(
Status::Ok,
ProtocolVariant::HTTP1_1,
HashMap::from([
("content-length".into(), HeaderValue::new(vec!["50".to_string()]))
])
);
let Response::WithSizedBody((h, mut b)) = client.receive_response().unwrap() else {
panic!();
};
let b = b.recv_all().unwrap();
assert_eq!(h, target_head);
assert_eq!(b, b"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
#[bench]
fn parse_response_sync(b: &mut Bencher) {
b.bytes = 37;
b.iter(|| {
parse_response_sync_inner();
});
}
#[bench]
fn parse_response_sync_with_body(b: &mut Bencher) {
b.bytes = 39 + 13;
b.iter(|| {
parse_response_sync_inner_body();
});
}

View File

@@ -1,43 +0,0 @@
#![feature(test)]
extern crate test;
use test::Bencher;
extern crate inferium;
use inferium::Status;
#[bench]
fn baseline(b: &mut Bencher) {
fn status_test_from_slice(raw: &[u8]) -> Result<Status, ()> {
match raw {
b"200" => Ok(Status::Ok),
b"404" => Ok(Status::NotFound),
_ => Err(()),
}
}
b.iter(|| {
assert_eq!(status_test_from_slice(b"200".as_slice()), Ok(Status::Ok));
});
}
#[bench]
fn valid_ok(b: &mut Bencher) {
b.iter(|| {
assert_eq!(Status::try_from(b"200".as_slice()), Ok(Status::Ok));
});
}
#[bench]
fn valid_internal_server_error(b: &mut Bencher) {
b.iter(|| {
assert_eq!(Status::try_from(b"500".as_slice()), Ok(Status::InternalServerError));
});
}
#[bench]
fn invalid(b: &mut Bencher) {
b.iter(|| {
assert!(Status::try_from(b"690".as_slice()).is_err());
})
}

View File

@@ -1,94 +0,0 @@
// This is an async port of `examples/simple_server.rs`. Please see that example first.
//
// Features `async` and `tokio-net` must be enabled for this example to compile.
// It is also possible to enable feature `full` (which will enable all the features).
use std::{collections::HashMap, net::SocketAddr};
use tokio::net::{TcpListener, TcpStream};
use inferium::{
h1::{ProtocolVariant, Request, ResponseHead, ServerSendError, AsyncServer},
HeaderKey, Method, Status, TokioInet
};
#[tokio::main]
async fn main() {
let listener = TcpListener::bind("localhost:8080").await.unwrap();
loop {
let (conn, addr) = listener.accept().await.unwrap();
// Here we are creating a new asynchronous task for every client.
// This will fork off in an asynchronous manner and won't block our accept loop.
tokio::task::spawn(async move {
// We created this new async block, so we need to `.await` on this function to propagate
// the future from the function to the top of the spawned task (this async block).
handle_client(conn, addr).await;
});
// You can now handle multiple clients at once... congratulations.
}
}
async fn handle_client(conn: TcpStream, addr: SocketAddr) {
println!("connection from {addr:?}");
let mut server_handler = AsyncServer::<TokioInet>::new(TokioInet::new(conn));
// When receiving or sending - we call the same functions with `.await` appended (in an async
// context). This will automatically poll the returned future from the top of the context.
// The polling is handled by tokio here - so we don't need to worry about it.
while let Ok(request) = server_handler.receive_request().await {
match handle_request(request, addr) {
Ok((h, b)) => if let Err(_) = send_response(h, b, &mut server_handler).await { break; },
Err(()) => break,
}
};
println!("ended connection for {addr:?}");
}
fn handle_request<T>(
req: Request<T>, addr: SocketAddr
) -> Result<(ResponseHead, &'static [u8]), ()> {
let Request::HeadersOnly(headers) = req else {
return Err(());
};
println!("req from {addr:?}: {headers}");
const OK_RESPONSE: &[u8] = b"<!DOCTYPE html>
<html>
<body>
<h1>Hello, world!</h1>
<p>Hello from inferium.</p>
</body>
</html>";
const NOT_FOUND_RESPONSE: &[u8] = b"<!DOCTYPE html>
<html>
<body>
<h1>Not found</h1>
<p>This page was not found</p>
</body>
</html>";
Ok(match (headers.method(), headers.uri().path()) {
(&Method::GET, "/") => (ResponseHead::new(
Status::Ok,
ProtocolVariant::HTTP1_0,
HashMap::from([
(HeaderKey::SERVER, "inferium".parse().unwrap()),
(HeaderKey::CONTENT_LENGTH, OK_RESPONSE.len().into()),
])
), OK_RESPONSE),
_ => (ResponseHead::new(
Status::NotFound,
ProtocolVariant::HTTP1_0,
HashMap::from([
(HeaderKey::SERVER, "inferium".parse().unwrap()),
(HeaderKey::CONTENT_LENGTH, NOT_FOUND_RESPONSE.len().into()),
])
), NOT_FOUND_RESPONSE),
})
}
async fn send_response(
response: ResponseHead, body: &[u8], conn: &mut AsyncServer<TokioInet>
)-> Result<(), ServerSendError> {
conn.send_response(&response).await?;
conn.send_body_bytes(body).await.map_err(|e| e.try_into().unwrap())
}

View File

@@ -1,88 +0,0 @@
// This is a port of a client from `examples/start_here.rs`. Please see that example first.
// Also... maybe brush up on some async tasks since we are going to need them here (there is an
// example on async with inferium in `examples/going_async.rs`).
//
// Features `async`, `tokio-net` and `webpki-roots` dependency must be enabled for this example to
// compile. We recommend enabling the `dev` feature when running this example.
use std::{collections::HashMap, sync::Arc};
use tokio::net::TcpStream;
use tokio_rustls::{
rustls::{
pki_types::ServerName,
ClientConfig,
RootCertStore
},
TlsConnector,
TlsStream
};
use inferium::{
h1::{
ProtocolVariant,
RequestHead,
Response,
AsyncClient
},
HeaderKey,
Method,
TokioRustls
};
async fn run_tls_handshake(raw_stream: TcpStream) -> TlsStream<TcpStream> {
let mut root_certs = RootCertStore::empty();
root_certs.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
let config = ClientConfig::builder()
.with_root_certificates(root_certs)
.with_no_client_auth();
let connector = TlsConnector::from(Arc::new(config));
let verify_server_name = ServerName::try_from("zumepro.cz").unwrap();
TlsStream::Client(connector.connect(verify_server_name, raw_stream).await.unwrap())
}
#[tokio::main]
async fn main() {
let stream = TcpStream::connect("zumepro.cz:443").await.unwrap();
let stream = run_tls_handshake(stream).await;
let conn = TokioRustls::new(stream);
let mut client = AsyncClient::<TokioRustls>::new(conn);
let to_send = RequestHead::new(
Method::GET, "/".parse().unwrap(), ProtocolVariant::HTTP1_1,
HashMap::from([
(HeaderKey::USER_AGENT, "Mozilla/5.0 (inferium)".parse().unwrap()),
(HeaderKey::HOST, "zumepro.cz".parse().unwrap()),
(HeaderKey::CONNECTION, "close".parse().unwrap())
])
);
println!("----------> Sending\n\n{to_send}\n");
client.send_request(&to_send).await.unwrap();
let response = client.receive_response().await.unwrap();
let (header, body) = match response {
Response::HeadersOnly(h) => (h, None),
Response::WithSizedBody((_, _)) => panic!(),
Response::WithChunkedBody((h, b)) => (h, Some(b)),
};
println!("----------< Received\n\n{header}\n");
if let Some(mut body) = body {
// Since our zumepro server sends bodies chunked - we will need to handle it.
// This simple loop just collects all the chunks into the res vector.
let mut res = Vec::new();
while let Some(mut chunk) = body.get_chunk_async().await.unwrap() {
// Now here is a difference between sync and async.
//
// For easy body manipulation and no redundant trait pollution, a body within an
// asynchronous stream can be sent/received using the same methods as a synchronous one,
// but with the suffix `_async`.
res.append(&mut chunk.recv_all_async().await.unwrap());
}
println!(
"----------< Body\n\n{:?}\n",
std::str::from_utf8(&res).unwrap()
);
}
}

View File

@@ -1,84 +0,0 @@
use std::{collections::HashMap, net::{SocketAddr, TcpListener}};
use inferium::{
h1::{ProtocolVariant, Request, ResponseHead, ServerSendError, SyncServer},
HeaderKey, Method, Status, StdInet
};
fn main() {
let listener = TcpListener::bind("localhost:8080").unwrap();
loop {
let (conn, addr) = listener.accept().unwrap();
println!("connection from {addr:?}");
let mut server_handler = SyncServer::<StdInet>::new(StdInet::new(conn));
// We'll serve the client as long as it sends valid requests.
// Note that this will effectively block other clients.
while let Ok(request) = server_handler.receive_request() {
// This matching is here to provide a way of controlling the while loop.
match handle_request(request, addr) {
Ok((h, b)) => if let Err(_) = send_response(h, b, &mut server_handler) { break; },
Err(()) => break,
}
};
println!("ended connection for {addr:?}");
}
}
fn handle_request<T>(
req: Request<T>, addr: SocketAddr
) -> Result<(ResponseHead, &'static [u8]), ()> {
let Request::HeadersOnly(headers) = req else {
// We will not handle POST requests with bodies - so let's tell the client to f*ck off.
return Err(());
};
println!("req from {addr:?}: {headers}");
const OK_RESPONSE: &[u8] = b"<!DOCTYPE html>
<html>
<body>
<h1>Hello, world!</h1>
<p>Hello from inferium.</p>
</body>
</html>";
const NOT_FOUND_RESPONSE: &[u8] = b"<!DOCTYPE html>
<html>
<body>
<h1>Not found</h1>
<p>This page was not found</p>
</body>
</html>";
// The URI can contain both path and parameters - so we're just getting the path here.
Ok(match (headers.method(), headers.uri().path()) {
// The ok response with our index page
(&Method::GET, "/") => (ResponseHead::new(
Status::Ok,
ProtocolVariant::HTTP1_0,
HashMap::from([
(HeaderKey::SERVER, "inferium".parse().unwrap()),
(HeaderKey::CONTENT_LENGTH, OK_RESPONSE.len().into()),
])
), OK_RESPONSE),
// The not found response with an example not found page
_ => (ResponseHead::new(
Status::NotFound,
ProtocolVariant::HTTP1_0,
HashMap::from([
(HeaderKey::SERVER, "inferium".parse().unwrap()),
(HeaderKey::CONTENT_LENGTH, NOT_FOUND_RESPONSE.len().into()),
])
), NOT_FOUND_RESPONSE),
})
}
fn send_response(
response: ResponseHead, body: &[u8], conn: &mut SyncServer<StdInet>
)-> Result<(), ServerSendError> {
conn.send_response(&response)?;
// The send body can fail on an I/O error or if the content-length header does not match the
// actual sent length in this scenario. But we know that we have the correct length so with
// `.try_into().unwrap()` we tell inferium to convert the error and panic on (not so much)
// possible body length discrepancy.
conn.send_body_bytes(body).map_err(|e| e.try_into().unwrap())
}

View File

@@ -1,101 +0,0 @@
// Hello, and welcome to inferium. A performance-oriented small HTTP library written in Rust that
// keeps you (the user) in charge.
// Let's first import some necessary things.
// In inferium - HashMaps are used to store uri parameters and headers.
use std::collections::HashMap;
// TcpStream is needed if we want to connect to the internet.
use std::net::TcpStream;
use inferium::{
// The h1 module contains all the protocol specific things for HTTP/1.(0/1).
h1::{
// ProtocolVariant contains variants with the protocol versions supported in this module:
// - HTTP/1.1
// - HTTP/1.0
ProtocolVariant,
// RequestHead contains headers and the HTTP request headline (method, path, protocol).
RequestHead,
// Response here is a wrapper for a response that can have the following:
// - Headers only
// - Headers and body (with a known length or chunked)
//
// ! The body in the response is not yet collected. It is up to you if you wish to discard
// the connection or receive and collect the response body into some structure.
//
// The same things here go for the Request object which is nearly the same except that the
// headline contains protocol and status instead.
Response,
// Sync client is a stream wrapper that helps us keep track of the open connection and
// perform request/response operations.
//
// The server equivalent is SyncServer.
SyncClient
},
// Header key contains various known header keys, but can also store arbitrary (unknown) header
// key in the OTHER variant.
HeaderKey,
Method,
// StdInet here is a stream wrapper that allows the TcpStream to be used by inferium. There is
// also a unix socket equivalent and some asynchronous io wrappers.
StdInet
};
fn main() {
// Let's first create a connection... nothing weird here.
let conn = StdInet::new(TcpStream::connect("zumepro.cz:80").unwrap());
// And a client...
let mut client = SyncClient::<StdInet>::new(conn);
// Now let's create a request to send
let to_send = RequestHead::new(
// The path here is parsed into an HTTP path object (which also supports parameters)
// I'm using HTTP/1.0 in this example as HTTP/1.1 automatically infers a compatibility with
// chunked encoding (which I'm not even trying to handle here).
Method::GET, "/".parse().unwrap(), ProtocolVariant::HTTP1_0,
HashMap::from([
// All headers are HeaderKey - HeaderValue pairs. We can parse the header value into
// the desired object.
//
// Constructing arbitrary header key is supported using the OTHER variant - however
// it's not recommended as a violation of the HTTP protocol can happen.
//
// If you really want to construct an arbitrary header key - please carefully check
// that all of the symbols are valid.
(HeaderKey::USER_AGENT, "Mozilla/5.0 (inferium)".parse().unwrap()),
(HeaderKey::HOST, "zumepro.cz".parse().unwrap()),
])
);
println!("----------> Sending\n\n{to_send}\n");
// Let's send the request - this is pretty straightforward.
client.send_request(&to_send).unwrap();
// As is receiving a response.
let response = client.receive_response().unwrap();
// Now (as we discussed earlier) - the response can have a body.
// In this example we'll try to handle a basic body with a known size.
let (header, body) = match response {
// Extracting the headers if no body is present.
Response::HeadersOnly(h) => (h, None),
// Extracting both the headers and the body if body is present.
Response::WithSizedBody((h, b)) => (h, Some(b)),
// We will not handle chunked responses in this example.
Response::WithChunkedBody((_, _)) => panic!(),
};
// inferium kindly provides a simple way to print the head of a request/response.
// It will be formatted pretty close to the actual protocol plaintext representation.
println!("----------< Received\n\n{header}\n");
// And finally... if we have a body, we'll print it.
if let Some(mut body) = body {
println!(
"----------< Body\n\n{:?}\n",
// A body is always returned in bytes. It's up to you to decode it however you see fit.
std::str::from_utf8(&mut body.recv_all().unwrap()).unwrap()
);
}
// And you're done. Come on... try to run it.
}

View File

@@ -158,6 +158,14 @@ impl HeaderValue {
self.inner.push(val);
}
/// Create a new unchecked value.
/// This is faster, but can cause protocol violation.
/// Please avoid putting unchecked content here.
#[inline]
pub fn new_unchecked(inner: String) -> Self {
Self { inner: vec![inner] }
}
/// Query the first entry for this header key.
///
/// See the documentation of [`HeaderValue`] for more information.

View File

@@ -1,3 +1,5 @@
#![allow(dead_code)]
pub mod settings;
mod io;

View File

@@ -322,6 +322,28 @@ pub enum Request<'a, T> {
WithChunkedBody((RequestHead, Incoming<'a, ChunkedIn<'a, PrependableStream<T>>>)),
}
impl<T> Response<'_, T> {
#[inline]
pub fn head(&self) -> &ResponseHead {
match self {
Self::HeadersOnly(h) => h,
Self::WithSizedBody((h, _)) => h,
Self::WithChunkedBody((h, _)) => h,
}
}
}
impl<T> Request<'_, T> {
#[inline]
pub fn head(&self) -> &RequestHead {
match self {
Self::HeadersOnly(h) => h,
Self::WithSizedBody((h, _)) => h,
Self::WithChunkedBody((h, _)) => h,
}
}
}
fn get_outgoing_req_content_length(head: &RequestHead) -> Result<Option<usize>, ClientSendError> {
let Some(l) = head.headers.get(&HeaderKey::CONTENT_LENGTH) else {
return Ok(None);

7
lib/search_and_replace/Cargo.lock generated Normal file
View File

@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "search_and_replace"
version = "0.1.0"

View File

@@ -0,0 +1,10 @@
[package]
name = "search_and_replace"
version = "0.1.0"
edition = "2021"
[profile.release]
strip = true
lto = true
[dependencies]

View File

@@ -0,0 +1,386 @@
use std::{env::args, io::Read};
#[derive(Debug, PartialEq)]
struct JumpTable {
jumps: Vec<usize>,
}
impl JumpTable {
/// Returns the pointer for the next char (if this char is matched).
fn resolve_char(
pat: &Vec<char>,
ptr: &usize,
cur_char: &char,
) -> usize {
if pat.get(*ptr).unwrap() == cur_char {
return *ptr + 1;
}
if pat.get(0).unwrap() == cur_char {
return 1;
}
return 0;
}
pub fn new(pat: &Vec<char>) -> Self {
if pat.len() == 0 {
return Self { jumps: vec![] };
}
let mut ptr = 0_usize;
let mut jumps = vec![0];
for cur_char in pat.iter().skip(1) {
ptr = JumpTable::resolve_char(pat, &ptr, cur_char);
jumps.push(ptr);
}
Self { jumps }
}
pub fn search(&self, pat: &Vec<char>, needle: &char, ptr: &usize) -> usize {
let mut ptr = *ptr;
while ptr != 0 {
if pat.get(ptr).unwrap() == needle {
break;
}
ptr = *self.jumps.get(ptr.saturating_sub(1)).unwrap();
}
ptr
}
}
#[derive(Debug, PartialEq)]
struct Pattern {
pat: Vec<char>,
jt: JumpTable,
ptr: usize,
}
impl Pattern {
pub fn new(pat: String) -> Result<Self, ()> {
if pat.len() == 0 {
return Err(());
}
let pat = pat.chars().collect::<Vec<_>>();
let jt = JumpTable::new(&pat);
Ok(Self {
pat,
jt,
ptr: 0,
})
}
pub fn search_and_update(&mut self, cur_char: &char) -> bool {
if self.pat.get(self.ptr).unwrap() == cur_char {
self.ptr += 1;
return if self.ptr == self.pat.len() { true } else { false };
}
let found = self.jt.search(&self.pat, &cur_char, &self.ptr);
self.ptr = found;
false
}
pub fn reset_ptr(&mut self) {
self.ptr = 0;
}
pub fn len(&self) -> usize {
self.pat.len()
}
}
#[derive(Debug, PartialEq, Eq)]
struct Match {
pub pat_id: usize,
pub start_pos: usize,
pub len: usize,
}
impl Match {
pub fn new(pat_id: usize, start_pos: usize, len: usize) -> Self {
Self { pat_id, start_pos, len }
}
}
/// [`MPSearch`] assumes that each [`Match`] will be replaced later. So this will match the first
/// pattern only and reset all the pointers (including the matched one) to zero.
#[derive(Debug)]
struct MPSearch {
pats: Vec<Pattern>,
}
impl MPSearch {
/// The [`Pattern`]s will be searched in the order as they are in the [`Vec`].
pub fn new(pats: Vec<Pattern>) -> Self {
Self { pats }
}
/// Reset pointers of all patterns
fn reset_all_ptrs(&mut self) {
for pat in self.pats.iter_mut() {
pat.reset_ptr();
}
}
/// Returns true if ANY pattern ended a match at this pos
///
/// This will also clear all ptrs on a match
fn search_at_pos(&mut self, cur_char: &char, pos: &usize) -> Option<Match> {
let mut cur_match = None;
for (idx, pat) in self.pats.iter_mut().enumerate() {
if pat.search_and_update(cur_char) {
cur_match = Some(Match::new(idx, pos.saturating_sub(pat.len() - 1), pat.len()));
break;
}
}
if let Some(_) = cur_match {
self.reset_all_ptrs();
}
cur_match
}
/// Perform a search on the haystack - returning the ordered [`Match`]es.
pub fn search(&mut self, haystack: &String) -> Vec<Match> {
let mut res = Vec::new();
for (cur_idx, cur_char) in haystack.chars().enumerate() {
if let Some(cur_match) = self.search_at_pos(&cur_char, &cur_idx) {
res.push(cur_match);
}
}
res
}
}
macro_rules! skip_n {
($iter: ident, $n: expr) => {
for _ in 0..$n {
$iter.next().ok_or(())?;
}
}
}
macro_rules! append_n {
($iter: ident, $target: ident, $n: expr) => {
for _ in 0..$n {
$target.push($iter.next().ok_or(())?);
}
}
}
/// Replace the given [`Match`]es with the replacements. The replacements need to be in the order
/// of the search patterns as when [`MPSearch`] was constructed.
#[derive(Debug)]
struct MPReplace {
replacements: Vec<String>,
matches: Vec<Match>,
}
impl MPReplace {
/// The replacement must be given in the same order as the patterns were on the corresponding
/// [`MPSearch`] construction.
pub fn new(matches: Vec<Match>, replacements: Vec<String>) -> Self {
Self { replacements, matches }
}
/// # Errors
/// This can fail if the [`Match`]es are invalid relevant to the given target.
pub fn replace(&self, target: &String) -> Result<String, ()> {
let mut ptr = 0;
let mut iter = target.chars();
let mut res = String::new();
for cur_match in self.matches.iter() {
append_n!(iter, res, cur_match.start_pos - ptr);
skip_n!(iter, cur_match.len);
res.push_str(self.replacements.get(cur_match.pat_id).ok_or(())?);
ptr = cur_match.start_pos + cur_match.len;
}
append_n!(iter, res, target.chars().count() - ptr);
Ok(res)
}
}
struct PatMatchArgs<I: Iterator<Item = String>> {
inner: I,
}
impl<I: Iterator<Item = String>> PatMatchArgs<I> {
pub fn new(inner: I, count: usize) -> Result<Self, ()> {
if count % 2 != 0 {
return Err(());
}
Ok(Self { inner })
}
}
impl<I: Iterator<Item = String>> Iterator for PatMatchArgs<I> {
type Item = (String, String);
fn next(&mut self) -> Option<Self::Item> {
Some((self.inner.next()?, self.inner.next()?))
}
}
fn main() {
let mut contents = Vec::new();
std::io::stdin().lock().read_to_end(&mut contents).unwrap();
let contents = std::str::from_utf8(&contents).unwrap().to_string();
let args = match PatMatchArgs::new(args().skip(1), args().len() - 1) {
Ok(val) => val,
Err(()) => {
eprintln!("the number of arguments after filepath must be divisible by two");
std::process::exit(1);
}
};
let mut pats = Vec::new();
let mut replacements = Vec::new();
for (cur_pat, cur_replacement) in args {
let Ok(cur_pat) = Pattern::new(cur_pat) else {
eprintln!("the patterns can't be empty");
std::process::exit(1);
};
pats.push(cur_pat);
replacements.push(cur_replacement);
}
let matches = MPSearch::new(pats).search(&contents);
let replaced = MPReplace::new(matches, replacements)
.replace(&contents).unwrap();
println!("{}", replaced);
}
#[cfg(test)]
mod test {
use crate::{JumpTable, MPReplace, MPSearch, Match, Pattern};
#[test]
fn jumps_01() {
let src = String::from("thisthen").chars().collect::<Vec<_>>();
let target = vec![0_usize, 0, 0, 0, 1, 2, 0, 0];
let jt = JumpTable::new(&src);
assert_eq!(jt.jumps, target);
}
#[test]
fn jumps_02() {
let src = String::from("tthis").chars().collect::<Vec<_>>();
let target = vec![0, 1, 0, 0, 0];
let jt = JumpTable::new(&src);
assert_eq!(jt.jumps, target);
}
#[test]
fn jumps_03() {
let src = String::from("t").chars().collect::<Vec<_>>();
let target = vec![0];
let jt = JumpTable::new(&src);
assert_eq!(jt.jumps, target);
}
#[test]
fn jumps_04() {
let src = String::from("tt").chars().collect::<Vec<_>>();
let target = vec![0, 1];
let jt = JumpTable::new(&src);
assert_eq!(jt.jumps, target);
}
#[test]
fn jumps_05() {
let src = String::from("").chars().collect::<Vec<_>>();
let target = vec![];
let jt = JumpTable::new(&src);
assert_eq!(jt.jumps, target);
}
#[test]
fn search_01() {
let pat = String::from("tthis").chars().collect::<Vec<_>>();
let jt = JumpTable::new(&pat);
assert_eq!(jt.search(&pat, &'t', &1), 1);
}
#[test]
fn search_02() {
let pat = String::from("testtesa").chars().collect::<Vec<_>>();
let jt = JumpTable::new(&pat);
assert_eq!(jt.search(&pat, &'t', &7), 3);
}
#[test]
fn search_03() {
let pat = String::from("ahojahoj").chars().collect::<Vec<_>>();
let jt = JumpTable::new(&pat);
assert_eq!(jt.search(&pat, &'j', &7), 7);
}
#[test]
fn search_04() {
let pat = String::from("ahojahoj").chars().collect::<Vec<_>>();
let jt = JumpTable::new(&pat);
assert_eq!(jt.search(&pat, &'j', &7), 7);
}
#[test]
fn search_05() {
let pat = String::from("jojojojojojoja").chars().collect::<Vec<_>>();
let jt = JumpTable::new(&pat);
assert_eq!(jt.search(&pat, &'o', &13), 11);
}
#[test]
fn search_and_update_01() {
let pat = String::from("test");
let mut pat = Pattern::new(pat).unwrap();
let haystack = String::from("thisisatest");
for cur_haystack in haystack.chars().take(haystack.len() - 1) {
assert_eq!(pat.search_and_update(&cur_haystack), false);
println!("{:?}", pat);
}
println!("{:?}", pat);
assert_eq!(pat.search_and_update(&'t'), true);
}
#[test]
fn empty_pattern() {
assert_eq!(Pattern::new("".chars().collect()), Err(()));
}
#[test]
fn mpsearch_01() {
let mut mpsearch = MPSearch::new(
vec!["this", "is", "a", "test"]
.iter()
.map(|p| Pattern::new(p.to_string()).unwrap())
.collect()
);
println!("{:?}", mpsearch);
let haystack = String::from("this is a test");
let target = vec![
Match::new(0, 0, 4),
Match::new(1, 5, 2),
Match::new(2, 8, 1),
Match::new(3, 10, 4)
];
assert_eq!(mpsearch.search(&haystack), target);
}
#[test]
fn mpreplace_01() {
let mut mpsearch = MPSearch::new(
vec!["this", "is", "a", "test"]
.iter()
.map(|p| Pattern::new(p.to_string()).unwrap())
.collect()
);
let haystack = String::from("this-is.a*test///");
let matches = mpsearch.search(&haystack);
let mpreplace = MPReplace::new(matches, vec![
"that".to_string(),
"isn't".to_string(),
"the".to_string(),
"final".to_string(),
]);
let replaced = mpreplace.replace(&haystack).unwrap();
assert_eq!(replaced, "that-isn't.the*final///");
}
}

25
src/logger.rs Normal file
View File

@@ -0,0 +1,25 @@
#[macro_export]
macro_rules! logger {
(@inner $fixed: literal, $msg: literal$(, $($arg: expr),*)?) => {
println!(concat!(
$fixed, $msg
), Utc::now().to_rfc3339_opts(SecondsFormat::Secs, false)$(, $($arg),*)?);
};
(debug $msg: literal $(, $($arg: expr),*$(,)?)?) => {
#[cfg(feature = "dev")]
logger!(@inner "\x1b[38;5;244m[{}] \x1b[38;5;159mDEBUG \x1b[39m", $msg $(,$($arg),*)?);
};
(info $msg: literal $(, $($arg: expr),*$(,)?)?) => {
logger!(@inner "\x1b[38;5;244m[{}] \x1b[38;5;148mINFO \x1b[39m", $msg $(,$($arg),*)?);
};
(warn $msg: literal $(, $($arg: expr),*$(,)?)?) => {
logger!(@inner "\x1b[38;5;244m[{}] \x1b[38;5;214mWARNING \x1b[39m", $msg $(,$($arg),*)?);
};
(err $msg: literal $(, $($arg: expr),*$(,)?)?) => {
logger!(@inner "\x1b[38;5;244m[{}] \x1b[38;5;196mERROR \x1b[39m", $msg $(,$($arg),*)?);
};
}

152
src/main.rs Normal file
View File

@@ -0,0 +1,152 @@
use std::sync::Arc;
use chrono::{SecondsFormat, Utc};
#[cfg(feature = "dev")]
use {
inferium::TokioInet,
tokio::net::TcpListener,
std::net::SocketAddr,
};
#[cfg(not(feature = "dev"))]
use {
inferium::TokioUnix,
tokio::net::UnixListener,
tokio::net::unix::SocketAddr,
};
use inferium::h1::AsyncServer;
use tokio::sync::mpsc::{self, Sender};
use std::sync::atomic::AtomicU32;
mod mime_types;
mod response_builder;
mod router;
mod logger;
async fn client_handler(
#[cfg(feature = "dev")]
mut stream: AsyncServer<TokioInet>,
#[cfg(not(feature = "dev"))]
mut stream: AsyncServer<TokioUnix>,
#[cfg(feature = "dev")]
addr: SocketAddr,
#[cfg(not(feature = "dev"))]
_addr: SocketAddr,
tx: Sender<()>,
state: Arc<State>,
) {
logger!(debug "new connection from {:?}", addr);
loop {
// Receive request
let Ok(req) = stream.receive_request().await else {
logger!(debug "invalid request from {:?}", addr);
break;
};
// Handling logic
let res = match router::handle_request(req, &tx, &state).await {
Ok(v) => v,
#[cfg(feature = "dev")]
Err(e) => { logger!(err "internal server error: {:?}", e); break; }
#[cfg(not(feature = "dev"))]
Err(_) => { break; },
};
// Send response
match stream.send_response(&res.0).await {
Ok(()) => {},
#[cfg(feature = "dev")]
Err(e) => { logger!(debug "err when sending response {:?}", e); break; },
#[cfg(not(feature = "dev"))]
Err(_) => { break; },
}
let Some(body) = res.1 else {
continue;
};
match stream.send_body_bytes(&body).await {
Ok(()) => {},
#[cfg(feature = "dev")]
Err(e) => { logger!(debug "err when sending response body {:?}", e); break; },
#[cfg(not(feature = "dev"))]
Err(_) => { break; },
}
}
logger!(debug "end connection to {:?}", addr);
}
async fn server_handler(
#[cfg(feature = "dev")]
listener: TcpListener,
#[cfg(not(feature = "dev"))]
listener: UnixListener,
tx: Sender<()>,
state: Arc<State>,
) {
loop {
let Ok((conn, addr)) = listener.accept().await else {
continue;
};
let tx = tx.clone();
let state = state.clone();
tokio::task::spawn(async move {
#[cfg(feature = "dev")]
client_handler(AsyncServer::<TokioInet>::new(TokioInet::new(conn)), addr, tx, state).await;
#[cfg(not(feature = "dev"))]
client_handler(AsyncServer::<TokioUnix>::new(TokioUnix::new(conn)), addr, tx, state).await;
});
}
}
pub struct State {
visit_count: AtomicU32,
db: sled::Db,
}
impl State {
pub fn new(db_path: &str) -> Self {
Self {
visit_count: 0.into(),
db: sled::open(db_path).expect("could not open the database")
}
}
}
/// Is used to cleanup old (non-active) unix socket before binding.
///
/// # Caution
/// This function WILL panic if any I/O operation fails.
#[cfg(not(feature = "dev"))]
#[inline(always)]
fn delete_if_exists(filepath: &str) {
if std::fs::exists(filepath)
.expect(&format!("could not get info for filepath {:?}", filepath)) {
std::fs::remove_file(filepath)
.expect(&format!("could not delete existing socket {:?}", filepath));
}
}
#[tokio::main(flavor = "multi_thread", worker_threads = 4)]
async fn main() {
const BIND_TO: &str = std::env!("BIND_TO");
const DB_PATH: &str = std::env!("DB_PATH");
logger!(debug "binding to {:?}", BIND_TO);
#[cfg(not(feature = "dev"))]
delete_if_exists(BIND_TO);
#[cfg(not(feature = "dev"))]
let listener = UnixListener::bind(BIND_TO)
.expect(&format!("could not bind to {:?}", BIND_TO));
#[cfg(feature = "dev")]
let listener = TcpListener::bind(BIND_TO).await
.expect(&format!("could not bind to {:?}", BIND_TO));
let (tx, mut rx) = mpsc::channel::<()>(1);
let state = Arc::new(State::new(DB_PATH));
logger!(info "server running on {:?}", BIND_TO);
tokio::select!(
_ = server_handler(listener, tx, state) => {}
_ = rx.recv() => {},
);
logger!(info "graceful shutdown");
}

20
src/mime_types.rs Normal file
View File

@@ -0,0 +1,20 @@
macro_rules! mimes {
($($ident: ident = $value: literal),*$(,)?) => {
pub enum MimeType {
$($ident),*
}
impl MimeType {
pub fn text(&self) -> &'static str {
match self { $(Self::$ident => $value),* }
}
}
};
}
mimes! {
Txt = "text/plain",
Html = "text/html",
Css = "text/css",
Js = "text/javascript",
}

8
src/response_builder.rs Normal file
View File

@@ -0,0 +1,8 @@
#[macro_export]
macro_rules! response {
($proto: ident $status: ident, [$($hkey: ident : $hval: literal),*$(,)?]) => {
ResponseHead::new(Status::$status, ProtocolVariant::$proto, HashMap::from([
$((HeaderKey::$hkey, HeaderValue::new_unchecked($hval))),*
]))
};
}

127
src/router.rs Normal file
View File

@@ -0,0 +1,127 @@
use std::{collections::HashMap, sync::{atomic::Ordering, Arc}};
use askama::Template;
#[cfg(feature = "dev")]
use inferium::TokioInet;
#[cfg(not(feature = "dev"))]
use inferium::TokioUnix;
use inferium::{
h1::{Request, ResponseHead, ProtocolVariant},
Method, Status, HeaderKey, HeaderValue
};
use tokio::sync::mpsc::Sender;
use chrono::{Utc, SecondsFormat};
use crate::{logger, mime_types::MimeType, State};
macro_rules! response {
(@add_default_headers $($entry: tt),*$(,)?) => {[
(HeaderKey::SERVER, HeaderValue::new_unchecked("inferium".to_string())),
(
HeaderKey::OTHER("x-powered-by".to_string()),
HeaderValue::new_unchecked("zumepro".to_string())
),
$($entry),*
]};
(@log $head: ident, $status: ident) => {
logger!(
info "[{}] {} {:?} {}",
Status::$status,
$head.method(),
$head.uri().to_string(),
$head.proto(),
);
};
(
$head: ident,
$proto: ident $status: ident,
[$($hkey: ident : $hval: expr),*$(,)?],
file $static_filename: literal $ctype: ident
) => {{
response!(@log $head, $status);
const BODY: &[u8] = include_bytes!($static_filename);
(ResponseHead::new(Status::$status, ProtocolVariant::$proto, HashMap::from(
response!(@add_default_headers
(HeaderKey::CONTENT_LENGTH, BODY.len().into()),
(HeaderKey::CONTENT_TYPE, HeaderValue::new_unchecked(MimeType::$ctype.text().to_string())),
$((HeaderKey::$hkey, HeaderValue::new_unchecked($hval.to_string()))),*
))), Some(BODY.to_vec()))
}};
(
$head: ident,
$proto: ident $status: ident,
[$($hkey: ident : $hval: expr),*$(,)?],
$body: expr
) => {{
response!(@log $head, $status);
(ResponseHead::new(Status::$status, ProtocolVariant::$proto, HashMap::from(
response!(@add_default_headers
(HeaderKey::CONTENT_LENGTH, $body.len().into()),
$((HeaderKey::$hkey, HeaderValue::new_unchecked($hval.to_string()))),*
))), Some($body))
}};
($head: ident, $proto: ident $status: ident, [$($hkey: ident : $hval: expr),*$(,)?]) => {{
response!(@log $head, $status);
(ResponseHead::new(Status::$status, ProtocolVariant::$proto, HashMap::from(
response!(@add_default_headers
$((HeaderKey::$hkey, HeaderValue::new_unchecked($hval.to_string()))),*
))), None)
}};
}
#[derive(Debug)]
pub enum InternalServerError {
Templating(askama::Error),
Generic(String),
}
impl From<askama::Error> for InternalServerError {
fn from(value: askama::Error) -> Self {
Self::Templating(value)
}
}
/// Generate a response for the given request
///
/// # Returns
/// - Http head to send.
/// - Optionally a body (note that this must be preceeded by putting `content-length` header in the
/// response head).
pub async fn handle_request<'a>(
#[cfg(feature = "dev")]
req: Request<'_, TokioInet>,
#[cfg(not(feature = "dev"))]
req: Request<'_, TokioUnix>,
_tx: &'_ Sender<()>,
state: &Arc<State>,
) -> Result<(ResponseHead, Option<Vec<u8>>), InternalServerError> {
let head = req.head();
Ok(match (head.method(), head.uri().path()) {
(&Method::GET, "/") => {
let counter = state.visit_count.fetch_add(1, Ordering::Relaxed);
let response_body = Homepage { visit_count: counter }.render()?.into_bytes();
response!(head, HTTP1_1 Ok, [CONTENT_TYPE: MimeType::Html.text()], response_body)
},
(&Method::GET, "/style.css") => response!(
head, HTTP1_1 Ok, [], file "../client/dst/style.css" Css
),
(&Method::GET, "/script.js") => response!(
head, HTTP1_1 Ok, [], file "../client/dst/script.js" Js
),
_ => response!(head, HTTP1_1 NotFound, [
CONTENT_TYPE: MimeType::Txt.text(),
], b"Not Found. :(".to_vec()),
})
}
#[derive(Template)]
#[template(path = "index.html")]
struct Homepage {
visit_count: u32,
}