From 625d1e52fef6fa47a3c6891c69eeae4d4f8e3b31 Mon Sep 17 00:00:00 2001 From: maxstrb Date: Sun, 23 Nov 2025 22:30:57 +0100 Subject: [PATCH] Started work on the multiplayer part of multiplayer game ( -_-) --- .../jshala3b279h5mwy9bha3spyj7xpw9wz-source | 1 + .../l4vzqgc77gsph6x2gig9wr0rd292cf89-source | 1 - ...e-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa | 2 +- ...5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa.rc | 15 +- flake.nix | 2 +- public/index.html | 47 ++- public/index.js | 2 +- src/content_type/any_content.rs | 77 +++++ src/content_type/any_content_type.rs | 50 +++ src/content_type/content.rs | 76 +++++ src/content_type/content_type.rs | 54 +++ src/content_type/mod.rs | 12 + src/content_type/parameters/charset.rs | 12 + src/content_type/parameters/mod.rs | 3 + src/content_type/parameters/parameters.rs | 37 +++ src/content_type/types/application.rs | 19 ++ src/content_type/types/image.rs | 23 ++ src/content_type/types/mod.rs | 4 + src/content_type/types/text.rs | 17 + src/game_manager.rs | 59 ++++ src/lib.rs | 6 + src/main.rs | 82 ++--- src/player_queue.rs | 35 ++ src/request.rs | 63 ++-- src/response.rs | 17 +- src/shared_enums.rs | 308 ------------------ 26 files changed, 587 insertions(+), 437 deletions(-) create mode 120000 .direnv/flake-inputs/jshala3b279h5mwy9bha3spyj7xpw9wz-source delete mode 120000 .direnv/flake-inputs/l4vzqgc77gsph6x2gig9wr0rd292cf89-source create mode 100644 src/content_type/any_content.rs create mode 100644 src/content_type/any_content_type.rs create mode 100644 src/content_type/content.rs create mode 100644 src/content_type/content_type.rs create mode 100644 src/content_type/mod.rs create mode 100644 src/content_type/parameters/charset.rs create mode 100644 src/content_type/parameters/mod.rs create mode 100644 src/content_type/parameters/parameters.rs create mode 100644 src/content_type/types/application.rs create mode 100644 src/content_type/types/image.rs create mode 100644 src/content_type/types/mod.rs create mode 100644 src/content_type/types/text.rs create mode 100644 src/game_manager.rs create mode 100644 src/lib.rs create mode 100644 src/player_queue.rs delete mode 100644 src/shared_enums.rs diff --git a/.direnv/flake-inputs/jshala3b279h5mwy9bha3spyj7xpw9wz-source b/.direnv/flake-inputs/jshala3b279h5mwy9bha3spyj7xpw9wz-source new file mode 120000 index 0000000..aaf1829 --- /dev/null +++ b/.direnv/flake-inputs/jshala3b279h5mwy9bha3spyj7xpw9wz-source @@ -0,0 +1 @@ +/nix/store/jshala3b279h5mwy9bha3spyj7xpw9wz-source \ No newline at end of file diff --git a/.direnv/flake-inputs/l4vzqgc77gsph6x2gig9wr0rd292cf89-source b/.direnv/flake-inputs/l4vzqgc77gsph6x2gig9wr0rd292cf89-source deleted file mode 120000 index 533f4c0..0000000 --- a/.direnv/flake-inputs/l4vzqgc77gsph6x2gig9wr0rd292cf89-source +++ /dev/null @@ -1 +0,0 @@ -/nix/store/l4vzqgc77gsph6x2gig9wr0rd292cf89-source \ No newline at end of file diff --git a/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa b/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa index 8cc2655..ea05503 120000 --- a/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa +++ b/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa @@ -1 +1 @@ -/nix/store/fx0gg8d8kpy3f98ar4k04623spprw59m-nix-shell-env \ No newline at end of file +/nix/store/i5gxwab2lp8mnrw0742lvwzzs4vp63rv-nix-shell-env \ No newline at end of file diff --git a/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa.rc b/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa.rc index eab7bb6..6b5e481 100644 --- a/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa.rc +++ b/.direnv/flake-profile-a5d5b61aa8a61b7d9d765e1daf971a9a578f1cfa.rc @@ -23,7 +23,7 @@ export CXX CXX_FOR_TARGET='g++' export CXX_FOR_TARGET HOSTTYPE='x86_64' -HOST_PATH='/nix/store/95k9rsn1zsw1yvir8mj824ldhf90i4qw-gcc-wrapper-14.3.0/bin:/nix/store/l19cddv64i52rhcwahif8sgyrd3mhiqb-binutils-wrapper-2.44/bin:/nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/bin:/nix/store/ahixbqp1sm5vwzc19hz30vxvbkh7scxd-rust-default-1.90.0/bin:/nix/store/cw0gfvxbi05kqy13b2acm1vpnghpy0ni-evcxr-0.21.1/bin:/nix/store/8ksax0a2mxglr5hlkj2dzl556jx7xqn5-coreutils-9.7/bin:/nix/store/l964krgbp613d5jxga2vy5qdssj7zfzj-findutils-4.10.0/bin:/nix/store/s2fvny566vls74p4qm9v3fdqd741fh3f-diffutils-3.12/bin:/nix/store/pmhkmqy0vxk47r6ndh0azybhf6gs6k25-gnused-4.9/bin:/nix/store/vlckk0vnmawq9wwh7ndkrwxlpv4h29yh-gnugrep-3.12/bin:/nix/store/03nvbw411p097h6yxjghc33rbcrjfb9d-gawk-5.3.2/bin:/nix/store/8av8pfs7bnyc6hqj764ns4z1fnr9bva1-gnutar-1.35/bin:/nix/store/8gsxxh82rf957ffbsk0q9670nhvl5lia-gzip-1.14/bin:/nix/store/6yjb3zdj448rm8qsmpiq3f67kvj5683a-bzip2-1.0.8-bin/bin:/nix/store/aqdvlkh0jdwkc22hh5vr9sl6qlw5ha74-gnumake-4.4.1/bin:/nix/store/q7sqwn7i6w2b67adw0bmix29pxg85x3w-bash-5.3p3/bin:/nix/store/856i1ajaci3kmmp15rifacfz3jvn5l3q-patch-2.8/bin:/nix/store/y9kgzp85ykrhd7l691w4djx121qygy68-xz-5.8.1-bin/bin:/nix/store/v40ijzz8p2fpk9ihjck3a1ncqaqfmn3c-file-5.45/bin' +HOST_PATH='/nix/store/95k9rsn1zsw1yvir8mj824ldhf90i4qw-gcc-wrapper-14.3.0/bin:/nix/store/l19cddv64i52rhcwahif8sgyrd3mhiqb-binutils-wrapper-2.44/bin:/nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/bin:/nix/store/k6nn8yn9w98xm7ibw5f7bm4ljfygdika-rust-default-1.91.0-beta.4-2025-09-28/bin:/nix/store/cw0gfvxbi05kqy13b2acm1vpnghpy0ni-evcxr-0.21.1/bin:/nix/store/8ksax0a2mxglr5hlkj2dzl556jx7xqn5-coreutils-9.7/bin:/nix/store/l964krgbp613d5jxga2vy5qdssj7zfzj-findutils-4.10.0/bin:/nix/store/s2fvny566vls74p4qm9v3fdqd741fh3f-diffutils-3.12/bin:/nix/store/pmhkmqy0vxk47r6ndh0azybhf6gs6k25-gnused-4.9/bin:/nix/store/vlckk0vnmawq9wwh7ndkrwxlpv4h29yh-gnugrep-3.12/bin:/nix/store/03nvbw411p097h6yxjghc33rbcrjfb9d-gawk-5.3.2/bin:/nix/store/8av8pfs7bnyc6hqj764ns4z1fnr9bva1-gnutar-1.35/bin:/nix/store/8gsxxh82rf957ffbsk0q9670nhvl5lia-gzip-1.14/bin:/nix/store/6yjb3zdj448rm8qsmpiq3f67kvj5683a-bzip2-1.0.8-bin/bin:/nix/store/aqdvlkh0jdwkc22hh5vr9sl6qlw5ha74-gnumake-4.4.1/bin:/nix/store/q7sqwn7i6w2b67adw0bmix29pxg85x3w-bash-5.3p3/bin:/nix/store/856i1ajaci3kmmp15rifacfz3jvn5l3q-patch-2.8/bin:/nix/store/y9kgzp85ykrhd7l691w4djx121qygy68-xz-5.8.1-bin/bin:/nix/store/v40ijzz8p2fpk9ihjck3a1ncqaqfmn3c-file-5.45/bin' export HOST_PATH IFS=' ' @@ -53,7 +53,7 @@ NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu='1' export NIX_CC_WRAPPER_TARGET_HOST_x86_64_unknown_linux_gnu NIX_CC_WRAPPER_TARGET_TARGET_x86_64_unknown_linux_gnu='1' export NIX_CC_WRAPPER_TARGET_TARGET_x86_64_unknown_linux_gnu -NIX_CFLAGS_COMPILE=' -frandom-seed=fx0gg8d8kp -isystem /nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/include -isystem /nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/include -isystem /nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/include -isystem /nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/include' +NIX_CFLAGS_COMPILE=' -frandom-seed=i5gxwab2lp -isystem /nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/include -isystem /nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/include -isystem /nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/include -isystem /nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/include' export NIX_CFLAGS_COMPILE NIX_CFLAGS_COMPILE_FOR_TARGET=' -isystem /nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/include' export NIX_CFLAGS_COMPILE_FOR_TARGET @@ -61,9 +61,9 @@ NIX_ENFORCE_NO_NATIVE='1' export NIX_ENFORCE_NO_NATIVE NIX_HARDENING_ENABLE='bindnow format fortify fortify3 pic relro stackclashprotection stackprotector strictoverflow zerocallusedregs' export NIX_HARDENING_ENABLE -NIX_LDFLAGS='-rpath /mnt/removable/Projects/Rust/multiplayer-game/outputs/out/lib -L/nix/store/ahixbqp1sm5vwzc19hz30vxvbkh7scxd-rust-default-1.90.0/lib -L/nix/store/ahixbqp1sm5vwzc19hz30vxvbkh7scxd-rust-default-1.90.0/lib -L/nix/store/ahixbqp1sm5vwzc19hz30vxvbkh7scxd-rust-default-1.90.0/lib -L/nix/store/ahixbqp1sm5vwzc19hz30vxvbkh7scxd-rust-default-1.90.0/lib' +NIX_LDFLAGS='-rpath /mnt/removable/Projects/Rust/multiplayer-game/outputs/out/lib -L/nix/store/k6nn8yn9w98xm7ibw5f7bm4ljfygdika-rust-default-1.91.0-beta.4-2025-09-28/lib -L/nix/store/k6nn8yn9w98xm7ibw5f7bm4ljfygdika-rust-default-1.91.0-beta.4-2025-09-28/lib -L/nix/store/k6nn8yn9w98xm7ibw5f7bm4ljfygdika-rust-default-1.91.0-beta.4-2025-09-28/lib -L/nix/store/k6nn8yn9w98xm7ibw5f7bm4ljfygdika-rust-default-1.91.0-beta.4-2025-09-28/lib' export NIX_LDFLAGS -NIX_LDFLAGS_FOR_TARGET=' -L/nix/store/ahixbqp1sm5vwzc19hz30vxvbkh7scxd-rust-default-1.90.0/lib' +NIX_LDFLAGS_FOR_TARGET=' -L/nix/store/k6nn8yn9w98xm7ibw5f7bm4ljfygdika-rust-default-1.91.0-beta.4-2025-09-28/lib' export NIX_LDFLAGS_FOR_TARGET NIX_NO_SELF_RPATH='1' NIX_STORE='/nix/store' @@ -84,7 +84,7 @@ OLDPWD='' export OLDPWD OPTERR='1' OSTYPE='linux-gnu' -PATH='/nix/store/gx2l0rnp3qcnysdddkg9dqnh2mz6w08k-patchelf-0.15.2/bin:/nix/store/95k9rsn1zsw1yvir8mj824ldhf90i4qw-gcc-wrapper-14.3.0/bin:/nix/store/82kmz7r96navanrc2fgckh2bamiqrgsw-gcc-14.3.0/bin:/nix/store/4jxivbjpr86wmsziqlf7iljlwjlxz8bh-glibc-2.40-66-bin/bin:/nix/store/8ksax0a2mxglr5hlkj2dzl556jx7xqn5-coreutils-9.7/bin:/nix/store/l19cddv64i52rhcwahif8sgyrd3mhiqb-binutils-wrapper-2.44/bin:/nix/store/c43ry7z24x3jhnjlj4gpay8a4g2p3x1h-binutils-2.44/bin:/nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/bin:/nix/store/ahixbqp1sm5vwzc19hz30vxvbkh7scxd-rust-default-1.90.0/bin:/nix/store/cw0gfvxbi05kqy13b2acm1vpnghpy0ni-evcxr-0.21.1/bin:/nix/store/8ksax0a2mxglr5hlkj2dzl556jx7xqn5-coreutils-9.7/bin:/nix/store/l964krgbp613d5jxga2vy5qdssj7zfzj-findutils-4.10.0/bin:/nix/store/s2fvny566vls74p4qm9v3fdqd741fh3f-diffutils-3.12/bin:/nix/store/pmhkmqy0vxk47r6ndh0azybhf6gs6k25-gnused-4.9/bin:/nix/store/vlckk0vnmawq9wwh7ndkrwxlpv4h29yh-gnugrep-3.12/bin:/nix/store/03nvbw411p097h6yxjghc33rbcrjfb9d-gawk-5.3.2/bin:/nix/store/8av8pfs7bnyc6hqj764ns4z1fnr9bva1-gnutar-1.35/bin:/nix/store/8gsxxh82rf957ffbsk0q9670nhvl5lia-gzip-1.14/bin:/nix/store/6yjb3zdj448rm8qsmpiq3f67kvj5683a-bzip2-1.0.8-bin/bin:/nix/store/aqdvlkh0jdwkc22hh5vr9sl6qlw5ha74-gnumake-4.4.1/bin:/nix/store/q7sqwn7i6w2b67adw0bmix29pxg85x3w-bash-5.3p3/bin:/nix/store/856i1ajaci3kmmp15rifacfz3jvn5l3q-patch-2.8/bin:/nix/store/y9kgzp85ykrhd7l691w4djx121qygy68-xz-5.8.1-bin/bin:/nix/store/v40ijzz8p2fpk9ihjck3a1ncqaqfmn3c-file-5.45/bin' +PATH='/nix/store/gx2l0rnp3qcnysdddkg9dqnh2mz6w08k-patchelf-0.15.2/bin:/nix/store/95k9rsn1zsw1yvir8mj824ldhf90i4qw-gcc-wrapper-14.3.0/bin:/nix/store/82kmz7r96navanrc2fgckh2bamiqrgsw-gcc-14.3.0/bin:/nix/store/4jxivbjpr86wmsziqlf7iljlwjlxz8bh-glibc-2.40-66-bin/bin:/nix/store/8ksax0a2mxglr5hlkj2dzl556jx7xqn5-coreutils-9.7/bin:/nix/store/l19cddv64i52rhcwahif8sgyrd3mhiqb-binutils-wrapper-2.44/bin:/nix/store/c43ry7z24x3jhnjlj4gpay8a4g2p3x1h-binutils-2.44/bin:/nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1/bin:/nix/store/k6nn8yn9w98xm7ibw5f7bm4ljfygdika-rust-default-1.91.0-beta.4-2025-09-28/bin:/nix/store/cw0gfvxbi05kqy13b2acm1vpnghpy0ni-evcxr-0.21.1/bin:/nix/store/8ksax0a2mxglr5hlkj2dzl556jx7xqn5-coreutils-9.7/bin:/nix/store/l964krgbp613d5jxga2vy5qdssj7zfzj-findutils-4.10.0/bin:/nix/store/s2fvny566vls74p4qm9v3fdqd741fh3f-diffutils-3.12/bin:/nix/store/pmhkmqy0vxk47r6ndh0azybhf6gs6k25-gnused-4.9/bin:/nix/store/vlckk0vnmawq9wwh7ndkrwxlpv4h29yh-gnugrep-3.12/bin:/nix/store/03nvbw411p097h6yxjghc33rbcrjfb9d-gawk-5.3.2/bin:/nix/store/8av8pfs7bnyc6hqj764ns4z1fnr9bva1-gnutar-1.35/bin:/nix/store/8gsxxh82rf957ffbsk0q9670nhvl5lia-gzip-1.14/bin:/nix/store/6yjb3zdj448rm8qsmpiq3f67kvj5683a-bzip2-1.0.8-bin/bin:/nix/store/aqdvlkh0jdwkc22hh5vr9sl6qlw5ha74-gnumake-4.4.1/bin:/nix/store/q7sqwn7i6w2b67adw0bmix29pxg85x3w-bash-5.3p3/bin:/nix/store/856i1ajaci3kmmp15rifacfz3jvn5l3q-patch-2.8/bin:/nix/store/y9kgzp85ykrhd7l691w4djx121qygy68-xz-5.8.1-bin/bin:/nix/store/v40ijzz8p2fpk9ihjck3a1ncqaqfmn3c-file-5.45/bin' export PATH PS4='+ ' RANLIB='ranlib' @@ -116,7 +116,7 @@ export XDG_DATA_DIRS __structuredAttrs='' export __structuredAttrs _substituteStream_has_warned_replace_deprecation='false' -buildInputs='/nix/store/95k9rsn1zsw1yvir8mj824ldhf90i4qw-gcc-wrapper-14.3.0 /nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1 /nix/store/ahixbqp1sm5vwzc19hz30vxvbkh7scxd-rust-default-1.90.0 /nix/store/cw0gfvxbi05kqy13b2acm1vpnghpy0ni-evcxr-0.21.1' +buildInputs='/nix/store/95k9rsn1zsw1yvir8mj824ldhf90i4qw-gcc-wrapper-14.3.0 /nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1 /nix/store/k6nn8yn9w98xm7ibw5f7bm4ljfygdika-rust-default-1.91.0-beta.4-2025-09-28 /nix/store/cw0gfvxbi05kqy13b2acm1vpnghpy0ni-evcxr-0.21.1' export buildInputs buildPhase='{ echo "------------------------------------------------------------"; echo " WARNING: the existence of this path is not guaranteed."; @@ -165,7 +165,6 @@ declare -a envHostHostHooks=('ccWrapper_addCVars' 'bintoolsWrapper_addLDVars' 'c declare -a envHostTargetHooks=('ccWrapper_addCVars' 'bintoolsWrapper_addLDVars' 'ccWrapper_addCVars' 'bintoolsWrapper_addLDVars' ) declare -a envTargetTargetHooks=('ccWrapper_addCVars' 'bintoolsWrapper_addLDVars' ) declare -a fixupOutputHooks=('if [ -z "${dontPatchELF-}" ]; then patchELF "$prefix"; fi' 'if [[ -z "${noAuditTmpdir-}" && -e "$prefix" ]]; then auditTmpdir "$prefix"; fi' 'if [ -z "${dontGzipMan-}" ]; then compressManPages "$prefix"; fi' '_moveLib64' '_moveSbin' '_moveSystemdUserUnits' 'patchShebangsAuto' '_pruneLibtoolFiles' '_doStrip' ) -guess='16' initialPath='/nix/store/8ksax0a2mxglr5hlkj2dzl556jx7xqn5-coreutils-9.7 /nix/store/l964krgbp613d5jxga2vy5qdssj7zfzj-findutils-4.10.0 /nix/store/s2fvny566vls74p4qm9v3fdqd741fh3f-diffutils-3.12 /nix/store/pmhkmqy0vxk47r6ndh0azybhf6gs6k25-gnused-4.9 /nix/store/vlckk0vnmawq9wwh7ndkrwxlpv4h29yh-gnugrep-3.12 /nix/store/03nvbw411p097h6yxjghc33rbcrjfb9d-gawk-5.3.2 /nix/store/8av8pfs7bnyc6hqj764ns4z1fnr9bva1-gnutar-1.35 /nix/store/8gsxxh82rf957ffbsk0q9670nhvl5lia-gzip-1.14 /nix/store/6yjb3zdj448rm8qsmpiq3f67kvj5683a-bzip2-1.0.8-bin /nix/store/aqdvlkh0jdwkc22hh5vr9sl6qlw5ha74-gnumake-4.4.1 /nix/store/q7sqwn7i6w2b67adw0bmix29pxg85x3w-bash-5.3p3 /nix/store/856i1ajaci3kmmp15rifacfz3jvn5l3q-patch-2.8 /nix/store/y9kgzp85ykrhd7l691w4djx121qygy68-xz-5.8.1-bin /nix/store/v40ijzz8p2fpk9ihjck3a1ncqaqfmn3c-file-5.45' mesonFlags='' export mesonFlags @@ -195,7 +194,7 @@ declare -a pkgsBuildBuild=() declare -a pkgsBuildHost=('/nix/store/gx2l0rnp3qcnysdddkg9dqnh2mz6w08k-patchelf-0.15.2' '/nix/store/jwjq0fjgn7d00kswhaw2m8hbgws5vbi4-update-autotools-gnu-config-scripts-hook' '/nix/store/0y5xmdb7qfvimjwbq7ibg1xdgkgjwqng-no-broken-symlinks.sh' '/nix/store/cv1d7p48379km6a85h4zp6kr86brh32q-audit-tmpdir.sh' '/nix/store/85clx3b0xkdf58jn161iy80y5223ilbi-compress-man-pages.sh' '/nix/store/wgrbkkaldkrlrni33ccvm3b6vbxzb656-make-symlinks-relative.sh' '/nix/store/5yzw0vhkyszf2d179m0qfkgxmp5wjjx4-move-docs.sh' '/nix/store/fyaryjvghbkpfnsyw97hb3lyb37s1pd6-move-lib64.sh' '/nix/store/kd4xwxjpjxi71jkm6ka0np72if9rm3y0-move-sbin.sh' '/nix/store/pag6l61paj1dc9sv15l7bm5c17xn5kyk-move-systemd-user-units.sh' '/nix/store/cmzya9irvxzlkh7lfy6i82gbp0saxqj3-multiple-outputs.sh' '/nix/store/x8c40nfigps493a07sdr2pm5s9j1cdc0-patch-shebangs.sh' '/nix/store/cickvswrvann041nqxb0rxilc46svw1n-prune-libtool-files.sh' '/nix/store/xyff06pkhki3qy1ls77w10s0v79c9il0-reproducible-builds.sh' '/nix/store/z7k98578dfzi6l3hsvbivzm7hfqlk0zc-set-source-date-epoch-to-latest.sh' '/nix/store/pilsssjjdxvdphlg2h19p0bfx5q0jzkn-strip.sh' '/nix/store/95k9rsn1zsw1yvir8mj824ldhf90i4qw-gcc-wrapper-14.3.0' '/nix/store/l19cddv64i52rhcwahif8sgyrd3mhiqb-binutils-wrapper-2.44' ) declare -a pkgsBuildTarget=() declare -a pkgsHostHost=('/nix/store/95k9rsn1zsw1yvir8mj824ldhf90i4qw-gcc-wrapper-14.3.0' '/nix/store/l19cddv64i52rhcwahif8sgyrd3mhiqb-binutils-wrapper-2.44' ) -declare -a pkgsHostTarget=('/nix/store/95k9rsn1zsw1yvir8mj824ldhf90i4qw-gcc-wrapper-14.3.0' '/nix/store/l19cddv64i52rhcwahif8sgyrd3mhiqb-binutils-wrapper-2.44' '/nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1' '/nix/store/ahixbqp1sm5vwzc19hz30vxvbkh7scxd-rust-default-1.90.0' '/nix/store/cw0gfvxbi05kqy13b2acm1vpnghpy0ni-evcxr-0.21.1' ) +declare -a pkgsHostTarget=('/nix/store/95k9rsn1zsw1yvir8mj824ldhf90i4qw-gcc-wrapper-14.3.0' '/nix/store/l19cddv64i52rhcwahif8sgyrd3mhiqb-binutils-wrapper-2.44' '/nix/store/ahxj2q2mrl9z2k77ahqsl9j4zxq1wf84-gnumake-4.4.1' '/nix/store/k6nn8yn9w98xm7ibw5f7bm4ljfygdika-rust-default-1.91.0-beta.4-2025-09-28' '/nix/store/cw0gfvxbi05kqy13b2acm1vpnghpy0ni-evcxr-0.21.1' ) declare -a pkgsTargetTarget=() declare -a postFixupHooks=('noBrokenSymlinksInAllOutputs' '_makeSymlinksRelativeInAllOutputs' '_multioutPropagateDev' ) declare -a postUnpackHooks=('_updateSourceDateEpochFromSourceRoot' ) diff --git a/flake.nix b/flake.nix index ef403a2..67795f3 100644 --- a/flake.nix +++ b/flake.nix @@ -19,7 +19,7 @@ buildInputs = with pkgs; [ gcc gnumake - rust-bin.stable.latest.default + rust-bin.beta.latest.default evcxr ]; }; diff --git a/public/index.html b/public/index.html index 819b5a0..b39b585 100644 --- a/public/index.html +++ b/public/index.html @@ -1,30 +1,29 @@ - + - - - - Hello World! - - + + + + Hello World! + + - - + + -
+
-
- -
- -
+
+ +
+ +
-
    - -
    - + +
    + diff --git a/public/index.js b/public/index.js index 58f68f1..48dd256 100644 --- a/public/index.js +++ b/public/index.js @@ -1,4 +1,4 @@ -const socket = new WebSocket("ws://localhost:8080/websocket"); +const socket = new WebSocket("/websocket"); socket.addEventListener("message", (event) => { let messages = document.getElementById("messages"); diff --git a/src/content_type/any_content.rs b/src/content_type/any_content.rs new file mode 100644 index 0000000..bd3216c --- /dev/null +++ b/src/content_type/any_content.rs @@ -0,0 +1,77 @@ +use std::str::FromStr; + +use crate::content_type::{ + any_content_type::ContentType, invalid_data_error, parameters::parameters::Parameter, +}; + +#[derive(Debug, PartialEq)] +pub struct Content { + pub content_type: Option, + pub parameter: Option>, +} + +impl Content { + pub fn is_text(&self) -> bool { + self.content_type.is_none() + || matches!(self.content_type.as_ref().unwrap(), ContentType::Text(_)) + } + + pub fn is_application(&self) -> bool { + self.content_type.is_none() + || matches!( + self.content_type.as_ref().unwrap(), + ContentType::Application(_) + ) + } + + pub fn quality(&self) -> Option { + self.parameter.as_ref()?.iter().find_map(|p| { + if let Parameter::Preference(q) = p { + Some(*q) + } else { + None + } + }) + } + + pub fn is_image(&self) -> bool { + self.content_type.is_none() + || matches!(self.content_type.as_ref().unwrap(), ContentType::Image(_)) + } +} + +impl FromStr for Content { + type Err = std::io::Error; + + fn from_str(s: &str) -> Result { + match s.split(';').collect::>().as_slice() { + ["*/*", params @ ..] => Ok(Self { + content_type: None, + parameter: if s.is_empty() { + None + } else { + Some( + params + .iter() + .map(|v| Parameter::from_str(v)) + .collect::>()?, + ) + }, + }), + [val, params @ ..] => Ok(Self { + content_type: Some(ContentType::from_str(val)?), + parameter: if s.is_empty() { + None + } else { + Some( + params + .iter() + .map(|v| Parameter::from_str(v)) + .collect::>()?, + ) + }, + }), + _ => Err(invalid_data_error("Invalid content-type")), + } + } +} diff --git a/src/content_type/any_content_type.rs b/src/content_type/any_content_type.rs new file mode 100644 index 0000000..232db79 --- /dev/null +++ b/src/content_type/any_content_type.rs @@ -0,0 +1,50 @@ +use std::str::FromStr; + +use crate::content_type::{ + invalid_data_error, + types::{application::ApplicationType, image::Image, text::TextType}, +}; + +#[derive(Debug, PartialEq)] +pub enum ContentType { + Text(Option), + Application(Option), + Image(Option), + Any, +} + +impl FromStr for ContentType { + type Err = std::io::Error; + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split('/').collect(); + + match parts.as_slice() { + ["text", "html"] => Ok(Self::Text(Some(TextType::Html))), + ["text", "css"] => Ok(Self::Text(Some(TextType::Css))), + ["text", "javascript"] => Ok(Self::Text(Some(TextType::Javascript))), + ["text", "*"] => Ok(Self::Text(None)), + + ["application", "json"] => Ok(Self::Application(Some(ApplicationType::Json))), + ["application", "xhtml+xml"] => Ok(Self::Application(Some(ApplicationType::XhtmlXml))), + ["application", "xml"] => Ok(Self::Application(Some(ApplicationType::Xml))), + ["application", "signed-exchange"] => { + Ok(Self::Application(Some(ApplicationType::SignedExchange))) + } + ["application", "*"] => Ok(Self::Application(None)), + + ["image", "png"] => Ok(Self::Image(Some(Image::Png))), + ["image", "apng"] => Ok(Self::Image(Some(Image::Png))), + ["image", "jpeg"] | ["image", "jpg"] => Ok(Self::Image(Some(Image::Jpeg))), + ["image", "avif"] => Ok(Self::Image(Some(Image::Avif))), + ["image", "webp"] => Ok(Self::Image(Some(Image::Webp))), + ["image", "svg"] | ["image", "svg+xml"] => Ok(Self::Image(Some(Image::Svg))), + ["image", "jxl"] => Ok(Self::Image(Some(Image::JpegXL))), + ["image", "*"] => Ok(Self::Image(None)), + + ["*", "*"] => Ok(Self::Any), + + _ => Err(invalid_data_error("Invalid content-type-type")), + } + } +} diff --git a/src/content_type/content.rs b/src/content_type/content.rs new file mode 100644 index 0000000..b8849cf --- /dev/null +++ b/src/content_type/content.rs @@ -0,0 +1,76 @@ +use crate::content_type::{content_type::ContentType, parameters::parameters::Parameter}; + +#[derive(Debug, PartialEq)] +pub struct Content { + pub content_type: ContentType, + pub parameter: Option>, +} + +impl Content { + pub fn new(content_type: ContentType) -> Self { + Self { + content_type, + parameter: None, + } + } + + pub fn with_params(content_type: ContentType, params: Vec) -> Self { + Self { + content_type, + parameter: Some(params), + } + } + + pub fn add_parameter(&mut self, param: Parameter) { + match &mut self.parameter { + Some(params) => params.push(param), + None => self.parameter = Some(vec![param]), + } + } + + pub fn charset(&self) -> Option<&crate::content_type::parameters::charset::Charset> { + self.parameter.as_ref()?.iter().find_map(|p| { + if let Parameter::Charset(cs) = p { + Some(cs) + } else { + None + } + }) + } + + pub fn html_utf8() -> Self { + use crate::content_type::parameters::charset::Charset; + use crate::content_type::types::text::TextType; + + Self::with_params( + ContentType::Text(TextType::Html), + vec![Parameter::Charset(Charset::UTF8)], + ) + } + + pub fn json_utf8() -> Self { + use crate::content_type::parameters::charset::Charset; + use crate::content_type::types::application::ApplicationType; + + Self::with_params( + ContentType::Aplication(ApplicationType::Json), + vec![Parameter::Charset(Charset::UTF8)], + ) + } + + pub fn to_str(&self) -> Box { + match &self.parameter { + Some(params) => format!( + "{}; {}", + self.content_type.to_str(), + params + .iter() + .map(|p| p.to_str()) + .collect::>() + .join("; ") + ) + .into(), + None => self.content_type.to_str(), + } + } +} diff --git a/src/content_type/content_type.rs b/src/content_type/content_type.rs new file mode 100644 index 0000000..acd41f6 --- /dev/null +++ b/src/content_type/content_type.rs @@ -0,0 +1,54 @@ +use std::str::FromStr; + +use crate::content_type::{ + invalid_data_error, + types::{application::ApplicationType, image::Image, text::TextType}, +}; + +#[derive(Debug, PartialEq)] +pub enum ContentType { + Text(TextType), + Aplication(ApplicationType), + Image(Image), +} + +impl ContentType { + pub fn to_str(&self) -> Box { + match self { + ContentType::Text(text) => format!("text/{}", text.to_str()).into(), + ContentType::Aplication(app) => format!("application/{}", app.to_str()).into(), + ContentType::Image(img) => format!("image/{}", img.to_str()).into(), + } + } +} + +impl FromStr for ContentType { + type Err = std::io::Error; + + fn from_str(s: &str) -> Result { + let parts: Vec<&str> = s.split('/').collect(); + + match parts.as_slice() { + ["text", "html"] => Ok(Self::Text(TextType::Html)), + ["text", "css"] => Ok(Self::Text(TextType::Css)), + ["text", "javascript"] => Ok(Self::Text(TextType::Javascript)), + + ["application", "json"] => Ok(Self::Aplication(ApplicationType::Json)), + ["application", "xhtml+xml"] => Ok(Self::Aplication(ApplicationType::XhtmlXml)), + ["application", "xml"] => Ok(Self::Aplication(ApplicationType::Xml)), + ["application", "signed-exchange"] => { + Ok(Self::Aplication(ApplicationType::SignedExchange)) + } + + ["image", "png"] => Ok(Self::Image(Image::Png)), + ["image", "apng"] => Ok(Self::Image(Image::Png)), + ["image", "jpeg"] | ["image", "jpg"] => Ok(Self::Image(Image::Jpeg)), + ["image", "avif"] => Ok(Self::Image(Image::Avif)), + ["image", "webp"] => Ok(Self::Image(Image::Webp)), + ["image", "svg"] | ["image", "svg+xml"] => Ok(Self::Image(Image::Svg)), + ["image", "jxl"] => Ok(Self::Image(Image::JpegXL)), + + _ => Err(invalid_data_error("Invalid content-type-type")), + } + } +} diff --git a/src/content_type/mod.rs b/src/content_type/mod.rs new file mode 100644 index 0000000..2faf7f2 --- /dev/null +++ b/src/content_type/mod.rs @@ -0,0 +1,12 @@ +pub mod any_content; +pub mod any_content_type; +pub mod content; +pub mod content_type; +pub mod parameters; +pub mod types; + +use tokio::io; + +pub fn invalid_data_error(error: &str) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, error) +} diff --git a/src/content_type/parameters/charset.rs b/src/content_type/parameters/charset.rs new file mode 100644 index 0000000..0044548 --- /dev/null +++ b/src/content_type/parameters/charset.rs @@ -0,0 +1,12 @@ +#[derive(Debug, PartialEq)] +pub enum Charset { + UTF8, +} + +impl Charset { + pub fn to_str(&self) -> &'static str { + match self { + Charset::UTF8 => "utf-8", + } + } +} diff --git a/src/content_type/parameters/mod.rs b/src/content_type/parameters/mod.rs new file mode 100644 index 0000000..0a06b34 --- /dev/null +++ b/src/content_type/parameters/mod.rs @@ -0,0 +1,3 @@ +pub mod parameters; +pub mod charset; + diff --git a/src/content_type/parameters/parameters.rs b/src/content_type/parameters/parameters.rs new file mode 100644 index 0000000..e3b14c0 --- /dev/null +++ b/src/content_type/parameters/parameters.rs @@ -0,0 +1,37 @@ +use crate::content_type::parameters::charset::Charset; +use crate::content_type::invalid_data_error; + +use std::str::FromStr; + +#[derive(Debug, PartialEq)] +pub enum Parameter { + Preference(f32), + Charset(Charset), + Other(Box, Box), +} + +impl Parameter { + pub fn to_str(&self) -> Box { + match self { + Parameter::Preference(val) => format!("q={val}").into(), + Parameter::Charset(ch) => format!("charset={}", ch.to_str()).into(), + Parameter::Other(p, v) => format!("{p}={v}").into(), + } + } +} + +impl FromStr for Parameter { + type Err = std::io::Error; + + fn from_str(s: &str) -> Result { + match s.split('=').collect::>().as_slice() { + ["q", val] => Ok(Self::Preference( + val.parse::().map_err(|_| invalid_data_error("Invalid preference"))?, + )), + ["charset", "utf-8"] => Ok(Self::Charset(Charset::UTF8)), + [name, val] => Ok(Self::Other((*name).into(), (*val).into())), + _ => Err(invalid_data_error("Invalid parameter")), + } + } +} + diff --git a/src/content_type/types/application.rs b/src/content_type/types/application.rs new file mode 100644 index 0000000..e2cefa0 --- /dev/null +++ b/src/content_type/types/application.rs @@ -0,0 +1,19 @@ +#[derive(Debug, PartialEq)] +pub enum ApplicationType { + Json, + XhtmlXml, + Xml, + SignedExchange, +} + +impl ApplicationType { + pub fn to_str(&self) -> &'static str { + match self { + ApplicationType::Json => "json", + ApplicationType::XhtmlXml => "xhtml+xml", + ApplicationType::Xml => "xml", + ApplicationType::SignedExchange => "signed-exchange", + } + } +} + diff --git a/src/content_type/types/image.rs b/src/content_type/types/image.rs new file mode 100644 index 0000000..90116c4 --- /dev/null +++ b/src/content_type/types/image.rs @@ -0,0 +1,23 @@ +#[derive(Debug, PartialEq)] +pub enum Image { + Png, + Avif, + Jpeg, + Webp, + Svg, + JpegXL, +} + +impl Image { + pub fn to_str(&self) -> &'static str { + match self { + Image::Png => "png", + Image::Avif => "avif", + Image::Jpeg => "jpeg", + Image::Webp => "webp", + Image::Svg => "svg", + Image::JpegXL => "jxl", + } + } +} + diff --git a/src/content_type/types/mod.rs b/src/content_type/types/mod.rs new file mode 100644 index 0000000..6305888 --- /dev/null +++ b/src/content_type/types/mod.rs @@ -0,0 +1,4 @@ +pub mod text; +pub mod application; +pub mod image; + diff --git a/src/content_type/types/text.rs b/src/content_type/types/text.rs new file mode 100644 index 0000000..5f9e1b0 --- /dev/null +++ b/src/content_type/types/text.rs @@ -0,0 +1,17 @@ +#[derive(Debug, PartialEq)] +pub enum TextType { + Html, + Css, + Javascript, +} + +impl TextType { + pub fn to_str(&self) -> &'static str { + match self { + TextType::Html => "html", + TextType::Css => "css", + TextType::Javascript => "javascript", + } + } +} + diff --git a/src/game_manager.rs b/src/game_manager.rs new file mode 100644 index 0000000..01b5aff --- /dev/null +++ b/src/game_manager.rs @@ -0,0 +1,59 @@ +use tokio::sync::mpsc; + +pub enum ServerMessage {} + +pub enum ClientMessage {} + +struct Player { + receiver: mpsc::Receiver, + sender: mpsc::Sender, + + intended_move: Option, +} + +enum Move { + Move { start: (u8, u8), end: (u8, u8) }, + Attack, +} + +pub struct GameManager { + player_1: Player, + player_2: Player, +} + +impl GameManager { + pub fn new( + p1r: mpsc::Receiver, + p1s: mpsc::Sender, + + p2r: mpsc::Receiver, + p2s: mpsc::Sender, + ) -> Self { + Self { + player_1: Player { + receiver: p1r, + sender: p1s, + intended_move: None, + }, + player_2: Player { + receiver: p2r, + sender: p2s, + intended_move: None, + }, + } + } + + pub async fn run(&mut self) { + loop { + tokio::select! { + p1_action = self.player_1.receiver.recv() => { + + } + + p2_action = self.player_2.receiver.recv() => { + + } + } + } + } +} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..93474a8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,6 @@ +pub mod content_type; +pub mod game_manager; +pub mod player_queue; +pub mod request; +pub mod response; +pub mod websoket_connection; diff --git a/src/main.rs b/src/main.rs index 840e7a1..422f051 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,11 @@ -mod request; -mod response; -mod shared_enums; -mod websoket_connection; +use multiplayer_game::request; +use multiplayer_game::response; +use multiplayer_game::websoket_connection; use std::time::Duration; use std::{path::Path, str::FromStr}; -use tokio::io::{self, AsyncWriteExt}; +use tokio::io::AsyncWriteExt; use tokio::net::{TcpListener, TcpStream}; use tokio::time; @@ -16,27 +15,19 @@ use crate::{ response::{Response, ResponseCode, ResponseHeader}, }; -use tokio::sync; - #[tokio::main] async fn main() -> tokio::io::Result<()> { - let listener = TcpListener::bind("127.0.0.1:8080").await?; - let (sender, _) = sync::broadcast::channel(16); + let listener = TcpListener::bind("0.0.0.0:9123").await?; + loop { let (stream, _) = listener.accept().await?; - let receiver = sender.subscribe(); - let sender = sender.clone(); - tokio::spawn(handle_connection(stream, receiver, sender)); + tokio::spawn(handle_connection(stream)); } } -async fn handle_connection( - stream: TcpStream, - receiver: sync::broadcast::Receiver, - sender: sync::broadcast::Sender, -) -> tokio::io::Result<()> { +async fn handle_connection(stream: TcpStream) -> tokio::io::Result<()> { if let Some(ws) = handle_http_connection(stream).await? { - handle_websocket(ws, receiver, sender).await? + handle_websocket(ws).await? } Ok(()) @@ -45,17 +36,19 @@ async fn handle_connection( async fn handle_http_connection( mut stream: TcpStream, ) -> tokio::io::Result> { - let timeout = 50; + let keep_alive = Duration::from_secs(5); + loop { - let req = match time::timeout( - Duration::from_millis(timeout), - request::Request::from_bufreader(&mut stream), - ) - .await - { + let req = time::timeout(keep_alive, request::Request::from_bufreader(&mut stream)).await; + let req = match req { Ok(Ok(r)) => r, Ok(Err(e)) => { println!("Wrong request: {e}"); + let _ = Response::new() + .with_code(ResponseCode::BadRequest) + .with_header(ResponseHeader::Connection(Connection::Close)) + .respond(&mut stream) + .await; break; } Err(_) => { @@ -87,59 +80,34 @@ async fn handle_http_connection( )), _ => Response::new().with_code(ResponseCode::NotFound), }; + response.respond(&mut stream).await?; stream.flush().await?; if req.headers.contains(&request::RequestHeader::Connection( request::Connection::Close, - )) || !req.headers.contains(&request::RequestHeader::Connection( - request::Connection::KeepAlive, )) { break; } } + + let closing = Response::new() + .with_code(ResponseCode::Ok) + .with_header(ResponseHeader::Connection(Connection::Close)); + closing.respond(&mut stream).await?; + Ok(None) } async fn handle_websocket( mut web_socket: (WebsocketRead, WebsocketWrite), - receiver: sync::broadcast::Receiver, - sender: sync::broadcast::Sender, ) -> tokio::io::Result<()> { - tokio::spawn(broadcast_message(web_socket.1, receiver)); - loop { let message = web_socket.0.read_next_message().await?; if message.frame_type == FrameType::TextFrame { let s = String::from_utf8_lossy(&message.data).to_string(); println!("{}", s); - let _ = sender.send(s); - } - } -} - -enum BroadcastError { - IoError(io::Error), - BroadcastError(sync::broadcast::error::RecvError), -} - -async fn broadcast_message( - mut write: WebsocketWrite, - mut receiver: sync::broadcast::Receiver, -) -> Result<(), BroadcastError> { - loop { - let new_message = match receiver.recv().await { - Ok(s) => s, - Err(e) => return Err(BroadcastError::BroadcastError(e)), - }; - - match write - .send_message(FrameType::TextFrame, new_message.as_bytes()) - .await - { - Ok(()) => {} - Err(e) => return Err(BroadcastError::IoError(e)), } } } diff --git a/src/player_queue.rs b/src/player_queue.rs new file mode 100644 index 0000000..5b7cdd7 --- /dev/null +++ b/src/player_queue.rs @@ -0,0 +1,35 @@ +use crate::game_manager::{ClientMessage, GameManager, ServerMessage}; +use std::collections::vec_deque::VecDeque; +use tokio::sync::mpsc; + +type WebsocketHandle = (mpsc::Sender, mpsc::Receiver); + +pub struct Queue { + queue: VecDeque, + listener: mpsc::Receiver, +} + +impl Queue { + pub fn start() -> (Self, mpsc::Sender) { + let comms = mpsc::channel(10); + ( + Self { + queue: VecDeque::new(), + listener: comms.1, + }, + comms.0, + ) + } + + pub async fn run(mut self) { + while let Some(client) = self.listener.recv().await { + if self.queue.is_empty() { + self.queue.push_back(client); + } else if let Some(opponent_tx) = self.queue.pop_front() { + let (p1_client_tx, p1_server_rx) = opponent_tx; + let (p2_client_tx, p2_server_rx) = client; + GameManager::new(p1_server_rx, p1_client_tx, p2_server_rx, p2_client_tx); + } + } + } +} diff --git a/src/request.rs b/src/request.rs index cdc7884..2ba7983 100644 --- a/src/request.rs +++ b/src/request.rs @@ -1,13 +1,13 @@ use std::str::FromStr; -use tokio::io::{self, AsyncBufReadExt, AsyncReadExt, BufReader, Take}; +use tokio::io::{self, AsyncReadExt, BufReader}; use tokio::net::TcpStream; -use crate::shared_enums::Content; +use crate::content_type::any_content::Content; -const MAX_LINE_WIDTH: u64 = 4096; // 4 KiB -const MAX_BODY_LENGTH: u64 = 8388608; // 5 MiB -const MAX_HEADER_COUNT: u64 = 512; +const MAX_LINE_WIDTH: usize = 4096; // 4 KiB +const MAX_BODY_LENGTH: usize = 8388608; // 5 MiB +const MAX_HEADER_COUNT: usize = 512; #[derive(Debug)] pub struct Request { @@ -156,12 +156,10 @@ impl FromStr for RequestHeader { impl Request { pub async fn from_bufreader(buffer: &mut TcpStream) -> tokio::io::Result { - let buffer = BufReader::new(buffer); + let mut new_buffer = BufReader::new(buffer); - let mut limited_buffer = buffer.take(MAX_LINE_WIDTH); - - let first_line = Self::read_line(&mut limited_buffer).await?; - let parsed_first_line = Self::parse_first_line(first_line)?; + let first_line: String = Self::read_line(&mut new_buffer).await?; + let parsed_first_line = Self::parse_first_line(&first_line)?; use std::collections::hash_set::HashSet; use std::mem::{Discriminant, discriminant}; @@ -170,7 +168,7 @@ impl Request { let mut headers = vec![]; for _ in 0..MAX_HEADER_COUNT { - let current_line = Self::read_line(&mut limited_buffer).await?; + let current_line = Self::read_line(&mut new_buffer).await?; if current_line.is_empty() || current_line == "\r\n" { break; @@ -200,25 +198,42 @@ impl Request { }) } - async fn read_line(buffer: &mut Take>) -> tokio::io::Result { - let mut read_buffer = vec![]; - buffer.set_limit(MAX_LINE_WIDTH); - buffer.read_until(b'\n', &mut read_buffer).await?; + async fn read_line(reader: &mut BufReader<&mut TcpStream>) -> tokio::io::Result { + let mut buf = Vec::with_capacity(256); + loop { + let mut byte = [0u8; 1]; + let n = reader.read(&mut byte).await?; - if read_buffer.len() < 2 { - return Err(tokio::io::Error::new( - tokio::io::ErrorKind::InvalidData, - "Invalid line", - )); + if n == 0 { + return Err(tokio::io::Error::new( + tokio::io::ErrorKind::UnexpectedEof, + "Connection closed", + )); + } + + let b = byte[0]; + if b == b'\n' { + break; + } + + if buf.len() >= MAX_LINE_WIDTH { + return Err(tokio::io::Error::new( + tokio::io::ErrorKind::InvalidData, + "Line too long", + )); + } + + buf.push(b); } - read_buffer.remove(read_buffer.len() - 1); - read_buffer.remove(read_buffer.len() - 1); + if buf.ends_with(b"\r") { + buf.pop(); + } - Ok(String::from_utf8_lossy(&read_buffer).to_string()) + Ok(String::from_utf8_lossy(&buf).to_string()) } - fn parse_first_line(line: String) -> tokio::io::Result<(Method, ServerPath, String)> { + fn parse_first_line(line: &str) -> tokio::io::Result<(Method, ServerPath, String)> { let splitted_line: Vec<&str> = line.split_whitespace().collect(); match splitted_line.as_slice() { diff --git a/src/response.rs b/src/response.rs index e301211..1ae20cc 100644 --- a/src/response.rs +++ b/src/response.rs @@ -1,8 +1,8 @@ use std::{ffi::OsStr, path::Path}; use crate::{ + content_type::{content::Content, content_type::ContentType, types}, request::{Connection, ServerPath, Upgrade}, - shared_enums::{Content, ContentType}, }; use tokio::io::{self, AsyncWriteExt}; @@ -20,13 +20,10 @@ impl Response { pub async fn respond(self, stream: &mut TcpStream) -> Result<(), io::Error> { let binding = self.to_str(); let mut output = binding.as_bytes().to_vec(); + output.extend_from_slice(format!("Content-Length: {}\r\n\r\n", self.data.len()).as_bytes()); if !self.data.is_empty() { - output.extend_from_slice(format!("Content-Length: {}", self.data.len()).as_bytes()); - output.extend_from_slice(b"\r\n\r\n"); output.extend_from_slice(&self.data); - } else if !self.headers.is_empty() { - output.extend_from_slice(b"\r\n"); } stream.write_all(output.as_slice()).await?; @@ -91,14 +88,10 @@ impl Response { let bytes = std::fs::read(path)?; let content_type = match path.extension() { - Some(a) if a == OsStr::new("html") => { - ContentType::Text(crate::shared_enums::TextType::Html) - } - Some(a) if a == OsStr::new("css") => { - ContentType::Text(crate::shared_enums::TextType::Css) - } + Some(a) if a == OsStr::new("html") => ContentType::Text(types::text::TextType::Html), + Some(a) if a == OsStr::new("css") => ContentType::Text(types::text::TextType::Css), Some(a) if a == OsStr::new("js") => { - ContentType::Text(crate::shared_enums::TextType::Javascript) + ContentType::Text(types::text::TextType::Javascript) } Some(_) | None => { return Err(io::Error::new( diff --git a/src/shared_enums.rs b/src/shared_enums.rs deleted file mode 100644 index 341fbc4..0000000 --- a/src/shared_enums.rs +++ /dev/null @@ -1,308 +0,0 @@ -use std::{io, str::FromStr}; - -#[derive(Debug, PartialEq)] -pub enum ContentType { - Text(TextType), - Aplication(ApplicationType), - Image(Image), - Any, -} - -impl ContentType { - fn to_str(&self) -> Box { - match self { - ContentType::Text(text) => format!("text/{}", text.to_str()).into(), - ContentType::Aplication(app) => format!("application/{}", app.to_str()).into(), - ContentType::Image(img) => format!("image/{}", img.to_str()).into(), - ContentType::Any => "*/*".into(), - } - } -} - -#[derive(Debug, PartialEq)] -pub enum Parameter { - Preference(f32), - Charset(Charset), - Other(Box, Box), -} - -impl Parameter { - fn to_str(&self) -> Box { - match &self { - Parameter::Preference(val) => format!("q={val}").into(), - Parameter::Charset(ch) => format!("charset={}", ch.to_str()).into(), - Parameter::Other(p, v) => format!("{p}={v}").into(), - } - } -} - -#[derive(Debug, PartialEq)] -pub enum Charset { - UTF8, -} - -impl Charset { - fn to_str(&self) -> &'static str { - match self { - Charset::UTF8 => "utf-8", - } - } -} - -#[derive(Debug, PartialEq)] -pub struct Content { - pub content_type: ContentType, - pub parameter: Option>, -} - -impl Content { - pub fn to_str(&self) -> Box { - match &self.parameter { - Some(p) => format!( - "{}; {}", - self.content_type.to_str(), - p.iter() - .map(|par| par.to_str()) - .collect::>() - .join("; ") - ) - .into(), - None => self.content_type.to_str(), - } - } -} - -impl Content { - pub fn new(content_type: ContentType) -> Self { - Self { - content_type, - parameter: None, - } - } - - pub fn with_params(content_type: ContentType, params: Vec) -> Self { - Self { - content_type, - parameter: Some(params), - } - } - - pub fn add_parameter(&mut self, param: Parameter) { - match &mut self.parameter { - Some(params) => params.push(param), - None => self.parameter = Some(vec![param]), - } - } - - pub fn quality(&self) -> Option { - self.parameter.as_ref()?.iter().find_map(|p| { - if let Parameter::Preference(q) = p { - Some(*q) - } else { - None - } - }) - } - - pub fn charset(&self) -> Option<&Charset> { - self.parameter.as_ref()?.iter().find_map(|p| { - if let Parameter::Charset(cs) = p { - Some(cs) - } else { - None - } - }) - } - - pub fn matches(&self, other: &ContentType) -> bool { - type C = ContentType; - match (&self.content_type, other) { - (C::Any, _) | (_, C::Any) => true, - (C::Text(TextType::Any), C::Text(_)) => true, - (C::Text(_), C::Text(TextType::Any)) => true, - (C::Aplication(ApplicationType::Any), C::Aplication(_)) => true, - (C::Aplication(_), C::Aplication(ApplicationType::Any)) => true, - (C::Image(Image::Any), C::Image(_)) => true, - (C::Image(_), C::Image(Image::Any)) => true, - (a, b) => std::mem::discriminant(a) == std::mem::discriminant(b), - } - } - - pub fn is_text(&self) -> bool { - matches!(self.content_type, ContentType::Text(_)) - } - - pub fn is_application(&self) -> bool { - matches!(self.content_type, ContentType::Aplication(_)) - } - - pub fn is_image(&self) -> bool { - matches!(self.content_type, ContentType::Image(_)) - } - - pub fn html_utf8() -> Self { - Self::with_params( - ContentType::Text(TextType::Html), - vec![Parameter::Charset(Charset::UTF8)], - ) - } - - pub fn json_utf8() -> Self { - Self::with_params( - ContentType::Aplication(ApplicationType::Json), - vec![Parameter::Charset(Charset::UTF8)], - ) - } -} - -pub fn invalid_data_error(error: &str) -> io::Error { - io::Error::new(io::ErrorKind::InvalidData, error) -} - -impl FromStr for Content { - type Err = io::Error; - - fn from_str(s: &str) -> Result { - match s.split(';').collect::>().as_slice() { - [val] => Ok(Self { - content_type: ContentType::from_str(val)?, - parameter: None, - }), - - [val, par @ ..] => Ok(Self { - content_type: ContentType::from_str(val)?, - parameter: Some( - par.iter() - .map(|p| Parameter::from_str(p)) - .collect::, _>>()?, - ), - }), - _ => Err(invalid_data_error("Invalid content-type")), - } - } -} - -impl FromStr for Parameter { - type Err = io::Error; - - fn from_str(s: &str) -> Result { - match s.split('=').collect::>().as_slice() { - ["q", value] => { - let pref_val = match value.parse::() { - Ok(v) => Ok(v), - Err(_) => Err(invalid_data_error("Invalid preference")), - }?; - - Ok(Parameter::Preference(pref_val)) - } - - ["charset", "utf-8"] => Ok(Parameter::Charset(Charset::UTF8)), - - [t, v] => Ok(Parameter::Other((*t).into(), (*v).into())), - - _ => Err(io::Error::new( - io::ErrorKind::InvalidData, - "Invalid parameter", - )), - } - } -} - -impl FromStr for ContentType { - type Err = io::Error; - - fn from_str(s: &str) -> Result { - let parts: Vec<&str> = s.split("/").collect(); - - match parts.as_slice() { - ["*", "*"] => Ok(ContentType::Any), - ["text", "*"] => Ok(ContentType::Text(TextType::Any)), - ["text", "html"] => Ok(ContentType::Text(TextType::Html)), - ["text", "css"] => Ok(ContentType::Text(TextType::Css)), - ["text", "javascript"] => Ok(ContentType::Text(TextType::Javascript)), - - ["application", "json"] => Ok(ContentType::Aplication(ApplicationType::Json)), - ["application", "xhtml+xml"] => Ok(ContentType::Aplication(ApplicationType::XhtmlXml)), - ["application", "xml"] => Ok(ContentType::Aplication(ApplicationType::Xml)), - ["application", "signed-exchange"] => Ok(ContentType::Aplication(ApplicationType::Any)), - ["application", "*"] => Ok(ContentType::Aplication(ApplicationType::Any)), - - ["image", "png"] => Ok(ContentType::Image(Image::Png)), - ["image", "apng"] => Ok(ContentType::Image(Image::Png)), - ["image", "jpeg"] | ["image", "jpg"] => Ok(ContentType::Image(Image::Jpeg)), - ["image", "avif"] => Ok(ContentType::Image(Image::Avif)), - ["image", "webp"] => Ok(ContentType::Image(Image::Webp)), - ["image", "svg"] | ["image", "svg+xml"] => Ok(ContentType::Image(Image::Svg)), - ["image", "*"] => Ok(ContentType::Image(Image::Any)), - ["image", "jxl"] => Ok(ContentType::Image(Image::JpegXL)), - - _ => { - println!("{parts:?}"); - Err(invalid_data_error("Invalid content-type-type")) - } - } - } -} - -#[derive(Debug, PartialEq)] -pub enum Image { - Png, - Avif, - Jpeg, - Webp, - Svg, - Any, - JpegXL, -} - -impl Image { - fn to_str(&self) -> &'static str { - match self { - Image::Png => "png", - Image::Avif => "avif", - Image::Jpeg => "jpeg", - Image::Webp => "webp", - Image::Svg => "svg", - Image::JpegXL => "jxl", - Image::Any => "*", - } - } -} - -#[derive(Debug, PartialEq)] -pub enum TextType { - Html, - Css, - Javascript, - Any, -} - -impl TextType { - fn to_str(&self) -> &'static str { - match self { - TextType::Html => "html", - TextType::Css => "css", - TextType::Javascript => "javascript", - TextType::Any => "*", - } - } -} - -#[derive(Debug, PartialEq)] -pub enum ApplicationType { - Json, - Any, - XhtmlXml, - Xml, -} - -impl ApplicationType { - fn to_str(&self) -> &'static str { - match self { - ApplicationType::Json => "json", - ApplicationType::Any => "*", - ApplicationType::XhtmlXml => "xhtml+xml", - ApplicationType::Xml => "xml", - } - } -}