diff --git a/.cargo/audit.toml b/.cargo/audit.toml new file mode 100644 index 0000000..9934f20 --- /dev/null +++ b/.cargo/audit.toml @@ -0,0 +1,2 @@ +[advisories] +ignore = ["RUSTSEC-2023-0071"] # rsa, no safe upgrade is available diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..c91c3f3 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[net] +git-fetch-with-cli = true diff --git a/.cargo/deny.toml b/.cargo/deny.toml new file mode 100644 index 0000000..81a79a8 --- /dev/null +++ b/.cargo/deny.toml @@ -0,0 +1,47 @@ +[graph] +targets = [] +all-features = false +no-default-features = false +exclude-dev = true + +[output] +feature-depth = 1 + +[licenses] +allow = [ + "Apache-2.0", + "MIT", + "Unicode-3.0", +] + +confidence-threshold = 0.8 +exceptions = [] + +[licenses.private] +ignore = true +registries = [] + +[bans] +multiple-versions = "allow" +wildcards = "allow" +highlight = "all" +workspace-default-features = "allow" +external-default-features = "allow" +allow = [] +deny = [] +skip = [] +skip-tree = [] + +[sources] +unknown-registry = "warn" +unknown-git = "warn" +allow-registry = ["https://github.com/rust-lang/crates.io-index"] +allow-git = [] + +[sources.allow-org] +github = [] +gitlab = [] +bitbucket = [] + +[advisories] +ignore = [] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..ae92c92 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,72 @@ +name: CI + +on: + pull_request: + branches: [main, master] + push: + branches: [main, master] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +env: + CARGO_TERM_COLOR: always + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Cache cargo and build + uses: Swatinem/rust-cache@v2 + + - name: Run rustfmt + run: cargo fmt --all -- --check + + - name: Run clippy + run: cargo clippy --all-targets --all-features -- -D warnings + + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo and build + uses: Swatinem/rust-cache@v2 + + - name: Run tests + run: cargo test --all-features --verbose --locked + + build: + name: Build (release) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo and build + uses: Swatinem/rust-cache@v2 + + - name: Build release + run: cargo build --release --all-features --locked + + - name: Upload binary + uses: actions/upload-artifact@v4 + with: + name: tiny-proxy-linux + path: target/release/tiny-proxy + if-no-files-found: error \ No newline at end of file diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml new file mode 100644 index 0000000..257fbe7 --- /dev/null +++ b/.github/workflows/pr-check.yml @@ -0,0 +1,72 @@ +name: PR Check + +on: + pull_request: + branches: [main, master] + workflow_dispatch: + +env: + CARGO_TERM_COLOR: always + +jobs: + quick-check: + name: Quick Quality Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + components: rustfmt, clippy + + - name: Cache cargo and build + uses: Swatinem/rust-cache@v2 + + - name: Check formatting + run: cargo fmt --all -- --check + + - name: Run Clippy + run: cargo clippy --all-targets --all-features -- -D warnings + + - name: Quick build check + run: cargo check --all-features --locked + + - name: Check documentation + run: cargo doc --no-deps --all-features --document-private-items + + security-audit: + name: Security Audit + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo and build + uses: Swatinem/rust-cache@v2 + + - name: Install cargo-audit + uses: taiki-e/install-action@cargo-audit + + - name: Run cargo audit + run: cargo audit + + license-check: + name: License Check + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache cargo and build + uses: Swatinem/rust-cache@v2 + + - name: Install cargo-deny + uses: taiki-e/install-action@cargo-deny + + - name: Check licenses + run: cargo deny check licenses \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..3620764 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,175 @@ +name: Release + +on: + push: + tags: + - "v*.*.*" + workflow_dispatch: + inputs: + tag: + description: "Tag to release (e.g., v0.1.0)" + required: true + type: string + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false + +env: + CARGO_TERM_COLOR: always + RUSTFLAGS: "-C strip=symbols" + +jobs: + release-build: + name: Build Release Binaries + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + artifact: tiny-proxy-linux-amd64 + use-cross: false + + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + artifact: tiny-proxy-linux-arm64 + use-cross: true + + - os: macos-13 + target: x86_64-apple-darwin + artifact: tiny-proxy-macos-amd64 + use-cross: false + + - os: macos-14 + target: aarch64-apple-darwin + artifact: tiny-proxy-macos-arm64 + use-cross: false + + - os: windows-latest + target: x86_64-pc-windows-msvc + artifact: tiny-proxy-windows-amd64.exe + use-cross: false + + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + with: + targets: ${{ matrix.target }} + + - name: Cache + uses: Swatinem/rust-cache@v2 + + - name: Install cross + if: matrix.use-cross + uses: taiki-e/install-action@cross + + - name: Build + run: | + if [ "${{ matrix.use-cross }}" = "true" ]; then + cross build --release --target ${{ matrix.target }} --all-features --locked + else + cargo build --release --target ${{ matrix.target }} --all-features --locked + fi + shell: bash + + - name: Prepare artifact + run: | + mkdir -p dist + + BIN_PATH=target/${{ matrix.target }}/release/tiny-proxy + + if [ "${{ runner.os }}" = "Windows" ]; then + BIN_PATH="${BIN_PATH}.exe" + fi + + cp "$BIN_PATH" dist/${{ matrix.artifact }} + + cd dist + shasum -a 256 ${{ matrix.artifact }} > ${{ matrix.artifact }}.sha256 + shell: bash + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: ${{ matrix.artifact }} + path: dist/ + + publish-crate: + name: Publish to crates.io + runs-on: ubuntu-latest + needs: release-build + steps: + - uses: actions/checkout@v4 + + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: Cache + uses: Swatinem/rust-cache@v2 + + - name: Publish + run: cargo publish --locked || echo "Already published" + env: + CARGO_REGISTRY_TOKEN: ${{ secrets.CRATES_IO_TOKEN }} + + create-release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: [release-build, publish-crate] + + steps: + - uses: actions/checkout@v4 + + - name: Download artifacts + uses: actions/download-artifact@v4 + with: + path: dist + + - name: Get tag + id: tag + run: | + if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + echo "tag=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT + else + echo "tag=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT + fi + + - name: Create release notes + run: | + cat > release_notes.md << EOF + # tiny-proxy ${{ steps.tag.outputs.tag }} + + ## Installation + + ### Linux/macOS + \`\`\`bash + curl -L https://github.com/${{ github.repository }}/releases/download/${{ steps.tag.outputs.tag }}/tiny-proxy-linux-amd64 -o tiny-proxy + chmod +x tiny-proxy + ./tiny-proxy --help + \`\`\` + + ### Windows + Download and run: + tiny-proxy-windows-amd64.exe + + ### Cargo + \`\`\`bash + cargo install tiny-proxy + \`\`\` + EOF + + - name: Create GitHub Release + uses: softprops/action-gh-release@v1 + with: + tag_name: ${{ steps.tag.outputs.tag }} + name: tiny-proxy ${{ steps.tag.outputs.tag }} + body_path: release_notes.md + files: dist/**/* + draft: false + prerelease: ${{ contains(steps.tag.outputs.tag, '-') }} + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/Cargo.lock b/Cargo.lock index 10891d1..f1689a8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -85,29 +85,17 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" -[[package]] -name = "base64" -version = "0.22.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" - [[package]] name = "bitflags" version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" -[[package]] -name = "bumpalo" -version = "3.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" - [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cast" @@ -236,8 +224,6 @@ dependencies = [ "num-traits", "once_cell", "oorandom", - "plotters", - "rayon", "regex", "serde", "serde_derive", @@ -256,31 +242,6 @@ dependencies = [ "itertools", ] -[[package]] -name = "crossbeam-deque" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" -dependencies = [ - "crossbeam-epoch", - "crossbeam-utils", -] - -[[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 = "crunchy" version = "0.2.4" @@ -293,12 +254,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "equivalent" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" - [[package]] name = "errno" version = "0.3.14" @@ -357,12 +312,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - [[package]] name = "futures-task" version = "0.3.31" @@ -393,25 +342,6 @@ dependencies = [ "wasip2", ] -[[package]] -name = "h2" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" -dependencies = [ - "atomic-waker", - "bytes", - "fnv", - "futures-core", - "futures-sink", - "http", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "half" version = "2.7.1" @@ -423,12 +353,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "hashbrown" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" - [[package]] name = "heck" version = "0.5.0" @@ -497,7 +421,6 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "h2", "http", "http-body", "httparse", @@ -532,7 +455,6 @@ version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c6995591a8f1380fcb4ba966a252a4b29188d51d2b89e3a252f5305be65aea8" dependencies = [ - "base64", "bytes", "futures-channel", "futures-core", @@ -540,34 +462,14 @@ dependencies = [ "http", "http-body", "hyper", - "ipnet", "libc", - "percent-encoding", "pin-project-lite", "socket2", - "system-configuration", "tokio", "tower-service", "tracing", - "windows-registry", -] - -[[package]] -name = "indexmap" -version = "2.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" -dependencies = [ - "equivalent", - "hashbrown", ] -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - [[package]] name = "is-terminal" version = "0.4.17" @@ -600,16 +502,6 @@ version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" -[[package]] -name = "js-sys" -version = "0.3.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" -dependencies = [ - "once_cell", - "wasm-bindgen", -] - [[package]] name = "lazy_static" version = "1.5.0" @@ -628,15 +520,6 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" -[[package]] -name = "lock_api" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" -dependencies = [ - "scopeguard", -] - [[package]] name = "log" version = "0.4.28" @@ -776,35 +659,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "parking_lot" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-link 0.2.1", -] - -[[package]] -name = "percent-encoding" -version = "2.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" - [[package]] name = "pin-project-lite" version = "0.2.16" @@ -823,34 +677,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "plotters" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" -dependencies = [ - "num-traits", - "plotters-backend", - "plotters-svg", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "plotters-backend" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" - -[[package]] -name = "plotters-svg" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" -dependencies = [ - "plotters-backend", -] - [[package]] name = "proc-macro2" version = "1.0.103" @@ -875,35 +701,6 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "rayon" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" -dependencies = [ - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" -dependencies = [ - "crossbeam-deque", - "crossbeam-utils", -] - -[[package]] -name = "redox_syscall" -version = "0.5.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" -dependencies = [ - "bitflags", -] - [[package]] name = "regex" version = "1.12.3" @@ -946,12 +743,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "rustversion" -version = "1.0.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" - [[package]] name = "same-file" version = "1.0.6" @@ -970,12 +761,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - [[package]] name = "security-framework" version = "2.11.1" @@ -1059,19 +844,14 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.6" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2a4719bff48cee6b39d12c020eeb490953ad2443b7055bd0b21fca26bd8c28b" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] -[[package]] -name = "slab" -version = "0.4.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" - [[package]] name = "smallvec" version = "1.15.1" @@ -1105,27 +885,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tempfile" version = "3.23.0" @@ -1176,6 +935,7 @@ dependencies = [ "bytes", "clap", "criterion", + "http-body", "http-body-util", "hyper", "hyper-tls", @@ -1208,7 +968,6 @@ dependencies = [ "bytes", "libc", "mio", - "parking_lot", "pin-project-lite", "signal-hook-registry", "socket2", @@ -1237,19 +996,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "tokio-util" -version = "0.7.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2efa149fe76073d6e8fd97ef4f4eca7b67f599660115591483572e406e165594" -dependencies = [ - "bytes", - "futures-core", - "futures-sink", - "pin-project-lite", - "tokio", -] - [[package]] name = "tower-service" version = "0.3.3" @@ -1381,61 +1127,6 @@ dependencies = [ "wit-bindgen", ] -[[package]] -name = "wasm-bindgen" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" -dependencies = [ - "cfg-if", - "once_cell", - "rustversion", - "wasm-bindgen-macro", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" -dependencies = [ - "bumpalo", - "proc-macro2", - "quote", - "syn", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.114" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "web-sys" -version = "0.3.91" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "winapi-util" version = "0.1.11" @@ -1445,47 +1136,12 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "windows-link" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" - [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-registry" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" -dependencies = [ - "windows-link 0.1.3", - "windows-result", - "windows-strings", -] - -[[package]] -name = "windows-result" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" -dependencies = [ - "windows-link 0.1.3", -] - -[[package]] -name = "windows-strings" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" -dependencies = [ - "windows-link 0.1.3", -] - [[package]] name = "windows-sys" version = "0.60.2" @@ -1501,7 +1157,7 @@ version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows-link 0.2.1", + "windows-link", ] [[package]] @@ -1510,7 +1166,7 @@ version = "0.53.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3" dependencies = [ - "windows-link 0.2.1", + "windows-link", "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", diff --git a/Cargo.toml b/Cargo.toml index a86d51c..597b085 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,37 @@ name = "tiny-proxy" version = "0.1.0" edition = "2021" +description = "A high-performance HTTP reverse proxy server written in Rust with SSE support, connection pooling, and configurable routing" +license = "MIT OR Apache-2.0" +repository = "https://github.com/denislituev/tiny-proxy" +readme = "README.md" +homepage = "https://github.com/denislituev/tiny-proxy" +documentation = "https://docs.rs/tiny-proxy" +categories = [ + "network-programming", + "web-programming::http-server", + "web-programming::http-client", + "asynchronous", + "command-line-utilities", +] +include = [ + "src/**", + "Cargo.toml", + "README.md", + "LICENSE*", +] +keywords = [ + "proxy", + "reverse-proxy", + "http", + "server-sent-events", + "sse", + "high-performance", + "tokio", + "hyper", +] +authors = ["Dzianis Lituyeu"] +rust-version = "1.75" [lib] name = "tiny_proxy" @@ -10,12 +41,13 @@ path = "src/lib.rs" [[bin]] name = "tiny-proxy" path = "src/main.rs" +required-features = ["cli"] [dependencies] -hyper = { version = "1.3", features = ["full"] } -hyper-util = { version = "0.1", features = ["full"] } +hyper = { version = "1.3", features = ["http1", "client", "server"] } +hyper-util = { version = "0.1", features = ["client", "server", "http1", "tokio"] } http-body-util = "0.1" -tokio = { version = "1", features = ["full"] } +tokio = { version = "1", features = ["rt-multi-thread", "macros", "net", "time", "signal", "process", "fs"] } anyhow = "1.0" thiserror = "2.0.18" bytes = "1.0" @@ -23,21 +55,37 @@ num_cpus = "1.16" serde = { version = "1.0", features = ["derive"], optional = true } serde_json = { version = "1.0", optional = true } -hyper-tls = "0.6" +hyper-tls = { version = "0.6", optional = true } tracing = "0.1" clap = { version = "4.5", features = ["derive"], optional = true } -tracing-subscriber = { version = "0.3", features = ["env-filter"], optional = true } +tracing-subscriber = { version = "0.3", features = ["env-filter", "fmt"], optional = true } +http-body = "1.0.1" [dev-dependencies] -criterion = "0.5.1" +criterion = { version = "0.5.1", default-features = false } + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[profile.release] +opt-level = 3 +lto = true +codegen-units = 1 +strip = true +panic = "abort" + +[profile.bench] +inherits = "release" +debug = true [[bench]] name = "proxy_bench" harness = false [features] -default = ["cli"] +default = ["cli", "tls"] cli = ["dep:clap", "dep:tracing-subscriber"] - +tls = ["dep:hyper-tls"] api = ["dep:serde", "dep:serde_json"] -full = ["cli", "api"] +full = ["cli", "tls", "api"] diff --git a/README.md b/README.md index 6fbe61b..96e3184 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,12 @@ # Tiny Proxy +[![CI](https://github.com/denislituev/tiny-proxy/actions/workflows/ci.yml/badge.svg)](https://github.com/denislituev/tiny-proxy/actions) +[![Version](https://img.shields.io/crates/v/tiny-proxy)](https://crates.io/crates/tiny-proxy) +[![Downloads](https://img.shields.io/crates/d/tiny-proxy)](https://crates.io/crates/tiny-proxy) + +[![License](https://img.shields.io/crates/l/tiny-proxy)](LICENSE) +[![Rust](https://img.shields.io/badge/rust-1.75%2B-orange)](https://www.rust-lang.org/) + Lightweight, embeddable HTTP reverse proxy written in Rust with Caddy-like configuration syntax. ## Features @@ -278,6 +285,7 @@ Use placeholders in header values: ### Default Features - `cli` - Command-line interface support (enabled by default) +- `tls` - HTTPS backend support (enabled by default) ### Optional Features @@ -286,7 +294,7 @@ Use placeholders in header values: tiny-proxy = { version = "0.1", features = ["full"] } # Or select specific features -tiny-proxy = { version = "0.1", features = ["cli", "auth", "api"] } +tiny-proxy = { version = "0.1", features = ["cli", "tls", "api"] } ``` #### `cli` (default) @@ -307,6 +315,10 @@ let is_valid = auth::validate_token(&req, "http://auth-service/validate").await? let value = auth::process_header_substitution("Bearer {header.Authorization}", &req)?; ``` +#### `tls` (default) + +Enable HTTPS backend support using `hyper-tls`. + #### `api` Management API for runtime configuration: @@ -401,7 +413,6 @@ tiny-proxy/ ├── examples/ # Usage examples ├── tests/ # Integration tests ├── benches/ # Benchmarks -└── ex/ # Architecture and roadmap docs ``` ### Build with Features @@ -435,7 +446,7 @@ cargo run --example hot_reload --features auth ## Roadmap -See [ex/roadmap.md](ex/roadmap.md) for detailed roadmap. + ### Current Status @@ -478,20 +489,3 @@ Contributions are welcome! Please: See [LICENSE](LICENSE) file. -## Architecture - -For detailed architecture documentation, see [ex/ARCHITECTURE.md](ex/ARCHITECTURE.md). - -## Related Projects - -- [Caddy](https://caddyserver.com/) - Inspiration for configuration format -- [hyper](https://hyper.rs/) - HTTP library used -- [tokio](https://tokio.rs/) - Async runtime - -## Support - -For issues and questions: - -- Open an issue on GitHub -- Check [ex/roadmap.md](ex/roadmap.md) for known issues -- See [examples/](examples/) for usage patterns \ No newline at end of file diff --git a/benches/proxy_bench.rs b/benches/proxy_bench.rs index 0b1f885..8542953 100644 --- a/benches/proxy_bench.rs +++ b/benches/proxy_bench.rs @@ -20,6 +20,7 @@ //! ``` use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; +use std::str::FromStr; use tiny_proxy::config::{Config, Directive, SiteConfig}; use tiny_proxy::proxy::handler::match_pattern; diff --git a/clippy.toml b/clippy.toml new file mode 100644 index 0000000..0789e7e --- /dev/null +++ b/clippy.toml @@ -0,0 +1,48 @@ +# Clippy configuration file +# More info: https://doc.rust-lang.org/clippy/configuration.html + +# Maximum allowed cognitive complexity for a function +# Proxy request handling can be complex, but we want to keep it maintainable +cognitive-complexity-threshold = 30 + +# Maximum allowed lines for a function +# Allow slightly longer functions for complex request routing logic +too-many-lines-threshold = 120 + +# Maximum allowed arguments for a function +# Keep functions focused with limited arguments +too-many-arguments-threshold = 7 + +# Valid identifiers in documentation +# Include HTTP, proxy, and networking-related terms +doc-valid-idents = [ + "HTTP", + "HTTPS", + "URL", + "URI", + "UUID", + "TCP", + "TLS", + "SSL", + "SSE", + "JSON", + "WebSocket", + "CLI", + "API", + "DNS", + "IPv4", + "IPv6", +] + +# Avoid breaking the public API when fixing clippy warnings +avoid-breaking-exported-api = true + +# Performance-critical settings for proxy server +# Warn about expensive operations in hot paths +warn-on-all-wildcard-imports = true + +# Disallow certain patterns that can hurt performance +disallowed-methods = [] + +# Enforce specific lints for high-performance code +# These will be set to deny in the CI pipeline diff --git a/src/api/endpoints.rs b/src/api/endpoints.rs index 817e4ec..1d4f32e 100644 --- a/src/api/endpoints.rs +++ b/src/api/endpoints.rs @@ -1,47 +1,25 @@ //! API endpoints for proxy management -//! -//! This module provides handlers for the management API endpoints. use anyhow::Result; use bytes::Bytes; +use http_body::Body; use http_body_util::{BodyExt, Full}; -use hyper::body::Incoming; use hyper::{Request, Response}; use std::sync::Arc; use tracing::info; use crate::config::Config; -/// Handle GET /config - Return current configuration -/// -/// Returns the current proxy configuration in JSON format. -/// -/// # Arguments -/// -/// * `_req` - The incoming HTTP request (not used directly) -/// * `config` - Shared configuration wrapped in Arc> -/// -/// # Returns -/// -/// HTTP response containing the current configuration -/// -/// # Example -/// -/// ```no_run -/// # use tiny_proxy::api::endpoints; -/// # use hyper::Request; -/// # async fn example(config: Arc>) -> hyper::Response> { -/// let req = Request::builder().body(hyper::body::Incoming::empty()).unwrap(); -/// endpoints::handle_get_config(req, config).await -/// # } -/// ``` -pub async fn handle_get_config( - _req: Request, +/// Handle GET /config +pub async fn handle_get_config( + _req: Request, config: Arc>, -) -> Result>> { +) -> Result>> +where + B: Body, +{ let config = config.read().await; - // Convert config to JSON (simplified version) let json = serde_json::to_string_pretty(&*config) .unwrap_or_else(|_| r#"{"error": "Failed to serialize config"}"#.to_string()); @@ -56,34 +34,15 @@ pub async fn handle_get_config( Ok(response) } -/// Handle POST /config - Update configuration -/// -/// Updates the proxy configuration with new settings provided in the request body. -/// -/// # Arguments -/// -/// * `req` - The incoming HTTP request containing the new configuration -/// * `config` - Shared configuration wrapped in Arc> -/// -/// # Returns -/// -/// HTTP response indicating success or failure -/// -/// # Example -/// -/// ```no_run -/// # use tiny_proxy::api::endpoints; -/// # use hyper::Request; -/// # async fn example(config: Arc>) -> hyper::Response> { -/// let req = Request::builder().body(hyper::body::Incoming::empty()).unwrap(); -/// endpoints::handle_post_config(req, config).await -/// # } -/// ``` -pub async fn handle_post_config( - req: Request, +/// Handle POST /config +pub async fn handle_post_config( + req: Request, _config: Arc>, -) -> Result>> { - // Collect request body +) -> Result>> +where + B: Body, + B::Error: std::fmt::Display, +{ let body_bytes = match BodyExt::collect(req.into_body()).await { Ok(collected) => collected.to_bytes(), Err(e) => { @@ -98,12 +57,6 @@ pub async fn handle_post_config( } }; - // Parse configuration from JSON (simplified version) - // In a real implementation, you would: - // 1. Parse the JSON body - // 2. Validate the configuration - // 3. Update the shared config - let body_str = match std::str::from_utf8(&body_bytes) { Ok(s) => s, Err(_) => { @@ -120,8 +73,6 @@ pub async fn handle_post_config( info!("POST /config - Updating configuration"); info!("New config: {}", body_str); - // TODO: Parse JSON and update config - // For now, just return a success message let response = Response::builder() .status(200) .header("Content-Type", "application/json") @@ -133,29 +84,11 @@ pub async fn handle_post_config( Ok(response) } -/// Handle GET /health - Health check endpoint -/// -/// Returns the health status of the proxy server. -/// -/// # Arguments -/// -/// * `_req` - The incoming HTTP request (not used) -/// -/// # Returns -/// -/// HTTP response with health status -/// -/// # Example -/// -/// ```no_run -/// # use tiny_proxy::api::endpoints; -/// # use hyper::Request; -/// # async fn example() -> hyper::Response> { -/// let req = Request::builder().body(hyper::body::Incoming::empty()).unwrap(); -/// endpoints::handle_health_check(req).await -/// # } -/// ``` -pub async fn handle_health_check(_req: Request) -> Result>> { +/// Handle GET /health +pub async fn handle_health_check(_req: Request) -> Result>> +where + B: Body, +{ info!("GET /health - Health check"); let health = serde_json::json!({ @@ -178,12 +111,13 @@ pub async fn handle_health_check(_req: Request) -> Result> = Request::builder().body(Empty::new()).unwrap(); let response = handle_health_check(req).await.unwrap(); assert_eq!(response.status(), 200); @@ -192,10 +126,10 @@ mod tests { #[tokio::test] async fn test_handle_get_config() { let config = Arc::new(tokio::sync::RwLock::new(Config { - sites: std::collections::HashMap::new(), + sites: HashMap::new(), })); - let req = Request::builder().body(Incoming::empty()).unwrap(); + let req: Request> = Request::builder().body(Empty::new()).unwrap(); let response = handle_get_config(req, config).await.unwrap(); assert_eq!(response.status(), 200); @@ -204,10 +138,10 @@ mod tests { #[tokio::test] async fn test_handle_post_config() { let config = Arc::new(tokio::sync::RwLock::new(Config { - sites: std::collections::HashMap::new(), + sites: HashMap::new(), })); - let req = Request::builder().body(Incoming::empty()).unwrap(); + let req: Request> = Request::builder().body(Empty::new()).unwrap(); let response = handle_post_config(req, config).await.unwrap(); assert_eq!(response.status(), 200); diff --git a/src/api/middleware.rs b/src/api/middleware.rs index 1e1a345..7dd6abd 100644 --- a/src/api/middleware.rs +++ b/src/api/middleware.rs @@ -1,124 +1,49 @@ //! Middleware for API requests -//! -//! This module provides middleware functions for processing API requests, -//! including authentication and other request preprocessing. +use bytes::Bytes; +use http_body::Body; use http_body_util::Full; -use hyper::body::Incoming; use hyper::{Request, Response, StatusCode}; /// API authentication middleware -/// -/// Validates that the request contains a valid API key in the X-API-Key header. -/// If the key is missing or invalid, returns an error response. -/// -/// # Arguments -/// -/// * `req` - The incoming HTTP request -/// * `api_key` - The expected API key to validate against -/// -/// # Returns -/// -/// * `Ok(req)` - Request is authenticated, returned for further processing -/// * `Err(response)` - Authentication failed, error response to return to client -/// -/// # Example -/// -/// ```no_run -/// # use hyper::Request; -/// # use tiny_proxy::api::middleware::auth_middleware; -/// # #[tokio::main] -/// # async fn example() -> Result<(), Box> { -/// # let req = Request::builder() -/// # .header("X-API-Key", "secret-key-123") -/// # .body(hyper::body::Incoming::empty()) -/// # .unwrap(); -/// let api_key = "secret-key-123"; -/// match auth_middleware(req, api_key).await { -/// Ok(authenticated_req) => { -/// // Process authenticated request -/// } -/// Err(response) => { -/// // Return authentication error to client -/// } -/// } -/// # Ok(()) -/// # } -/// ``` -pub async fn auth_middleware( - req: Request, +pub async fn auth_middleware( + req: Request, api_key: &str, -) -> Result, Response>> { - // Extract API key from X-API-Key header - let provided_key = req.headers().get("X-API-Key").and_then(|h| h.to_str().ok()); +) -> Result, Response>> +where + B: Body, +{ + let provided_key = req + .headers() + .get("X-API-Key") + .and_then(|h: &hyper::header::HeaderValue| h.to_str().ok()); - // Validate API key match provided_key { - Some(key) if key == api_key => { - tracing::debug!("API authentication successful"); - Ok(req) - } - Some(_) => { - tracing::warn!("API authentication failed: invalid API key"); - Err(unauthorized_response("Invalid API key")) - } - None => { - tracing::warn!("API authentication failed: missing API key"); - Err(unauthorized_response("Missing API key")) - } + Some(key) if key == api_key => Ok(req), + Some(_) => Err(unauthorized_response("Invalid API key")), + None => Err(unauthorized_response("Missing API key")), } } -/// Create an unauthorized error response -/// -/// # Arguments -/// -/// * `message` - Error message to include in response -/// -/// # Returns -/// -/// HTTP 401 Unauthorized response with error details -fn unauthorized_response(message: &str) -> Response> { +fn unauthorized_response(message: &str) -> Response> { let body = format!(r#"{{"error": "Unauthorized", "message": "{}"}}"#, message); Response::builder() .status(StatusCode::UNAUTHORIZED) .header("Content-Type", "application/json") - .body(Full::new(bytes::Bytes::from(body))) + .body(Full::new(Bytes::from(body))) .unwrap() } -/// Logging middleware for API requests -/// -/// Logs basic information about incoming API requests including method, -/// path, and client IP address. -/// -/// # Arguments -/// -/// * `req` - The incoming HTTP request -/// -/// # Returns -/// -/// The request unchanged, ready for further processing -/// -/// # Example -/// -/// ```no_run -/// # use hyper::Request; -/// # use tiny_proxy::api::middleware::logging_middleware; -/// # fn example(req: Request) { -/// let req = logging_middleware(req); -/// // Request has been logged, continue processing -/// # } -/// ``` -pub fn logging_middleware(req: Request) -> Request { +/// Logging middleware +pub fn logging_middleware(req: Request) -> Request { let method = req.method(); let path = req.uri().path(); let client_ip = req .headers() .get("X-Real-IP") .or_else(|| req.headers().get("X-Forwarded-For")) - .and_then(|h| h.to_str().ok()) + .and_then(|h: &hyper::header::HeaderValue| h.to_str().ok()) .unwrap_or("unknown"); tracing::info!("API request from {}: {} {}", client_ip, method, path); @@ -129,54 +54,54 @@ pub fn logging_middleware(req: Request) -> Request { #[cfg(test)] mod tests { use super::*; - use hyper::body::Incoming; + use http_body_util::Empty; + use hyper::Request; - #[test] - fn test_auth_middleware_valid_key() { - let req = Request::builder() + #[tokio::test] + async fn test_auth_middleware_valid_key() { + let req: Request> = Request::builder() .header("X-API-Key", "secret-key-123") - .body(Incoming::empty()) + .body(Empty::new()) .unwrap(); let api_key = "secret-key-123"; - // Note: This test demonstrates the function signature - // In a real test, you'd use a tokio::test and await the result + let result = auth_middleware(req, api_key).await; + assert!(result.is_ok()); } - #[test] - fn test_auth_middleware_invalid_key() { - let req = Request::builder() + #[tokio::test] + async fn test_auth_middleware_invalid_key() { + let req: Request> = Request::builder() .header("X-API-Key", "wrong-key") - .body(Incoming::empty()) + .body(Empty::new()) .unwrap(); let api_key = "secret-key-123"; - // Note: This test demonstrates the function signature - // In a real test, you'd use a tokio::test and await the result + let result = auth_middleware(req, api_key).await; + assert!(result.is_err()); } - #[test] - fn test_auth_middleware_missing_key() { - let req = Request::builder().body(Incoming::empty()).unwrap(); + #[tokio::test] + async fn test_auth_middleware_missing_key() { + let req: Request> = Request::builder().body(Empty::new()).unwrap(); let api_key = "secret-key-123"; - // Note: This test demonstrates the function signature - // In a real test, you'd use a tokio::test and await the result + let result = auth_middleware(req, api_key).await; + assert!(result.is_err()); } - #[test] - fn test_logging_middleware() { - let req = Request::builder() + #[tokio::test] + async fn test_logging_middleware() { + let req: Request> = Request::builder() .header("X-Real-IP", "192.168.1.1") - .body(Incoming::empty()) + .body(Empty::new()) .unwrap(); let _logged_req = logging_middleware(req); - // Request should be logged } - #[test] - fn test_unauthorized_response() { + #[tokio::test] + async fn test_unauthorized_response() { let response = unauthorized_response("Test error"); assert_eq!(response.status(), StatusCode::UNAUTHORIZED); diff --git a/src/api/mod.rs b/src/api/mod.rs index 3c5303c..5ad4e58 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -14,10 +14,9 @@ //! # #[tokio::main] //! # async fn main() -> anyhow::Result<()> { //! let config = Arc::new(RwLock::new(Config::from_file("config.caddy")?)); -//! let addr = "127.0.0.1:8081".parse()?; //! //! // Start the management API server -//! api::start_api_server(addr, config).await?; +//! api::start_api_server("127.0.0.1:8081", config).await?; //! # Ok(()) //! # } //! ``` diff --git a/src/config/parser.rs b/src/config/parser.rs index 5462166..577622b 100644 --- a/src/config/parser.rs +++ b/src/config/parser.rs @@ -1,6 +1,7 @@ use crate::config::{Config, Directive, SiteConfig}; use crate::error::ProxyError; use std::collections::HashMap; +use std::str::FromStr; #[derive(Debug)] struct PendingBlock { @@ -11,10 +12,14 @@ struct PendingBlock { impl Config { pub fn from_file(path: &str) -> Result { let content = std::fs::read_to_string(path)?; - Self::from_str(&content) + content.parse() } +} + +impl FromStr for Config { + type Err = ProxyError; - pub fn from_str(content: &str) -> Result { + fn from_str(content: &str) -> Result { let mut sites = HashMap::new(); let mut current_site_address: Option = None; @@ -64,7 +69,7 @@ impl Config { let completed_directive = match block_info.directive_type.as_str() { "handle_path" => { - let pattern = block_info.args.get(0).cloned().unwrap_or_default(); + let pattern = block_info.args.first().cloned().unwrap_or_default(); Directive::HandlePath { pattern, directives: finished_directives, @@ -115,13 +120,13 @@ impl Config { let directive = match directive_name { "reverse_proxy" => { - let to = args.get(0).cloned().ok_or_else(|| { + let to = args.first().cloned().ok_or_else(|| { ProxyError::Parse("Missing backend URL for reverse_proxy".to_string()) })?; Directive::ReverseProxy { to: to.to_string() } } "uri_replace" => { - let find = args.get(0).cloned().ok_or_else(|| { + let find = args.first().cloned().ok_or_else(|| { ProxyError::Parse("Missing 'find' arg for uri_replace".to_string()) })?; let replace = args.get(1).cloned().ok_or_else(|| { @@ -133,7 +138,7 @@ impl Config { } } "header" => { - let name = args.get(0).cloned().ok_or_else(|| { + let name = args.first().cloned().ok_or_else(|| { ProxyError::Parse("Missing 'name' arg for header".to_string()) })?; let value = args.get(1).cloned().ok_or_else(|| { @@ -145,7 +150,7 @@ impl Config { } } "respond" => { - let status = args.get(0).and_then(|s| s.parse().ok()).ok_or_else(|| { + let status = args.first().and_then(|s| s.parse().ok()).ok_or_else(|| { ProxyError::Parse("Invalid status for respond".to_string()) })?; let body = args.get(1).cloned().unwrap_or_default(); diff --git a/src/main.rs b/src/main.rs index 92970ce..59a01a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,12 @@ use tracing_subscriber::EnvFilter; use tiny_proxy::cli::Cli; use tiny_proxy::config::Config; +#[cfg(feature = "cli")] +use std::sync::Arc; + +#[cfg(feature = "cli")] +use tokio::sync::{broadcast, RwLock}; + #[cfg(feature = "api")] use tiny_proxy::start_api_server; use tiny_proxy::Proxy; @@ -57,7 +63,7 @@ async fn run_proxy_only(cli: Cli, config: Config) -> Result<(), anyhow::Error> { result = proxy.start(&cli.addr) => { if let Err(e) = result { error!("Proxy server error: {}", e); - Err(e.into()) + Err(e) } else { Ok(()) } @@ -189,7 +195,7 @@ async fn run_proxy_server( // Run proxy server tokio::select! { result = proxy.start(&addr) => { - result.map_err(|e| e.into()) + result }, _ = shutdown_rx.recv() => { info!("Proxy server received shutdown signal"); diff --git a/src/proxy/handler.rs b/src/proxy/handler.rs index 54da554..e358361 100644 --- a/src/proxy/handler.rs +++ b/src/proxy/handler.rs @@ -161,7 +161,7 @@ pub async fn proxy( let boxed: ResponseBody = Full::new(Bytes::from(body)) .map_err(|e| Box::new(e) as Box) .boxed(); - return Ok(Response::builder().status(status_code).body(boxed).unwrap()); + Ok(Response::builder().status(status_code).body(boxed).unwrap()) } ActionResult::ReverseProxy { backend_url, @@ -337,8 +337,7 @@ fn error_response(status: StatusCode, message: &str) -> Response { /// Match path against pattern (supports wildcard *) /// Returns Some(remaining_path) if match, None otherwise pub fn match_pattern(pattern: &str, path: &str) -> Option { - if pattern.ends_with("/*") { - let prefix = &pattern[..pattern.len() - 2]; + if let Some(prefix) = pattern.strip_suffix("/*") { if path.starts_with(prefix) { // Remove prefix and return remaining path let remaining = path.strip_prefix(prefix).unwrap_or(path); @@ -346,11 +345,9 @@ pub fn match_pattern(pattern: &str, path: &str) -> Option { } else { None } + } else if pattern == path { + Some("/".to_string()) // Exact match, send root } else { - if pattern == path { - Some("/".to_string()) // Exact match, send root - } else { - None - } + None } } diff --git a/src/proxy/mod.rs b/src/proxy/mod.rs index a4cff02..c0433c8 100644 --- a/src/proxy/mod.rs +++ b/src/proxy/mod.rs @@ -1,7 +1,8 @@ mod directives; pub mod handler; +#[allow(clippy::module_inception)] mod proxy; mod types; pub use proxy::Proxy; -pub use types::ActionResult; \ No newline at end of file +pub use types::ActionResult;