diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..fb88b01 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,40 @@ +{ + // Verwendet IntelliSense zum Ermitteln möglicher Attribute. + // Zeigen Sie auf vorhandene Attribute, um die zugehörigen Beschreibungen anzuzeigen. + // Weitere Informationen finden Sie unter https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "Debug Windows", + "type": "cppvsdbg", + "request": "launch", + "program": "${workspaceFolder}/target/debug/webserver.exe", + "preLaunchTask": "rust: cargo build", + "args": [ + "--path", + "./dir" + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "console": "internalConsole", + }, + { + "name": "Debug Linux", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/target/debug/webserver", + "preLaunchTask": "rust: cargo build", + "args": [ + "--path", + "./dir" + ], + "cwd": "${workspaceFolder}", + "console": "internalConsole", + "stopOnEntry": false, + // breakpoints are not working with lldb + "sourceLanguages": ["rust"], + + }, + ] +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..2c212d9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,575 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "anstream" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "342258dd14006105c2b75ab1bd7543a03bdf0cfc94383303ac212a04939dff6f" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-wincon", + "concolor-override", + "concolor-query", + "is-terminal", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23ea9e81bd02e310c216d080f6223c179012256e5151c41db88d12c88a1684d2" + +[[package]] +name = "anstyle-parse" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7d1bb534e9efed14f3e5f44e7dd1a4f709384023a4165199a4241e18dff0116" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-wincon" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3127af6145b149f3287bb9a0d10ad9c5692dba8c53ad48285e5bec4063834fa" +dependencies = [ + "anstyle", + "windows-sys", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "046ae530c528f252094e4a77886ee1374437744b2bff1497aa898bbddbbb29b3" +dependencies = [ + "clap_builder", + "clap_derive", + "once_cell", +] + +[[package]] +name = "clap_builder" +version = "4.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "223163f58c9a40c3b0a43e1c4b50a9ce09f007ea2cb1ec258a687945b4b7929f" +dependencies = [ + "anstream", + "anstyle", + "bitflags", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9644cd56d6b87dbe899ef8b053e331c0637664e9e21a33dfcdc36093f5c5c4" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a2dd5a6fe8c6e3502f568a6353e5273bbb15193ad9a89e457b9970798efbea1" + +[[package]] +name = "concolor-override" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a855d4a1978dc52fb0536a04d384c2c0c1aa273597f08b77c8c4d3b2eec6037f" + +[[package]] +name = "concolor-query" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d11d52c3d7ca2e6d0040212be9e4dbbcd78b6447f535b6b561f449427944cf" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "cpufeatures" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "280a9f2d8b3a38871a3c8a46fb80db65e5e5ed97da80c4d08bf27fb63e35e181" +dependencies = [ + "libc", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "errno" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d6a0976c999d473fe89ad888d5a284e55366d9dc9038b1ba2aa15128c4afa0" +dependencies = [ + "errno-dragonfly", + "libc", + "windows-sys", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "handlebars" +version = "4.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "035ef95d03713f2c347a72547b7cd38cbc9af7cd51e6099fb62d586d4a6dee3a" +dependencies = [ + "log", + "pest", + "pest_derive", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" + +[[package]] +name = "io-lifetimes" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09270fd4fa1111bc614ed2246c7ef56239a3063d5be0d1ec3b589c505d400aeb" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys", +] + +[[package]] +name = "is-terminal" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "256017f749ab3117e93acb91063009e1f1bb56d03965b14c2c8df4eb02c524d8" +dependencies = [ + "hermit-abi", + "io-lifetimes", + "rustix", + "windows-sys", +] + +[[package]] +name = "itoa" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6" + +[[package]] +name = "libc" +version = "0.2.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c" + +[[package]] +name = "linux-raw-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59d8c75012853d2e872fb56bc8a2e53718e2cafe1a4c823143141c6d90c322f" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "once_cell" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" + +[[package]] +name = "pest" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b1403e8401ad5dedea73c626b99758535b342502f8d1e361f4a2dd952749122" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "pest_derive" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be99c4c1d2fc2769b1d00239431d711d08f6efedcecb8b6e30707160aee99c15" +dependencies = [ + "pest", + "pest_generator", +] + +[[package]] +name = "pest_generator" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e56094789873daa36164de2e822b3888c6ae4b4f9da555a1103587658c805b1e" +dependencies = [ + "pest", + "pest_meta", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pest_meta" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6733073c7cff3d8459fda0e42f13a047870242aed8b509fe98000928975f359e" +dependencies = [ + "once_cell", + "pest", + "sha2", +] + +[[package]] +name = "proc-macro2" +version = "1.0.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d0dd4be24fcdcfeaa12a432d588dc59bbad6cad3510c67e74a2b6b2fc950564" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "rustix" +version = "0.37.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d097081ed288dfe45699b72f5b5d648e5f15d64d900c7080273baa20c16a6849" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91339c0467de62360649f8d3e185ca8de4224ff281f66000de5eb2a77a79041" + +[[package]] +name = "serde" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d721eca97ac802aa7777b701877c8004d950fc142651367300d21c1cc0194744" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "unicode-ident" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4" + +[[package]] +name = "urlencoding" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8db7427f936968176eaa7cdf81b7f98b980b18495ec28f1b5791ac3bfe3eea9" + +[[package]] +name = "utf8parse" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "webserver" +version = "0.1.0" +dependencies = [ + "clap", + "handlebars", + "regex", + "serde", + "urlencoding", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..63be2c8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "webserver" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = { version = "4.2.1", features = ["derive"] } +handlebars = "4.3.6" +regex = "1.7.3" +serde = { version = "1.0.159", features = ["derive"] } +urlencoding = "2.1.2" diff --git a/README.md b/README.md new file mode 100644 index 0000000..e3c5985 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# RustyFS + +RustyFS is a minimal multithreading file server written in Rust, inspired by the Rust example. It allows for easy and efficient file sharing between clients and servers through its multi-threaded architecture. With RustyFS, users can transfer files with ease while enjoying the speed and safety provided by the Rust programming language. + +## Getting Started + +To get started with RustyFS, you'll need to have Rust installed on your machine. Once you have Rust installed, you can install RustyFS by running the following command in your terminal: + +## From releases + +```bash +./rustyfs --path /path/to/directory --port 7878 --threads 4 +``` + +## From source + +Once RustyFS is downloaded, you can start the file server by running the following command: + +```bash +cargo run -- --path /path/to/directory --port 7878 --threads 4 +``` + +To download a file from RustyFS, you can use a web browser to navigate to http://localhost:7878/path/to/file. + +To list the files in the current directory of RustyFS, you can use a web browser to navigate to http://localhost:7878/. + +## Contributing + +If you'd like to contribute to RustyFS, please feel free to open an issue or submit a pull request on GitHub. We welcome all contributions! + +## License + +RustyFS is open source software licensed under the MIT license. See the LICENSE file for more information. diff --git a/assets/404.html b/assets/404.html new file mode 100644 index 0000000..191f3f0 --- /dev/null +++ b/assets/404.html @@ -0,0 +1,11 @@ + + + + + Hello! + + +

Oops!

+

Sorry, I don't know what you're asking for.

+ + \ No newline at end of file diff --git a/assets/index.html b/assets/index.html new file mode 100644 index 0000000..cc9647d --- /dev/null +++ b/assets/index.html @@ -0,0 +1,19 @@ + + + + + + Webserver + + + +

Webserver

+
+ .. + {{#each files}} + {{this.name}} + {{/each}} +
+ + + \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..0f94517 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,120 @@ +use std::{ + sync::{mpsc, Arc, Mutex}, + thread, +}; + +// Import logging.rs file +mod logging; +use logging::Logging; + +// Import macros.rs file +#[macro_use] +mod macros; + +pub struct ThreadPool { + workers: Vec, + sender: Option>, +} + +type Job = Box; + +impl ThreadPool { + /// Create a new ThreadPool. + /// + /// The size is the number of threads in the pool. + /// + /// # Panics + /// + /// The `new` function will panic if the size is zero. + pub fn new(size: usize) -> ThreadPool { + assert!(size > 0); + + let (sender, receiver) = mpsc::channel(); + + let receiver = Arc::new(Mutex::new(receiver)); + + let mut workers = Vec::with_capacity(size); + + for id in 0..size { + workers.push(Worker::new(id, Arc::clone(&receiver))); + } + + ThreadPool { + workers, + sender: Some(sender), + } + } + + pub fn execute(&self, f: F) + where + F: FnOnce() + Send + 'static, + { + let job = Box::new(f); + debug!("Sending job to thread pool"); + + match self.sender.as_ref() { + Some(sender) => { + match sender.send(job) { + Ok(_) => (), + Err(_) => error!("Error sending job to thread pool"), + } + }, + None => panic!("Sender is None"), + }; + } +} + +impl Drop for ThreadPool { + fn drop(&mut self) { + drop(self.sender.take()); + + for worker in &mut self.workers { + debug!("Shutting down worker {}", worker.id); + + if let Some(thread) = worker.thread.take() { + match thread.join() { + Ok(_) => (), + Err(_) => error!("Joining thread"), + } + } + } + } +} + +struct Worker { + id: usize, + thread: Option>, +} + +impl Worker { + fn new(id: usize, receiver: Arc>>) -> Worker { + + let builder = thread::Builder::new(); + + let thread = builder.spawn(move || loop { + let message = match receiver.lock() { + Ok(receiver) => receiver.recv(), + Err(_) => break, + }; + + match message { + Ok(job) => { + debug!("Worker {id} got a job; executing."); + + job(); + } + Err(_) => { + debug!("Worker {id} disconnected; shutting down."); + break; + } + } + }); + + let thread: Option> = match thread { + Ok(thread) => Some(thread), + Err(_) => None, + }; + + Worker { id, thread } + } +} \ No newline at end of file diff --git a/src/logging.rs b/src/logging.rs new file mode 100644 index 0000000..93f0219 --- /dev/null +++ b/src/logging.rs @@ -0,0 +1,64 @@ +use std::sync::atomic::{AtomicBool, Ordering}; +static VERBOSE: AtomicBool = AtomicBool::new(false); + +pub struct Logging { +} + +/// Logging class +/// This class is used to log messages to the console +/// Usage: Logging::info(f!("message")); +/// Usage: Logging::error(f!("message")); +/// Usage: Logging::debug(f!("message")); +/// Note: f!() is a macro that adds the line number to the message +impl Logging { + pub fn set_verbose(verbose: bool) { + VERBOSE.store(verbose, Ordering::SeqCst); + } + + fn remove_path(message: String) -> String { + // Regex to remove the path from the message + let re = regex::Regex::new(r"\((.*) L[0-9]+\)").unwrap(); + re.replace(&message, "").to_string() + } + + pub fn info(message: String) { + if !VERBOSE.load(Ordering::SeqCst) { + println!("\x1b[32mINFO:\x1b[0m {}", Self::remove_path(message)); + return; + } + // Color green + println!("\x1b[32mINFO:\x1b[0m {message}"); + } + + pub fn error(message: String) { + if !VERBOSE.load(Ordering::SeqCst) { + println!("\x1b[31mERROR:\x1b[0m {}", Self::remove_path(message)); + return; + } + // Color red + println!("\x1b[31mERROR:\x1b[0m {message}"); + } + + pub fn debug(message: String) { + if !VERBOSE.load(Ordering::SeqCst) { + return; + } + // Color blue + println!("\x1b[34mDEBUG:\x1b[0m {message}"); + } +} + +/// Tests +#[cfg(test)] +mod tests { + use crate::f; + + use super::*; + + #[test] + fn test_logging() { + Logging::info(f!("test")); + Logging::error(f!("test")); + Logging::debug(f!("test")); + } +} diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..dd40f77 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,46 @@ +/// Macro for line number +/// Usage: f!("message") +/// Note: f!() is a macro that adds the line number to the message +#[macro_export] +macro_rules! f { + ( $( $arg:tt )* ) => {{ + format!( "({} L{}) {}", file!(), line!(), format!( $( $arg )* ) ) + }} +} + +/// Macro for logging info +/// Usage: info!("message") +#[macro_export] +macro_rules! info { + ( $( $arg:tt )* ) => {{ + Logging::info(f!( $( $arg )* )) + }} +} + +/// Macro for logging error +/// Usage: error!("message") +#[macro_export] +macro_rules! error { + ( $( $arg:tt )* ) => {{ + Logging::error(f!( $( $arg )* )) + }} +} + +/// Macro for logging debug +/// Usage: debug!("message") +#[macro_export] +macro_rules! debug { + ( $( $arg:tt )* ) => {{ + Logging::debug(f!( $( $arg )* )) + }} +} + +/// Test macro +#[cfg(test)] +mod tests { + #[test] + fn test_f() { + let message = f!("test {}", "test"); + assert_eq!(message, "(src\\macros.rs L44) test test"); // 16 is the line number of this line + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..7bc762f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,212 @@ +use clap::Parser; +use handlebars::Handlebars; +use regex::Regex; +use serde::Serialize; +use std::{ + collections::BTreeMap, + fs, + io::{prelude::*, BufReader}, + net::{TcpListener, TcpStream}, + path::{Path, PathBuf}, + sync::Arc, +}; +use webserver::ThreadPool; + +// Import logging.rs file +mod logging; +use logging::Logging; + +// Import macros.rs file +#[macro_use] +mod macros; + +#[derive(Parser, Debug)] +#[command(author, version, about, long_about = None)] +struct Args { + /// Directory path mandatory + #[arg(short = 'd', long, required = true)] + path: String, + + /// Number of threads + /// Default: 4 + #[arg(short, long, default_value = "4")] + threads: Option, + + /// Port + /// Default: 7878 + #[arg(short, long, default_value = "7878")] + port: Option, + + /// Host + /// Default: 127.0.0.1 + #[arg(short = 'i', long, default_value = "127.0.0.1")] + host: Option, + + /// Verbose + /// Default: false + /// Note: This will print the line number and file path + #[arg(short, long)] + verbose: Option, +} + +fn main() { + let args = Args::parse(); + let path = Arc::new(PathBuf::from(&args.path)); + + // Check path + if !path.exists() { + panic!("Path does not exist"); + } + + // Set verbosity + Logging::set_verbose(args.verbose.unwrap_or(false)); + + // Listen for connections + let listener = TcpListener::bind(format!( + "{}:{}", + args.host.unwrap_or("127.0.0.1".to_string()), + args.port.as_ref().unwrap_or(&"7878".to_string()) + ).as_str()) + .unwrap(); + Logging::info(f!("Listening on port {}", args.port.unwrap_or("7878".to_string()))); + + let pool = ThreadPool::new(args.threads.unwrap_or(4)); + for stream in listener.incoming() { + let stream = match stream { + Ok(stream) => stream, + Err(e) => { + error!("{}", e); + continue; + } + }; + let aras = Arc::clone(&path); + pool.execute(|| { + handle_connection(stream, aras); + }); + } +} + +#[derive(Serialize)] +struct CustomFile { + path: String, + name: String, +} + +fn build_page(path: Arc, ospath: Arc) -> (&'static str, String) { + // Setup handlebars + let mut handlebars = Handlebars::new(); + match handlebars.register_template_string("index", include_str!("../assets/index.html")) { + Ok(_) => {} + Err(e) => { + error!("Failed to register template: {}", e); + } + } + let mut data = BTreeMap::new(); + + // Get files in directory + let mut files: Vec = Vec::new(); + let paths = match fs::read_dir(path.as_os_str()) { + Ok(paths) => paths, + Err(e) => { + error!("Failed to read directory: {}", e); + return ("HTTP/1.1 500 Internal Server Error", "".to_string()); + } + }; + + for path in paths { + let name = &path + .as_ref() + .unwrap() + .file_name() + .to_str() + .unwrap() + .to_string(); + let path = path.unwrap().path(); + let path = path.to_str().unwrap(); + let ospath = ospath.to_str().unwrap(); + let path = path.replace(ospath, ""); + + // Get file name + files.push(CustomFile { + path: path.to_string(), + name: name.to_string(), + }); + } + + // Add to handlebars data + let handlebar_render = match handlebars.render("index", &data) { + Ok(render) => render, + Err(e) => { + error!("Failed to render template: {}", e); + return ("HTTP/1.1 500 Internal Server Error", "".to_string()); + } + }; + data.insert("files".to_string(), files); + ( + "HTTP/1.1 200 OK", + handlebar_render + ) +} + +fn handle_connection<'a>(mut stream: TcpStream, ospath: Arc) { + let buf_reader = BufReader::new(&stream); + let request_line = match buf_reader.lines().next() { + Some(line) => match line { + Ok(line) => line, + Err(e) => { + error!("Failed to read request line: {}", e); + return; + } + }, + None => { + error!("Failed to read request line"); + return; + } + }; + + let (status_line, contents) = if request_line == "GET / HTTP/1.1" { + build_page(Arc::clone(&ospath), ospath) + } else { + // Check if the request is for a file + let re = Regex::new(r"GET /(.*) HTTP/1.1").unwrap(); + let cap = re.captures(&request_line).unwrap(); + let file = cap.get(1).unwrap().as_str(); + let file_path = Path::new(&file); + let file_path = ospath.join(file_path); + + // Unescape the path + let file_path = file_path.to_str().unwrap(); + let file_path = urlencoding::decode(file_path).unwrap(); + let file_path = Path::new(file_path.as_ref()); + + // File exists + if file_path.exists() { + // Is directory + if file_path.is_dir() { + let path = Arc::new(file_path.to_path_buf()); + build_page(path, ospath) + } else { + // Read non UTF-8 file + let length = file_path.metadata().unwrap().len(); + + let response = format!("HTTP/1.1 200 OK\r\nContent-Length: {length}\r\n\r\n"); + + stream.write_all(response.as_bytes()).unwrap(); + stream.write_all(&fs::read(file_path).unwrap()).unwrap(); + + ("HTTP/1.1 200 OK", "contents".to_string()) + } + } else { + ( + "HTTP/1.1 404 NOT FOUND", + include_str!("../assets/404.html").to_string(), + ) + } + }; + + let length = contents.len(); + + let response = format!("{status_line}\r\nContent-Length: {length}\r\n\r\n{contents}"); + + stream.write_all(response.as_bytes()).unwrap(); +}