Spaces:
Runtime error
Runtime error
Upload folder using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .dockerignore +21 -0
- .gitattributes +2 -0
- .github/dependabot.yml +11 -0
- .github/workflows/release.yml +237 -0
- .github/workflows/rust.yml +37 -0
- .gitignore +21 -0
- .rusty-hook.toml +5 -0
- CODE_OF_CONDUCT.md +5 -0
- CONTRIBUTING.md +3 -0
- Cargo.lock +0 -0
- Cargo.toml +18 -0
- Dockerfile +40 -0
- LICENSE +21 -0
- README.md +64 -10
- SECURITY.md +40 -0
- ferron-docker.yaml +4 -0
- ferron-passwd/Cargo.toml +20 -0
- ferron-passwd/assets/icon.ico +3 -0
- ferron-passwd/build.rs +13 -0
- ferron-passwd/src/main.rs +65 -0
- ferron-release.yaml +2 -0
- ferron.yaml +11 -0
- ferron/Cargo.toml +80 -0
- ferron/assets/icon.ico +3 -0
- ferron/build.rs +13 -0
- ferron/src/common/log.rs +32 -0
- ferron/src/common/mod.rs +593 -0
- ferron/src/common/with_runtime.rs +56 -0
- ferron/src/main.rs +478 -0
- ferron/src/modules/blocklist.rs +135 -0
- ferron/src/modules/default_handler_checks.rs +134 -0
- ferron/src/modules/non_standard_codes.rs +537 -0
- ferron/src/modules/redirect_trailing_slashes.rs +235 -0
- ferron/src/modules/redirects.rs +245 -0
- ferron/src/modules/static_file_serving.rs +928 -0
- ferron/src/modules/url_rewrite.rs +350 -0
- ferron/src/modules/x_forwarded_for.rs +144 -0
- ferron/src/optional_modules/asgi.rs +1476 -0
- ferron/src/optional_modules/cache.rs +525 -0
- ferron/src/optional_modules/cgi.rs +859 -0
- ferron/src/optional_modules/example.rs +143 -0
- ferron/src/optional_modules/fauth.rs +572 -0
- ferron/src/optional_modules/fcgi.rs +964 -0
- ferron/src/optional_modules/fproxy.rs +301 -0
- ferron/src/optional_modules/rproxy.rs +803 -0
- ferron/src/optional_modules/scgi.rs +673 -0
- ferron/src/optional_modules/wsgi.rs +742 -0
- ferron/src/optional_modules/wsgid.rs +1035 -0
- ferron/src/request_handler.rs +2211 -0
- ferron/src/res/server_software.rs +1 -0
.dockerignore
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Compiled binaries and generated documentation
|
| 2 |
+
/target/
|
| 3 |
+
|
| 4 |
+
# Profiling data
|
| 5 |
+
/perf.data
|
| 6 |
+
/perf.data.old
|
| 7 |
+
/flamegraph.svg
|
| 8 |
+
|
| 9 |
+
# Logs generated by the web server
|
| 10 |
+
/access.log
|
| 11 |
+
/error.log
|
| 12 |
+
|
| 13 |
+
# OS-specific files
|
| 14 |
+
.DS_Store
|
| 15 |
+
Thumbs.db
|
| 16 |
+
.Spotlight-V100
|
| 17 |
+
.Trashes
|
| 18 |
+
|
| 19 |
+
# Temporary files used by the editor
|
| 20 |
+
*.swp
|
| 21 |
+
*.swo
|
.gitattributes
CHANGED
|
@@ -33,3 +33,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 33 |
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
+
ferron/assets/icon.ico filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
ferron-passwd/assets/icon.ico filter=lfs diff=lfs merge=lfs -text
|
.github/dependabot.yml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# To get started with Dependabot version updates, you'll need to specify which
|
| 2 |
+
# package ecosystems to update and where the package manifests are located.
|
| 3 |
+
# Please see the documentation for all configuration options:
|
| 4 |
+
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
| 5 |
+
|
| 6 |
+
version: 2
|
| 7 |
+
updates:
|
| 8 |
+
- package-ecosystem: "cargo" # See documentation for possible values
|
| 9 |
+
directory: "/" # Location of package manifests
|
| 10 |
+
schedule:
|
| 11 |
+
interval: "weekly"
|
.github/workflows/release.yml
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Release
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ "main" ]
|
| 6 |
+
|
| 7 |
+
jobs:
|
| 8 |
+
release:
|
| 9 |
+
# Set the job to run on the platform specified by the matrix below
|
| 10 |
+
runs-on: ${{ matrix.runner }}
|
| 11 |
+
|
| 12 |
+
# Define the build matrix for cross-compilation
|
| 13 |
+
strategy:
|
| 14 |
+
matrix:
|
| 15 |
+
include:
|
| 16 |
+
- name: x86_64-unknown-linux-gnu
|
| 17 |
+
runner: ubuntu-latest
|
| 18 |
+
target: x86_64-unknown-linux-gnu
|
| 19 |
+
command: cross
|
| 20 |
+
- name: x86_64-unknown-linux-musl
|
| 21 |
+
runner: ubuntu-latest
|
| 22 |
+
target: x86_64-unknown-linux-musl
|
| 23 |
+
command: cross
|
| 24 |
+
- name: i686-unknown-linux-gnu
|
| 25 |
+
runner: ubuntu-latest
|
| 26 |
+
target: i686-unknown-linux-gnu
|
| 27 |
+
command: cross
|
| 28 |
+
- name: aarch64-unknown-linux-gnu
|
| 29 |
+
runner: ubuntu-latest
|
| 30 |
+
target: aarch64-unknown-linux-gnu
|
| 31 |
+
command: cross
|
| 32 |
+
- name: armv7-unknown-linux-gnueabihf
|
| 33 |
+
runner: ubuntu-latest
|
| 34 |
+
target: armv7-unknown-linux-gnueabihf
|
| 35 |
+
command: cross
|
| 36 |
+
- name: riscv64gc-unknown-linux-gnu
|
| 37 |
+
runner: ubuntu-latest
|
| 38 |
+
target: riscv64gc-unknown-linux-gnu
|
| 39 |
+
command: cross
|
| 40 |
+
- name: x86_64-unknown-freebsd
|
| 41 |
+
runner: ubuntu-latest
|
| 42 |
+
target: x86_64-unknown-freebsd
|
| 43 |
+
command: cross
|
| 44 |
+
- name: x86_64-pc-windows-msvc
|
| 45 |
+
runner: windows-latest
|
| 46 |
+
target: x86_64-pc-windows-msvc
|
| 47 |
+
command: cargo
|
| 48 |
+
- name: i686-pc-windows-msvc
|
| 49 |
+
runner: windows-latest
|
| 50 |
+
target: i686-pc-windows-msvc
|
| 51 |
+
command: cargo
|
| 52 |
+
- name: aarch64-pc-windows-msvc
|
| 53 |
+
runner: windows-latest
|
| 54 |
+
target: aarch64-pc-windows-msvc
|
| 55 |
+
command: cargo
|
| 56 |
+
- name: x86_64-apple-darwin
|
| 57 |
+
runner: macos-latest
|
| 58 |
+
target: x86_64-apple-darwin
|
| 59 |
+
command: cargo
|
| 60 |
+
- name: aarch64-apple-darwin
|
| 61 |
+
runner: macos-latest
|
| 62 |
+
target: aarch64-apple-darwin
|
| 63 |
+
command: cargo
|
| 64 |
+
|
| 65 |
+
# The steps to run for each matrix item
|
| 66 |
+
steps:
|
| 67 |
+
- name: Checkout
|
| 68 |
+
uses: actions/checkout@v3
|
| 69 |
+
with:
|
| 70 |
+
ref: main
|
| 71 |
+
fetch-depth: 0
|
| 72 |
+
|
| 73 |
+
- name: Install Rust
|
| 74 |
+
uses: dtolnay/rust-toolchain@stable
|
| 75 |
+
with:
|
| 76 |
+
targets: "${{ matrix.target }}"
|
| 77 |
+
|
| 78 |
+
- name: Setup cache
|
| 79 |
+
uses: Swatinem/rust-cache@v2
|
| 80 |
+
|
| 81 |
+
- name: Determine Ferron version
|
| 82 |
+
shell: bash
|
| 83 |
+
run: |
|
| 84 |
+
FERRON_VERSION_CARGO="$(cat ferron/Cargo.toml | grep -E '^version' | sed -E 's|.*"([0-9a-zA-Z.+-]+)"$|\1|g')"
|
| 85 |
+
FERRON_VERSION_GIT="$(git tag --sort=-committerdate | head -n 1 | sed s/[^0-9a-zA-Z.+-]//g)"
|
| 86 |
+
if [ "$FERRON_VERSION_CARGO" != "" ]; then
|
| 87 |
+
echo "Version determined from Cargo.toml file"
|
| 88 |
+
echo "FERRON_VERSION=$FERRON_VERSION_CARGO" >> $GITHUB_ENV
|
| 89 |
+
elif [ "$FERRON_VERSION_GIT" != "" ]; then
|
| 90 |
+
echo "Version determined from the Git tag"
|
| 91 |
+
echo "FERRON_VERSION=$FERRON_VERSION_GIT" >> $GITHUB_ENV
|
| 92 |
+
else
|
| 93 |
+
echo "Can't determine the server version!" 2>&1
|
| 94 |
+
exit 1
|
| 95 |
+
fi
|
| 96 |
+
|
| 97 |
+
- name: Install Cross
|
| 98 |
+
if: matrix.command == 'cross'
|
| 99 |
+
shell: bash
|
| 100 |
+
run: |
|
| 101 |
+
curl -L --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/cargo-bins/cargo-binstall/main/install-from-binstall-release.sh | bash
|
| 102 |
+
cargo binstall --no-confirm cross
|
| 103 |
+
|
| 104 |
+
- name: Build binaries
|
| 105 |
+
run: "${{ matrix.command }} build --verbose --locked --release --target ${{ matrix.target }}"
|
| 106 |
+
|
| 107 |
+
- name: Prepare for packaging
|
| 108 |
+
shell: bash
|
| 109 |
+
run: |
|
| 110 |
+
mkdir release
|
| 111 |
+
find target/${{ matrix.target }}/release -mindepth 1 -maxdepth 1 -type f ! -name "*.*" -o -name "*.exe" -o -name "*.dll" -o -name "*.dylib" -o -name "*.so" | sed -E "s|(.*)|cp -a \1 release|" | bash
|
| 112 |
+
cp -a ferron-release.yaml release/ferron.yaml
|
| 113 |
+
cp -a wwwroot release
|
| 114 |
+
|
| 115 |
+
- name: Create a release ZIP archive
|
| 116 |
+
uses: thedoctor0/zip-release@0.7.5
|
| 117 |
+
with:
|
| 118 |
+
type: "zip"
|
| 119 |
+
filename: "../ferron.zip"
|
| 120 |
+
directory: "release"
|
| 121 |
+
|
| 122 |
+
- name: Set up SSH
|
| 123 |
+
uses: LuisEnMarroquin/setup-ssh-action@v2.0.5
|
| 124 |
+
with:
|
| 125 |
+
ORIGIN: ${{ secrets.SSH_HOSTNAME }}
|
| 126 |
+
SSHKEY: ${{ secrets.SSH_KEY }}
|
| 127 |
+
NAME: ferron-servers
|
| 128 |
+
PORT: ${{ secrets.SSH_PORT }}
|
| 129 |
+
USER: ${{ secrets.SSH_USERNAME }}
|
| 130 |
+
|
| 131 |
+
- name: Release Ferron on Ferron's servers
|
| 132 |
+
shell: bash
|
| 133 |
+
run: |
|
| 134 |
+
ssh ferron-servers "mkdir -p ferron/${{ env.FERRON_VERSION }} || true"
|
| 135 |
+
scp ferron.zip ferron-servers:ferron/${{ env.FERRON_VERSION }}/ferron-${{ env.FERRON_VERSION }}-${{ matrix.target }}.zip
|
| 136 |
+
|
| 137 |
+
# The "move-ferron-archive" is a custom command that moves the ZIP archive to be served by the download server
|
| 138 |
+
ssh ferron-servers "sudo move-ferron-archive ${{ env.FERRON_VERSION }} ${{ matrix.target }}"
|
| 139 |
+
|
| 140 |
+
docs:
|
| 141 |
+
runs-on: ubuntu-latest
|
| 142 |
+
|
| 143 |
+
steps:
|
| 144 |
+
- name: Checkout
|
| 145 |
+
uses: actions/checkout@v3
|
| 146 |
+
with:
|
| 147 |
+
ref: main
|
| 148 |
+
|
| 149 |
+
- name: Install Rust
|
| 150 |
+
uses: dtolnay/rust-toolchain@stable
|
| 151 |
+
|
| 152 |
+
- name: Setup cache
|
| 153 |
+
uses: Swatinem/rust-cache@v2
|
| 154 |
+
|
| 155 |
+
- name: Generate the Rust crate documentation
|
| 156 |
+
run: "cargo doc --verbose --locked --release"
|
| 157 |
+
|
| 158 |
+
- name: Create the documentation ZIP archive
|
| 159 |
+
uses: thedoctor0/zip-release@0.7.5
|
| 160 |
+
with:
|
| 161 |
+
type: "zip"
|
| 162 |
+
filename: "../../ferron-rustdocs.zip"
|
| 163 |
+
directory: "target/doc"
|
| 164 |
+
|
| 165 |
+
- name: Set up SSH
|
| 166 |
+
uses: LuisEnMarroquin/setup-ssh-action@v2.0.5
|
| 167 |
+
with:
|
| 168 |
+
ORIGIN: ${{ secrets.SSH_HOSTNAME }}
|
| 169 |
+
SSHKEY: ${{ secrets.SSH_KEY }}
|
| 170 |
+
NAME: ferron-servers
|
| 171 |
+
PORT: ${{ secrets.SSH_PORT }}
|
| 172 |
+
USER: ${{ secrets.SSH_USERNAME }}
|
| 173 |
+
|
| 174 |
+
- name: Deploy the documentation
|
| 175 |
+
shell: bash
|
| 176 |
+
run: |
|
| 177 |
+
scp ferron-rustdocs.zip ferron-servers:.
|
| 178 |
+
|
| 179 |
+
# The "deploy-ferron-rustdocs" is a custom command that deploys the Ferron's Rust crate documentation
|
| 180 |
+
ssh ferron-servers "sudo deploy-ferron-rustdocs ferron-rustdocs.zip && rm ferron-rustdocs.zip"
|
| 181 |
+
|
| 182 |
+
docker:
|
| 183 |
+
runs-on: ubuntu-latest
|
| 184 |
+
|
| 185 |
+
permissions:
|
| 186 |
+
packages: write
|
| 187 |
+
contents: read
|
| 188 |
+
attestations: write
|
| 189 |
+
id-token: write
|
| 190 |
+
|
| 191 |
+
steps:
|
| 192 |
+
- name: Checkout
|
| 193 |
+
uses: actions/checkout@v3
|
| 194 |
+
with:
|
| 195 |
+
ref: main
|
| 196 |
+
fetch-depth: 0
|
| 197 |
+
|
| 198 |
+
- name: Log in to Docker Hub
|
| 199 |
+
uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
|
| 200 |
+
with:
|
| 201 |
+
username: ${{ secrets.DOCKER_USERNAME }}
|
| 202 |
+
password: ${{ secrets.DOCKER_PASSWORD }}
|
| 203 |
+
|
| 204 |
+
- name: Determine Ferron version
|
| 205 |
+
shell: bash
|
| 206 |
+
run: |
|
| 207 |
+
FERRON_VERSION_CARGO="$(cat ferron/Cargo.toml | grep -E '^version' | sed -E 's|.*"([0-9a-zA-Z.+-]+)"$|\1|g')"
|
| 208 |
+
FERRON_VERSION_GIT="$(git tag --sort=-committerdate | head -n 1 | sed s/[^0-9a-zA-Z.+-]//g)"
|
| 209 |
+
if [ "$FERRON_VERSION_CARGO" != "" ]; then
|
| 210 |
+
echo "Version determined from Cargo.toml file"
|
| 211 |
+
echo "FERRON_VERSION=$FERRON_VERSION_CARGO" >> $GITHUB_ENV
|
| 212 |
+
elif [ "$FERRON_VERSION_GIT" != "" ]; then
|
| 213 |
+
echo "Version determined from the Git tag"
|
| 214 |
+
echo "FERRON_VERSION=$FERRON_VERSION_GIT" >> $GITHUB_ENV
|
| 215 |
+
else
|
| 216 |
+
echo "Can't determine the server version!" 2>&1
|
| 217 |
+
exit 1
|
| 218 |
+
fi
|
| 219 |
+
|
| 220 |
+
- name: Set up Docker Buildx
|
| 221 |
+
uses: docker/setup-buildx-action@v3
|
| 222 |
+
|
| 223 |
+
- name: Build and push Docker image
|
| 224 |
+
id: push
|
| 225 |
+
uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
|
| 226 |
+
with:
|
| 227 |
+
context: .
|
| 228 |
+
file: ./Dockerfile
|
| 229 |
+
push: true
|
| 230 |
+
tags: "ferronserver/ferron:${{ env.FERRON_VERSION }},ferronserver/ferron:latest"
|
| 231 |
+
|
| 232 |
+
- name: Generate artifact attestation
|
| 233 |
+
uses: actions/attest-build-provenance@v2
|
| 234 |
+
with:
|
| 235 |
+
subject-name: index.docker.io/ferronserver/ferron
|
| 236 |
+
subject-digest: ${{ steps.push.outputs.digest }}
|
| 237 |
+
push-to-registry: true
|
.github/workflows/rust.yml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Rust
|
| 2 |
+
|
| 3 |
+
on:
|
| 4 |
+
push:
|
| 5 |
+
branches: [ "develop" ]
|
| 6 |
+
pull_request:
|
| 7 |
+
branches: [ "develop" ]
|
| 8 |
+
|
| 9 |
+
env:
|
| 10 |
+
CARGO_TERM_COLOR: always
|
| 11 |
+
|
| 12 |
+
jobs:
|
| 13 |
+
build:
|
| 14 |
+
|
| 15 |
+
runs-on: ubuntu-latest
|
| 16 |
+
|
| 17 |
+
steps:
|
| 18 |
+
- uses: actions/checkout@v4
|
| 19 |
+
- uses: Swatinem/rust-cache@v2
|
| 20 |
+
- name: Build
|
| 21 |
+
run: cargo build --verbose
|
| 22 |
+
- name: Run tests
|
| 23 |
+
run: cargo test --workspace --verbose
|
| 24 |
+
|
| 25 |
+
check:
|
| 26 |
+
runs-on: ubuntu-latest
|
| 27 |
+
steps:
|
| 28 |
+
- uses: actions/checkout@v4
|
| 29 |
+
- uses: dtolnay/rust-toolchain@stable
|
| 30 |
+
with:
|
| 31 |
+
components: clippy, rustfmt
|
| 32 |
+
- uses: Swatinem/rust-cache@v2
|
| 33 |
+
- name: rustfmt
|
| 34 |
+
run: cargo fmt --all -- --check
|
| 35 |
+
- name: clippy
|
| 36 |
+
run: cargo clippy --workspace -- -D warnings
|
| 37 |
+
|
.gitignore
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Compiled binaries and generated documentation
|
| 2 |
+
/target/
|
| 3 |
+
|
| 4 |
+
# Profiling data
|
| 5 |
+
/perf.data
|
| 6 |
+
/perf.data.old
|
| 7 |
+
/flamegraph.svg
|
| 8 |
+
|
| 9 |
+
# Logs generated by the web server
|
| 10 |
+
/access.log
|
| 11 |
+
/error.log
|
| 12 |
+
|
| 13 |
+
# OS-specific files
|
| 14 |
+
.DS_Store
|
| 15 |
+
Thumbs.db
|
| 16 |
+
.Spotlight-V100
|
| 17 |
+
.Trashes
|
| 18 |
+
|
| 19 |
+
# Temporary files used by the editor
|
| 20 |
+
*.swp
|
| 21 |
+
*.swo
|
.rusty-hook.toml
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[hooks]
|
| 2 |
+
pre-commit = "cargo clippy --fix --all-targets --allow-staged && cargo fmt && git add -A"
|
| 3 |
+
|
| 4 |
+
[logging]
|
| 5 |
+
verbose = true
|
CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contributor Code of Conduct
|
| 2 |
+
|
| 3 |
+
This project adheres to No Code of Conduct. We are all adults. We accept anyone's contributions. Nothing else matters.
|
| 4 |
+
|
| 5 |
+
For more information please visit the [No Code of Conduct](https://nocodeofconduct.com) homepage.
|
CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Contribution guidelines
|
| 2 |
+
|
| 3 |
+
_See [contribution page on Ferron's website](https://www.ferronweb.org/contribute)_
|
Cargo.lock
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
Cargo.toml
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[workspace]
|
| 2 |
+
members = [
|
| 3 |
+
"ferron",
|
| 4 |
+
"ferron-passwd",
|
| 5 |
+
]
|
| 6 |
+
resolver = "2"
|
| 7 |
+
|
| 8 |
+
[workspace.dependencies]
|
| 9 |
+
yaml-rust2 = "0.10.0"
|
| 10 |
+
password-auth = { version = "1.0.0", features = ["argon2", "pbkdf2", "scrypt"] }
|
| 11 |
+
rusty-hook = "0.11.2"
|
| 12 |
+
mimalloc = "0.1.45"
|
| 13 |
+
|
| 14 |
+
[profile.release]
|
| 15 |
+
strip = true
|
| 16 |
+
lto = true
|
| 17 |
+
codegen-units = 1
|
| 18 |
+
panic = "abort"
|
Dockerfile
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Use the official Rust image as a build stage
|
| 2 |
+
FROM rust as builder
|
| 3 |
+
|
| 4 |
+
# Set the working directory
|
| 5 |
+
WORKDIR /usr/src/ferron
|
| 6 |
+
|
| 7 |
+
# Copy the source code
|
| 8 |
+
COPY . .
|
| 9 |
+
|
| 10 |
+
# Build the actual application
|
| 11 |
+
RUN cargo build --release
|
| 12 |
+
|
| 13 |
+
# Use a Devuan base image for the final image
|
| 14 |
+
FROM devuan/devuan
|
| 15 |
+
|
| 16 |
+
# Copy the compiled binaries from the builder stage
|
| 17 |
+
COPY --from=builder /usr/src/ferron/target/release/ferron /usr/sbin/ferron
|
| 18 |
+
COPY --from=builder /usr/src/ferron/target/release/ferron-passwd /usr/sbin/ferron-passwd
|
| 19 |
+
|
| 20 |
+
# Copy the web server configuration
|
| 21 |
+
COPY ferron-docker.yaml /etc/ferron.yaml
|
| 22 |
+
|
| 23 |
+
# Copy the web root contents
|
| 24 |
+
RUN mkdir -p /var/www/ferron
|
| 25 |
+
COPY wwwroot/* /var/www/ferron
|
| 26 |
+
|
| 27 |
+
# Create a directory where Ferron logs are stored
|
| 28 |
+
RUN mkdir -p /var/log/ferron
|
| 29 |
+
|
| 30 |
+
# Create a "ferron" user and grant the permissions for the log directory and the webroot to that user
|
| 31 |
+
RUN useradd -d /nonexistent -s /usr/sbin/nologin -r ferron && chown -hR ferron:ferron /var/www/ferron && chown -hR ferron:ferron /var/log/ferron
|
| 32 |
+
|
| 33 |
+
# Expose the port 80 (used for HTTP)
|
| 34 |
+
EXPOSE 80
|
| 35 |
+
|
| 36 |
+
# Switch to "ferron" user
|
| 37 |
+
USER ferron
|
| 38 |
+
|
| 39 |
+
# Set the command to run the binary
|
| 40 |
+
CMD ["/usr/sbin/ferron", "-c", "/etc/ferron.yaml"]
|
LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
MIT License
|
| 2 |
+
|
| 3 |
+
Copyright (c) 2025 Ferron
|
| 4 |
+
|
| 5 |
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
| 6 |
+
of this software and associated documentation files (the "Software"), to deal
|
| 7 |
+
in the Software without restriction, including without limitation the rights
|
| 8 |
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
| 9 |
+
copies of the Software, and to permit persons to whom the Software is
|
| 10 |
+
furnished to do so, subject to the following conditions:
|
| 11 |
+
|
| 12 |
+
The above copyright notice and this permission notice shall be included in all
|
| 13 |
+
copies or substantial portions of the Software.
|
| 14 |
+
|
| 15 |
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
| 16 |
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
| 17 |
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
| 18 |
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
| 19 |
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
| 20 |
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
| 21 |
+
SOFTWARE.
|
README.md
CHANGED
|
@@ -1,10 +1,64 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<p align="center">
|
| 2 |
+
<a href="https://www.ferronweb.org" target="_blank">
|
| 3 |
+
<picture>
|
| 4 |
+
<source media="(prefers-color-scheme: dark)" srcset="logo-dark.png">
|
| 5 |
+
<img alt="Ferron logo" src="logo.png" width="256">
|
| 6 |
+
</picture>
|
| 7 |
+
</a>
|
| 8 |
+
</p>
|
| 9 |
+
<p align="center">
|
| 10 |
+
<b>Ferron</b> - a fast, memory-safe web server written in Rust
|
| 11 |
+
</p>
|
| 12 |
+
<p align="center">
|
| 13 |
+
<a href="https://www.ferronweb.org/docs" target="_blank"><img alt="Static Badge" src="https://img.shields.io/badge/Documentation-orange"></a>
|
| 14 |
+
<a href="https://www.ferronweb.org" target="_blank"><img alt="Website" src="https://img.shields.io/website?url=https%3A%2F%2Fwww.ferronweb.org"></a>
|
| 15 |
+
<a href="https://x.com/ferron_web" target="_blank"><img alt="X (formerly Twitter) Follow" src="https://img.shields.io/twitter/follow/ferron_web"></a>
|
| 16 |
+
<a href="https://hub.docker.com/r/ferronserver/ferron" target="_blank"><img alt="Docker Pulls" src="https://img.shields.io/docker/pulls/ferronserver/ferron"></a>
|
| 17 |
+
<a href="https://github.com/ferronweb/ferron" target="_blank"><img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/ferronweb/ferron"></a>
|
| 18 |
+
</p>
|
| 19 |
+
|
| 20 |
+
* * *
|
| 21 |
+
|
| 22 |
+
## Features
|
| 23 |
+
|
| 24 |
+
- **High performance** - built with Rust’s async capabilities for optimal speed.
|
| 25 |
+
- **Memory-safe** - built with Rust, which is a programming language offering memory safety.
|
| 26 |
+
- **Extensibility** - modular architecture for easy customization.
|
| 27 |
+
- **Secure** - focus on robust security practices and safe concurrency.
|
| 28 |
+
|
| 29 |
+
## Components
|
| 30 |
+
|
| 31 |
+
Ferron consists of multiple components:
|
| 32 |
+
|
| 33 |
+
- **`ferron`**: The main web server.
|
| 34 |
+
- **`ferron-passwd`**: A tool for generating user entries with hashed passwords, which can be copied into the web server's configuration file.
|
| 35 |
+
|
| 36 |
+
## Building Ferron from source
|
| 37 |
+
|
| 38 |
+
You can clone the repository and explore the existing code:
|
| 39 |
+
|
| 40 |
+
```sh
|
| 41 |
+
git clone https://github.com/ferronweb/ferron.git
|
| 42 |
+
cd ferron
|
| 43 |
+
```
|
| 44 |
+
|
| 45 |
+
You can then build and run the web server using Cargo:
|
| 46 |
+
|
| 47 |
+
```sh
|
| 48 |
+
cargo build -r
|
| 49 |
+
cargo run -r --bin ferron
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
You can also use [Ferron Forge](https://github.com/ferronweb/ferron-forge) to build the web server. Ferron Forge outputs a ZIP archive that can be used by the Ferron installer.
|
| 53 |
+
|
| 54 |
+
## Server configuration
|
| 55 |
+
|
| 56 |
+
You can check the [Ferron documentation](https://www.ferronweb.org/docs/configuration) to see configuration properties used by Ferron.
|
| 57 |
+
|
| 58 |
+
## Contributing
|
| 59 |
+
|
| 60 |
+
See [Ferron contribution page](https://www.ferronweb.org/contribute) for details.
|
| 61 |
+
|
| 62 |
+
## License
|
| 63 |
+
|
| 64 |
+
Ferron is licensed under the MIT License. See `LICENSE` for details.
|
SECURITY.md
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Ferron Security Policy
|
| 2 |
+
|
| 3 |
+
## Overview
|
| 4 |
+
Ferron is a fast, memory-safe web server written in Rust, designed for performance and security. This document outlines the security policies and procedures to ensure Ferron remains a secure and reliable software project.
|
| 5 |
+
|
| 6 |
+
## Supported versions
|
| 7 |
+
Ferron actively supports the latest stable release and provides security updates for the most recent minor versions. Users are encouraged to upgrade promptly to receive security patches.
|
| 8 |
+
|
| 9 |
+
## Reporting security issues
|
| 10 |
+
Security is a top priority for Ferron. If you discover a vulnerability, please report it responsibly by sending an email message to [security@ferronweb.org](mailto:security@ferronweb.org).
|
| 11 |
+
|
| 12 |
+
We strongly discourage public disclosure of vulnerabilities before a fix is released.
|
| 13 |
+
|
| 14 |
+
## Security best practices
|
| 15 |
+
To maintain security, we follow these principles:
|
| 16 |
+
|
| 17 |
+
- **Memory safety** - Ferron leverages Rust’s ownership model and borrow checker to eliminate memory-related vulnerabilities.
|
| 18 |
+
- **Minimal attack surface** - features are enabled only as needed, reducing exposure to potential threats.
|
| 19 |
+
- **Regular audits** - code is reviewed regularly, and dependencies are monitored for security vulnerabilities.
|
| 20 |
+
- **Safe defaults** - Ferron has some insecure configuration disabled by default, like exposing the server version or directory listings.
|
| 21 |
+
|
| 22 |
+
## Secure development process
|
| 23 |
+
Ferron follows industry best practices to maintain a secure development lifecycle:
|
| 24 |
+
|
| 25 |
+
1. **Code review** - all changes undergo peer review with security checks.
|
| 26 |
+
2. **Dependency management** - regularly check and update dependencies to patch known vulnerabilities.
|
| 27 |
+
3. **Responsible disclosure** - work with the security community to resolve issues before public disclosure.
|
| 28 |
+
|
| 29 |
+
## Handling security incidents
|
| 30 |
+
In the event of a security breach or vulnerability:
|
| 31 |
+
|
| 32 |
+
1. **Triage** - assess and prioritize the issue based on severity.
|
| 33 |
+
2. **Mitigation** - develop and test a fix.
|
| 34 |
+
3. **Advisory** - issue a security advisory with mitigation steps and fixed versions.
|
| 35 |
+
4. **Update users** - notify users via release notes and security mailing lists.
|
| 36 |
+
|
| 37 |
+
## Contact information
|
| 38 |
+
For any security concerns, contact us at [security@ferronweb.org](mailto:security@ferronweb.org). Stay updated on security patches via [our website](https://www.ferronweb.org).
|
| 39 |
+
|
| 40 |
+
By following this policy, we ensure Ferron remains a secure and trustworthy web server for all users.
|
ferron-docker.yaml
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
global:
|
| 2 |
+
wwwroot: /var/www/ferron
|
| 3 |
+
logFilePath: /var/log/ferron/access.log
|
| 4 |
+
errorLogFilePath: /var/log/ferron/error.log
|
ferron-passwd/Cargo.toml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[package]
|
| 2 |
+
name = "ferron-passwd"
|
| 3 |
+
version = "1.0.0"
|
| 4 |
+
edition = "2021"
|
| 5 |
+
|
| 6 |
+
[package.metadata.winresource]
|
| 7 |
+
ProductName = "Ferron password utility"
|
| 8 |
+
|
| 9 |
+
[dependencies]
|
| 10 |
+
clap = { version = "4.5.28", features = ["derive"] }
|
| 11 |
+
password-auth.workspace = true
|
| 12 |
+
rpassword = "7.4.0"
|
| 13 |
+
yaml-rust2.workspace = true
|
| 14 |
+
mimalloc = { workspace = true }
|
| 15 |
+
|
| 16 |
+
[dev-dependencies]
|
| 17 |
+
rusty-hook = { workspace = true }
|
| 18 |
+
|
| 19 |
+
[build-dependencies]
|
| 20 |
+
winresource = "0.1.19"
|
ferron-passwd/assets/icon.ico
ADDED
|
|
Git LFS Details
|
ferron-passwd/build.rs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use {
|
| 2 |
+
std::{env, io},
|
| 3 |
+
winresource::WindowsResource,
|
| 4 |
+
};
|
| 5 |
+
|
| 6 |
+
fn main() -> io::Result<()> {
|
| 7 |
+
if env::var_os("CARGO_CFG_WINDOWS").is_some() {
|
| 8 |
+
WindowsResource::new()
|
| 9 |
+
.set_icon("assets/icon.ico")
|
| 10 |
+
.compile()?;
|
| 11 |
+
}
|
| 12 |
+
Ok(())
|
| 13 |
+
}
|
ferron-passwd/src/main.rs
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use clap::Parser;
|
| 2 |
+
use mimalloc::MiMalloc;
|
| 3 |
+
use password_auth::generate_hash;
|
| 4 |
+
use rpassword::prompt_password;
|
| 5 |
+
use std::process;
|
| 6 |
+
use yaml_rust2::{yaml, Yaml, YamlEmitter};
|
| 7 |
+
|
| 8 |
+
#[global_allocator]
|
| 9 |
+
static GLOBAL: MiMalloc = MiMalloc;
|
| 10 |
+
|
| 11 |
+
/// A password tool for Ferron
|
| 12 |
+
#[derive(Parser, Debug)]
|
| 13 |
+
#[command(version, about, long_about = None)]
|
| 14 |
+
struct Args {
|
| 15 |
+
/// The username, for which you want to generate an user entry
|
| 16 |
+
#[arg()]
|
| 17 |
+
username: String,
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
fn main() {
|
| 21 |
+
let args = Args::parse();
|
| 22 |
+
|
| 23 |
+
let password = match prompt_password("Password: ") {
|
| 24 |
+
Ok(pass) => pass,
|
| 25 |
+
Err(e) => {
|
| 26 |
+
eprintln!("Error reading password: {}", e);
|
| 27 |
+
process::exit(1);
|
| 28 |
+
}
|
| 29 |
+
};
|
| 30 |
+
let password2 = match prompt_password("Confirm password: ") {
|
| 31 |
+
Ok(pass) => pass,
|
| 32 |
+
Err(e) => {
|
| 33 |
+
eprintln!("Error reading password confirmation: {}", e);
|
| 34 |
+
process::exit(1);
|
| 35 |
+
}
|
| 36 |
+
};
|
| 37 |
+
|
| 38 |
+
if password != password2 {
|
| 39 |
+
eprintln!("Passwords don't match!");
|
| 40 |
+
process::exit(1);
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
let password_hash = generate_hash(password);
|
| 44 |
+
|
| 45 |
+
let mut yaml_user_hashmap = yaml::Hash::new();
|
| 46 |
+
yaml_user_hashmap.insert(
|
| 47 |
+
Yaml::String("name".to_string()),
|
| 48 |
+
Yaml::String(args.username),
|
| 49 |
+
);
|
| 50 |
+
yaml_user_hashmap.insert(
|
| 51 |
+
Yaml::String("pass".to_string()),
|
| 52 |
+
Yaml::String(password_hash),
|
| 53 |
+
);
|
| 54 |
+
|
| 55 |
+
let yaml_data = Yaml::Array(vec![Yaml::Hash(yaml_user_hashmap)]);
|
| 56 |
+
|
| 57 |
+
let mut output = String::new();
|
| 58 |
+
if let Err(e) = YamlEmitter::new(&mut output).dump(&yaml_data) {
|
| 59 |
+
eprintln!("Error generating YAML output: {}", e);
|
| 60 |
+
process::exit(1);
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
println!("Copy the user object below into \"users\" property of either global configuration or a virtual host in the \"ferron.yaml\" file. Remember about the indentation in the server configuration.");
|
| 64 |
+
println!("{}", output);
|
| 65 |
+
}
|
ferron-release.yaml
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
global:
|
| 2 |
+
wwwroot: wwwroot
|
ferron.yaml
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Global server configuration
|
| 2 |
+
global:
|
| 3 |
+
port: 8080
|
| 4 |
+
logFilePath: access.log
|
| 5 |
+
errorLogFilePath: error.log
|
| 6 |
+
loadModules:
|
| 7 |
+
- cgi
|
| 8 |
+
wwwroot: wwwroot # Replace "wwwroot" with desired webroot
|
| 9 |
+
enableDirectoryListing: true
|
| 10 |
+
cgiScriptExtensions:
|
| 11 |
+
- .php # Necessary to be able to execute PHP scripts via PHP-CGI
|
ferron/Cargo.toml
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[package]
|
| 2 |
+
name = "ferron"
|
| 3 |
+
version = "1.0.0"
|
| 4 |
+
edition = "2021"
|
| 5 |
+
|
| 6 |
+
[package.metadata.winresource]
|
| 7 |
+
ProductName = "Ferron"
|
| 8 |
+
|
| 9 |
+
[dependencies]
|
| 10 |
+
hyper = { version = "1.6.0", features = ["full"] }
|
| 11 |
+
tokio = { version = "1.44.2", features = ["full"] }
|
| 12 |
+
http-body-util = "0.1.0"
|
| 13 |
+
hyper-util = { version = "0.1", features = ["full"] }
|
| 14 |
+
tokio-util = { version = "0.7.13", features = ["io"] }
|
| 15 |
+
rustls = { version = "0.23.24", default-features = false, features = ["tls12", "std", "ring"] }
|
| 16 |
+
rustls-acme = { version = "0.13.0", default-features = false, features = ["tls12", "ring"] }
|
| 17 |
+
tokio-rustls = { version = "0.26.1", default-features = false, features = ["tls12", "ring"] }
|
| 18 |
+
rustls-pki-types = "1.11.0"
|
| 19 |
+
rustls-pemfile = "2.2.0"
|
| 20 |
+
yaml-rust2 = { workspace = true }
|
| 21 |
+
anyhow = "1.0.98"
|
| 22 |
+
futures-util = "0.3.31"
|
| 23 |
+
chrono = "0.4.39"
|
| 24 |
+
async-trait = "0.1.86"
|
| 25 |
+
rustls-native-certs = "0.8.1"
|
| 26 |
+
ocsp-stapler = { version = "0.4.4", default-features = false }
|
| 27 |
+
clap = { version = "4.5.28", features = ["derive"] }
|
| 28 |
+
fancy-regex = "0.14.0"
|
| 29 |
+
password-auth = { workspace = true }
|
| 30 |
+
base64 = "0.22.1"
|
| 31 |
+
sha2 = "0.10.8"
|
| 32 |
+
new_mime_guess = "4.0.4"
|
| 33 |
+
async-compression = { version = "0.4.18", features = ["tokio", "gzip", "brotli", "deflate", "zstd"] }
|
| 34 |
+
urlencoding = "2.1.3"
|
| 35 |
+
async-channel = "2.3.1"
|
| 36 |
+
mimalloc = { workspace = true }
|
| 37 |
+
cache_control = { git = "https://github.com/DorianNiemiecSVRJS/rust-cache-control.git", optional = true } # Temporarily replaced with a fork
|
| 38 |
+
itertools = { version = "0.14.0", optional = true }
|
| 39 |
+
rand = "0.9.0"
|
| 40 |
+
memmem = { version = "0.1.1", optional = true }
|
| 41 |
+
httparse = { version = "1.10.0", optional = true }
|
| 42 |
+
pin-project-lite = "0.2.16"
|
| 43 |
+
hashlink = "0.10.0"
|
| 44 |
+
glob = "0.3.2"
|
| 45 |
+
hyper-tungstenite = "0.17.0"
|
| 46 |
+
tokio-tungstenite = { version = "0.26.2", features = ["rustls-tls-native-roots"] }
|
| 47 |
+
http = "1.2.0"
|
| 48 |
+
pyo3 = { version = "0.24.1", optional = true, features = ["anyhow", "auto-initialize"] }
|
| 49 |
+
futures-lite = "2.6.0"
|
| 50 |
+
nix = { version = "0.30.0", optional = true, features = ["process", "signal"] }
|
| 51 |
+
interprocess = { version = "2.2.3", features = ["tokio"], optional = true }
|
| 52 |
+
serde = { version = "1.0.219", optional = true, features = ["derive"] }
|
| 53 |
+
serde_bytes = { version = "0.11.17", optional = true }
|
| 54 |
+
postcard = { version = "1.1.1", optional = true, default-features = false, features = ["use-std"] }
|
| 55 |
+
bytes = { version = "1.10.1", optional = true }
|
| 56 |
+
pyo3-async-runtimes = { version = "0.24.0", optional = true, features = ["tokio", "tokio-runtime"] }
|
| 57 |
+
h3 = "0.0.7"
|
| 58 |
+
h3-quinn = "0.0.9"
|
| 59 |
+
quinn = "0.11.7"
|
| 60 |
+
|
| 61 |
+
[dev-dependencies]
|
| 62 |
+
tokio-test = "0.4.4"
|
| 63 |
+
rusty-hook = { workspace = true }
|
| 64 |
+
|
| 65 |
+
[features]
|
| 66 |
+
default = ["cache", "cgi", "fauth", "fcgi", "fproxy", "rproxy", "scgi"]
|
| 67 |
+
asgi = ["pyo3", "pyo3-async-runtimes"]
|
| 68 |
+
cache = ["cache_control", "itertools"]
|
| 69 |
+
cgi = ["httparse", "memmem"]
|
| 70 |
+
example = []
|
| 71 |
+
fauth = []
|
| 72 |
+
fcgi = ["httparse", "memmem"]
|
| 73 |
+
fproxy = []
|
| 74 |
+
rproxy = []
|
| 75 |
+
scgi = ["httparse", "memmem"]
|
| 76 |
+
wsgi = ["pyo3"]
|
| 77 |
+
wsgid = ["pyo3", "nix", "interprocess", "itertools", "serde", "serde_bytes", "postcard", "hashlink/serde", "hashlink/serde_impl", "bytes"]
|
| 78 |
+
|
| 79 |
+
[build-dependencies]
|
| 80 |
+
winresource = "0.1.19"
|
ferron/assets/icon.ico
ADDED
|
|
Git LFS Details
|
ferron/build.rs
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use {
|
| 2 |
+
std::{env, io},
|
| 3 |
+
winresource::WindowsResource,
|
| 4 |
+
};
|
| 5 |
+
|
| 6 |
+
fn main() -> io::Result<()> {
|
| 7 |
+
if env::var_os("CARGO_CFG_WINDOWS").is_some() {
|
| 8 |
+
WindowsResource::new()
|
| 9 |
+
.set_icon("assets/icon.ico")
|
| 10 |
+
.compile()?;
|
| 11 |
+
}
|
| 12 |
+
Ok(())
|
| 13 |
+
}
|
ferron/src/common/log.rs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/// Represents a log message with its content and error status.
|
| 2 |
+
pub struct LogMessage {
|
| 3 |
+
is_error: bool,
|
| 4 |
+
message: String,
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
impl LogMessage {
|
| 8 |
+
/// Creates a new `LogMessage` instance.
|
| 9 |
+
///
|
| 10 |
+
/// # Parameters
|
| 11 |
+
///
|
| 12 |
+
/// - `message`: The content of the log message.
|
| 13 |
+
/// - `is_error`: A boolean indicating whether the message is an error (`true`) or not (`false`).
|
| 14 |
+
///
|
| 15 |
+
/// # Returns
|
| 16 |
+
///
|
| 17 |
+
/// A `LogMessage` object containing the specified message and error status.
|
| 18 |
+
pub fn new(message: String, is_error: bool) -> Self {
|
| 19 |
+
Self { is_error, message }
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
/// Consumes the `LogMessage` and returns its components.
|
| 23 |
+
///
|
| 24 |
+
/// # Returns
|
| 25 |
+
///
|
| 26 |
+
/// A tuple containing:
|
| 27 |
+
/// - `String`: The content of the log message.
|
| 28 |
+
/// - `bool`: A boolean indicating whether the message is an error.
|
| 29 |
+
pub fn get_message(self) -> (String, bool) {
|
| 30 |
+
(self.message, self.is_error)
|
| 31 |
+
}
|
| 32 |
+
}
|
ferron/src/common/mod.rs
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#![allow(dead_code)]
|
| 2 |
+
|
| 3 |
+
use std::{error::Error, future::Future, net::SocketAddr, pin::Pin};
|
| 4 |
+
|
| 5 |
+
use async_channel::Sender;
|
| 6 |
+
use async_trait::async_trait;
|
| 7 |
+
use http_body_util::combinators::BoxBody;
|
| 8 |
+
use hyper::{body::Bytes, upgrade::Upgraded, HeaderMap, Request, Response, StatusCode, Uri};
|
| 9 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 10 |
+
use tokio::runtime::Handle;
|
| 11 |
+
use yaml_rust2::Yaml;
|
| 12 |
+
|
| 13 |
+
#[path = "log.rs"]
|
| 14 |
+
mod log;
|
| 15 |
+
#[path = "with_runtime.rs"]
|
| 16 |
+
mod with_runtime;
|
| 17 |
+
|
| 18 |
+
/// Contains information about a network socket, including remote and local addresses,
|
| 19 |
+
/// and whether the connection is encrypted.
|
| 20 |
+
pub struct SocketData {
|
| 21 |
+
/// The remote address of the socket.
|
| 22 |
+
pub remote_addr: SocketAddr,
|
| 23 |
+
/// The local address of the socket.
|
| 24 |
+
pub local_addr: SocketAddr,
|
| 25 |
+
/// Indicates if the connection is encrypted.
|
| 26 |
+
pub encrypted: bool,
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
impl SocketData {
|
| 30 |
+
/// Creates a new `SocketData` instance.
|
| 31 |
+
///
|
| 32 |
+
/// # Parameters
|
| 33 |
+
///
|
| 34 |
+
/// - `remote_addr`: The remote address of the socket.
|
| 35 |
+
/// - `local_addr`: The local address of the socket.
|
| 36 |
+
/// - `encrypted`: A boolean indicating if the connection is encrypted.
|
| 37 |
+
///
|
| 38 |
+
/// # Returns
|
| 39 |
+
///
|
| 40 |
+
/// A new `SocketData` instance with the provided parameters.
|
| 41 |
+
pub fn new(remote_addr: SocketAddr, local_addr: SocketAddr, encrypted: bool) -> Self {
|
| 42 |
+
Self {
|
| 43 |
+
remote_addr,
|
| 44 |
+
local_addr,
|
| 45 |
+
encrypted,
|
| 46 |
+
}
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
/// Represents a log message. This is a type alias for `crate::log::LogMessage`.
|
| 51 |
+
pub type LogMessage = log::LogMessage;
|
| 52 |
+
|
| 53 |
+
/// Represents the server configuration object. This is a type alias for `Yaml` from the `yaml_rust2` crate.
|
| 54 |
+
pub type ServerConfig = Yaml;
|
| 55 |
+
|
| 56 |
+
/// Represents the HTTP request from Hyper.
|
| 57 |
+
pub type HyperRequest = Request<BoxBody<Bytes, std::io::Error>>;
|
| 58 |
+
|
| 59 |
+
/// Represents the HTTP response from Hyper.
|
| 60 |
+
pub type HyperResponse = Response<BoxBody<Bytes, std::io::Error>>;
|
| 61 |
+
|
| 62 |
+
/// Represents the upgraded HTTP connection from Hyper.
|
| 63 |
+
pub type HyperUpgraded = Upgraded;
|
| 64 |
+
|
| 65 |
+
/// A wrapper that ensures a function is executed within a specific runtime context.
|
| 66 |
+
/// This is a type alias for `crate::with_runtime::WithRuntime<F>`.
|
| 67 |
+
pub type WithRuntime<F> = with_runtime::WithRuntime<F>;
|
| 68 |
+
|
| 69 |
+
/// Contains data related to an HTTP request, including the original Hyper request
|
| 70 |
+
/// and optional authentication user information.
|
| 71 |
+
pub struct RequestData {
|
| 72 |
+
hyper_request: HyperRequest,
|
| 73 |
+
auth_user: Option<String>,
|
| 74 |
+
original_url: Option<Uri>,
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
impl RequestData {
|
| 78 |
+
/// Creates a new `RequestData` instance.
|
| 79 |
+
///
|
| 80 |
+
/// # Parameters
|
| 81 |
+
///
|
| 82 |
+
/// - `hyper_request`: The original Hyper `Request` object.
|
| 83 |
+
/// - `auth_user`: An optional string representing the authenticated user.
|
| 84 |
+
///
|
| 85 |
+
/// # Returns
|
| 86 |
+
///
|
| 87 |
+
/// A new `RequestData` instance with the provided parameters.
|
| 88 |
+
pub fn new(
|
| 89 |
+
hyper_request: HyperRequest,
|
| 90 |
+
auth_user: Option<String>,
|
| 91 |
+
original_url: Option<Uri>,
|
| 92 |
+
) -> Self {
|
| 93 |
+
Self {
|
| 94 |
+
hyper_request,
|
| 95 |
+
auth_user,
|
| 96 |
+
original_url,
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
/// Sets the authenticated user for the request.
|
| 101 |
+
///
|
| 102 |
+
/// # Parameters
|
| 103 |
+
///
|
| 104 |
+
/// - `auth_user`: A string representing the authenticated user.
|
| 105 |
+
pub fn set_auth_user(&mut self, auth_user: String) {
|
| 106 |
+
self.auth_user = Some(auth_user);
|
| 107 |
+
}
|
| 108 |
+
|
| 109 |
+
/// Retrieves the authenticated user associated with the request, if any.
|
| 110 |
+
///
|
| 111 |
+
/// # Returns
|
| 112 |
+
///
|
| 113 |
+
/// An `Option` containing a reference to the authenticated user's string, or `None` if not set.
|
| 114 |
+
pub fn get_auth_user(&self) -> Option<&str> {
|
| 115 |
+
match &self.auth_user {
|
| 116 |
+
Some(auth_user) => Some(auth_user),
|
| 117 |
+
None => None,
|
| 118 |
+
}
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
/// Sets the original URL (before URL rewriting) for the request.
|
| 122 |
+
///
|
| 123 |
+
/// # Parameters
|
| 124 |
+
///
|
| 125 |
+
/// - `original_url`: An `Uri` object representing the original request URL before rewriting.
|
| 126 |
+
pub fn set_original_url(&mut self, original_url: Uri) {
|
| 127 |
+
self.original_url = Some(original_url);
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
/// Retrieves the original URL (before URL rewriting) associated with the request, if any.
|
| 131 |
+
///
|
| 132 |
+
/// # Returns
|
| 133 |
+
///
|
| 134 |
+
/// An `Option` containing a reference to the `Uri` object representing the original request URL before rewriting, or `None` if not set.
|
| 135 |
+
pub fn get_original_url(&self) -> Option<&Uri> {
|
| 136 |
+
match &self.original_url {
|
| 137 |
+
Some(original_url) => Some(original_url),
|
| 138 |
+
None => None,
|
| 139 |
+
}
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
/// Provides a reference to the underlying Hyper `Request` object.
|
| 143 |
+
///
|
| 144 |
+
/// # Returns
|
| 145 |
+
///
|
| 146 |
+
/// A reference to the `HyperRequest` object.
|
| 147 |
+
pub fn get_hyper_request(&self) -> &HyperRequest {
|
| 148 |
+
&self.hyper_request
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
/// Provides a mutable reference to the underlying Hyper `Request` object.
|
| 152 |
+
///
|
| 153 |
+
/// # Returns
|
| 154 |
+
///
|
| 155 |
+
/// A mutable reference to the `HyperRequest` object.
|
| 156 |
+
pub fn get_mut_hyper_request(&mut self) -> &mut HyperRequest {
|
| 157 |
+
&mut self.hyper_request
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
/// Consumes the `RequestData` instance and returns its components.
|
| 161 |
+
///
|
| 162 |
+
/// # Returns
|
| 163 |
+
///
|
| 164 |
+
/// A tuple containing the `HyperRequest` object, an optional authenticated user string, and an optional `Uri` object representing the original request URL before rewriting.
|
| 165 |
+
pub fn into_parts(self) -> (HyperRequest, Option<String>, Option<Uri>) {
|
| 166 |
+
(self.hyper_request, self.auth_user, self.original_url)
|
| 167 |
+
}
|
| 168 |
+
}
|
| 169 |
+
|
| 170 |
+
/// Facilitates logging of error messages through a provided logger sender.
|
| 171 |
+
pub struct ErrorLogger {
|
| 172 |
+
logger: Option<Sender<LogMessage>>,
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
impl ErrorLogger {
|
| 176 |
+
/// Creates a new `ErrorLogger` instance.
|
| 177 |
+
///
|
| 178 |
+
/// # Parameters
|
| 179 |
+
///
|
| 180 |
+
/// - `logger`: A `Sender<LogMessage>` used for sending log messages.
|
| 181 |
+
///
|
| 182 |
+
/// # Returns
|
| 183 |
+
///
|
| 184 |
+
/// A new `ErrorLogger` instance associated with the provided logger.
|
| 185 |
+
pub fn new(logger: Sender<LogMessage>) -> Self {
|
| 186 |
+
Self {
|
| 187 |
+
logger: Some(logger),
|
| 188 |
+
}
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
/// Creates a new `ErrorLogger` instance without any underlying logger.
|
| 192 |
+
///
|
| 193 |
+
/// # Returns
|
| 194 |
+
///
|
| 195 |
+
/// A new `ErrorLogger` instance not associated with any logger.
|
| 196 |
+
pub fn without_logger() -> Self {
|
| 197 |
+
Self { logger: None }
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
/// Logs an error message asynchronously.
|
| 201 |
+
///
|
| 202 |
+
/// # Parameters
|
| 203 |
+
///
|
| 204 |
+
/// - `message`: A string slice containing the error message to be logged.
|
| 205 |
+
///
|
| 206 |
+
/// # Examples
|
| 207 |
+
///
|
| 208 |
+
/// ```
|
| 209 |
+
/// # use crate::ferron_common::ErrorLogger;
|
| 210 |
+
/// # #[tokio::main]
|
| 211 |
+
/// # async fn main() {
|
| 212 |
+
/// let (tx, mut rx) = async_channel::bounded(100);
|
| 213 |
+
/// let logger = ErrorLogger::new(tx);
|
| 214 |
+
/// logger.log("An error occurred").await;
|
| 215 |
+
/// # }
|
| 216 |
+
/// ```
|
| 217 |
+
pub async fn log(&self, message: &str) {
|
| 218 |
+
if let Some(logger) = &self.logger {
|
| 219 |
+
logger
|
| 220 |
+
.send(LogMessage::new(String::from(message), true))
|
| 221 |
+
.await
|
| 222 |
+
.unwrap_or_default();
|
| 223 |
+
}
|
| 224 |
+
}
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
impl Clone for ErrorLogger {
|
| 228 |
+
/// Clone a `ErrorLogger`.
|
| 229 |
+
///
|
| 230 |
+
/// # Returns
|
| 231 |
+
///
|
| 232 |
+
/// A cloned `ErrorLogger` instance
|
| 233 |
+
fn clone(&self) -> Self {
|
| 234 |
+
Self {
|
| 235 |
+
logger: self.logger.clone(),
|
| 236 |
+
}
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
/// Holds data related to an HTTP response, including the original request,
|
| 241 |
+
/// optional authentication user information, and the response details.
|
| 242 |
+
pub struct ResponseData {
|
| 243 |
+
request: Option<HyperRequest>,
|
| 244 |
+
auth_user: Option<String>,
|
| 245 |
+
original_url: Option<Uri>,
|
| 246 |
+
response: Option<Response<BoxBody<Bytes, std::io::Error>>>,
|
| 247 |
+
response_status: Option<StatusCode>,
|
| 248 |
+
response_headers: Option<HeaderMap>,
|
| 249 |
+
new_remote_address: Option<SocketAddr>,
|
| 250 |
+
parallel_fn: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
impl ResponseData {
|
| 254 |
+
/// Initiates the building process for a `ResponseData` instance using a `RequestData` object.
|
| 255 |
+
///
|
| 256 |
+
/// # Parameters
|
| 257 |
+
///
|
| 258 |
+
/// - `request`: A `RequestData` instance containing the original request and authentication information.
|
| 259 |
+
///
|
| 260 |
+
/// # Returns
|
| 261 |
+
///
|
| 262 |
+
/// A `ResponseDataBuilder` initialized with the provided request data.
|
| 263 |
+
pub fn builder(request: RequestData) -> ResponseDataBuilder {
|
| 264 |
+
let (request, auth_user, original_url) = request.into_parts();
|
| 265 |
+
|
| 266 |
+
ResponseDataBuilder {
|
| 267 |
+
request: Some(request),
|
| 268 |
+
auth_user,
|
| 269 |
+
original_url,
|
| 270 |
+
response: None,
|
| 271 |
+
response_status: None,
|
| 272 |
+
response_headers: None,
|
| 273 |
+
new_remote_address: None,
|
| 274 |
+
parallel_fn: None,
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
/// Initiates the building process for a `ResponseData` instance without a `RequestData` object.
|
| 279 |
+
///
|
| 280 |
+
/// # Returns
|
| 281 |
+
///
|
| 282 |
+
/// A `ResponseDataBuilder` initialized without any request data.
|
| 283 |
+
pub fn builder_without_request() -> ResponseDataBuilder {
|
| 284 |
+
ResponseDataBuilder {
|
| 285 |
+
request: None,
|
| 286 |
+
auth_user: None,
|
| 287 |
+
original_url: None,
|
| 288 |
+
response: None,
|
| 289 |
+
response_status: None,
|
| 290 |
+
response_headers: None,
|
| 291 |
+
new_remote_address: None,
|
| 292 |
+
parallel_fn: None,
|
| 293 |
+
}
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
/// Consumes the `ResponseData` instance and returns its components.
|
| 297 |
+
///
|
| 298 |
+
/// # Returns
|
| 299 |
+
///
|
| 300 |
+
/// A tuple containing:
|
| 301 |
+
/// - The optional original `HyperRequest` object.
|
| 302 |
+
/// - An optional authenticated user string.
|
| 303 |
+
/// - An optional `Uri` object representing the original request URL (before rewriting)
|
| 304 |
+
/// - An optional `Response` object encapsulated in a `BoxBody` with `Bytes` and `std::io::Error`.
|
| 305 |
+
/// - An optional HTTP `StatusCode`.
|
| 306 |
+
/// - An optional `HeaderMap` containing the HTTP headers.
|
| 307 |
+
/// - An optional `SocketAddr` containing the client's new IP address and port.
|
| 308 |
+
/// - An optional `Future` with `()` output that would be executed in parallel.
|
| 309 |
+
#[allow(clippy::type_complexity)]
|
| 310 |
+
pub fn into_parts(
|
| 311 |
+
self,
|
| 312 |
+
) -> (
|
| 313 |
+
Option<HyperRequest>,
|
| 314 |
+
Option<String>,
|
| 315 |
+
Option<Uri>,
|
| 316 |
+
Option<Response<BoxBody<Bytes, std::io::Error>>>,
|
| 317 |
+
Option<StatusCode>,
|
| 318 |
+
Option<HeaderMap>,
|
| 319 |
+
Option<SocketAddr>,
|
| 320 |
+
Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
|
| 321 |
+
) {
|
| 322 |
+
(
|
| 323 |
+
self.request,
|
| 324 |
+
self.auth_user,
|
| 325 |
+
self.original_url,
|
| 326 |
+
self.response,
|
| 327 |
+
self.response_status,
|
| 328 |
+
self.response_headers,
|
| 329 |
+
self.new_remote_address,
|
| 330 |
+
self.parallel_fn,
|
| 331 |
+
)
|
| 332 |
+
}
|
| 333 |
+
}
|
| 334 |
+
|
| 335 |
+
pub struct ResponseDataBuilder {
|
| 336 |
+
request: Option<HyperRequest>,
|
| 337 |
+
auth_user: Option<String>,
|
| 338 |
+
original_url: Option<Uri>,
|
| 339 |
+
response: Option<Response<BoxBody<Bytes, std::io::Error>>>,
|
| 340 |
+
response_status: Option<StatusCode>,
|
| 341 |
+
response_headers: Option<HeaderMap>,
|
| 342 |
+
new_remote_address: Option<SocketAddr>,
|
| 343 |
+
parallel_fn: Option<Pin<Box<dyn Future<Output = ()> + Send>>>,
|
| 344 |
+
}
|
| 345 |
+
|
| 346 |
+
impl ResponseDataBuilder {
|
| 347 |
+
/// Sets the response for the `ResponseData`.
|
| 348 |
+
///
|
| 349 |
+
/// # Parameters
|
| 350 |
+
///
|
| 351 |
+
/// - `response`: A `Response` object encapsulated in a `BoxBody` with `Bytes` and `std::io::Error`.
|
| 352 |
+
///
|
| 353 |
+
/// # Returns
|
| 354 |
+
///
|
| 355 |
+
/// The updated `ResponseDataBuilder` instance with the specified response.
|
| 356 |
+
pub fn response(mut self, response: Response<BoxBody<Bytes, std::io::Error>>) -> Self {
|
| 357 |
+
self.response = Some(response);
|
| 358 |
+
self
|
| 359 |
+
}
|
| 360 |
+
|
| 361 |
+
/// Sets the status code for the `ResponseData`.
|
| 362 |
+
///
|
| 363 |
+
/// # Parameters
|
| 364 |
+
///
|
| 365 |
+
/// - `status`: A `StatusCode` representing the HTTP status code.
|
| 366 |
+
///
|
| 367 |
+
/// # Returns
|
| 368 |
+
///
|
| 369 |
+
/// The updated `ResponseDataBuilder` instance with the specified status code.
|
| 370 |
+
pub fn status(mut self, status: StatusCode) -> Self {
|
| 371 |
+
self.response_status = Some(status);
|
| 372 |
+
self
|
| 373 |
+
}
|
| 374 |
+
|
| 375 |
+
/// Sets the headers for the `ResponseData`.
|
| 376 |
+
///
|
| 377 |
+
/// # Parameters
|
| 378 |
+
///
|
| 379 |
+
/// - `headers`: A `HeaderMap` containing the HTTP headers.
|
| 380 |
+
///
|
| 381 |
+
/// # Returns
|
| 382 |
+
///
|
| 383 |
+
/// The updated `ResponseDataBuilder` instance with the specified headers.
|
| 384 |
+
pub fn headers(mut self, headers: HeaderMap) -> Self {
|
| 385 |
+
self.response_headers = Some(headers);
|
| 386 |
+
self
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
/// Sets the new client address for the `ResponseData`.
|
| 390 |
+
///
|
| 391 |
+
/// # Parameters
|
| 392 |
+
///
|
| 393 |
+
/// - `new_remote_address`: A `SocketAddr` containing the new client's IP address and port.
|
| 394 |
+
///
|
| 395 |
+
/// # Returns
|
| 396 |
+
///
|
| 397 |
+
/// The updated `ResponseDataBuilder` instance with the specified headers.
|
| 398 |
+
pub fn new_remote_address(mut self, new_remote_address: SocketAddr) -> Self {
|
| 399 |
+
self.new_remote_address = Some(new_remote_address);
|
| 400 |
+
self
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
/// Sets the function to be executed in parallel.
|
| 404 |
+
///
|
| 405 |
+
/// # Parameters
|
| 406 |
+
///
|
| 407 |
+
/// - `parallel_fn`: A `Future` with `()` output.
|
| 408 |
+
///
|
| 409 |
+
/// # Returns
|
| 410 |
+
///
|
| 411 |
+
/// The updated `ResponseDataBuilder` instance with the specified function to be executed in parallel.
|
| 412 |
+
pub fn parallel_fn(mut self, parallel_fn: impl Future<Output = ()> + Send + 'static) -> Self {
|
| 413 |
+
self.parallel_fn = Some(Box::pin(parallel_fn));
|
| 414 |
+
self
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
/// Builds the `ResponseData` instance.
|
| 418 |
+
///
|
| 419 |
+
/// # Returns
|
| 420 |
+
///
|
| 421 |
+
/// A `ResponseData` object containing the accumulated data from the builder.
|
| 422 |
+
pub fn build(self) -> ResponseData {
|
| 423 |
+
ResponseData {
|
| 424 |
+
request: self.request,
|
| 425 |
+
auth_user: self.auth_user,
|
| 426 |
+
original_url: self.original_url,
|
| 427 |
+
response: self.response,
|
| 428 |
+
response_status: self.response_status,
|
| 429 |
+
response_headers: self.response_headers,
|
| 430 |
+
new_remote_address: self.new_remote_address,
|
| 431 |
+
parallel_fn: self.parallel_fn,
|
| 432 |
+
}
|
| 433 |
+
}
|
| 434 |
+
}
|
| 435 |
+
|
| 436 |
+
/// Defines the interface for server module handlers, specifying how requests should be processed.
|
| 437 |
+
#[async_trait]
|
| 438 |
+
pub trait ServerModuleHandlers {
|
| 439 |
+
/// Handles an incoming request.
|
| 440 |
+
///
|
| 441 |
+
/// # Parameters
|
| 442 |
+
///
|
| 443 |
+
/// - `request`: A `RequestData` object containing the incoming request and associated data.
|
| 444 |
+
/// - `config`: A reference to the combined server configuration (`ServerConfig`). The combined configuration has properties in its root.
|
| 445 |
+
/// - `socket_data`: A reference to the `SocketData` containing socket-related information.
|
| 446 |
+
/// - `error_logger`: A reference to an `ErrorLogger` for logging errors.
|
| 447 |
+
///
|
| 448 |
+
/// # Returns
|
| 449 |
+
///
|
| 450 |
+
/// A `Result` containing a `ResponseData` object upon success, or a boxed `dyn Error` if an error occurs.
|
| 451 |
+
async fn request_handler(
|
| 452 |
+
&mut self,
|
| 453 |
+
request: RequestData,
|
| 454 |
+
config: &ServerConfig,
|
| 455 |
+
socket_data: &SocketData,
|
| 456 |
+
error_logger: &ErrorLogger,
|
| 457 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>>;
|
| 458 |
+
|
| 459 |
+
/// Handles an incoming forward proxy request (not using CONNECT method).
|
| 460 |
+
///
|
| 461 |
+
/// # Parameters
|
| 462 |
+
///
|
| 463 |
+
/// - `request`: A `RequestData` object containing the incoming request and associated data.
|
| 464 |
+
/// - `config`: A reference to the combined server configuration (`ServerConfig`). The combined configuration has properties in its root.
|
| 465 |
+
/// - `socket_data`: A reference to the `SocketData` containing socket-related information.
|
| 466 |
+
/// - `error_logger`: A reference to an `ErrorLogger` for logging errors.
|
| 467 |
+
///
|
| 468 |
+
/// # Returns
|
| 469 |
+
///
|
| 470 |
+
/// A `Result` containing a `ResponseData` object upon success, or a boxed `dyn Error` if an error occurs.
|
| 471 |
+
async fn proxy_request_handler(
|
| 472 |
+
&mut self,
|
| 473 |
+
request: RequestData,
|
| 474 |
+
config: &ServerConfig,
|
| 475 |
+
socket_data: &SocketData,
|
| 476 |
+
error_logger: &ErrorLogger,
|
| 477 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>>;
|
| 478 |
+
|
| 479 |
+
/// Modifies an outgoing response before it is sent to the client.
|
| 480 |
+
///
|
| 481 |
+
/// This function allows for inspection and modification of the response generated by the server
|
| 482 |
+
/// or other handlers. Implementers can use this to add, remove, or alter headers, change the
|
| 483 |
+
/// status code, or modify the body of the response as needed.
|
| 484 |
+
///
|
| 485 |
+
/// # Parameters
|
| 486 |
+
///
|
| 487 |
+
/// - `response`: A `HyperResponse` object representing the outgoing HTTP response.
|
| 488 |
+
///
|
| 489 |
+
/// # Returns
|
| 490 |
+
///
|
| 491 |
+
/// A `Result` containing the potentially modified `HyperResponse` object upon success, or a boxed
|
| 492 |
+
/// `dyn Error` if an error occurs during processing.
|
| 493 |
+
async fn response_modifying_handler(
|
| 494 |
+
&mut self,
|
| 495 |
+
response: HyperResponse,
|
| 496 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>>;
|
| 497 |
+
|
| 498 |
+
/// Modifies an outgoing response for forward proxy requests (not using CONNECT method) before it is sent to the client.
|
| 499 |
+
///
|
| 500 |
+
/// This function allows for inspection and modification of the response generated by the server
|
| 501 |
+
/// or other handlers. Implementers can use this to add, remove, or alter headers, change the
|
| 502 |
+
/// status code, or modify the body of the response as needed.
|
| 503 |
+
///
|
| 504 |
+
/// # Parameters
|
| 505 |
+
///
|
| 506 |
+
/// - `response`: A `HyperResponse` object representing the outgoing HTTP response.
|
| 507 |
+
///
|
| 508 |
+
/// # Returns
|
| 509 |
+
///
|
| 510 |
+
/// A `Result` containing the potentially modified `HyperResponse` object upon success, or a boxed
|
| 511 |
+
/// `dyn Error` if an error occurs during processing.
|
| 512 |
+
async fn proxy_response_modifying_handler(
|
| 513 |
+
&mut self,
|
| 514 |
+
response: HyperResponse,
|
| 515 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>>;
|
| 516 |
+
|
| 517 |
+
/// Handles an incoming forward proxy request (using CONNECT method).
|
| 518 |
+
///
|
| 519 |
+
/// # Parameters
|
| 520 |
+
///
|
| 521 |
+
/// - `upgraded_request`: A `HyperUpgraded` object containing the upgraded HTTP connection.
|
| 522 |
+
/// - `connect_address`: A reference to a string containing the address and port number of the destination server (for example "example.com:443").
|
| 523 |
+
/// - `config`: A reference to the combined server configuration (`ServerConfig`). The combined configuration has properties in its root.
|
| 524 |
+
/// - `socket_data`: A reference to the `SocketData` containing socket-related information.
|
| 525 |
+
/// - `error_logger`: A reference to an `ErrorLogger` for logging errors.
|
| 526 |
+
///
|
| 527 |
+
/// # Returns
|
| 528 |
+
///
|
| 529 |
+
/// A `Result` containing an empty value upon success, or a boxed `dyn Error` if an error occurs.
|
| 530 |
+
async fn connect_proxy_request_handler(
|
| 531 |
+
&mut self,
|
| 532 |
+
upgraded_request: HyperUpgraded,
|
| 533 |
+
connect_address: &str,
|
| 534 |
+
config: &ServerConfig,
|
| 535 |
+
socket_data: &SocketData,
|
| 536 |
+
error_logger: &ErrorLogger,
|
| 537 |
+
) -> Result<(), Box<dyn Error + Send + Sync>>;
|
| 538 |
+
|
| 539 |
+
/// Checks if the module is a forward proxy module utilizing CONNECT method.
|
| 540 |
+
///
|
| 541 |
+
/// # Returns
|
| 542 |
+
///
|
| 543 |
+
/// `true` if the module is a forward proxy module utlilzing CONNECT method, or `false` otherwise.
|
| 544 |
+
fn does_connect_proxy_requests(&mut self) -> bool;
|
| 545 |
+
|
| 546 |
+
/// Handles an incoming WebSocket request.
|
| 547 |
+
///
|
| 548 |
+
/// # Parameters
|
| 549 |
+
///
|
| 550 |
+
/// - `websocket`: A `HyperWebsocket` object containing a future that resolves to a WebSocket stream.
|
| 551 |
+
/// - `uri`: A `hyper::Uri` object containig the HTTP request URI.
|
| 552 |
+
/// - `config`: A reference to the combined server configuration (`ServerConfig`). The combined configuration has properties in its root.
|
| 553 |
+
/// - `socket_data`: A reference to the `SocketData` containing socket-related information.
|
| 554 |
+
/// - `error_logger`: A reference to an `ErrorLogger` for logging errors.
|
| 555 |
+
///
|
| 556 |
+
/// # Returns
|
| 557 |
+
///
|
| 558 |
+
/// A `Result` containing an empty value upon success, or a boxed `dyn Error` if an error occurs.
|
| 559 |
+
async fn websocket_request_handler(
|
| 560 |
+
&mut self,
|
| 561 |
+
websocket: HyperWebsocket,
|
| 562 |
+
uri: &hyper::Uri,
|
| 563 |
+
config: &ServerConfig,
|
| 564 |
+
socket_data: &SocketData,
|
| 565 |
+
error_logger: &ErrorLogger,
|
| 566 |
+
) -> Result<(), Box<dyn Error + Send + Sync>>;
|
| 567 |
+
|
| 568 |
+
/// Checks if the module is a module supporting WebSocket requests.
|
| 569 |
+
///
|
| 570 |
+
/// # Parameters
|
| 571 |
+
///
|
| 572 |
+
/// - `config`: A reference to the combined server configuration (`ServerConfig`). The combined configuration has properties in its root.
|
| 573 |
+
/// - `socket_data`: A reference to the `SocketData` containing socket-related information.
|
| 574 |
+
///
|
| 575 |
+
/// # Returns
|
| 576 |
+
///
|
| 577 |
+
/// `true` if the module is a module supporting WebSocket requests, or `false` otherwise.
|
| 578 |
+
fn does_websocket_requests(&mut self, config: &ServerConfig, socket_data: &SocketData) -> bool;
|
| 579 |
+
}
|
| 580 |
+
|
| 581 |
+
/// Represents a server module that can provide handlers for processing requests.
|
| 582 |
+
pub trait ServerModule {
|
| 583 |
+
/// Retrieves the handlers associated with the server module.
|
| 584 |
+
///
|
| 585 |
+
/// # Parameters
|
| 586 |
+
///
|
| 587 |
+
/// - `handle`: A `Handle` to the Tokio runtime.
|
| 588 |
+
///
|
| 589 |
+
/// # Returns
|
| 590 |
+
///
|
| 591 |
+
/// A boxed object implementing `ServerModuleHandlers` that can be sent across threads.
|
| 592 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send>;
|
| 593 |
+
}
|
ferron/src/common/with_runtime.rs
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use pin_project_lite::pin_project;
|
| 2 |
+
use std::{
|
| 3 |
+
future::Future,
|
| 4 |
+
pin::Pin,
|
| 5 |
+
task::{Context, Poll},
|
| 6 |
+
};
|
| 7 |
+
use tokio::runtime::Handle;
|
| 8 |
+
|
| 9 |
+
pin_project! {
|
| 10 |
+
/// A future that executes within a specific Tokio runtime.
|
| 11 |
+
///
|
| 12 |
+
/// This struct ensures that the wrapped future (`fut`) is polled within the context of the provided Tokio runtime handle (`runtime`).
|
| 13 |
+
pub struct WithRuntime<F> {
|
| 14 |
+
runtime: Handle,
|
| 15 |
+
#[pin]
|
| 16 |
+
fut: F,
|
| 17 |
+
}
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
impl<F> WithRuntime<F> {
|
| 21 |
+
/// Creates a new `WithRuntime` instance.
|
| 22 |
+
///
|
| 23 |
+
/// # Parameters
|
| 24 |
+
///
|
| 25 |
+
/// - `runtime`: A `Handle` to the Tokio runtime in which the future should be executed.
|
| 26 |
+
/// - `fut`: The future to be executed within the specified runtime.
|
| 27 |
+
///
|
| 28 |
+
/// # Returns
|
| 29 |
+
///
|
| 30 |
+
/// A `WithRuntime` object encapsulating the provided runtime handle and future.
|
| 31 |
+
pub fn new(runtime: Handle, fut: F) -> Self {
|
| 32 |
+
Self { runtime, fut }
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
impl<F> Future for WithRuntime<F>
|
| 37 |
+
where
|
| 38 |
+
F: Future,
|
| 39 |
+
{
|
| 40 |
+
type Output = F::Output;
|
| 41 |
+
|
| 42 |
+
/// Polls the wrapped future within the context of the specified Tokio runtime.
|
| 43 |
+
///
|
| 44 |
+
/// # Parameters
|
| 45 |
+
///
|
| 46 |
+
/// - `ctx`: The current task context.
|
| 47 |
+
///
|
| 48 |
+
/// # Returns
|
| 49 |
+
///
|
| 50 |
+
/// A `Poll` indicating the state of the wrapped future (`Pending` or `Ready`).
|
| 51 |
+
fn poll(self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll<Self::Output> {
|
| 52 |
+
let this = self.project();
|
| 53 |
+
let _guard = this.runtime.enter();
|
| 54 |
+
this.fut.poll(ctx)
|
| 55 |
+
}
|
| 56 |
+
}
|
ferron/src/main.rs
ADDED
|
@@ -0,0 +1,478 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// Import server module from "server.rs"
|
| 2 |
+
#[path = "server.rs"]
|
| 3 |
+
mod ferron_server;
|
| 4 |
+
|
| 5 |
+
// Import request handler module from "request_handler.rs"
|
| 6 |
+
#[path = "request_handler.rs"]
|
| 7 |
+
mod ferron_request_handler;
|
| 8 |
+
|
| 9 |
+
// Import resources from "res" directory
|
| 10 |
+
#[path = "res"]
|
| 11 |
+
mod ferron_res {
|
| 12 |
+
pub mod server_software;
|
| 13 |
+
}
|
| 14 |
+
|
| 15 |
+
// Import common modules from "common" directory
|
| 16 |
+
#[path = "common/mod.rs"]
|
| 17 |
+
mod ferron_common;
|
| 18 |
+
|
| 19 |
+
// Import utility modules from "util" directory
|
| 20 |
+
#[path = "util"]
|
| 21 |
+
mod ferron_util {
|
| 22 |
+
pub mod anti_xss;
|
| 23 |
+
#[cfg(feature = "asgi")]
|
| 24 |
+
pub mod asgi_messages;
|
| 25 |
+
#[cfg(feature = "asgi")]
|
| 26 |
+
pub mod asgi_structs;
|
| 27 |
+
#[cfg(any(feature = "cgi", feature = "scgi", feature = "fcgi"))]
|
| 28 |
+
pub mod cgi_response;
|
| 29 |
+
pub mod combine_config;
|
| 30 |
+
#[cfg(any(feature = "cgi", feature = "scgi", feature = "fcgi"))]
|
| 31 |
+
pub mod copy_move;
|
| 32 |
+
pub mod error_pages;
|
| 33 |
+
#[cfg(feature = "fcgi")]
|
| 34 |
+
pub mod fcgi_decoder;
|
| 35 |
+
#[cfg(feature = "fcgi")]
|
| 36 |
+
pub mod fcgi_encoder;
|
| 37 |
+
#[cfg(feature = "fcgi")]
|
| 38 |
+
pub mod fcgi_name_value_pair;
|
| 39 |
+
#[cfg(feature = "fcgi")]
|
| 40 |
+
pub mod fcgi_record;
|
| 41 |
+
pub mod generate_directory_listing;
|
| 42 |
+
pub mod ip_blocklist;
|
| 43 |
+
pub mod ip_match;
|
| 44 |
+
pub mod load_config;
|
| 45 |
+
pub mod load_tls;
|
| 46 |
+
pub mod match_hostname;
|
| 47 |
+
pub mod match_location;
|
| 48 |
+
#[cfg(any(feature = "rproxy", feature = "fauth"))]
|
| 49 |
+
pub mod no_server_verifier;
|
| 50 |
+
pub mod non_standard_code_structs;
|
| 51 |
+
#[cfg(all(unix, feature = "wsgid"))]
|
| 52 |
+
pub mod preforked_process_pool;
|
| 53 |
+
#[cfg(feature = "fcgi")]
|
| 54 |
+
pub mod read_to_end_move;
|
| 55 |
+
pub mod sizify;
|
| 56 |
+
pub mod sni;
|
| 57 |
+
#[cfg(feature = "fcgi")]
|
| 58 |
+
pub mod split_stream_by_map;
|
| 59 |
+
pub mod ttl_cache;
|
| 60 |
+
pub mod url_rewrite_structs;
|
| 61 |
+
pub mod url_sanitizer;
|
| 62 |
+
pub mod validate_config;
|
| 63 |
+
#[cfg(feature = "wsgi")]
|
| 64 |
+
pub mod wsgi_error_stream;
|
| 65 |
+
#[cfg(feature = "wsgi")]
|
| 66 |
+
pub mod wsgi_input_stream;
|
| 67 |
+
#[cfg(any(feature = "wsgi", feature = "wsgid"))]
|
| 68 |
+
pub mod wsgi_load_application;
|
| 69 |
+
#[cfg(feature = "wsgi")]
|
| 70 |
+
pub mod wsgi_structs;
|
| 71 |
+
#[cfg(feature = "wsgid")]
|
| 72 |
+
pub mod wsgid_body_reader;
|
| 73 |
+
#[cfg(feature = "wsgid")]
|
| 74 |
+
pub mod wsgid_error_stream;
|
| 75 |
+
#[cfg(feature = "wsgid")]
|
| 76 |
+
pub mod wsgid_input_stream;
|
| 77 |
+
#[cfg(feature = "wsgid")]
|
| 78 |
+
pub mod wsgid_message_structs;
|
| 79 |
+
#[cfg(feature = "wsgid")]
|
| 80 |
+
pub mod wsgid_structs;
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
// Import project modules from "modules" directory
|
| 84 |
+
#[path = "modules"]
|
| 85 |
+
mod ferron_modules {
|
| 86 |
+
pub mod blocklist;
|
| 87 |
+
pub mod default_handler_checks;
|
| 88 |
+
pub mod non_standard_codes;
|
| 89 |
+
pub mod redirect_trailing_slashes;
|
| 90 |
+
pub mod redirects;
|
| 91 |
+
pub mod static_file_serving;
|
| 92 |
+
pub mod url_rewrite;
|
| 93 |
+
pub mod x_forwarded_for;
|
| 94 |
+
}
|
| 95 |
+
|
| 96 |
+
// Import optional project modules from "modules" directory
|
| 97 |
+
#[path = "optional_modules"]
|
| 98 |
+
mod ferron_optional_modules {
|
| 99 |
+
#[cfg(feature = "asgi")]
|
| 100 |
+
pub mod asgi;
|
| 101 |
+
#[cfg(feature = "cache")]
|
| 102 |
+
pub mod cache;
|
| 103 |
+
#[cfg(feature = "cgi")]
|
| 104 |
+
pub mod cgi;
|
| 105 |
+
#[cfg(feature = "example")]
|
| 106 |
+
pub mod example;
|
| 107 |
+
#[cfg(feature = "fauth")]
|
| 108 |
+
pub mod fauth;
|
| 109 |
+
#[cfg(feature = "fcgi")]
|
| 110 |
+
pub mod fcgi;
|
| 111 |
+
#[cfg(feature = "fproxy")]
|
| 112 |
+
pub mod fproxy;
|
| 113 |
+
#[cfg(feature = "rproxy")]
|
| 114 |
+
pub mod rproxy;
|
| 115 |
+
#[cfg(feature = "scgi")]
|
| 116 |
+
pub mod scgi;
|
| 117 |
+
#[cfg(feature = "wsgi")]
|
| 118 |
+
pub mod wsgi;
|
| 119 |
+
#[cfg(feature = "wsgid")]
|
| 120 |
+
pub mod wsgid;
|
| 121 |
+
}
|
| 122 |
+
|
| 123 |
+
// Standard library imports
|
| 124 |
+
use std::sync::Arc;
|
| 125 |
+
use std::{error::Error, path::PathBuf};
|
| 126 |
+
|
| 127 |
+
// External crate imports
|
| 128 |
+
use clap::Parser;
|
| 129 |
+
use ferron_server::start_server;
|
| 130 |
+
use ferron_util::load_config::load_config;
|
| 131 |
+
use mimalloc::MiMalloc;
|
| 132 |
+
|
| 133 |
+
// Set the global allocator to use mimalloc for performance optimization
|
| 134 |
+
#[global_allocator]
|
| 135 |
+
static GLOBAL: MiMalloc = MiMalloc;
|
| 136 |
+
|
| 137 |
+
// Struct for command-line arguments
|
| 138 |
+
/// A fast, memory-safe web server written in Rust
|
| 139 |
+
#[derive(Parser, Debug)]
|
| 140 |
+
#[command(name = "Ferron")]
|
| 141 |
+
#[command(version, about, long_about = None)]
|
| 142 |
+
struct Args {
|
| 143 |
+
/// The path to the server configuration file
|
| 144 |
+
#[arg(short, long, default_value_t = String::from("./ferron.yaml"))]
|
| 145 |
+
config: String,
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
// Function to execute before starting the server
|
| 149 |
+
#[allow(clippy::type_complexity)]
|
| 150 |
+
fn before_starting_server(
|
| 151 |
+
args: &Args,
|
| 152 |
+
first_start: bool,
|
| 153 |
+
) -> Result<bool, Box<dyn Error + Send + Sync>> {
|
| 154 |
+
// Load the configuration
|
| 155 |
+
let yaml_config = load_config(PathBuf::from(args.config.clone()))?;
|
| 156 |
+
|
| 157 |
+
let mut module_error = None;
|
| 158 |
+
let mut module_libs = Vec::new();
|
| 159 |
+
|
| 160 |
+
// Load external modules defined in the configuration file
|
| 161 |
+
if let Some(modules) = yaml_config["global"]["loadModules"].as_vec() {
|
| 162 |
+
for module_name_yaml in modules.iter() {
|
| 163 |
+
if let Some(module_name) = module_name_yaml.as_str() {
|
| 164 |
+
module_libs.push(String::from(module_name));
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
let mut external_modules = Vec::new();
|
| 170 |
+
#[allow(unused_mut)]
|
| 171 |
+
let mut modules_optional_builtin = Vec::new();
|
| 172 |
+
// Iterate over loaded module libraries and initialize them
|
| 173 |
+
for module_name in module_libs.iter() {
|
| 174 |
+
match module_name as &str {
|
| 175 |
+
#[cfg(feature = "rproxy")]
|
| 176 |
+
"rproxy" => {
|
| 177 |
+
external_modules.push(
|
| 178 |
+
match ferron_optional_modules::rproxy::server_module_init(&yaml_config) {
|
| 179 |
+
Ok(module) => module,
|
| 180 |
+
Err(err) => {
|
| 181 |
+
module_error = Some(anyhow::anyhow!(
|
| 182 |
+
"Cannot initialize optional built-in module \"{}\": {}",
|
| 183 |
+
module_name,
|
| 184 |
+
err
|
| 185 |
+
));
|
| 186 |
+
break;
|
| 187 |
+
}
|
| 188 |
+
},
|
| 189 |
+
);
|
| 190 |
+
|
| 191 |
+
modules_optional_builtin.push(module_name.clone());
|
| 192 |
+
}
|
| 193 |
+
#[cfg(feature = "fproxy")]
|
| 194 |
+
"fproxy" => {
|
| 195 |
+
external_modules.push(
|
| 196 |
+
match ferron_optional_modules::fproxy::server_module_init(&yaml_config) {
|
| 197 |
+
Ok(module) => module,
|
| 198 |
+
Err(err) => {
|
| 199 |
+
module_error = Some(anyhow::anyhow!(
|
| 200 |
+
"Cannot initialize optional built-in module \"{}\": {}",
|
| 201 |
+
module_name,
|
| 202 |
+
err
|
| 203 |
+
));
|
| 204 |
+
break;
|
| 205 |
+
}
|
| 206 |
+
},
|
| 207 |
+
);
|
| 208 |
+
|
| 209 |
+
modules_optional_builtin.push(module_name.clone());
|
| 210 |
+
}
|
| 211 |
+
#[cfg(feature = "cache")]
|
| 212 |
+
"cache" => {
|
| 213 |
+
external_modules.push(
|
| 214 |
+
match ferron_optional_modules::cache::server_module_init(&yaml_config) {
|
| 215 |
+
Ok(module) => module,
|
| 216 |
+
Err(err) => {
|
| 217 |
+
module_error = Some(anyhow::anyhow!(
|
| 218 |
+
"Cannot initialize optional built-in module \"{}\": {}",
|
| 219 |
+
module_name,
|
| 220 |
+
err
|
| 221 |
+
));
|
| 222 |
+
break;
|
| 223 |
+
}
|
| 224 |
+
},
|
| 225 |
+
);
|
| 226 |
+
|
| 227 |
+
modules_optional_builtin.push(module_name.clone());
|
| 228 |
+
}
|
| 229 |
+
#[cfg(feature = "cgi")]
|
| 230 |
+
"cgi" => {
|
| 231 |
+
external_modules.push(
|
| 232 |
+
match ferron_optional_modules::cgi::server_module_init(&yaml_config) {
|
| 233 |
+
Ok(module) => module,
|
| 234 |
+
Err(err) => {
|
| 235 |
+
module_error = Some(anyhow::anyhow!(
|
| 236 |
+
"Cannot initialize optional built-in module \"{}\": {}",
|
| 237 |
+
module_name,
|
| 238 |
+
err
|
| 239 |
+
));
|
| 240 |
+
break;
|
| 241 |
+
}
|
| 242 |
+
},
|
| 243 |
+
);
|
| 244 |
+
|
| 245 |
+
modules_optional_builtin.push(module_name.clone());
|
| 246 |
+
}
|
| 247 |
+
#[cfg(feature = "scgi")]
|
| 248 |
+
"scgi" => {
|
| 249 |
+
external_modules.push(
|
| 250 |
+
match ferron_optional_modules::scgi::server_module_init(&yaml_config) {
|
| 251 |
+
Ok(module) => module,
|
| 252 |
+
Err(err) => {
|
| 253 |
+
module_error = Some(anyhow::anyhow!(
|
| 254 |
+
"Cannot initialize optional built-in module \"{}\": {}",
|
| 255 |
+
module_name,
|
| 256 |
+
err
|
| 257 |
+
));
|
| 258 |
+
break;
|
| 259 |
+
}
|
| 260 |
+
},
|
| 261 |
+
);
|
| 262 |
+
|
| 263 |
+
modules_optional_builtin.push(module_name.clone());
|
| 264 |
+
}
|
| 265 |
+
#[cfg(feature = "fcgi")]
|
| 266 |
+
"fcgi" => {
|
| 267 |
+
external_modules.push(
|
| 268 |
+
match ferron_optional_modules::fcgi::server_module_init(&yaml_config) {
|
| 269 |
+
Ok(module) => module,
|
| 270 |
+
Err(err) => {
|
| 271 |
+
module_error = Some(anyhow::anyhow!(
|
| 272 |
+
"Cannot initialize optional built-in module \"{}\": {}",
|
| 273 |
+
module_name,
|
| 274 |
+
err
|
| 275 |
+
));
|
| 276 |
+
break;
|
| 277 |
+
}
|
| 278 |
+
},
|
| 279 |
+
);
|
| 280 |
+
|
| 281 |
+
modules_optional_builtin.push(module_name.clone());
|
| 282 |
+
}
|
| 283 |
+
#[cfg(feature = "fauth")]
|
| 284 |
+
"fauth" => {
|
| 285 |
+
external_modules.push(
|
| 286 |
+
match ferron_optional_modules::fauth::server_module_init(&yaml_config) {
|
| 287 |
+
Ok(module) => module,
|
| 288 |
+
Err(err) => {
|
| 289 |
+
module_error = Some(anyhow::anyhow!(
|
| 290 |
+
"Cannot initialize optional built-in module \"{}\": {}",
|
| 291 |
+
module_name,
|
| 292 |
+
err
|
| 293 |
+
));
|
| 294 |
+
break;
|
| 295 |
+
}
|
| 296 |
+
},
|
| 297 |
+
);
|
| 298 |
+
|
| 299 |
+
modules_optional_builtin.push(module_name.clone());
|
| 300 |
+
}
|
| 301 |
+
#[cfg(feature = "example")]
|
| 302 |
+
"example" => {
|
| 303 |
+
external_modules.push(
|
| 304 |
+
match ferron_optional_modules::example::server_module_init(&yaml_config) {
|
| 305 |
+
Ok(module) => module,
|
| 306 |
+
Err(err) => {
|
| 307 |
+
module_error = Some(anyhow::anyhow!(
|
| 308 |
+
"Cannot initialize optional built-in module \"{}\": {}",
|
| 309 |
+
module_name,
|
| 310 |
+
err
|
| 311 |
+
));
|
| 312 |
+
break;
|
| 313 |
+
}
|
| 314 |
+
},
|
| 315 |
+
);
|
| 316 |
+
|
| 317 |
+
modules_optional_builtin.push(module_name.clone());
|
| 318 |
+
}
|
| 319 |
+
#[cfg(feature = "wsgi")]
|
| 320 |
+
"wsgi" => {
|
| 321 |
+
external_modules.push(
|
| 322 |
+
match ferron_optional_modules::wsgi::server_module_init(&yaml_config) {
|
| 323 |
+
Ok(module) => module,
|
| 324 |
+
Err(err) => {
|
| 325 |
+
module_error = Some(anyhow::anyhow!(
|
| 326 |
+
"Cannot initialize optional built-in module \"{}\": {}",
|
| 327 |
+
module_name,
|
| 328 |
+
err
|
| 329 |
+
));
|
| 330 |
+
break;
|
| 331 |
+
}
|
| 332 |
+
},
|
| 333 |
+
);
|
| 334 |
+
|
| 335 |
+
modules_optional_builtin.push(module_name.clone());
|
| 336 |
+
}
|
| 337 |
+
#[cfg(feature = "wsgid")]
|
| 338 |
+
"wsgid" => {
|
| 339 |
+
external_modules.push(
|
| 340 |
+
match ferron_optional_modules::wsgid::server_module_init(&yaml_config) {
|
| 341 |
+
Ok(module) => module,
|
| 342 |
+
Err(err) => {
|
| 343 |
+
module_error = Some(anyhow::anyhow!(
|
| 344 |
+
"Cannot initialize optional built-in module \"{}\": {}",
|
| 345 |
+
module_name,
|
| 346 |
+
err
|
| 347 |
+
));
|
| 348 |
+
break;
|
| 349 |
+
}
|
| 350 |
+
},
|
| 351 |
+
);
|
| 352 |
+
|
| 353 |
+
modules_optional_builtin.push(module_name.clone());
|
| 354 |
+
}
|
| 355 |
+
#[cfg(feature = "asgi")]
|
| 356 |
+
"asgi" => {
|
| 357 |
+
external_modules.push(
|
| 358 |
+
match ferron_optional_modules::asgi::server_module_init(&yaml_config) {
|
| 359 |
+
Ok(module) => module,
|
| 360 |
+
Err(err) => {
|
| 361 |
+
module_error = Some(anyhow::anyhow!(
|
| 362 |
+
"Cannot initialize optional built-in module \"{}\": {}",
|
| 363 |
+
module_name,
|
| 364 |
+
err
|
| 365 |
+
));
|
| 366 |
+
break;
|
| 367 |
+
}
|
| 368 |
+
},
|
| 369 |
+
);
|
| 370 |
+
|
| 371 |
+
modules_optional_builtin.push(module_name.clone());
|
| 372 |
+
}
|
| 373 |
+
_ => {
|
| 374 |
+
module_error = Some(anyhow::anyhow!(
|
| 375 |
+
"The optional built-in module \"{}\" doesn't exist",
|
| 376 |
+
module_name
|
| 377 |
+
));
|
| 378 |
+
break;
|
| 379 |
+
}
|
| 380 |
+
}
|
| 381 |
+
}
|
| 382 |
+
|
| 383 |
+
// Add modules (both built-in and loaded)
|
| 384 |
+
let mut modules = Vec::new();
|
| 385 |
+
match ferron_modules::x_forwarded_for::server_module_init() {
|
| 386 |
+
Ok(module) => modules.push(module),
|
| 387 |
+
Err(err) => {
|
| 388 |
+
if module_error.is_none() {
|
| 389 |
+
module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
};
|
| 393 |
+
match ferron_modules::redirects::server_module_init() {
|
| 394 |
+
Ok(module) => modules.push(module),
|
| 395 |
+
Err(err) => {
|
| 396 |
+
if module_error.is_none() {
|
| 397 |
+
module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
|
| 398 |
+
}
|
| 399 |
+
}
|
| 400 |
+
};
|
| 401 |
+
match ferron_modules::blocklist::server_module_init(&yaml_config) {
|
| 402 |
+
Ok(module) => modules.push(module),
|
| 403 |
+
Err(err) => {
|
| 404 |
+
if module_error.is_none() {
|
| 405 |
+
module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
|
| 406 |
+
}
|
| 407 |
+
}
|
| 408 |
+
};
|
| 409 |
+
match ferron_modules::url_rewrite::server_module_init(&yaml_config) {
|
| 410 |
+
Ok(module) => modules.push(module),
|
| 411 |
+
Err(err) => {
|
| 412 |
+
if module_error.is_none() {
|
| 413 |
+
module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
|
| 414 |
+
}
|
| 415 |
+
}
|
| 416 |
+
};
|
| 417 |
+
match ferron_modules::non_standard_codes::server_module_init(&yaml_config) {
|
| 418 |
+
Ok(module) => modules.push(module),
|
| 419 |
+
Err(err) => {
|
| 420 |
+
if module_error.is_none() {
|
| 421 |
+
module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
|
| 422 |
+
}
|
| 423 |
+
}
|
| 424 |
+
};
|
| 425 |
+
match ferron_modules::redirect_trailing_slashes::server_module_init() {
|
| 426 |
+
Ok(module) => modules.push(module),
|
| 427 |
+
Err(err) => {
|
| 428 |
+
if module_error.is_none() {
|
| 429 |
+
module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
|
| 430 |
+
}
|
| 431 |
+
}
|
| 432 |
+
};
|
| 433 |
+
modules.append(&mut external_modules);
|
| 434 |
+
match ferron_modules::default_handler_checks::server_module_init() {
|
| 435 |
+
Ok(module) => modules.push(module),
|
| 436 |
+
Err(err) => {
|
| 437 |
+
if module_error.is_none() {
|
| 438 |
+
module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
|
| 439 |
+
}
|
| 440 |
+
}
|
| 441 |
+
};
|
| 442 |
+
match ferron_modules::static_file_serving::server_module_init() {
|
| 443 |
+
Ok(module) => modules.push(module),
|
| 444 |
+
Err(err) => {
|
| 445 |
+
if module_error.is_none() {
|
| 446 |
+
module_error = Some(anyhow::anyhow!("Cannot load a built-in module: {}", err));
|
| 447 |
+
}
|
| 448 |
+
}
|
| 449 |
+
};
|
| 450 |
+
|
| 451 |
+
// Start the server with configuration and loaded modules
|
| 452 |
+
start_server(
|
| 453 |
+
Arc::new(yaml_config),
|
| 454 |
+
modules,
|
| 455 |
+
module_error,
|
| 456 |
+
modules_optional_builtin,
|
| 457 |
+
first_start,
|
| 458 |
+
)
|
| 459 |
+
}
|
| 460 |
+
|
| 461 |
+
// Entry point of the application
|
| 462 |
+
fn main() {
|
| 463 |
+
let args = &Args::parse(); // Parse command-line arguments
|
| 464 |
+
let mut first_start = true;
|
| 465 |
+
loop {
|
| 466 |
+
match before_starting_server(args, first_start) {
|
| 467 |
+
Ok(false) => break,
|
| 468 |
+
Ok(true) => {
|
| 469 |
+
first_start = false;
|
| 470 |
+
println!("Reloading the server configuration...");
|
| 471 |
+
}
|
| 472 |
+
Err(err) => {
|
| 473 |
+
eprintln!("FATAL ERROR: {}", err);
|
| 474 |
+
std::process::exit(1);
|
| 475 |
+
}
|
| 476 |
+
}
|
| 477 |
+
}
|
| 478 |
+
}
|
ferron/src/modules/blocklist.rs
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::error::Error;
|
| 2 |
+
use std::sync::Arc;
|
| 3 |
+
|
| 4 |
+
use crate::ferron_common::{
|
| 5 |
+
ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 6 |
+
ServerModuleHandlers, SocketData,
|
| 7 |
+
};
|
| 8 |
+
use crate::ferron_common::{HyperUpgraded, WithRuntime};
|
| 9 |
+
use async_trait::async_trait;
|
| 10 |
+
use hyper::StatusCode;
|
| 11 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 12 |
+
use tokio::runtime::Handle;
|
| 13 |
+
|
| 14 |
+
use crate::ferron_util::ip_blocklist::IpBlockList;
|
| 15 |
+
|
| 16 |
+
struct BlockListModule {
|
| 17 |
+
blocklist: Arc<IpBlockList>,
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
pub fn server_module_init(
|
| 21 |
+
config: &ServerConfig,
|
| 22 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 23 |
+
let blocklist_vec = match config["global"]["blocklist"].as_vec() {
|
| 24 |
+
Some(blocklist_vec) => blocklist_vec,
|
| 25 |
+
None => &Vec::new(),
|
| 26 |
+
};
|
| 27 |
+
|
| 28 |
+
let mut blocklist_str_vec = Vec::new();
|
| 29 |
+
for blocked_yaml in blocklist_vec.iter() {
|
| 30 |
+
if let Some(blocked) = blocked_yaml.as_str() {
|
| 31 |
+
blocklist_str_vec.push(blocked);
|
| 32 |
+
}
|
| 33 |
+
}
|
| 34 |
+
|
| 35 |
+
let mut blocklist = IpBlockList::new();
|
| 36 |
+
blocklist.load_from_vec(blocklist_str_vec);
|
| 37 |
+
|
| 38 |
+
Ok(Box::new(BlockListModule::new(Arc::new(blocklist))))
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
impl BlockListModule {
|
| 42 |
+
fn new(blocklist: Arc<IpBlockList>) -> Self {
|
| 43 |
+
Self { blocklist }
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
impl ServerModule for BlockListModule {
|
| 48 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 49 |
+
Box::new(BlockListModuleHandlers {
|
| 50 |
+
blocklist: self.blocklist.clone(),
|
| 51 |
+
handle,
|
| 52 |
+
})
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
struct BlockListModuleHandlers {
|
| 56 |
+
blocklist: Arc<IpBlockList>,
|
| 57 |
+
handle: Handle,
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
#[async_trait]
|
| 61 |
+
impl ServerModuleHandlers for BlockListModuleHandlers {
|
| 62 |
+
async fn request_handler(
|
| 63 |
+
&mut self,
|
| 64 |
+
request: RequestData,
|
| 65 |
+
_config: &ServerConfig,
|
| 66 |
+
socket_data: &SocketData,
|
| 67 |
+
_error_logger: &ErrorLogger,
|
| 68 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 69 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 70 |
+
if self.blocklist.is_blocked(socket_data.remote_addr.ip()) {
|
| 71 |
+
return Ok(
|
| 72 |
+
ResponseData::builder(request)
|
| 73 |
+
.status(StatusCode::FORBIDDEN)
|
| 74 |
+
.build(),
|
| 75 |
+
);
|
| 76 |
+
}
|
| 77 |
+
Ok(ResponseData::builder(request).build())
|
| 78 |
+
})
|
| 79 |
+
.await
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
async fn proxy_request_handler(
|
| 83 |
+
&mut self,
|
| 84 |
+
request: RequestData,
|
| 85 |
+
_config: &ServerConfig,
|
| 86 |
+
_socket_data: &SocketData,
|
| 87 |
+
_error_logger: &ErrorLogger,
|
| 88 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 89 |
+
Ok(ResponseData::builder(request).build())
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
async fn response_modifying_handler(
|
| 93 |
+
&mut self,
|
| 94 |
+
response: HyperResponse,
|
| 95 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 96 |
+
Ok(response)
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
async fn proxy_response_modifying_handler(
|
| 100 |
+
&mut self,
|
| 101 |
+
response: HyperResponse,
|
| 102 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 103 |
+
Ok(response)
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
async fn connect_proxy_request_handler(
|
| 107 |
+
&mut self,
|
| 108 |
+
_upgraded_request: HyperUpgraded,
|
| 109 |
+
_connect_address: &str,
|
| 110 |
+
_config: &ServerConfig,
|
| 111 |
+
_socket_data: &SocketData,
|
| 112 |
+
_error_logger: &ErrorLogger,
|
| 113 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 114 |
+
Ok(())
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 118 |
+
false
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
async fn websocket_request_handler(
|
| 122 |
+
&mut self,
|
| 123 |
+
_websocket: HyperWebsocket,
|
| 124 |
+
_uri: &hyper::Uri,
|
| 125 |
+
_config: &ServerConfig,
|
| 126 |
+
_socket_data: &SocketData,
|
| 127 |
+
_error_logger: &ErrorLogger,
|
| 128 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 129 |
+
Ok(())
|
| 130 |
+
}
|
| 131 |
+
|
| 132 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 133 |
+
false
|
| 134 |
+
}
|
| 135 |
+
}
|
ferron/src/modules/default_handler_checks.rs
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::error::Error;
|
| 2 |
+
|
| 3 |
+
use crate::ferron_common::{
|
| 4 |
+
ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 5 |
+
ServerModuleHandlers, SocketData,
|
| 6 |
+
};
|
| 7 |
+
use crate::ferron_common::{HyperUpgraded, WithRuntime};
|
| 8 |
+
use async_trait::async_trait;
|
| 9 |
+
use http_body_util::{BodyExt, Empty};
|
| 10 |
+
use hyper::header::HeaderValue;
|
| 11 |
+
use hyper::{header, HeaderMap, Method, Response, StatusCode};
|
| 12 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 13 |
+
use tokio::runtime::Handle;
|
| 14 |
+
|
| 15 |
+
struct DefaultHandlerChecksModule;
|
| 16 |
+
|
| 17 |
+
pub fn server_module_init(
|
| 18 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 19 |
+
Ok(Box::new(DefaultHandlerChecksModule::new()))
|
| 20 |
+
}
|
| 21 |
+
|
| 22 |
+
impl DefaultHandlerChecksModule {
|
| 23 |
+
fn new() -> Self {
|
| 24 |
+
Self
|
| 25 |
+
}
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
impl ServerModule for DefaultHandlerChecksModule {
|
| 29 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 30 |
+
Box::new(DefaultHandlerChecksModuleHandlers { handle })
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
struct DefaultHandlerChecksModuleHandlers {
|
| 34 |
+
handle: Handle,
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
#[async_trait]
|
| 38 |
+
impl ServerModuleHandlers for DefaultHandlerChecksModuleHandlers {
|
| 39 |
+
async fn request_handler(
|
| 40 |
+
&mut self,
|
| 41 |
+
request: RequestData,
|
| 42 |
+
_config: &ServerConfig,
|
| 43 |
+
_socket_data: &SocketData,
|
| 44 |
+
_error_logger: &ErrorLogger,
|
| 45 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 46 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 47 |
+
match request.get_hyper_request().method() {
|
| 48 |
+
&Method::OPTIONS => Ok(
|
| 49 |
+
ResponseData::builder(request)
|
| 50 |
+
.response(
|
| 51 |
+
Response::builder()
|
| 52 |
+
.status(StatusCode::NO_CONTENT)
|
| 53 |
+
.header(header::ALLOW, "GET, POST, HEAD, OPTIONS")
|
| 54 |
+
.body(Empty::new().map_err(|e| match e {}).boxed())
|
| 55 |
+
.unwrap_or_default(),
|
| 56 |
+
)
|
| 57 |
+
.build(),
|
| 58 |
+
),
|
| 59 |
+
&Method::GET | &Method::POST | &Method::HEAD => Ok(ResponseData::builder(request).build()),
|
| 60 |
+
_ => {
|
| 61 |
+
let mut header_map = HeaderMap::new();
|
| 62 |
+
if let Ok(header_value) = HeaderValue::from_str("GET, POST, HEAD, OPTIONS") {
|
| 63 |
+
header_map.insert(header::ALLOW, header_value);
|
| 64 |
+
};
|
| 65 |
+
Ok(
|
| 66 |
+
ResponseData::builder(request)
|
| 67 |
+
.status(StatusCode::METHOD_NOT_ALLOWED)
|
| 68 |
+
.headers(header_map)
|
| 69 |
+
.build(),
|
| 70 |
+
)
|
| 71 |
+
}
|
| 72 |
+
}
|
| 73 |
+
})
|
| 74 |
+
.await
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
async fn proxy_request_handler(
|
| 78 |
+
&mut self,
|
| 79 |
+
request: RequestData,
|
| 80 |
+
_config: &ServerConfig,
|
| 81 |
+
_socket_data: &SocketData,
|
| 82 |
+
_error_logger: &ErrorLogger,
|
| 83 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 84 |
+
Ok(
|
| 85 |
+
ResponseData::builder(request)
|
| 86 |
+
.status(StatusCode::NOT_IMPLEMENTED)
|
| 87 |
+
.build(),
|
| 88 |
+
)
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
async fn response_modifying_handler(
|
| 92 |
+
&mut self,
|
| 93 |
+
response: HyperResponse,
|
| 94 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 95 |
+
Ok(response)
|
| 96 |
+
}
|
| 97 |
+
|
| 98 |
+
async fn proxy_response_modifying_handler(
|
| 99 |
+
&mut self,
|
| 100 |
+
response: HyperResponse,
|
| 101 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 102 |
+
Ok(response)
|
| 103 |
+
}
|
| 104 |
+
|
| 105 |
+
async fn connect_proxy_request_handler(
|
| 106 |
+
&mut self,
|
| 107 |
+
_upgraded_request: HyperUpgraded,
|
| 108 |
+
_connect_address: &str,
|
| 109 |
+
_config: &ServerConfig,
|
| 110 |
+
_socket_data: &SocketData,
|
| 111 |
+
_error_logger: &ErrorLogger,
|
| 112 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 113 |
+
Ok(())
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 117 |
+
false
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
async fn websocket_request_handler(
|
| 121 |
+
&mut self,
|
| 122 |
+
_websocket: HyperWebsocket,
|
| 123 |
+
_uri: &hyper::Uri,
|
| 124 |
+
_config: &ServerConfig,
|
| 125 |
+
_socket_data: &SocketData,
|
| 126 |
+
_error_logger: &ErrorLogger,
|
| 127 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 128 |
+
Ok(())
|
| 129 |
+
}
|
| 130 |
+
|
| 131 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 132 |
+
false
|
| 133 |
+
}
|
| 134 |
+
}
|
ferron/src/modules/non_standard_codes.rs
ADDED
|
@@ -0,0 +1,537 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::error::Error;
|
| 2 |
+
use std::sync::Arc;
|
| 3 |
+
use std::time::Duration;
|
| 4 |
+
|
| 5 |
+
use crate::ferron_util::ip_blocklist::IpBlockList;
|
| 6 |
+
use crate::ferron_util::ip_match::ip_match;
|
| 7 |
+
use crate::ferron_util::match_hostname::match_hostname;
|
| 8 |
+
use crate::ferron_util::match_location::match_location;
|
| 9 |
+
use crate::ferron_util::non_standard_code_structs::{
|
| 10 |
+
NonStandardCode, NonStandardCodesLocationWrap, NonStandardCodesWrap,
|
| 11 |
+
};
|
| 12 |
+
use crate::ferron_util::ttl_cache::TtlCache;
|
| 13 |
+
|
| 14 |
+
use crate::ferron_common::{
|
| 15 |
+
ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 16 |
+
ServerModuleHandlers, SocketData,
|
| 17 |
+
};
|
| 18 |
+
use crate::ferron_common::{HyperUpgraded, WithRuntime};
|
| 19 |
+
use async_trait::async_trait;
|
| 20 |
+
use base64::{engine::general_purpose, Engine};
|
| 21 |
+
use fancy_regex::RegexBuilder;
|
| 22 |
+
use http_body_util::{BodyExt, Empty};
|
| 23 |
+
use hyper::header::HeaderValue;
|
| 24 |
+
use hyper::{header, HeaderMap, Response, StatusCode};
|
| 25 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 26 |
+
use password_auth::verify_password;
|
| 27 |
+
use tokio::runtime::Handle;
|
| 28 |
+
use tokio::sync::RwLock;
|
| 29 |
+
use yaml_rust2::Yaml;
|
| 30 |
+
|
| 31 |
+
fn non_standard_codes_config_init(
|
| 32 |
+
non_standard_codes_list: &[Yaml],
|
| 33 |
+
) -> Result<Vec<NonStandardCode>, anyhow::Error> {
|
| 34 |
+
let non_standard_codes_list_iter = non_standard_codes_list.iter();
|
| 35 |
+
let mut non_standard_codes_list_vec = Vec::new();
|
| 36 |
+
for non_standard_codes_list_entry in non_standard_codes_list_iter {
|
| 37 |
+
let status_code: u16 = match non_standard_codes_list_entry["scode"].as_i64() {
|
| 38 |
+
Some(scode) => scode.try_into()?,
|
| 39 |
+
None => {
|
| 40 |
+
return Err(anyhow::anyhow!(
|
| 41 |
+
"Non-standard codes must include a status code"
|
| 42 |
+
));
|
| 43 |
+
}
|
| 44 |
+
};
|
| 45 |
+
let regex = match non_standard_codes_list_entry["regex"].as_str() {
|
| 46 |
+
Some(regex_str) => match RegexBuilder::new(regex_str)
|
| 47 |
+
.case_insensitive(cfg!(windows))
|
| 48 |
+
.build()
|
| 49 |
+
{
|
| 50 |
+
Ok(regex) => Some(regex),
|
| 51 |
+
Err(err) => {
|
| 52 |
+
return Err(anyhow::anyhow!(
|
| 53 |
+
"Invalid non-standard code regular expression: {}",
|
| 54 |
+
err.to_string()
|
| 55 |
+
));
|
| 56 |
+
}
|
| 57 |
+
},
|
| 58 |
+
None => None,
|
| 59 |
+
};
|
| 60 |
+
let url = non_standard_codes_list_entry["url"]
|
| 61 |
+
.as_str()
|
| 62 |
+
.map(|s| s.to_string());
|
| 63 |
+
|
| 64 |
+
if regex.is_none() && url.is_none() {
|
| 65 |
+
return Err(anyhow::anyhow!(
|
| 66 |
+
"Non-standard codes must either include URL or a matching regular expression"
|
| 67 |
+
));
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
let location = non_standard_codes_list_entry["location"]
|
| 71 |
+
.as_str()
|
| 72 |
+
.map(|s| s.to_string());
|
| 73 |
+
let realm = non_standard_codes_list_entry["realm"]
|
| 74 |
+
.as_str()
|
| 75 |
+
.map(|s| s.to_string());
|
| 76 |
+
let disable_brute_force_protection = non_standard_codes_list_entry["disableBruteProtection"]
|
| 77 |
+
.as_bool()
|
| 78 |
+
.unwrap_or(false);
|
| 79 |
+
let user_list = match non_standard_codes_list_entry["userList"].as_vec() {
|
| 80 |
+
Some(userlist) => {
|
| 81 |
+
let mut new_userlist = Vec::new();
|
| 82 |
+
for user_yaml in userlist.iter() {
|
| 83 |
+
if let Some(user) = user_yaml.as_str() {
|
| 84 |
+
new_userlist.push(user.to_string());
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
Some(new_userlist)
|
| 88 |
+
}
|
| 89 |
+
None => None,
|
| 90 |
+
};
|
| 91 |
+
let users = match non_standard_codes_list_entry["users"].as_vec() {
|
| 92 |
+
Some(users_vec) => {
|
| 93 |
+
let mut users_str_vec = Vec::new();
|
| 94 |
+
for user_yaml in users_vec.iter() {
|
| 95 |
+
if let Some(user) = user_yaml.as_str() {
|
| 96 |
+
users_str_vec.push(user);
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
let mut users_init = IpBlockList::new();
|
| 101 |
+
users_init.load_from_vec(users_str_vec);
|
| 102 |
+
Some(users_init)
|
| 103 |
+
}
|
| 104 |
+
None => None,
|
| 105 |
+
};
|
| 106 |
+
non_standard_codes_list_vec.push(NonStandardCode::new(
|
| 107 |
+
status_code,
|
| 108 |
+
url,
|
| 109 |
+
regex,
|
| 110 |
+
location,
|
| 111 |
+
realm,
|
| 112 |
+
disable_brute_force_protection,
|
| 113 |
+
user_list,
|
| 114 |
+
users,
|
| 115 |
+
));
|
| 116 |
+
}
|
| 117 |
+
|
| 118 |
+
Ok(non_standard_codes_list_vec)
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
pub fn server_module_init(
|
| 122 |
+
config: &ServerConfig,
|
| 123 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 124 |
+
let mut global_non_standard_codes_list = Vec::new();
|
| 125 |
+
let mut host_non_standard_codes_lists = Vec::new();
|
| 126 |
+
if let Some(non_standard_codes_list_yaml) = config["global"]["nonStandardCodes"].as_vec() {
|
| 127 |
+
global_non_standard_codes_list = non_standard_codes_config_init(non_standard_codes_list_yaml)?;
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
if let Some(hosts) = config["hosts"].as_vec() {
|
| 131 |
+
for host_yaml in hosts.iter() {
|
| 132 |
+
let domain = host_yaml["domain"].as_str().map(String::from);
|
| 133 |
+
let ip = host_yaml["ip"].as_str().map(String::from);
|
| 134 |
+
let mut locations = Vec::new();
|
| 135 |
+
if let Some(locations_yaml) = host_yaml["locations"].as_vec() {
|
| 136 |
+
for location_yaml in locations_yaml.iter() {
|
| 137 |
+
if let Some(path_str) = location_yaml["path"].as_str() {
|
| 138 |
+
let path = String::from(path_str);
|
| 139 |
+
if let Some(non_standard_codes_list_yaml) = location_yaml["nonStandardCodes"].as_vec() {
|
| 140 |
+
locations.push(NonStandardCodesLocationWrap::new(
|
| 141 |
+
path,
|
| 142 |
+
non_standard_codes_config_init(non_standard_codes_list_yaml)?,
|
| 143 |
+
));
|
| 144 |
+
}
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
}
|
| 148 |
+
if let Some(non_standard_codes_list_yaml) = host_yaml["nonStandardCodes"].as_vec() {
|
| 149 |
+
host_non_standard_codes_lists.push(NonStandardCodesWrap::new(
|
| 150 |
+
domain,
|
| 151 |
+
ip,
|
| 152 |
+
non_standard_codes_config_init(non_standard_codes_list_yaml)?,
|
| 153 |
+
locations,
|
| 154 |
+
));
|
| 155 |
+
} else if !locations.is_empty() {
|
| 156 |
+
host_non_standard_codes_lists.push(NonStandardCodesWrap::new(
|
| 157 |
+
domain,
|
| 158 |
+
ip,
|
| 159 |
+
Vec::new(),
|
| 160 |
+
locations,
|
| 161 |
+
));
|
| 162 |
+
}
|
| 163 |
+
}
|
| 164 |
+
}
|
| 165 |
+
|
| 166 |
+
Ok(Box::new(NonStandardCodesModule::new(
|
| 167 |
+
Arc::new(global_non_standard_codes_list),
|
| 168 |
+
Arc::new(host_non_standard_codes_lists),
|
| 169 |
+
Arc::new(RwLock::new(TtlCache::new(Duration::new(300, 0)))),
|
| 170 |
+
)))
|
| 171 |
+
}
|
| 172 |
+
|
| 173 |
+
struct NonStandardCodesModule {
|
| 174 |
+
global_non_standard_codes_list: Arc<Vec<NonStandardCode>>,
|
| 175 |
+
host_non_standard_codes_lists: Arc<Vec<NonStandardCodesWrap>>,
|
| 176 |
+
brute_force_db: Arc<RwLock<TtlCache<String, u8>>>,
|
| 177 |
+
}
|
| 178 |
+
|
| 179 |
+
impl NonStandardCodesModule {
|
| 180 |
+
fn new(
|
| 181 |
+
global_non_standard_codes_list: Arc<Vec<NonStandardCode>>,
|
| 182 |
+
host_non_standard_codes_lists: Arc<Vec<NonStandardCodesWrap>>,
|
| 183 |
+
brute_force_db: Arc<RwLock<TtlCache<String, u8>>>,
|
| 184 |
+
) -> Self {
|
| 185 |
+
Self {
|
| 186 |
+
global_non_standard_codes_list,
|
| 187 |
+
host_non_standard_codes_lists,
|
| 188 |
+
brute_force_db,
|
| 189 |
+
}
|
| 190 |
+
}
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
impl ServerModule for NonStandardCodesModule {
|
| 194 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 195 |
+
Box::new(NonStandardCodesModuleHandlers {
|
| 196 |
+
global_non_standard_codes_list: self.global_non_standard_codes_list.clone(),
|
| 197 |
+
host_non_standard_codes_lists: self.host_non_standard_codes_lists.clone(),
|
| 198 |
+
brute_force_db: self.brute_force_db.clone(),
|
| 199 |
+
handle,
|
| 200 |
+
})
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
|
| 204 |
+
fn parse_basic_auth(auth_str: &str) -> Option<(String, String)> {
|
| 205 |
+
if let Some(base64_credentials) = auth_str.strip_prefix("Basic ") {
|
| 206 |
+
if let Ok(decoded) = general_purpose::STANDARD.decode(base64_credentials) {
|
| 207 |
+
if let Ok(decoded_str) = std::str::from_utf8(&decoded) {
|
| 208 |
+
let parts: Vec<&str> = decoded_str.splitn(2, ':').collect();
|
| 209 |
+
if parts.len() == 2 {
|
| 210 |
+
return Some((parts[0].to_string(), parts[1].to_string()));
|
| 211 |
+
}
|
| 212 |
+
}
|
| 213 |
+
}
|
| 214 |
+
}
|
| 215 |
+
None
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
struct NonStandardCodesModuleHandlers {
|
| 219 |
+
global_non_standard_codes_list: Arc<Vec<NonStandardCode>>,
|
| 220 |
+
host_non_standard_codes_lists: Arc<Vec<NonStandardCodesWrap>>,
|
| 221 |
+
brute_force_db: Arc<RwLock<TtlCache<String, u8>>>,
|
| 222 |
+
handle: Handle,
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
#[async_trait]
|
| 226 |
+
impl ServerModuleHandlers for NonStandardCodesModuleHandlers {
|
| 227 |
+
async fn request_handler(
|
| 228 |
+
&mut self,
|
| 229 |
+
request: RequestData,
|
| 230 |
+
config: &ServerConfig,
|
| 231 |
+
socket_data: &SocketData,
|
| 232 |
+
error_logger: &ErrorLogger,
|
| 233 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 234 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 235 |
+
let hyper_request = request.get_hyper_request();
|
| 236 |
+
let global_non_standard_codes_list = self.global_non_standard_codes_list.iter();
|
| 237 |
+
let empty_vector = Vec::new();
|
| 238 |
+
let another_empty_vector = Vec::new();
|
| 239 |
+
let mut host_non_standard_codes_list = empty_vector.iter();
|
| 240 |
+
let mut location_non_standard_codes_list = another_empty_vector.iter();
|
| 241 |
+
|
| 242 |
+
// Should have used a HashMap instead of iterating over an array for better performance...
|
| 243 |
+
for host_non_standard_codes_list_wrap in self.host_non_standard_codes_lists.iter() {
|
| 244 |
+
if match_hostname(
|
| 245 |
+
match &host_non_standard_codes_list_wrap.domain {
|
| 246 |
+
Some(value) => Some(value as &str),
|
| 247 |
+
None => None,
|
| 248 |
+
},
|
| 249 |
+
match hyper_request.headers().get(header::HOST) {
|
| 250 |
+
Some(value) => value.to_str().ok(),
|
| 251 |
+
None => None,
|
| 252 |
+
},
|
| 253 |
+
) && match &host_non_standard_codes_list_wrap.ip {
|
| 254 |
+
Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
|
| 255 |
+
None => true,
|
| 256 |
+
} {
|
| 257 |
+
host_non_standard_codes_list =
|
| 258 |
+
host_non_standard_codes_list_wrap.non_standard_codes.iter();
|
| 259 |
+
if let Ok(path_decoded) = urlencoding::decode(
|
| 260 |
+
request
|
| 261 |
+
.get_original_url()
|
| 262 |
+
.unwrap_or(request.get_hyper_request().uri())
|
| 263 |
+
.path(),
|
| 264 |
+
) {
|
| 265 |
+
for location_wrap in host_non_standard_codes_list_wrap.locations.iter() {
|
| 266 |
+
if match_location(&location_wrap.path, &path_decoded) {
|
| 267 |
+
location_non_standard_codes_list = location_wrap.non_standard_codes.iter();
|
| 268 |
+
break;
|
| 269 |
+
}
|
| 270 |
+
}
|
| 271 |
+
}
|
| 272 |
+
break;
|
| 273 |
+
}
|
| 274 |
+
}
|
| 275 |
+
|
| 276 |
+
let combined_non_standard_codes_list = global_non_standard_codes_list
|
| 277 |
+
.chain(host_non_standard_codes_list)
|
| 278 |
+
.chain(location_non_standard_codes_list);
|
| 279 |
+
|
| 280 |
+
let request_url = format!(
|
| 281 |
+
"{}{}",
|
| 282 |
+
hyper_request.uri().path(),
|
| 283 |
+
match hyper_request.uri().query() {
|
| 284 |
+
Some(query) => format!("?{}", query),
|
| 285 |
+
None => String::from(""),
|
| 286 |
+
}
|
| 287 |
+
);
|
| 288 |
+
|
| 289 |
+
let mut auth_user = None;
|
| 290 |
+
|
| 291 |
+
for non_standard_code in combined_non_standard_codes_list {
|
| 292 |
+
let mut redirect_url = None;
|
| 293 |
+
let mut url_matched = false;
|
| 294 |
+
|
| 295 |
+
if let Some(users) = &non_standard_code.users {
|
| 296 |
+
if !users.is_blocked(socket_data.remote_addr.ip()) {
|
| 297 |
+
// Don't process this non-standard code
|
| 298 |
+
continue;
|
| 299 |
+
}
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
if let Some(regex) = &non_standard_code.regex {
|
| 303 |
+
let regex_match_option = regex.find(&request_url)?;
|
| 304 |
+
if let Some(regex_match) = regex_match_option {
|
| 305 |
+
url_matched = true;
|
| 306 |
+
if non_standard_code.status_code == 301
|
| 307 |
+
|| non_standard_code.status_code == 302
|
| 308 |
+
|| non_standard_code.status_code == 307
|
| 309 |
+
|| non_standard_code.status_code == 308
|
| 310 |
+
{
|
| 311 |
+
let matched_text = regex_match.as_str();
|
| 312 |
+
if let Some(location) = &non_standard_code.location {
|
| 313 |
+
redirect_url = Some(regex.replace(matched_text, location).to_string());
|
| 314 |
+
}
|
| 315 |
+
}
|
| 316 |
+
}
|
| 317 |
+
}
|
| 318 |
+
|
| 319 |
+
if !url_matched {
|
| 320 |
+
if let Some(url) = &non_standard_code.url {
|
| 321 |
+
if url == hyper_request.uri().path() {
|
| 322 |
+
url_matched = true;
|
| 323 |
+
if non_standard_code.status_code == 301
|
| 324 |
+
|| non_standard_code.status_code == 302
|
| 325 |
+
|| non_standard_code.status_code == 307
|
| 326 |
+
|| non_standard_code.status_code == 308
|
| 327 |
+
{
|
| 328 |
+
if let Some(location) = &non_standard_code.location {
|
| 329 |
+
redirect_url = Some(format!(
|
| 330 |
+
"{}{}",
|
| 331 |
+
location,
|
| 332 |
+
match hyper_request.uri().query() {
|
| 333 |
+
Some(query) => format!("?{}", query),
|
| 334 |
+
None => String::from(""),
|
| 335 |
+
}
|
| 336 |
+
));
|
| 337 |
+
}
|
| 338 |
+
}
|
| 339 |
+
}
|
| 340 |
+
}
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
if url_matched {
|
| 344 |
+
match non_standard_code.status_code {
|
| 345 |
+
301 | 302 | 307 | 308 => {
|
| 346 |
+
return Ok(
|
| 347 |
+
ResponseData::builder(request)
|
| 348 |
+
.response(
|
| 349 |
+
Response::builder()
|
| 350 |
+
.status(StatusCode::from_u16(non_standard_code.status_code)?)
|
| 351 |
+
.header(header::LOCATION, redirect_url.unwrap_or(request_url))
|
| 352 |
+
.body(Empty::new().map_err(|e| match e {}).boxed())?,
|
| 353 |
+
)
|
| 354 |
+
.build(),
|
| 355 |
+
);
|
| 356 |
+
}
|
| 357 |
+
401 => {
|
| 358 |
+
let brute_force_db_key = socket_data.remote_addr.ip().to_string();
|
| 359 |
+
if !non_standard_code.disable_brute_force_protection {
|
| 360 |
+
let rwlock_read = self.brute_force_db.read().await;
|
| 361 |
+
let current_attempts = rwlock_read.get(&brute_force_db_key).unwrap_or(0);
|
| 362 |
+
if current_attempts >= 10 {
|
| 363 |
+
error_logger
|
| 364 |
+
.log(&format!(
|
| 365 |
+
"Too many failed authorization attempts for client \"{}\"",
|
| 366 |
+
socket_data.remote_addr.ip()
|
| 367 |
+
))
|
| 368 |
+
.await;
|
| 369 |
+
|
| 370 |
+
return Ok(
|
| 371 |
+
ResponseData::builder(request)
|
| 372 |
+
.status(StatusCode::TOO_MANY_REQUESTS)
|
| 373 |
+
.build(),
|
| 374 |
+
);
|
| 375 |
+
}
|
| 376 |
+
}
|
| 377 |
+
let mut header_map = HeaderMap::new();
|
| 378 |
+
header_map.insert(
|
| 379 |
+
header::WWW_AUTHENTICATE,
|
| 380 |
+
HeaderValue::from_str(&format!(
|
| 381 |
+
"Basic realm=\"{}\", charset=\"UTF-8\"",
|
| 382 |
+
non_standard_code
|
| 383 |
+
.realm
|
| 384 |
+
.clone()
|
| 385 |
+
.unwrap_or("Ferron HTTP Basic Authorization".to_string())
|
| 386 |
+
.replace("\\", "\\\\")
|
| 387 |
+
.replace("\"", "\\\"")
|
| 388 |
+
))?,
|
| 389 |
+
);
|
| 390 |
+
|
| 391 |
+
if let Some(authorization_header_value) =
|
| 392 |
+
hyper_request.headers().get(header::AUTHORIZATION)
|
| 393 |
+
{
|
| 394 |
+
let authorization_str = match authorization_header_value.to_str() {
|
| 395 |
+
Ok(str) => str,
|
| 396 |
+
Err(_) => {
|
| 397 |
+
return Ok(
|
| 398 |
+
ResponseData::builder(request)
|
| 399 |
+
.status(StatusCode::BAD_REQUEST)
|
| 400 |
+
.build(),
|
| 401 |
+
);
|
| 402 |
+
}
|
| 403 |
+
};
|
| 404 |
+
|
| 405 |
+
if let Some((username, password)) = parse_basic_auth(authorization_str) {
|
| 406 |
+
if let Some(users_vec_yaml) = config["users"].as_vec() {
|
| 407 |
+
let mut authorized_user = None;
|
| 408 |
+
for user_yaml in users_vec_yaml {
|
| 409 |
+
if let Some(username_db) = user_yaml["name"].as_str() {
|
| 410 |
+
if username_db != username {
|
| 411 |
+
continue;
|
| 412 |
+
}
|
| 413 |
+
if let Some(user_list) = &non_standard_code.user_list {
|
| 414 |
+
if !user_list.contains(&username) {
|
| 415 |
+
continue;
|
| 416 |
+
}
|
| 417 |
+
}
|
| 418 |
+
if let Some(password_hash_db) = user_yaml["pass"].as_str() {
|
| 419 |
+
let password_cloned = password.clone();
|
| 420 |
+
let password_hash_db_cloned = password_hash_db.to_string();
|
| 421 |
+
// Offload verifying the hash into a separate blocking thread.
|
| 422 |
+
let password_valid = tokio::task::spawn_blocking(move || {
|
| 423 |
+
verify_password(password_cloned, &password_hash_db_cloned).is_ok()
|
| 424 |
+
})
|
| 425 |
+
.await?;
|
| 426 |
+
if password_valid {
|
| 427 |
+
authorized_user = Some(&username);
|
| 428 |
+
break;
|
| 429 |
+
}
|
| 430 |
+
}
|
| 431 |
+
}
|
| 432 |
+
}
|
| 433 |
+
if let Some(authorized_user) = authorized_user {
|
| 434 |
+
auth_user = Some(authorized_user.to_owned());
|
| 435 |
+
continue;
|
| 436 |
+
}
|
| 437 |
+
}
|
| 438 |
+
|
| 439 |
+
if !non_standard_code.disable_brute_force_protection {
|
| 440 |
+
let mut rwlock_write = self.brute_force_db.write().await;
|
| 441 |
+
rwlock_write.cleanup();
|
| 442 |
+
let current_attempts = rwlock_write.get(&brute_force_db_key).unwrap_or(0);
|
| 443 |
+
rwlock_write.insert(brute_force_db_key, current_attempts + 1);
|
| 444 |
+
}
|
| 445 |
+
|
| 446 |
+
error_logger
|
| 447 |
+
.log(&format!(
|
| 448 |
+
"Authorization failed for user \"{}\" and client \"{}\"",
|
| 449 |
+
username,
|
| 450 |
+
socket_data.remote_addr.ip()
|
| 451 |
+
))
|
| 452 |
+
.await;
|
| 453 |
+
}
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
return Ok(
|
| 457 |
+
ResponseData::builder(request)
|
| 458 |
+
.status(StatusCode::UNAUTHORIZED)
|
| 459 |
+
.headers(header_map)
|
| 460 |
+
.build(),
|
| 461 |
+
);
|
| 462 |
+
}
|
| 463 |
+
_ => {
|
| 464 |
+
return Ok(
|
| 465 |
+
ResponseData::builder(request)
|
| 466 |
+
.status(StatusCode::from_u16(non_standard_code.status_code)?)
|
| 467 |
+
.build(),
|
| 468 |
+
)
|
| 469 |
+
}
|
| 470 |
+
}
|
| 471 |
+
}
|
| 472 |
+
}
|
| 473 |
+
|
| 474 |
+
if auth_user.is_some() {
|
| 475 |
+
let (hyper_request, _, original_url) = request.into_parts();
|
| 476 |
+
Ok(ResponseData::builder(RequestData::new(hyper_request, auth_user, original_url)).build())
|
| 477 |
+
} else {
|
| 478 |
+
Ok(ResponseData::builder(request).build())
|
| 479 |
+
}
|
| 480 |
+
})
|
| 481 |
+
.await
|
| 482 |
+
}
|
| 483 |
+
|
| 484 |
+
async fn proxy_request_handler(
|
| 485 |
+
&mut self,
|
| 486 |
+
request: RequestData,
|
| 487 |
+
_config: &ServerConfig,
|
| 488 |
+
_socket_data: &SocketData,
|
| 489 |
+
_error_logger: &ErrorLogger,
|
| 490 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 491 |
+
Ok(ResponseData::builder(request).build())
|
| 492 |
+
}
|
| 493 |
+
|
| 494 |
+
async fn response_modifying_handler(
|
| 495 |
+
&mut self,
|
| 496 |
+
response: HyperResponse,
|
| 497 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 498 |
+
Ok(response)
|
| 499 |
+
}
|
| 500 |
+
|
| 501 |
+
async fn proxy_response_modifying_handler(
|
| 502 |
+
&mut self,
|
| 503 |
+
response: HyperResponse,
|
| 504 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 505 |
+
Ok(response)
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
async fn connect_proxy_request_handler(
|
| 509 |
+
&mut self,
|
| 510 |
+
_upgraded_request: HyperUpgraded,
|
| 511 |
+
_connect_address: &str,
|
| 512 |
+
_config: &ServerConfig,
|
| 513 |
+
_socket_data: &SocketData,
|
| 514 |
+
_error_logger: &ErrorLogger,
|
| 515 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 516 |
+
Ok(())
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 520 |
+
false
|
| 521 |
+
}
|
| 522 |
+
|
| 523 |
+
async fn websocket_request_handler(
|
| 524 |
+
&mut self,
|
| 525 |
+
_websocket: HyperWebsocket,
|
| 526 |
+
_uri: &hyper::Uri,
|
| 527 |
+
_config: &ServerConfig,
|
| 528 |
+
_socket_data: &SocketData,
|
| 529 |
+
_error_logger: &ErrorLogger,
|
| 530 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 531 |
+
Ok(())
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 535 |
+
false
|
| 536 |
+
}
|
| 537 |
+
}
|
ferron/src/modules/redirect_trailing_slashes.rs
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::error::Error;
|
| 2 |
+
use std::path::Path;
|
| 3 |
+
use std::sync::Arc;
|
| 4 |
+
use std::time::Duration;
|
| 5 |
+
|
| 6 |
+
use crate::ferron_common::{
|
| 7 |
+
ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 8 |
+
ServerModuleHandlers, SocketData,
|
| 9 |
+
};
|
| 10 |
+
use crate::ferron_common::{HyperUpgraded, WithRuntime};
|
| 11 |
+
use async_trait::async_trait;
|
| 12 |
+
use http_body_util::{BodyExt, Empty};
|
| 13 |
+
use hyper::{header, Response, StatusCode};
|
| 14 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 15 |
+
use tokio::fs;
|
| 16 |
+
use tokio::runtime::Handle;
|
| 17 |
+
use tokio::sync::RwLock;
|
| 18 |
+
|
| 19 |
+
use crate::ferron_util::ttl_cache::TtlCache;
|
| 20 |
+
|
| 21 |
+
pub fn server_module_init(
|
| 22 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 23 |
+
let cache = Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(100))));
|
| 24 |
+
Ok(Box::new(RedirectTrailingSlashesModule::new(cache)))
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
struct RedirectTrailingSlashesModule {
|
| 28 |
+
cache: Arc<RwLock<TtlCache<String, bool>>>,
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
impl RedirectTrailingSlashesModule {
|
| 32 |
+
fn new(cache: Arc<RwLock<TtlCache<String, bool>>>) -> Self {
|
| 33 |
+
Self { cache }
|
| 34 |
+
}
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
impl ServerModule for RedirectTrailingSlashesModule {
|
| 38 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 39 |
+
Box::new(RedirectTrailingSlashesModuleHandlers {
|
| 40 |
+
cache: self.cache.clone(),
|
| 41 |
+
handle,
|
| 42 |
+
})
|
| 43 |
+
}
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
struct RedirectTrailingSlashesModuleHandlers {
|
| 47 |
+
cache: Arc<RwLock<TtlCache<String, bool>>>,
|
| 48 |
+
handle: Handle,
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
#[async_trait]
|
| 52 |
+
impl ServerModuleHandlers for RedirectTrailingSlashesModuleHandlers {
|
| 53 |
+
async fn request_handler(
|
| 54 |
+
&mut self,
|
| 55 |
+
request: RequestData,
|
| 56 |
+
config: &ServerConfig,
|
| 57 |
+
_socket_data: &SocketData,
|
| 58 |
+
_error_logger: &ErrorLogger,
|
| 59 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 60 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 61 |
+
if config["disableTrailingSlashRedirects"].as_bool() != Some(true) {
|
| 62 |
+
if let Some(wwwroot) = config["wwwroot"].as_str() {
|
| 63 |
+
let hyper_request = request.get_hyper_request();
|
| 64 |
+
|
| 65 |
+
let request_path = hyper_request.uri().path();
|
| 66 |
+
let request_query = hyper_request.uri().query();
|
| 67 |
+
let mut request_path_bytes = request_path.bytes();
|
| 68 |
+
if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
|
| 69 |
+
return Ok(
|
| 70 |
+
ResponseData::builder(request)
|
| 71 |
+
.status(StatusCode::BAD_REQUEST)
|
| 72 |
+
.build(),
|
| 73 |
+
);
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
match request_path_bytes.last() {
|
| 77 |
+
Some(b'/') | None => {
|
| 78 |
+
return Ok(ResponseData::builder(request).build());
|
| 79 |
+
}
|
| 80 |
+
_ => {
|
| 81 |
+
let cache_key = format!(
|
| 82 |
+
"{}{}{}",
|
| 83 |
+
match config["ip"].as_str() {
|
| 84 |
+
Some(ip) => format!("{}-", ip),
|
| 85 |
+
None => String::from(""),
|
| 86 |
+
},
|
| 87 |
+
match config["domain"].as_str() {
|
| 88 |
+
Some(domain) => format!("{}-", domain),
|
| 89 |
+
None => String::from(""),
|
| 90 |
+
},
|
| 91 |
+
request_path
|
| 92 |
+
);
|
| 93 |
+
|
| 94 |
+
let read_rwlock = self.cache.read().await;
|
| 95 |
+
if let Some(is_directory) = read_rwlock.get(&cache_key) {
|
| 96 |
+
drop(read_rwlock);
|
| 97 |
+
if is_directory {
|
| 98 |
+
let new_request_uri = format!(
|
| 99 |
+
"{}/{}",
|
| 100 |
+
request_path,
|
| 101 |
+
match request_query {
|
| 102 |
+
Some(query) => format!("?{}", query),
|
| 103 |
+
None => String::from(""),
|
| 104 |
+
}
|
| 105 |
+
);
|
| 106 |
+
return Ok(
|
| 107 |
+
ResponseData::builder(request)
|
| 108 |
+
.response(
|
| 109 |
+
Response::builder()
|
| 110 |
+
.status(StatusCode::MOVED_PERMANENTLY)
|
| 111 |
+
.header(header::LOCATION, new_request_uri)
|
| 112 |
+
.body(Empty::new().map_err(|e| match e {}).boxed())?,
|
| 113 |
+
)
|
| 114 |
+
.build(),
|
| 115 |
+
);
|
| 116 |
+
}
|
| 117 |
+
} else {
|
| 118 |
+
drop(read_rwlock);
|
| 119 |
+
|
| 120 |
+
let path = Path::new(wwwroot);
|
| 121 |
+
let mut relative_path = &request_path[1..];
|
| 122 |
+
while relative_path.as_bytes().first().copied() == Some(b'/') {
|
| 123 |
+
relative_path = &relative_path[1..];
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
let decoded_relative_path = match urlencoding::decode(relative_path) {
|
| 127 |
+
Ok(path) => path.to_string(),
|
| 128 |
+
Err(_) => {
|
| 129 |
+
return Ok(
|
| 130 |
+
ResponseData::builder(request)
|
| 131 |
+
.status(StatusCode::BAD_REQUEST)
|
| 132 |
+
.build(),
|
| 133 |
+
);
|
| 134 |
+
}
|
| 135 |
+
};
|
| 136 |
+
|
| 137 |
+
let joined_pathbuf = path.join(decoded_relative_path);
|
| 138 |
+
|
| 139 |
+
match fs::metadata(joined_pathbuf).await {
|
| 140 |
+
Ok(metadata) => {
|
| 141 |
+
let is_directory = metadata.is_dir();
|
| 142 |
+
let mut write_rwlock = self.cache.write().await;
|
| 143 |
+
write_rwlock.cleanup();
|
| 144 |
+
write_rwlock.insert(cache_key, is_directory);
|
| 145 |
+
if is_directory {
|
| 146 |
+
let new_request_uri = format!(
|
| 147 |
+
"{}/{}",
|
| 148 |
+
request_path,
|
| 149 |
+
match request_query {
|
| 150 |
+
Some(query) => format!("?{}", query),
|
| 151 |
+
None => String::from(""),
|
| 152 |
+
}
|
| 153 |
+
);
|
| 154 |
+
return Ok(
|
| 155 |
+
ResponseData::builder(request)
|
| 156 |
+
.response(
|
| 157 |
+
Response::builder()
|
| 158 |
+
.status(StatusCode::MOVED_PERMANENTLY)
|
| 159 |
+
.header(header::LOCATION, new_request_uri)
|
| 160 |
+
.body(Empty::new().map_err(|e| match e {}).boxed())?,
|
| 161 |
+
)
|
| 162 |
+
.build(),
|
| 163 |
+
);
|
| 164 |
+
}
|
| 165 |
+
}
|
| 166 |
+
Err(_) => {
|
| 167 |
+
let mut write_rwlock = self.cache.write().await;
|
| 168 |
+
write_rwlock.cleanup();
|
| 169 |
+
write_rwlock.insert(cache_key, false);
|
| 170 |
+
}
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
};
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
Ok(ResponseData::builder(request).build())
|
| 178 |
+
})
|
| 179 |
+
.await
|
| 180 |
+
}
|
| 181 |
+
|
| 182 |
+
async fn proxy_request_handler(
|
| 183 |
+
&mut self,
|
| 184 |
+
request: RequestData,
|
| 185 |
+
_config: &ServerConfig,
|
| 186 |
+
_socket_data: &SocketData,
|
| 187 |
+
_error_logger: &ErrorLogger,
|
| 188 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 189 |
+
Ok(ResponseData::builder(request).build())
|
| 190 |
+
}
|
| 191 |
+
|
| 192 |
+
async fn response_modifying_handler(
|
| 193 |
+
&mut self,
|
| 194 |
+
response: HyperResponse,
|
| 195 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 196 |
+
Ok(response)
|
| 197 |
+
}
|
| 198 |
+
|
| 199 |
+
async fn proxy_response_modifying_handler(
|
| 200 |
+
&mut self,
|
| 201 |
+
response: HyperResponse,
|
| 202 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 203 |
+
Ok(response)
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
async fn connect_proxy_request_handler(
|
| 207 |
+
&mut self,
|
| 208 |
+
_upgraded_request: HyperUpgraded,
|
| 209 |
+
_connect_address: &str,
|
| 210 |
+
_config: &ServerConfig,
|
| 211 |
+
_socket_data: &SocketData,
|
| 212 |
+
_error_logger: &ErrorLogger,
|
| 213 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 214 |
+
Ok(())
|
| 215 |
+
}
|
| 216 |
+
|
| 217 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 218 |
+
false
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
async fn websocket_request_handler(
|
| 222 |
+
&mut self,
|
| 223 |
+
_websocket: HyperWebsocket,
|
| 224 |
+
_uri: &hyper::Uri,
|
| 225 |
+
_config: &ServerConfig,
|
| 226 |
+
_socket_data: &SocketData,
|
| 227 |
+
_error_logger: &ErrorLogger,
|
| 228 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 229 |
+
Ok(())
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 233 |
+
false
|
| 234 |
+
}
|
| 235 |
+
}
|
ferron/src/modules/redirects.rs
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::error::Error;
|
| 2 |
+
|
| 3 |
+
use crate::ferron_common::{
|
| 4 |
+
ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 5 |
+
ServerModuleHandlers, SocketData,
|
| 6 |
+
};
|
| 7 |
+
use crate::ferron_common::{HyperUpgraded, WithRuntime};
|
| 8 |
+
use async_trait::async_trait;
|
| 9 |
+
use http_body_util::{BodyExt, Empty};
|
| 10 |
+
use hyper::{header, Response, StatusCode, Uri};
|
| 11 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 12 |
+
use tokio::runtime::Handle;
|
| 13 |
+
|
| 14 |
+
struct RedirectsModule;
|
| 15 |
+
|
| 16 |
+
pub fn server_module_init(
|
| 17 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 18 |
+
Ok(Box::new(RedirectsModule::new()))
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
impl RedirectsModule {
|
| 22 |
+
fn new() -> Self {
|
| 23 |
+
Self
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
impl ServerModule for RedirectsModule {
|
| 28 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 29 |
+
Box::new(RedirectsModuleHandlers { handle })
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
struct RedirectsModuleHandlers {
|
| 33 |
+
handle: Handle,
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
#[async_trait]
|
| 37 |
+
impl ServerModuleHandlers for RedirectsModuleHandlers {
|
| 38 |
+
async fn request_handler(
|
| 39 |
+
&mut self,
|
| 40 |
+
request: RequestData,
|
| 41 |
+
config: &ServerConfig,
|
| 42 |
+
socket_data: &SocketData,
|
| 43 |
+
_error_logger: &ErrorLogger,
|
| 44 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 45 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 46 |
+
let hyper_request = request.get_hyper_request();
|
| 47 |
+
|
| 48 |
+
if config["secure"].as_bool() == Some(true)
|
| 49 |
+
&& !socket_data.encrypted
|
| 50 |
+
&& config["disableNonEncryptedServer"].as_bool() != Some(true)
|
| 51 |
+
&& config["disableToHTTPSRedirect"].as_bool() != Some(true)
|
| 52 |
+
{
|
| 53 |
+
let host_header_option = hyper_request.headers().get(header::HOST);
|
| 54 |
+
let host_header = match host_header_option {
|
| 55 |
+
Some(header_data) => header_data.to_str()?,
|
| 56 |
+
None => {
|
| 57 |
+
return Ok(
|
| 58 |
+
ResponseData::builder(request)
|
| 59 |
+
.status(StatusCode::BAD_REQUEST)
|
| 60 |
+
.build(),
|
| 61 |
+
)
|
| 62 |
+
}
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
let path_and_query_option = hyper_request.uri().path_and_query();
|
| 66 |
+
let path_and_query = match path_and_query_option {
|
| 67 |
+
Some(path_and_query) => path_and_query.to_string(),
|
| 68 |
+
None => {
|
| 69 |
+
return Ok(
|
| 70 |
+
ResponseData::builder(request)
|
| 71 |
+
.status(StatusCode::BAD_REQUEST)
|
| 72 |
+
.build(),
|
| 73 |
+
)
|
| 74 |
+
}
|
| 75 |
+
};
|
| 76 |
+
|
| 77 |
+
let mut parts: Vec<&str> = host_header.split(':').collect();
|
| 78 |
+
|
| 79 |
+
if parts.len() > 1
|
| 80 |
+
&& !(parts[0].starts_with('[')
|
| 81 |
+
&& parts
|
| 82 |
+
.last()
|
| 83 |
+
.map(|part| part.ends_with(']'))
|
| 84 |
+
.unwrap_or(false))
|
| 85 |
+
{
|
| 86 |
+
parts.pop();
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
let host_name = parts.join(":");
|
| 90 |
+
|
| 91 |
+
let new_uri = Uri::builder()
|
| 92 |
+
.scheme("https")
|
| 93 |
+
.authority(match config["sport"].as_i64() {
|
| 94 |
+
None | Some(443) => host_name,
|
| 95 |
+
Some(port) => format!("{}:{}", host_name, port),
|
| 96 |
+
})
|
| 97 |
+
.path_and_query(path_and_query)
|
| 98 |
+
.build()?;
|
| 99 |
+
|
| 100 |
+
return Ok(
|
| 101 |
+
ResponseData::builder(request)
|
| 102 |
+
.response(
|
| 103 |
+
Response::builder()
|
| 104 |
+
.status(StatusCode::MOVED_PERMANENTLY)
|
| 105 |
+
.header(header::LOCATION, new_uri.to_string())
|
| 106 |
+
.body(Empty::new().map_err(|e| match e {}).boxed())?,
|
| 107 |
+
)
|
| 108 |
+
.build(),
|
| 109 |
+
);
|
| 110 |
+
}
|
| 111 |
+
|
| 112 |
+
let domain_yaml = &config["domain"];
|
| 113 |
+
let domain = domain_yaml.as_str();
|
| 114 |
+
|
| 115 |
+
if let Some(domain) = domain {
|
| 116 |
+
if config["wwwredirect"].as_bool() == Some(true) {
|
| 117 |
+
// Even more code rewritten from SVR.JS...
|
| 118 |
+
if let Some(host_header_value) = hyper_request.headers().get(header::HOST) {
|
| 119 |
+
let host_header = host_header_value.to_str()?;
|
| 120 |
+
|
| 121 |
+
let path_and_query_option = hyper_request.uri().path_and_query();
|
| 122 |
+
let path_and_query = match path_and_query_option {
|
| 123 |
+
Some(path_and_query) => path_and_query.to_string(),
|
| 124 |
+
None => {
|
| 125 |
+
return Ok(
|
| 126 |
+
ResponseData::builder(request)
|
| 127 |
+
.status(StatusCode::BAD_REQUEST)
|
| 128 |
+
.build(),
|
| 129 |
+
)
|
| 130 |
+
}
|
| 131 |
+
};
|
| 132 |
+
|
| 133 |
+
let mut parts: Vec<&str> = host_header.split(':').collect();
|
| 134 |
+
let mut host_port: Option<&str> = None;
|
| 135 |
+
|
| 136 |
+
if parts.len() > 1
|
| 137 |
+
&& !(parts[0].starts_with('[')
|
| 138 |
+
&& parts
|
| 139 |
+
.last()
|
| 140 |
+
.map(|part| part.ends_with(']'))
|
| 141 |
+
.unwrap_or(false))
|
| 142 |
+
{
|
| 143 |
+
host_port = parts.pop();
|
| 144 |
+
}
|
| 145 |
+
|
| 146 |
+
let host_name = parts.join(":");
|
| 147 |
+
|
| 148 |
+
if host_name == domain && !host_name.starts_with("www.") {
|
| 149 |
+
let new_uri = Uri::builder()
|
| 150 |
+
.scheme(match socket_data.encrypted {
|
| 151 |
+
true => "https",
|
| 152 |
+
false => "http",
|
| 153 |
+
})
|
| 154 |
+
.authority(match host_port {
|
| 155 |
+
Some(port) => format!("www.{}:{}", host_name, port),
|
| 156 |
+
None => host_name,
|
| 157 |
+
})
|
| 158 |
+
.path_and_query(path_and_query)
|
| 159 |
+
.build()?;
|
| 160 |
+
|
| 161 |
+
return Ok(
|
| 162 |
+
ResponseData::builder(request)
|
| 163 |
+
.response(
|
| 164 |
+
Response::builder()
|
| 165 |
+
.status(StatusCode::MOVED_PERMANENTLY)
|
| 166 |
+
.header(header::LOCATION, new_uri.to_string())
|
| 167 |
+
.body(Empty::new().map_err(|e| match e {}).boxed())?,
|
| 168 |
+
)
|
| 169 |
+
.build(),
|
| 170 |
+
);
|
| 171 |
+
}
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
Ok(ResponseData::builder(request).build())
|
| 177 |
+
})
|
| 178 |
+
.await
|
| 179 |
+
}
|
| 180 |
+
|
| 181 |
+
async fn proxy_request_handler(
|
| 182 |
+
&mut self,
|
| 183 |
+
request: RequestData,
|
| 184 |
+
config: &ServerConfig,
|
| 185 |
+
socket_data: &SocketData,
|
| 186 |
+
_error_logger: &ErrorLogger,
|
| 187 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 188 |
+
if config["secure"].as_bool() == Some(true)
|
| 189 |
+
&& !socket_data.encrypted
|
| 190 |
+
&& config["disableNonEncryptedServer"].as_bool() != Some(true)
|
| 191 |
+
&& config["disableToHTTPSRedirect"].as_bool() != Some(true)
|
| 192 |
+
{
|
| 193 |
+
return Ok(
|
| 194 |
+
ResponseData::builder(request)
|
| 195 |
+
.status(StatusCode::NOT_IMPLEMENTED)
|
| 196 |
+
.build(),
|
| 197 |
+
);
|
| 198 |
+
}
|
| 199 |
+
Ok(ResponseData::builder(request).build())
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
async fn response_modifying_handler(
|
| 203 |
+
&mut self,
|
| 204 |
+
response: HyperResponse,
|
| 205 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 206 |
+
Ok(response)
|
| 207 |
+
}
|
| 208 |
+
|
| 209 |
+
async fn proxy_response_modifying_handler(
|
| 210 |
+
&mut self,
|
| 211 |
+
response: HyperResponse,
|
| 212 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 213 |
+
Ok(response)
|
| 214 |
+
}
|
| 215 |
+
|
| 216 |
+
async fn connect_proxy_request_handler(
|
| 217 |
+
&mut self,
|
| 218 |
+
_upgraded_request: HyperUpgraded,
|
| 219 |
+
_connect_address: &str,
|
| 220 |
+
_config: &ServerConfig,
|
| 221 |
+
_socket_data: &SocketData,
|
| 222 |
+
_error_logger: &ErrorLogger,
|
| 223 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 224 |
+
Ok(())
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 228 |
+
false
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
async fn websocket_request_handler(
|
| 232 |
+
&mut self,
|
| 233 |
+
_websocket: HyperWebsocket,
|
| 234 |
+
_uri: &hyper::Uri,
|
| 235 |
+
_config: &ServerConfig,
|
| 236 |
+
_socket_data: &SocketData,
|
| 237 |
+
_error_logger: &ErrorLogger,
|
| 238 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 239 |
+
Ok(())
|
| 240 |
+
}
|
| 241 |
+
|
| 242 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 243 |
+
false
|
| 244 |
+
}
|
| 245 |
+
}
|
ferron/src/modules/static_file_serving.rs
ADDED
|
@@ -0,0 +1,928 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::error::Error;
|
| 2 |
+
use std::fmt::Write;
|
| 3 |
+
use std::io::SeekFrom;
|
| 4 |
+
use std::path::{Path, PathBuf};
|
| 5 |
+
use std::str::FromStr;
|
| 6 |
+
use std::sync::Arc;
|
| 7 |
+
use std::time::Duration;
|
| 8 |
+
|
| 9 |
+
use crate::ferron_common::{
|
| 10 |
+
ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 11 |
+
ServerModuleHandlers, SocketData,
|
| 12 |
+
};
|
| 13 |
+
use crate::ferron_common::{HyperUpgraded, WithRuntime};
|
| 14 |
+
use async_compression::tokio::bufread::{BrotliEncoder, DeflateEncoder, GzipEncoder, ZstdEncoder};
|
| 15 |
+
use async_compression::zstd::CParameter;
|
| 16 |
+
use async_compression::Level;
|
| 17 |
+
use async_trait::async_trait;
|
| 18 |
+
use chrono::offset::Local;
|
| 19 |
+
use chrono::DateTime;
|
| 20 |
+
use futures_util::TryStreamExt;
|
| 21 |
+
use hashlink::LruCache;
|
| 22 |
+
use http::HeaderValue;
|
| 23 |
+
use http_body_util::{BodyExt, Empty, Full, StreamBody};
|
| 24 |
+
use hyper::body::Bytes;
|
| 25 |
+
use hyper::{body::Frame, Response, StatusCode};
|
| 26 |
+
use hyper::{header, HeaderMap, Method};
|
| 27 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 28 |
+
use sha2::{Digest, Sha256};
|
| 29 |
+
use tokio::fs;
|
| 30 |
+
use tokio::io::{AsyncReadExt, AsyncSeekExt, BufReader};
|
| 31 |
+
use tokio::runtime::Handle;
|
| 32 |
+
use tokio::sync::RwLock;
|
| 33 |
+
use tokio_util::io::ReaderStream;
|
| 34 |
+
|
| 35 |
+
use crate::ferron_util::generate_directory_listing::generate_directory_listing;
|
| 36 |
+
use crate::ferron_util::ttl_cache::TtlCache;
|
| 37 |
+
|
| 38 |
+
pub fn server_module_init(
|
| 39 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 40 |
+
let pathbuf_cache = Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(100))));
|
| 41 |
+
let etag_cache = Arc::new(RwLock::new(LruCache::new(1000)));
|
| 42 |
+
Ok(Box::new(StaticFileServingModule::new(
|
| 43 |
+
pathbuf_cache,
|
| 44 |
+
etag_cache,
|
| 45 |
+
)))
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
struct StaticFileServingModule {
|
| 49 |
+
pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
|
| 50 |
+
etag_cache: Arc<RwLock<LruCache<String, String>>>,
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
impl StaticFileServingModule {
|
| 54 |
+
fn new(
|
| 55 |
+
pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
|
| 56 |
+
etag_cache: Arc<RwLock<LruCache<String, String>>>,
|
| 57 |
+
) -> Self {
|
| 58 |
+
Self {
|
| 59 |
+
pathbuf_cache,
|
| 60 |
+
etag_cache,
|
| 61 |
+
}
|
| 62 |
+
}
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
impl ServerModule for StaticFileServingModule {
|
| 66 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 67 |
+
Box::new(StaticFileServingModuleHandlers {
|
| 68 |
+
pathbuf_cache: self.pathbuf_cache.clone(),
|
| 69 |
+
etag_cache: self.etag_cache.clone(),
|
| 70 |
+
handle,
|
| 71 |
+
})
|
| 72 |
+
}
|
| 73 |
+
}
|
| 74 |
+
struct StaticFileServingModuleHandlers {
|
| 75 |
+
pathbuf_cache: Arc<RwLock<TtlCache<String, PathBuf>>>,
|
| 76 |
+
etag_cache: Arc<RwLock<LruCache<String, String>>>,
|
| 77 |
+
handle: Handle,
|
| 78 |
+
}
|
| 79 |
+
|
| 80 |
+
fn parse_range_header(range_str: &str, default_end: u64) -> Option<(u64, u64)> {
|
| 81 |
+
if let Some(range_part) = range_str.strip_prefix("bytes=") {
|
| 82 |
+
let parts: Vec<&str> = range_part.split('-').collect();
|
| 83 |
+
if parts.len() == 2 {
|
| 84 |
+
if parts[0].is_empty() {
|
| 85 |
+
if let Ok(end) = u64::from_str(parts[1]) {
|
| 86 |
+
return Some((default_end - end + 1, default_end));
|
| 87 |
+
}
|
| 88 |
+
} else if parts[1].is_empty() {
|
| 89 |
+
if let Ok(start) = u64::from_str(parts[0]) {
|
| 90 |
+
return Some((start, default_end));
|
| 91 |
+
}
|
| 92 |
+
} else if !parts[0].is_empty() && !parts[1].is_empty() {
|
| 93 |
+
if let (Ok(start), Ok(end)) = (u64::from_str(parts[0]), u64::from_str(parts[1])) {
|
| 94 |
+
return Some((start, end));
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
None
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
fn extract_etag_inner(input: &str) -> Option<String> {
|
| 103 |
+
// Remove the surrounding double quotes
|
| 104 |
+
let trimmed = input.trim_matches('"');
|
| 105 |
+
|
| 106 |
+
// Split the string at the hyphen and take the first part
|
| 107 |
+
let parts: Vec<&str> = trimmed.split('-').collect();
|
| 108 |
+
if parts.is_empty() {
|
| 109 |
+
None
|
| 110 |
+
} else {
|
| 111 |
+
Some(parts[0].to_string())
|
| 112 |
+
}
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
#[async_trait]
|
| 116 |
+
impl ServerModuleHandlers for StaticFileServingModuleHandlers {
|
| 117 |
+
async fn request_handler(
|
| 118 |
+
&mut self,
|
| 119 |
+
request: RequestData,
|
| 120 |
+
config: &ServerConfig,
|
| 121 |
+
_socket_data: &SocketData,
|
| 122 |
+
_error_logger: &ErrorLogger,
|
| 123 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 124 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 125 |
+
if let Some(wwwroot) = config["wwwroot"].as_str() {
|
| 126 |
+
let hyper_request = request.get_hyper_request();
|
| 127 |
+
let request_path = hyper_request.uri().path();
|
| 128 |
+
let mut request_path_bytes = request_path.bytes();
|
| 129 |
+
if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
|
| 130 |
+
return Ok(
|
| 131 |
+
ResponseData::builder(request)
|
| 132 |
+
.status(StatusCode::BAD_REQUEST)
|
| 133 |
+
.build(),
|
| 134 |
+
);
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
let original_request_path = request
|
| 138 |
+
.get_original_url()
|
| 139 |
+
.map_or(request_path, |u| u.path());
|
| 140 |
+
|
| 141 |
+
let cache_key = format!(
|
| 142 |
+
"{}{}{}",
|
| 143 |
+
match config["ip"].as_str() {
|
| 144 |
+
Some(ip) => format!("{}-", ip),
|
| 145 |
+
None => String::from(""),
|
| 146 |
+
},
|
| 147 |
+
match config["domain"].as_str() {
|
| 148 |
+
Some(domain) => format!("{}-", domain),
|
| 149 |
+
None => String::from(""),
|
| 150 |
+
},
|
| 151 |
+
request_path
|
| 152 |
+
);
|
| 153 |
+
|
| 154 |
+
let rwlock_read = self.pathbuf_cache.read().await;
|
| 155 |
+
let joined_pathbuf_option = rwlock_read.get(&cache_key);
|
| 156 |
+
drop(rwlock_read);
|
| 157 |
+
|
| 158 |
+
let joined_pathbuf_cached = joined_pathbuf_option.is_some();
|
| 159 |
+
let mut joined_pathbuf = match joined_pathbuf_option {
|
| 160 |
+
Some(joined_pathbuf) => joined_pathbuf,
|
| 161 |
+
None => {
|
| 162 |
+
let path = Path::new(wwwroot);
|
| 163 |
+
let mut relative_path = &request_path[1..];
|
| 164 |
+
while relative_path.as_bytes().first().copied() == Some(b'/') {
|
| 165 |
+
relative_path = &relative_path[1..];
|
| 166 |
+
}
|
| 167 |
+
|
| 168 |
+
let decoded_relative_path = match urlencoding::decode(relative_path) {
|
| 169 |
+
Ok(path) => path.to_string(),
|
| 170 |
+
Err(_) => {
|
| 171 |
+
return Ok(
|
| 172 |
+
ResponseData::builder(request)
|
| 173 |
+
.status(StatusCode::BAD_REQUEST)
|
| 174 |
+
.build(),
|
| 175 |
+
);
|
| 176 |
+
}
|
| 177 |
+
};
|
| 178 |
+
|
| 179 |
+
path.join(decoded_relative_path)
|
| 180 |
+
}
|
| 181 |
+
};
|
| 182 |
+
|
| 183 |
+
match fs::metadata(&joined_pathbuf).await {
|
| 184 |
+
Ok(mut metadata) => {
|
| 185 |
+
if !joined_pathbuf_cached {
|
| 186 |
+
if metadata.is_dir() {
|
| 187 |
+
let indexes = vec!["index.html", "index.htm", "index.xhtml"];
|
| 188 |
+
for index in indexes {
|
| 189 |
+
let temp_joined_pathbuf = joined_pathbuf.join(index);
|
| 190 |
+
match fs::metadata(&temp_joined_pathbuf).await {
|
| 191 |
+
Ok(temp_metadata) => {
|
| 192 |
+
if temp_metadata.is_file() {
|
| 193 |
+
metadata = temp_metadata;
|
| 194 |
+
joined_pathbuf = temp_joined_pathbuf;
|
| 195 |
+
break;
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
Err(err) => match err.kind() {
|
| 199 |
+
tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
|
| 200 |
+
continue;
|
| 201 |
+
}
|
| 202 |
+
tokio::io::ErrorKind::PermissionDenied => {
|
| 203 |
+
return Ok(
|
| 204 |
+
ResponseData::builder(request)
|
| 205 |
+
.status(StatusCode::FORBIDDEN)
|
| 206 |
+
.build(),
|
| 207 |
+
);
|
| 208 |
+
}
|
| 209 |
+
_ => Err(err)?,
|
| 210 |
+
},
|
| 211 |
+
};
|
| 212 |
+
}
|
| 213 |
+
}
|
| 214 |
+
let mut rwlock_write = self.pathbuf_cache.write().await;
|
| 215 |
+
rwlock_write.cleanup();
|
| 216 |
+
rwlock_write.insert(cache_key, joined_pathbuf.clone());
|
| 217 |
+
drop(rwlock_write);
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
if metadata.is_file() {
|
| 221 |
+
// Check if compression is possible at all
|
| 222 |
+
let mut compression_possible = false;
|
| 223 |
+
|
| 224 |
+
if config["enableCompression"].as_bool() != Some(false) {
|
| 225 |
+
// A hard-coded list of non-compressible file extension
|
| 226 |
+
let non_compressible_file_extensions = vec![
|
| 227 |
+
"7z",
|
| 228 |
+
"air",
|
| 229 |
+
"amlx",
|
| 230 |
+
"apk",
|
| 231 |
+
"apng",
|
| 232 |
+
"appinstaller",
|
| 233 |
+
"appx",
|
| 234 |
+
"appxbundle",
|
| 235 |
+
"arj",
|
| 236 |
+
"au",
|
| 237 |
+
"avif",
|
| 238 |
+
"bdoc",
|
| 239 |
+
"boz",
|
| 240 |
+
"br",
|
| 241 |
+
"bz",
|
| 242 |
+
"bz2",
|
| 243 |
+
"caf",
|
| 244 |
+
"class",
|
| 245 |
+
"doc",
|
| 246 |
+
"docx",
|
| 247 |
+
"dot",
|
| 248 |
+
"dvi",
|
| 249 |
+
"ear",
|
| 250 |
+
"epub",
|
| 251 |
+
"flv",
|
| 252 |
+
"gdoc",
|
| 253 |
+
"gif",
|
| 254 |
+
"gsheet",
|
| 255 |
+
"gslides",
|
| 256 |
+
"gz",
|
| 257 |
+
"iges",
|
| 258 |
+
"igs",
|
| 259 |
+
"jar",
|
| 260 |
+
"jnlp",
|
| 261 |
+
"jp2",
|
| 262 |
+
"jpe",
|
| 263 |
+
"jpeg",
|
| 264 |
+
"jpf",
|
| 265 |
+
"jpg",
|
| 266 |
+
"jpg2",
|
| 267 |
+
"jpgm",
|
| 268 |
+
"jpm",
|
| 269 |
+
"jpx",
|
| 270 |
+
"kmz",
|
| 271 |
+
"latex",
|
| 272 |
+
"m1v",
|
| 273 |
+
"m2a",
|
| 274 |
+
"m2v",
|
| 275 |
+
"m3a",
|
| 276 |
+
"m4a",
|
| 277 |
+
"mesh",
|
| 278 |
+
"mk3d",
|
| 279 |
+
"mks",
|
| 280 |
+
"mkv",
|
| 281 |
+
"mov",
|
| 282 |
+
"mp2",
|
| 283 |
+
"mp2a",
|
| 284 |
+
"mp3",
|
| 285 |
+
"mp4",
|
| 286 |
+
"mp4a",
|
| 287 |
+
"mp4v",
|
| 288 |
+
"mpe",
|
| 289 |
+
"mpeg",
|
| 290 |
+
"mpg",
|
| 291 |
+
"mpg4",
|
| 292 |
+
"mpga",
|
| 293 |
+
"msg",
|
| 294 |
+
"msh",
|
| 295 |
+
"msix",
|
| 296 |
+
"msixbundle",
|
| 297 |
+
"odg",
|
| 298 |
+
"odp",
|
| 299 |
+
"ods",
|
| 300 |
+
"odt",
|
| 301 |
+
"oga",
|
| 302 |
+
"ogg",
|
| 303 |
+
"ogv",
|
| 304 |
+
"ogx",
|
| 305 |
+
"opus",
|
| 306 |
+
"p12",
|
| 307 |
+
"pdf",
|
| 308 |
+
"pfx",
|
| 309 |
+
"pgp",
|
| 310 |
+
"pkpass",
|
| 311 |
+
"png",
|
| 312 |
+
"pot",
|
| 313 |
+
"pps",
|
| 314 |
+
"ppt",
|
| 315 |
+
"pptx",
|
| 316 |
+
"qt",
|
| 317 |
+
"ser",
|
| 318 |
+
"silo",
|
| 319 |
+
"sit",
|
| 320 |
+
"snd",
|
| 321 |
+
"spx",
|
| 322 |
+
"stpxz",
|
| 323 |
+
"stpz",
|
| 324 |
+
"swf",
|
| 325 |
+
"tif",
|
| 326 |
+
"tiff",
|
| 327 |
+
"ubj",
|
| 328 |
+
"usdz",
|
| 329 |
+
"vbox-extpack",
|
| 330 |
+
"vrml",
|
| 331 |
+
"war",
|
| 332 |
+
"wav",
|
| 333 |
+
"weba",
|
| 334 |
+
"webm",
|
| 335 |
+
"wmv",
|
| 336 |
+
"wrl",
|
| 337 |
+
"x3dbz",
|
| 338 |
+
"x3dvz",
|
| 339 |
+
"xla",
|
| 340 |
+
"xlc",
|
| 341 |
+
"xlm",
|
| 342 |
+
"xls",
|
| 343 |
+
"xlsx",
|
| 344 |
+
"xlt",
|
| 345 |
+
"xlw",
|
| 346 |
+
"xpi",
|
| 347 |
+
"xps",
|
| 348 |
+
"zip",
|
| 349 |
+
"zst",
|
| 350 |
+
];
|
| 351 |
+
let file_extension = joined_pathbuf
|
| 352 |
+
.extension()
|
| 353 |
+
.map_or_else(|| "".to_string(), |ext| ext.to_string_lossy().to_string());
|
| 354 |
+
let file_extension_compressible =
|
| 355 |
+
!non_compressible_file_extensions.contains(&(&file_extension as &str));
|
| 356 |
+
|
| 357 |
+
if metadata.len() > 256 && file_extension_compressible {
|
| 358 |
+
compression_possible = true;
|
| 359 |
+
}
|
| 360 |
+
}
|
| 361 |
+
|
| 362 |
+
let vary;
|
| 363 |
+
|
| 364 |
+
// Handle ETags
|
| 365 |
+
let mut etag_option = None;
|
| 366 |
+
if config["enableETag"].as_bool() != Some(false) {
|
| 367 |
+
let etag_cache_key = format!(
|
| 368 |
+
"{}-{}-{}",
|
| 369 |
+
joined_pathbuf.to_string_lossy(),
|
| 370 |
+
metadata.len(),
|
| 371 |
+
match metadata.modified() {
|
| 372 |
+
Ok(mtime) => {
|
| 373 |
+
let datetime: DateTime<Local> = mtime.into();
|
| 374 |
+
datetime.format("%Y-%m-%d %H:%M:%S").to_string()
|
| 375 |
+
}
|
| 376 |
+
Err(_) => String::from(""),
|
| 377 |
+
}
|
| 378 |
+
);
|
| 379 |
+
let rwlock_read = self.etag_cache.read().await;
|
| 380 |
+
// Had to use "peek", since "get" would mutate the LRU cache
|
| 381 |
+
let etag_locked_option = rwlock_read.peek(&etag_cache_key).cloned();
|
| 382 |
+
drop(rwlock_read);
|
| 383 |
+
let etag = match etag_locked_option {
|
| 384 |
+
Some(etag) => etag,
|
| 385 |
+
None => {
|
| 386 |
+
let etag_cache_key_clone = etag_cache_key.clone();
|
| 387 |
+
let etag = tokio::task::spawn_blocking(move || {
|
| 388 |
+
let mut hasher = Sha256::new();
|
| 389 |
+
hasher.update(etag_cache_key_clone);
|
| 390 |
+
hasher
|
| 391 |
+
.finalize()
|
| 392 |
+
.iter()
|
| 393 |
+
.fold(String::new(), |mut output, b| {
|
| 394 |
+
let _ = write!(output, "{b:02x}");
|
| 395 |
+
output
|
| 396 |
+
})
|
| 397 |
+
})
|
| 398 |
+
.await?;
|
| 399 |
+
|
| 400 |
+
let mut rwlock_write = self.etag_cache.write().await;
|
| 401 |
+
rwlock_write.insert(etag_cache_key, etag.clone());
|
| 402 |
+
drop(rwlock_write);
|
| 403 |
+
|
| 404 |
+
etag
|
| 405 |
+
}
|
| 406 |
+
};
|
| 407 |
+
|
| 408 |
+
vary = if compression_possible {
|
| 409 |
+
"Accept-Encoding, If-Match, If-None-Match, Range"
|
| 410 |
+
} else {
|
| 411 |
+
"If-Match, If-None-Match, Range"
|
| 412 |
+
};
|
| 413 |
+
|
| 414 |
+
if let Some(if_none_match_value) =
|
| 415 |
+
hyper_request.headers().get(header::IF_NONE_MATCH)
|
| 416 |
+
{
|
| 417 |
+
match if_none_match_value.to_str() {
|
| 418 |
+
Ok(if_none_match) => {
|
| 419 |
+
if let Some(etag_extracted) = extract_etag_inner(if_none_match) {
|
| 420 |
+
if etag_extracted == etag {
|
| 421 |
+
let etag_original = if_none_match.to_string();
|
| 422 |
+
return Ok(
|
| 423 |
+
ResponseData::builder(request)
|
| 424 |
+
.response(
|
| 425 |
+
Response::builder()
|
| 426 |
+
.status(StatusCode::NOT_MODIFIED)
|
| 427 |
+
.header(header::ETAG, etag_original)
|
| 428 |
+
.header(header::VARY, vary)
|
| 429 |
+
.body(Empty::new().map_err(|e| match e {}).boxed())?,
|
| 430 |
+
)
|
| 431 |
+
.build(),
|
| 432 |
+
);
|
| 433 |
+
}
|
| 434 |
+
}
|
| 435 |
+
}
|
| 436 |
+
Err(_) => {
|
| 437 |
+
let mut header_map = HeaderMap::new();
|
| 438 |
+
if let Ok(vary) = HeaderValue::from_str(vary) {
|
| 439 |
+
header_map.insert(header::VARY, vary);
|
| 440 |
+
}
|
| 441 |
+
return Ok(
|
| 442 |
+
ResponseData::builder(request)
|
| 443 |
+
.status(StatusCode::BAD_REQUEST)
|
| 444 |
+
.headers(header_map)
|
| 445 |
+
.build(),
|
| 446 |
+
);
|
| 447 |
+
}
|
| 448 |
+
}
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
if let Some(if_match_value) = hyper_request.headers().get(header::IF_MATCH) {
|
| 452 |
+
match if_match_value.to_str() {
|
| 453 |
+
Ok(if_match) => {
|
| 454 |
+
if if_match != "*" {
|
| 455 |
+
if let Some(etag_extracted) = extract_etag_inner(if_match) {
|
| 456 |
+
if etag_extracted != etag {
|
| 457 |
+
let mut header_map = HeaderMap::new();
|
| 458 |
+
header_map.insert(header::ETAG, if_match_value.clone());
|
| 459 |
+
if let Ok(vary) = HeaderValue::from_str(vary) {
|
| 460 |
+
header_map.insert(header::VARY, vary);
|
| 461 |
+
}
|
| 462 |
+
return Ok(
|
| 463 |
+
ResponseData::builder(request)
|
| 464 |
+
.status(StatusCode::PRECONDITION_FAILED)
|
| 465 |
+
.headers(header_map)
|
| 466 |
+
.build(),
|
| 467 |
+
);
|
| 468 |
+
}
|
| 469 |
+
}
|
| 470 |
+
}
|
| 471 |
+
}
|
| 472 |
+
Err(_) => {
|
| 473 |
+
let mut header_map = HeaderMap::new();
|
| 474 |
+
if let Ok(vary) = HeaderValue::from_str(vary) {
|
| 475 |
+
header_map.insert(header::VARY, vary);
|
| 476 |
+
}
|
| 477 |
+
return Ok(
|
| 478 |
+
ResponseData::builder(request)
|
| 479 |
+
.status(StatusCode::BAD_REQUEST)
|
| 480 |
+
.headers(header_map)
|
| 481 |
+
.build(),
|
| 482 |
+
);
|
| 483 |
+
}
|
| 484 |
+
}
|
| 485 |
+
}
|
| 486 |
+
etag_option = Some(etag);
|
| 487 |
+
} else {
|
| 488 |
+
vary = if compression_possible {
|
| 489 |
+
"Accept-Encoding, Range"
|
| 490 |
+
} else {
|
| 491 |
+
"Range"
|
| 492 |
+
};
|
| 493 |
+
}
|
| 494 |
+
|
| 495 |
+
let content_type_option = new_mime_guess::from_path(&joined_pathbuf)
|
| 496 |
+
.first()
|
| 497 |
+
.map(|mime_type| mime_type.to_string());
|
| 498 |
+
|
| 499 |
+
let range_header = match hyper_request.headers().get(header::RANGE) {
|
| 500 |
+
Some(value) => match value.to_str() {
|
| 501 |
+
Ok(value) => Some(value),
|
| 502 |
+
Err(_) => {
|
| 503 |
+
let mut header_map = HeaderMap::new();
|
| 504 |
+
if let Ok(vary) = HeaderValue::from_str(vary) {
|
| 505 |
+
header_map.insert(header::VARY, vary);
|
| 506 |
+
}
|
| 507 |
+
return Ok(
|
| 508 |
+
ResponseData::builder(request)
|
| 509 |
+
.status(StatusCode::BAD_REQUEST)
|
| 510 |
+
.headers(header_map)
|
| 511 |
+
.build(),
|
| 512 |
+
);
|
| 513 |
+
}
|
| 514 |
+
},
|
| 515 |
+
None => None,
|
| 516 |
+
};
|
| 517 |
+
|
| 518 |
+
if let Some(range_header) = range_header {
|
| 519 |
+
let file_length = metadata.len();
|
| 520 |
+
if file_length == 0 {
|
| 521 |
+
let mut header_map = HeaderMap::new();
|
| 522 |
+
if let Ok(vary) = HeaderValue::from_str(vary) {
|
| 523 |
+
header_map.insert(header::VARY, vary);
|
| 524 |
+
}
|
| 525 |
+
return Ok(
|
| 526 |
+
ResponseData::builder(request)
|
| 527 |
+
.status(StatusCode::RANGE_NOT_SATISFIABLE)
|
| 528 |
+
.headers(header_map)
|
| 529 |
+
.build(),
|
| 530 |
+
);
|
| 531 |
+
}
|
| 532 |
+
if let Some((range_begin, range_end)) =
|
| 533 |
+
parse_range_header(range_header, file_length - 1)
|
| 534 |
+
{
|
| 535 |
+
if range_end > file_length - 1
|
| 536 |
+
|| range_begin > file_length - 1
|
| 537 |
+
|| range_begin > range_end
|
| 538 |
+
{
|
| 539 |
+
let mut header_map = HeaderMap::new();
|
| 540 |
+
if let Ok(vary) = HeaderValue::from_str(vary) {
|
| 541 |
+
header_map.insert(header::VARY, vary);
|
| 542 |
+
}
|
| 543 |
+
return Ok(
|
| 544 |
+
ResponseData::builder(request)
|
| 545 |
+
.status(StatusCode::RANGE_NOT_SATISFIABLE)
|
| 546 |
+
.headers(header_map)
|
| 547 |
+
.build(),
|
| 548 |
+
);
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
let request_method = hyper_request.method();
|
| 552 |
+
let content_length = range_end - range_begin + 1;
|
| 553 |
+
|
| 554 |
+
// Build response
|
| 555 |
+
let mut response_builder = Response::builder()
|
| 556 |
+
.status(StatusCode::PARTIAL_CONTENT)
|
| 557 |
+
.header(header::CONTENT_LENGTH, content_length)
|
| 558 |
+
.header(
|
| 559 |
+
header::CONTENT_RANGE,
|
| 560 |
+
format!("bytes {}-{}/{}", range_begin, range_end, file_length),
|
| 561 |
+
);
|
| 562 |
+
|
| 563 |
+
if let Some(etag) = etag_option {
|
| 564 |
+
response_builder = response_builder.header(header::ETAG, etag);
|
| 565 |
+
}
|
| 566 |
+
|
| 567 |
+
if let Some(content_type) = content_type_option {
|
| 568 |
+
response_builder = response_builder.header(header::CONTENT_TYPE, content_type);
|
| 569 |
+
}
|
| 570 |
+
|
| 571 |
+
response_builder = response_builder.header(header::VARY, vary);
|
| 572 |
+
|
| 573 |
+
let response = match request_method {
|
| 574 |
+
&Method::HEAD => {
|
| 575 |
+
response_builder.body(Empty::new().map_err(|e| match e {}).boxed())?
|
| 576 |
+
}
|
| 577 |
+
_ => {
|
| 578 |
+
// Open file for reading
|
| 579 |
+
let mut file = match fs::File::open(joined_pathbuf).await {
|
| 580 |
+
Ok(file) => file,
|
| 581 |
+
Err(err) => match err.kind() {
|
| 582 |
+
tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
|
| 583 |
+
return Ok(
|
| 584 |
+
ResponseData::builder(request)
|
| 585 |
+
.status(StatusCode::NOT_FOUND)
|
| 586 |
+
.build(),
|
| 587 |
+
);
|
| 588 |
+
}
|
| 589 |
+
tokio::io::ErrorKind::PermissionDenied => {
|
| 590 |
+
return Ok(
|
| 591 |
+
ResponseData::builder(request)
|
| 592 |
+
.status(StatusCode::FORBIDDEN)
|
| 593 |
+
.build(),
|
| 594 |
+
);
|
| 595 |
+
}
|
| 596 |
+
_ => Err(err)?,
|
| 597 |
+
},
|
| 598 |
+
};
|
| 599 |
+
|
| 600 |
+
// Seek and limit the file reader
|
| 601 |
+
file.seek(SeekFrom::Start(range_begin)).await?;
|
| 602 |
+
let file_limited = file.take(content_length);
|
| 603 |
+
|
| 604 |
+
// Use BufReader for better performance.
|
| 605 |
+
let file_bufreader = BufReader::with_capacity(12800, file_limited);
|
| 606 |
+
|
| 607 |
+
// Construct a boxed body
|
| 608 |
+
let reader_stream = ReaderStream::new(file_bufreader);
|
| 609 |
+
let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
|
| 610 |
+
let boxed_body = stream_body.boxed();
|
| 611 |
+
|
| 612 |
+
response_builder.body(boxed_body)?
|
| 613 |
+
}
|
| 614 |
+
};
|
| 615 |
+
|
| 616 |
+
return Ok(ResponseData::builder(request).response(response).build());
|
| 617 |
+
} else {
|
| 618 |
+
let mut header_map = HeaderMap::new();
|
| 619 |
+
if let Ok(vary) = HeaderValue::from_str(vary) {
|
| 620 |
+
header_map.insert(header::VARY, vary);
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
return Ok(
|
| 624 |
+
ResponseData::builder(request)
|
| 625 |
+
.status(StatusCode::RANGE_NOT_SATISFIABLE)
|
| 626 |
+
.headers(header_map)
|
| 627 |
+
.build(),
|
| 628 |
+
);
|
| 629 |
+
}
|
| 630 |
+
} else {
|
| 631 |
+
let mut use_gzip = false;
|
| 632 |
+
let mut use_deflate = false;
|
| 633 |
+
let mut use_brotli = false;
|
| 634 |
+
let mut use_zstd = false;
|
| 635 |
+
|
| 636 |
+
if compression_possible {
|
| 637 |
+
let user_agent = match hyper_request.headers().get(header::USER_AGENT) {
|
| 638 |
+
Some(user_agent_value) => user_agent_value.to_str().unwrap_or_default(),
|
| 639 |
+
None => "",
|
| 640 |
+
};
|
| 641 |
+
|
| 642 |
+
// Some web browsers have broken HTTP compression handling
|
| 643 |
+
let is_netscape_4_broken_html_compression = user_agent.starts_with("Mozilla/4.");
|
| 644 |
+
let is_netscape_4_broken_compression = match user_agent.strip_prefix("Mozilla/4.")
|
| 645 |
+
{
|
| 646 |
+
Some(stripped_user_agent) => matches!(
|
| 647 |
+
stripped_user_agent.chars().nth(0),
|
| 648 |
+
Some('6') | Some('7') | Some('8')
|
| 649 |
+
),
|
| 650 |
+
None => false,
|
| 651 |
+
};
|
| 652 |
+
let is_w3m_broken_html_compression = user_agent.starts_with("w3m/");
|
| 653 |
+
if !(content_type_option == Some("text/html".to_string())
|
| 654 |
+
&& (is_netscape_4_broken_html_compression || is_w3m_broken_html_compression))
|
| 655 |
+
&& !is_netscape_4_broken_compression
|
| 656 |
+
{
|
| 657 |
+
let accept_encoding = match hyper_request.headers().get(header::ACCEPT_ENCODING)
|
| 658 |
+
{
|
| 659 |
+
Some(header_value) => header_value.to_str().unwrap_or_default(),
|
| 660 |
+
None => "",
|
| 661 |
+
};
|
| 662 |
+
|
| 663 |
+
// Checking the Accept-Encoding header naively...
|
| 664 |
+
if accept_encoding.contains("br") {
|
| 665 |
+
use_brotli = true;
|
| 666 |
+
} else if accept_encoding.contains("zstd") {
|
| 667 |
+
use_zstd = true;
|
| 668 |
+
} else if accept_encoding.contains("deflate") {
|
| 669 |
+
use_deflate = true;
|
| 670 |
+
} else if accept_encoding.contains("gzip") {
|
| 671 |
+
use_gzip = true;
|
| 672 |
+
}
|
| 673 |
+
}
|
| 674 |
+
}
|
| 675 |
+
|
| 676 |
+
let request_method = hyper_request.method();
|
| 677 |
+
let content_length = metadata.len();
|
| 678 |
+
|
| 679 |
+
// Build response
|
| 680 |
+
let mut response_builder = Response::builder()
|
| 681 |
+
.status(StatusCode::OK)
|
| 682 |
+
.header(header::ACCEPT_RANGES, "bytes");
|
| 683 |
+
|
| 684 |
+
if let Some(etag) = etag_option {
|
| 685 |
+
if use_brotli {
|
| 686 |
+
response_builder =
|
| 687 |
+
response_builder.header(header::ETAG, format!("\"{}-br\"", etag));
|
| 688 |
+
} else if use_zstd {
|
| 689 |
+
response_builder =
|
| 690 |
+
response_builder.header(header::ETAG, format!("\"{}-zstd\"", etag));
|
| 691 |
+
} else if use_deflate {
|
| 692 |
+
response_builder =
|
| 693 |
+
response_builder.header(header::ETAG, format!("\"{}-deflate\"", etag));
|
| 694 |
+
} else if use_gzip {
|
| 695 |
+
response_builder =
|
| 696 |
+
response_builder.header(header::ETAG, format!("\"{}-gzip\"", etag));
|
| 697 |
+
} else {
|
| 698 |
+
response_builder =
|
| 699 |
+
response_builder.header(header::ETAG, format!("\"{}\"", etag));
|
| 700 |
+
}
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
response_builder = response_builder.header(header::VARY, vary);
|
| 704 |
+
|
| 705 |
+
if let Some(content_type) = content_type_option {
|
| 706 |
+
response_builder = response_builder.header(header::CONTENT_TYPE, content_type);
|
| 707 |
+
}
|
| 708 |
+
|
| 709 |
+
if use_brotli {
|
| 710 |
+
response_builder = response_builder.header(header::CONTENT_ENCODING, "br");
|
| 711 |
+
} else if use_zstd {
|
| 712 |
+
response_builder = response_builder.header(header::CONTENT_ENCODING, "zstd");
|
| 713 |
+
} else if use_deflate {
|
| 714 |
+
response_builder = response_builder.header(header::CONTENT_ENCODING, "deflate");
|
| 715 |
+
} else if use_gzip {
|
| 716 |
+
response_builder = response_builder.header(header::CONTENT_ENCODING, "gzip");
|
| 717 |
+
} else {
|
| 718 |
+
// Content-Length header + HTTP compression = broken HTTP responses!
|
| 719 |
+
response_builder =
|
| 720 |
+
response_builder.header(header::CONTENT_LENGTH, content_length);
|
| 721 |
+
}
|
| 722 |
+
|
| 723 |
+
let response = match request_method {
|
| 724 |
+
&Method::HEAD => {
|
| 725 |
+
response_builder.body(Empty::new().map_err(|e| match e {}).boxed())?
|
| 726 |
+
}
|
| 727 |
+
_ => {
|
| 728 |
+
// Open file for reading
|
| 729 |
+
let file = match fs::File::open(joined_pathbuf).await {
|
| 730 |
+
Ok(file) => file,
|
| 731 |
+
Err(err) => match err.kind() {
|
| 732 |
+
tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
|
| 733 |
+
return Ok(
|
| 734 |
+
ResponseData::builder(request)
|
| 735 |
+
.status(StatusCode::NOT_FOUND)
|
| 736 |
+
.build(),
|
| 737 |
+
);
|
| 738 |
+
}
|
| 739 |
+
tokio::io::ErrorKind::PermissionDenied => {
|
| 740 |
+
return Ok(
|
| 741 |
+
ResponseData::builder(request)
|
| 742 |
+
.status(StatusCode::FORBIDDEN)
|
| 743 |
+
.build(),
|
| 744 |
+
);
|
| 745 |
+
}
|
| 746 |
+
_ => Err(err)?,
|
| 747 |
+
},
|
| 748 |
+
};
|
| 749 |
+
|
| 750 |
+
// Use BufReader for better performance.
|
| 751 |
+
let file_bufreader = BufReader::with_capacity(12800, file);
|
| 752 |
+
|
| 753 |
+
// Construct a boxed body
|
| 754 |
+
let boxed_body = if use_brotli {
|
| 755 |
+
// Brotli compression quality of 4
|
| 756 |
+
let reader_stream = ReaderStream::new(BrotliEncoder::with_quality(
|
| 757 |
+
file_bufreader,
|
| 758 |
+
Level::Precise(4),
|
| 759 |
+
));
|
| 760 |
+
let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
|
| 761 |
+
stream_body.boxed()
|
| 762 |
+
} else if use_zstd {
|
| 763 |
+
// Limit the Zstandard window size to 128K (2^17 bytes) to support many HTTP clients
|
| 764 |
+
let reader_stream = ReaderStream::new(ZstdEncoder::with_quality_and_params(
|
| 765 |
+
file_bufreader,
|
| 766 |
+
Level::Default,
|
| 767 |
+
&[CParameter::window_log(17)],
|
| 768 |
+
));
|
| 769 |
+
let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
|
| 770 |
+
stream_body.boxed()
|
| 771 |
+
} else if use_deflate {
|
| 772 |
+
let reader_stream = ReaderStream::new(DeflateEncoder::new(file_bufreader));
|
| 773 |
+
let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
|
| 774 |
+
stream_body.boxed()
|
| 775 |
+
} else if use_gzip {
|
| 776 |
+
let reader_stream = ReaderStream::new(GzipEncoder::new(file_bufreader));
|
| 777 |
+
let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
|
| 778 |
+
stream_body.boxed()
|
| 779 |
+
} else {
|
| 780 |
+
let reader_stream = ReaderStream::new(file_bufreader);
|
| 781 |
+
let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
|
| 782 |
+
stream_body.boxed()
|
| 783 |
+
};
|
| 784 |
+
|
| 785 |
+
response_builder.body(boxed_body)?
|
| 786 |
+
}
|
| 787 |
+
};
|
| 788 |
+
|
| 789 |
+
return Ok(ResponseData::builder(request).response(response).build());
|
| 790 |
+
}
|
| 791 |
+
} else if metadata.is_dir() {
|
| 792 |
+
if config["enableDirectoryListing"].as_bool() == Some(true) {
|
| 793 |
+
let joined_maindesc_pathbuf = joined_pathbuf.join(".maindesc");
|
| 794 |
+
let directory = match fs::read_dir(joined_pathbuf).await {
|
| 795 |
+
Ok(directory) => directory,
|
| 796 |
+
Err(err) => match err.kind() {
|
| 797 |
+
tokio::io::ErrorKind::NotFound => {
|
| 798 |
+
return Ok(
|
| 799 |
+
ResponseData::builder(request)
|
| 800 |
+
.status(StatusCode::NOT_FOUND)
|
| 801 |
+
.build(),
|
| 802 |
+
);
|
| 803 |
+
}
|
| 804 |
+
tokio::io::ErrorKind::PermissionDenied => {
|
| 805 |
+
return Ok(
|
| 806 |
+
ResponseData::builder(request)
|
| 807 |
+
.status(StatusCode::FORBIDDEN)
|
| 808 |
+
.build(),
|
| 809 |
+
);
|
| 810 |
+
}
|
| 811 |
+
_ => Err(err)?,
|
| 812 |
+
},
|
| 813 |
+
};
|
| 814 |
+
|
| 815 |
+
let description = (fs::read_to_string(joined_maindesc_pathbuf).await).ok();
|
| 816 |
+
|
| 817 |
+
let directory_listing_html =
|
| 818 |
+
generate_directory_listing(directory, original_request_path, description).await?;
|
| 819 |
+
let content_length: Option<u64> = directory_listing_html.len().try_into().ok();
|
| 820 |
+
|
| 821 |
+
let mut response_builder = Response::builder().status(StatusCode::OK);
|
| 822 |
+
|
| 823 |
+
if let Some(content_length) = content_length {
|
| 824 |
+
response_builder = response_builder.header(header::CONTENT_LENGTH, content_length)
|
| 825 |
+
}
|
| 826 |
+
response_builder = response_builder.header(header::CONTENT_TYPE, "text/html");
|
| 827 |
+
|
| 828 |
+
let response = response_builder.body(
|
| 829 |
+
Full::new(Bytes::from(directory_listing_html))
|
| 830 |
+
.map_err(|e| match e {})
|
| 831 |
+
.boxed(),
|
| 832 |
+
)?;
|
| 833 |
+
|
| 834 |
+
return Ok(ResponseData::builder(request).response(response).build());
|
| 835 |
+
} else {
|
| 836 |
+
return Ok(
|
| 837 |
+
ResponseData::builder(request)
|
| 838 |
+
.status(StatusCode::FORBIDDEN)
|
| 839 |
+
.build(),
|
| 840 |
+
);
|
| 841 |
+
}
|
| 842 |
+
} else {
|
| 843 |
+
return Ok(
|
| 844 |
+
ResponseData::builder(request)
|
| 845 |
+
.status(StatusCode::NOT_IMPLEMENTED)
|
| 846 |
+
.build(),
|
| 847 |
+
);
|
| 848 |
+
}
|
| 849 |
+
}
|
| 850 |
+
Err(err) => match err.kind() {
|
| 851 |
+
tokio::io::ErrorKind::NotFound | tokio::io::ErrorKind::NotADirectory => {
|
| 852 |
+
return Ok(
|
| 853 |
+
ResponseData::builder(request)
|
| 854 |
+
.status(StatusCode::NOT_FOUND)
|
| 855 |
+
.build(),
|
| 856 |
+
);
|
| 857 |
+
}
|
| 858 |
+
tokio::io::ErrorKind::PermissionDenied => {
|
| 859 |
+
return Ok(
|
| 860 |
+
ResponseData::builder(request)
|
| 861 |
+
.status(StatusCode::FORBIDDEN)
|
| 862 |
+
.build(),
|
| 863 |
+
);
|
| 864 |
+
}
|
| 865 |
+
_ => Err(err)?,
|
| 866 |
+
},
|
| 867 |
+
}
|
| 868 |
+
}
|
| 869 |
+
|
| 870 |
+
Ok(ResponseData::builder(request).build())
|
| 871 |
+
})
|
| 872 |
+
.await
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
+
async fn proxy_request_handler(
|
| 876 |
+
&mut self,
|
| 877 |
+
request: RequestData,
|
| 878 |
+
_config: &ServerConfig,
|
| 879 |
+
_socket_data: &SocketData,
|
| 880 |
+
_error_logger: &ErrorLogger,
|
| 881 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 882 |
+
Ok(ResponseData::builder(request).build())
|
| 883 |
+
}
|
| 884 |
+
|
| 885 |
+
async fn response_modifying_handler(
|
| 886 |
+
&mut self,
|
| 887 |
+
response: HyperResponse,
|
| 888 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 889 |
+
Ok(response)
|
| 890 |
+
}
|
| 891 |
+
|
| 892 |
+
async fn proxy_response_modifying_handler(
|
| 893 |
+
&mut self,
|
| 894 |
+
response: HyperResponse,
|
| 895 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 896 |
+
Ok(response)
|
| 897 |
+
}
|
| 898 |
+
|
| 899 |
+
async fn connect_proxy_request_handler(
|
| 900 |
+
&mut self,
|
| 901 |
+
_upgraded_request: HyperUpgraded,
|
| 902 |
+
_connect_address: &str,
|
| 903 |
+
_config: &ServerConfig,
|
| 904 |
+
_socket_data: &SocketData,
|
| 905 |
+
_error_logger: &ErrorLogger,
|
| 906 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 907 |
+
Ok(())
|
| 908 |
+
}
|
| 909 |
+
|
| 910 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 911 |
+
false
|
| 912 |
+
}
|
| 913 |
+
|
| 914 |
+
async fn websocket_request_handler(
|
| 915 |
+
&mut self,
|
| 916 |
+
_websocket: HyperWebsocket,
|
| 917 |
+
_uri: &hyper::Uri,
|
| 918 |
+
_config: &ServerConfig,
|
| 919 |
+
_socket_data: &SocketData,
|
| 920 |
+
_error_logger: &ErrorLogger,
|
| 921 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 922 |
+
Ok(())
|
| 923 |
+
}
|
| 924 |
+
|
| 925 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 926 |
+
false
|
| 927 |
+
}
|
| 928 |
+
}
|
ferron/src/modules/url_rewrite.rs
ADDED
|
@@ -0,0 +1,350 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::error::Error;
|
| 2 |
+
use std::path::Path;
|
| 3 |
+
use std::sync::Arc;
|
| 4 |
+
|
| 5 |
+
use crate::ferron_util::ip_match::ip_match;
|
| 6 |
+
use crate::ferron_util::match_hostname::match_hostname;
|
| 7 |
+
use crate::ferron_util::match_location::match_location;
|
| 8 |
+
use crate::ferron_util::url_rewrite_structs::{
|
| 9 |
+
UrlRewriteMapEntry, UrlRewriteMapLocationWrap, UrlRewriteMapWrap,
|
| 10 |
+
};
|
| 11 |
+
|
| 12 |
+
use crate::ferron_common::{
|
| 13 |
+
ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 14 |
+
ServerModuleHandlers, SocketData,
|
| 15 |
+
};
|
| 16 |
+
use crate::ferron_common::{HyperUpgraded, WithRuntime};
|
| 17 |
+
use async_trait::async_trait;
|
| 18 |
+
use fancy_regex::RegexBuilder;
|
| 19 |
+
use hyper::{header, Request, StatusCode};
|
| 20 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 21 |
+
use tokio::fs;
|
| 22 |
+
use tokio::runtime::Handle;
|
| 23 |
+
use yaml_rust2::Yaml;
|
| 24 |
+
|
| 25 |
+
fn url_rewrite_config_init(rewrite_map: &[Yaml]) -> Result<Vec<UrlRewriteMapEntry>, anyhow::Error> {
|
| 26 |
+
let rewrite_map_iter = rewrite_map.iter();
|
| 27 |
+
let mut rewrite_map_vec = Vec::new();
|
| 28 |
+
for rewrite_map_entry in rewrite_map_iter {
|
| 29 |
+
let regex_str = match rewrite_map_entry["regex"].as_str() {
|
| 30 |
+
Some(regex_str) => regex_str,
|
| 31 |
+
None => return Err(anyhow::anyhow!("Invalid URL rewrite regular expression")),
|
| 32 |
+
};
|
| 33 |
+
let regex = match RegexBuilder::new(regex_str)
|
| 34 |
+
.case_insensitive(cfg!(windows))
|
| 35 |
+
.build()
|
| 36 |
+
{
|
| 37 |
+
Ok(regex) => regex,
|
| 38 |
+
Err(err) => {
|
| 39 |
+
return Err(anyhow::anyhow!(
|
| 40 |
+
"Invalid URL rewrite regular expression: {}",
|
| 41 |
+
err.to_string()
|
| 42 |
+
))
|
| 43 |
+
}
|
| 44 |
+
};
|
| 45 |
+
let replacement = match rewrite_map_entry["replacement"].as_str() {
|
| 46 |
+
Some(replacement) => String::from(replacement),
|
| 47 |
+
None => return Err(anyhow::anyhow!("URL rewrite rules must have replacements")),
|
| 48 |
+
};
|
| 49 |
+
let is_not_file = rewrite_map_entry["isNotFile"].as_bool().unwrap_or(false);
|
| 50 |
+
let is_not_directory = rewrite_map_entry["isNotDirectory"]
|
| 51 |
+
.as_bool()
|
| 52 |
+
.unwrap_or(false);
|
| 53 |
+
let last = rewrite_map_entry["last"].as_bool().unwrap_or_default();
|
| 54 |
+
let allow_double_slashes = rewrite_map_entry["allowDoubleSlashes"]
|
| 55 |
+
.as_bool()
|
| 56 |
+
.unwrap_or(false);
|
| 57 |
+
rewrite_map_vec.push(UrlRewriteMapEntry::new(
|
| 58 |
+
regex,
|
| 59 |
+
replacement,
|
| 60 |
+
is_not_directory,
|
| 61 |
+
is_not_file,
|
| 62 |
+
last,
|
| 63 |
+
allow_double_slashes,
|
| 64 |
+
));
|
| 65 |
+
}
|
| 66 |
+
|
| 67 |
+
Ok(rewrite_map_vec)
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
pub fn server_module_init(
|
| 71 |
+
config: &ServerConfig,
|
| 72 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 73 |
+
let mut global_url_rewrite_map = Vec::new();
|
| 74 |
+
let mut host_url_rewrite_maps = Vec::new();
|
| 75 |
+
if let Some(rewrite_map_yaml) = config["global"]["rewriteMap"].as_vec() {
|
| 76 |
+
global_url_rewrite_map = url_rewrite_config_init(rewrite_map_yaml)?;
|
| 77 |
+
}
|
| 78 |
+
|
| 79 |
+
if let Some(hosts) = config["hosts"].as_vec() {
|
| 80 |
+
for host_yaml in hosts.iter() {
|
| 81 |
+
let domain = host_yaml["domain"].as_str().map(String::from);
|
| 82 |
+
let ip = host_yaml["ip"].as_str().map(String::from);
|
| 83 |
+
let mut locations = Vec::new();
|
| 84 |
+
if let Some(locations_yaml) = host_yaml["locations"].as_vec() {
|
| 85 |
+
for location_yaml in locations_yaml.iter() {
|
| 86 |
+
if let Some(path_str) = location_yaml["path"].as_str() {
|
| 87 |
+
let path = String::from(path_str);
|
| 88 |
+
if let Some(rewrite_map_yaml) = location_yaml["rewriteMap"].as_vec() {
|
| 89 |
+
locations.push(UrlRewriteMapLocationWrap::new(
|
| 90 |
+
path,
|
| 91 |
+
url_rewrite_config_init(rewrite_map_yaml)?,
|
| 92 |
+
));
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
if let Some(rewrite_map_yaml) = host_yaml["rewriteMap"].as_vec() {
|
| 98 |
+
host_url_rewrite_maps.push(UrlRewriteMapWrap::new(
|
| 99 |
+
domain,
|
| 100 |
+
ip,
|
| 101 |
+
url_rewrite_config_init(rewrite_map_yaml)?,
|
| 102 |
+
locations,
|
| 103 |
+
));
|
| 104 |
+
} else if !locations.is_empty() {
|
| 105 |
+
host_url_rewrite_maps.push(UrlRewriteMapWrap::new(domain, ip, Vec::new(), locations));
|
| 106 |
+
}
|
| 107 |
+
}
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
Ok(Box::new(UrlRewriteModule::new(
|
| 111 |
+
Arc::new(global_url_rewrite_map),
|
| 112 |
+
Arc::new(host_url_rewrite_maps),
|
| 113 |
+
)))
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
struct UrlRewriteModule {
|
| 117 |
+
global_url_rewrite_map: Arc<Vec<UrlRewriteMapEntry>>,
|
| 118 |
+
host_url_rewrite_maps: Arc<Vec<UrlRewriteMapWrap>>,
|
| 119 |
+
}
|
| 120 |
+
|
| 121 |
+
impl UrlRewriteModule {
|
| 122 |
+
fn new(
|
| 123 |
+
global_url_rewrite_map: Arc<Vec<UrlRewriteMapEntry>>,
|
| 124 |
+
host_url_rewrite_maps: Arc<Vec<UrlRewriteMapWrap>>,
|
| 125 |
+
) -> Self {
|
| 126 |
+
Self {
|
| 127 |
+
global_url_rewrite_map,
|
| 128 |
+
host_url_rewrite_maps,
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
impl ServerModule for UrlRewriteModule {
|
| 134 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 135 |
+
Box::new(UrlRewriteModuleHandlers {
|
| 136 |
+
global_url_rewrite_map: self.global_url_rewrite_map.clone(),
|
| 137 |
+
host_url_rewrite_maps: self.host_url_rewrite_maps.clone(),
|
| 138 |
+
handle,
|
| 139 |
+
})
|
| 140 |
+
}
|
| 141 |
+
}
|
| 142 |
+
struct UrlRewriteModuleHandlers {
|
| 143 |
+
global_url_rewrite_map: Arc<Vec<UrlRewriteMapEntry>>,
|
| 144 |
+
host_url_rewrite_maps: Arc<Vec<UrlRewriteMapWrap>>,
|
| 145 |
+
handle: Handle,
|
| 146 |
+
}
|
| 147 |
+
|
| 148 |
+
#[async_trait]
|
| 149 |
+
impl ServerModuleHandlers for UrlRewriteModuleHandlers {
|
| 150 |
+
async fn request_handler(
|
| 151 |
+
&mut self,
|
| 152 |
+
request: RequestData,
|
| 153 |
+
config: &ServerConfig,
|
| 154 |
+
socket_data: &SocketData,
|
| 155 |
+
error_logger: &ErrorLogger,
|
| 156 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 157 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 158 |
+
let hyper_request = request.get_hyper_request();
|
| 159 |
+
let global_url_rewrite_map = self.global_url_rewrite_map.iter();
|
| 160 |
+
let empty_vector = Vec::new();
|
| 161 |
+
let another_empty_vector = Vec::new();
|
| 162 |
+
let mut host_url_rewrite_map = empty_vector.iter();
|
| 163 |
+
let mut location_url_rewrite_map = another_empty_vector.iter();
|
| 164 |
+
|
| 165 |
+
// Should have used a HashMap instead of iterating over an array for better performance...
|
| 166 |
+
for host_url_rewrite_map_wrap in self.host_url_rewrite_maps.iter() {
|
| 167 |
+
if match_hostname(
|
| 168 |
+
match &host_url_rewrite_map_wrap.domain {
|
| 169 |
+
Some(value) => Some(value as &str),
|
| 170 |
+
None => None,
|
| 171 |
+
},
|
| 172 |
+
match hyper_request.headers().get(header::HOST) {
|
| 173 |
+
Some(value) => value.to_str().ok(),
|
| 174 |
+
None => None,
|
| 175 |
+
},
|
| 176 |
+
) && match &host_url_rewrite_map_wrap.ip {
|
| 177 |
+
Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
|
| 178 |
+
None => true,
|
| 179 |
+
} {
|
| 180 |
+
host_url_rewrite_map = host_url_rewrite_map_wrap.rewrite_map.iter();
|
| 181 |
+
if let Ok(path_decoded) = urlencoding::decode(
|
| 182 |
+
request
|
| 183 |
+
.get_original_url()
|
| 184 |
+
.unwrap_or(request.get_hyper_request().uri())
|
| 185 |
+
.path(),
|
| 186 |
+
) {
|
| 187 |
+
for location_wrap in host_url_rewrite_map_wrap.locations.iter() {
|
| 188 |
+
if match_location(&location_wrap.path, &path_decoded) {
|
| 189 |
+
location_url_rewrite_map = location_wrap.rewrite_map.iter();
|
| 190 |
+
break;
|
| 191 |
+
}
|
| 192 |
+
}
|
| 193 |
+
}
|
| 194 |
+
break;
|
| 195 |
+
}
|
| 196 |
+
}
|
| 197 |
+
|
| 198 |
+
let combined_url_rewrite_map = global_url_rewrite_map
|
| 199 |
+
.chain(host_url_rewrite_map)
|
| 200 |
+
.chain(location_url_rewrite_map);
|
| 201 |
+
|
| 202 |
+
let original_url = format!(
|
| 203 |
+
"{}{}",
|
| 204 |
+
hyper_request.uri().path(),
|
| 205 |
+
match hyper_request.uri().query() {
|
| 206 |
+
Some(query) => format!("?{}", query),
|
| 207 |
+
None => String::from(""),
|
| 208 |
+
}
|
| 209 |
+
);
|
| 210 |
+
let mut rewritten_url = original_url.clone();
|
| 211 |
+
|
| 212 |
+
let mut rewritten_url_bytes = rewritten_url.bytes();
|
| 213 |
+
if rewritten_url_bytes.len() < 1 || rewritten_url_bytes.nth(0) != Some(b'/') {
|
| 214 |
+
return Ok(
|
| 215 |
+
ResponseData::builder(request)
|
| 216 |
+
.status(StatusCode::BAD_REQUEST)
|
| 217 |
+
.build(),
|
| 218 |
+
);
|
| 219 |
+
}
|
| 220 |
+
|
| 221 |
+
for url_rewrite_map_entry in combined_url_rewrite_map {
|
| 222 |
+
// Check if it's a file or a directory according to the rewrite map configuration
|
| 223 |
+
if url_rewrite_map_entry.is_not_directory || url_rewrite_map_entry.is_not_file {
|
| 224 |
+
if let Some(wwwroot) = config["wwwroot"].as_str() {
|
| 225 |
+
let path = Path::new(wwwroot);
|
| 226 |
+
let mut relative_path = &rewritten_url[1..];
|
| 227 |
+
while relative_path.as_bytes().first().copied() == Some(b'/') {
|
| 228 |
+
relative_path = &relative_path[1..];
|
| 229 |
+
}
|
| 230 |
+
let relative_path_split: Vec<&str> = relative_path.split("?").collect();
|
| 231 |
+
if !relative_path_split.is_empty() {
|
| 232 |
+
relative_path = relative_path_split[0];
|
| 233 |
+
}
|
| 234 |
+
let joined_pathbuf = path.join(relative_path);
|
| 235 |
+
if let Ok(metadata) = fs::metadata(joined_pathbuf).await {
|
| 236 |
+
if (url_rewrite_map_entry.is_not_file && metadata.is_file())
|
| 237 |
+
|| (url_rewrite_map_entry.is_not_directory && metadata.is_dir())
|
| 238 |
+
{
|
| 239 |
+
continue;
|
| 240 |
+
}
|
| 241 |
+
}
|
| 242 |
+
}
|
| 243 |
+
}
|
| 244 |
+
|
| 245 |
+
if !url_rewrite_map_entry.allow_double_slashes {
|
| 246 |
+
while rewritten_url.contains("//") {
|
| 247 |
+
rewritten_url = rewritten_url.replace("//", "/");
|
| 248 |
+
}
|
| 249 |
+
}
|
| 250 |
+
|
| 251 |
+
// Actual URL rewriting
|
| 252 |
+
let old_rewritten_url = rewritten_url;
|
| 253 |
+
rewritten_url = url_rewrite_map_entry
|
| 254 |
+
.regex
|
| 255 |
+
.replace(&old_rewritten_url, &url_rewrite_map_entry.replacement)
|
| 256 |
+
.to_string();
|
| 257 |
+
|
| 258 |
+
let mut rewritten_url_bytes = rewritten_url.bytes();
|
| 259 |
+
if rewritten_url_bytes.len() < 1 || rewritten_url_bytes.nth(0) != Some(b'/') {
|
| 260 |
+
return Ok(
|
| 261 |
+
ResponseData::builder(request)
|
| 262 |
+
.status(StatusCode::BAD_REQUEST)
|
| 263 |
+
.build(),
|
| 264 |
+
);
|
| 265 |
+
}
|
| 266 |
+
|
| 267 |
+
if url_rewrite_map_entry.last && old_rewritten_url != rewritten_url {
|
| 268 |
+
break;
|
| 269 |
+
}
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
if rewritten_url == original_url {
|
| 273 |
+
Ok(ResponseData::builder(request).build())
|
| 274 |
+
} else {
|
| 275 |
+
if config["enableRewriteLogging"].as_bool() == Some(true) {
|
| 276 |
+
error_logger
|
| 277 |
+
.log(&format!(
|
| 278 |
+
"URL rewritten from \"{}\" to \"{}\"",
|
| 279 |
+
original_url, rewritten_url
|
| 280 |
+
))
|
| 281 |
+
.await;
|
| 282 |
+
}
|
| 283 |
+
let (hyper_request, auth_user, _) = request.into_parts();
|
| 284 |
+
let (mut parts, body) = hyper_request.into_parts();
|
| 285 |
+
let original_url = parts.uri.clone();
|
| 286 |
+
let mut url_parts = parts.uri.into_parts();
|
| 287 |
+
url_parts.path_and_query = Some(rewritten_url.parse()?);
|
| 288 |
+
parts.uri = hyper::Uri::from_parts(url_parts)?;
|
| 289 |
+
let hyper_request = Request::from_parts(parts, body);
|
| 290 |
+
let request = RequestData::new(hyper_request, auth_user, Some(original_url));
|
| 291 |
+
Ok(ResponseData::builder(request).build())
|
| 292 |
+
}
|
| 293 |
+
})
|
| 294 |
+
.await
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
async fn proxy_request_handler(
|
| 298 |
+
&mut self,
|
| 299 |
+
request: RequestData,
|
| 300 |
+
_config: &ServerConfig,
|
| 301 |
+
_socket_data: &SocketData,
|
| 302 |
+
_error_logger: &ErrorLogger,
|
| 303 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 304 |
+
Ok(ResponseData::builder(request).build())
|
| 305 |
+
}
|
| 306 |
+
|
| 307 |
+
async fn response_modifying_handler(
|
| 308 |
+
&mut self,
|
| 309 |
+
response: HyperResponse,
|
| 310 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 311 |
+
Ok(response)
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
async fn proxy_response_modifying_handler(
|
| 315 |
+
&mut self,
|
| 316 |
+
response: HyperResponse,
|
| 317 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 318 |
+
Ok(response)
|
| 319 |
+
}
|
| 320 |
+
|
| 321 |
+
async fn connect_proxy_request_handler(
|
| 322 |
+
&mut self,
|
| 323 |
+
_upgraded_request: HyperUpgraded,
|
| 324 |
+
_connect_address: &str,
|
| 325 |
+
_config: &ServerConfig,
|
| 326 |
+
_socket_data: &SocketData,
|
| 327 |
+
_error_logger: &ErrorLogger,
|
| 328 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 329 |
+
Ok(())
|
| 330 |
+
}
|
| 331 |
+
|
| 332 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 333 |
+
false
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
async fn websocket_request_handler(
|
| 337 |
+
&mut self,
|
| 338 |
+
_websocket: HyperWebsocket,
|
| 339 |
+
_uri: &hyper::Uri,
|
| 340 |
+
_config: &ServerConfig,
|
| 341 |
+
_socket_data: &SocketData,
|
| 342 |
+
_error_logger: &ErrorLogger,
|
| 343 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 344 |
+
Ok(())
|
| 345 |
+
}
|
| 346 |
+
|
| 347 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 348 |
+
false
|
| 349 |
+
}
|
| 350 |
+
}
|
ferron/src/modules/x_forwarded_for.rs
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::error::Error;
|
| 2 |
+
use std::net::{IpAddr, SocketAddr};
|
| 3 |
+
|
| 4 |
+
use crate::ferron_common::{
|
| 5 |
+
ErrorLogger, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 6 |
+
ServerModuleHandlers, SocketData,
|
| 7 |
+
};
|
| 8 |
+
use crate::ferron_common::{HyperUpgraded, WithRuntime};
|
| 9 |
+
use async_trait::async_trait;
|
| 10 |
+
use hyper::StatusCode;
|
| 11 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 12 |
+
use tokio::runtime::Handle;
|
| 13 |
+
|
| 14 |
+
struct XForwardedForModule;
|
| 15 |
+
|
| 16 |
+
pub fn server_module_init(
|
| 17 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 18 |
+
Ok(Box::new(XForwardedForModule::new()))
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
impl XForwardedForModule {
|
| 22 |
+
fn new() -> Self {
|
| 23 |
+
Self
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
impl ServerModule for XForwardedForModule {
|
| 28 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 29 |
+
Box::new(XForwardedForModuleHandlers { handle })
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
struct XForwardedForModuleHandlers {
|
| 33 |
+
handle: Handle,
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
#[async_trait]
|
| 37 |
+
impl ServerModuleHandlers for XForwardedForModuleHandlers {
|
| 38 |
+
async fn request_handler(
|
| 39 |
+
&mut self,
|
| 40 |
+
request: RequestData,
|
| 41 |
+
config: &ServerConfig,
|
| 42 |
+
socket_data: &SocketData,
|
| 43 |
+
_error_logger: &ErrorLogger,
|
| 44 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 45 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 46 |
+
if config["enableIPSpoofing"].as_bool() == Some(true) {
|
| 47 |
+
let hyper_request = request.get_hyper_request();
|
| 48 |
+
|
| 49 |
+
if let Some(x_forwarded_for_value) = hyper_request.headers().get("x-forwarded-for") {
|
| 50 |
+
let x_forwarded_for = x_forwarded_for_value.to_str()?;
|
| 51 |
+
|
| 52 |
+
let prepared_remote_ip_str = match x_forwarded_for.split(",").nth(0) {
|
| 53 |
+
Some(ip_address_str) => ip_address_str.replace(" ", ""),
|
| 54 |
+
None => {
|
| 55 |
+
return Ok(
|
| 56 |
+
ResponseData::builder(request)
|
| 57 |
+
.status(StatusCode::BAD_REQUEST)
|
| 58 |
+
.build(),
|
| 59 |
+
);
|
| 60 |
+
}
|
| 61 |
+
};
|
| 62 |
+
|
| 63 |
+
let prepared_remote_ip: IpAddr = match prepared_remote_ip_str.parse() {
|
| 64 |
+
Ok(ip_address) => ip_address,
|
| 65 |
+
Err(_) => {
|
| 66 |
+
return Ok(
|
| 67 |
+
ResponseData::builder(request)
|
| 68 |
+
.status(StatusCode::BAD_REQUEST)
|
| 69 |
+
.build(),
|
| 70 |
+
);
|
| 71 |
+
}
|
| 72 |
+
};
|
| 73 |
+
|
| 74 |
+
let new_socket_addr = SocketAddr::new(prepared_remote_ip, socket_data.remote_addr.port());
|
| 75 |
+
|
| 76 |
+
return Ok(
|
| 77 |
+
ResponseData::builder(request)
|
| 78 |
+
.new_remote_address(new_socket_addr)
|
| 79 |
+
.build(),
|
| 80 |
+
);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
return Ok(ResponseData::builder(request).build());
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
Ok(ResponseData::builder(request).build())
|
| 87 |
+
})
|
| 88 |
+
.await
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
async fn proxy_request_handler(
|
| 92 |
+
&mut self,
|
| 93 |
+
request: RequestData,
|
| 94 |
+
_config: &ServerConfig,
|
| 95 |
+
_socket_data: &SocketData,
|
| 96 |
+
_error_logger: &ErrorLogger,
|
| 97 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 98 |
+
Ok(ResponseData::builder(request).build())
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
async fn response_modifying_handler(
|
| 102 |
+
&mut self,
|
| 103 |
+
response: HyperResponse,
|
| 104 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 105 |
+
Ok(response)
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
async fn proxy_response_modifying_handler(
|
| 109 |
+
&mut self,
|
| 110 |
+
response: HyperResponse,
|
| 111 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 112 |
+
Ok(response)
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
async fn connect_proxy_request_handler(
|
| 116 |
+
&mut self,
|
| 117 |
+
_upgraded_request: HyperUpgraded,
|
| 118 |
+
_connect_address: &str,
|
| 119 |
+
_config: &ServerConfig,
|
| 120 |
+
_socket_data: &SocketData,
|
| 121 |
+
_error_logger: &ErrorLogger,
|
| 122 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 123 |
+
Ok(())
|
| 124 |
+
}
|
| 125 |
+
|
| 126 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 127 |
+
false
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
async fn websocket_request_handler(
|
| 131 |
+
&mut self,
|
| 132 |
+
_websocket: HyperWebsocket,
|
| 133 |
+
_uri: &hyper::Uri,
|
| 134 |
+
_config: &ServerConfig,
|
| 135 |
+
_socket_data: &SocketData,
|
| 136 |
+
_error_logger: &ErrorLogger,
|
| 137 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 138 |
+
Ok(())
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 142 |
+
false
|
| 143 |
+
}
|
| 144 |
+
}
|
ferron/src/optional_modules/asgi.rs
ADDED
|
@@ -0,0 +1,1476 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// WARNING: We have measured this module on our computers, and found it to be slower than Uvicorn (with 1 worker),
|
| 2 |
+
// with FastAPI application, vanilla ASGI application is found out to be faster than Uvicorn (with 1 worker).
|
| 3 |
+
// It might be more performant to just use Ferron as a reverse proxy for Uvicorn (or any other ASGI server).
|
| 4 |
+
|
| 5 |
+
use std::error::Error;
|
| 6 |
+
use std::ffi::CString;
|
| 7 |
+
use std::path::{Path, PathBuf};
|
| 8 |
+
use std::str::FromStr;
|
| 9 |
+
use std::sync::atomic::{AtomicBool, Ordering};
|
| 10 |
+
use std::sync::Arc;
|
| 11 |
+
use std::thread;
|
| 12 |
+
|
| 13 |
+
use crate::ferron_common::{
|
| 14 |
+
ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 15 |
+
ServerModuleHandlers, SocketData,
|
| 16 |
+
};
|
| 17 |
+
use crate::ferron_common::{HyperResponse, WithRuntime};
|
| 18 |
+
use crate::ferron_util::asgi_messages::{
|
| 19 |
+
asgi_event_to_outgoing_struct, incoming_struct_to_asgi_event, AsgiHttpBody, AsgiHttpInitData,
|
| 20 |
+
AsgiInitData, AsgiWebsocketClose, AsgiWebsocketInitData, AsgiWebsocketMessage,
|
| 21 |
+
IncomingAsgiMessage, IncomingAsgiMessageInner, OutgoingAsgiMessage, OutgoingAsgiMessageInner,
|
| 22 |
+
};
|
| 23 |
+
use crate::ferron_util::asgi_structs::{AsgiApplicationLocationWrap, AsgiApplicationWrap};
|
| 24 |
+
use crate::ferron_util::ip_match::ip_match;
|
| 25 |
+
use crate::ferron_util::match_hostname::match_hostname;
|
| 26 |
+
use crate::ferron_util::match_location::match_location;
|
| 27 |
+
use async_channel::{Receiver, Sender};
|
| 28 |
+
use async_trait::async_trait;
|
| 29 |
+
use futures_util::{SinkExt, StreamExt};
|
| 30 |
+
use http::{HeaderMap, HeaderName, HeaderValue, Response, Version};
|
| 31 |
+
use http_body_util::{BodyExt, StreamBody};
|
| 32 |
+
use hyper::body::{Bytes, Frame};
|
| 33 |
+
use hyper::{header, StatusCode};
|
| 34 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 35 |
+
use pyo3::exceptions::{PyIOError, PyOSError, PyRuntimeError, PyTypeError};
|
| 36 |
+
use pyo3::prelude::*;
|
| 37 |
+
use pyo3::types::{PyCFunction, PyDict, PyList, PyTuple, PyType};
|
| 38 |
+
use tokio::fs;
|
| 39 |
+
use tokio::runtime::{Handle, Runtime};
|
| 40 |
+
use tokio::sync::Mutex;
|
| 41 |
+
use tokio_tungstenite::tungstenite::protocol::CloseFrame;
|
| 42 |
+
use tokio_tungstenite::tungstenite::Message;
|
| 43 |
+
use tokio_util::sync::CancellationToken;
|
| 44 |
+
|
| 45 |
+
type AsgiChannelResult =
|
| 46 |
+
Result<(Sender<IncomingAsgiMessage>, Receiver<OutgoingAsgiMessage>), anyhow::Error>;
|
| 47 |
+
type AsgiEventLoopCommunication = Vec<(Sender<()>, Receiver<AsgiChannelResult>)>;
|
| 48 |
+
|
| 49 |
+
async fn asgi_application_fn(
|
| 50 |
+
asgi_application: Arc<Py<PyAny>>,
|
| 51 |
+
tx: Sender<OutgoingAsgiMessage>,
|
| 52 |
+
rx: Receiver<IncomingAsgiMessage>,
|
| 53 |
+
) {
|
| 54 |
+
let init_message = match rx.recv().await {
|
| 55 |
+
Ok(IncomingAsgiMessage::Init(message)) => message,
|
| 56 |
+
Err(err) => {
|
| 57 |
+
tx.send(OutgoingAsgiMessage::Error(PyErr::new::<PyIOError, _>(
|
| 58 |
+
err.to_string(),
|
| 59 |
+
)))
|
| 60 |
+
.await
|
| 61 |
+
.unwrap_or_default();
|
| 62 |
+
return;
|
| 63 |
+
}
|
| 64 |
+
_ => {
|
| 65 |
+
tx.send(OutgoingAsgiMessage::Error(PyErr::new::<PyIOError, _>(
|
| 66 |
+
"Unexpected message received",
|
| 67 |
+
)))
|
| 68 |
+
.await
|
| 69 |
+
.unwrap_or_default();
|
| 70 |
+
return;
|
| 71 |
+
}
|
| 72 |
+
};
|
| 73 |
+
let tx_clone = tx.clone();
|
| 74 |
+
let rx_clone = rx.clone();
|
| 75 |
+
match Python::with_gil(move |py| -> PyResult<_> {
|
| 76 |
+
let tx_clone = tx_clone.clone();
|
| 77 |
+
let rx_clone = rx_clone.clone();
|
| 78 |
+
|
| 79 |
+
let scope = PyDict::new(py);
|
| 80 |
+
let scope_asgi = PyDict::new(py);
|
| 81 |
+
|
| 82 |
+
match init_message {
|
| 83 |
+
AsgiInitData::Lifespan => {
|
| 84 |
+
scope.set_item("type", "lifespan")?;
|
| 85 |
+
scope_asgi.set_item("version", "3.0")?;
|
| 86 |
+
}
|
| 87 |
+
AsgiInitData::Http(http_init_data) => {
|
| 88 |
+
let path = http_init_data.hyper_request_parts.uri.path().to_owned();
|
| 89 |
+
let query_string = http_init_data
|
| 90 |
+
.hyper_request_parts
|
| 91 |
+
.uri
|
| 92 |
+
.query()
|
| 93 |
+
.unwrap_or("")
|
| 94 |
+
.to_owned();
|
| 95 |
+
let original_request_uri = http_init_data
|
| 96 |
+
.original_request_uri
|
| 97 |
+
.unwrap_or(http_init_data.hyper_request_parts.uri);
|
| 98 |
+
scope.set_item("type", "http")?;
|
| 99 |
+
scope_asgi.set_item("version", "2.5")?;
|
| 100 |
+
scope.set_item(
|
| 101 |
+
"http_version",
|
| 102 |
+
match http_init_data.hyper_request_parts.version {
|
| 103 |
+
Version::HTTP_09 => "1.0", // ASGI doesn't support HTTP/0.9
|
| 104 |
+
Version::HTTP_10 => "1.0",
|
| 105 |
+
Version::HTTP_11 => "1.1",
|
| 106 |
+
Version::HTTP_2 => "2",
|
| 107 |
+
Version::HTTP_3 => "2", // ASGI doesn't support HTTP/3
|
| 108 |
+
_ => "1.1", // Some other HTTP versions, of course...
|
| 109 |
+
},
|
| 110 |
+
)?;
|
| 111 |
+
scope.set_item(
|
| 112 |
+
"method",
|
| 113 |
+
http_init_data.hyper_request_parts.method.to_string(),
|
| 114 |
+
)?;
|
| 115 |
+
scope.set_item(
|
| 116 |
+
"scheme",
|
| 117 |
+
if http_init_data.socket_data.encrypted {
|
| 118 |
+
"https"
|
| 119 |
+
} else {
|
| 120 |
+
"http"
|
| 121 |
+
},
|
| 122 |
+
)?;
|
| 123 |
+
scope.set_item("path", urlencoding::decode(&path)?)?;
|
| 124 |
+
scope.set_item("raw_path", original_request_uri.to_string().as_bytes())?;
|
| 125 |
+
scope.set_item("query_string", query_string.as_bytes())?;
|
| 126 |
+
if let Ok(script_path) = http_init_data
|
| 127 |
+
.execute_pathbuf
|
| 128 |
+
.as_path()
|
| 129 |
+
.strip_prefix(http_init_data.wwwroot)
|
| 130 |
+
{
|
| 131 |
+
scope.set_item(
|
| 132 |
+
"root_path",
|
| 133 |
+
format!(
|
| 134 |
+
"/{}",
|
| 135 |
+
match cfg!(windows) {
|
| 136 |
+
true => script_path.to_string_lossy().to_string().replace("\\", "/"),
|
| 137 |
+
false => script_path.to_string_lossy().to_string(),
|
| 138 |
+
}
|
| 139 |
+
),
|
| 140 |
+
)?;
|
| 141 |
+
}
|
| 142 |
+
let headers = PyList::empty(py);
|
| 143 |
+
for (header_name, header_value) in http_init_data.hyper_request_parts.headers.iter() {
|
| 144 |
+
let header_name = header_name.as_str().as_bytes();
|
| 145 |
+
let header_value = header_value.as_bytes();
|
| 146 |
+
if !header_name.is_empty() && header_name[0] != b':' {
|
| 147 |
+
headers.append(PyTuple::new(py, [header_name, header_value].into_iter())?)?;
|
| 148 |
+
}
|
| 149 |
+
}
|
| 150 |
+
scope.set_item("headers", headers)?;
|
| 151 |
+
scope.set_item(
|
| 152 |
+
"client",
|
| 153 |
+
(
|
| 154 |
+
http_init_data
|
| 155 |
+
.socket_data
|
| 156 |
+
.remote_addr
|
| 157 |
+
.ip()
|
| 158 |
+
.to_canonical()
|
| 159 |
+
.to_string(),
|
| 160 |
+
http_init_data.socket_data.remote_addr.port(),
|
| 161 |
+
),
|
| 162 |
+
)?;
|
| 163 |
+
scope.set_item(
|
| 164 |
+
"server",
|
| 165 |
+
(
|
| 166 |
+
http_init_data
|
| 167 |
+
.socket_data
|
| 168 |
+
.local_addr
|
| 169 |
+
.ip()
|
| 170 |
+
.to_canonical()
|
| 171 |
+
.to_string(),
|
| 172 |
+
http_init_data.socket_data.local_addr.port(),
|
| 173 |
+
),
|
| 174 |
+
)?;
|
| 175 |
+
}
|
| 176 |
+
AsgiInitData::Websocket(websocket_init_data) => {
|
| 177 |
+
let path = websocket_init_data.uri.path().to_owned();
|
| 178 |
+
let query_string = websocket_init_data.uri.query().unwrap_or("").to_owned();
|
| 179 |
+
let original_request_uri = websocket_init_data.uri;
|
| 180 |
+
scope.set_item("type", "websocket")?;
|
| 181 |
+
scope_asgi.set_item("version", "2.5")?;
|
| 182 |
+
scope.set_item(
|
| 183 |
+
"http_version",
|
| 184 |
+
"1.1", // WebSocket is supported only on HTTP/1.1 in Ferron
|
| 185 |
+
)?;
|
| 186 |
+
scope.set_item(
|
| 187 |
+
"scheme",
|
| 188 |
+
if websocket_init_data.socket_data.encrypted {
|
| 189 |
+
"wss"
|
| 190 |
+
} else {
|
| 191 |
+
"ws"
|
| 192 |
+
},
|
| 193 |
+
)?;
|
| 194 |
+
scope.set_item("path", urlencoding::decode(&path)?)?;
|
| 195 |
+
scope.set_item("raw_path", original_request_uri.to_string().as_bytes())?;
|
| 196 |
+
scope.set_item("query_string", query_string.as_bytes())?;
|
| 197 |
+
if let Ok(script_path) = websocket_init_data
|
| 198 |
+
.execute_pathbuf
|
| 199 |
+
.as_path()
|
| 200 |
+
.strip_prefix(websocket_init_data.wwwroot)
|
| 201 |
+
{
|
| 202 |
+
scope.set_item(
|
| 203 |
+
"root_path",
|
| 204 |
+
format!(
|
| 205 |
+
"/{}",
|
| 206 |
+
match cfg!(windows) {
|
| 207 |
+
true => script_path.to_string_lossy().to_string().replace("\\", "/"),
|
| 208 |
+
false => script_path.to_string_lossy().to_string(),
|
| 209 |
+
}
|
| 210 |
+
),
|
| 211 |
+
)?;
|
| 212 |
+
}
|
| 213 |
+
// Ferron doesn't send original request headers (before WebSocket upgrade) to WebSocket request handlers
|
| 214 |
+
scope.set_item("headers", PyList::empty(py))?;
|
| 215 |
+
scope.set_item(
|
| 216 |
+
"client",
|
| 217 |
+
(
|
| 218 |
+
websocket_init_data
|
| 219 |
+
.socket_data
|
| 220 |
+
.remote_addr
|
| 221 |
+
.ip()
|
| 222 |
+
.to_canonical()
|
| 223 |
+
.to_string(),
|
| 224 |
+
websocket_init_data.socket_data.remote_addr.port(),
|
| 225 |
+
),
|
| 226 |
+
)?;
|
| 227 |
+
scope.set_item(
|
| 228 |
+
"server",
|
| 229 |
+
(
|
| 230 |
+
websocket_init_data
|
| 231 |
+
.socket_data
|
| 232 |
+
.local_addr
|
| 233 |
+
.ip()
|
| 234 |
+
.to_canonical()
|
| 235 |
+
.to_string(),
|
| 236 |
+
websocket_init_data.socket_data.local_addr.port(),
|
| 237 |
+
),
|
| 238 |
+
)?;
|
| 239 |
+
scope.set_item("subprotocols", PyList::empty(py))?;
|
| 240 |
+
}
|
| 241 |
+
};
|
| 242 |
+
|
| 243 |
+
scope_asgi.set_item("spec_version", "1.0")?;
|
| 244 |
+
scope.set_item("asgi", scope_asgi)?;
|
| 245 |
+
let scope_extensions = PyDict::new(py);
|
| 246 |
+
scope_extensions.set_item("http.response.trailers", PyDict::new(py))?;
|
| 247 |
+
scope.set_item("extensions", scope_extensions)?;
|
| 248 |
+
|
| 249 |
+
let client_disconnected = Arc::new(AtomicBool::new(false));
|
| 250 |
+
let client_disconnected_clone = client_disconnected.clone();
|
| 251 |
+
|
| 252 |
+
let receive = PyCFunction::new_closure(
|
| 253 |
+
py,
|
| 254 |
+
None,
|
| 255 |
+
None,
|
| 256 |
+
move |args: &Bound<'_, PyTuple>, _: Option<&Bound<'_, PyDict>>| -> PyResult<_> {
|
| 257 |
+
let rx = rx_clone.clone();
|
| 258 |
+
let client_disconnected = client_disconnected.clone();
|
| 259 |
+
Ok(
|
| 260 |
+
pyo3_async_runtimes::tokio::future_into_py(args.py(), async move {
|
| 261 |
+
if client_disconnected.load(Ordering::Relaxed) {
|
| 262 |
+
Err(PyErr::new::<PyOSError, _>("Client disconnected"))
|
| 263 |
+
} else {
|
| 264 |
+
let message = rx
|
| 265 |
+
.recv()
|
| 266 |
+
.await
|
| 267 |
+
.map_err(|e| PyErr::new::<PyOSError, _>(e.to_string()))?;
|
| 268 |
+
match message {
|
| 269 |
+
IncomingAsgiMessage::Init(_) => Err(PyErr::new::<PyOSError, _>(
|
| 270 |
+
"Unexpected ASGI initialization message",
|
| 271 |
+
)),
|
| 272 |
+
IncomingAsgiMessage::Message(message) => {
|
| 273 |
+
if let IncomingAsgiMessageInner::HttpDisconnect = &message {
|
| 274 |
+
client_disconnected.store(true, Ordering::Relaxed);
|
| 275 |
+
}
|
| 276 |
+
incoming_struct_to_asgi_event(message)
|
| 277 |
+
}
|
| 278 |
+
}
|
| 279 |
+
}
|
| 280 |
+
})?
|
| 281 |
+
.unbind(),
|
| 282 |
+
)
|
| 283 |
+
},
|
| 284 |
+
)?;
|
| 285 |
+
let send = PyCFunction::new_closure(
|
| 286 |
+
py,
|
| 287 |
+
None,
|
| 288 |
+
None,
|
| 289 |
+
move |args: &Bound<'_, PyTuple>, _: Option<&Bound<'_, PyDict>>| -> PyResult<_> {
|
| 290 |
+
let event = args.get_item(0)?.downcast::<PyDict>()?.clone();
|
| 291 |
+
let message = asgi_event_to_outgoing_struct(event)?;
|
| 292 |
+
let tx = tx_clone.clone();
|
| 293 |
+
let client_disconnected = client_disconnected_clone.clone();
|
| 294 |
+
Ok(
|
| 295 |
+
pyo3_async_runtimes::tokio::future_into_py(args.py(), async move {
|
| 296 |
+
if client_disconnected.load(Ordering::Relaxed) {
|
| 297 |
+
Err(PyErr::new::<PyOSError, _>("Client disconnected"))
|
| 298 |
+
} else {
|
| 299 |
+
tx.send(OutgoingAsgiMessage::Message(message))
|
| 300 |
+
.await
|
| 301 |
+
.map_err(|e| PyErr::new::<PyOSError, _>(e.to_string()))?;
|
| 302 |
+
Ok(())
|
| 303 |
+
}
|
| 304 |
+
})?
|
| 305 |
+
.unbind(),
|
| 306 |
+
)
|
| 307 |
+
},
|
| 308 |
+
)?;
|
| 309 |
+
|
| 310 |
+
let asgi_coroutine =
|
| 311 |
+
match asgi_application.call(py, (scope.clone(), receive.clone(), send.clone()), None) {
|
| 312 |
+
Ok(coroutine) => coroutine,
|
| 313 |
+
Err(err) => {
|
| 314 |
+
if !err.get_type(py).is(&PyType::new::<PyTypeError>(py)) {
|
| 315 |
+
return Err(err);
|
| 316 |
+
} else {
|
| 317 |
+
asgi_application
|
| 318 |
+
.call(py, (scope,), None)?
|
| 319 |
+
.call(py, (receive, send), None)?
|
| 320 |
+
}
|
| 321 |
+
}
|
| 322 |
+
};
|
| 323 |
+
|
| 324 |
+
pyo3_async_runtimes::tokio::into_future(asgi_coroutine.into_bound(py))
|
| 325 |
+
}) {
|
| 326 |
+
Err(err) => tx
|
| 327 |
+
.send(OutgoingAsgiMessage::Error(PyErr::new::<PyRuntimeError, _>(
|
| 328 |
+
err.to_string(),
|
| 329 |
+
)))
|
| 330 |
+
.await
|
| 331 |
+
.unwrap_or_default(),
|
| 332 |
+
Ok(asgi_future) => match asgi_future.await {
|
| 333 |
+
Err(err) => tx
|
| 334 |
+
.send(OutgoingAsgiMessage::Error(err))
|
| 335 |
+
.await
|
| 336 |
+
.unwrap_or_default(),
|
| 337 |
+
Ok(_) => tx
|
| 338 |
+
.send(OutgoingAsgiMessage::Finished)
|
| 339 |
+
.await
|
| 340 |
+
.unwrap_or_default(),
|
| 341 |
+
},
|
| 342 |
+
}
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
async fn asgi_lifetime_init_fn(asgi_applications: Vec<Arc<Py<PyAny>>>) -> Vec<AsgiChannelResult> {
|
| 346 |
+
let mut results = Vec::new();
|
| 347 |
+
for asgi_application in asgi_applications {
|
| 348 |
+
results.push(
|
| 349 |
+
async {
|
| 350 |
+
let (tx, rx_task) = async_channel::unbounded::<IncomingAsgiMessage>();
|
| 351 |
+
let (tx_task, rx) = async_channel::unbounded::<OutgoingAsgiMessage>();
|
| 352 |
+
if let Ok(locals) = Python::with_gil(pyo3_async_runtimes::tokio::get_current_locals) {
|
| 353 |
+
tokio::spawn(pyo3_async_runtimes::tokio::scope(
|
| 354 |
+
locals,
|
| 355 |
+
asgi_application_fn(asgi_application, tx_task, rx_task),
|
| 356 |
+
));
|
| 357 |
+
tx.send(IncomingAsgiMessage::Init(AsgiInitData::Lifespan))
|
| 358 |
+
.await
|
| 359 |
+
.map_err(|e| anyhow::anyhow!(e.to_string()))?;
|
| 360 |
+
Ok((tx, rx))
|
| 361 |
+
} else {
|
| 362 |
+
Err(anyhow::anyhow!("Cannot obtain task locals"))
|
| 363 |
+
}
|
| 364 |
+
}
|
| 365 |
+
.await,
|
| 366 |
+
);
|
| 367 |
+
}
|
| 368 |
+
results
|
| 369 |
+
}
|
| 370 |
+
|
| 371 |
+
async fn asgi_event_loop_fn(
|
| 372 |
+
asgi_application: Arc<Py<PyAny>>,
|
| 373 |
+
tx: Sender<AsgiChannelResult>,
|
| 374 |
+
rx: Receiver<()>,
|
| 375 |
+
) {
|
| 376 |
+
loop {
|
| 377 |
+
if rx.recv().await.is_err() {
|
| 378 |
+
continue;
|
| 379 |
+
}
|
| 380 |
+
|
| 381 |
+
let (tx_send, rx_task) = async_channel::unbounded::<IncomingAsgiMessage>();
|
| 382 |
+
let (tx_task, rx_send) = async_channel::unbounded::<OutgoingAsgiMessage>();
|
| 383 |
+
let asgi_application_cloned = asgi_application.clone();
|
| 384 |
+
if let Ok(locals) = Python::with_gil(pyo3_async_runtimes::tokio::get_current_locals) {
|
| 385 |
+
tokio::spawn(pyo3_async_runtimes::tokio::scope(
|
| 386 |
+
locals,
|
| 387 |
+
asgi_application_fn(asgi_application_cloned, tx_task, rx_task),
|
| 388 |
+
));
|
| 389 |
+
tx.send(Ok((tx_send, rx_send))).await.unwrap_or_default();
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
}
|
| 393 |
+
|
| 394 |
+
async fn asgi_init_event_loop_fn(
|
| 395 |
+
cancel_token: CancellationToken,
|
| 396 |
+
asgi_applications: Vec<Arc<Py<PyAny>>>,
|
| 397 |
+
mut channels: Vec<(Sender<AsgiChannelResult>, Receiver<()>)>,
|
| 398 |
+
) {
|
| 399 |
+
Python::with_gil(|py| {
|
| 400 |
+
// Try installing `uvloop`, when it fails, use `asyncio` fallback instead.
|
| 401 |
+
if let Ok(uvloop) = py.import("uvloop") {
|
| 402 |
+
let _ = uvloop.call_method0("install");
|
| 403 |
+
}
|
| 404 |
+
|
| 405 |
+
pyo3_async_runtimes::tokio::run::<_, ()>(py, async move {
|
| 406 |
+
let asgi_lifetime_channels = asgi_lifetime_init_fn(asgi_applications.clone()).await;
|
| 407 |
+
for asgi_lifetime_channel_result in &asgi_lifetime_channels {
|
| 408 |
+
if let Ok((tx, rx)) = asgi_lifetime_channel_result.as_ref() {
|
| 409 |
+
tx.send(IncomingAsgiMessage::Message(
|
| 410 |
+
IncomingAsgiMessageInner::LifespanStartup,
|
| 411 |
+
))
|
| 412 |
+
.await
|
| 413 |
+
.unwrap_or_default();
|
| 414 |
+
loop {
|
| 415 |
+
match rx.recv().await {
|
| 416 |
+
Ok(OutgoingAsgiMessage::Message(
|
| 417 |
+
OutgoingAsgiMessageInner::LifespanStartupComplete,
|
| 418 |
+
))
|
| 419 |
+
| Ok(OutgoingAsgiMessage::Message(
|
| 420 |
+
OutgoingAsgiMessageInner::LifespanStartupFailed(_),
|
| 421 |
+
))
|
| 422 |
+
| Ok(OutgoingAsgiMessage::Finished)
|
| 423 |
+
| Ok(OutgoingAsgiMessage::Error(_))
|
| 424 |
+
| Err(_) => break,
|
| 425 |
+
_ => (),
|
| 426 |
+
}
|
| 427 |
+
}
|
| 428 |
+
}
|
| 429 |
+
}
|
| 430 |
+
let init_closure = async move {
|
| 431 |
+
let mut channels_len = channels.len();
|
| 432 |
+
if let Some((tx_last, rx_last)) = channels.pop() {
|
| 433 |
+
channels_len -= 1;
|
| 434 |
+
let last_channel_id = channels_len;
|
| 435 |
+
for (tx, rx) in channels {
|
| 436 |
+
channels_len -= 1;
|
| 437 |
+
if let Ok(locals) = Python::with_gil(pyo3_async_runtimes::tokio::get_current_locals) {
|
| 438 |
+
tokio::spawn(pyo3_async_runtimes::tokio::scope(
|
| 439 |
+
locals,
|
| 440 |
+
asgi_event_loop_fn(asgi_applications[channels_len].clone(), tx, rx),
|
| 441 |
+
));
|
| 442 |
+
}
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
if let Ok(locals) = Python::with_gil(pyo3_async_runtimes::tokio::get_current_locals) {
|
| 446 |
+
tokio::spawn(pyo3_async_runtimes::tokio::scope(
|
| 447 |
+
locals,
|
| 448 |
+
asgi_event_loop_fn(asgi_applications[last_channel_id].clone(), tx_last, rx_last),
|
| 449 |
+
))
|
| 450 |
+
.await
|
| 451 |
+
.unwrap_or_default();
|
| 452 |
+
}
|
| 453 |
+
}
|
| 454 |
+
};
|
| 455 |
+
tokio::select! {
|
| 456 |
+
_ = cancel_token.cancelled() => {}
|
| 457 |
+
_ = init_closure => {}
|
| 458 |
+
}
|
| 459 |
+
for asgi_lifetime_channel_result in &asgi_lifetime_channels {
|
| 460 |
+
if let Ok((tx, rx)) = asgi_lifetime_channel_result.as_ref() {
|
| 461 |
+
tx.send(IncomingAsgiMessage::Message(
|
| 462 |
+
IncomingAsgiMessageInner::LifespanShutdown,
|
| 463 |
+
))
|
| 464 |
+
.await
|
| 465 |
+
.unwrap_or_default();
|
| 466 |
+
loop {
|
| 467 |
+
match rx.recv().await {
|
| 468 |
+
Ok(OutgoingAsgiMessage::Message(
|
| 469 |
+
OutgoingAsgiMessageInner::LifespanShutdownComplete,
|
| 470 |
+
))
|
| 471 |
+
| Ok(OutgoingAsgiMessage::Message(
|
| 472 |
+
OutgoingAsgiMessageInner::LifespanShutdownFailed(_),
|
| 473 |
+
))
|
| 474 |
+
| Ok(OutgoingAsgiMessage::Finished)
|
| 475 |
+
| Ok(OutgoingAsgiMessage::Error(_))
|
| 476 |
+
| Err(_) => break,
|
| 477 |
+
_ => (),
|
| 478 |
+
}
|
| 479 |
+
}
|
| 480 |
+
}
|
| 481 |
+
}
|
| 482 |
+
Ok(())
|
| 483 |
+
})
|
| 484 |
+
})
|
| 485 |
+
.unwrap_or_default();
|
| 486 |
+
}
|
| 487 |
+
|
| 488 |
+
pub fn load_asgi_application(
|
| 489 |
+
file_path: &Path,
|
| 490 |
+
clear_sys_path: bool,
|
| 491 |
+
) -> Result<Py<PyAny>, Box<dyn Error + Send + Sync>> {
|
| 492 |
+
let script_dirname = file_path
|
| 493 |
+
.parent()
|
| 494 |
+
.map(|path| path.to_string_lossy().to_string());
|
| 495 |
+
let script_name = file_path.to_string_lossy().to_string();
|
| 496 |
+
let script_name_cstring = CString::from_str(&script_name)?;
|
| 497 |
+
let module_name = script_name
|
| 498 |
+
.strip_suffix(".py")
|
| 499 |
+
.unwrap_or(&script_name)
|
| 500 |
+
.to_lowercase()
|
| 501 |
+
.chars()
|
| 502 |
+
.map(|c| if c.is_lowercase() { '_' } else { c })
|
| 503 |
+
.collect::<String>();
|
| 504 |
+
let module_name_cstring = CString::from_str(&module_name)?;
|
| 505 |
+
let script_data = std::fs::read_to_string(file_path)?;
|
| 506 |
+
let script_data_cstring = CString::from_str(&script_data)?;
|
| 507 |
+
let asgi_application = Python::with_gil(move |py| -> PyResult<Py<PyAny>> {
|
| 508 |
+
let mut sys_path_old = None;
|
| 509 |
+
if let Some(script_dirname) = script_dirname {
|
| 510 |
+
if let Ok(sys_module) = PyModule::import(py, "sys") {
|
| 511 |
+
if let Ok(sys_path_any) = sys_module.getattr("path") {
|
| 512 |
+
if let Ok(sys_path) = sys_path_any.downcast::<PyList>() {
|
| 513 |
+
let sys_path = sys_path.clone();
|
| 514 |
+
sys_path_old = sys_path.extract::<Vec<String>>().ok();
|
| 515 |
+
sys_path.insert(0, script_dirname).unwrap_or_default();
|
| 516 |
+
}
|
| 517 |
+
}
|
| 518 |
+
}
|
| 519 |
+
}
|
| 520 |
+
let asgi_application = PyModule::from_code(
|
| 521 |
+
py,
|
| 522 |
+
&script_data_cstring,
|
| 523 |
+
&script_name_cstring,
|
| 524 |
+
&module_name_cstring,
|
| 525 |
+
)?
|
| 526 |
+
.getattr("application")?
|
| 527 |
+
.unbind();
|
| 528 |
+
if clear_sys_path {
|
| 529 |
+
if let Some(sys_path) = sys_path_old {
|
| 530 |
+
if let Ok(sys_module) = PyModule::import(py, "sys") {
|
| 531 |
+
sys_module.setattr("path", sys_path).unwrap_or_default();
|
| 532 |
+
}
|
| 533 |
+
}
|
| 534 |
+
}
|
| 535 |
+
Ok(asgi_application)
|
| 536 |
+
})?;
|
| 537 |
+
Ok(asgi_application)
|
| 538 |
+
}
|
| 539 |
+
|
| 540 |
+
pub fn server_module_init(
|
| 541 |
+
config: &ServerConfig,
|
| 542 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 543 |
+
let mut asgi_applications = Vec::new();
|
| 544 |
+
let mut global_asgi_application_id = None;
|
| 545 |
+
let mut host_asgi_application_ids = Vec::new();
|
| 546 |
+
let clear_sys_path = config["global"]["asgiClearModuleImportPath"]
|
| 547 |
+
.as_bool()
|
| 548 |
+
.unwrap_or(false);
|
| 549 |
+
if let Some(asgi_application_path) = config["global"]["asgiApplicationPath"].as_str() {
|
| 550 |
+
let asgi_application_id = asgi_applications.len();
|
| 551 |
+
asgi_applications.push(Arc::new(load_asgi_application(
|
| 552 |
+
PathBuf::from_str(asgi_application_path)?.as_path(),
|
| 553 |
+
clear_sys_path,
|
| 554 |
+
)?));
|
| 555 |
+
global_asgi_application_id = Some(asgi_application_id);
|
| 556 |
+
}
|
| 557 |
+
let global_asgi_path = config["global"]["asgiPath"].as_str().map(|s| s.to_string());
|
| 558 |
+
|
| 559 |
+
if let Some(hosts) = config["hosts"].as_vec() {
|
| 560 |
+
for host_yaml in hosts.iter() {
|
| 561 |
+
let domain = host_yaml["domain"].as_str().map(String::from);
|
| 562 |
+
let ip = host_yaml["ip"].as_str().map(String::from);
|
| 563 |
+
let mut locations = Vec::new();
|
| 564 |
+
if let Some(locations_yaml) = host_yaml["locations"].as_vec() {
|
| 565 |
+
for location_yaml in locations_yaml.iter() {
|
| 566 |
+
if let Some(path_str) = location_yaml["path"].as_str() {
|
| 567 |
+
let path = String::from(path_str);
|
| 568 |
+
if let Some(asgi_application_path) = location_yaml["asgiApplicationPath"].as_str() {
|
| 569 |
+
let asgi_application_id = asgi_applications.len();
|
| 570 |
+
asgi_applications.push(Arc::new(load_asgi_application(
|
| 571 |
+
PathBuf::from_str(asgi_application_path)?.as_path(),
|
| 572 |
+
clear_sys_path,
|
| 573 |
+
)?));
|
| 574 |
+
|
| 575 |
+
locations.push(AsgiApplicationLocationWrap::new(
|
| 576 |
+
path,
|
| 577 |
+
asgi_application_id,
|
| 578 |
+
asgi_application_path.to_string(),
|
| 579 |
+
location_yaml["asgiPath"].as_str().map(|s| s.to_string()),
|
| 580 |
+
));
|
| 581 |
+
}
|
| 582 |
+
}
|
| 583 |
+
}
|
| 584 |
+
}
|
| 585 |
+
if let Some(asgi_application_path) = host_yaml["asgiApplicationPath"].as_str() {
|
| 586 |
+
let asgi_application_id = asgi_applications.len();
|
| 587 |
+
asgi_applications.push(Arc::new(load_asgi_application(
|
| 588 |
+
PathBuf::from_str(asgi_application_path)?.as_path(),
|
| 589 |
+
clear_sys_path,
|
| 590 |
+
)?));
|
| 591 |
+
host_asgi_application_ids.push(AsgiApplicationWrap::new(
|
| 592 |
+
domain,
|
| 593 |
+
ip,
|
| 594 |
+
Some(asgi_application_id),
|
| 595 |
+
Some(asgi_application_path.to_string()),
|
| 596 |
+
host_yaml["asgiPath"].as_str().map(|s| s.to_string()),
|
| 597 |
+
locations,
|
| 598 |
+
));
|
| 599 |
+
} else if !locations.is_empty() {
|
| 600 |
+
host_asgi_application_ids.push(AsgiApplicationWrap::new(
|
| 601 |
+
domain,
|
| 602 |
+
ip,
|
| 603 |
+
None,
|
| 604 |
+
None,
|
| 605 |
+
host_yaml["asgiPath"].as_str().map(|s| s.to_string()),
|
| 606 |
+
locations,
|
| 607 |
+
));
|
| 608 |
+
}
|
| 609 |
+
}
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
let cancel_token: CancellationToken = CancellationToken::new();
|
| 613 |
+
let cancel_token_thread = cancel_token.clone();
|
| 614 |
+
let mut asgi_event_loop_communication = Vec::new();
|
| 615 |
+
let mut asgi_event_loop_communication_thread = Vec::new();
|
| 616 |
+
|
| 617 |
+
for _ in 0..asgi_applications.len() {
|
| 618 |
+
let (tx, rx_thread) = async_channel::unbounded::<()>();
|
| 619 |
+
let (tx_thread, rx) = async_channel::unbounded::<AsgiChannelResult>();
|
| 620 |
+
asgi_event_loop_communication.push((tx, rx));
|
| 621 |
+
asgi_event_loop_communication_thread.push((tx_thread, rx_thread));
|
| 622 |
+
}
|
| 623 |
+
|
| 624 |
+
let available_parallelism = thread::available_parallelism()?.get();
|
| 625 |
+
|
| 626 |
+
// Initialize a single-threaded (due to Python's GIL) Tokio runtime to be used as an intermediary event loop for asynchronous Python
|
| 627 |
+
let mut runtime_builder = tokio::runtime::Builder::new_multi_thread();
|
| 628 |
+
runtime_builder
|
| 629 |
+
.worker_threads(1)
|
| 630 |
+
.enable_all()
|
| 631 |
+
.thread_name("python-async-pool");
|
| 632 |
+
pyo3_async_runtimes::tokio::init(runtime_builder);
|
| 633 |
+
|
| 634 |
+
// Create and spawn a task in the Tokio runtime for ASGI
|
| 635 |
+
let runtime = tokio::runtime::Builder::new_multi_thread()
|
| 636 |
+
.worker_threads(match available_parallelism / 2 {
|
| 637 |
+
0 => 1,
|
| 638 |
+
non_zero => non_zero,
|
| 639 |
+
})
|
| 640 |
+
.enable_all()
|
| 641 |
+
.thread_name("asgi-pool")
|
| 642 |
+
.build()?;
|
| 643 |
+
|
| 644 |
+
runtime.spawn(asgi_init_event_loop_fn(
|
| 645 |
+
cancel_token_thread,
|
| 646 |
+
asgi_applications,
|
| 647 |
+
asgi_event_loop_communication_thread,
|
| 648 |
+
));
|
| 649 |
+
|
| 650 |
+
Ok(Box::new(AsgiModule::new(
|
| 651 |
+
global_asgi_application_id,
|
| 652 |
+
global_asgi_path,
|
| 653 |
+
Arc::new(host_asgi_application_ids),
|
| 654 |
+
cancel_token,
|
| 655 |
+
asgi_event_loop_communication,
|
| 656 |
+
runtime,
|
| 657 |
+
)))
|
| 658 |
+
}
|
| 659 |
+
|
| 660 |
+
struct AsgiModule {
|
| 661 |
+
global_asgi_application_id: Option<usize>,
|
| 662 |
+
global_asgi_path: Option<String>,
|
| 663 |
+
host_asgi_application_ids: Arc<Vec<AsgiApplicationWrap>>,
|
| 664 |
+
cancel_token: CancellationToken,
|
| 665 |
+
asgi_event_loop_communication: AsgiEventLoopCommunication,
|
| 666 |
+
#[allow(dead_code)]
|
| 667 |
+
runtime: Runtime,
|
| 668 |
+
}
|
| 669 |
+
|
| 670 |
+
impl AsgiModule {
|
| 671 |
+
fn new(
|
| 672 |
+
global_asgi_application_id: Option<usize>,
|
| 673 |
+
global_asgi_path: Option<String>,
|
| 674 |
+
host_asgi_application_ids: Arc<Vec<AsgiApplicationWrap>>,
|
| 675 |
+
cancel_token: CancellationToken,
|
| 676 |
+
asgi_event_loop_communication: AsgiEventLoopCommunication,
|
| 677 |
+
runtime: Runtime,
|
| 678 |
+
) -> Self {
|
| 679 |
+
AsgiModule {
|
| 680 |
+
global_asgi_application_id,
|
| 681 |
+
global_asgi_path,
|
| 682 |
+
host_asgi_application_ids,
|
| 683 |
+
cancel_token,
|
| 684 |
+
asgi_event_loop_communication,
|
| 685 |
+
runtime,
|
| 686 |
+
}
|
| 687 |
+
}
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
impl ServerModule for AsgiModule {
|
| 691 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 692 |
+
Box::new(AsgiModuleHandlers {
|
| 693 |
+
global_asgi_application_id: self.global_asgi_application_id,
|
| 694 |
+
global_asgi_path: self.global_asgi_path.clone(),
|
| 695 |
+
host_asgi_application_ids: self.host_asgi_application_ids.clone(),
|
| 696 |
+
asgi_event_loop_communication: self.asgi_event_loop_communication.clone(),
|
| 697 |
+
handle,
|
| 698 |
+
})
|
| 699 |
+
}
|
| 700 |
+
}
|
| 701 |
+
|
| 702 |
+
impl Drop for AsgiModule {
|
| 703 |
+
fn drop(&mut self) {
|
| 704 |
+
self.cancel_token.cancel();
|
| 705 |
+
}
|
| 706 |
+
}
|
| 707 |
+
|
| 708 |
+
struct AsgiModuleHandlers {
|
| 709 |
+
global_asgi_application_id: Option<usize>,
|
| 710 |
+
global_asgi_path: Option<String>,
|
| 711 |
+
host_asgi_application_ids: Arc<Vec<AsgiApplicationWrap>>,
|
| 712 |
+
asgi_event_loop_communication: AsgiEventLoopCommunication,
|
| 713 |
+
handle: Handle,
|
| 714 |
+
}
|
| 715 |
+
|
| 716 |
+
#[async_trait]
|
| 717 |
+
impl ServerModuleHandlers for AsgiModuleHandlers {
|
| 718 |
+
async fn request_handler(
|
| 719 |
+
&mut self,
|
| 720 |
+
request: RequestData,
|
| 721 |
+
config: &ServerConfig,
|
| 722 |
+
socket_data: &SocketData,
|
| 723 |
+
error_logger: &ErrorLogger,
|
| 724 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 725 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 726 |
+
let hyper_request = request.get_hyper_request();
|
| 727 |
+
|
| 728 |
+
// Use .take() instead of .clone(), since the values in Options will only be used once.
|
| 729 |
+
let mut asgi_application_id = self.global_asgi_application_id.take();
|
| 730 |
+
let mut asgi_path = self.global_asgi_path.take();
|
| 731 |
+
|
| 732 |
+
// Should have used a HashMap instead of iterating over an array for better performance...
|
| 733 |
+
for host_asgi_application_wrap in self.host_asgi_application_ids.iter() {
|
| 734 |
+
if match_hostname(
|
| 735 |
+
match &host_asgi_application_wrap.domain {
|
| 736 |
+
Some(value) => Some(value as &str),
|
| 737 |
+
None => None,
|
| 738 |
+
},
|
| 739 |
+
match hyper_request.headers().get(header::HOST) {
|
| 740 |
+
Some(value) => value.to_str().ok(),
|
| 741 |
+
None => None,
|
| 742 |
+
},
|
| 743 |
+
) && match &host_asgi_application_wrap.ip {
|
| 744 |
+
Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
|
| 745 |
+
None => true,
|
| 746 |
+
} {
|
| 747 |
+
asgi_application_id = host_asgi_application_wrap.asgi_application_id;
|
| 748 |
+
asgi_path = host_asgi_application_wrap.asgi_path.clone();
|
| 749 |
+
if let Ok(path_decoded) = urlencoding::decode(
|
| 750 |
+
request
|
| 751 |
+
.get_original_url()
|
| 752 |
+
.unwrap_or(request.get_hyper_request().uri())
|
| 753 |
+
.path(),
|
| 754 |
+
) {
|
| 755 |
+
for location_wrap in host_asgi_application_wrap.locations.iter() {
|
| 756 |
+
if match_location(&location_wrap.path, &path_decoded) {
|
| 757 |
+
asgi_application_id = Some(location_wrap.asgi_application_id);
|
| 758 |
+
asgi_path = location_wrap.asgi_path.clone();
|
| 759 |
+
break;
|
| 760 |
+
}
|
| 761 |
+
}
|
| 762 |
+
}
|
| 763 |
+
break;
|
| 764 |
+
}
|
| 765 |
+
}
|
| 766 |
+
|
| 767 |
+
let request_path = hyper_request.uri().path();
|
| 768 |
+
let mut request_path_bytes = request_path.bytes();
|
| 769 |
+
if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
|
| 770 |
+
return Ok(
|
| 771 |
+
ResponseData::builder(request)
|
| 772 |
+
.status(StatusCode::BAD_REQUEST)
|
| 773 |
+
.build(),
|
| 774 |
+
);
|
| 775 |
+
}
|
| 776 |
+
|
| 777 |
+
if let Some(asgi_application_id) = asgi_application_id {
|
| 778 |
+
let asgi_path = asgi_path.unwrap_or("/".to_string());
|
| 779 |
+
let mut canonical_asgi_path: &str = &asgi_path;
|
| 780 |
+
if canonical_asgi_path.bytes().last() == Some(b'/') {
|
| 781 |
+
canonical_asgi_path = &canonical_asgi_path[..(canonical_asgi_path.len() - 1)];
|
| 782 |
+
}
|
| 783 |
+
|
| 784 |
+
let request_path_with_slashes = match request_path == canonical_asgi_path {
|
| 785 |
+
true => format!("{}/", request_path),
|
| 786 |
+
false => request_path.to_string(),
|
| 787 |
+
};
|
| 788 |
+
if let Some(stripped_request_path) =
|
| 789 |
+
request_path_with_slashes.strip_prefix(canonical_asgi_path)
|
| 790 |
+
{
|
| 791 |
+
let wwwroot_yaml = &config["wwwroot"];
|
| 792 |
+
let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
|
| 793 |
+
|
| 794 |
+
let wwwroot_unknown = PathBuf::from(wwwroot);
|
| 795 |
+
let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
|
| 796 |
+
true => wwwroot_unknown,
|
| 797 |
+
false => match fs::canonicalize(&wwwroot_unknown).await {
|
| 798 |
+
Ok(pathbuf) => pathbuf,
|
| 799 |
+
Err(_) => wwwroot_unknown,
|
| 800 |
+
},
|
| 801 |
+
};
|
| 802 |
+
let wwwroot = wwwroot_pathbuf.as_path();
|
| 803 |
+
|
| 804 |
+
let mut relative_path = &request_path[1..];
|
| 805 |
+
while relative_path.as_bytes().first().copied() == Some(b'/') {
|
| 806 |
+
relative_path = &relative_path[1..];
|
| 807 |
+
}
|
| 808 |
+
|
| 809 |
+
let decoded_relative_path = match urlencoding::decode(relative_path) {
|
| 810 |
+
Ok(path) => path.to_string(),
|
| 811 |
+
Err(_) => {
|
| 812 |
+
return Ok(
|
| 813 |
+
ResponseData::builder(request)
|
| 814 |
+
.status(StatusCode::BAD_REQUEST)
|
| 815 |
+
.build(),
|
| 816 |
+
);
|
| 817 |
+
}
|
| 818 |
+
};
|
| 819 |
+
|
| 820 |
+
let joined_pathbuf = wwwroot.join(decoded_relative_path);
|
| 821 |
+
let execute_pathbuf = joined_pathbuf;
|
| 822 |
+
let execute_path_info = stripped_request_path
|
| 823 |
+
.strip_prefix("/")
|
| 824 |
+
.map(|s| s.to_string());
|
| 825 |
+
|
| 826 |
+
let (tx, rx) = {
|
| 827 |
+
let (tx, rx) = &self.asgi_event_loop_communication[asgi_application_id];
|
| 828 |
+
tx.send(()).await?;
|
| 829 |
+
rx.recv().await??
|
| 830 |
+
};
|
| 831 |
+
|
| 832 |
+
return execute_asgi(
|
| 833 |
+
request,
|
| 834 |
+
socket_data,
|
| 835 |
+
error_logger,
|
| 836 |
+
wwwroot,
|
| 837 |
+
execute_pathbuf,
|
| 838 |
+
execute_path_info,
|
| 839 |
+
config["serverAdministratorEmail"].as_str(),
|
| 840 |
+
tx,
|
| 841 |
+
rx,
|
| 842 |
+
)
|
| 843 |
+
.await;
|
| 844 |
+
}
|
| 845 |
+
}
|
| 846 |
+
Ok(ResponseData::builder(request).build())
|
| 847 |
+
})
|
| 848 |
+
.await
|
| 849 |
+
}
|
| 850 |
+
|
| 851 |
+
async fn proxy_request_handler(
|
| 852 |
+
&mut self,
|
| 853 |
+
request: RequestData,
|
| 854 |
+
_config: &ServerConfig,
|
| 855 |
+
_socket_data: &SocketData,
|
| 856 |
+
_error_logger: &ErrorLogger,
|
| 857 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 858 |
+
Ok(ResponseData::builder(request).build())
|
| 859 |
+
}
|
| 860 |
+
|
| 861 |
+
async fn response_modifying_handler(
|
| 862 |
+
&mut self,
|
| 863 |
+
response: HyperResponse,
|
| 864 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 865 |
+
Ok(response)
|
| 866 |
+
}
|
| 867 |
+
|
| 868 |
+
async fn proxy_response_modifying_handler(
|
| 869 |
+
&mut self,
|
| 870 |
+
response: HyperResponse,
|
| 871 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 872 |
+
Ok(response)
|
| 873 |
+
}
|
| 874 |
+
|
| 875 |
+
async fn connect_proxy_request_handler(
|
| 876 |
+
&mut self,
|
| 877 |
+
_upgraded_request: HyperUpgraded,
|
| 878 |
+
_connect_address: &str,
|
| 879 |
+
_config: &ServerConfig,
|
| 880 |
+
_socket_data: &SocketData,
|
| 881 |
+
_error_logger: &ErrorLogger,
|
| 882 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 883 |
+
Ok(())
|
| 884 |
+
}
|
| 885 |
+
|
| 886 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 887 |
+
false
|
| 888 |
+
}
|
| 889 |
+
|
| 890 |
+
async fn websocket_request_handler(
|
| 891 |
+
&mut self,
|
| 892 |
+
websocket: HyperWebsocket,
|
| 893 |
+
uri: &hyper::Uri,
|
| 894 |
+
config: &ServerConfig,
|
| 895 |
+
socket_data: &SocketData,
|
| 896 |
+
error_logger: &ErrorLogger,
|
| 897 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 898 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 899 |
+
// Use .take() instead of .clone(), since the values in Options will only be used once.
|
| 900 |
+
let mut asgi_application_id = self.global_asgi_application_id.take();
|
| 901 |
+
let mut asgi_path = self.global_asgi_path.take();
|
| 902 |
+
|
| 903 |
+
// Should have used a HashMap instead of iterating over an array for better performance...
|
| 904 |
+
for host_asgi_application_wrap in self.host_asgi_application_ids.iter() {
|
| 905 |
+
// Workaround for Ferron not providing the domain name for WebSocket connections
|
| 906 |
+
let config_test_domain = host_asgi_application_wrap
|
| 907 |
+
.domain
|
| 908 |
+
.as_ref()
|
| 909 |
+
.map(|value| value as &str);
|
| 910 |
+
let obtained_domain = config["domain"].as_str();
|
| 911 |
+
if config_test_domain == obtained_domain
|
| 912 |
+
&& config["asgiApplicationPath"].as_str()
|
| 913 |
+
== host_asgi_application_wrap.asgi_application_path.as_deref()
|
| 914 |
+
&& match &host_asgi_application_wrap.ip {
|
| 915 |
+
Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
|
| 916 |
+
None => true,
|
| 917 |
+
}
|
| 918 |
+
{
|
| 919 |
+
asgi_application_id = host_asgi_application_wrap.asgi_application_id;
|
| 920 |
+
asgi_path = host_asgi_application_wrap.asgi_path.clone();
|
| 921 |
+
if let Ok(path_decoded) = urlencoding::decode(uri.path()) {
|
| 922 |
+
for location_wrap in host_asgi_application_wrap.locations.iter() {
|
| 923 |
+
if match_location(&location_wrap.path, &path_decoded) {
|
| 924 |
+
asgi_application_id = Some(location_wrap.asgi_application_id);
|
| 925 |
+
asgi_path = location_wrap.asgi_path.clone();
|
| 926 |
+
break;
|
| 927 |
+
}
|
| 928 |
+
}
|
| 929 |
+
}
|
| 930 |
+
break;
|
| 931 |
+
}
|
| 932 |
+
}
|
| 933 |
+
|
| 934 |
+
let request_path = uri.path();
|
| 935 |
+
let mut request_path_bytes = request_path.bytes();
|
| 936 |
+
if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
|
| 937 |
+
return Ok(());
|
| 938 |
+
}
|
| 939 |
+
|
| 940 |
+
if let Some(asgi_application_id) = asgi_application_id {
|
| 941 |
+
let asgi_path = asgi_path.unwrap_or("/".to_string());
|
| 942 |
+
let mut canonical_asgi_path: &str = &asgi_path;
|
| 943 |
+
if canonical_asgi_path.bytes().last() == Some(b'/') {
|
| 944 |
+
canonical_asgi_path = &canonical_asgi_path[..(canonical_asgi_path.len() - 1)];
|
| 945 |
+
}
|
| 946 |
+
|
| 947 |
+
let request_path_with_slashes = match request_path == canonical_asgi_path {
|
| 948 |
+
true => format!("{}/", request_path),
|
| 949 |
+
false => request_path.to_string(),
|
| 950 |
+
};
|
| 951 |
+
if let Some(stripped_request_path) =
|
| 952 |
+
request_path_with_slashes.strip_prefix(canonical_asgi_path)
|
| 953 |
+
{
|
| 954 |
+
let wwwroot_yaml = &config["wwwroot"];
|
| 955 |
+
let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
|
| 956 |
+
|
| 957 |
+
let wwwroot_unknown = PathBuf::from(wwwroot);
|
| 958 |
+
let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
|
| 959 |
+
true => wwwroot_unknown,
|
| 960 |
+
false => match fs::canonicalize(&wwwroot_unknown).await {
|
| 961 |
+
Ok(pathbuf) => pathbuf,
|
| 962 |
+
Err(_) => wwwroot_unknown,
|
| 963 |
+
},
|
| 964 |
+
};
|
| 965 |
+
let wwwroot = wwwroot_pathbuf.as_path();
|
| 966 |
+
|
| 967 |
+
let mut relative_path = &request_path[1..];
|
| 968 |
+
while relative_path.as_bytes().first().copied() == Some(b'/') {
|
| 969 |
+
relative_path = &relative_path[1..];
|
| 970 |
+
}
|
| 971 |
+
|
| 972 |
+
let decoded_relative_path = match urlencoding::decode(relative_path) {
|
| 973 |
+
Ok(path) => path.to_string(),
|
| 974 |
+
Err(_) => {
|
| 975 |
+
return Ok(());
|
| 976 |
+
}
|
| 977 |
+
};
|
| 978 |
+
|
| 979 |
+
let joined_pathbuf = wwwroot.join(decoded_relative_path);
|
| 980 |
+
let execute_pathbuf = joined_pathbuf;
|
| 981 |
+
let execute_path_info = stripped_request_path
|
| 982 |
+
.strip_prefix("/")
|
| 983 |
+
.map(|s| s.to_string());
|
| 984 |
+
|
| 985 |
+
let (tx, rx) = {
|
| 986 |
+
let (tx, rx) = &self.asgi_event_loop_communication[asgi_application_id];
|
| 987 |
+
tx.send(()).await?;
|
| 988 |
+
rx.recv().await??
|
| 989 |
+
};
|
| 990 |
+
|
| 991 |
+
return execute_asgi_websocket(
|
| 992 |
+
websocket,
|
| 993 |
+
uri,
|
| 994 |
+
socket_data,
|
| 995 |
+
error_logger,
|
| 996 |
+
wwwroot,
|
| 997 |
+
execute_pathbuf,
|
| 998 |
+
execute_path_info,
|
| 999 |
+
config["serverAdministratorEmail"].as_str(),
|
| 1000 |
+
tx,
|
| 1001 |
+
rx,
|
| 1002 |
+
)
|
| 1003 |
+
.await;
|
| 1004 |
+
}
|
| 1005 |
+
}
|
| 1006 |
+
Ok(())
|
| 1007 |
+
})
|
| 1008 |
+
.await
|
| 1009 |
+
}
|
| 1010 |
+
|
| 1011 |
+
fn does_websocket_requests(&mut self, config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 1012 |
+
config["asgiApplicationPath"].as_str().is_some()
|
| 1013 |
+
}
|
| 1014 |
+
}
|
| 1015 |
+
|
| 1016 |
+
#[allow(clippy::too_many_arguments)]
|
| 1017 |
+
async fn execute_asgi(
|
| 1018 |
+
request: RequestData,
|
| 1019 |
+
socket_data: &SocketData,
|
| 1020 |
+
error_logger: &ErrorLogger,
|
| 1021 |
+
wwwroot: &Path,
|
| 1022 |
+
execute_pathbuf: PathBuf,
|
| 1023 |
+
_path_info: Option<String>,
|
| 1024 |
+
_server_administrator_email: Option<&str>,
|
| 1025 |
+
asgi_tx: Sender<IncomingAsgiMessage>,
|
| 1026 |
+
asgi_rx: Receiver<OutgoingAsgiMessage>,
|
| 1027 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 1028 |
+
let (hyper_request, _, original_request_uri) = request.into_parts();
|
| 1029 |
+
let (hyper_request_parts, request_body) = hyper_request.into_parts();
|
| 1030 |
+
asgi_tx
|
| 1031 |
+
.send(IncomingAsgiMessage::Init(AsgiInitData::Http(
|
| 1032 |
+
AsgiHttpInitData {
|
| 1033 |
+
hyper_request_parts,
|
| 1034 |
+
original_request_uri,
|
| 1035 |
+
socket_data: SocketData {
|
| 1036 |
+
remote_addr: socket_data.remote_addr,
|
| 1037 |
+
local_addr: socket_data.local_addr,
|
| 1038 |
+
encrypted: socket_data.encrypted,
|
| 1039 |
+
},
|
| 1040 |
+
error_logger: error_logger.clone(),
|
| 1041 |
+
wwwroot: wwwroot.to_path_buf(),
|
| 1042 |
+
execute_pathbuf,
|
| 1043 |
+
},
|
| 1044 |
+
)))
|
| 1045 |
+
.await?;
|
| 1046 |
+
|
| 1047 |
+
let mut request_body_stream = request_body.into_data_stream();
|
| 1048 |
+
let asgi_tx_clone = asgi_tx.clone();
|
| 1049 |
+
|
| 1050 |
+
tokio::spawn(async move {
|
| 1051 |
+
loop {
|
| 1052 |
+
match request_body_stream.next().await {
|
| 1053 |
+
Some(Ok(data)) => asgi_tx_clone
|
| 1054 |
+
.send(IncomingAsgiMessage::Message(
|
| 1055 |
+
IncomingAsgiMessageInner::HttpRequest(AsgiHttpBody {
|
| 1056 |
+
body: data.to_vec(),
|
| 1057 |
+
more_body: true,
|
| 1058 |
+
}),
|
| 1059 |
+
))
|
| 1060 |
+
.await
|
| 1061 |
+
.unwrap_or_default(),
|
| 1062 |
+
Some(Err(_)) => {
|
| 1063 |
+
asgi_tx_clone
|
| 1064 |
+
.send(IncomingAsgiMessage::Message(
|
| 1065 |
+
IncomingAsgiMessageInner::HttpDisconnect,
|
| 1066 |
+
))
|
| 1067 |
+
.await
|
| 1068 |
+
.unwrap_or_default();
|
| 1069 |
+
}
|
| 1070 |
+
None => {
|
| 1071 |
+
asgi_tx_clone
|
| 1072 |
+
.send(IncomingAsgiMessage::Message(
|
| 1073 |
+
IncomingAsgiMessageInner::HttpRequest(AsgiHttpBody {
|
| 1074 |
+
body: b"".to_vec(),
|
| 1075 |
+
more_body: false,
|
| 1076 |
+
}),
|
| 1077 |
+
))
|
| 1078 |
+
.await
|
| 1079 |
+
.unwrap_or_default();
|
| 1080 |
+
break;
|
| 1081 |
+
}
|
| 1082 |
+
}
|
| 1083 |
+
}
|
| 1084 |
+
});
|
| 1085 |
+
|
| 1086 |
+
let asgi_http_response_start;
|
| 1087 |
+
|
| 1088 |
+
loop {
|
| 1089 |
+
match asgi_rx.recv().await? {
|
| 1090 |
+
OutgoingAsgiMessage::Finished => Err(anyhow::anyhow!(
|
| 1091 |
+
"ASGI application returned before sending the HTTP response start event"
|
| 1092 |
+
))?,
|
| 1093 |
+
OutgoingAsgiMessage::Error(err) => Err(err)?,
|
| 1094 |
+
OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::HttpResponseStart(
|
| 1095 |
+
http_response_start,
|
| 1096 |
+
)) => {
|
| 1097 |
+
asgi_http_response_start = http_response_start;
|
| 1098 |
+
break;
|
| 1099 |
+
}
|
| 1100 |
+
_ => (),
|
| 1101 |
+
}
|
| 1102 |
+
}
|
| 1103 |
+
|
| 1104 |
+
let response_body_stream = futures_util::stream::unfold(
|
| 1105 |
+
(asgi_tx, asgi_rx, false),
|
| 1106 |
+
move |(asgi_tx, asgi_rx, request_end)| {
|
| 1107 |
+
let has_trailers = asgi_http_response_start.trailers;
|
| 1108 |
+
async move {
|
| 1109 |
+
if request_end {
|
| 1110 |
+
asgi_tx
|
| 1111 |
+
.send(IncomingAsgiMessage::Message(
|
| 1112 |
+
IncomingAsgiMessageInner::HttpDisconnect,
|
| 1113 |
+
))
|
| 1114 |
+
.await
|
| 1115 |
+
.unwrap_or_default();
|
| 1116 |
+
return None;
|
| 1117 |
+
}
|
| 1118 |
+
loop {
|
| 1119 |
+
match asgi_rx.recv().await {
|
| 1120 |
+
Err(err) => {
|
| 1121 |
+
return Some((
|
| 1122 |
+
Err(std::io::Error::other(err.to_string())),
|
| 1123 |
+
(asgi_tx, asgi_rx, false),
|
| 1124 |
+
))
|
| 1125 |
+
}
|
| 1126 |
+
Ok(OutgoingAsgiMessage::Finished) => return None,
|
| 1127 |
+
Ok(OutgoingAsgiMessage::Error(err)) => {
|
| 1128 |
+
return Some((
|
| 1129 |
+
Err(std::io::Error::other(err.to_string())),
|
| 1130 |
+
(asgi_tx, asgi_rx, false),
|
| 1131 |
+
))
|
| 1132 |
+
}
|
| 1133 |
+
Ok(OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::HttpResponseBody(
|
| 1134 |
+
http_response_body,
|
| 1135 |
+
))) => {
|
| 1136 |
+
if !http_response_body.more_body {
|
| 1137 |
+
if http_response_body.body.is_empty() {
|
| 1138 |
+
if !has_trailers {
|
| 1139 |
+
asgi_tx
|
| 1140 |
+
.send(IncomingAsgiMessage::Message(
|
| 1141 |
+
IncomingAsgiMessageInner::HttpDisconnect,
|
| 1142 |
+
))
|
| 1143 |
+
.await
|
| 1144 |
+
.unwrap_or_default();
|
| 1145 |
+
return None;
|
| 1146 |
+
}
|
| 1147 |
+
} else {
|
| 1148 |
+
return Some((
|
| 1149 |
+
Ok(Frame::data(Bytes::from(http_response_body.body))),
|
| 1150 |
+
(asgi_tx, asgi_rx, !has_trailers),
|
| 1151 |
+
));
|
| 1152 |
+
}
|
| 1153 |
+
} else if !http_response_body.body.is_empty() {
|
| 1154 |
+
return Some((
|
| 1155 |
+
Ok(Frame::data(Bytes::from(http_response_body.body))),
|
| 1156 |
+
(asgi_tx, asgi_rx, false),
|
| 1157 |
+
));
|
| 1158 |
+
}
|
| 1159 |
+
}
|
| 1160 |
+
Ok(OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::HttpResponseTrailers(
|
| 1161 |
+
http_response_trailers,
|
| 1162 |
+
))) => {
|
| 1163 |
+
if !http_response_trailers.more_trailers {
|
| 1164 |
+
if http_response_trailers.headers.is_empty() {
|
| 1165 |
+
asgi_tx
|
| 1166 |
+
.send(IncomingAsgiMessage::Message(
|
| 1167 |
+
IncomingAsgiMessageInner::HttpDisconnect,
|
| 1168 |
+
))
|
| 1169 |
+
.await
|
| 1170 |
+
.unwrap_or_default();
|
| 1171 |
+
return None;
|
| 1172 |
+
} else {
|
| 1173 |
+
match async {
|
| 1174 |
+
let mut headers = HeaderMap::new();
|
| 1175 |
+
for (header_name, header_value) in http_response_trailers.headers {
|
| 1176 |
+
if !header_name.is_empty() && header_name[0] != b':' {
|
| 1177 |
+
headers.append(
|
| 1178 |
+
HeaderName::from_bytes(&header_name)?,
|
| 1179 |
+
HeaderValue::from_bytes(&header_value)?,
|
| 1180 |
+
);
|
| 1181 |
+
}
|
| 1182 |
+
}
|
| 1183 |
+
Ok::<_, Box<dyn Error + Send + Sync>>(headers)
|
| 1184 |
+
}
|
| 1185 |
+
.await
|
| 1186 |
+
{
|
| 1187 |
+
Ok(headers) => {
|
| 1188 |
+
return Some((Ok(Frame::trailers(headers)), (asgi_tx, asgi_rx, true)))
|
| 1189 |
+
}
|
| 1190 |
+
Err(err) => {
|
| 1191 |
+
return Some((
|
| 1192 |
+
Err(std::io::Error::other(err.to_string())),
|
| 1193 |
+
(asgi_tx, asgi_rx, false),
|
| 1194 |
+
))
|
| 1195 |
+
}
|
| 1196 |
+
}
|
| 1197 |
+
}
|
| 1198 |
+
} else if !http_response_trailers.headers.is_empty() {
|
| 1199 |
+
match async {
|
| 1200 |
+
let mut headers = HeaderMap::new();
|
| 1201 |
+
for (header_name, header_value) in http_response_trailers.headers {
|
| 1202 |
+
if !header_name.is_empty() && header_name[0] != b':' {
|
| 1203 |
+
headers.append(
|
| 1204 |
+
HeaderName::from_bytes(&header_name)?,
|
| 1205 |
+
HeaderValue::from_bytes(&header_value)?,
|
| 1206 |
+
);
|
| 1207 |
+
}
|
| 1208 |
+
}
|
| 1209 |
+
Ok::<_, Box<dyn Error + Send + Sync>>(headers)
|
| 1210 |
+
}
|
| 1211 |
+
.await
|
| 1212 |
+
{
|
| 1213 |
+
Ok(headers) => {
|
| 1214 |
+
return Some((Ok(Frame::trailers(headers)), (asgi_tx, asgi_rx, true)))
|
| 1215 |
+
}
|
| 1216 |
+
Err(err) => {
|
| 1217 |
+
return Some((
|
| 1218 |
+
Err(std::io::Error::other(err.to_string())),
|
| 1219 |
+
(asgi_tx, asgi_rx, false),
|
| 1220 |
+
))
|
| 1221 |
+
}
|
| 1222 |
+
}
|
| 1223 |
+
}
|
| 1224 |
+
}
|
| 1225 |
+
_ => (),
|
| 1226 |
+
}
|
| 1227 |
+
}
|
| 1228 |
+
}
|
| 1229 |
+
},
|
| 1230 |
+
);
|
| 1231 |
+
let response_body = BodyExt::boxed(StreamBody::new(response_body_stream));
|
| 1232 |
+
|
| 1233 |
+
let mut hyper_response = Response::new(response_body);
|
| 1234 |
+
*hyper_response.status_mut() = StatusCode::from_u16(asgi_http_response_start.status)?;
|
| 1235 |
+
let headers = hyper_response.headers_mut();
|
| 1236 |
+
for (header_name, header_value) in asgi_http_response_start.headers {
|
| 1237 |
+
if !header_name.is_empty() && header_name[0] != b':' {
|
| 1238 |
+
headers.append(
|
| 1239 |
+
HeaderName::from_bytes(&header_name)?,
|
| 1240 |
+
HeaderValue::from_bytes(&header_value)?,
|
| 1241 |
+
);
|
| 1242 |
+
}
|
| 1243 |
+
}
|
| 1244 |
+
|
| 1245 |
+
Ok(
|
| 1246 |
+
ResponseData::builder_without_request()
|
| 1247 |
+
.response(hyper_response)
|
| 1248 |
+
.build(),
|
| 1249 |
+
)
|
| 1250 |
+
}
|
| 1251 |
+
|
| 1252 |
+
#[allow(clippy::too_many_arguments)]
|
| 1253 |
+
async fn execute_asgi_websocket(
|
| 1254 |
+
websocket: HyperWebsocket,
|
| 1255 |
+
uri: &hyper::Uri,
|
| 1256 |
+
socket_data: &SocketData,
|
| 1257 |
+
error_logger: &ErrorLogger,
|
| 1258 |
+
wwwroot: &Path,
|
| 1259 |
+
execute_pathbuf: PathBuf,
|
| 1260 |
+
_path_info: Option<String>,
|
| 1261 |
+
_server_administrator_email: Option<&str>,
|
| 1262 |
+
asgi_tx: Sender<IncomingAsgiMessage>,
|
| 1263 |
+
asgi_rx: Receiver<OutgoingAsgiMessage>,
|
| 1264 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 1265 |
+
asgi_tx
|
| 1266 |
+
.send(IncomingAsgiMessage::Init(AsgiInitData::Websocket(
|
| 1267 |
+
AsgiWebsocketInitData {
|
| 1268 |
+
uri: uri.to_owned(),
|
| 1269 |
+
socket_data: SocketData {
|
| 1270 |
+
remote_addr: socket_data.remote_addr,
|
| 1271 |
+
local_addr: socket_data.local_addr,
|
| 1272 |
+
encrypted: socket_data.encrypted,
|
| 1273 |
+
},
|
| 1274 |
+
error_logger: error_logger.clone(),
|
| 1275 |
+
wwwroot: wwwroot.to_path_buf(),
|
| 1276 |
+
execute_pathbuf,
|
| 1277 |
+
},
|
| 1278 |
+
)))
|
| 1279 |
+
.await?;
|
| 1280 |
+
|
| 1281 |
+
asgi_tx
|
| 1282 |
+
.send(IncomingAsgiMessage::Message(
|
| 1283 |
+
IncomingAsgiMessageInner::WebsocketConnect,
|
| 1284 |
+
))
|
| 1285 |
+
.await?;
|
| 1286 |
+
|
| 1287 |
+
let client_bi_stream;
|
| 1288 |
+
loop {
|
| 1289 |
+
match asgi_rx.recv().await? {
|
| 1290 |
+
OutgoingAsgiMessage::Finished => Err(anyhow::anyhow!(
|
| 1291 |
+
"ASGI application returned before sending the WebSocket accept event"
|
| 1292 |
+
))?,
|
| 1293 |
+
OutgoingAsgiMessage::Error(err) => Err(err)?,
|
| 1294 |
+
OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::WebsocketAccept(_)) => {
|
| 1295 |
+
client_bi_stream = websocket.await?;
|
| 1296 |
+
break;
|
| 1297 |
+
}
|
| 1298 |
+
OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::WebsocketClose(_)) => {
|
| 1299 |
+
asgi_tx
|
| 1300 |
+
.send(IncomingAsgiMessage::Message(
|
| 1301 |
+
IncomingAsgiMessageInner::WebsocketDisconnect(AsgiWebsocketClose {
|
| 1302 |
+
code: 1005,
|
| 1303 |
+
reason: "ASGI application closed the WebSocket connection before accepting it"
|
| 1304 |
+
.to_string(),
|
| 1305 |
+
}),
|
| 1306 |
+
))
|
| 1307 |
+
.await
|
| 1308 |
+
.unwrap_or_default();
|
| 1309 |
+
}
|
| 1310 |
+
_ => (),
|
| 1311 |
+
}
|
| 1312 |
+
}
|
| 1313 |
+
|
| 1314 |
+
let (client_sink, mut client_stream) = client_bi_stream.split();
|
| 1315 |
+
|
| 1316 |
+
let client_disconnected_mutex = Arc::new(Mutex::new(AtomicBool::new(false)));
|
| 1317 |
+
let client_disconnected_mutex_clone = client_disconnected_mutex.clone();
|
| 1318 |
+
|
| 1319 |
+
let asgi_tx_clone = asgi_tx.clone();
|
| 1320 |
+
let (ping, pong) = async_channel::unbounded();
|
| 1321 |
+
|
| 1322 |
+
tokio::spawn(async move {
|
| 1323 |
+
while let Some(websocket_frame) = client_stream.next().await {
|
| 1324 |
+
match websocket_frame {
|
| 1325 |
+
Err(_) => {
|
| 1326 |
+
let client_disconnected = client_disconnected_mutex_clone.lock().await;
|
| 1327 |
+
if !client_disconnected.load(Ordering::Relaxed) {
|
| 1328 |
+
client_disconnected.store(true, Ordering::Relaxed);
|
| 1329 |
+
asgi_tx_clone
|
| 1330 |
+
.send(IncomingAsgiMessage::Message(
|
| 1331 |
+
IncomingAsgiMessageInner::WebsocketDisconnect(AsgiWebsocketClose {
|
| 1332 |
+
code: 1005,
|
| 1333 |
+
reason: "Error while receiving WebSocket data".to_string(),
|
| 1334 |
+
}),
|
| 1335 |
+
))
|
| 1336 |
+
.await
|
| 1337 |
+
.unwrap_or_default();
|
| 1338 |
+
}
|
| 1339 |
+
}
|
| 1340 |
+
Ok(Message::Ping(message)) => {
|
| 1341 |
+
ping.send(message).await.unwrap_or_default();
|
| 1342 |
+
}
|
| 1343 |
+
Ok(Message::Binary(message)) => {
|
| 1344 |
+
asgi_tx_clone
|
| 1345 |
+
.send(IncomingAsgiMessage::Message(
|
| 1346 |
+
IncomingAsgiMessageInner::WebsocketReceive(AsgiWebsocketMessage {
|
| 1347 |
+
bytes: Some(message.to_vec()),
|
| 1348 |
+
text: None,
|
| 1349 |
+
}),
|
| 1350 |
+
))
|
| 1351 |
+
.await
|
| 1352 |
+
.unwrap_or_default();
|
| 1353 |
+
}
|
| 1354 |
+
Ok(Message::Text(message)) => {
|
| 1355 |
+
asgi_tx_clone
|
| 1356 |
+
.send(IncomingAsgiMessage::Message(
|
| 1357 |
+
IncomingAsgiMessageInner::WebsocketReceive(AsgiWebsocketMessage {
|
| 1358 |
+
bytes: None,
|
| 1359 |
+
text: Some(message.to_string()),
|
| 1360 |
+
}),
|
| 1361 |
+
))
|
| 1362 |
+
.await
|
| 1363 |
+
.unwrap_or_default();
|
| 1364 |
+
}
|
| 1365 |
+
Ok(Message::Close(close_frame)) => {
|
| 1366 |
+
let client_disconnected = client_disconnected_mutex_clone.lock().await;
|
| 1367 |
+
if !client_disconnected.load(Ordering::Relaxed) {
|
| 1368 |
+
client_disconnected.store(true, Ordering::Relaxed);
|
| 1369 |
+
client_disconnected_mutex_clone
|
| 1370 |
+
.lock()
|
| 1371 |
+
.await
|
| 1372 |
+
.store(true, Ordering::Relaxed);
|
| 1373 |
+
let (status_code, message) = if let Some(close_frame) = close_frame {
|
| 1374 |
+
(close_frame.code.into(), close_frame.reason.to_string())
|
| 1375 |
+
} else {
|
| 1376 |
+
(
|
| 1377 |
+
1005,
|
| 1378 |
+
"Websocket connection closed for unknown reason".to_string(),
|
| 1379 |
+
)
|
| 1380 |
+
};
|
| 1381 |
+
asgi_tx_clone
|
| 1382 |
+
.send(IncomingAsgiMessage::Message(
|
| 1383 |
+
IncomingAsgiMessageInner::WebsocketDisconnect(AsgiWebsocketClose {
|
| 1384 |
+
code: status_code,
|
| 1385 |
+
reason: message,
|
| 1386 |
+
}),
|
| 1387 |
+
))
|
| 1388 |
+
.await
|
| 1389 |
+
.unwrap_or_default();
|
| 1390 |
+
}
|
| 1391 |
+
}
|
| 1392 |
+
_ => (),
|
| 1393 |
+
}
|
| 1394 |
+
}
|
| 1395 |
+
});
|
| 1396 |
+
|
| 1397 |
+
let client_sink_mutex = Arc::new(Mutex::new(client_sink));
|
| 1398 |
+
let client_sink_mutex_cloned = client_sink_mutex.clone();
|
| 1399 |
+
|
| 1400 |
+
tokio::spawn(async move {
|
| 1401 |
+
while let Ok(message) = pong.recv().await {
|
| 1402 |
+
if client_sink_mutex_cloned
|
| 1403 |
+
.lock()
|
| 1404 |
+
.await
|
| 1405 |
+
.send(Message::Pong(message))
|
| 1406 |
+
.await
|
| 1407 |
+
.is_err()
|
| 1408 |
+
{
|
| 1409 |
+
break;
|
| 1410 |
+
}
|
| 1411 |
+
}
|
| 1412 |
+
});
|
| 1413 |
+
|
| 1414 |
+
loop {
|
| 1415 |
+
match asgi_rx.recv().await? {
|
| 1416 |
+
OutgoingAsgiMessage::Finished => Err(anyhow::anyhow!(
|
| 1417 |
+
"ASGI application returned before sending the WebSocket accept event"
|
| 1418 |
+
))?,
|
| 1419 |
+
OutgoingAsgiMessage::Error(err) => Err(err)?,
|
| 1420 |
+
OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::WebsocketSend(websocket_message)) => {
|
| 1421 |
+
let frame_option = if let Some(bytes) = websocket_message.bytes {
|
| 1422 |
+
Some(Message::binary(bytes))
|
| 1423 |
+
} else {
|
| 1424 |
+
websocket_message.text.map(Message::text)
|
| 1425 |
+
};
|
| 1426 |
+
if let Some(frame) = frame_option {
|
| 1427 |
+
let mut client_sink = client_sink_mutex.lock().await;
|
| 1428 |
+
if let Err(err) = client_sink.send(frame).await {
|
| 1429 |
+
drop(client_sink);
|
| 1430 |
+
let client_disconnected = client_disconnected_mutex.lock().await;
|
| 1431 |
+
if !client_disconnected.load(Ordering::Relaxed) {
|
| 1432 |
+
client_disconnected.store(true, Ordering::Relaxed);
|
| 1433 |
+
asgi_tx
|
| 1434 |
+
.send(IncomingAsgiMessage::Message(
|
| 1435 |
+
IncomingAsgiMessageInner::WebsocketDisconnect(AsgiWebsocketClose {
|
| 1436 |
+
code: 1005,
|
| 1437 |
+
reason: "Error while sending WebSocket data".to_string(),
|
| 1438 |
+
}),
|
| 1439 |
+
))
|
| 1440 |
+
.await
|
| 1441 |
+
.unwrap_or_default();
|
| 1442 |
+
}
|
| 1443 |
+
Err(err)?;
|
| 1444 |
+
}
|
| 1445 |
+
}
|
| 1446 |
+
}
|
| 1447 |
+
OutgoingAsgiMessage::Message(OutgoingAsgiMessageInner::WebsocketClose(websocket_close)) => {
|
| 1448 |
+
let client_disconnected = client_disconnected_mutex.lock().await;
|
| 1449 |
+
if !client_disconnected.load(Ordering::Relaxed) {
|
| 1450 |
+
client_disconnected.store(true, Ordering::Relaxed);
|
| 1451 |
+
asgi_tx
|
| 1452 |
+
.send(IncomingAsgiMessage::Message(
|
| 1453 |
+
IncomingAsgiMessageInner::WebsocketDisconnect(AsgiWebsocketClose {
|
| 1454 |
+
code: websocket_close.code,
|
| 1455 |
+
reason: websocket_close.reason.clone(),
|
| 1456 |
+
}),
|
| 1457 |
+
))
|
| 1458 |
+
.await
|
| 1459 |
+
.unwrap_or_default();
|
| 1460 |
+
}
|
| 1461 |
+
let mut client_sink = client_sink_mutex.lock().await;
|
| 1462 |
+
client_sink
|
| 1463 |
+
.send(Message::Close(Some(CloseFrame {
|
| 1464 |
+
code: websocket_close.code.into(),
|
| 1465 |
+
reason: websocket_close.reason.into(),
|
| 1466 |
+
})))
|
| 1467 |
+
.await?;
|
| 1468 |
+
client_sink.close().await.unwrap_or_default();
|
| 1469 |
+
break;
|
| 1470 |
+
}
|
| 1471 |
+
_ => (),
|
| 1472 |
+
}
|
| 1473 |
+
}
|
| 1474 |
+
|
| 1475 |
+
Ok(())
|
| 1476 |
+
}
|
ferron/src/optional_modules/cache.rs
ADDED
|
@@ -0,0 +1,525 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::collections::HashMap;
|
| 2 |
+
use std::error::Error;
|
| 3 |
+
use std::hash::RandomState;
|
| 4 |
+
use std::sync::Arc;
|
| 5 |
+
use std::time::{Duration, Instant};
|
| 6 |
+
|
| 7 |
+
use crate::ferron_common::{
|
| 8 |
+
ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 9 |
+
ServerModuleHandlers, SocketData,
|
| 10 |
+
};
|
| 11 |
+
use crate::ferron_common::{HyperResponse, WithRuntime};
|
| 12 |
+
use async_trait::async_trait;
|
| 13 |
+
use cache_control::{Cachability, CacheControl};
|
| 14 |
+
use futures_util::{StreamExt, TryStreamExt};
|
| 15 |
+
use hashlink::LinkedHashMap;
|
| 16 |
+
use http_body_util::{BodyExt, Full, StreamBody};
|
| 17 |
+
use hyper::body::{Bytes, Frame};
|
| 18 |
+
use hyper::header::HeaderValue;
|
| 19 |
+
use hyper::{header, HeaderMap, Method, Response, StatusCode};
|
| 20 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 21 |
+
use itertools::Itertools;
|
| 22 |
+
use tokio::runtime::Handle;
|
| 23 |
+
use tokio::sync::RwLock;
|
| 24 |
+
|
| 25 |
+
const CACHE_HEADER_NAME: &str = "X-Ferron-Cache";
|
| 26 |
+
const DEFAULT_MAX_AGE: u64 = 300;
|
| 27 |
+
|
| 28 |
+
pub fn server_module_init(
|
| 29 |
+
config: &ServerConfig,
|
| 30 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 31 |
+
let maximum_cache_entries = config["global"]["maximumCacheEntries"]
|
| 32 |
+
.as_i64()
|
| 33 |
+
.map(|v| v as usize);
|
| 34 |
+
|
| 35 |
+
Ok(Box::new(CacheModule::new(
|
| 36 |
+
Arc::new(RwLock::new(LinkedHashMap::with_hasher(RandomState::new()))),
|
| 37 |
+
Arc::new(RwLock::new(HashMap::new())),
|
| 38 |
+
maximum_cache_entries,
|
| 39 |
+
)))
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
#[allow(clippy::type_complexity)]
|
| 43 |
+
struct CacheModule {
|
| 44 |
+
cache: Arc<
|
| 45 |
+
RwLock<
|
| 46 |
+
LinkedHashMap<
|
| 47 |
+
String,
|
| 48 |
+
(
|
| 49 |
+
StatusCode,
|
| 50 |
+
HeaderMap,
|
| 51 |
+
Vec<u8>,
|
| 52 |
+
Instant,
|
| 53 |
+
Option<CacheControl>,
|
| 54 |
+
),
|
| 55 |
+
RandomState,
|
| 56 |
+
>,
|
| 57 |
+
>,
|
| 58 |
+
>,
|
| 59 |
+
vary_cache: Arc<RwLock<HashMap<String, Vec<String>>>>,
|
| 60 |
+
maximum_cache_entries: Option<usize>,
|
| 61 |
+
}
|
| 62 |
+
|
| 63 |
+
impl CacheModule {
|
| 64 |
+
#[allow(clippy::type_complexity)]
|
| 65 |
+
fn new(
|
| 66 |
+
cache: Arc<
|
| 67 |
+
RwLock<
|
| 68 |
+
LinkedHashMap<
|
| 69 |
+
String,
|
| 70 |
+
(
|
| 71 |
+
StatusCode,
|
| 72 |
+
HeaderMap,
|
| 73 |
+
Vec<u8>,
|
| 74 |
+
Instant,
|
| 75 |
+
Option<CacheControl>,
|
| 76 |
+
),
|
| 77 |
+
RandomState,
|
| 78 |
+
>,
|
| 79 |
+
>,
|
| 80 |
+
>,
|
| 81 |
+
vary_cache: Arc<RwLock<HashMap<String, Vec<String>>>>,
|
| 82 |
+
maximum_cache_entries: Option<usize>,
|
| 83 |
+
) -> Self {
|
| 84 |
+
Self {
|
| 85 |
+
cache,
|
| 86 |
+
vary_cache,
|
| 87 |
+
maximum_cache_entries,
|
| 88 |
+
}
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
impl ServerModule for CacheModule {
|
| 93 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 94 |
+
Box::new(CacheModuleHandlers {
|
| 95 |
+
cache: self.cache.clone(),
|
| 96 |
+
vary_cache: self.vary_cache.clone(),
|
| 97 |
+
maximum_cache_entries: self.maximum_cache_entries,
|
| 98 |
+
cache_vary_headers_configured: Vec::new(),
|
| 99 |
+
cache_ignore_headers_configured: Vec::new(),
|
| 100 |
+
maximum_cached_response_size: None,
|
| 101 |
+
cache_key: None,
|
| 102 |
+
request_headers: HeaderMap::new(),
|
| 103 |
+
has_authorization: false,
|
| 104 |
+
cached: false,
|
| 105 |
+
no_store: false,
|
| 106 |
+
handle,
|
| 107 |
+
})
|
| 108 |
+
}
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
#[allow(clippy::type_complexity)]
|
| 112 |
+
struct CacheModuleHandlers {
|
| 113 |
+
handle: Handle,
|
| 114 |
+
cache: Arc<
|
| 115 |
+
RwLock<
|
| 116 |
+
LinkedHashMap<
|
| 117 |
+
String,
|
| 118 |
+
(
|
| 119 |
+
StatusCode,
|
| 120 |
+
HeaderMap,
|
| 121 |
+
Vec<u8>,
|
| 122 |
+
Instant,
|
| 123 |
+
Option<CacheControl>,
|
| 124 |
+
),
|
| 125 |
+
RandomState,
|
| 126 |
+
>,
|
| 127 |
+
>,
|
| 128 |
+
>,
|
| 129 |
+
vary_cache: Arc<RwLock<HashMap<String, Vec<String>>>>,
|
| 130 |
+
maximum_cache_entries: Option<usize>,
|
| 131 |
+
cache_vary_headers_configured: Vec<String>,
|
| 132 |
+
cache_ignore_headers_configured: Vec<String>,
|
| 133 |
+
maximum_cached_response_size: Option<u64>,
|
| 134 |
+
cache_key: Option<String>,
|
| 135 |
+
request_headers: HeaderMap<HeaderValue>,
|
| 136 |
+
has_authorization: bool,
|
| 137 |
+
cached: bool,
|
| 138 |
+
no_store: bool,
|
| 139 |
+
}
|
| 140 |
+
|
| 141 |
+
#[async_trait]
|
| 142 |
+
impl ServerModuleHandlers for CacheModuleHandlers {
|
| 143 |
+
async fn request_handler(
|
| 144 |
+
&mut self,
|
| 145 |
+
request: RequestData,
|
| 146 |
+
config: &ServerConfig,
|
| 147 |
+
socket_data: &SocketData,
|
| 148 |
+
_error_logger: &ErrorLogger,
|
| 149 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 150 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 151 |
+
self.cache_vary_headers_configured = match config["cacheVaryHeaders"].as_vec() {
|
| 152 |
+
Some(vector) => {
|
| 153 |
+
let mut new_vector = Vec::new();
|
| 154 |
+
for yaml_value in vector.iter() {
|
| 155 |
+
if let Some(str_value) = yaml_value.as_str() {
|
| 156 |
+
new_vector.push(str_value.to_string());
|
| 157 |
+
}
|
| 158 |
+
}
|
| 159 |
+
new_vector
|
| 160 |
+
}
|
| 161 |
+
None => Vec::new(),
|
| 162 |
+
};
|
| 163 |
+
self.cache_ignore_headers_configured = match config["cacheIgnoreHeaders"].as_vec() {
|
| 164 |
+
Some(vector) => {
|
| 165 |
+
let mut new_vector = Vec::new();
|
| 166 |
+
for yaml_value in vector.iter() {
|
| 167 |
+
if let Some(str_value) = yaml_value.as_str() {
|
| 168 |
+
new_vector.push(str_value.to_string());
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
new_vector
|
| 172 |
+
}
|
| 173 |
+
None => Vec::new(),
|
| 174 |
+
};
|
| 175 |
+
self.maximum_cached_response_size = config["maximumCachedResponseSize"]
|
| 176 |
+
.as_i64()
|
| 177 |
+
.map(|f| f as u64);
|
| 178 |
+
|
| 179 |
+
let hyper_request = request.get_hyper_request();
|
| 180 |
+
let cache_key = format!(
|
| 181 |
+
"{} {}{}{}{}",
|
| 182 |
+
hyper_request.method().as_str(),
|
| 183 |
+
match socket_data.encrypted {
|
| 184 |
+
false => "http://",
|
| 185 |
+
true => "https://",
|
| 186 |
+
},
|
| 187 |
+
match hyper_request.headers().get(header::HOST) {
|
| 188 |
+
Some(host) => String::from_utf8_lossy(host.as_bytes()).into_owned(),
|
| 189 |
+
None => "".to_string(),
|
| 190 |
+
},
|
| 191 |
+
hyper_request.uri().path(),
|
| 192 |
+
match hyper_request.uri().query() {
|
| 193 |
+
Some(query) => format!("?{}", query),
|
| 194 |
+
None => "".to_string(),
|
| 195 |
+
}
|
| 196 |
+
);
|
| 197 |
+
|
| 198 |
+
let request_cache_control = match hyper_request.headers().get(header::CACHE_CONTROL) {
|
| 199 |
+
Some(value) => CacheControl::from_value(&String::from_utf8_lossy(value.as_bytes())),
|
| 200 |
+
None => None,
|
| 201 |
+
};
|
| 202 |
+
|
| 203 |
+
let mut no_store = false;
|
| 204 |
+
let mut no_cache = false;
|
| 205 |
+
|
| 206 |
+
if let Some(request_cache_control) = request_cache_control {
|
| 207 |
+
no_store = request_cache_control.no_store;
|
| 208 |
+
if let Some(cachability) = request_cache_control.cachability {
|
| 209 |
+
if cachability == Cachability::NoCache {
|
| 210 |
+
no_cache = true;
|
| 211 |
+
}
|
| 212 |
+
}
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
match hyper_request.method() {
|
| 216 |
+
&Method::GET | &Method::HEAD => (),
|
| 217 |
+
_ => {
|
| 218 |
+
no_store = true;
|
| 219 |
+
}
|
| 220 |
+
};
|
| 221 |
+
|
| 222 |
+
if no_store {
|
| 223 |
+
self.no_store = true;
|
| 224 |
+
return Ok(ResponseData::builder(request).build());
|
| 225 |
+
}
|
| 226 |
+
|
| 227 |
+
if !no_cache {
|
| 228 |
+
let rwlock_read = self.vary_cache.read().await;
|
| 229 |
+
let processed_vary = rwlock_read.get(&cache_key);
|
| 230 |
+
if let Some(processed_vary) = processed_vary {
|
| 231 |
+
let cache_key_with_vary = format!(
|
| 232 |
+
"{}\n{}",
|
| 233 |
+
&cache_key,
|
| 234 |
+
processed_vary
|
| 235 |
+
.iter()
|
| 236 |
+
.map(|header_name| {
|
| 237 |
+
match hyper_request.headers().get(header_name) {
|
| 238 |
+
Some(header_value) => format!(
|
| 239 |
+
"{}: {}",
|
| 240 |
+
header_name,
|
| 241 |
+
String::from_utf8_lossy(header_value.as_bytes()).into_owned()
|
| 242 |
+
),
|
| 243 |
+
None => "".to_string(),
|
| 244 |
+
}
|
| 245 |
+
})
|
| 246 |
+
.collect::<Vec<String>>()
|
| 247 |
+
.join("\n")
|
| 248 |
+
);
|
| 249 |
+
|
| 250 |
+
drop(rwlock_read);
|
| 251 |
+
|
| 252 |
+
let rwlock_read = self.cache.read().await;
|
| 253 |
+
let cached_entry_option = rwlock_read.get(&cache_key_with_vary);
|
| 254 |
+
|
| 255 |
+
if let Some((status_code, headers, body, timestamp, response_cache_control)) =
|
| 256 |
+
cached_entry_option
|
| 257 |
+
{
|
| 258 |
+
let max_age = match response_cache_control {
|
| 259 |
+
Some(response_cache_control) => match response_cache_control.s_max_age {
|
| 260 |
+
Some(s_max_age) => Some(s_max_age),
|
| 261 |
+
None => response_cache_control.max_age,
|
| 262 |
+
},
|
| 263 |
+
None => None,
|
| 264 |
+
};
|
| 265 |
+
|
| 266 |
+
let mut cached = true;
|
| 267 |
+
|
| 268 |
+
if timestamp.elapsed() > max_age.unwrap_or(Duration::from_secs(DEFAULT_MAX_AGE)) {
|
| 269 |
+
cached = false;
|
| 270 |
+
}
|
| 271 |
+
|
| 272 |
+
if cached {
|
| 273 |
+
self.cached = true;
|
| 274 |
+
let mut hyper_response_builder = Response::builder().status(status_code);
|
| 275 |
+
for (header_name, header_value) in headers.iter() {
|
| 276 |
+
hyper_response_builder = hyper_response_builder.header(header_name, header_value);
|
| 277 |
+
}
|
| 278 |
+
let hyper_response = hyper_response_builder.body(
|
| 279 |
+
Full::new(Bytes::from(body.clone()))
|
| 280 |
+
.map_err(|e| match e {})
|
| 281 |
+
.boxed(),
|
| 282 |
+
)?;
|
| 283 |
+
return Ok(
|
| 284 |
+
ResponseData::builder(request)
|
| 285 |
+
.response(hyper_response)
|
| 286 |
+
.build(),
|
| 287 |
+
);
|
| 288 |
+
} else {
|
| 289 |
+
drop(rwlock_read);
|
| 290 |
+
}
|
| 291 |
+
}
|
| 292 |
+
} else {
|
| 293 |
+
drop(rwlock_read);
|
| 294 |
+
}
|
| 295 |
+
}
|
| 296 |
+
|
| 297 |
+
self.request_headers = hyper_request.headers().clone();
|
| 298 |
+
self.cache_key = Some(cache_key);
|
| 299 |
+
self.has_authorization = hyper_request.headers().contains_key(header::AUTHORIZATION);
|
| 300 |
+
|
| 301 |
+
Ok(ResponseData::builder(request).build())
|
| 302 |
+
})
|
| 303 |
+
.await
|
| 304 |
+
}
|
| 305 |
+
|
| 306 |
+
async fn proxy_request_handler(
|
| 307 |
+
&mut self,
|
| 308 |
+
request: RequestData,
|
| 309 |
+
_config: &ServerConfig,
|
| 310 |
+
_socket_data: &SocketData,
|
| 311 |
+
_error_logger: &ErrorLogger,
|
| 312 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 313 |
+
Ok(ResponseData::builder(request).build())
|
| 314 |
+
}
|
| 315 |
+
|
| 316 |
+
async fn response_modifying_handler(
|
| 317 |
+
&mut self,
|
| 318 |
+
mut response: HyperResponse,
|
| 319 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 320 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 321 |
+
if self.no_store {
|
| 322 |
+
response
|
| 323 |
+
.headers_mut()
|
| 324 |
+
.insert(CACHE_HEADER_NAME, HeaderValue::from_str("BYPASS")?);
|
| 325 |
+
Ok(response)
|
| 326 |
+
} else if self.cached {
|
| 327 |
+
response
|
| 328 |
+
.headers_mut()
|
| 329 |
+
.insert(CACHE_HEADER_NAME, HeaderValue::from_str("HIT")?);
|
| 330 |
+
Ok(response)
|
| 331 |
+
} else if let Some(cache_key) = &self.cache_key {
|
| 332 |
+
let (mut response_parts, mut response_body) = response.into_parts();
|
| 333 |
+
let response_cache_control = match response_parts.headers.get(header::CACHE_CONTROL) {
|
| 334 |
+
Some(value) => CacheControl::from_value(&String::from_utf8_lossy(value.as_bytes())),
|
| 335 |
+
None => None,
|
| 336 |
+
};
|
| 337 |
+
|
| 338 |
+
let should_cache_response = match &response_cache_control {
|
| 339 |
+
Some(response_cache_control) => {
|
| 340 |
+
let is_private = response_cache_control.cachability == Some(Cachability::Private);
|
| 341 |
+
let is_public = response_cache_control.cachability == Some(Cachability::Public);
|
| 342 |
+
|
| 343 |
+
!response_cache_control.no_store
|
| 344 |
+
&& !is_private
|
| 345 |
+
&& (is_public
|
| 346 |
+
|| (!self.has_authorization
|
| 347 |
+
&& (response_cache_control.max_age.is_some()
|
| 348 |
+
|| response_cache_control.s_max_age.is_some())))
|
| 349 |
+
}
|
| 350 |
+
None => false,
|
| 351 |
+
};
|
| 352 |
+
|
| 353 |
+
if should_cache_response {
|
| 354 |
+
let mut response_body_buffer = Vec::new();
|
| 355 |
+
let mut maximum_cached_response_size_exceeded = false;
|
| 356 |
+
|
| 357 |
+
while let Some(frame) = response_body.frame().await {
|
| 358 |
+
let frame_unwrapped = frame?;
|
| 359 |
+
if frame_unwrapped.is_data() {
|
| 360 |
+
if let Some(bytes) = frame_unwrapped.data_ref() {
|
| 361 |
+
response_body_buffer.extend_from_slice(bytes);
|
| 362 |
+
if let Some(maximum_cached_response_size) = self.maximum_cached_response_size {
|
| 363 |
+
if response_body_buffer.len() as u64 > maximum_cached_response_size {
|
| 364 |
+
maximum_cached_response_size_exceeded = true;
|
| 365 |
+
break;
|
| 366 |
+
}
|
| 367 |
+
}
|
| 368 |
+
}
|
| 369 |
+
}
|
| 370 |
+
}
|
| 371 |
+
|
| 372 |
+
if maximum_cached_response_size_exceeded {
|
| 373 |
+
let cached_stream =
|
| 374 |
+
futures_util::stream::once(async move { Ok(Bytes::from(response_body_buffer)) });
|
| 375 |
+
let response_stream = response_body.into_data_stream();
|
| 376 |
+
let chained_stream = cached_stream.chain(response_stream);
|
| 377 |
+
let stream_body = StreamBody::new(chained_stream.map_ok(Frame::data));
|
| 378 |
+
let response_body = BodyExt::boxed(stream_body);
|
| 379 |
+
response_parts
|
| 380 |
+
.headers
|
| 381 |
+
.insert(CACHE_HEADER_NAME, HeaderValue::from_str("MISS")?);
|
| 382 |
+
let response = Response::from_parts(response_parts, response_body);
|
| 383 |
+
Ok(response)
|
| 384 |
+
} else {
|
| 385 |
+
let mut response_vary = match response_parts.headers.get(header::VARY) {
|
| 386 |
+
Some(value) => String::from_utf8_lossy(value.as_bytes())
|
| 387 |
+
.split(",")
|
| 388 |
+
.map(|s| s.trim().to_owned())
|
| 389 |
+
.collect(),
|
| 390 |
+
None => Vec::new(),
|
| 391 |
+
};
|
| 392 |
+
|
| 393 |
+
let mut processed_vary_orig = self.cache_vary_headers_configured.clone();
|
| 394 |
+
processed_vary_orig.append(&mut response_vary);
|
| 395 |
+
|
| 396 |
+
let processed_vary = processed_vary_orig
|
| 397 |
+
.iter()
|
| 398 |
+
.unique()
|
| 399 |
+
.map(|s| s.to_owned())
|
| 400 |
+
.collect::<Vec<String>>();
|
| 401 |
+
|
| 402 |
+
if !processed_vary.contains(&"*".to_string()) {
|
| 403 |
+
let cache_key_with_vary = format!(
|
| 404 |
+
"{}\n{}",
|
| 405 |
+
&cache_key,
|
| 406 |
+
processed_vary
|
| 407 |
+
.iter()
|
| 408 |
+
.map(|header_name| {
|
| 409 |
+
match self.request_headers.get(header_name) {
|
| 410 |
+
Some(header_value) => format!(
|
| 411 |
+
"{}: {}",
|
| 412 |
+
header_name,
|
| 413 |
+
String::from_utf8_lossy(header_value.as_bytes()).into_owned()
|
| 414 |
+
),
|
| 415 |
+
None => "".to_string(),
|
| 416 |
+
}
|
| 417 |
+
})
|
| 418 |
+
.collect::<Vec<String>>()
|
| 419 |
+
.join("\n")
|
| 420 |
+
);
|
| 421 |
+
|
| 422 |
+
let mut rwlock_write = self.vary_cache.write().await;
|
| 423 |
+
rwlock_write.insert(cache_key.clone(), processed_vary);
|
| 424 |
+
drop(rwlock_write);
|
| 425 |
+
|
| 426 |
+
let mut written_headers = response_parts.headers.clone();
|
| 427 |
+
for header in self.cache_ignore_headers_configured.iter() {
|
| 428 |
+
while written_headers.remove(header).is_some() {}
|
| 429 |
+
}
|
| 430 |
+
|
| 431 |
+
let mut rwlock_write = self.cache.write().await;
|
| 432 |
+
rwlock_write.retain(|_, (_, _, _, timestamp, response_cache_control)| {
|
| 433 |
+
let max_age = match response_cache_control {
|
| 434 |
+
Some(response_cache_control) => match response_cache_control.s_max_age {
|
| 435 |
+
Some(s_max_age) => Some(s_max_age),
|
| 436 |
+
None => response_cache_control.max_age,
|
| 437 |
+
},
|
| 438 |
+
None => None,
|
| 439 |
+
};
|
| 440 |
+
|
| 441 |
+
timestamp.elapsed() <= max_age.unwrap_or(Duration::from_secs(DEFAULT_MAX_AGE))
|
| 442 |
+
});
|
| 443 |
+
|
| 444 |
+
if let Some(maximum_cache_entries) = self.maximum_cache_entries {
|
| 445 |
+
// Remove a value at the front of the list
|
| 446 |
+
while !rwlock_write.is_empty() && rwlock_write.len() >= maximum_cache_entries {
|
| 447 |
+
rwlock_write.pop_front();
|
| 448 |
+
}
|
| 449 |
+
}
|
| 450 |
+
|
| 451 |
+
// This inserts a value at the back of the list
|
| 452 |
+
rwlock_write.insert(
|
| 453 |
+
cache_key_with_vary,
|
| 454 |
+
(
|
| 455 |
+
response_parts.status,
|
| 456 |
+
written_headers,
|
| 457 |
+
response_body_buffer.clone(),
|
| 458 |
+
Instant::now(),
|
| 459 |
+
response_cache_control,
|
| 460 |
+
),
|
| 461 |
+
);
|
| 462 |
+
drop(rwlock_write);
|
| 463 |
+
}
|
| 464 |
+
|
| 465 |
+
let cached_stream =
|
| 466 |
+
futures_util::stream::once(async move { Ok(Bytes::from(response_body_buffer)) });
|
| 467 |
+
let stream_body = StreamBody::new(cached_stream.map_ok(Frame::data));
|
| 468 |
+
let response_body = BodyExt::boxed(stream_body);
|
| 469 |
+
response_parts
|
| 470 |
+
.headers
|
| 471 |
+
.insert(CACHE_HEADER_NAME, HeaderValue::from_str("MISS")?);
|
| 472 |
+
let response = Response::from_parts(response_parts, response_body);
|
| 473 |
+
Ok(response)
|
| 474 |
+
}
|
| 475 |
+
} else {
|
| 476 |
+
response_parts
|
| 477 |
+
.headers
|
| 478 |
+
.insert(CACHE_HEADER_NAME, HeaderValue::from_str("MISS")?);
|
| 479 |
+
let response = Response::from_parts(response_parts, response_body);
|
| 480 |
+
Ok(response)
|
| 481 |
+
}
|
| 482 |
+
} else {
|
| 483 |
+
Ok(response)
|
| 484 |
+
}
|
| 485 |
+
})
|
| 486 |
+
.await
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
async fn proxy_response_modifying_handler(
|
| 490 |
+
&mut self,
|
| 491 |
+
response: HyperResponse,
|
| 492 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 493 |
+
Ok(response)
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
async fn connect_proxy_request_handler(
|
| 497 |
+
&mut self,
|
| 498 |
+
_upgraded_request: HyperUpgraded,
|
| 499 |
+
_connect_address: &str,
|
| 500 |
+
_config: &ServerConfig,
|
| 501 |
+
_socket_data: &SocketData,
|
| 502 |
+
_error_logger: &ErrorLogger,
|
| 503 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 504 |
+
Ok(())
|
| 505 |
+
}
|
| 506 |
+
|
| 507 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 508 |
+
false
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
async fn websocket_request_handler(
|
| 512 |
+
&mut self,
|
| 513 |
+
_websocket: HyperWebsocket,
|
| 514 |
+
_uri: &hyper::Uri,
|
| 515 |
+
_config: &ServerConfig,
|
| 516 |
+
_socket_data: &SocketData,
|
| 517 |
+
_error_logger: &ErrorLogger,
|
| 518 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 519 |
+
Ok(())
|
| 520 |
+
}
|
| 521 |
+
|
| 522 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 523 |
+
false
|
| 524 |
+
}
|
| 525 |
+
}
|
ferron/src/optional_modules/cgi.rs
ADDED
|
@@ -0,0 +1,859 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// CGI handler code inspired by SVR.JS's RedBrick mod, translated from JavaScript to Rust.
|
| 2 |
+
use std::collections::HashMap;
|
| 3 |
+
use std::error::Error;
|
| 4 |
+
use std::path::{Path, PathBuf};
|
| 5 |
+
use std::process::Stdio;
|
| 6 |
+
use std::sync::Arc;
|
| 7 |
+
use std::time::Duration;
|
| 8 |
+
|
| 9 |
+
use crate::ferron_common::{
|
| 10 |
+
ErrorLogger, HyperRequest, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 11 |
+
ServerModuleHandlers, SocketData,
|
| 12 |
+
};
|
| 13 |
+
use crate::ferron_common::{HyperUpgraded, WithRuntime};
|
| 14 |
+
use async_trait::async_trait;
|
| 15 |
+
use futures_util::TryStreamExt;
|
| 16 |
+
use hashlink::LinkedHashMap;
|
| 17 |
+
use http_body_util::{BodyExt, StreamBody};
|
| 18 |
+
use httparse::EMPTY_HEADER;
|
| 19 |
+
use hyper::body::Frame;
|
| 20 |
+
use hyper::{header, Response, StatusCode};
|
| 21 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 22 |
+
use tokio::fs;
|
| 23 |
+
use tokio::io::AsyncReadExt;
|
| 24 |
+
use tokio::process::Command;
|
| 25 |
+
use tokio::runtime::Handle;
|
| 26 |
+
use tokio::sync::RwLock;
|
| 27 |
+
use tokio_util::io::{ReaderStream, StreamReader};
|
| 28 |
+
|
| 29 |
+
use crate::ferron_res::server_software::SERVER_SOFTWARE;
|
| 30 |
+
use crate::ferron_util::cgi_response::CgiResponse;
|
| 31 |
+
use crate::ferron_util::copy_move::Copier;
|
| 32 |
+
use crate::ferron_util::ttl_cache::TtlCache;
|
| 33 |
+
|
| 34 |
+
pub fn server_module_init(
|
| 35 |
+
_config: &ServerConfig,
|
| 36 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 37 |
+
let cache = Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(100))));
|
| 38 |
+
Ok(Box::new(CgiModule::new(cache)))
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
#[allow(clippy::type_complexity)]
|
| 42 |
+
struct CgiModule {
|
| 43 |
+
path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>,
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
impl CgiModule {
|
| 47 |
+
#[allow(clippy::type_complexity)]
|
| 48 |
+
fn new(path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>) -> Self {
|
| 49 |
+
Self { path_cache }
|
| 50 |
+
}
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
impl ServerModule for CgiModule {
|
| 54 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 55 |
+
Box::new(CgiModuleHandlers {
|
| 56 |
+
path_cache: self.path_cache.clone(),
|
| 57 |
+
handle,
|
| 58 |
+
})
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
#[allow(clippy::type_complexity)]
|
| 63 |
+
struct CgiModuleHandlers {
|
| 64 |
+
handle: Handle,
|
| 65 |
+
path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>,
|
| 66 |
+
}
|
| 67 |
+
|
| 68 |
+
#[async_trait]
|
| 69 |
+
impl ServerModuleHandlers for CgiModuleHandlers {
|
| 70 |
+
async fn request_handler(
|
| 71 |
+
&mut self,
|
| 72 |
+
request: RequestData,
|
| 73 |
+
config: &ServerConfig,
|
| 74 |
+
socket_data: &SocketData,
|
| 75 |
+
error_logger: &ErrorLogger,
|
| 76 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 77 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 78 |
+
let mut cgi_script_exts = Vec::new();
|
| 79 |
+
|
| 80 |
+
let cgi_script_exts_yaml = &config["cgiScriptExtensions"];
|
| 81 |
+
if let Some(cgi_script_exts_obtained) = cgi_script_exts_yaml.as_vec() {
|
| 82 |
+
for cgi_script_ext_yaml in cgi_script_exts_obtained.iter() {
|
| 83 |
+
if let Some(cgi_script_ext) = cgi_script_ext_yaml.as_str() {
|
| 84 |
+
cgi_script_exts.push(cgi_script_ext);
|
| 85 |
+
}
|
| 86 |
+
}
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
+
if let Some(wwwroot) = config["wwwroot"].as_str() {
|
| 90 |
+
let hyper_request = request.get_hyper_request();
|
| 91 |
+
|
| 92 |
+
let request_path = hyper_request.uri().path();
|
| 93 |
+
let mut request_path_bytes = request_path.bytes();
|
| 94 |
+
if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
|
| 95 |
+
return Ok(
|
| 96 |
+
ResponseData::builder(request)
|
| 97 |
+
.status(StatusCode::BAD_REQUEST)
|
| 98 |
+
.build(),
|
| 99 |
+
);
|
| 100 |
+
}
|
| 101 |
+
|
| 102 |
+
let cache_key = format!(
|
| 103 |
+
"{}{}{}",
|
| 104 |
+
match config["ip"].as_str() {
|
| 105 |
+
Some(ip) => format!("{}-", ip),
|
| 106 |
+
None => String::from(""),
|
| 107 |
+
},
|
| 108 |
+
match config["domain"].as_str() {
|
| 109 |
+
Some(domain) => format!("{}-", domain),
|
| 110 |
+
None => String::from(""),
|
| 111 |
+
},
|
| 112 |
+
request_path
|
| 113 |
+
);
|
| 114 |
+
|
| 115 |
+
let wwwroot_unknown = PathBuf::from(wwwroot);
|
| 116 |
+
let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
|
| 117 |
+
true => wwwroot_unknown,
|
| 118 |
+
false => match fs::canonicalize(&wwwroot_unknown).await {
|
| 119 |
+
Ok(pathbuf) => pathbuf,
|
| 120 |
+
Err(_) => wwwroot_unknown,
|
| 121 |
+
},
|
| 122 |
+
};
|
| 123 |
+
let wwwroot = wwwroot_pathbuf.as_path();
|
| 124 |
+
|
| 125 |
+
let read_rwlock = self.path_cache.read().await;
|
| 126 |
+
let (execute_pathbuf, execute_path_info) = match read_rwlock.get(&cache_key) {
|
| 127 |
+
Some(data) => {
|
| 128 |
+
drop(read_rwlock);
|
| 129 |
+
data
|
| 130 |
+
}
|
| 131 |
+
None => {
|
| 132 |
+
drop(read_rwlock);
|
| 133 |
+
let mut relative_path = &request_path[1..];
|
| 134 |
+
while relative_path.as_bytes().first().copied() == Some(b'/') {
|
| 135 |
+
relative_path = &relative_path[1..];
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
let decoded_relative_path = match urlencoding::decode(relative_path) {
|
| 139 |
+
Ok(path) => path.to_string(),
|
| 140 |
+
Err(_) => {
|
| 141 |
+
return Ok(
|
| 142 |
+
ResponseData::builder(request)
|
| 143 |
+
.status(StatusCode::BAD_REQUEST)
|
| 144 |
+
.build(),
|
| 145 |
+
);
|
| 146 |
+
}
|
| 147 |
+
};
|
| 148 |
+
|
| 149 |
+
let joined_pathbuf = wwwroot.join(decoded_relative_path);
|
| 150 |
+
let mut execute_pathbuf: Option<PathBuf> = None;
|
| 151 |
+
let mut execute_path_info: Option<String> = None;
|
| 152 |
+
|
| 153 |
+
match fs::metadata(&joined_pathbuf).await {
|
| 154 |
+
Ok(metadata) => {
|
| 155 |
+
if metadata.is_file() {
|
| 156 |
+
let mut request_path_normalized = match cfg!(windows) {
|
| 157 |
+
true => request_path.to_lowercase(),
|
| 158 |
+
false => request_path.to_string(),
|
| 159 |
+
};
|
| 160 |
+
while request_path_normalized.contains("//") {
|
| 161 |
+
request_path_normalized = request_path_normalized.replace("//", "/");
|
| 162 |
+
}
|
| 163 |
+
if request_path_normalized == "/cgi-bin"
|
| 164 |
+
|| request_path_normalized.starts_with("/cgi-bin/")
|
| 165 |
+
{
|
| 166 |
+
execute_pathbuf = Some(joined_pathbuf);
|
| 167 |
+
} else {
|
| 168 |
+
let contained_extension = joined_pathbuf
|
| 169 |
+
.extension()
|
| 170 |
+
.map(|a| format!(".{}", a.to_string_lossy()));
|
| 171 |
+
if let Some(contained_extension) = contained_extension {
|
| 172 |
+
if cgi_script_exts.contains(&(&contained_extension as &str)) {
|
| 173 |
+
execute_pathbuf = Some(joined_pathbuf);
|
| 174 |
+
}
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
} else if metadata.is_dir() {
|
| 178 |
+
let indexes = vec!["index.php", "index.cgi"];
|
| 179 |
+
for index in indexes {
|
| 180 |
+
let temp_joined_pathbuf = joined_pathbuf.join(index);
|
| 181 |
+
match fs::metadata(&temp_joined_pathbuf).await {
|
| 182 |
+
Ok(temp_metadata) => {
|
| 183 |
+
if temp_metadata.is_file() {
|
| 184 |
+
let request_path_normalized = match cfg!(windows) {
|
| 185 |
+
true => request_path.to_lowercase(),
|
| 186 |
+
false => request_path.to_string(),
|
| 187 |
+
};
|
| 188 |
+
if request_path_normalized == "/cgi-bin"
|
| 189 |
+
|| request_path_normalized.starts_with("/cgi-bin/")
|
| 190 |
+
{
|
| 191 |
+
execute_pathbuf = Some(temp_joined_pathbuf);
|
| 192 |
+
break;
|
| 193 |
+
} else {
|
| 194 |
+
let contained_extension = temp_joined_pathbuf
|
| 195 |
+
.extension()
|
| 196 |
+
.map(|a| format!(".{}", a.to_string_lossy()));
|
| 197 |
+
if let Some(contained_extension) = contained_extension {
|
| 198 |
+
if cgi_script_exts.contains(&(&contained_extension as &str)) {
|
| 199 |
+
execute_pathbuf = Some(temp_joined_pathbuf);
|
| 200 |
+
break;
|
| 201 |
+
}
|
| 202 |
+
}
|
| 203 |
+
}
|
| 204 |
+
}
|
| 205 |
+
}
|
| 206 |
+
Err(_) => continue,
|
| 207 |
+
};
|
| 208 |
+
}
|
| 209 |
+
}
|
| 210 |
+
}
|
| 211 |
+
Err(err) => {
|
| 212 |
+
if err.kind() == tokio::io::ErrorKind::NotADirectory {
|
| 213 |
+
// TODO: find a file
|
| 214 |
+
let mut temp_pathbuf = joined_pathbuf.clone();
|
| 215 |
+
loop {
|
| 216 |
+
if !temp_pathbuf.pop() {
|
| 217 |
+
break;
|
| 218 |
+
}
|
| 219 |
+
match fs::metadata(&temp_pathbuf).await {
|
| 220 |
+
Ok(metadata) => {
|
| 221 |
+
if metadata.is_file() {
|
| 222 |
+
let temp_path = temp_pathbuf.as_path();
|
| 223 |
+
if !temp_path.starts_with(wwwroot) {
|
| 224 |
+
// Traversed above the webroot, so ignore that.
|
| 225 |
+
break;
|
| 226 |
+
}
|
| 227 |
+
let path_info = match joined_pathbuf.as_path().strip_prefix(temp_path) {
|
| 228 |
+
Ok(path) => {
|
| 229 |
+
let path = path.to_string_lossy().to_string();
|
| 230 |
+
Some(match cfg!(windows) {
|
| 231 |
+
true => path.replace("\\", "/"),
|
| 232 |
+
false => path,
|
| 233 |
+
})
|
| 234 |
+
}
|
| 235 |
+
Err(_) => None,
|
| 236 |
+
};
|
| 237 |
+
let mut request_path_normalized = match cfg!(windows) {
|
| 238 |
+
true => request_path.to_lowercase(),
|
| 239 |
+
false => request_path.to_string(),
|
| 240 |
+
};
|
| 241 |
+
while request_path_normalized.contains("//") {
|
| 242 |
+
request_path_normalized = request_path_normalized.replace("//", "/");
|
| 243 |
+
}
|
| 244 |
+
if request_path_normalized == "/cgi-bin"
|
| 245 |
+
|| request_path_normalized.starts_with("/cgi-bin/")
|
| 246 |
+
{
|
| 247 |
+
execute_pathbuf = Some(temp_pathbuf);
|
| 248 |
+
execute_path_info = path_info;
|
| 249 |
+
break;
|
| 250 |
+
} else {
|
| 251 |
+
let contained_extension = temp_pathbuf
|
| 252 |
+
.extension()
|
| 253 |
+
.map(|a| format!(".{}", a.to_string_lossy()));
|
| 254 |
+
if let Some(contained_extension) = contained_extension {
|
| 255 |
+
if cgi_script_exts.contains(&(&contained_extension as &str)) {
|
| 256 |
+
execute_pathbuf = Some(temp_pathbuf);
|
| 257 |
+
execute_path_info = path_info;
|
| 258 |
+
break;
|
| 259 |
+
}
|
| 260 |
+
}
|
| 261 |
+
}
|
| 262 |
+
} else {
|
| 263 |
+
break;
|
| 264 |
+
}
|
| 265 |
+
}
|
| 266 |
+
Err(err) => match err.kind() {
|
| 267 |
+
tokio::io::ErrorKind::NotADirectory => (),
|
| 268 |
+
_ => break,
|
| 269 |
+
},
|
| 270 |
+
};
|
| 271 |
+
}
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
};
|
| 275 |
+
let data = (execute_pathbuf, execute_path_info);
|
| 276 |
+
|
| 277 |
+
let mut write_rwlock = self.path_cache.write().await;
|
| 278 |
+
write_rwlock.cleanup();
|
| 279 |
+
write_rwlock.insert(cache_key, data.clone());
|
| 280 |
+
drop(write_rwlock);
|
| 281 |
+
data
|
| 282 |
+
}
|
| 283 |
+
};
|
| 284 |
+
|
| 285 |
+
if let Some(execute_pathbuf) = execute_pathbuf {
|
| 286 |
+
let mut cgi_interpreters = HashMap::new();
|
| 287 |
+
cgi_interpreters.insert(".pl".to_string(), vec!["perl".to_string()]);
|
| 288 |
+
cgi_interpreters.insert(".py".to_string(), vec!["python".to_string()]);
|
| 289 |
+
cgi_interpreters.insert(".sh".to_string(), vec!["bash".to_string()]);
|
| 290 |
+
cgi_interpreters.insert(".ksh".to_string(), vec!["ksh".to_string()]);
|
| 291 |
+
cgi_interpreters.insert(".csh".to_string(), vec!["csh".to_string()]);
|
| 292 |
+
cgi_interpreters.insert(".rb".to_string(), vec!["ruby".to_string()]);
|
| 293 |
+
cgi_interpreters.insert(".php".to_string(), vec!["php-cgi".to_string()]);
|
| 294 |
+
if cfg!(windows) {
|
| 295 |
+
cgi_interpreters.insert(".exe".to_string(), vec![]);
|
| 296 |
+
cgi_interpreters.insert(
|
| 297 |
+
".bat".to_string(),
|
| 298 |
+
vec!["cmd".to_string(), "/c".to_string()],
|
| 299 |
+
);
|
| 300 |
+
cgi_interpreters.insert(".vbs".to_string(), vec!["cscript".to_string()]);
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
let cgi_interpreters_yaml = &config["cgiScriptInterpreters"];
|
| 304 |
+
if let Some(cgi_interpreters_hashmap) = cgi_interpreters_yaml.as_hash() {
|
| 305 |
+
for (key_yaml, value_yaml) in cgi_interpreters_hashmap.iter() {
|
| 306 |
+
if let Some(key) = key_yaml.as_str() {
|
| 307 |
+
if value_yaml.is_null() {
|
| 308 |
+
cgi_interpreters.remove(key);
|
| 309 |
+
} else if let Some(value) = value_yaml.as_vec() {
|
| 310 |
+
let mut params = Vec::new();
|
| 311 |
+
for param_yaml in value.iter() {
|
| 312 |
+
if let Some(param) = param_yaml.as_str() {
|
| 313 |
+
params.push(param.to_string());
|
| 314 |
+
}
|
| 315 |
+
}
|
| 316 |
+
cgi_interpreters.insert(key.to_string(), params);
|
| 317 |
+
}
|
| 318 |
+
}
|
| 319 |
+
}
|
| 320 |
+
}
|
| 321 |
+
|
| 322 |
+
return execute_cgi_with_environment_variables(
|
| 323 |
+
request,
|
| 324 |
+
socket_data,
|
| 325 |
+
error_logger,
|
| 326 |
+
wwwroot,
|
| 327 |
+
execute_pathbuf,
|
| 328 |
+
execute_path_info,
|
| 329 |
+
config["serverAdministratorEmail"].as_str(),
|
| 330 |
+
cgi_interpreters,
|
| 331 |
+
)
|
| 332 |
+
.await;
|
| 333 |
+
}
|
| 334 |
+
}
|
| 335 |
+
|
| 336 |
+
Ok(ResponseData::builder(request).build())
|
| 337 |
+
})
|
| 338 |
+
.await
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
async fn proxy_request_handler(
|
| 342 |
+
&mut self,
|
| 343 |
+
request: RequestData,
|
| 344 |
+
_config: &ServerConfig,
|
| 345 |
+
_socket_data: &SocketData,
|
| 346 |
+
_error_logger: &ErrorLogger,
|
| 347 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 348 |
+
Ok(ResponseData::builder(request).build())
|
| 349 |
+
}
|
| 350 |
+
|
| 351 |
+
async fn response_modifying_handler(
|
| 352 |
+
&mut self,
|
| 353 |
+
response: HyperResponse,
|
| 354 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 355 |
+
Ok(response)
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
async fn proxy_response_modifying_handler(
|
| 359 |
+
&mut self,
|
| 360 |
+
response: HyperResponse,
|
| 361 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 362 |
+
Ok(response)
|
| 363 |
+
}
|
| 364 |
+
|
| 365 |
+
async fn connect_proxy_request_handler(
|
| 366 |
+
&mut self,
|
| 367 |
+
_upgraded_request: HyperUpgraded,
|
| 368 |
+
_connect_address: &str,
|
| 369 |
+
_config: &ServerConfig,
|
| 370 |
+
_socket_data: &SocketData,
|
| 371 |
+
_error_logger: &ErrorLogger,
|
| 372 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 373 |
+
Ok(())
|
| 374 |
+
}
|
| 375 |
+
|
| 376 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 377 |
+
false
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
async fn websocket_request_handler(
|
| 381 |
+
&mut self,
|
| 382 |
+
_websocket: HyperWebsocket,
|
| 383 |
+
_uri: &hyper::Uri,
|
| 384 |
+
_config: &ServerConfig,
|
| 385 |
+
_socket_data: &SocketData,
|
| 386 |
+
_error_logger: &ErrorLogger,
|
| 387 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 388 |
+
Ok(())
|
| 389 |
+
}
|
| 390 |
+
|
| 391 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 392 |
+
false
|
| 393 |
+
}
|
| 394 |
+
}
|
| 395 |
+
|
| 396 |
+
#[allow(clippy::too_many_arguments)]
|
| 397 |
+
async fn execute_cgi_with_environment_variables(
|
| 398 |
+
request: RequestData,
|
| 399 |
+
socket_data: &SocketData,
|
| 400 |
+
error_logger: &ErrorLogger,
|
| 401 |
+
wwwroot: &Path,
|
| 402 |
+
execute_pathbuf: PathBuf,
|
| 403 |
+
path_info: Option<String>,
|
| 404 |
+
server_administrator_email: Option<&str>,
|
| 405 |
+
cgi_interpreters: HashMap<String, Vec<String>>,
|
| 406 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 407 |
+
let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
|
| 408 |
+
|
| 409 |
+
let hyper_request = request.get_hyper_request();
|
| 410 |
+
let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
|
| 411 |
+
|
| 412 |
+
if let Some(auth_user) = request.get_auth_user() {
|
| 413 |
+
if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
|
| 414 |
+
let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
|
| 415 |
+
let mut authorization_value_split = authorization_value.split(" ");
|
| 416 |
+
if let Some(authorization_type) = authorization_value_split.next() {
|
| 417 |
+
environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
|
| 418 |
+
}
|
| 419 |
+
}
|
| 420 |
+
environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
|
| 421 |
+
}
|
| 422 |
+
|
| 423 |
+
environment_variables.insert(
|
| 424 |
+
"QUERY_STRING".to_string(),
|
| 425 |
+
match hyper_request.uri().query() {
|
| 426 |
+
Some(query) => query.to_string(),
|
| 427 |
+
None => "".to_string(),
|
| 428 |
+
},
|
| 429 |
+
);
|
| 430 |
+
|
| 431 |
+
environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
|
| 432 |
+
environment_variables.insert(
|
| 433 |
+
"SERVER_PROTOCOL".to_string(),
|
| 434 |
+
match hyper_request.version() {
|
| 435 |
+
hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
|
| 436 |
+
hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
|
| 437 |
+
hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
|
| 438 |
+
hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
|
| 439 |
+
hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
|
| 440 |
+
_ => "HTTP/Unknown".to_string(),
|
| 441 |
+
},
|
| 442 |
+
);
|
| 443 |
+
environment_variables.insert(
|
| 444 |
+
"SERVER_PORT".to_string(),
|
| 445 |
+
socket_data.local_addr.port().to_string(),
|
| 446 |
+
);
|
| 447 |
+
environment_variables.insert(
|
| 448 |
+
"SERVER_ADDR".to_string(),
|
| 449 |
+
socket_data.local_addr.ip().to_canonical().to_string(),
|
| 450 |
+
);
|
| 451 |
+
if let Some(server_administrator_email) = server_administrator_email {
|
| 452 |
+
environment_variables.insert(
|
| 453 |
+
"SERVER_ADMIN".to_string(),
|
| 454 |
+
server_administrator_email.to_string(),
|
| 455 |
+
);
|
| 456 |
+
}
|
| 457 |
+
if let Some(host) = hyper_request.headers().get(header::HOST) {
|
| 458 |
+
environment_variables.insert(
|
| 459 |
+
"SERVER_NAME".to_string(),
|
| 460 |
+
String::from_utf8_lossy(host.as_bytes()).to_string(),
|
| 461 |
+
);
|
| 462 |
+
}
|
| 463 |
+
|
| 464 |
+
environment_variables.insert(
|
| 465 |
+
"DOCUMENT_ROOT".to_string(),
|
| 466 |
+
wwwroot.to_string_lossy().to_string(),
|
| 467 |
+
);
|
| 468 |
+
environment_variables.insert(
|
| 469 |
+
"PATH_INFO".to_string(),
|
| 470 |
+
match &path_info {
|
| 471 |
+
Some(path_info) => format!("/{}", path_info),
|
| 472 |
+
None => "".to_string(),
|
| 473 |
+
},
|
| 474 |
+
);
|
| 475 |
+
environment_variables.insert(
|
| 476 |
+
"PATH_TRANSLATED".to_string(),
|
| 477 |
+
match &path_info {
|
| 478 |
+
Some(path_info) => {
|
| 479 |
+
let mut path_translated = execute_pathbuf.clone();
|
| 480 |
+
path_translated.push(path_info);
|
| 481 |
+
path_translated.to_string_lossy().to_string()
|
| 482 |
+
}
|
| 483 |
+
None => "".to_string(),
|
| 484 |
+
},
|
| 485 |
+
);
|
| 486 |
+
environment_variables.insert(
|
| 487 |
+
"REQUEST_METHOD".to_string(),
|
| 488 |
+
hyper_request.method().to_string(),
|
| 489 |
+
);
|
| 490 |
+
environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
|
| 491 |
+
environment_variables.insert(
|
| 492 |
+
"REQUEST_URI".to_string(),
|
| 493 |
+
format!(
|
| 494 |
+
"{}{}",
|
| 495 |
+
original_request_uri.path(),
|
| 496 |
+
match original_request_uri.query() {
|
| 497 |
+
Some(query) => format!("?{}", query),
|
| 498 |
+
None => String::from(""),
|
| 499 |
+
}
|
| 500 |
+
),
|
| 501 |
+
);
|
| 502 |
+
|
| 503 |
+
environment_variables.insert(
|
| 504 |
+
"REMOTE_PORT".to_string(),
|
| 505 |
+
socket_data.remote_addr.port().to_string(),
|
| 506 |
+
);
|
| 507 |
+
environment_variables.insert(
|
| 508 |
+
"REMOTE_ADDR".to_string(),
|
| 509 |
+
socket_data.remote_addr.ip().to_canonical().to_string(),
|
| 510 |
+
);
|
| 511 |
+
|
| 512 |
+
environment_variables.insert(
|
| 513 |
+
"SCRIPT_FILENAME".to_string(),
|
| 514 |
+
execute_pathbuf.to_string_lossy().to_string(),
|
| 515 |
+
);
|
| 516 |
+
if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
|
| 517 |
+
environment_variables.insert(
|
| 518 |
+
"SCRIPT_NAME".to_string(),
|
| 519 |
+
format!(
|
| 520 |
+
"/{}",
|
| 521 |
+
match cfg!(windows) {
|
| 522 |
+
true => script_path.to_string_lossy().to_string().replace("\\", "/"),
|
| 523 |
+
false => script_path.to_string_lossy().to_string(),
|
| 524 |
+
}
|
| 525 |
+
),
|
| 526 |
+
);
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
+
if socket_data.encrypted {
|
| 530 |
+
environment_variables.insert("HTTPS".to_string(), "ON".to_string());
|
| 531 |
+
}
|
| 532 |
+
|
| 533 |
+
for (header_name, header_value) in hyper_request.headers().iter() {
|
| 534 |
+
let env_header_name = match *header_name {
|
| 535 |
+
header::CONTENT_LENGTH => "CONTENT_LENGTH".to_string(),
|
| 536 |
+
header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
|
| 537 |
+
_ => {
|
| 538 |
+
let mut result = String::new();
|
| 539 |
+
|
| 540 |
+
result.push_str("HTTP_");
|
| 541 |
+
|
| 542 |
+
for c in header_name.as_str().to_uppercase().chars() {
|
| 543 |
+
if c.is_alphanumeric() {
|
| 544 |
+
result.push(c);
|
| 545 |
+
} else {
|
| 546 |
+
result.push('_');
|
| 547 |
+
}
|
| 548 |
+
}
|
| 549 |
+
|
| 550 |
+
result
|
| 551 |
+
}
|
| 552 |
+
};
|
| 553 |
+
if environment_variables.contains_key(&env_header_name) {
|
| 554 |
+
let value = environment_variables.get_mut(&env_header_name);
|
| 555 |
+
if let Some(value) = value {
|
| 556 |
+
if env_header_name == "HTTP_COOKIE" {
|
| 557 |
+
value.push_str("; ");
|
| 558 |
+
} else {
|
| 559 |
+
// See https://stackoverflow.com/a/1801191
|
| 560 |
+
value.push_str(", ");
|
| 561 |
+
}
|
| 562 |
+
value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
|
| 563 |
+
} else {
|
| 564 |
+
environment_variables.insert(
|
| 565 |
+
env_header_name,
|
| 566 |
+
String::from_utf8_lossy(header_value.as_bytes()).to_string(),
|
| 567 |
+
);
|
| 568 |
+
}
|
| 569 |
+
} else {
|
| 570 |
+
environment_variables.insert(
|
| 571 |
+
env_header_name,
|
| 572 |
+
String::from_utf8_lossy(header_value.as_bytes()).to_string(),
|
| 573 |
+
);
|
| 574 |
+
}
|
| 575 |
+
}
|
| 576 |
+
|
| 577 |
+
let (hyper_request, _, _) = request.into_parts();
|
| 578 |
+
|
| 579 |
+
execute_cgi(
|
| 580 |
+
hyper_request,
|
| 581 |
+
error_logger,
|
| 582 |
+
execute_pathbuf,
|
| 583 |
+
cgi_interpreters,
|
| 584 |
+
environment_variables,
|
| 585 |
+
)
|
| 586 |
+
.await
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
async fn execute_cgi(
|
| 590 |
+
hyper_request: HyperRequest,
|
| 591 |
+
error_logger: &ErrorLogger,
|
| 592 |
+
execute_pathbuf: PathBuf,
|
| 593 |
+
cgi_interpreters: HashMap<String, Vec<String>>,
|
| 594 |
+
environment_variables: LinkedHashMap<String, String>,
|
| 595 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 596 |
+
let (_, body) = hyper_request.into_parts();
|
| 597 |
+
|
| 598 |
+
let executable_params = match get_executable(&execute_pathbuf).await {
|
| 599 |
+
Ok(params) => params,
|
| 600 |
+
Err(err) => {
|
| 601 |
+
let contained_extension = execute_pathbuf
|
| 602 |
+
.extension()
|
| 603 |
+
.map(|a| format!(".{}", a.to_string_lossy()));
|
| 604 |
+
if let Some(contained_extension) = contained_extension {
|
| 605 |
+
if let Some(params_init) = cgi_interpreters.get(&contained_extension) {
|
| 606 |
+
let mut params: Vec<String> = params_init.iter().map(|s| s.to_owned()).collect();
|
| 607 |
+
params.push(execute_pathbuf.to_string_lossy().to_string());
|
| 608 |
+
params
|
| 609 |
+
} else {
|
| 610 |
+
Err(err)?
|
| 611 |
+
}
|
| 612 |
+
} else {
|
| 613 |
+
Err(err)?
|
| 614 |
+
}
|
| 615 |
+
}
|
| 616 |
+
};
|
| 617 |
+
|
| 618 |
+
let mut executable_params_iter = executable_params.iter();
|
| 619 |
+
|
| 620 |
+
let mut command = Command::new(match executable_params_iter.next() {
|
| 621 |
+
Some(executable_name) => executable_name,
|
| 622 |
+
None => Err(anyhow::anyhow!("Cannot determine the executable"))?,
|
| 623 |
+
});
|
| 624 |
+
|
| 625 |
+
// Set standard I/O to be piped
|
| 626 |
+
command.stdin(Stdio::piped());
|
| 627 |
+
command.stdout(Stdio::piped());
|
| 628 |
+
command.stderr(Stdio::piped());
|
| 629 |
+
|
| 630 |
+
for param in executable_params_iter {
|
| 631 |
+
command.arg(param);
|
| 632 |
+
}
|
| 633 |
+
|
| 634 |
+
command.envs(environment_variables);
|
| 635 |
+
|
| 636 |
+
let mut execute_dir_pathbuf = execute_pathbuf.clone();
|
| 637 |
+
execute_dir_pathbuf.pop();
|
| 638 |
+
command.current_dir(execute_dir_pathbuf);
|
| 639 |
+
|
| 640 |
+
let mut child = command.spawn()?;
|
| 641 |
+
|
| 642 |
+
let cgi_stdin_reader = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
|
| 643 |
+
|
| 644 |
+
let stdin = match child.stdin.take() {
|
| 645 |
+
Some(stdin) => stdin,
|
| 646 |
+
None => Err(anyhow::anyhow!(
|
| 647 |
+
"The CGI process doesn't have standard input"
|
| 648 |
+
))?,
|
| 649 |
+
};
|
| 650 |
+
let stdout = match child.stdout.take() {
|
| 651 |
+
Some(stdout) => stdout,
|
| 652 |
+
None => Err(anyhow::anyhow!(
|
| 653 |
+
"The CGI process doesn't have standard output"
|
| 654 |
+
))?,
|
| 655 |
+
};
|
| 656 |
+
let stderr = child.stderr.take();
|
| 657 |
+
|
| 658 |
+
let mut cgi_response = CgiResponse::new(stdout);
|
| 659 |
+
|
| 660 |
+
let stdin_copy_future = Copier::new(cgi_stdin_reader, stdin).copy();
|
| 661 |
+
let mut stdin_copy_future_pinned = Box::pin(stdin_copy_future);
|
| 662 |
+
|
| 663 |
+
let mut headers = [EMPTY_HEADER; 128];
|
| 664 |
+
|
| 665 |
+
let mut early_stdin_copied = false;
|
| 666 |
+
|
| 667 |
+
// Needed to wrap this in another scope to prevent errors with multiple mutable borrows.
|
| 668 |
+
{
|
| 669 |
+
let mut head_obtained = false;
|
| 670 |
+
let stdout_parse_future = cgi_response.get_head();
|
| 671 |
+
tokio::pin!(stdout_parse_future);
|
| 672 |
+
|
| 673 |
+
// Cannot use a loop with tokio::select, since stdin_copy_future_pinned being constantly ready will make the web server stop responding to HTTP requests
|
| 674 |
+
tokio::select! {
|
| 675 |
+
biased;
|
| 676 |
+
|
| 677 |
+
obtained_head = &mut stdout_parse_future => {
|
| 678 |
+
let obtained_head = obtained_head?;
|
| 679 |
+
if !obtained_head.is_empty() {
|
| 680 |
+
httparse::parse_headers(obtained_head, &mut headers)?;
|
| 681 |
+
}
|
| 682 |
+
head_obtained = true;
|
| 683 |
+
},
|
| 684 |
+
result = &mut stdin_copy_future_pinned => {
|
| 685 |
+
early_stdin_copied = true;
|
| 686 |
+
result?;
|
| 687 |
+
}
|
| 688 |
+
}
|
| 689 |
+
|
| 690 |
+
if !head_obtained {
|
| 691 |
+
// Kept it same as in the tokio::select macro
|
| 692 |
+
let obtained_head = stdout_parse_future.await?;
|
| 693 |
+
if !obtained_head.is_empty() {
|
| 694 |
+
httparse::parse_headers(obtained_head, &mut headers)?;
|
| 695 |
+
}
|
| 696 |
+
}
|
| 697 |
+
}
|
| 698 |
+
|
| 699 |
+
let mut response_builder = Response::builder();
|
| 700 |
+
let mut status_code = 200;
|
| 701 |
+
for header in headers {
|
| 702 |
+
if header == EMPTY_HEADER {
|
| 703 |
+
break;
|
| 704 |
+
}
|
| 705 |
+
let mut is_status_header = false;
|
| 706 |
+
match &header.name.to_lowercase() as &str {
|
| 707 |
+
"location" => {
|
| 708 |
+
if !(300..=399).contains(&status_code) {
|
| 709 |
+
status_code = 302;
|
| 710 |
+
}
|
| 711 |
+
}
|
| 712 |
+
"status" => {
|
| 713 |
+
is_status_header = true;
|
| 714 |
+
let header_value_cow = String::from_utf8_lossy(header.value);
|
| 715 |
+
let mut split_status = header_value_cow.split(" ");
|
| 716 |
+
let first_part = split_status.next();
|
| 717 |
+
if let Some(first_part) = first_part {
|
| 718 |
+
if first_part.starts_with("HTTP/") {
|
| 719 |
+
let second_part = split_status.next();
|
| 720 |
+
if let Some(second_part) = second_part {
|
| 721 |
+
if let Ok(parsed_status_code) = second_part.parse::<u16>() {
|
| 722 |
+
status_code = parsed_status_code;
|
| 723 |
+
}
|
| 724 |
+
}
|
| 725 |
+
} else if let Ok(parsed_status_code) = first_part.parse::<u16>() {
|
| 726 |
+
status_code = parsed_status_code;
|
| 727 |
+
}
|
| 728 |
+
}
|
| 729 |
+
}
|
| 730 |
+
_ => (),
|
| 731 |
+
}
|
| 732 |
+
if !is_status_header {
|
| 733 |
+
response_builder = response_builder.header(header.name, header.value);
|
| 734 |
+
}
|
| 735 |
+
}
|
| 736 |
+
|
| 737 |
+
response_builder = response_builder.status(status_code);
|
| 738 |
+
|
| 739 |
+
let reader_stream = ReaderStream::new(cgi_response);
|
| 740 |
+
let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
|
| 741 |
+
let boxed_body = stream_body.boxed();
|
| 742 |
+
|
| 743 |
+
let response = response_builder.body(boxed_body)?;
|
| 744 |
+
|
| 745 |
+
if let Some(exit_code) = child.try_wait()? {
|
| 746 |
+
if !exit_code.success() {
|
| 747 |
+
if let Some(mut stderr) = stderr {
|
| 748 |
+
let mut stderr_string = String::new();
|
| 749 |
+
stderr
|
| 750 |
+
.read_to_string(&mut stderr_string)
|
| 751 |
+
.await
|
| 752 |
+
.unwrap_or_default();
|
| 753 |
+
let stderr_string_trimmed = stderr_string.trim();
|
| 754 |
+
if !stderr_string_trimmed.is_empty() {
|
| 755 |
+
error_logger
|
| 756 |
+
.log(&format!("There were CGI errors: {}", stderr_string_trimmed))
|
| 757 |
+
.await;
|
| 758 |
+
}
|
| 759 |
+
}
|
| 760 |
+
return Ok(
|
| 761 |
+
ResponseData::builder_without_request()
|
| 762 |
+
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
| 763 |
+
.build(),
|
| 764 |
+
);
|
| 765 |
+
}
|
| 766 |
+
}
|
| 767 |
+
|
| 768 |
+
let error_logger = error_logger.clone();
|
| 769 |
+
|
| 770 |
+
Ok(
|
| 771 |
+
ResponseData::builder_without_request()
|
| 772 |
+
.response(response)
|
| 773 |
+
.parallel_fn(async move {
|
| 774 |
+
if !early_stdin_copied {
|
| 775 |
+
stdin_copy_future_pinned.await.unwrap_or_default();
|
| 776 |
+
}
|
| 777 |
+
|
| 778 |
+
if let Some(mut stderr) = stderr {
|
| 779 |
+
let mut stderr_string = String::new();
|
| 780 |
+
stderr
|
| 781 |
+
.read_to_string(&mut stderr_string)
|
| 782 |
+
.await
|
| 783 |
+
.unwrap_or_default();
|
| 784 |
+
let stderr_string_trimmed = stderr_string.trim();
|
| 785 |
+
if !stderr_string_trimmed.is_empty() {
|
| 786 |
+
error_logger
|
| 787 |
+
.log(&format!("There were CGI errors: {}", stderr_string_trimmed))
|
| 788 |
+
.await;
|
| 789 |
+
}
|
| 790 |
+
}
|
| 791 |
+
})
|
| 792 |
+
.build(),
|
| 793 |
+
)
|
| 794 |
+
}
|
| 795 |
+
|
| 796 |
+
#[allow(dead_code)]
|
| 797 |
+
#[cfg(unix)]
|
| 798 |
+
async fn get_executable(
|
| 799 |
+
execute_pathbuf: &PathBuf,
|
| 800 |
+
) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
|
| 801 |
+
use std::os::unix::fs::PermissionsExt;
|
| 802 |
+
|
| 803 |
+
let metadata = fs::metadata(&execute_pathbuf).await?;
|
| 804 |
+
let permissions = metadata.permissions();
|
| 805 |
+
let is_executable = permissions.mode() & 0o111 != 0;
|
| 806 |
+
|
| 807 |
+
if !is_executable {
|
| 808 |
+
Err(anyhow::anyhow!("The CGI program is not executable"))?
|
| 809 |
+
}
|
| 810 |
+
|
| 811 |
+
let executable_params_vector = vec![execute_pathbuf.to_string_lossy().to_string()];
|
| 812 |
+
Ok(executable_params_vector)
|
| 813 |
+
}
|
| 814 |
+
|
| 815 |
+
#[allow(dead_code)]
|
| 816 |
+
#[cfg(not(unix))]
|
| 817 |
+
async fn get_executable(
|
| 818 |
+
execute_pathbuf: &PathBuf,
|
| 819 |
+
) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
|
| 820 |
+
use tokio::io::{AsyncBufReadExt, AsyncSeekExt, BufReader};
|
| 821 |
+
|
| 822 |
+
let mut magic_signature_buffer = [0u8; 2];
|
| 823 |
+
let mut open_file = fs::File::open(&execute_pathbuf).await?;
|
| 824 |
+
if open_file
|
| 825 |
+
.read_exact(&mut magic_signature_buffer)
|
| 826 |
+
.await
|
| 827 |
+
.is_err()
|
| 828 |
+
{
|
| 829 |
+
Err(anyhow::anyhow!("Failed to read the CGI program signature"))?
|
| 830 |
+
}
|
| 831 |
+
|
| 832 |
+
match &magic_signature_buffer {
|
| 833 |
+
b"PE" => {
|
| 834 |
+
// Windows executables
|
| 835 |
+
let executable_params_vector = vec![execute_pathbuf.to_string_lossy().to_string()];
|
| 836 |
+
Ok(executable_params_vector)
|
| 837 |
+
}
|
| 838 |
+
b"#!" => {
|
| 839 |
+
// Scripts with a shebang line
|
| 840 |
+
open_file.rewind().await?;
|
| 841 |
+
let mut buffered_file = BufReader::new(open_file);
|
| 842 |
+
let mut shebang_line = String::new();
|
| 843 |
+
buffered_file.read_line(&mut shebang_line).await?;
|
| 844 |
+
|
| 845 |
+
let mut command_begin: Vec<String> = (&shebang_line[2..])
|
| 846 |
+
.replace("\r", "")
|
| 847 |
+
.replace("\n", "")
|
| 848 |
+
.split(" ")
|
| 849 |
+
.map(|s| s.to_owned())
|
| 850 |
+
.collect();
|
| 851 |
+
command_begin.push(execute_pathbuf.to_string_lossy().to_string());
|
| 852 |
+
Ok(command_begin)
|
| 853 |
+
}
|
| 854 |
+
_ => {
|
| 855 |
+
// It's not executable
|
| 856 |
+
Err(anyhow::anyhow!("The CGI program is not executable"))?
|
| 857 |
+
}
|
| 858 |
+
}
|
| 859 |
+
}
|
ferron/src/optional_modules/example.rs
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::error::Error;
|
| 2 |
+
|
| 3 |
+
use crate::ferron_common::{
|
| 4 |
+
ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 5 |
+
ServerModuleHandlers, SocketData,
|
| 6 |
+
};
|
| 7 |
+
use crate::ferron_common::{HyperResponse, WithRuntime};
|
| 8 |
+
use async_trait::async_trait;
|
| 9 |
+
use http_body_util::{BodyExt, Full};
|
| 10 |
+
use hyper::Response;
|
| 11 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 12 |
+
use tokio::runtime::Handle;
|
| 13 |
+
|
| 14 |
+
// Define a struct for the module implementation
|
| 15 |
+
struct ExampleModule;
|
| 16 |
+
|
| 17 |
+
/// Initializes the server module and returns an instance of `ExampleModule`.
|
| 18 |
+
pub fn server_module_init(
|
| 19 |
+
_config: &ServerConfig, // This is YAML configuration parsed as-is.
|
| 20 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 21 |
+
Ok(Box::new(ExampleModule::new()))
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
impl ExampleModule {
|
| 25 |
+
/// Creates a new instance of `ExampleModule`.
|
| 26 |
+
fn new() -> Self {
|
| 27 |
+
ExampleModule
|
| 28 |
+
}
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
/// Implements the `ServerModule` trait for `ExampleModule`.
|
| 32 |
+
impl ServerModule for ExampleModule {
|
| 33 |
+
/// Returns an instance of `ExampleModuleHandlers` to handle HTTP requests.
|
| 34 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 35 |
+
Box::new(ExampleModuleHandlers { handle })
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// Define a struct to handle HTTP requests
|
| 40 |
+
struct ExampleModuleHandlers {
|
| 41 |
+
handle: Handle,
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/// Implements the `ServerModuleHandlers` trait for `ExampleModuleHandlers`.
|
| 45 |
+
#[async_trait]
|
| 46 |
+
impl ServerModuleHandlers for ExampleModuleHandlers {
|
| 47 |
+
/// Handles incoming HTTP requests.
|
| 48 |
+
/// If the request path is `/hello`, it responds with "Hello World!".
|
| 49 |
+
async fn request_handler(
|
| 50 |
+
&mut self,
|
| 51 |
+
request: RequestData,
|
| 52 |
+
_config: &ServerConfig,
|
| 53 |
+
_socket_data: &SocketData,
|
| 54 |
+
_error_logger: &ErrorLogger,
|
| 55 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 56 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 57 |
+
if request.get_hyper_request().uri().path() == "/hello" {
|
| 58 |
+
Ok(
|
| 59 |
+
ResponseData::builder(request)
|
| 60 |
+
.response(
|
| 61 |
+
Response::builder().body(
|
| 62 |
+
Full::new("Hello World!".into())
|
| 63 |
+
.map_err(|e| match e {})
|
| 64 |
+
.boxed(),
|
| 65 |
+
)?,
|
| 66 |
+
)
|
| 67 |
+
.build(),
|
| 68 |
+
)
|
| 69 |
+
} else {
|
| 70 |
+
Ok(ResponseData::builder(request).build())
|
| 71 |
+
}
|
| 72 |
+
})
|
| 73 |
+
.await
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
/// Handles non-CONNECT proxy requests (not used in this module).
|
| 77 |
+
async fn proxy_request_handler(
|
| 78 |
+
&mut self,
|
| 79 |
+
request: RequestData,
|
| 80 |
+
_config: &ServerConfig,
|
| 81 |
+
_socket_data: &SocketData,
|
| 82 |
+
_error_logger: &ErrorLogger,
|
| 83 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 84 |
+
// No proxy request handling needed.
|
| 85 |
+
Ok(ResponseData::builder(request).build())
|
| 86 |
+
}
|
| 87 |
+
|
| 88 |
+
/// Modifies outgoing responses (not used in this module).
|
| 89 |
+
async fn response_modifying_handler(
|
| 90 |
+
&mut self,
|
| 91 |
+
response: HyperResponse,
|
| 92 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 93 |
+
// No response modification needed.
|
| 94 |
+
Ok(response)
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
/// Modifies outgoing proxy responses (not used in this module).
|
| 98 |
+
async fn proxy_response_modifying_handler(
|
| 99 |
+
&mut self,
|
| 100 |
+
response: HyperResponse,
|
| 101 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 102 |
+
// No proxy response modification needed.
|
| 103 |
+
Ok(response)
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
/// Handles CONNECT proxy requests (not used in this module).
|
| 107 |
+
async fn connect_proxy_request_handler(
|
| 108 |
+
&mut self,
|
| 109 |
+
_upgraded_request: HyperUpgraded,
|
| 110 |
+
_connect_address: &str,
|
| 111 |
+
_config: &ServerConfig,
|
| 112 |
+
_socket_data: &SocketData,
|
| 113 |
+
_error_logger: &ErrorLogger,
|
| 114 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 115 |
+
// No proxy request handling needed.
|
| 116 |
+
Ok(())
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
/// Checks if the module is a forward proxy module utilizing CONNECT method.
|
| 120 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 121 |
+
// This is not a forward proxy module utilizing CONNECT method
|
| 122 |
+
false
|
| 123 |
+
}
|
| 124 |
+
|
| 125 |
+
/// Handles WebSocket requests (not used in this module).
|
| 126 |
+
async fn websocket_request_handler(
|
| 127 |
+
&mut self,
|
| 128 |
+
_websocket: HyperWebsocket,
|
| 129 |
+
_uri: &hyper::Uri,
|
| 130 |
+
_config: &ServerConfig,
|
| 131 |
+
_socket_data: &SocketData,
|
| 132 |
+
_error_logger: &ErrorLogger,
|
| 133 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 134 |
+
// No proxy request handling needed.
|
| 135 |
+
Ok(())
|
| 136 |
+
}
|
| 137 |
+
|
| 138 |
+
/// Checks if the module supports WebSocket connections.
|
| 139 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 140 |
+
// This module doesn't support WebSocket connections.
|
| 141 |
+
false
|
| 142 |
+
}
|
| 143 |
+
}
|
ferron/src/optional_modules/fauth.rs
ADDED
|
@@ -0,0 +1,572 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// The "fauth" module is derived from the "rproxy" module, and inspired by Traefik's ForwardAuth middleware.
|
| 2 |
+
|
| 3 |
+
use std::collections::HashMap;
|
| 4 |
+
use std::error::Error;
|
| 5 |
+
use std::str::FromStr;
|
| 6 |
+
use std::sync::Arc;
|
| 7 |
+
|
| 8 |
+
use crate::ferron_common::{
|
| 9 |
+
ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 10 |
+
ServerModuleHandlers, SocketData,
|
| 11 |
+
};
|
| 12 |
+
use crate::ferron_common::{HyperResponse, WithRuntime};
|
| 13 |
+
use async_trait::async_trait;
|
| 14 |
+
use http_body_util::combinators::BoxBody;
|
| 15 |
+
use http_body_util::{BodyExt, Empty};
|
| 16 |
+
use hyper::body::Bytes;
|
| 17 |
+
use hyper::client::conn::http1::SendRequest;
|
| 18 |
+
use hyper::header::HeaderName;
|
| 19 |
+
use hyper::{header, Method, Request, StatusCode, Uri};
|
| 20 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 21 |
+
use hyper_util::rt::TokioIo;
|
| 22 |
+
use rustls::pki_types::ServerName;
|
| 23 |
+
use rustls::RootCertStore;
|
| 24 |
+
use rustls_native_certs::load_native_certs;
|
| 25 |
+
use tokio::io::{AsyncRead, AsyncWrite};
|
| 26 |
+
use tokio::net::TcpStream;
|
| 27 |
+
use tokio::runtime::Handle;
|
| 28 |
+
use tokio::sync::RwLock;
|
| 29 |
+
use tokio_rustls::TlsConnector;
|
| 30 |
+
|
| 31 |
+
const DEFAULT_CONCURRENT_CONNECTIONS_PER_HOST: u32 = 32;
|
| 32 |
+
|
| 33 |
+
pub fn server_module_init(
|
| 34 |
+
_config: &ServerConfig,
|
| 35 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 36 |
+
let mut roots: RootCertStore = RootCertStore::empty();
|
| 37 |
+
let certs_result = load_native_certs();
|
| 38 |
+
if !certs_result.errors.is_empty() {
|
| 39 |
+
Err(anyhow::anyhow!(format!(
|
| 40 |
+
"Couldn't load the native certificate store: {}",
|
| 41 |
+
certs_result.errors[0]
|
| 42 |
+
)))?
|
| 43 |
+
}
|
| 44 |
+
let certs = certs_result.certs;
|
| 45 |
+
|
| 46 |
+
for cert in certs {
|
| 47 |
+
match roots.add(cert) {
|
| 48 |
+
Ok(_) => (),
|
| 49 |
+
Err(err) => Err(anyhow::anyhow!(format!(
|
| 50 |
+
"Couldn't add a certificate to the certificate store: {}",
|
| 51 |
+
err
|
| 52 |
+
)))?,
|
| 53 |
+
}
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
let mut connections_vec = Vec::new();
|
| 57 |
+
for _ in 0..DEFAULT_CONCURRENT_CONNECTIONS_PER_HOST {
|
| 58 |
+
connections_vec.push(RwLock::new(HashMap::new()));
|
| 59 |
+
}
|
| 60 |
+
Ok(Box::new(ForwardedAuthenticationModule::new(
|
| 61 |
+
Arc::new(roots),
|
| 62 |
+
Arc::new(connections_vec),
|
| 63 |
+
)))
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
#[allow(clippy::type_complexity)]
|
| 67 |
+
struct ForwardedAuthenticationModule {
|
| 68 |
+
roots: Arc<RootCertStore>,
|
| 69 |
+
connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, hyper::Error>>>>>>,
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
impl ForwardedAuthenticationModule {
|
| 73 |
+
#[allow(clippy::type_complexity)]
|
| 74 |
+
fn new(
|
| 75 |
+
roots: Arc<RootCertStore>,
|
| 76 |
+
connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, hyper::Error>>>>>>,
|
| 77 |
+
) -> Self {
|
| 78 |
+
Self { roots, connections }
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
impl ServerModule for ForwardedAuthenticationModule {
|
| 83 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 84 |
+
Box::new(ForwardedAuthenticationModuleHandlers {
|
| 85 |
+
roots: self.roots.clone(),
|
| 86 |
+
connections: self.connections.clone(),
|
| 87 |
+
handle,
|
| 88 |
+
})
|
| 89 |
+
}
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
#[allow(clippy::type_complexity)]
|
| 93 |
+
struct ForwardedAuthenticationModuleHandlers {
|
| 94 |
+
handle: Handle,
|
| 95 |
+
roots: Arc<RootCertStore>,
|
| 96 |
+
connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, hyper::Error>>>>>>,
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
#[async_trait]
|
| 100 |
+
impl ServerModuleHandlers for ForwardedAuthenticationModuleHandlers {
|
| 101 |
+
async fn request_handler(
|
| 102 |
+
&mut self,
|
| 103 |
+
request: RequestData,
|
| 104 |
+
config: &ServerConfig,
|
| 105 |
+
socket_data: &SocketData,
|
| 106 |
+
error_logger: &ErrorLogger,
|
| 107 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 108 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 109 |
+
let mut auth_to = None;
|
| 110 |
+
|
| 111 |
+
if let Some(auth_to_str) = config["authTo"].as_str() {
|
| 112 |
+
auth_to = Some(auth_to_str.to_string());
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
let forwarded_auth_copy_headers = match config["forwardedAuthCopyHeaders"].as_vec() {
|
| 116 |
+
Some(vector) => {
|
| 117 |
+
let mut new_vector = Vec::new();
|
| 118 |
+
for yaml_value in vector.iter() {
|
| 119 |
+
if let Some(str_value) = yaml_value.as_str() {
|
| 120 |
+
new_vector.push(str_value.to_string());
|
| 121 |
+
}
|
| 122 |
+
}
|
| 123 |
+
new_vector
|
| 124 |
+
}
|
| 125 |
+
None => Vec::new(),
|
| 126 |
+
};
|
| 127 |
+
|
| 128 |
+
if let Some(auth_to) = auth_to {
|
| 129 |
+
let (hyper_request, auth_user, original_url) = request.into_parts();
|
| 130 |
+
let (hyper_request_parts, request_body) = hyper_request.into_parts();
|
| 131 |
+
|
| 132 |
+
let auth_request_url = auth_to.parse::<hyper::Uri>()?;
|
| 133 |
+
let scheme_str = auth_request_url.scheme_str();
|
| 134 |
+
let mut encrypted = false;
|
| 135 |
+
|
| 136 |
+
match scheme_str {
|
| 137 |
+
Some("http") => {
|
| 138 |
+
encrypted = false;
|
| 139 |
+
}
|
| 140 |
+
Some("https") => {
|
| 141 |
+
encrypted = true;
|
| 142 |
+
}
|
| 143 |
+
_ => Err(anyhow::anyhow!(
|
| 144 |
+
"Only HTTP and HTTPS reverse proxy URLs are supported."
|
| 145 |
+
))?,
|
| 146 |
+
};
|
| 147 |
+
|
| 148 |
+
let host = match auth_request_url.host() {
|
| 149 |
+
Some(host) => host,
|
| 150 |
+
None => Err(anyhow::anyhow!(
|
| 151 |
+
"The reverse proxy URL doesn't include the host"
|
| 152 |
+
))?,
|
| 153 |
+
};
|
| 154 |
+
|
| 155 |
+
let port = auth_request_url.port_u16().unwrap_or(match scheme_str {
|
| 156 |
+
Some("http") => 80,
|
| 157 |
+
Some("https") => 443,
|
| 158 |
+
_ => 80,
|
| 159 |
+
});
|
| 160 |
+
|
| 161 |
+
let addr = format!("{}:{}", host, port);
|
| 162 |
+
let authority = auth_request_url.authority().cloned();
|
| 163 |
+
|
| 164 |
+
let hyper_request_path = hyper_request_parts.uri.path();
|
| 165 |
+
|
| 166 |
+
let path_and_query = format!(
|
| 167 |
+
"{}{}",
|
| 168 |
+
hyper_request_path,
|
| 169 |
+
match hyper_request_parts.uri.query() {
|
| 170 |
+
Some(query) => format!("?{}", query),
|
| 171 |
+
None => "".to_string(),
|
| 172 |
+
}
|
| 173 |
+
);
|
| 174 |
+
|
| 175 |
+
let mut auth_hyper_request_parts = hyper_request_parts.clone();
|
| 176 |
+
|
| 177 |
+
auth_hyper_request_parts.uri = Uri::from_str(&format!(
|
| 178 |
+
"{}{}",
|
| 179 |
+
auth_request_url.path(),
|
| 180 |
+
match auth_request_url.query() {
|
| 181 |
+
Some(query) => format!("?{}", query),
|
| 182 |
+
None => "".to_string(),
|
| 183 |
+
}
|
| 184 |
+
))?;
|
| 185 |
+
|
| 186 |
+
let original_host = hyper_request_parts.headers.get(header::HOST).cloned();
|
| 187 |
+
|
| 188 |
+
// Host header for host identification
|
| 189 |
+
match authority {
|
| 190 |
+
Some(authority) => {
|
| 191 |
+
auth_hyper_request_parts
|
| 192 |
+
.headers
|
| 193 |
+
.insert(header::HOST, authority.to_string().parse()?);
|
| 194 |
+
}
|
| 195 |
+
None => {
|
| 196 |
+
auth_hyper_request_parts.headers.remove(header::HOST);
|
| 197 |
+
}
|
| 198 |
+
}
|
| 199 |
+
|
| 200 |
+
// Connection header to enable HTTP/1.1 keep-alive
|
| 201 |
+
auth_hyper_request_parts
|
| 202 |
+
.headers
|
| 203 |
+
.insert(header::CONNECTION, "keep-alive".parse()?);
|
| 204 |
+
|
| 205 |
+
// X-Forwarded-* headers to send the client's data to a forwarded authentication server
|
| 206 |
+
auth_hyper_request_parts.headers.insert(
|
| 207 |
+
"x-forwarded-for",
|
| 208 |
+
socket_data
|
| 209 |
+
.remote_addr
|
| 210 |
+
.ip()
|
| 211 |
+
.to_canonical()
|
| 212 |
+
.to_string()
|
| 213 |
+
.parse()?,
|
| 214 |
+
);
|
| 215 |
+
|
| 216 |
+
if socket_data.encrypted {
|
| 217 |
+
auth_hyper_request_parts
|
| 218 |
+
.headers
|
| 219 |
+
.insert("x-forwarded-proto", "https".parse()?);
|
| 220 |
+
} else {
|
| 221 |
+
auth_hyper_request_parts
|
| 222 |
+
.headers
|
| 223 |
+
.insert("x-forwarded-proto", "http".parse()?);
|
| 224 |
+
}
|
| 225 |
+
|
| 226 |
+
if let Some(original_host) = original_host {
|
| 227 |
+
auth_hyper_request_parts
|
| 228 |
+
.headers
|
| 229 |
+
.insert("x-forwarded-host", original_host);
|
| 230 |
+
}
|
| 231 |
+
|
| 232 |
+
auth_hyper_request_parts
|
| 233 |
+
.headers
|
| 234 |
+
.insert("x-forwarded-uri", path_and_query.parse()?);
|
| 235 |
+
|
| 236 |
+
auth_hyper_request_parts.headers.insert(
|
| 237 |
+
"x-forwarded-method",
|
| 238 |
+
hyper_request_parts.method.as_str().parse()?,
|
| 239 |
+
);
|
| 240 |
+
|
| 241 |
+
auth_hyper_request_parts.method = Method::GET;
|
| 242 |
+
|
| 243 |
+
let auth_request = Request::from_parts(
|
| 244 |
+
auth_hyper_request_parts,
|
| 245 |
+
Empty::new().map_err(|e| match e {}).boxed(),
|
| 246 |
+
);
|
| 247 |
+
let original_hyper_request = Request::from_parts(hyper_request_parts, request_body);
|
| 248 |
+
let original_request = RequestData::new(original_hyper_request, auth_user, original_url);
|
| 249 |
+
|
| 250 |
+
let connections = &self.connections[rand::random_range(..self.connections.len())];
|
| 251 |
+
|
| 252 |
+
let rwlock_read = connections.read().await;
|
| 253 |
+
let sender_read_option = rwlock_read.get(&addr);
|
| 254 |
+
|
| 255 |
+
if let Some(sender_read) = sender_read_option {
|
| 256 |
+
if !sender_read.is_closed() {
|
| 257 |
+
drop(rwlock_read);
|
| 258 |
+
let mut rwlock_write = connections.write().await;
|
| 259 |
+
let sender_option = rwlock_write.get_mut(&addr);
|
| 260 |
+
|
| 261 |
+
if let Some(sender) = sender_option {
|
| 262 |
+
if !sender.is_closed() {
|
| 263 |
+
let result = http_forwarded_auth_kept_alive(
|
| 264 |
+
sender,
|
| 265 |
+
auth_request,
|
| 266 |
+
error_logger,
|
| 267 |
+
original_request,
|
| 268 |
+
forwarded_auth_copy_headers,
|
| 269 |
+
)
|
| 270 |
+
.await;
|
| 271 |
+
drop(rwlock_write);
|
| 272 |
+
return result;
|
| 273 |
+
} else {
|
| 274 |
+
drop(rwlock_write);
|
| 275 |
+
}
|
| 276 |
+
} else {
|
| 277 |
+
drop(rwlock_write);
|
| 278 |
+
}
|
| 279 |
+
} else {
|
| 280 |
+
drop(rwlock_read);
|
| 281 |
+
}
|
| 282 |
+
} else {
|
| 283 |
+
drop(rwlock_read);
|
| 284 |
+
}
|
| 285 |
+
|
| 286 |
+
let stream = match TcpStream::connect(&addr).await {
|
| 287 |
+
Ok(stream) => stream,
|
| 288 |
+
Err(err) => {
|
| 289 |
+
match err.kind() {
|
| 290 |
+
tokio::io::ErrorKind::ConnectionRefused
|
| 291 |
+
| tokio::io::ErrorKind::NotFound
|
| 292 |
+
| tokio::io::ErrorKind::HostUnreachable => {
|
| 293 |
+
error_logger
|
| 294 |
+
.log(&format!("Service unavailable: {}", err))
|
| 295 |
+
.await;
|
| 296 |
+
return Ok(
|
| 297 |
+
ResponseData::builder_without_request()
|
| 298 |
+
.status(StatusCode::SERVICE_UNAVAILABLE)
|
| 299 |
+
.build(),
|
| 300 |
+
);
|
| 301 |
+
}
|
| 302 |
+
tokio::io::ErrorKind::TimedOut => {
|
| 303 |
+
error_logger.log(&format!("Gateway timeout: {}", err)).await;
|
| 304 |
+
return Ok(
|
| 305 |
+
ResponseData::builder_without_request()
|
| 306 |
+
.status(StatusCode::GATEWAY_TIMEOUT)
|
| 307 |
+
.build(),
|
| 308 |
+
);
|
| 309 |
+
}
|
| 310 |
+
_ => {
|
| 311 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 312 |
+
return Ok(
|
| 313 |
+
ResponseData::builder_without_request()
|
| 314 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 315 |
+
.build(),
|
| 316 |
+
);
|
| 317 |
+
}
|
| 318 |
+
};
|
| 319 |
+
}
|
| 320 |
+
};
|
| 321 |
+
|
| 322 |
+
match stream.set_nodelay(true) {
|
| 323 |
+
Ok(_) => (),
|
| 324 |
+
Err(err) => {
|
| 325 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 326 |
+
return Ok(
|
| 327 |
+
ResponseData::builder_without_request()
|
| 328 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 329 |
+
.build(),
|
| 330 |
+
);
|
| 331 |
+
}
|
| 332 |
+
};
|
| 333 |
+
|
| 334 |
+
if !encrypted {
|
| 335 |
+
http_forwarded_auth(
|
| 336 |
+
connections,
|
| 337 |
+
addr,
|
| 338 |
+
stream,
|
| 339 |
+
auth_request,
|
| 340 |
+
error_logger,
|
| 341 |
+
original_request,
|
| 342 |
+
forwarded_auth_copy_headers,
|
| 343 |
+
)
|
| 344 |
+
.await
|
| 345 |
+
} else {
|
| 346 |
+
let tls_client_config = rustls::ClientConfig::builder()
|
| 347 |
+
.with_root_certificates(self.roots.clone())
|
| 348 |
+
.with_no_client_auth();
|
| 349 |
+
let connector = TlsConnector::from(Arc::new(tls_client_config));
|
| 350 |
+
let domain = ServerName::try_from(host)?.to_owned();
|
| 351 |
+
|
| 352 |
+
let tls_stream = match connector.connect(domain, stream).await {
|
| 353 |
+
Ok(stream) => stream,
|
| 354 |
+
Err(err) => {
|
| 355 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 356 |
+
return Ok(
|
| 357 |
+
ResponseData::builder_without_request()
|
| 358 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 359 |
+
.build(),
|
| 360 |
+
);
|
| 361 |
+
}
|
| 362 |
+
};
|
| 363 |
+
|
| 364 |
+
http_forwarded_auth(
|
| 365 |
+
connections,
|
| 366 |
+
addr,
|
| 367 |
+
tls_stream,
|
| 368 |
+
auth_request,
|
| 369 |
+
error_logger,
|
| 370 |
+
original_request,
|
| 371 |
+
forwarded_auth_copy_headers,
|
| 372 |
+
)
|
| 373 |
+
.await
|
| 374 |
+
}
|
| 375 |
+
} else {
|
| 376 |
+
Ok(ResponseData::builder(request).build())
|
| 377 |
+
}
|
| 378 |
+
})
|
| 379 |
+
.await
|
| 380 |
+
}
|
| 381 |
+
|
| 382 |
+
async fn proxy_request_handler(
|
| 383 |
+
&mut self,
|
| 384 |
+
request: RequestData,
|
| 385 |
+
_config: &ServerConfig,
|
| 386 |
+
_socket_data: &SocketData,
|
| 387 |
+
_error_logger: &ErrorLogger,
|
| 388 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 389 |
+
Ok(ResponseData::builder(request).build())
|
| 390 |
+
}
|
| 391 |
+
|
| 392 |
+
async fn response_modifying_handler(
|
| 393 |
+
&mut self,
|
| 394 |
+
response: HyperResponse,
|
| 395 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 396 |
+
Ok(response)
|
| 397 |
+
}
|
| 398 |
+
|
| 399 |
+
async fn proxy_response_modifying_handler(
|
| 400 |
+
&mut self,
|
| 401 |
+
response: HyperResponse,
|
| 402 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 403 |
+
Ok(response)
|
| 404 |
+
}
|
| 405 |
+
|
| 406 |
+
async fn connect_proxy_request_handler(
|
| 407 |
+
&mut self,
|
| 408 |
+
_upgraded_request: HyperUpgraded,
|
| 409 |
+
_connect_address: &str,
|
| 410 |
+
_config: &ServerConfig,
|
| 411 |
+
_socket_data: &SocketData,
|
| 412 |
+
_error_logger: &ErrorLogger,
|
| 413 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 414 |
+
Ok(())
|
| 415 |
+
}
|
| 416 |
+
|
| 417 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 418 |
+
false
|
| 419 |
+
}
|
| 420 |
+
|
| 421 |
+
async fn websocket_request_handler(
|
| 422 |
+
&mut self,
|
| 423 |
+
_websocket: HyperWebsocket,
|
| 424 |
+
_uri: &hyper::Uri,
|
| 425 |
+
_config: &ServerConfig,
|
| 426 |
+
_socket_data: &SocketData,
|
| 427 |
+
_error_logger: &ErrorLogger,
|
| 428 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 429 |
+
Ok(())
|
| 430 |
+
}
|
| 431 |
+
|
| 432 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 433 |
+
false
|
| 434 |
+
}
|
| 435 |
+
}
|
| 436 |
+
|
| 437 |
+
async fn http_forwarded_auth(
|
| 438 |
+
connections: &RwLock<HashMap<String, SendRequest<BoxBody<Bytes, hyper::Error>>>>,
|
| 439 |
+
connect_addr: String,
|
| 440 |
+
stream: impl AsyncRead + AsyncWrite + Send + Unpin + 'static,
|
| 441 |
+
proxy_request: Request<BoxBody<Bytes, hyper::Error>>,
|
| 442 |
+
error_logger: &ErrorLogger,
|
| 443 |
+
mut original_request: RequestData,
|
| 444 |
+
forwarded_auth_copy_headers: Vec<String>,
|
| 445 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 446 |
+
let io = TokioIo::new(stream);
|
| 447 |
+
|
| 448 |
+
let (mut sender, conn) = match hyper::client::conn::http1::handshake(io).await {
|
| 449 |
+
Ok(data) => data,
|
| 450 |
+
Err(err) => {
|
| 451 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 452 |
+
return Ok(
|
| 453 |
+
ResponseData::builder_without_request()
|
| 454 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 455 |
+
.build(),
|
| 456 |
+
);
|
| 457 |
+
}
|
| 458 |
+
};
|
| 459 |
+
|
| 460 |
+
let send_request = sender.send_request(proxy_request);
|
| 461 |
+
|
| 462 |
+
let mut pinned_conn = Box::pin(conn);
|
| 463 |
+
tokio::pin!(send_request);
|
| 464 |
+
|
| 465 |
+
let response;
|
| 466 |
+
|
| 467 |
+
loop {
|
| 468 |
+
tokio::select! {
|
| 469 |
+
biased;
|
| 470 |
+
|
| 471 |
+
proxy_response = &mut send_request => {
|
| 472 |
+
let proxy_response = match proxy_response {
|
| 473 |
+
Ok(response) => response,
|
| 474 |
+
Err(err) => {
|
| 475 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 476 |
+
return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
|
| 477 |
+
}
|
| 478 |
+
};
|
| 479 |
+
|
| 480 |
+
if proxy_response.status().is_success() {
|
| 481 |
+
if !forwarded_auth_copy_headers.is_empty() {
|
| 482 |
+
let response_headers = proxy_response.headers();
|
| 483 |
+
let request_headers = original_request.get_mut_hyper_request().headers_mut();
|
| 484 |
+
for forwarded_auth_copy_header_string in forwarded_auth_copy_headers.iter() {
|
| 485 |
+
let forwarded_auth_copy_header= HeaderName::from_str(forwarded_auth_copy_header_string)?;
|
| 486 |
+
if response_headers.contains_key(&forwarded_auth_copy_header) {
|
| 487 |
+
while request_headers.remove(&forwarded_auth_copy_header).is_some() {}
|
| 488 |
+
for header_value in response_headers.get_all(&forwarded_auth_copy_header).iter() {
|
| 489 |
+
request_headers.append(&forwarded_auth_copy_header, header_value.clone());
|
| 490 |
+
}
|
| 491 |
+
}
|
| 492 |
+
}
|
| 493 |
+
}
|
| 494 |
+
response = ResponseData::builder(original_request).build();
|
| 495 |
+
} else {
|
| 496 |
+
response = ResponseData::builder_without_request()
|
| 497 |
+
.response(proxy_response.map(|b| {
|
| 498 |
+
b.map_err(|e| std::io::Error::other(e.to_string()))
|
| 499 |
+
.boxed()
|
| 500 |
+
}))
|
| 501 |
+
.parallel_fn(async move {
|
| 502 |
+
pinned_conn.await.unwrap_or_default();
|
| 503 |
+
})
|
| 504 |
+
.build();
|
| 505 |
+
|
| 506 |
+
}
|
| 507 |
+
|
| 508 |
+
break;
|
| 509 |
+
},
|
| 510 |
+
state = &mut pinned_conn => {
|
| 511 |
+
if state.is_err() {
|
| 512 |
+
error_logger.log("Bad gateway: incomplete response").await;
|
| 513 |
+
return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
|
| 514 |
+
}
|
| 515 |
+
},
|
| 516 |
+
};
|
| 517 |
+
}
|
| 518 |
+
|
| 519 |
+
if !sender.is_closed() {
|
| 520 |
+
let mut rwlock_write = connections.write().await;
|
| 521 |
+
rwlock_write.insert(connect_addr, sender);
|
| 522 |
+
drop(rwlock_write);
|
| 523 |
+
}
|
| 524 |
+
|
| 525 |
+
Ok(response)
|
| 526 |
+
}
|
| 527 |
+
|
| 528 |
+
async fn http_forwarded_auth_kept_alive(
|
| 529 |
+
sender: &mut SendRequest<BoxBody<Bytes, hyper::Error>>,
|
| 530 |
+
proxy_request: Request<BoxBody<Bytes, hyper::Error>>,
|
| 531 |
+
error_logger: &ErrorLogger,
|
| 532 |
+
mut original_request: RequestData,
|
| 533 |
+
forwarded_auth_copy_headers: Vec<String>,
|
| 534 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 535 |
+
let proxy_response = match sender.send_request(proxy_request).await {
|
| 536 |
+
Ok(response) => response,
|
| 537 |
+
Err(err) => {
|
| 538 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 539 |
+
return Ok(
|
| 540 |
+
ResponseData::builder_without_request()
|
| 541 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 542 |
+
.build(),
|
| 543 |
+
);
|
| 544 |
+
}
|
| 545 |
+
};
|
| 546 |
+
|
| 547 |
+
let response = if proxy_response.status().is_success() {
|
| 548 |
+
if !forwarded_auth_copy_headers.is_empty() {
|
| 549 |
+
let response_headers = proxy_response.headers();
|
| 550 |
+
let request_headers = original_request.get_mut_hyper_request().headers_mut();
|
| 551 |
+
for forwarded_auth_copy_header_string in forwarded_auth_copy_headers.iter() {
|
| 552 |
+
let forwarded_auth_copy_header = HeaderName::from_str(forwarded_auth_copy_header_string)?;
|
| 553 |
+
if response_headers.contains_key(&forwarded_auth_copy_header) {
|
| 554 |
+
while request_headers
|
| 555 |
+
.remove(&forwarded_auth_copy_header)
|
| 556 |
+
.is_some()
|
| 557 |
+
{}
|
| 558 |
+
for header_value in response_headers.get_all(&forwarded_auth_copy_header).iter() {
|
| 559 |
+
request_headers.append(&forwarded_auth_copy_header, header_value.clone());
|
| 560 |
+
}
|
| 561 |
+
}
|
| 562 |
+
}
|
| 563 |
+
}
|
| 564 |
+
ResponseData::builder(original_request).build()
|
| 565 |
+
} else {
|
| 566 |
+
ResponseData::builder_without_request()
|
| 567 |
+
.response(proxy_response.map(|b| b.map_err(|e| std::io::Error::other(e.to_string())).boxed()))
|
| 568 |
+
.build()
|
| 569 |
+
};
|
| 570 |
+
|
| 571 |
+
Ok(response)
|
| 572 |
+
}
|
ferron/src/optional_modules/fcgi.rs
ADDED
|
@@ -0,0 +1,964 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// FastCGI handler code inspired by SVR.JS's GreenRhombus mod, translated from JavaScript to Rust.
|
| 2 |
+
// Based on the "cgi" and "scgi" module
|
| 3 |
+
use std::env;
|
| 4 |
+
use std::error::Error;
|
| 5 |
+
use std::path::{Path, PathBuf};
|
| 6 |
+
use std::sync::Arc;
|
| 7 |
+
use std::time::Duration;
|
| 8 |
+
|
| 9 |
+
use crate::ferron_common::{
|
| 10 |
+
ErrorLogger, HyperRequest, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 11 |
+
ServerModuleHandlers, SocketData,
|
| 12 |
+
};
|
| 13 |
+
use crate::ferron_common::{HyperUpgraded, WithRuntime};
|
| 14 |
+
use async_trait::async_trait;
|
| 15 |
+
use futures_util::future::Either;
|
| 16 |
+
use futures_util::TryStreamExt;
|
| 17 |
+
use hashlink::LinkedHashMap;
|
| 18 |
+
use http_body_util::{BodyExt, StreamBody};
|
| 19 |
+
use httparse::EMPTY_HEADER;
|
| 20 |
+
use hyper::body::{Bytes, Frame};
|
| 21 |
+
use hyper::{header, Response, StatusCode};
|
| 22 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 23 |
+
use tokio::fs;
|
| 24 |
+
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
| 25 |
+
use tokio::net::TcpStream;
|
| 26 |
+
use tokio::runtime::Handle;
|
| 27 |
+
use tokio::sync::RwLock;
|
| 28 |
+
use tokio_util::codec::{FramedRead, FramedWrite};
|
| 29 |
+
use tokio_util::io::{ReaderStream, SinkWriter, StreamReader};
|
| 30 |
+
|
| 31 |
+
use crate::ferron_res::server_software::SERVER_SOFTWARE;
|
| 32 |
+
use crate::ferron_util::cgi_response::CgiResponse;
|
| 33 |
+
use crate::ferron_util::copy_move::Copier;
|
| 34 |
+
use crate::ferron_util::fcgi_decoder::{FcgiDecodedData, FcgiDecoder};
|
| 35 |
+
use crate::ferron_util::fcgi_encoder::FcgiEncoder;
|
| 36 |
+
use crate::ferron_util::fcgi_name_value_pair::construct_fastcgi_name_value_pair;
|
| 37 |
+
use crate::ferron_util::fcgi_record::construct_fastcgi_record;
|
| 38 |
+
use crate::ferron_util::read_to_end_move::ReadToEndFuture;
|
| 39 |
+
use crate::ferron_util::split_stream_by_map::SplitStreamByMapExt;
|
| 40 |
+
use crate::ferron_util::ttl_cache::TtlCache;
|
| 41 |
+
|
| 42 |
+
pub fn server_module_init(
|
| 43 |
+
_config: &ServerConfig,
|
| 44 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 45 |
+
let cache = Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(100))));
|
| 46 |
+
Ok(Box::new(FcgiModule::new(cache)))
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
#[allow(clippy::type_complexity)]
|
| 50 |
+
struct FcgiModule {
|
| 51 |
+
path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>,
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
impl FcgiModule {
|
| 55 |
+
#[allow(clippy::type_complexity)]
|
| 56 |
+
fn new(path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>) -> Self {
|
| 57 |
+
Self { path_cache }
|
| 58 |
+
}
|
| 59 |
+
}
|
| 60 |
+
|
| 61 |
+
impl ServerModule for FcgiModule {
|
| 62 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 63 |
+
Box::new(FcgiModuleHandlers {
|
| 64 |
+
path_cache: self.path_cache.clone(),
|
| 65 |
+
handle,
|
| 66 |
+
})
|
| 67 |
+
}
|
| 68 |
+
}
|
| 69 |
+
|
| 70 |
+
#[allow(clippy::type_complexity)]
|
| 71 |
+
struct FcgiModuleHandlers {
|
| 72 |
+
handle: Handle,
|
| 73 |
+
path_cache: Arc<RwLock<TtlCache<String, (Option<PathBuf>, Option<String>)>>>,
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
#[async_trait]
|
| 77 |
+
impl ServerModuleHandlers for FcgiModuleHandlers {
|
| 78 |
+
async fn request_handler(
|
| 79 |
+
&mut self,
|
| 80 |
+
request: RequestData,
|
| 81 |
+
config: &ServerConfig,
|
| 82 |
+
socket_data: &SocketData,
|
| 83 |
+
error_logger: &ErrorLogger,
|
| 84 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 85 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 86 |
+
let mut fastcgi_script_exts = Vec::new();
|
| 87 |
+
|
| 88 |
+
let fastcgi_script_exts_yaml = &config["fcgiScriptExtensions"];
|
| 89 |
+
if let Some(fastcgi_script_exts_obtained) = fastcgi_script_exts_yaml.as_vec() {
|
| 90 |
+
for fastcgi_script_ext_yaml in fastcgi_script_exts_obtained.iter() {
|
| 91 |
+
if let Some(fastcgi_script_ext) = fastcgi_script_ext_yaml.as_str() {
|
| 92 |
+
fastcgi_script_exts.push(fastcgi_script_ext);
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
let mut fastcgi_to = "tcp://localhost:4000/";
|
| 98 |
+
let fastcgi_to_yaml = &config["fcgiTo"];
|
| 99 |
+
if let Some(fastcgi_to_obtained) = fastcgi_to_yaml.as_str() {
|
| 100 |
+
fastcgi_to = fastcgi_to_obtained;
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
let mut fastcgi_path = None;
|
| 104 |
+
if let Some(fastcgi_path_obtained) = config["fcgiPath"].as_str() {
|
| 105 |
+
fastcgi_path = Some(fastcgi_path_obtained.to_string());
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
let hyper_request = request.get_hyper_request();
|
| 109 |
+
|
| 110 |
+
let request_path = hyper_request.uri().path();
|
| 111 |
+
let mut request_path_bytes = request_path.bytes();
|
| 112 |
+
if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
|
| 113 |
+
return Ok(
|
| 114 |
+
ResponseData::builder(request)
|
| 115 |
+
.status(StatusCode::BAD_REQUEST)
|
| 116 |
+
.build(),
|
| 117 |
+
);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
let mut execute_pathbuf = None;
|
| 121 |
+
let mut execute_path_info = None;
|
| 122 |
+
let mut wwwroot_detected = None;
|
| 123 |
+
|
| 124 |
+
if let Some(fastcgi_path) = fastcgi_path {
|
| 125 |
+
let mut canonical_fastcgi_path: &str = &fastcgi_path;
|
| 126 |
+
if canonical_fastcgi_path.bytes().last() == Some(b'/') {
|
| 127 |
+
canonical_fastcgi_path = &canonical_fastcgi_path[..(canonical_fastcgi_path.len() - 1)];
|
| 128 |
+
}
|
| 129 |
+
|
| 130 |
+
let request_path_with_slashes = match request_path == canonical_fastcgi_path {
|
| 131 |
+
true => format!("{}/", request_path),
|
| 132 |
+
false => request_path.to_string(),
|
| 133 |
+
};
|
| 134 |
+
if let Some(stripped_request_path) =
|
| 135 |
+
request_path_with_slashes.strip_prefix(canonical_fastcgi_path)
|
| 136 |
+
{
|
| 137 |
+
let wwwroot_yaml = &config["wwwroot"];
|
| 138 |
+
let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
|
| 139 |
+
|
| 140 |
+
let wwwroot_unknown = PathBuf::from(wwwroot);
|
| 141 |
+
let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
|
| 142 |
+
true => wwwroot_unknown,
|
| 143 |
+
false => match fs::canonicalize(&wwwroot_unknown).await {
|
| 144 |
+
Ok(pathbuf) => pathbuf,
|
| 145 |
+
Err(_) => wwwroot_unknown,
|
| 146 |
+
},
|
| 147 |
+
};
|
| 148 |
+
wwwroot_detected = Some(wwwroot_pathbuf.clone());
|
| 149 |
+
let wwwroot = wwwroot_pathbuf.as_path();
|
| 150 |
+
|
| 151 |
+
let mut relative_path = &request_path[1..];
|
| 152 |
+
while relative_path.as_bytes().first().copied() == Some(b'/') {
|
| 153 |
+
relative_path = &relative_path[1..];
|
| 154 |
+
}
|
| 155 |
+
|
| 156 |
+
let decoded_relative_path = match urlencoding::decode(relative_path) {
|
| 157 |
+
Ok(path) => path.to_string(),
|
| 158 |
+
Err(_) => {
|
| 159 |
+
return Ok(
|
| 160 |
+
ResponseData::builder(request)
|
| 161 |
+
.status(StatusCode::BAD_REQUEST)
|
| 162 |
+
.build(),
|
| 163 |
+
);
|
| 164 |
+
}
|
| 165 |
+
};
|
| 166 |
+
|
| 167 |
+
let joined_pathbuf = wwwroot.join(decoded_relative_path);
|
| 168 |
+
execute_pathbuf = Some(joined_pathbuf);
|
| 169 |
+
execute_path_info = stripped_request_path
|
| 170 |
+
.strip_prefix("/")
|
| 171 |
+
.map(|s| s.to_string());
|
| 172 |
+
}
|
| 173 |
+
}
|
| 174 |
+
|
| 175 |
+
if execute_pathbuf.is_none() {
|
| 176 |
+
if let Some(wwwroot) = config["wwwroot"].as_str() {
|
| 177 |
+
let cache_key = format!(
|
| 178 |
+
"{}{}{}",
|
| 179 |
+
match config["ip"].as_str() {
|
| 180 |
+
Some(ip) => format!("{}-", ip),
|
| 181 |
+
None => String::from(""),
|
| 182 |
+
},
|
| 183 |
+
match config["domain"].as_str() {
|
| 184 |
+
Some(domain) => format!("{}-", domain),
|
| 185 |
+
None => String::from(""),
|
| 186 |
+
},
|
| 187 |
+
request_path
|
| 188 |
+
);
|
| 189 |
+
|
| 190 |
+
let wwwroot_unknown = PathBuf::from(wwwroot);
|
| 191 |
+
let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
|
| 192 |
+
true => wwwroot_unknown,
|
| 193 |
+
false => match fs::canonicalize(&wwwroot_unknown).await {
|
| 194 |
+
Ok(pathbuf) => pathbuf,
|
| 195 |
+
Err(_) => wwwroot_unknown,
|
| 196 |
+
},
|
| 197 |
+
};
|
| 198 |
+
wwwroot_detected = Some(wwwroot_pathbuf.clone());
|
| 199 |
+
let wwwroot = wwwroot_pathbuf.as_path();
|
| 200 |
+
|
| 201 |
+
let read_rwlock = self.path_cache.read().await;
|
| 202 |
+
let (execute_pathbuf_got, execute_path_info_got) = match read_rwlock.get(&cache_key) {
|
| 203 |
+
Some(data) => {
|
| 204 |
+
drop(read_rwlock);
|
| 205 |
+
data
|
| 206 |
+
}
|
| 207 |
+
None => {
|
| 208 |
+
drop(read_rwlock);
|
| 209 |
+
let mut relative_path = &request_path[1..];
|
| 210 |
+
while relative_path.as_bytes().first().copied() == Some(b'/') {
|
| 211 |
+
relative_path = &relative_path[1..];
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
let decoded_relative_path = match urlencoding::decode(relative_path) {
|
| 215 |
+
Ok(path) => path.to_string(),
|
| 216 |
+
Err(_) => {
|
| 217 |
+
return Ok(
|
| 218 |
+
ResponseData::builder(request)
|
| 219 |
+
.status(StatusCode::BAD_REQUEST)
|
| 220 |
+
.build(),
|
| 221 |
+
);
|
| 222 |
+
}
|
| 223 |
+
};
|
| 224 |
+
|
| 225 |
+
let joined_pathbuf = wwwroot.join(decoded_relative_path);
|
| 226 |
+
let mut execute_pathbuf: Option<PathBuf> = None;
|
| 227 |
+
let mut execute_path_info: Option<String> = None;
|
| 228 |
+
|
| 229 |
+
match fs::metadata(&joined_pathbuf).await {
|
| 230 |
+
Ok(metadata) => {
|
| 231 |
+
if metadata.is_file() {
|
| 232 |
+
let contained_extension = joined_pathbuf
|
| 233 |
+
.extension()
|
| 234 |
+
.map(|a| format!(".{}", a.to_string_lossy()));
|
| 235 |
+
if let Some(contained_extension) = contained_extension {
|
| 236 |
+
if fastcgi_script_exts.contains(&(&contained_extension as &str)) {
|
| 237 |
+
execute_pathbuf = Some(joined_pathbuf);
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
} else if metadata.is_dir() {
|
| 241 |
+
let indexes = vec!["index.php", "index.cgi"];
|
| 242 |
+
for index in indexes {
|
| 243 |
+
let temp_joined_pathbuf = joined_pathbuf.join(index);
|
| 244 |
+
match fs::metadata(&temp_joined_pathbuf).await {
|
| 245 |
+
Ok(temp_metadata) => {
|
| 246 |
+
if temp_metadata.is_file() {
|
| 247 |
+
let contained_extension = temp_joined_pathbuf
|
| 248 |
+
.extension()
|
| 249 |
+
.map(|a| format!(".{}", a.to_string_lossy()));
|
| 250 |
+
if let Some(contained_extension) = contained_extension {
|
| 251 |
+
if fastcgi_script_exts.contains(&(&contained_extension as &str)) {
|
| 252 |
+
execute_pathbuf = Some(temp_joined_pathbuf);
|
| 253 |
+
break;
|
| 254 |
+
}
|
| 255 |
+
}
|
| 256 |
+
}
|
| 257 |
+
}
|
| 258 |
+
Err(_) => continue,
|
| 259 |
+
};
|
| 260 |
+
}
|
| 261 |
+
}
|
| 262 |
+
}
|
| 263 |
+
Err(err) => {
|
| 264 |
+
if err.kind() == tokio::io::ErrorKind::NotADirectory {
|
| 265 |
+
// TODO: find a file
|
| 266 |
+
let mut temp_pathbuf = joined_pathbuf.clone();
|
| 267 |
+
loop {
|
| 268 |
+
if !temp_pathbuf.pop() {
|
| 269 |
+
break;
|
| 270 |
+
}
|
| 271 |
+
match fs::metadata(&temp_pathbuf).await {
|
| 272 |
+
Ok(metadata) => {
|
| 273 |
+
if metadata.is_file() {
|
| 274 |
+
let temp_path = temp_pathbuf.as_path();
|
| 275 |
+
if !temp_path.starts_with(wwwroot) {
|
| 276 |
+
// Traversed above the webroot, so ignore that.
|
| 277 |
+
break;
|
| 278 |
+
}
|
| 279 |
+
let path_info = match joined_pathbuf.as_path().strip_prefix(temp_path) {
|
| 280 |
+
Ok(path) => {
|
| 281 |
+
let path = path.to_string_lossy().to_string();
|
| 282 |
+
Some(match cfg!(windows) {
|
| 283 |
+
true => path.replace("\\", "/"),
|
| 284 |
+
false => path,
|
| 285 |
+
})
|
| 286 |
+
}
|
| 287 |
+
Err(_) => None,
|
| 288 |
+
};
|
| 289 |
+
let mut request_path_normalized = match cfg!(windows) {
|
| 290 |
+
true => request_path.to_lowercase(),
|
| 291 |
+
false => request_path.to_string(),
|
| 292 |
+
};
|
| 293 |
+
while request_path_normalized.contains("//") {
|
| 294 |
+
request_path_normalized = request_path_normalized.replace("//", "/");
|
| 295 |
+
}
|
| 296 |
+
if request_path_normalized == "/cgi-bin"
|
| 297 |
+
|| request_path_normalized.starts_with("/cgi-bin/")
|
| 298 |
+
{
|
| 299 |
+
execute_pathbuf = Some(temp_pathbuf);
|
| 300 |
+
execute_path_info = path_info;
|
| 301 |
+
break;
|
| 302 |
+
} else {
|
| 303 |
+
let contained_extension = temp_pathbuf
|
| 304 |
+
.extension()
|
| 305 |
+
.map(|a| format!(".{}", a.to_string_lossy()));
|
| 306 |
+
if let Some(contained_extension) = contained_extension {
|
| 307 |
+
if fastcgi_script_exts.contains(&(&contained_extension as &str)) {
|
| 308 |
+
execute_pathbuf = Some(temp_pathbuf);
|
| 309 |
+
execute_path_info = path_info;
|
| 310 |
+
break;
|
| 311 |
+
}
|
| 312 |
+
}
|
| 313 |
+
}
|
| 314 |
+
} else {
|
| 315 |
+
break;
|
| 316 |
+
}
|
| 317 |
+
}
|
| 318 |
+
Err(err) => match err.kind() {
|
| 319 |
+
tokio::io::ErrorKind::NotADirectory => (),
|
| 320 |
+
_ => break,
|
| 321 |
+
},
|
| 322 |
+
};
|
| 323 |
+
}
|
| 324 |
+
}
|
| 325 |
+
}
|
| 326 |
+
};
|
| 327 |
+
let data = (execute_pathbuf, execute_path_info);
|
| 328 |
+
|
| 329 |
+
let mut write_rwlock = self.path_cache.write().await;
|
| 330 |
+
write_rwlock.cleanup();
|
| 331 |
+
write_rwlock.insert(cache_key, data.clone());
|
| 332 |
+
drop(write_rwlock);
|
| 333 |
+
data
|
| 334 |
+
}
|
| 335 |
+
};
|
| 336 |
+
|
| 337 |
+
execute_pathbuf = execute_pathbuf_got;
|
| 338 |
+
execute_path_info = execute_path_info_got;
|
| 339 |
+
}
|
| 340 |
+
}
|
| 341 |
+
|
| 342 |
+
if let Some(execute_pathbuf) = execute_pathbuf {
|
| 343 |
+
if let Some(wwwroot_detected) = wwwroot_detected {
|
| 344 |
+
return execute_fastcgi_with_environment_variables(
|
| 345 |
+
request,
|
| 346 |
+
socket_data,
|
| 347 |
+
error_logger,
|
| 348 |
+
wwwroot_detected.as_path(),
|
| 349 |
+
execute_pathbuf,
|
| 350 |
+
execute_path_info,
|
| 351 |
+
config["serverAdministratorEmail"].as_str(),
|
| 352 |
+
fastcgi_to,
|
| 353 |
+
)
|
| 354 |
+
.await;
|
| 355 |
+
}
|
| 356 |
+
}
|
| 357 |
+
|
| 358 |
+
Ok(ResponseData::builder(request).build())
|
| 359 |
+
})
|
| 360 |
+
.await
|
| 361 |
+
}
|
| 362 |
+
|
| 363 |
+
async fn proxy_request_handler(
|
| 364 |
+
&mut self,
|
| 365 |
+
request: RequestData,
|
| 366 |
+
_config: &ServerConfig,
|
| 367 |
+
_socket_data: &SocketData,
|
| 368 |
+
_error_logger: &ErrorLogger,
|
| 369 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 370 |
+
Ok(ResponseData::builder(request).build())
|
| 371 |
+
}
|
| 372 |
+
|
| 373 |
+
async fn response_modifying_handler(
|
| 374 |
+
&mut self,
|
| 375 |
+
response: HyperResponse,
|
| 376 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 377 |
+
Ok(response)
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
async fn proxy_response_modifying_handler(
|
| 381 |
+
&mut self,
|
| 382 |
+
response: HyperResponse,
|
| 383 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 384 |
+
Ok(response)
|
| 385 |
+
}
|
| 386 |
+
|
| 387 |
+
async fn connect_proxy_request_handler(
|
| 388 |
+
&mut self,
|
| 389 |
+
_upgraded_request: HyperUpgraded,
|
| 390 |
+
_connect_address: &str,
|
| 391 |
+
_config: &ServerConfig,
|
| 392 |
+
_socket_data: &SocketData,
|
| 393 |
+
_error_logger: &ErrorLogger,
|
| 394 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 395 |
+
Ok(())
|
| 396 |
+
}
|
| 397 |
+
|
| 398 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 399 |
+
false
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
async fn websocket_request_handler(
|
| 403 |
+
&mut self,
|
| 404 |
+
_websocket: HyperWebsocket,
|
| 405 |
+
_uri: &hyper::Uri,
|
| 406 |
+
_config: &ServerConfig,
|
| 407 |
+
_socket_data: &SocketData,
|
| 408 |
+
_error_logger: &ErrorLogger,
|
| 409 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 410 |
+
Ok(())
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 414 |
+
false
|
| 415 |
+
}
|
| 416 |
+
}
|
| 417 |
+
|
| 418 |
+
#[allow(clippy::too_many_arguments)]
|
| 419 |
+
async fn execute_fastcgi_with_environment_variables(
|
| 420 |
+
request: RequestData,
|
| 421 |
+
socket_data: &SocketData,
|
| 422 |
+
error_logger: &ErrorLogger,
|
| 423 |
+
wwwroot: &Path,
|
| 424 |
+
execute_pathbuf: PathBuf,
|
| 425 |
+
path_info: Option<String>,
|
| 426 |
+
server_administrator_email: Option<&str>,
|
| 427 |
+
fastcgi_to: &str,
|
| 428 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 429 |
+
let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
|
| 430 |
+
|
| 431 |
+
let hyper_request = request.get_hyper_request();
|
| 432 |
+
let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
|
| 433 |
+
|
| 434 |
+
if let Some(auth_user) = request.get_auth_user() {
|
| 435 |
+
if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
|
| 436 |
+
let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
|
| 437 |
+
let mut authorization_value_split = authorization_value.split(" ");
|
| 438 |
+
if let Some(authorization_type) = authorization_value_split.next() {
|
| 439 |
+
environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
|
| 440 |
+
}
|
| 441 |
+
}
|
| 442 |
+
environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
environment_variables.insert(
|
| 446 |
+
"QUERY_STRING".to_string(),
|
| 447 |
+
match hyper_request.uri().query() {
|
| 448 |
+
Some(query) => query.to_string(),
|
| 449 |
+
None => "".to_string(),
|
| 450 |
+
},
|
| 451 |
+
);
|
| 452 |
+
|
| 453 |
+
environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
|
| 454 |
+
environment_variables.insert(
|
| 455 |
+
"SERVER_PROTOCOL".to_string(),
|
| 456 |
+
match hyper_request.version() {
|
| 457 |
+
hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
|
| 458 |
+
hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
|
| 459 |
+
hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
|
| 460 |
+
hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
|
| 461 |
+
hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
|
| 462 |
+
_ => "HTTP/Unknown".to_string(),
|
| 463 |
+
},
|
| 464 |
+
);
|
| 465 |
+
environment_variables.insert(
|
| 466 |
+
"SERVER_PORT".to_string(),
|
| 467 |
+
socket_data.local_addr.port().to_string(),
|
| 468 |
+
);
|
| 469 |
+
environment_variables.insert(
|
| 470 |
+
"SERVER_ADDR".to_string(),
|
| 471 |
+
socket_data.local_addr.ip().to_canonical().to_string(),
|
| 472 |
+
);
|
| 473 |
+
if let Some(server_administrator_email) = server_administrator_email {
|
| 474 |
+
environment_variables.insert(
|
| 475 |
+
"SERVER_ADMIN".to_string(),
|
| 476 |
+
server_administrator_email.to_string(),
|
| 477 |
+
);
|
| 478 |
+
}
|
| 479 |
+
if let Some(host) = hyper_request.headers().get(header::HOST) {
|
| 480 |
+
environment_variables.insert(
|
| 481 |
+
"SERVER_NAME".to_string(),
|
| 482 |
+
String::from_utf8_lossy(host.as_bytes()).to_string(),
|
| 483 |
+
);
|
| 484 |
+
}
|
| 485 |
+
|
| 486 |
+
environment_variables.insert(
|
| 487 |
+
"DOCUMENT_ROOT".to_string(),
|
| 488 |
+
wwwroot.to_string_lossy().to_string(),
|
| 489 |
+
);
|
| 490 |
+
environment_variables.insert(
|
| 491 |
+
"PATH_INFO".to_string(),
|
| 492 |
+
match &path_info {
|
| 493 |
+
Some(path_info) => format!("/{}", path_info),
|
| 494 |
+
None => "".to_string(),
|
| 495 |
+
},
|
| 496 |
+
);
|
| 497 |
+
environment_variables.insert(
|
| 498 |
+
"PATH_TRANSLATED".to_string(),
|
| 499 |
+
match &path_info {
|
| 500 |
+
Some(path_info) => {
|
| 501 |
+
let mut path_translated = execute_pathbuf.clone();
|
| 502 |
+
path_translated.push(path_info);
|
| 503 |
+
path_translated.to_string_lossy().to_string()
|
| 504 |
+
}
|
| 505 |
+
None => "".to_string(),
|
| 506 |
+
},
|
| 507 |
+
);
|
| 508 |
+
environment_variables.insert(
|
| 509 |
+
"REQUEST_METHOD".to_string(),
|
| 510 |
+
hyper_request.method().to_string(),
|
| 511 |
+
);
|
| 512 |
+
environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
|
| 513 |
+
environment_variables.insert(
|
| 514 |
+
"REQUEST_URI".to_string(),
|
| 515 |
+
format!(
|
| 516 |
+
"{}{}",
|
| 517 |
+
original_request_uri.path(),
|
| 518 |
+
match original_request_uri.query() {
|
| 519 |
+
Some(query) => format!("?{}", query),
|
| 520 |
+
None => String::from(""),
|
| 521 |
+
}
|
| 522 |
+
),
|
| 523 |
+
);
|
| 524 |
+
|
| 525 |
+
environment_variables.insert(
|
| 526 |
+
"REMOTE_PORT".to_string(),
|
| 527 |
+
socket_data.remote_addr.port().to_string(),
|
| 528 |
+
);
|
| 529 |
+
environment_variables.insert(
|
| 530 |
+
"REMOTE_ADDR".to_string(),
|
| 531 |
+
socket_data.remote_addr.ip().to_canonical().to_string(),
|
| 532 |
+
);
|
| 533 |
+
|
| 534 |
+
environment_variables.insert(
|
| 535 |
+
"SCRIPT_FILENAME".to_string(),
|
| 536 |
+
execute_pathbuf.to_string_lossy().to_string(),
|
| 537 |
+
);
|
| 538 |
+
if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
|
| 539 |
+
environment_variables.insert(
|
| 540 |
+
"SCRIPT_NAME".to_string(),
|
| 541 |
+
format!(
|
| 542 |
+
"/{}",
|
| 543 |
+
match cfg!(windows) {
|
| 544 |
+
true => script_path.to_string_lossy().to_string().replace("\\", "/"),
|
| 545 |
+
false => script_path.to_string_lossy().to_string(),
|
| 546 |
+
}
|
| 547 |
+
),
|
| 548 |
+
);
|
| 549 |
+
}
|
| 550 |
+
|
| 551 |
+
if socket_data.encrypted {
|
| 552 |
+
environment_variables.insert("HTTPS".to_string(), "ON".to_string());
|
| 553 |
+
}
|
| 554 |
+
|
| 555 |
+
let mut content_length_set = false;
|
| 556 |
+
for (header_name, header_value) in hyper_request.headers().iter() {
|
| 557 |
+
let env_header_name = match *header_name {
|
| 558 |
+
header::CONTENT_LENGTH => {
|
| 559 |
+
content_length_set = true;
|
| 560 |
+
"CONTENT_LENGTH".to_string()
|
| 561 |
+
}
|
| 562 |
+
header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
|
| 563 |
+
_ => {
|
| 564 |
+
let mut result = String::new();
|
| 565 |
+
|
| 566 |
+
result.push_str("HTTP_");
|
| 567 |
+
|
| 568 |
+
for c in header_name.as_str().to_uppercase().chars() {
|
| 569 |
+
if c.is_alphanumeric() {
|
| 570 |
+
result.push(c);
|
| 571 |
+
} else {
|
| 572 |
+
result.push('_');
|
| 573 |
+
}
|
| 574 |
+
}
|
| 575 |
+
|
| 576 |
+
result
|
| 577 |
+
}
|
| 578 |
+
};
|
| 579 |
+
if environment_variables.contains_key(&env_header_name) {
|
| 580 |
+
let value = environment_variables.get_mut(&env_header_name);
|
| 581 |
+
if let Some(value) = value {
|
| 582 |
+
if env_header_name == "HTTP_COOKIE" {
|
| 583 |
+
value.push_str("; ");
|
| 584 |
+
} else {
|
| 585 |
+
// See https://stackoverflow.com/a/1801191
|
| 586 |
+
value.push_str(", ");
|
| 587 |
+
}
|
| 588 |
+
value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
|
| 589 |
+
} else {
|
| 590 |
+
environment_variables.insert(
|
| 591 |
+
env_header_name,
|
| 592 |
+
String::from_utf8_lossy(header_value.as_bytes()).to_string(),
|
| 593 |
+
);
|
| 594 |
+
}
|
| 595 |
+
} else {
|
| 596 |
+
environment_variables.insert(
|
| 597 |
+
env_header_name,
|
| 598 |
+
String::from_utf8_lossy(header_value.as_bytes()).to_string(),
|
| 599 |
+
);
|
| 600 |
+
}
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
if !content_length_set {
|
| 604 |
+
environment_variables.insert("CONTENT_LENGTH".to_string(), "0".to_string());
|
| 605 |
+
}
|
| 606 |
+
|
| 607 |
+
let (hyper_request, _, _) = request.into_parts();
|
| 608 |
+
|
| 609 |
+
execute_fastcgi(
|
| 610 |
+
hyper_request,
|
| 611 |
+
error_logger,
|
| 612 |
+
fastcgi_to,
|
| 613 |
+
environment_variables,
|
| 614 |
+
)
|
| 615 |
+
.await
|
| 616 |
+
}
|
| 617 |
+
|
| 618 |
+
async fn execute_fastcgi(
|
| 619 |
+
hyper_request: HyperRequest,
|
| 620 |
+
error_logger: &ErrorLogger,
|
| 621 |
+
fastcgi_to: &str,
|
| 622 |
+
mut environment_variables: LinkedHashMap<String, String>,
|
| 623 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 624 |
+
let (_, body) = hyper_request.into_parts();
|
| 625 |
+
|
| 626 |
+
// Insert other environment variables
|
| 627 |
+
for (key, value) in env::vars_os() {
|
| 628 |
+
let key_string = key.to_string_lossy().to_string();
|
| 629 |
+
let value_string = value.to_string_lossy().to_string();
|
| 630 |
+
environment_variables
|
| 631 |
+
.entry(key_string)
|
| 632 |
+
.or_insert(value_string);
|
| 633 |
+
}
|
| 634 |
+
|
| 635 |
+
let fastcgi_to_fixed = if let Some(stripped) = fastcgi_to.strip_prefix("unix:///") {
|
| 636 |
+
// hyper::Uri fails to parse a string if there is an empty authority, so add an "ignore" authority to Unix socket URLs
|
| 637 |
+
&format!("unix://ignore/{}", stripped)
|
| 638 |
+
} else {
|
| 639 |
+
fastcgi_to
|
| 640 |
+
};
|
| 641 |
+
|
| 642 |
+
let fastcgi_to_url = fastcgi_to_fixed.parse::<hyper::Uri>()?;
|
| 643 |
+
let scheme_str = fastcgi_to_url.scheme_str();
|
| 644 |
+
|
| 645 |
+
let (socket_reader, mut socket_writer) = match scheme_str {
|
| 646 |
+
Some("tcp") => {
|
| 647 |
+
let host = match fastcgi_to_url.host() {
|
| 648 |
+
Some(host) => host,
|
| 649 |
+
None => Err(anyhow::anyhow!("The FastCGI URL doesn't include the host"))?,
|
| 650 |
+
};
|
| 651 |
+
|
| 652 |
+
let port = match fastcgi_to_url.port_u16() {
|
| 653 |
+
Some(port) => port,
|
| 654 |
+
None => Err(anyhow::anyhow!("The FastCGI URL doesn't include the port"))?,
|
| 655 |
+
};
|
| 656 |
+
|
| 657 |
+
let addr = format!("{}:{}", host, port);
|
| 658 |
+
|
| 659 |
+
match connect_tcp(&addr).await {
|
| 660 |
+
Ok(data) => data,
|
| 661 |
+
Err(err) => match err.kind() {
|
| 662 |
+
tokio::io::ErrorKind::ConnectionRefused
|
| 663 |
+
| tokio::io::ErrorKind::NotFound
|
| 664 |
+
| tokio::io::ErrorKind::HostUnreachable => {
|
| 665 |
+
error_logger
|
| 666 |
+
.log(&format!("Service unavailable: {}", err))
|
| 667 |
+
.await;
|
| 668 |
+
return Ok(
|
| 669 |
+
ResponseData::builder_without_request()
|
| 670 |
+
.status(StatusCode::SERVICE_UNAVAILABLE)
|
| 671 |
+
.build(),
|
| 672 |
+
);
|
| 673 |
+
}
|
| 674 |
+
_ => Err(err)?,
|
| 675 |
+
},
|
| 676 |
+
}
|
| 677 |
+
}
|
| 678 |
+
Some("unix") => {
|
| 679 |
+
let path = fastcgi_to_url.path();
|
| 680 |
+
match connect_unix(path).await {
|
| 681 |
+
Ok(data) => data,
|
| 682 |
+
Err(err) => match err.kind() {
|
| 683 |
+
tokio::io::ErrorKind::ConnectionRefused
|
| 684 |
+
| tokio::io::ErrorKind::NotFound
|
| 685 |
+
| tokio::io::ErrorKind::HostUnreachable => {
|
| 686 |
+
error_logger
|
| 687 |
+
.log(&format!("Service unavailable: {}", err))
|
| 688 |
+
.await;
|
| 689 |
+
return Ok(
|
| 690 |
+
ResponseData::builder_without_request()
|
| 691 |
+
.status(StatusCode::SERVICE_UNAVAILABLE)
|
| 692 |
+
.build(),
|
| 693 |
+
);
|
| 694 |
+
}
|
| 695 |
+
_ => Err(err)?,
|
| 696 |
+
},
|
| 697 |
+
}
|
| 698 |
+
}
|
| 699 |
+
_ => Err(anyhow::anyhow!(
|
| 700 |
+
"Only HTTP and HTTPS reverse proxy URLs are supported."
|
| 701 |
+
))?,
|
| 702 |
+
};
|
| 703 |
+
|
| 704 |
+
// Construct and send BEGIN_REQUEST record
|
| 705 |
+
// Use the responder role and don't use keep-alive
|
| 706 |
+
let begin_request_packet = construct_fastcgi_record(1, 1, &[0, 1, 0, 0, 0, 0, 0, 0]);
|
| 707 |
+
socket_writer.write_all(&begin_request_packet).await?;
|
| 708 |
+
|
| 709 |
+
// Construct and send PARAMS records
|
| 710 |
+
let mut environment_variables_to_wrap = Vec::new();
|
| 711 |
+
for (key, value) in environment_variables.iter() {
|
| 712 |
+
let mut environment_variable =
|
| 713 |
+
construct_fastcgi_name_value_pair(key.as_bytes(), value.as_bytes());
|
| 714 |
+
environment_variables_to_wrap.append(&mut environment_variable);
|
| 715 |
+
}
|
| 716 |
+
if !environment_variables_to_wrap.is_empty() {
|
| 717 |
+
let mut offset = 0;
|
| 718 |
+
while offset < environment_variables_to_wrap.len() {
|
| 719 |
+
let chunk_size = std::cmp::min(65536, environment_variables_to_wrap.len() - offset);
|
| 720 |
+
let chunk = &environment_variables_to_wrap[offset..offset + chunk_size];
|
| 721 |
+
|
| 722 |
+
// Record type 4 means PARAMS
|
| 723 |
+
let params_packet = construct_fastcgi_record(4, 1, chunk);
|
| 724 |
+
socket_writer.write_all(¶ms_packet).await?;
|
| 725 |
+
|
| 726 |
+
offset += chunk_size;
|
| 727 |
+
}
|
| 728 |
+
}
|
| 729 |
+
|
| 730 |
+
let params_packet_terminating = construct_fastcgi_record(4, 1, &[]);
|
| 731 |
+
socket_writer.write_all(¶ms_packet_terminating).await?;
|
| 732 |
+
|
| 733 |
+
let cgi_stdin_reader = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
|
| 734 |
+
|
| 735 |
+
// Emulated standard input, standard output, and standard error
|
| 736 |
+
type EitherStream = Either<Result<Bytes, std::io::Error>, Result<Bytes, std::io::Error>>;
|
| 737 |
+
let stdin = SinkWriter::new(FramedWrite::new(socket_writer, FcgiEncoder::new()));
|
| 738 |
+
let stdout_and_stderr = FramedRead::new(socket_reader, FcgiDecoder::new());
|
| 739 |
+
let (stdout_stream, stderr_stream) = stdout_and_stderr.split_by_map(|item| match item {
|
| 740 |
+
Ok(FcgiDecodedData::Stdout(bytes)) => EitherStream::Left(Ok(bytes)),
|
| 741 |
+
Ok(FcgiDecodedData::Stderr(bytes)) => EitherStream::Right(Ok(bytes)),
|
| 742 |
+
Err(err) => EitherStream::Left(Err(err)),
|
| 743 |
+
});
|
| 744 |
+
let stdout = StreamReader::new(stdout_stream);
|
| 745 |
+
let stderr = StreamReader::new(stderr_stream);
|
| 746 |
+
|
| 747 |
+
let mut cgi_response = CgiResponse::new(stdout);
|
| 748 |
+
|
| 749 |
+
let stdin_copy_future = Copier::with_zero_packet_writing(cgi_stdin_reader, stdin).copy();
|
| 750 |
+
let mut stdin_copy_future_pinned = Box::pin(stdin_copy_future);
|
| 751 |
+
|
| 752 |
+
let stderr_read_future = ReadToEndFuture::new(stderr);
|
| 753 |
+
let mut stderr_read_future_pinned = Box::pin(stderr_read_future);
|
| 754 |
+
|
| 755 |
+
let mut headers = [EMPTY_HEADER; 128];
|
| 756 |
+
|
| 757 |
+
let mut early_stdin_copied = false;
|
| 758 |
+
|
| 759 |
+
// Needed to wrap this in another scope to prevent errors with multiple mutable borrows.
|
| 760 |
+
{
|
| 761 |
+
let mut head_obtained = false;
|
| 762 |
+
let stdout_parse_future = cgi_response.get_head();
|
| 763 |
+
tokio::pin!(stdout_parse_future);
|
| 764 |
+
|
| 765 |
+
// Cannot use a loop with tokio::select, since stdin_copy_future_pinned being constantly ready will make the web server stop responding to HTTP requests
|
| 766 |
+
tokio::select! {
|
| 767 |
+
biased;
|
| 768 |
+
|
| 769 |
+
result = &mut stdin_copy_future_pinned => {
|
| 770 |
+
early_stdin_copied = true;
|
| 771 |
+
result?;
|
| 772 |
+
},
|
| 773 |
+
obtained_head = &mut stdout_parse_future => {
|
| 774 |
+
let obtained_head = obtained_head?;
|
| 775 |
+
if !obtained_head.is_empty() {
|
| 776 |
+
httparse::parse_headers(obtained_head, &mut headers)?;
|
| 777 |
+
}
|
| 778 |
+
head_obtained = true;
|
| 779 |
+
},
|
| 780 |
+
result = &mut stderr_read_future_pinned => {
|
| 781 |
+
let stderr_vec = result?;
|
| 782 |
+
let stderr_string = String::from_utf8_lossy(stderr_vec.as_slice()).to_string();
|
| 783 |
+
if !stderr_string.is_empty() {
|
| 784 |
+
error_logger
|
| 785 |
+
.log(&format!("There were CGI errors: {}", stderr_string))
|
| 786 |
+
.await;
|
| 787 |
+
}
|
| 788 |
+
return Ok(
|
| 789 |
+
ResponseData::builder_without_request()
|
| 790 |
+
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
| 791 |
+
.build(),
|
| 792 |
+
);
|
| 793 |
+
},
|
| 794 |
+
}
|
| 795 |
+
|
| 796 |
+
if !head_obtained {
|
| 797 |
+
// Kept it same as in the tokio::select macro
|
| 798 |
+
tokio::select! {
|
| 799 |
+
biased;
|
| 800 |
+
|
| 801 |
+
result = &mut stderr_read_future_pinned => {
|
| 802 |
+
let stderr_vec = result?;
|
| 803 |
+
let stderr_string = String::from_utf8_lossy(stderr_vec.as_slice()).to_string();
|
| 804 |
+
if !stderr_string.is_empty() {
|
| 805 |
+
error_logger
|
| 806 |
+
.log(&format!("There were FastCGI errors: {}", stderr_string))
|
| 807 |
+
.await;
|
| 808 |
+
}
|
| 809 |
+
return Ok(
|
| 810 |
+
ResponseData::builder_without_request()
|
| 811 |
+
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
| 812 |
+
.build(),
|
| 813 |
+
);
|
| 814 |
+
},
|
| 815 |
+
obtained_head = &mut stdout_parse_future => {
|
| 816 |
+
let obtained_head = obtained_head?;
|
| 817 |
+
if !obtained_head.is_empty() {
|
| 818 |
+
httparse::parse_headers(obtained_head, &mut headers)?;
|
| 819 |
+
}
|
| 820 |
+
}
|
| 821 |
+
}
|
| 822 |
+
}
|
| 823 |
+
}
|
| 824 |
+
|
| 825 |
+
let mut response_builder = Response::builder();
|
| 826 |
+
let mut status_code = 200;
|
| 827 |
+
for header in headers {
|
| 828 |
+
if header == EMPTY_HEADER {
|
| 829 |
+
break;
|
| 830 |
+
}
|
| 831 |
+
let mut is_status_header = false;
|
| 832 |
+
match &header.name.to_lowercase() as &str {
|
| 833 |
+
"location" => {
|
| 834 |
+
if !(300..=399).contains(&status_code) {
|
| 835 |
+
status_code = 302;
|
| 836 |
+
}
|
| 837 |
+
}
|
| 838 |
+
"status" => {
|
| 839 |
+
is_status_header = true;
|
| 840 |
+
let header_value_cow = String::from_utf8_lossy(header.value);
|
| 841 |
+
let mut split_status = header_value_cow.split(" ");
|
| 842 |
+
let first_part = split_status.next();
|
| 843 |
+
if let Some(first_part) = first_part {
|
| 844 |
+
if first_part.starts_with("HTTP/") {
|
| 845 |
+
let second_part = split_status.next();
|
| 846 |
+
if let Some(second_part) = second_part {
|
| 847 |
+
if let Ok(parsed_status_code) = second_part.parse::<u16>() {
|
| 848 |
+
status_code = parsed_status_code;
|
| 849 |
+
}
|
| 850 |
+
}
|
| 851 |
+
} else if let Ok(parsed_status_code) = first_part.parse::<u16>() {
|
| 852 |
+
status_code = parsed_status_code;
|
| 853 |
+
}
|
| 854 |
+
}
|
| 855 |
+
}
|
| 856 |
+
_ => (),
|
| 857 |
+
}
|
| 858 |
+
if !is_status_header {
|
| 859 |
+
response_builder = response_builder.header(header.name, header.value);
|
| 860 |
+
}
|
| 861 |
+
}
|
| 862 |
+
|
| 863 |
+
response_builder = response_builder.status(status_code);
|
| 864 |
+
|
| 865 |
+
let reader_stream = ReaderStream::new(cgi_response);
|
| 866 |
+
let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
|
| 867 |
+
let boxed_body = stream_body.boxed();
|
| 868 |
+
|
| 869 |
+
let response = response_builder.body(boxed_body)?;
|
| 870 |
+
|
| 871 |
+
let error_logger = error_logger.clone();
|
| 872 |
+
|
| 873 |
+
Ok(
|
| 874 |
+
ResponseData::builder_without_request()
|
| 875 |
+
.response(response)
|
| 876 |
+
.parallel_fn(async move {
|
| 877 |
+
let mut stdin_copied = early_stdin_copied;
|
| 878 |
+
|
| 879 |
+
if !stdin_copied {
|
| 880 |
+
tokio::select! {
|
| 881 |
+
biased;
|
| 882 |
+
|
| 883 |
+
_ = &mut stdin_copy_future_pinned => {
|
| 884 |
+
stdin_copied = true;
|
| 885 |
+
},
|
| 886 |
+
result = &mut stderr_read_future_pinned => {
|
| 887 |
+
let stderr_vec = result.unwrap_or(vec![]);
|
| 888 |
+
let stderr_string = String::from_utf8_lossy(stderr_vec.as_slice()).to_string();
|
| 889 |
+
if !stderr_string.is_empty() {
|
| 890 |
+
error_logger
|
| 891 |
+
.log(&format!("There were FastCGI errors: {}", stderr_string))
|
| 892 |
+
.await;
|
| 893 |
+
}
|
| 894 |
+
},
|
| 895 |
+
}
|
| 896 |
+
}
|
| 897 |
+
|
| 898 |
+
if stdin_copied {
|
| 899 |
+
let stderr_vec = stderr_read_future_pinned.await.unwrap_or(vec![]);
|
| 900 |
+
let stderr_string = String::from_utf8_lossy(stderr_vec.as_slice()).to_string();
|
| 901 |
+
if !stderr_string.is_empty() {
|
| 902 |
+
error_logger
|
| 903 |
+
.log(&format!("There were FastCGI errors: {}", stderr_string))
|
| 904 |
+
.await;
|
| 905 |
+
}
|
| 906 |
+
} else {
|
| 907 |
+
stdin_copy_future_pinned.await.unwrap_or_default();
|
| 908 |
+
}
|
| 909 |
+
})
|
| 910 |
+
.build(),
|
| 911 |
+
)
|
| 912 |
+
}
|
| 913 |
+
|
| 914 |
+
async fn connect_tcp(
|
| 915 |
+
addr: &str,
|
| 916 |
+
) -> Result<
|
| 917 |
+
(
|
| 918 |
+
Box<dyn AsyncRead + Send + Sync + Unpin>,
|
| 919 |
+
Box<dyn AsyncWrite + Send + Sync + Unpin>,
|
| 920 |
+
),
|
| 921 |
+
tokio::io::Error,
|
| 922 |
+
> {
|
| 923 |
+
let socket = TcpStream::connect(addr).await?;
|
| 924 |
+
socket.set_nodelay(true)?;
|
| 925 |
+
|
| 926 |
+
let (socket_reader_set, socket_writer_set) = tokio::io::split(socket);
|
| 927 |
+
Ok((Box::new(socket_reader_set), Box::new(socket_writer_set)))
|
| 928 |
+
}
|
| 929 |
+
|
| 930 |
+
#[allow(dead_code)]
|
| 931 |
+
#[cfg(unix)]
|
| 932 |
+
async fn connect_unix(
|
| 933 |
+
path: &str,
|
| 934 |
+
) -> Result<
|
| 935 |
+
(
|
| 936 |
+
Box<dyn AsyncRead + Send + Sync + Unpin>,
|
| 937 |
+
Box<dyn AsyncWrite + Send + Sync + Unpin>,
|
| 938 |
+
),
|
| 939 |
+
tokio::io::Error,
|
| 940 |
+
> {
|
| 941 |
+
use tokio::net::UnixStream;
|
| 942 |
+
|
| 943 |
+
let socket = UnixStream::connect(path).await?;
|
| 944 |
+
|
| 945 |
+
let (socket_reader_set, socket_writer_set) = tokio::io::split(socket);
|
| 946 |
+
Ok((Box::new(socket_reader_set), Box::new(socket_writer_set)))
|
| 947 |
+
}
|
| 948 |
+
|
| 949 |
+
#[allow(dead_code)]
|
| 950 |
+
#[cfg(not(unix))]
|
| 951 |
+
async fn connect_unix(
|
| 952 |
+
_path: &str,
|
| 953 |
+
) -> Result<
|
| 954 |
+
(
|
| 955 |
+
Box<dyn AsyncRead + Send + Sync + Unpin>,
|
| 956 |
+
Box<dyn AsyncWrite + Send + Sync + Unpin>,
|
| 957 |
+
),
|
| 958 |
+
tokio::io::Error,
|
| 959 |
+
> {
|
| 960 |
+
Err(tokio::io::Error::new(
|
| 961 |
+
tokio::io::ErrorKind::Unsupported,
|
| 962 |
+
"Unix sockets are not supports on non-Unix platforms.",
|
| 963 |
+
))
|
| 964 |
+
}
|
ferron/src/optional_modules/fproxy.rs
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::error::Error;
|
| 2 |
+
use std::str::FromStr;
|
| 3 |
+
|
| 4 |
+
use crate::ferron_common::{
|
| 5 |
+
ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 6 |
+
ServerModuleHandlers, SocketData,
|
| 7 |
+
};
|
| 8 |
+
use crate::ferron_common::{HyperResponse, WithRuntime};
|
| 9 |
+
use async_trait::async_trait;
|
| 10 |
+
use http_body_util::combinators::BoxBody;
|
| 11 |
+
use http_body_util::BodyExt;
|
| 12 |
+
use hyper::body::Bytes;
|
| 13 |
+
use hyper::{header, Request, StatusCode, Uri};
|
| 14 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 15 |
+
use hyper_util::rt::TokioIo;
|
| 16 |
+
use tokio::io::{AsyncRead, AsyncWrite};
|
| 17 |
+
use tokio::net::TcpStream;
|
| 18 |
+
use tokio::runtime::Handle;
|
| 19 |
+
|
| 20 |
+
pub fn server_module_init(
|
| 21 |
+
_config: &ServerConfig,
|
| 22 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 23 |
+
Ok(Box::new(ForwardProxyModule::new()))
|
| 24 |
+
}
|
| 25 |
+
|
| 26 |
+
struct ForwardProxyModule;
|
| 27 |
+
|
| 28 |
+
impl ForwardProxyModule {
|
| 29 |
+
fn new() -> Self {
|
| 30 |
+
Self
|
| 31 |
+
}
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
impl ServerModule for ForwardProxyModule {
|
| 35 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 36 |
+
Box::new(ForwardProxyModuleHandlers { handle })
|
| 37 |
+
}
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
struct ForwardProxyModuleHandlers {
|
| 41 |
+
handle: Handle,
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
#[async_trait]
|
| 45 |
+
impl ServerModuleHandlers for ForwardProxyModuleHandlers {
|
| 46 |
+
async fn request_handler(
|
| 47 |
+
&mut self,
|
| 48 |
+
request: RequestData,
|
| 49 |
+
_config: &ServerConfig,
|
| 50 |
+
_socket_data: &SocketData,
|
| 51 |
+
_error_logger: &ErrorLogger,
|
| 52 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 53 |
+
Ok(ResponseData::builder(request).build())
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
async fn proxy_request_handler(
|
| 57 |
+
&mut self,
|
| 58 |
+
request: RequestData,
|
| 59 |
+
_config: &ServerConfig,
|
| 60 |
+
_socket_data: &SocketData,
|
| 61 |
+
error_logger: &ErrorLogger,
|
| 62 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 63 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 64 |
+
// Code taken from reverse proxy module
|
| 65 |
+
let (hyper_request, _auth_user, _original_url) = request.into_parts();
|
| 66 |
+
let (mut hyper_request_parts, request_body) = hyper_request.into_parts();
|
| 67 |
+
|
| 68 |
+
match hyper_request_parts.uri.scheme_str() {
|
| 69 |
+
Some("http") | None => (),
|
| 70 |
+
_ => {
|
| 71 |
+
return Ok(
|
| 72 |
+
ResponseData::builder_without_request()
|
| 73 |
+
.status(StatusCode::BAD_REQUEST)
|
| 74 |
+
.build(),
|
| 75 |
+
);
|
| 76 |
+
}
|
| 77 |
+
};
|
| 78 |
+
|
| 79 |
+
let host = match hyper_request_parts.uri.host() {
|
| 80 |
+
Some(host) => host,
|
| 81 |
+
None => {
|
| 82 |
+
return Ok(
|
| 83 |
+
ResponseData::builder_without_request()
|
| 84 |
+
.status(StatusCode::BAD_REQUEST)
|
| 85 |
+
.build(),
|
| 86 |
+
);
|
| 87 |
+
}
|
| 88 |
+
};
|
| 89 |
+
|
| 90 |
+
let port = hyper_request_parts.uri.port_u16().unwrap_or(80);
|
| 91 |
+
|
| 92 |
+
let addr = format!("{}:{}", host, port);
|
| 93 |
+
let stream = match TcpStream::connect(addr).await {
|
| 94 |
+
Ok(stream) => stream,
|
| 95 |
+
Err(err) => {
|
| 96 |
+
match err.kind() {
|
| 97 |
+
tokio::io::ErrorKind::ConnectionRefused
|
| 98 |
+
| tokio::io::ErrorKind::NotFound
|
| 99 |
+
| tokio::io::ErrorKind::HostUnreachable => {
|
| 100 |
+
error_logger
|
| 101 |
+
.log(&format!("Service unavailable: {}", err))
|
| 102 |
+
.await;
|
| 103 |
+
return Ok(
|
| 104 |
+
ResponseData::builder_without_request()
|
| 105 |
+
.status(StatusCode::SERVICE_UNAVAILABLE)
|
| 106 |
+
.build(),
|
| 107 |
+
);
|
| 108 |
+
}
|
| 109 |
+
tokio::io::ErrorKind::TimedOut => {
|
| 110 |
+
error_logger.log(&format!("Gateway timeout: {}", err)).await;
|
| 111 |
+
return Ok(
|
| 112 |
+
ResponseData::builder_without_request()
|
| 113 |
+
.status(StatusCode::GATEWAY_TIMEOUT)
|
| 114 |
+
.build(),
|
| 115 |
+
);
|
| 116 |
+
}
|
| 117 |
+
_ => {
|
| 118 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 119 |
+
return Ok(
|
| 120 |
+
ResponseData::builder_without_request()
|
| 121 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 122 |
+
.build(),
|
| 123 |
+
);
|
| 124 |
+
}
|
| 125 |
+
};
|
| 126 |
+
}
|
| 127 |
+
};
|
| 128 |
+
|
| 129 |
+
match stream.set_nodelay(true) {
|
| 130 |
+
Ok(_) => (),
|
| 131 |
+
Err(err) => {
|
| 132 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 133 |
+
return Ok(
|
| 134 |
+
ResponseData::builder_without_request()
|
| 135 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 136 |
+
.build(),
|
| 137 |
+
);
|
| 138 |
+
}
|
| 139 |
+
};
|
| 140 |
+
|
| 141 |
+
let hyper_request_path = hyper_request_parts.uri.path();
|
| 142 |
+
|
| 143 |
+
hyper_request_parts.uri = Uri::from_str(&format!(
|
| 144 |
+
"{}{}",
|
| 145 |
+
hyper_request_path,
|
| 146 |
+
match hyper_request_parts.uri.query() {
|
| 147 |
+
Some(query) => format!("?{}", query),
|
| 148 |
+
None => "".to_string(),
|
| 149 |
+
}
|
| 150 |
+
))?;
|
| 151 |
+
|
| 152 |
+
// Connection header to disable HTTP/1.1 keep-alive
|
| 153 |
+
hyper_request_parts
|
| 154 |
+
.headers
|
| 155 |
+
.insert(header::CONNECTION, "close".parse()?);
|
| 156 |
+
|
| 157 |
+
let proxy_request = Request::from_parts(hyper_request_parts, request_body);
|
| 158 |
+
|
| 159 |
+
http_proxy(stream, proxy_request, error_logger).await
|
| 160 |
+
})
|
| 161 |
+
.await
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
async fn response_modifying_handler(
|
| 165 |
+
&mut self,
|
| 166 |
+
response: HyperResponse,
|
| 167 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 168 |
+
Ok(response)
|
| 169 |
+
}
|
| 170 |
+
|
| 171 |
+
async fn proxy_response_modifying_handler(
|
| 172 |
+
&mut self,
|
| 173 |
+
response: HyperResponse,
|
| 174 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 175 |
+
Ok(response)
|
| 176 |
+
}
|
| 177 |
+
|
| 178 |
+
async fn connect_proxy_request_handler(
|
| 179 |
+
&mut self,
|
| 180 |
+
upgraded_request: HyperUpgraded,
|
| 181 |
+
connect_address: &str,
|
| 182 |
+
_config: &ServerConfig,
|
| 183 |
+
_socket_data: &SocketData,
|
| 184 |
+
error_logger: &ErrorLogger,
|
| 185 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 186 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 187 |
+
let mut stream = match TcpStream::connect(connect_address).await {
|
| 188 |
+
Ok(stream) => stream,
|
| 189 |
+
Err(err) => {
|
| 190 |
+
error_logger
|
| 191 |
+
.log(&format!("Cannot connect to the remote server: {}", err))
|
| 192 |
+
.await;
|
| 193 |
+
return Ok(());
|
| 194 |
+
}
|
| 195 |
+
};
|
| 196 |
+
match stream.set_nodelay(true) {
|
| 197 |
+
Ok(_) => (),
|
| 198 |
+
Err(err) => {
|
| 199 |
+
error_logger
|
| 200 |
+
.log(&format!(
|
| 201 |
+
"Cannot disable Nagle algorithm when connecting to the remote server: {}",
|
| 202 |
+
err
|
| 203 |
+
))
|
| 204 |
+
.await;
|
| 205 |
+
return Ok(());
|
| 206 |
+
}
|
| 207 |
+
};
|
| 208 |
+
|
| 209 |
+
let mut upgraded = TokioIo::new(upgraded_request);
|
| 210 |
+
|
| 211 |
+
tokio::io::copy_bidirectional(&mut upgraded, &mut stream)
|
| 212 |
+
.await
|
| 213 |
+
.unwrap_or_default();
|
| 214 |
+
|
| 215 |
+
Ok(())
|
| 216 |
+
})
|
| 217 |
+
.await
|
| 218 |
+
}
|
| 219 |
+
|
| 220 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 221 |
+
true
|
| 222 |
+
}
|
| 223 |
+
|
| 224 |
+
async fn websocket_request_handler(
|
| 225 |
+
&mut self,
|
| 226 |
+
_websocket: HyperWebsocket,
|
| 227 |
+
_uri: &hyper::Uri,
|
| 228 |
+
_config: &ServerConfig,
|
| 229 |
+
_socket_data: &SocketData,
|
| 230 |
+
_error_logger: &ErrorLogger,
|
| 231 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 232 |
+
Ok(())
|
| 233 |
+
}
|
| 234 |
+
|
| 235 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 236 |
+
false
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
+
async fn http_proxy(
|
| 241 |
+
stream: impl AsyncRead + AsyncWrite + Send + Unpin + 'static,
|
| 242 |
+
proxy_request: Request<BoxBody<Bytes, std::io::Error>>,
|
| 243 |
+
error_logger: &ErrorLogger,
|
| 244 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 245 |
+
let io = TokioIo::new(stream);
|
| 246 |
+
|
| 247 |
+
let (mut sender, conn) = match hyper::client::conn::http1::handshake(io).await {
|
| 248 |
+
Ok(data) => data,
|
| 249 |
+
Err(err) => {
|
| 250 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 251 |
+
return Ok(
|
| 252 |
+
ResponseData::builder_without_request()
|
| 253 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 254 |
+
.build(),
|
| 255 |
+
);
|
| 256 |
+
}
|
| 257 |
+
};
|
| 258 |
+
|
| 259 |
+
let send_request = sender.send_request(proxy_request);
|
| 260 |
+
|
| 261 |
+
let mut pinned_conn = Box::pin(conn);
|
| 262 |
+
tokio::pin!(send_request);
|
| 263 |
+
|
| 264 |
+
let response;
|
| 265 |
+
|
| 266 |
+
loop {
|
| 267 |
+
tokio::select! {
|
| 268 |
+
biased;
|
| 269 |
+
|
| 270 |
+
proxy_response = &mut send_request => {
|
| 271 |
+
let proxy_response = match proxy_response {
|
| 272 |
+
Ok(response) => response,
|
| 273 |
+
Err(err) => {
|
| 274 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 275 |
+
return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
|
| 276 |
+
}
|
| 277 |
+
};
|
| 278 |
+
|
| 279 |
+
response = ResponseData::builder_without_request()
|
| 280 |
+
.response(proxy_response.map(|b| {
|
| 281 |
+
b.map_err(|e| std::io::Error::other(e.to_string()))
|
| 282 |
+
.boxed()
|
| 283 |
+
}))
|
| 284 |
+
.parallel_fn(async move {
|
| 285 |
+
pinned_conn.await.unwrap_or_default();
|
| 286 |
+
})
|
| 287 |
+
.build();
|
| 288 |
+
|
| 289 |
+
break;
|
| 290 |
+
},
|
| 291 |
+
state = &mut pinned_conn => {
|
| 292 |
+
if state.is_err() {
|
| 293 |
+
error_logger.log("Bad gateway: incomplete response").await;
|
| 294 |
+
return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
|
| 295 |
+
}
|
| 296 |
+
},
|
| 297 |
+
};
|
| 298 |
+
}
|
| 299 |
+
|
| 300 |
+
Ok(response)
|
| 301 |
+
}
|
ferron/src/optional_modules/rproxy.rs
ADDED
|
@@ -0,0 +1,803 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::collections::HashMap;
|
| 2 |
+
use std::error::Error;
|
| 3 |
+
use std::str::FromStr;
|
| 4 |
+
use std::sync::Arc;
|
| 5 |
+
use std::time::Duration;
|
| 6 |
+
|
| 7 |
+
use crate::ferron_common::{
|
| 8 |
+
ErrorLogger, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 9 |
+
ServerModuleHandlers, SocketData,
|
| 10 |
+
};
|
| 11 |
+
use crate::ferron_common::{HyperResponse, WithRuntime};
|
| 12 |
+
use async_trait::async_trait;
|
| 13 |
+
use futures_util::{SinkExt, StreamExt};
|
| 14 |
+
use http::uri::{PathAndQuery, Scheme};
|
| 15 |
+
use http_body_util::combinators::BoxBody;
|
| 16 |
+
use http_body_util::BodyExt;
|
| 17 |
+
use hyper::body::Bytes;
|
| 18 |
+
use hyper::client::conn::http1::SendRequest;
|
| 19 |
+
use hyper::{header, Request, StatusCode, Uri};
|
| 20 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 21 |
+
use hyper_util::rt::TokioIo;
|
| 22 |
+
use rustls::pki_types::ServerName;
|
| 23 |
+
use rustls::RootCertStore;
|
| 24 |
+
use rustls_native_certs::load_native_certs;
|
| 25 |
+
use tokio::io::{AsyncRead, AsyncWrite};
|
| 26 |
+
use tokio::net::TcpStream;
|
| 27 |
+
use tokio::runtime::Handle;
|
| 28 |
+
use tokio::sync::RwLock;
|
| 29 |
+
use tokio_rustls::TlsConnector;
|
| 30 |
+
use tokio_tungstenite::Connector;
|
| 31 |
+
|
| 32 |
+
use crate::ferron_util::no_server_verifier::NoServerVerifier;
|
| 33 |
+
use crate::ferron_util::ttl_cache::TtlCache;
|
| 34 |
+
|
| 35 |
+
const DEFAULT_CONCURRENT_CONNECTIONS_PER_HOST: u32 = 32;
|
| 36 |
+
|
| 37 |
+
pub fn server_module_init(
|
| 38 |
+
config: &ServerConfig,
|
| 39 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 40 |
+
let mut roots: RootCertStore = RootCertStore::empty();
|
| 41 |
+
let certs_result = load_native_certs();
|
| 42 |
+
if !certs_result.errors.is_empty() {
|
| 43 |
+
Err(anyhow::anyhow!(format!(
|
| 44 |
+
"Couldn't load the native certificate store: {}",
|
| 45 |
+
certs_result.errors[0]
|
| 46 |
+
)))?
|
| 47 |
+
}
|
| 48 |
+
let certs = certs_result.certs;
|
| 49 |
+
|
| 50 |
+
for cert in certs {
|
| 51 |
+
match roots.add(cert) {
|
| 52 |
+
Ok(_) => (),
|
| 53 |
+
Err(err) => Err(anyhow::anyhow!(format!(
|
| 54 |
+
"Couldn't add a certificate to the certificate store: {}",
|
| 55 |
+
err
|
| 56 |
+
)))?,
|
| 57 |
+
}
|
| 58 |
+
}
|
| 59 |
+
|
| 60 |
+
let mut connections_vec = Vec::new();
|
| 61 |
+
for _ in 0..DEFAULT_CONCURRENT_CONNECTIONS_PER_HOST {
|
| 62 |
+
connections_vec.push(RwLock::new(HashMap::new()));
|
| 63 |
+
}
|
| 64 |
+
Ok(Box::new(ReverseProxyModule::new(
|
| 65 |
+
Arc::new(roots),
|
| 66 |
+
Arc::new(connections_vec),
|
| 67 |
+
Arc::new(RwLock::new(TtlCache::new(Duration::from_millis(
|
| 68 |
+
config["global"]["loadBalancerHealthCheckWindow"]
|
| 69 |
+
.as_i64()
|
| 70 |
+
.unwrap_or(5000) as u64,
|
| 71 |
+
)))),
|
| 72 |
+
)))
|
| 73 |
+
}
|
| 74 |
+
|
| 75 |
+
#[allow(clippy::type_complexity)]
|
| 76 |
+
struct ReverseProxyModule {
|
| 77 |
+
roots: Arc<RootCertStore>,
|
| 78 |
+
connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, std::io::Error>>>>>>,
|
| 79 |
+
failed_backends: Arc<RwLock<TtlCache<String, u64>>>,
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
impl ReverseProxyModule {
|
| 83 |
+
#[allow(clippy::type_complexity)]
|
| 84 |
+
fn new(
|
| 85 |
+
roots: Arc<RootCertStore>,
|
| 86 |
+
connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, std::io::Error>>>>>>,
|
| 87 |
+
failed_backends: Arc<RwLock<TtlCache<String, u64>>>,
|
| 88 |
+
) -> Self {
|
| 89 |
+
Self {
|
| 90 |
+
roots,
|
| 91 |
+
connections,
|
| 92 |
+
failed_backends,
|
| 93 |
+
}
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
|
| 97 |
+
impl ServerModule for ReverseProxyModule {
|
| 98 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 99 |
+
Box::new(ReverseProxyModuleHandlers {
|
| 100 |
+
roots: self.roots.clone(),
|
| 101 |
+
connections: self.connections.clone(),
|
| 102 |
+
failed_backends: self.failed_backends.clone(),
|
| 103 |
+
handle,
|
| 104 |
+
})
|
| 105 |
+
}
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
#[allow(clippy::type_complexity)]
|
| 109 |
+
struct ReverseProxyModuleHandlers {
|
| 110 |
+
handle: Handle,
|
| 111 |
+
roots: Arc<RootCertStore>,
|
| 112 |
+
connections: Arc<Vec<RwLock<HashMap<String, SendRequest<BoxBody<Bytes, std::io::Error>>>>>>,
|
| 113 |
+
failed_backends: Arc<RwLock<TtlCache<String, u64>>>,
|
| 114 |
+
}
|
| 115 |
+
|
| 116 |
+
#[async_trait]
|
| 117 |
+
impl ServerModuleHandlers for ReverseProxyModuleHandlers {
|
| 118 |
+
async fn request_handler(
|
| 119 |
+
&mut self,
|
| 120 |
+
request: RequestData,
|
| 121 |
+
config: &ServerConfig,
|
| 122 |
+
socket_data: &SocketData,
|
| 123 |
+
error_logger: &ErrorLogger,
|
| 124 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 125 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 126 |
+
let enable_health_check = config["enableLoadBalancerHealthCheck"]
|
| 127 |
+
.as_bool()
|
| 128 |
+
.unwrap_or(false);
|
| 129 |
+
let health_check_max_fails = config["loadBalancerHealthCheckMaximumFails"]
|
| 130 |
+
.as_i64()
|
| 131 |
+
.unwrap_or(3) as u64;
|
| 132 |
+
let disable_certificate_verification = config["disableProxyCertificateVerification"]
|
| 133 |
+
.as_bool()
|
| 134 |
+
.unwrap_or(false);
|
| 135 |
+
if let Some(proxy_to) = determine_proxy_to(
|
| 136 |
+
config,
|
| 137 |
+
socket_data.encrypted,
|
| 138 |
+
&self.failed_backends,
|
| 139 |
+
enable_health_check,
|
| 140 |
+
health_check_max_fails,
|
| 141 |
+
)
|
| 142 |
+
.await
|
| 143 |
+
{
|
| 144 |
+
let (hyper_request, _auth_user, _original_url) = request.into_parts();
|
| 145 |
+
let (mut hyper_request_parts, request_body) = hyper_request.into_parts();
|
| 146 |
+
|
| 147 |
+
let proxy_request_url = proxy_to.parse::<hyper::Uri>()?;
|
| 148 |
+
let scheme_str = proxy_request_url.scheme_str();
|
| 149 |
+
let mut encrypted = false;
|
| 150 |
+
|
| 151 |
+
match scheme_str {
|
| 152 |
+
Some("http") => {
|
| 153 |
+
encrypted = false;
|
| 154 |
+
}
|
| 155 |
+
Some("https") => {
|
| 156 |
+
encrypted = true;
|
| 157 |
+
}
|
| 158 |
+
_ => Err(anyhow::anyhow!(
|
| 159 |
+
"Only HTTP and HTTPS reverse proxy URLs are supported."
|
| 160 |
+
))?,
|
| 161 |
+
};
|
| 162 |
+
|
| 163 |
+
let host = match proxy_request_url.host() {
|
| 164 |
+
Some(host) => host,
|
| 165 |
+
None => Err(anyhow::anyhow!(
|
| 166 |
+
"The reverse proxy URL doesn't include the host"
|
| 167 |
+
))?,
|
| 168 |
+
};
|
| 169 |
+
|
| 170 |
+
let port = proxy_request_url.port_u16().unwrap_or(match scheme_str {
|
| 171 |
+
Some("http") => 80,
|
| 172 |
+
Some("https") => 443,
|
| 173 |
+
_ => 80,
|
| 174 |
+
});
|
| 175 |
+
|
| 176 |
+
let addr = format!("{}:{}", host, port);
|
| 177 |
+
let authority = proxy_request_url.authority().cloned();
|
| 178 |
+
|
| 179 |
+
let hyper_request_path = hyper_request_parts.uri.path();
|
| 180 |
+
|
| 181 |
+
let path = match hyper_request_path.as_bytes().first() {
|
| 182 |
+
Some(b'/') => {
|
| 183 |
+
let mut proxy_request_path = proxy_request_url.path();
|
| 184 |
+
while proxy_request_path.as_bytes().last().copied() == Some(b'/') {
|
| 185 |
+
proxy_request_path = &proxy_request_path[..(proxy_request_path.len() - 1)];
|
| 186 |
+
}
|
| 187 |
+
format!("{}{}", proxy_request_path, hyper_request_path)
|
| 188 |
+
}
|
| 189 |
+
_ => hyper_request_path.to_string(),
|
| 190 |
+
};
|
| 191 |
+
|
| 192 |
+
hyper_request_parts.uri = Uri::from_str(&format!(
|
| 193 |
+
"{}{}",
|
| 194 |
+
path,
|
| 195 |
+
match hyper_request_parts.uri.query() {
|
| 196 |
+
Some(query) => format!("?{}", query),
|
| 197 |
+
None => "".to_string(),
|
| 198 |
+
}
|
| 199 |
+
))?;
|
| 200 |
+
|
| 201 |
+
let original_host = hyper_request_parts.headers.get(header::HOST).cloned();
|
| 202 |
+
|
| 203 |
+
// Host header for host identification
|
| 204 |
+
match authority {
|
| 205 |
+
Some(authority) => {
|
| 206 |
+
hyper_request_parts
|
| 207 |
+
.headers
|
| 208 |
+
.insert(header::HOST, authority.to_string().parse()?);
|
| 209 |
+
}
|
| 210 |
+
None => {
|
| 211 |
+
hyper_request_parts.headers.remove(header::HOST);
|
| 212 |
+
}
|
| 213 |
+
}
|
| 214 |
+
|
| 215 |
+
// Connection header to enable HTTP/1.1 keep-alive
|
| 216 |
+
hyper_request_parts
|
| 217 |
+
.headers
|
| 218 |
+
.insert(header::CONNECTION, "keep-alive".parse()?);
|
| 219 |
+
|
| 220 |
+
// X-Forwarded-* headers to send the client's data to a server that's behind the reverse proxy
|
| 221 |
+
hyper_request_parts.headers.insert(
|
| 222 |
+
"x-forwarded-for",
|
| 223 |
+
socket_data
|
| 224 |
+
.remote_addr
|
| 225 |
+
.ip()
|
| 226 |
+
.to_canonical()
|
| 227 |
+
.to_string()
|
| 228 |
+
.parse()?,
|
| 229 |
+
);
|
| 230 |
+
|
| 231 |
+
if socket_data.encrypted {
|
| 232 |
+
hyper_request_parts
|
| 233 |
+
.headers
|
| 234 |
+
.insert("x-forwarded-proto", "https".parse()?);
|
| 235 |
+
} else {
|
| 236 |
+
hyper_request_parts
|
| 237 |
+
.headers
|
| 238 |
+
.insert("x-forwarded-proto", "http".parse()?);
|
| 239 |
+
}
|
| 240 |
+
|
| 241 |
+
if let Some(original_host) = original_host {
|
| 242 |
+
hyper_request_parts
|
| 243 |
+
.headers
|
| 244 |
+
.insert("x-forwarded-host", original_host);
|
| 245 |
+
}
|
| 246 |
+
|
| 247 |
+
let proxy_request = Request::from_parts(hyper_request_parts, request_body);
|
| 248 |
+
|
| 249 |
+
let connections = &self.connections[rand::random_range(..self.connections.len())];
|
| 250 |
+
|
| 251 |
+
let rwlock_read = connections.read().await;
|
| 252 |
+
let sender_read_option = rwlock_read.get(&addr);
|
| 253 |
+
|
| 254 |
+
if let Some(sender_read) = sender_read_option {
|
| 255 |
+
if !sender_read.is_closed() {
|
| 256 |
+
drop(rwlock_read);
|
| 257 |
+
let mut rwlock_write = connections.write().await;
|
| 258 |
+
let sender_option = rwlock_write.get_mut(&addr);
|
| 259 |
+
|
| 260 |
+
if let Some(sender) = sender_option {
|
| 261 |
+
if !sender.is_closed() {
|
| 262 |
+
let result = http_proxy_kept_alive(sender, proxy_request, error_logger).await;
|
| 263 |
+
drop(rwlock_write);
|
| 264 |
+
return result;
|
| 265 |
+
} else {
|
| 266 |
+
drop(rwlock_write);
|
| 267 |
+
}
|
| 268 |
+
} else {
|
| 269 |
+
drop(rwlock_write);
|
| 270 |
+
}
|
| 271 |
+
} else {
|
| 272 |
+
drop(rwlock_read);
|
| 273 |
+
}
|
| 274 |
+
} else {
|
| 275 |
+
drop(rwlock_read);
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
+
let stream = match TcpStream::connect(&addr).await {
|
| 279 |
+
Ok(stream) => stream,
|
| 280 |
+
Err(err) => {
|
| 281 |
+
if enable_health_check {
|
| 282 |
+
let mut failed_backends_write = self.failed_backends.write().await;
|
| 283 |
+
let proxy_to = proxy_to.clone();
|
| 284 |
+
let failed_attempts = failed_backends_write.get(&proxy_to);
|
| 285 |
+
failed_backends_write.insert(proxy_to, failed_attempts.map_or(1, |x| x + 1));
|
| 286 |
+
}
|
| 287 |
+
match err.kind() {
|
| 288 |
+
tokio::io::ErrorKind::ConnectionRefused
|
| 289 |
+
| tokio::io::ErrorKind::NotFound
|
| 290 |
+
| tokio::io::ErrorKind::HostUnreachable => {
|
| 291 |
+
error_logger
|
| 292 |
+
.log(&format!("Service unavailable: {}", err))
|
| 293 |
+
.await;
|
| 294 |
+
return Ok(
|
| 295 |
+
ResponseData::builder_without_request()
|
| 296 |
+
.status(StatusCode::SERVICE_UNAVAILABLE)
|
| 297 |
+
.build(),
|
| 298 |
+
);
|
| 299 |
+
}
|
| 300 |
+
tokio::io::ErrorKind::TimedOut => {
|
| 301 |
+
error_logger.log(&format!("Gateway timeout: {}", err)).await;
|
| 302 |
+
return Ok(
|
| 303 |
+
ResponseData::builder_without_request()
|
| 304 |
+
.status(StatusCode::GATEWAY_TIMEOUT)
|
| 305 |
+
.build(),
|
| 306 |
+
);
|
| 307 |
+
}
|
| 308 |
+
_ => {
|
| 309 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 310 |
+
return Ok(
|
| 311 |
+
ResponseData::builder_without_request()
|
| 312 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 313 |
+
.build(),
|
| 314 |
+
);
|
| 315 |
+
}
|
| 316 |
+
};
|
| 317 |
+
}
|
| 318 |
+
};
|
| 319 |
+
|
| 320 |
+
match stream.set_nodelay(true) {
|
| 321 |
+
Ok(_) => (),
|
| 322 |
+
Err(err) => {
|
| 323 |
+
if enable_health_check {
|
| 324 |
+
let mut failed_backends_write = self.failed_backends.write().await;
|
| 325 |
+
let proxy_to = proxy_to.clone();
|
| 326 |
+
let failed_attempts = failed_backends_write.get(&proxy_to);
|
| 327 |
+
failed_backends_write.insert(proxy_to, failed_attempts.map_or(1, |x| x + 1));
|
| 328 |
+
}
|
| 329 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 330 |
+
return Ok(
|
| 331 |
+
ResponseData::builder_without_request()
|
| 332 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 333 |
+
.build(),
|
| 334 |
+
);
|
| 335 |
+
}
|
| 336 |
+
};
|
| 337 |
+
|
| 338 |
+
let failed_backends_option_borrowed = if enable_health_check {
|
| 339 |
+
Some(&*self.failed_backends)
|
| 340 |
+
} else {
|
| 341 |
+
None
|
| 342 |
+
};
|
| 343 |
+
|
| 344 |
+
if !encrypted {
|
| 345 |
+
http_proxy(
|
| 346 |
+
connections,
|
| 347 |
+
addr,
|
| 348 |
+
stream,
|
| 349 |
+
proxy_request,
|
| 350 |
+
error_logger,
|
| 351 |
+
proxy_to,
|
| 352 |
+
failed_backends_option_borrowed,
|
| 353 |
+
)
|
| 354 |
+
.await
|
| 355 |
+
} else {
|
| 356 |
+
let tls_client_config = (if disable_certificate_verification {
|
| 357 |
+
rustls::ClientConfig::builder()
|
| 358 |
+
.dangerous()
|
| 359 |
+
.with_custom_certificate_verifier(Arc::new(NoServerVerifier::new()))
|
| 360 |
+
} else {
|
| 361 |
+
rustls::ClientConfig::builder().with_root_certificates(self.roots.clone())
|
| 362 |
+
})
|
| 363 |
+
.with_no_client_auth();
|
| 364 |
+
let connector = TlsConnector::from(Arc::new(tls_client_config));
|
| 365 |
+
let domain = ServerName::try_from(host)?.to_owned();
|
| 366 |
+
|
| 367 |
+
let tls_stream = match connector.connect(domain, stream).await {
|
| 368 |
+
Ok(stream) => stream,
|
| 369 |
+
Err(err) => {
|
| 370 |
+
if enable_health_check {
|
| 371 |
+
let mut failed_backends_write = self.failed_backends.write().await;
|
| 372 |
+
let proxy_to = proxy_to.clone();
|
| 373 |
+
let failed_attempts = failed_backends_write.get(&proxy_to);
|
| 374 |
+
failed_backends_write.insert(proxy_to, failed_attempts.map_or(1, |x| x + 1));
|
| 375 |
+
}
|
| 376 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 377 |
+
return Ok(
|
| 378 |
+
ResponseData::builder_without_request()
|
| 379 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 380 |
+
.build(),
|
| 381 |
+
);
|
| 382 |
+
}
|
| 383 |
+
};
|
| 384 |
+
|
| 385 |
+
http_proxy(
|
| 386 |
+
connections,
|
| 387 |
+
addr,
|
| 388 |
+
tls_stream,
|
| 389 |
+
proxy_request,
|
| 390 |
+
error_logger,
|
| 391 |
+
proxy_to,
|
| 392 |
+
failed_backends_option_borrowed,
|
| 393 |
+
)
|
| 394 |
+
.await
|
| 395 |
+
}
|
| 396 |
+
} else {
|
| 397 |
+
Ok(ResponseData::builder(request).build())
|
| 398 |
+
}
|
| 399 |
+
})
|
| 400 |
+
.await
|
| 401 |
+
}
|
| 402 |
+
|
| 403 |
+
async fn proxy_request_handler(
|
| 404 |
+
&mut self,
|
| 405 |
+
request: RequestData,
|
| 406 |
+
_config: &ServerConfig,
|
| 407 |
+
_socket_data: &SocketData,
|
| 408 |
+
_error_logger: &ErrorLogger,
|
| 409 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 410 |
+
Ok(ResponseData::builder(request).build())
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
async fn response_modifying_handler(
|
| 414 |
+
&mut self,
|
| 415 |
+
response: HyperResponse,
|
| 416 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 417 |
+
Ok(response)
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
async fn proxy_response_modifying_handler(
|
| 421 |
+
&mut self,
|
| 422 |
+
response: HyperResponse,
|
| 423 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 424 |
+
Ok(response)
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
async fn connect_proxy_request_handler(
|
| 428 |
+
&mut self,
|
| 429 |
+
_upgraded_request: HyperUpgraded,
|
| 430 |
+
_connect_address: &str,
|
| 431 |
+
_config: &ServerConfig,
|
| 432 |
+
_socket_data: &SocketData,
|
| 433 |
+
_error_logger: &ErrorLogger,
|
| 434 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 435 |
+
Ok(())
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 439 |
+
false
|
| 440 |
+
}
|
| 441 |
+
|
| 442 |
+
async fn websocket_request_handler(
|
| 443 |
+
&mut self,
|
| 444 |
+
websocket: HyperWebsocket,
|
| 445 |
+
uri: &hyper::Uri,
|
| 446 |
+
config: &ServerConfig,
|
| 447 |
+
socket_data: &SocketData,
|
| 448 |
+
error_logger: &ErrorLogger,
|
| 449 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 450 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 451 |
+
let enable_health_check = config["enableLoadBalancerHealthCheck"]
|
| 452 |
+
.as_bool()
|
| 453 |
+
.unwrap_or(false);
|
| 454 |
+
let health_check_max_fails = config["loadBalancerHealthCheckMaximumFails"]
|
| 455 |
+
.as_i64()
|
| 456 |
+
.unwrap_or(3) as u64;
|
| 457 |
+
|
| 458 |
+
let disable_certificate_verification = config["disableProxyCertificateVerification"]
|
| 459 |
+
.as_bool()
|
| 460 |
+
.unwrap_or(false);
|
| 461 |
+
if let Some(proxy_to) = determine_proxy_to(
|
| 462 |
+
config,
|
| 463 |
+
socket_data.encrypted,
|
| 464 |
+
&self.failed_backends,
|
| 465 |
+
enable_health_check,
|
| 466 |
+
health_check_max_fails,
|
| 467 |
+
)
|
| 468 |
+
.await
|
| 469 |
+
{
|
| 470 |
+
let proxy_request_url = proxy_to.parse::<hyper::Uri>()?;
|
| 471 |
+
let scheme_str = proxy_request_url.scheme_str();
|
| 472 |
+
let mut encrypted = false;
|
| 473 |
+
|
| 474 |
+
match scheme_str {
|
| 475 |
+
Some("http") => {
|
| 476 |
+
encrypted = false;
|
| 477 |
+
}
|
| 478 |
+
Some("https") => {
|
| 479 |
+
encrypted = true;
|
| 480 |
+
}
|
| 481 |
+
_ => Err(anyhow::anyhow!(
|
| 482 |
+
"Only HTTP and HTTPS reverse proxy URLs are supported."
|
| 483 |
+
))?,
|
| 484 |
+
};
|
| 485 |
+
|
| 486 |
+
let request_path = uri.path();
|
| 487 |
+
|
| 488 |
+
let path = match request_path.as_bytes().first() {
|
| 489 |
+
Some(b'/') => {
|
| 490 |
+
let mut proxy_request_path = proxy_request_url.path();
|
| 491 |
+
while proxy_request_path.as_bytes().last().copied() == Some(b'/') {
|
| 492 |
+
proxy_request_path = &proxy_request_path[..(proxy_request_path.len() - 1)];
|
| 493 |
+
}
|
| 494 |
+
format!("{}{}", proxy_request_path, request_path)
|
| 495 |
+
}
|
| 496 |
+
_ => request_path.to_string(),
|
| 497 |
+
};
|
| 498 |
+
|
| 499 |
+
let mut proxy_request_url_parts = proxy_request_url.into_parts();
|
| 500 |
+
proxy_request_url_parts.scheme = if encrypted {
|
| 501 |
+
Some(Scheme::from_str("wss")?)
|
| 502 |
+
} else {
|
| 503 |
+
Some(Scheme::from_str("ws")?)
|
| 504 |
+
};
|
| 505 |
+
match proxy_request_url_parts.path_and_query {
|
| 506 |
+
Some(path_and_query) => {
|
| 507 |
+
let path_and_query_string = match path_and_query.query() {
|
| 508 |
+
Some(query) => {
|
| 509 |
+
format!("{}?{}", path, query)
|
| 510 |
+
}
|
| 511 |
+
None => path,
|
| 512 |
+
};
|
| 513 |
+
proxy_request_url_parts.path_and_query =
|
| 514 |
+
Some(PathAndQuery::from_str(&path_and_query_string)?);
|
| 515 |
+
}
|
| 516 |
+
None => {
|
| 517 |
+
proxy_request_url_parts.path_and_query = Some(PathAndQuery::from_str(&path)?);
|
| 518 |
+
}
|
| 519 |
+
};
|
| 520 |
+
|
| 521 |
+
let proxy_request_url = hyper::Uri::from_parts(proxy_request_url_parts)?;
|
| 522 |
+
|
| 523 |
+
let connector = if !encrypted {
|
| 524 |
+
Connector::Plain
|
| 525 |
+
} else {
|
| 526 |
+
Connector::Rustls(Arc::new(
|
| 527 |
+
(if disable_certificate_verification {
|
| 528 |
+
rustls::ClientConfig::builder()
|
| 529 |
+
.dangerous()
|
| 530 |
+
.with_custom_certificate_verifier(Arc::new(NoServerVerifier::new()))
|
| 531 |
+
} else {
|
| 532 |
+
rustls::ClientConfig::builder().with_root_certificates(self.roots.clone())
|
| 533 |
+
})
|
| 534 |
+
.with_no_client_auth(),
|
| 535 |
+
))
|
| 536 |
+
};
|
| 537 |
+
|
| 538 |
+
let client_bi_stream = websocket.await?;
|
| 539 |
+
|
| 540 |
+
let (proxy_bi_stream, _) = match tokio_tungstenite::connect_async_tls_with_config(
|
| 541 |
+
proxy_request_url,
|
| 542 |
+
None,
|
| 543 |
+
true,
|
| 544 |
+
Some(connector),
|
| 545 |
+
)
|
| 546 |
+
.await
|
| 547 |
+
{
|
| 548 |
+
Ok(data) => data,
|
| 549 |
+
Err(err) => {
|
| 550 |
+
error_logger
|
| 551 |
+
.log(&format!("Cannot connect to WebSocket server: {}", err))
|
| 552 |
+
.await;
|
| 553 |
+
return Ok(());
|
| 554 |
+
}
|
| 555 |
+
};
|
| 556 |
+
|
| 557 |
+
let (mut client_sink, mut client_stream) = client_bi_stream.split();
|
| 558 |
+
let (mut proxy_sink, mut proxy_stream) = proxy_bi_stream.split();
|
| 559 |
+
|
| 560 |
+
let client_to_proxy = async {
|
| 561 |
+
while let Some(Ok(value)) = client_stream.next().await {
|
| 562 |
+
if proxy_sink.send(value).await.is_err() {
|
| 563 |
+
break;
|
| 564 |
+
}
|
| 565 |
+
}
|
| 566 |
+
};
|
| 567 |
+
|
| 568 |
+
let proxy_to_client = async {
|
| 569 |
+
while let Some(Ok(value)) = proxy_stream.next().await {
|
| 570 |
+
if client_sink.send(value).await.is_err() {
|
| 571 |
+
break;
|
| 572 |
+
}
|
| 573 |
+
}
|
| 574 |
+
};
|
| 575 |
+
|
| 576 |
+
tokio::pin!(client_to_proxy);
|
| 577 |
+
tokio::pin!(proxy_to_client);
|
| 578 |
+
|
| 579 |
+
let client_to_proxy_first;
|
| 580 |
+
tokio::select! {
|
| 581 |
+
_ = &mut client_to_proxy => {
|
| 582 |
+
client_to_proxy_first = true;
|
| 583 |
+
}
|
| 584 |
+
_ = &mut proxy_to_client => {
|
| 585 |
+
client_to_proxy_first = false;
|
| 586 |
+
}
|
| 587 |
+
}
|
| 588 |
+
|
| 589 |
+
if client_to_proxy_first {
|
| 590 |
+
proxy_to_client.await;
|
| 591 |
+
} else {
|
| 592 |
+
client_to_proxy.await;
|
| 593 |
+
}
|
| 594 |
+
}
|
| 595 |
+
|
| 596 |
+
Ok(())
|
| 597 |
+
})
|
| 598 |
+
.await
|
| 599 |
+
}
|
| 600 |
+
|
| 601 |
+
fn does_websocket_requests(&mut self, config: &ServerConfig, socket_data: &SocketData) -> bool {
|
| 602 |
+
if socket_data.encrypted {
|
| 603 |
+
let secure_proxy_to = &config["secureProxyTo"];
|
| 604 |
+
if secure_proxy_to.as_vec().is_some() || secure_proxy_to.as_str().is_some() {
|
| 605 |
+
return true;
|
| 606 |
+
}
|
| 607 |
+
}
|
| 608 |
+
|
| 609 |
+
let proxy_to = &config["proxyTo"];
|
| 610 |
+
proxy_to.as_vec().is_some() || proxy_to.as_str().is_some()
|
| 611 |
+
}
|
| 612 |
+
}
|
| 613 |
+
|
| 614 |
+
async fn determine_proxy_to(
|
| 615 |
+
config: &ServerConfig,
|
| 616 |
+
encrypted: bool,
|
| 617 |
+
failed_backends: &RwLock<TtlCache<String, u64>>,
|
| 618 |
+
enable_health_check: bool,
|
| 619 |
+
health_check_max_fails: u64,
|
| 620 |
+
) -> Option<String> {
|
| 621 |
+
let mut proxy_to = None;
|
| 622 |
+
// When the array is supplied with non-string values, the reverse proxy may have undesirable behavior
|
| 623 |
+
// The "proxyTo" and "secureProxyTo" are validated though.
|
| 624 |
+
|
| 625 |
+
if encrypted {
|
| 626 |
+
let secure_proxy_to_yaml = &config["secureProxyTo"];
|
| 627 |
+
if let Some(secure_proxy_to_vector) = secure_proxy_to_yaml.as_vec() {
|
| 628 |
+
if enable_health_check {
|
| 629 |
+
let mut secure_proxy_to_vector = secure_proxy_to_vector.clone();
|
| 630 |
+
loop {
|
| 631 |
+
if !secure_proxy_to_vector.is_empty() {
|
| 632 |
+
let index = rand::random_range(..secure_proxy_to_vector.len());
|
| 633 |
+
if let Some(secure_proxy_to) = secure_proxy_to_vector[index].as_str() {
|
| 634 |
+
proxy_to = Some(secure_proxy_to.to_string());
|
| 635 |
+
let failed_backends_read = failed_backends.read().await;
|
| 636 |
+
let failed_backend_fails =
|
| 637 |
+
match failed_backends_read.get(&secure_proxy_to.to_string()) {
|
| 638 |
+
Some(fails) => fails,
|
| 639 |
+
None => break,
|
| 640 |
+
};
|
| 641 |
+
if failed_backend_fails > health_check_max_fails {
|
| 642 |
+
secure_proxy_to_vector.remove(index);
|
| 643 |
+
} else {
|
| 644 |
+
break;
|
| 645 |
+
}
|
| 646 |
+
}
|
| 647 |
+
} else {
|
| 648 |
+
break;
|
| 649 |
+
}
|
| 650 |
+
}
|
| 651 |
+
} else if !secure_proxy_to_vector.is_empty() {
|
| 652 |
+
if let Some(secure_proxy_to) =
|
| 653 |
+
secure_proxy_to_vector[rand::random_range(..secure_proxy_to_vector.len())].as_str()
|
| 654 |
+
{
|
| 655 |
+
proxy_to = Some(secure_proxy_to.to_string());
|
| 656 |
+
}
|
| 657 |
+
}
|
| 658 |
+
} else if let Some(secure_proxy_to) = secure_proxy_to_yaml.as_str() {
|
| 659 |
+
proxy_to = Some(secure_proxy_to.to_string());
|
| 660 |
+
}
|
| 661 |
+
}
|
| 662 |
+
|
| 663 |
+
if proxy_to.is_none() {
|
| 664 |
+
let proxy_to_yaml = &config["proxyTo"];
|
| 665 |
+
if let Some(proxy_to_vector) = proxy_to_yaml.as_vec() {
|
| 666 |
+
if enable_health_check {
|
| 667 |
+
let mut proxy_to_vector = proxy_to_vector.clone();
|
| 668 |
+
loop {
|
| 669 |
+
if !proxy_to_vector.is_empty() {
|
| 670 |
+
let index = rand::random_range(..proxy_to_vector.len());
|
| 671 |
+
if let Some(proxy_to_str) = proxy_to_vector[index].as_str() {
|
| 672 |
+
proxy_to = Some(proxy_to_str.to_string());
|
| 673 |
+
let failed_backends_read = failed_backends.read().await;
|
| 674 |
+
let failed_backend_fails = match failed_backends_read.get(&proxy_to_str.to_string()) {
|
| 675 |
+
Some(fails) => fails,
|
| 676 |
+
None => break,
|
| 677 |
+
};
|
| 678 |
+
if failed_backend_fails > health_check_max_fails {
|
| 679 |
+
proxy_to_vector.remove(index);
|
| 680 |
+
} else {
|
| 681 |
+
break;
|
| 682 |
+
}
|
| 683 |
+
}
|
| 684 |
+
} else {
|
| 685 |
+
break;
|
| 686 |
+
}
|
| 687 |
+
}
|
| 688 |
+
} else if !proxy_to_vector.is_empty() {
|
| 689 |
+
if let Some(proxy_to_str) =
|
| 690 |
+
proxy_to_vector[rand::random_range(..proxy_to_vector.len())].as_str()
|
| 691 |
+
{
|
| 692 |
+
proxy_to = Some(proxy_to_str.to_string());
|
| 693 |
+
}
|
| 694 |
+
}
|
| 695 |
+
} else if let Some(proxy_to_str) = proxy_to_yaml.as_str() {
|
| 696 |
+
proxy_to = Some(proxy_to_str.to_string());
|
| 697 |
+
}
|
| 698 |
+
}
|
| 699 |
+
|
| 700 |
+
proxy_to
|
| 701 |
+
}
|
| 702 |
+
|
| 703 |
+
async fn http_proxy(
|
| 704 |
+
connections: &RwLock<HashMap<String, SendRequest<BoxBody<Bytes, std::io::Error>>>>,
|
| 705 |
+
connect_addr: String,
|
| 706 |
+
stream: impl AsyncRead + AsyncWrite + Send + Unpin + 'static,
|
| 707 |
+
proxy_request: Request<BoxBody<Bytes, std::io::Error>>,
|
| 708 |
+
error_logger: &ErrorLogger,
|
| 709 |
+
proxy_to: String,
|
| 710 |
+
failed_backends: Option<&tokio::sync::RwLock<TtlCache<std::string::String, u64>>>,
|
| 711 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 712 |
+
let io = TokioIo::new(stream);
|
| 713 |
+
|
| 714 |
+
let (mut sender, conn) = match hyper::client::conn::http1::handshake(io).await {
|
| 715 |
+
Ok(data) => data,
|
| 716 |
+
Err(err) => {
|
| 717 |
+
if let Some(failed_backends) = failed_backends {
|
| 718 |
+
let mut failed_backends_write = failed_backends.write().await;
|
| 719 |
+
let failed_attempts = failed_backends_write.get(&proxy_to);
|
| 720 |
+
failed_backends_write.insert(proxy_to, failed_attempts.map_or(1, |x| x + 1));
|
| 721 |
+
}
|
| 722 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 723 |
+
return Ok(
|
| 724 |
+
ResponseData::builder_without_request()
|
| 725 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 726 |
+
.build(),
|
| 727 |
+
);
|
| 728 |
+
}
|
| 729 |
+
};
|
| 730 |
+
|
| 731 |
+
let send_request = sender.send_request(proxy_request);
|
| 732 |
+
|
| 733 |
+
let mut pinned_conn = Box::pin(conn);
|
| 734 |
+
tokio::pin!(send_request);
|
| 735 |
+
|
| 736 |
+
let response;
|
| 737 |
+
|
| 738 |
+
loop {
|
| 739 |
+
tokio::select! {
|
| 740 |
+
biased;
|
| 741 |
+
|
| 742 |
+
proxy_response = &mut send_request => {
|
| 743 |
+
let proxy_response = match proxy_response {
|
| 744 |
+
Ok(response) => response,
|
| 745 |
+
Err(err) => {
|
| 746 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 747 |
+
return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
|
| 748 |
+
}
|
| 749 |
+
};
|
| 750 |
+
|
| 751 |
+
response = ResponseData::builder_without_request()
|
| 752 |
+
.response(proxy_response.map(|b| {
|
| 753 |
+
b.map_err(|e| std::io::Error::other(e.to_string()))
|
| 754 |
+
.boxed()
|
| 755 |
+
}))
|
| 756 |
+
.parallel_fn(async move {
|
| 757 |
+
pinned_conn.await.unwrap_or_default();
|
| 758 |
+
})
|
| 759 |
+
.build();
|
| 760 |
+
|
| 761 |
+
break;
|
| 762 |
+
},
|
| 763 |
+
state = &mut pinned_conn => {
|
| 764 |
+
if state.is_err() {
|
| 765 |
+
error_logger.log("Bad gateway: incomplete response").await;
|
| 766 |
+
return Ok(ResponseData::builder_without_request().status(StatusCode::BAD_GATEWAY).build());
|
| 767 |
+
}
|
| 768 |
+
},
|
| 769 |
+
};
|
| 770 |
+
}
|
| 771 |
+
|
| 772 |
+
if !sender.is_closed() {
|
| 773 |
+
let mut rwlock_write = connections.write().await;
|
| 774 |
+
rwlock_write.insert(connect_addr, sender);
|
| 775 |
+
drop(rwlock_write);
|
| 776 |
+
}
|
| 777 |
+
|
| 778 |
+
Ok(response)
|
| 779 |
+
}
|
| 780 |
+
|
| 781 |
+
async fn http_proxy_kept_alive(
|
| 782 |
+
sender: &mut SendRequest<BoxBody<Bytes, std::io::Error>>,
|
| 783 |
+
proxy_request: Request<BoxBody<Bytes, std::io::Error>>,
|
| 784 |
+
error_logger: &ErrorLogger,
|
| 785 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 786 |
+
let proxy_response = match sender.send_request(proxy_request).await {
|
| 787 |
+
Ok(response) => response,
|
| 788 |
+
Err(err) => {
|
| 789 |
+
error_logger.log(&format!("Bad gateway: {}", err)).await;
|
| 790 |
+
return Ok(
|
| 791 |
+
ResponseData::builder_without_request()
|
| 792 |
+
.status(StatusCode::BAD_GATEWAY)
|
| 793 |
+
.build(),
|
| 794 |
+
);
|
| 795 |
+
}
|
| 796 |
+
};
|
| 797 |
+
|
| 798 |
+
let response = ResponseData::builder_without_request()
|
| 799 |
+
.response(proxy_response.map(|b| b.map_err(|e| std::io::Error::other(e.to_string())).boxed()))
|
| 800 |
+
.build();
|
| 801 |
+
|
| 802 |
+
Ok(response)
|
| 803 |
+
}
|
ferron/src/optional_modules/scgi.rs
ADDED
|
@@ -0,0 +1,673 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// SCGI handler code inspired by SVR.JS's OrangeCircle mod, translated from JavaScript to Rust.
|
| 2 |
+
// Based on the "cgi" module
|
| 3 |
+
use std::env;
|
| 4 |
+
use std::error::Error;
|
| 5 |
+
use std::path::{Path, PathBuf};
|
| 6 |
+
|
| 7 |
+
use crate::ferron_common::{
|
| 8 |
+
ErrorLogger, HyperRequest, HyperResponse, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 9 |
+
ServerModuleHandlers, SocketData,
|
| 10 |
+
};
|
| 11 |
+
use crate::ferron_common::{HyperUpgraded, WithRuntime};
|
| 12 |
+
use async_trait::async_trait;
|
| 13 |
+
use futures_util::TryStreamExt;
|
| 14 |
+
use hashlink::LinkedHashMap;
|
| 15 |
+
use http_body_util::{BodyExt, StreamBody};
|
| 16 |
+
use httparse::EMPTY_HEADER;
|
| 17 |
+
use hyper::body::Frame;
|
| 18 |
+
use hyper::{header, Response, StatusCode};
|
| 19 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 20 |
+
use tokio::fs;
|
| 21 |
+
use tokio::io::{AsyncRead, AsyncWrite, AsyncWriteExt};
|
| 22 |
+
use tokio::net::TcpStream;
|
| 23 |
+
use tokio::runtime::Handle;
|
| 24 |
+
use tokio_util::io::{ReaderStream, StreamReader};
|
| 25 |
+
|
| 26 |
+
use crate::ferron_res::server_software::SERVER_SOFTWARE;
|
| 27 |
+
use crate::ferron_util::cgi_response::CgiResponse;
|
| 28 |
+
use crate::ferron_util::copy_move::Copier;
|
| 29 |
+
|
| 30 |
+
pub fn server_module_init(
|
| 31 |
+
_config: &ServerConfig,
|
| 32 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 33 |
+
Ok(Box::new(ScgiModule::new()))
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
struct ScgiModule;
|
| 37 |
+
|
| 38 |
+
impl ScgiModule {
|
| 39 |
+
fn new() -> Self {
|
| 40 |
+
Self
|
| 41 |
+
}
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
impl ServerModule for ScgiModule {
|
| 45 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 46 |
+
Box::new(ScgiModuleHandlers { handle })
|
| 47 |
+
}
|
| 48 |
+
}
|
| 49 |
+
struct ScgiModuleHandlers {
|
| 50 |
+
handle: Handle,
|
| 51 |
+
}
|
| 52 |
+
|
| 53 |
+
#[async_trait]
|
| 54 |
+
impl ServerModuleHandlers for ScgiModuleHandlers {
|
| 55 |
+
async fn request_handler(
|
| 56 |
+
&mut self,
|
| 57 |
+
request: RequestData,
|
| 58 |
+
config: &ServerConfig,
|
| 59 |
+
socket_data: &SocketData,
|
| 60 |
+
error_logger: &ErrorLogger,
|
| 61 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 62 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 63 |
+
let mut scgi_to = "tcp://localhost:4000/";
|
| 64 |
+
let scgi_to_yaml = &config["scgiTo"];
|
| 65 |
+
if let Some(scgi_to_obtained) = scgi_to_yaml.as_str() {
|
| 66 |
+
scgi_to = scgi_to_obtained;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
let mut scgi_path = None;
|
| 70 |
+
if let Some(scgi_path_obtained) = config["scgiPath"].as_str() {
|
| 71 |
+
scgi_path = Some(scgi_path_obtained.to_string());
|
| 72 |
+
}
|
| 73 |
+
|
| 74 |
+
let hyper_request = request.get_hyper_request();
|
| 75 |
+
|
| 76 |
+
let request_path = hyper_request.uri().path();
|
| 77 |
+
let mut request_path_bytes = request_path.bytes();
|
| 78 |
+
if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
|
| 79 |
+
return Ok(
|
| 80 |
+
ResponseData::builder(request)
|
| 81 |
+
.status(StatusCode::BAD_REQUEST)
|
| 82 |
+
.build(),
|
| 83 |
+
);
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
if let Some(scgi_path) = scgi_path {
|
| 87 |
+
let mut canonical_scgi_path: &str = &scgi_path;
|
| 88 |
+
if canonical_scgi_path.bytes().last() == Some(b'/') {
|
| 89 |
+
canonical_scgi_path = &canonical_scgi_path[..(canonical_scgi_path.len() - 1)];
|
| 90 |
+
}
|
| 91 |
+
|
| 92 |
+
let request_path_with_slashes = match request_path == canonical_scgi_path {
|
| 93 |
+
true => format!("{}/", request_path),
|
| 94 |
+
false => request_path.to_string(),
|
| 95 |
+
};
|
| 96 |
+
if let Some(stripped_request_path) =
|
| 97 |
+
request_path_with_slashes.strip_prefix(canonical_scgi_path)
|
| 98 |
+
{
|
| 99 |
+
let wwwroot_yaml = &config["wwwroot"];
|
| 100 |
+
let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
|
| 101 |
+
|
| 102 |
+
let wwwroot_unknown = PathBuf::from(wwwroot);
|
| 103 |
+
let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
|
| 104 |
+
true => wwwroot_unknown,
|
| 105 |
+
false => match fs::canonicalize(&wwwroot_unknown).await {
|
| 106 |
+
Ok(pathbuf) => pathbuf,
|
| 107 |
+
Err(_) => wwwroot_unknown,
|
| 108 |
+
},
|
| 109 |
+
};
|
| 110 |
+
let wwwroot = wwwroot_pathbuf.as_path();
|
| 111 |
+
|
| 112 |
+
let mut relative_path = &request_path[1..];
|
| 113 |
+
while relative_path.as_bytes().first().copied() == Some(b'/') {
|
| 114 |
+
relative_path = &relative_path[1..];
|
| 115 |
+
}
|
| 116 |
+
|
| 117 |
+
let decoded_relative_path = match urlencoding::decode(relative_path) {
|
| 118 |
+
Ok(path) => path.to_string(),
|
| 119 |
+
Err(_) => {
|
| 120 |
+
return Ok(
|
| 121 |
+
ResponseData::builder(request)
|
| 122 |
+
.status(StatusCode::BAD_REQUEST)
|
| 123 |
+
.build(),
|
| 124 |
+
);
|
| 125 |
+
}
|
| 126 |
+
};
|
| 127 |
+
|
| 128 |
+
let joined_pathbuf = wwwroot.join(decoded_relative_path);
|
| 129 |
+
let execute_pathbuf = joined_pathbuf;
|
| 130 |
+
let execute_path_info = stripped_request_path
|
| 131 |
+
.strip_prefix("/")
|
| 132 |
+
.map(|s| s.to_string());
|
| 133 |
+
|
| 134 |
+
return execute_scgi_with_environment_variables(
|
| 135 |
+
request,
|
| 136 |
+
socket_data,
|
| 137 |
+
error_logger,
|
| 138 |
+
wwwroot,
|
| 139 |
+
execute_pathbuf,
|
| 140 |
+
execute_path_info,
|
| 141 |
+
config["serverAdministratorEmail"].as_str(),
|
| 142 |
+
scgi_to,
|
| 143 |
+
)
|
| 144 |
+
.await;
|
| 145 |
+
}
|
| 146 |
+
}
|
| 147 |
+
Ok(ResponseData::builder(request).build())
|
| 148 |
+
})
|
| 149 |
+
.await
|
| 150 |
+
}
|
| 151 |
+
|
| 152 |
+
async fn proxy_request_handler(
|
| 153 |
+
&mut self,
|
| 154 |
+
request: RequestData,
|
| 155 |
+
_config: &ServerConfig,
|
| 156 |
+
_socket_data: &SocketData,
|
| 157 |
+
_error_logger: &ErrorLogger,
|
| 158 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 159 |
+
Ok(ResponseData::builder(request).build())
|
| 160 |
+
}
|
| 161 |
+
|
| 162 |
+
async fn response_modifying_handler(
|
| 163 |
+
&mut self,
|
| 164 |
+
response: HyperResponse,
|
| 165 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 166 |
+
Ok(response)
|
| 167 |
+
}
|
| 168 |
+
|
| 169 |
+
async fn proxy_response_modifying_handler(
|
| 170 |
+
&mut self,
|
| 171 |
+
response: HyperResponse,
|
| 172 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 173 |
+
Ok(response)
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
async fn connect_proxy_request_handler(
|
| 177 |
+
&mut self,
|
| 178 |
+
_upgraded_request: HyperUpgraded,
|
| 179 |
+
_connect_address: &str,
|
| 180 |
+
_config: &ServerConfig,
|
| 181 |
+
_socket_data: &SocketData,
|
| 182 |
+
_error_logger: &ErrorLogger,
|
| 183 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 184 |
+
Ok(())
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 188 |
+
false
|
| 189 |
+
}
|
| 190 |
+
|
| 191 |
+
async fn websocket_request_handler(
|
| 192 |
+
&mut self,
|
| 193 |
+
_websocket: HyperWebsocket,
|
| 194 |
+
_uri: &hyper::Uri,
|
| 195 |
+
_config: &ServerConfig,
|
| 196 |
+
_socket_data: &SocketData,
|
| 197 |
+
_error_logger: &ErrorLogger,
|
| 198 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 199 |
+
Ok(())
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 203 |
+
false
|
| 204 |
+
}
|
| 205 |
+
}
|
| 206 |
+
|
| 207 |
+
#[allow(clippy::too_many_arguments)]
|
| 208 |
+
async fn execute_scgi_with_environment_variables(
|
| 209 |
+
request: RequestData,
|
| 210 |
+
socket_data: &SocketData,
|
| 211 |
+
error_logger: &ErrorLogger,
|
| 212 |
+
wwwroot: &Path,
|
| 213 |
+
execute_pathbuf: PathBuf,
|
| 214 |
+
path_info: Option<String>,
|
| 215 |
+
server_administrator_email: Option<&str>,
|
| 216 |
+
scgi_to: &str,
|
| 217 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 218 |
+
let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
|
| 219 |
+
|
| 220 |
+
let hyper_request = request.get_hyper_request();
|
| 221 |
+
let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
|
| 222 |
+
|
| 223 |
+
if let Some(auth_user) = request.get_auth_user() {
|
| 224 |
+
if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
|
| 225 |
+
let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
|
| 226 |
+
let mut authorization_value_split = authorization_value.split(" ");
|
| 227 |
+
if let Some(authorization_type) = authorization_value_split.next() {
|
| 228 |
+
environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
|
| 229 |
+
}
|
| 230 |
+
}
|
| 231 |
+
environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
environment_variables.insert(
|
| 235 |
+
"QUERY_STRING".to_string(),
|
| 236 |
+
match hyper_request.uri().query() {
|
| 237 |
+
Some(query) => query.to_string(),
|
| 238 |
+
None => "".to_string(),
|
| 239 |
+
},
|
| 240 |
+
);
|
| 241 |
+
|
| 242 |
+
environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
|
| 243 |
+
environment_variables.insert(
|
| 244 |
+
"SERVER_PROTOCOL".to_string(),
|
| 245 |
+
match hyper_request.version() {
|
| 246 |
+
hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
|
| 247 |
+
hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
|
| 248 |
+
hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
|
| 249 |
+
hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
|
| 250 |
+
hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
|
| 251 |
+
_ => "HTTP/Unknown".to_string(),
|
| 252 |
+
},
|
| 253 |
+
);
|
| 254 |
+
environment_variables.insert(
|
| 255 |
+
"SERVER_PORT".to_string(),
|
| 256 |
+
socket_data.local_addr.port().to_string(),
|
| 257 |
+
);
|
| 258 |
+
environment_variables.insert(
|
| 259 |
+
"SERVER_ADDR".to_string(),
|
| 260 |
+
socket_data.local_addr.ip().to_canonical().to_string(),
|
| 261 |
+
);
|
| 262 |
+
if let Some(server_administrator_email) = server_administrator_email {
|
| 263 |
+
environment_variables.insert(
|
| 264 |
+
"SERVER_ADMIN".to_string(),
|
| 265 |
+
server_administrator_email.to_string(),
|
| 266 |
+
);
|
| 267 |
+
}
|
| 268 |
+
if let Some(host) = hyper_request.headers().get(header::HOST) {
|
| 269 |
+
environment_variables.insert(
|
| 270 |
+
"SERVER_NAME".to_string(),
|
| 271 |
+
String::from_utf8_lossy(host.as_bytes()).to_string(),
|
| 272 |
+
);
|
| 273 |
+
}
|
| 274 |
+
|
| 275 |
+
environment_variables.insert(
|
| 276 |
+
"DOCUMENT_ROOT".to_string(),
|
| 277 |
+
wwwroot.to_string_lossy().to_string(),
|
| 278 |
+
);
|
| 279 |
+
environment_variables.insert(
|
| 280 |
+
"PATH_INFO".to_string(),
|
| 281 |
+
match &path_info {
|
| 282 |
+
Some(path_info) => format!("/{}", path_info),
|
| 283 |
+
None => "".to_string(),
|
| 284 |
+
},
|
| 285 |
+
);
|
| 286 |
+
environment_variables.insert(
|
| 287 |
+
"PATH_TRANSLATED".to_string(),
|
| 288 |
+
match &path_info {
|
| 289 |
+
Some(path_info) => {
|
| 290 |
+
let mut path_translated = execute_pathbuf.clone();
|
| 291 |
+
path_translated.push(path_info);
|
| 292 |
+
path_translated.to_string_lossy().to_string()
|
| 293 |
+
}
|
| 294 |
+
None => "".to_string(),
|
| 295 |
+
},
|
| 296 |
+
);
|
| 297 |
+
environment_variables.insert(
|
| 298 |
+
"REQUEST_METHOD".to_string(),
|
| 299 |
+
hyper_request.method().to_string(),
|
| 300 |
+
);
|
| 301 |
+
environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
|
| 302 |
+
environment_variables.insert("SCGI".to_string(), "1".to_string());
|
| 303 |
+
environment_variables.insert(
|
| 304 |
+
"REQUEST_URI".to_string(),
|
| 305 |
+
format!(
|
| 306 |
+
"{}{}",
|
| 307 |
+
original_request_uri.path(),
|
| 308 |
+
match original_request_uri.query() {
|
| 309 |
+
Some(query) => format!("?{}", query),
|
| 310 |
+
None => String::from(""),
|
| 311 |
+
}
|
| 312 |
+
),
|
| 313 |
+
);
|
| 314 |
+
|
| 315 |
+
environment_variables.insert(
|
| 316 |
+
"REMOTE_PORT".to_string(),
|
| 317 |
+
socket_data.remote_addr.port().to_string(),
|
| 318 |
+
);
|
| 319 |
+
environment_variables.insert(
|
| 320 |
+
"REMOTE_ADDR".to_string(),
|
| 321 |
+
socket_data.remote_addr.ip().to_canonical().to_string(),
|
| 322 |
+
);
|
| 323 |
+
|
| 324 |
+
environment_variables.insert(
|
| 325 |
+
"SCRIPT_FILENAME".to_string(),
|
| 326 |
+
execute_pathbuf.to_string_lossy().to_string(),
|
| 327 |
+
);
|
| 328 |
+
if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
|
| 329 |
+
environment_variables.insert(
|
| 330 |
+
"SCRIPT_NAME".to_string(),
|
| 331 |
+
format!(
|
| 332 |
+
"/{}",
|
| 333 |
+
match cfg!(windows) {
|
| 334 |
+
true => script_path.to_string_lossy().to_string().replace("\\", "/"),
|
| 335 |
+
false => script_path.to_string_lossy().to_string(),
|
| 336 |
+
}
|
| 337 |
+
),
|
| 338 |
+
);
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
if socket_data.encrypted {
|
| 342 |
+
environment_variables.insert("HTTPS".to_string(), "ON".to_string());
|
| 343 |
+
}
|
| 344 |
+
|
| 345 |
+
let mut content_length_set = false;
|
| 346 |
+
for (header_name, header_value) in hyper_request.headers().iter() {
|
| 347 |
+
let env_header_name = match *header_name {
|
| 348 |
+
header::CONTENT_LENGTH => {
|
| 349 |
+
content_length_set = true;
|
| 350 |
+
"CONTENT_LENGTH".to_string()
|
| 351 |
+
}
|
| 352 |
+
header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
|
| 353 |
+
_ => {
|
| 354 |
+
let mut result = String::new();
|
| 355 |
+
|
| 356 |
+
result.push_str("HTTP_");
|
| 357 |
+
|
| 358 |
+
for c in header_name.as_str().to_uppercase().chars() {
|
| 359 |
+
if c.is_alphanumeric() {
|
| 360 |
+
result.push(c);
|
| 361 |
+
} else {
|
| 362 |
+
result.push('_');
|
| 363 |
+
}
|
| 364 |
+
}
|
| 365 |
+
|
| 366 |
+
result
|
| 367 |
+
}
|
| 368 |
+
};
|
| 369 |
+
if environment_variables.contains_key(&env_header_name) {
|
| 370 |
+
let value = environment_variables.get_mut(&env_header_name);
|
| 371 |
+
if let Some(value) = value {
|
| 372 |
+
if env_header_name == "HTTP_COOKIE" {
|
| 373 |
+
value.push_str("; ");
|
| 374 |
+
} else {
|
| 375 |
+
// See https://stackoverflow.com/a/1801191
|
| 376 |
+
value.push_str(", ");
|
| 377 |
+
}
|
| 378 |
+
value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
|
| 379 |
+
} else {
|
| 380 |
+
environment_variables.insert(
|
| 381 |
+
env_header_name,
|
| 382 |
+
String::from_utf8_lossy(header_value.as_bytes()).to_string(),
|
| 383 |
+
);
|
| 384 |
+
}
|
| 385 |
+
} else {
|
| 386 |
+
environment_variables.insert(
|
| 387 |
+
env_header_name,
|
| 388 |
+
String::from_utf8_lossy(header_value.as_bytes()).to_string(),
|
| 389 |
+
);
|
| 390 |
+
}
|
| 391 |
+
}
|
| 392 |
+
|
| 393 |
+
if !content_length_set {
|
| 394 |
+
environment_variables.insert("CONTENT_LENGTH".to_string(), "0".to_string());
|
| 395 |
+
}
|
| 396 |
+
|
| 397 |
+
let (hyper_request, _, _) = request.into_parts();
|
| 398 |
+
|
| 399 |
+
execute_scgi(hyper_request, error_logger, scgi_to, environment_variables).await
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
async fn execute_scgi(
|
| 403 |
+
hyper_request: HyperRequest,
|
| 404 |
+
error_logger: &ErrorLogger,
|
| 405 |
+
scgi_to: &str,
|
| 406 |
+
mut environment_variables: LinkedHashMap<String, String>,
|
| 407 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 408 |
+
let (_, body) = hyper_request.into_parts();
|
| 409 |
+
|
| 410 |
+
// Insert other environment variables
|
| 411 |
+
for (key, value) in env::vars_os() {
|
| 412 |
+
let key_string = key.to_string_lossy().to_string();
|
| 413 |
+
let value_string = value.to_string_lossy().to_string();
|
| 414 |
+
environment_variables
|
| 415 |
+
.entry(key_string)
|
| 416 |
+
.or_insert(value_string);
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
let scgi_to_fixed = if let Some(stripped) = scgi_to.strip_prefix("unix:///") {
|
| 420 |
+
// hyper::Uri fails to parse a string if there is an empty authority, so add an "ignore" authority to Unix socket URLs
|
| 421 |
+
&format!("unix://ignore/{}", stripped)
|
| 422 |
+
} else {
|
| 423 |
+
scgi_to
|
| 424 |
+
};
|
| 425 |
+
|
| 426 |
+
let scgi_to_url = scgi_to_fixed.parse::<hyper::Uri>()?;
|
| 427 |
+
let scheme_str = scgi_to_url.scheme_str();
|
| 428 |
+
|
| 429 |
+
let (socket_reader, mut socket_writer) = match scheme_str {
|
| 430 |
+
Some("tcp") => {
|
| 431 |
+
let host = match scgi_to_url.host() {
|
| 432 |
+
Some(host) => host,
|
| 433 |
+
None => Err(anyhow::anyhow!("The SCGI URL doesn't include the host"))?,
|
| 434 |
+
};
|
| 435 |
+
|
| 436 |
+
let port = match scgi_to_url.port_u16() {
|
| 437 |
+
Some(port) => port,
|
| 438 |
+
None => Err(anyhow::anyhow!("The SCGI URL doesn't include the port"))?,
|
| 439 |
+
};
|
| 440 |
+
|
| 441 |
+
let addr = format!("{}:{}", host, port);
|
| 442 |
+
|
| 443 |
+
match connect_tcp(&addr).await {
|
| 444 |
+
Ok(data) => data,
|
| 445 |
+
Err(err) => match err.kind() {
|
| 446 |
+
tokio::io::ErrorKind::ConnectionRefused
|
| 447 |
+
| tokio::io::ErrorKind::NotFound
|
| 448 |
+
| tokio::io::ErrorKind::HostUnreachable => {
|
| 449 |
+
error_logger
|
| 450 |
+
.log(&format!("Service unavailable: {}", err))
|
| 451 |
+
.await;
|
| 452 |
+
return Ok(
|
| 453 |
+
ResponseData::builder_without_request()
|
| 454 |
+
.status(StatusCode::SERVICE_UNAVAILABLE)
|
| 455 |
+
.build(),
|
| 456 |
+
);
|
| 457 |
+
}
|
| 458 |
+
_ => Err(err)?,
|
| 459 |
+
},
|
| 460 |
+
}
|
| 461 |
+
}
|
| 462 |
+
Some("unix") => {
|
| 463 |
+
let path = scgi_to_url.path();
|
| 464 |
+
match connect_unix(path).await {
|
| 465 |
+
Ok(data) => data,
|
| 466 |
+
Err(err) => match err.kind() {
|
| 467 |
+
tokio::io::ErrorKind::ConnectionRefused
|
| 468 |
+
| tokio::io::ErrorKind::NotFound
|
| 469 |
+
| tokio::io::ErrorKind::HostUnreachable => {
|
| 470 |
+
error_logger
|
| 471 |
+
.log(&format!("Service unavailable: {}", err))
|
| 472 |
+
.await;
|
| 473 |
+
return Ok(
|
| 474 |
+
ResponseData::builder_without_request()
|
| 475 |
+
.status(StatusCode::SERVICE_UNAVAILABLE)
|
| 476 |
+
.build(),
|
| 477 |
+
);
|
| 478 |
+
}
|
| 479 |
+
_ => Err(err)?,
|
| 480 |
+
},
|
| 481 |
+
}
|
| 482 |
+
}
|
| 483 |
+
_ => Err(anyhow::anyhow!(
|
| 484 |
+
"Only HTTP and HTTPS reverse proxy URLs are supported."
|
| 485 |
+
))?,
|
| 486 |
+
};
|
| 487 |
+
|
| 488 |
+
// Create environment variable netstring
|
| 489 |
+
let mut environment_variables_to_wrap = Vec::new();
|
| 490 |
+
for (key, value) in environment_variables.iter() {
|
| 491 |
+
let mut environment_variable = Vec::new();
|
| 492 |
+
environment_variable.extend_from_slice(key.as_bytes());
|
| 493 |
+
environment_variable.push(b'\0');
|
| 494 |
+
environment_variable.extend_from_slice(value.as_bytes());
|
| 495 |
+
environment_variable.push(b'\0');
|
| 496 |
+
if key == "CONTENT_LENGTH" {
|
| 497 |
+
environment_variable.append(&mut environment_variables_to_wrap);
|
| 498 |
+
environment_variables_to_wrap = environment_variable;
|
| 499 |
+
} else {
|
| 500 |
+
environment_variables_to_wrap.append(&mut environment_variable);
|
| 501 |
+
}
|
| 502 |
+
}
|
| 503 |
+
|
| 504 |
+
let environment_variables_to_wrap_length = environment_variables_to_wrap.len();
|
| 505 |
+
let mut environment_variables_netstring = Vec::new();
|
| 506 |
+
environment_variables_netstring
|
| 507 |
+
.extend_from_slice(environment_variables_to_wrap_length.to_string().as_bytes());
|
| 508 |
+
environment_variables_netstring.push(b':');
|
| 509 |
+
environment_variables_netstring.append(&mut environment_variables_to_wrap);
|
| 510 |
+
environment_variables_netstring.push(b',');
|
| 511 |
+
|
| 512 |
+
// Write environment variable netstring
|
| 513 |
+
socket_writer
|
| 514 |
+
.write_all(&environment_variables_netstring)
|
| 515 |
+
.await?;
|
| 516 |
+
|
| 517 |
+
let cgi_stdin_reader = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
|
| 518 |
+
|
| 519 |
+
// Emulated standard input and standard output
|
| 520 |
+
// SCGI doesn't support standard error
|
| 521 |
+
let stdin = socket_writer;
|
| 522 |
+
let stdout = socket_reader;
|
| 523 |
+
|
| 524 |
+
let mut cgi_response = CgiResponse::new(stdout);
|
| 525 |
+
|
| 526 |
+
let stdin_copy_future = Copier::new(cgi_stdin_reader, stdin).copy();
|
| 527 |
+
let mut stdin_copy_future_pinned = Box::pin(stdin_copy_future);
|
| 528 |
+
|
| 529 |
+
let mut headers = [EMPTY_HEADER; 128];
|
| 530 |
+
|
| 531 |
+
let mut early_stdin_copied = false;
|
| 532 |
+
|
| 533 |
+
// Needed to wrap this in another scope to prevent errors with multiple mutable borrows.
|
| 534 |
+
{
|
| 535 |
+
let mut head_obtained = false;
|
| 536 |
+
let stdout_parse_future = cgi_response.get_head();
|
| 537 |
+
tokio::pin!(stdout_parse_future);
|
| 538 |
+
|
| 539 |
+
// Cannot use a loop with tokio::select, since stdin_copy_future_pinned being constantly ready will make the web server stop responding to HTTP requests
|
| 540 |
+
tokio::select! {
|
| 541 |
+
biased;
|
| 542 |
+
|
| 543 |
+
obtained_head = &mut stdout_parse_future => {
|
| 544 |
+
let obtained_head = obtained_head?;
|
| 545 |
+
if !obtained_head.is_empty() {
|
| 546 |
+
httparse::parse_headers(obtained_head, &mut headers)?;
|
| 547 |
+
}
|
| 548 |
+
head_obtained = true;
|
| 549 |
+
},
|
| 550 |
+
result = &mut stdin_copy_future_pinned => {
|
| 551 |
+
early_stdin_copied = true;
|
| 552 |
+
result?;
|
| 553 |
+
}
|
| 554 |
+
}
|
| 555 |
+
|
| 556 |
+
if !head_obtained {
|
| 557 |
+
// Kept it same as in the tokio::select macro
|
| 558 |
+
let obtained_head = stdout_parse_future.await?;
|
| 559 |
+
if !obtained_head.is_empty() {
|
| 560 |
+
httparse::parse_headers(obtained_head, &mut headers)?;
|
| 561 |
+
}
|
| 562 |
+
}
|
| 563 |
+
}
|
| 564 |
+
|
| 565 |
+
let mut response_builder = Response::builder();
|
| 566 |
+
let mut status_code = 200;
|
| 567 |
+
for header in headers {
|
| 568 |
+
if header == EMPTY_HEADER {
|
| 569 |
+
break;
|
| 570 |
+
}
|
| 571 |
+
let mut is_status_header = false;
|
| 572 |
+
match &header.name.to_lowercase() as &str {
|
| 573 |
+
"location" => {
|
| 574 |
+
if !(300..=399).contains(&status_code) {
|
| 575 |
+
status_code = 302;
|
| 576 |
+
}
|
| 577 |
+
}
|
| 578 |
+
"status" => {
|
| 579 |
+
is_status_header = true;
|
| 580 |
+
let header_value_cow = String::from_utf8_lossy(header.value);
|
| 581 |
+
let mut split_status = header_value_cow.split(" ");
|
| 582 |
+
let first_part = split_status.next();
|
| 583 |
+
if let Some(first_part) = first_part {
|
| 584 |
+
if first_part.starts_with("HTTP/") {
|
| 585 |
+
let second_part = split_status.next();
|
| 586 |
+
if let Some(second_part) = second_part {
|
| 587 |
+
if let Ok(parsed_status_code) = second_part.parse::<u16>() {
|
| 588 |
+
status_code = parsed_status_code;
|
| 589 |
+
}
|
| 590 |
+
}
|
| 591 |
+
} else if let Ok(parsed_status_code) = first_part.parse::<u16>() {
|
| 592 |
+
status_code = parsed_status_code;
|
| 593 |
+
}
|
| 594 |
+
}
|
| 595 |
+
}
|
| 596 |
+
_ => (),
|
| 597 |
+
}
|
| 598 |
+
if !is_status_header {
|
| 599 |
+
response_builder = response_builder.header(header.name, header.value);
|
| 600 |
+
}
|
| 601 |
+
}
|
| 602 |
+
|
| 603 |
+
response_builder = response_builder.status(status_code);
|
| 604 |
+
|
| 605 |
+
let reader_stream = ReaderStream::new(cgi_response);
|
| 606 |
+
let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
|
| 607 |
+
let boxed_body = stream_body.boxed();
|
| 608 |
+
|
| 609 |
+
let response = response_builder.body(boxed_body)?;
|
| 610 |
+
|
| 611 |
+
Ok(
|
| 612 |
+
ResponseData::builder_without_request()
|
| 613 |
+
.response(response)
|
| 614 |
+
.parallel_fn(async move {
|
| 615 |
+
if !early_stdin_copied {
|
| 616 |
+
stdin_copy_future_pinned.await.unwrap_or_default();
|
| 617 |
+
}
|
| 618 |
+
})
|
| 619 |
+
.build(),
|
| 620 |
+
)
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
async fn connect_tcp(
|
| 624 |
+
addr: &str,
|
| 625 |
+
) -> Result<
|
| 626 |
+
(
|
| 627 |
+
Box<dyn AsyncRead + Send + Sync + Unpin>,
|
| 628 |
+
Box<dyn AsyncWrite + Send + Sync + Unpin>,
|
| 629 |
+
),
|
| 630 |
+
tokio::io::Error,
|
| 631 |
+
> {
|
| 632 |
+
let socket = TcpStream::connect(addr).await?;
|
| 633 |
+
socket.set_nodelay(true)?;
|
| 634 |
+
|
| 635 |
+
let (socket_reader_set, socket_writer_set) = tokio::io::split(socket);
|
| 636 |
+
Ok((Box::new(socket_reader_set), Box::new(socket_writer_set)))
|
| 637 |
+
}
|
| 638 |
+
|
| 639 |
+
#[allow(dead_code)]
|
| 640 |
+
#[cfg(unix)]
|
| 641 |
+
async fn connect_unix(
|
| 642 |
+
path: &str,
|
| 643 |
+
) -> Result<
|
| 644 |
+
(
|
| 645 |
+
Box<dyn AsyncRead + Send + Sync + Unpin>,
|
| 646 |
+
Box<dyn AsyncWrite + Send + Sync + Unpin>,
|
| 647 |
+
),
|
| 648 |
+
tokio::io::Error,
|
| 649 |
+
> {
|
| 650 |
+
use tokio::net::UnixStream;
|
| 651 |
+
|
| 652 |
+
let socket = UnixStream::connect(path).await?;
|
| 653 |
+
|
| 654 |
+
let (socket_reader_set, socket_writer_set) = tokio::io::split(socket);
|
| 655 |
+
Ok((Box::new(socket_reader_set), Box::new(socket_writer_set)))
|
| 656 |
+
}
|
| 657 |
+
|
| 658 |
+
#[allow(dead_code)]
|
| 659 |
+
#[cfg(not(unix))]
|
| 660 |
+
async fn connect_unix(
|
| 661 |
+
_path: &str,
|
| 662 |
+
) -> Result<
|
| 663 |
+
(
|
| 664 |
+
Box<dyn AsyncRead + Send + Sync + Unpin>,
|
| 665 |
+
Box<dyn AsyncWrite + Send + Sync + Unpin>,
|
| 666 |
+
),
|
| 667 |
+
tokio::io::Error,
|
| 668 |
+
> {
|
| 669 |
+
Err(tokio::io::Error::new(
|
| 670 |
+
tokio::io::ErrorKind::Unsupported,
|
| 671 |
+
"Unix sockets are not supports on non-Unix platforms.",
|
| 672 |
+
))
|
| 673 |
+
}
|
ferron/src/optional_modules/wsgi.rs
ADDED
|
@@ -0,0 +1,742 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// This module would provide higher WSGI application performance,
|
| 2 |
+
// if it used a process pool dedicated for WSGI applications (aka pre-fork model)
|
| 3 |
+
// instead of spawning blocking threads in a Tokio runtime,
|
| 4 |
+
// because of Python's GIL, which causes the WSGI application in current setup
|
| 5 |
+
// to effectively run as single-threaded single-process.
|
| 6 |
+
// Pre-forking a process pool isn't supported on Windows.
|
| 7 |
+
|
| 8 |
+
use std::collections::HashMap;
|
| 9 |
+
use std::error::Error;
|
| 10 |
+
use std::path::{Path, PathBuf};
|
| 11 |
+
use std::str::FromStr;
|
| 12 |
+
use std::sync::Arc;
|
| 13 |
+
|
| 14 |
+
use crate::ferron_common::{
|
| 15 |
+
ErrorLogger, HyperRequest, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 16 |
+
ServerModuleHandlers, SocketData,
|
| 17 |
+
};
|
| 18 |
+
use crate::ferron_common::{HyperResponse, WithRuntime};
|
| 19 |
+
use crate::ferron_res::server_software::SERVER_SOFTWARE;
|
| 20 |
+
use crate::ferron_util::ip_match::ip_match;
|
| 21 |
+
use crate::ferron_util::match_hostname::match_hostname;
|
| 22 |
+
use crate::ferron_util::match_location::match_location;
|
| 23 |
+
use crate::ferron_util::wsgi_error_stream::WsgiErrorStream;
|
| 24 |
+
use crate::ferron_util::wsgi_input_stream::WsgiInputStream;
|
| 25 |
+
use crate::ferron_util::wsgi_load_application::load_wsgi_application;
|
| 26 |
+
use crate::ferron_util::wsgi_structs::{WsgiApplicationLocationWrap, WsgiApplicationWrap};
|
| 27 |
+
use async_trait::async_trait;
|
| 28 |
+
use futures_util::{StreamExt, TryStreamExt};
|
| 29 |
+
use hashlink::LinkedHashMap;
|
| 30 |
+
use http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
|
| 31 |
+
use http_body_util::{BodyExt, Empty, StreamBody};
|
| 32 |
+
use hyper::body::{Bytes, Frame};
|
| 33 |
+
use hyper::header;
|
| 34 |
+
use hyper::Response;
|
| 35 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 36 |
+
use pyo3::exceptions::{PyAssertionError, PyException};
|
| 37 |
+
use pyo3::prelude::*;
|
| 38 |
+
use pyo3::types::{PyAny, PyBool, PyCFunction, PyDict, PyIterator, PyString, PyTuple};
|
| 39 |
+
use tokio::fs;
|
| 40 |
+
use tokio::io::AsyncReadExt;
|
| 41 |
+
use tokio::runtime::Handle;
|
| 42 |
+
use tokio::sync::Mutex;
|
| 43 |
+
use tokio_util::io::StreamReader;
|
| 44 |
+
|
| 45 |
+
pub fn server_module_init(
|
| 46 |
+
config: &ServerConfig,
|
| 47 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 48 |
+
let mut global_wsgi_application = None;
|
| 49 |
+
let mut host_wsgi_applications = Vec::new();
|
| 50 |
+
let clear_sys_path = config["global"]["wsgiClearModuleImportPath"]
|
| 51 |
+
.as_bool()
|
| 52 |
+
.unwrap_or(false);
|
| 53 |
+
if let Some(wsgi_application_path) = config["global"]["wsgiApplicationPath"].as_str() {
|
| 54 |
+
global_wsgi_application = Some(Arc::new(load_wsgi_application(
|
| 55 |
+
PathBuf::from_str(wsgi_application_path)?.as_path(),
|
| 56 |
+
clear_sys_path,
|
| 57 |
+
)?));
|
| 58 |
+
}
|
| 59 |
+
let global_wsgi_path = config["global"]["wsgiPath"].as_str().map(|s| s.to_string());
|
| 60 |
+
|
| 61 |
+
if let Some(hosts) = config["hosts"].as_vec() {
|
| 62 |
+
for host_yaml in hosts.iter() {
|
| 63 |
+
let domain = host_yaml["domain"].as_str().map(String::from);
|
| 64 |
+
let ip = host_yaml["ip"].as_str().map(String::from);
|
| 65 |
+
let mut locations = Vec::new();
|
| 66 |
+
if let Some(locations_yaml) = host_yaml["locations"].as_vec() {
|
| 67 |
+
for location_yaml in locations_yaml.iter() {
|
| 68 |
+
if let Some(path_str) = location_yaml["path"].as_str() {
|
| 69 |
+
let path = String::from(path_str);
|
| 70 |
+
if let Some(wsgi_application_path) = location_yaml["wsgiApplicationPath"].as_str() {
|
| 71 |
+
locations.push(WsgiApplicationLocationWrap::new(
|
| 72 |
+
path,
|
| 73 |
+
Arc::new(load_wsgi_application(
|
| 74 |
+
PathBuf::from_str(wsgi_application_path)?.as_path(),
|
| 75 |
+
clear_sys_path,
|
| 76 |
+
)?),
|
| 77 |
+
location_yaml["wsgiPath"].as_str().map(|s| s.to_string()),
|
| 78 |
+
));
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
if let Some(wsgi_application_path) = host_yaml["wsgiApplicationPath"].as_str() {
|
| 84 |
+
host_wsgi_applications.push(WsgiApplicationWrap::new(
|
| 85 |
+
domain,
|
| 86 |
+
ip,
|
| 87 |
+
Some(Arc::new(load_wsgi_application(
|
| 88 |
+
PathBuf::from_str(wsgi_application_path)?.as_path(),
|
| 89 |
+
clear_sys_path,
|
| 90 |
+
)?)),
|
| 91 |
+
host_yaml["wsgiPath"].as_str().map(|s| s.to_string()),
|
| 92 |
+
locations,
|
| 93 |
+
));
|
| 94 |
+
} else if !locations.is_empty() {
|
| 95 |
+
host_wsgi_applications.push(WsgiApplicationWrap::new(
|
| 96 |
+
domain,
|
| 97 |
+
ip,
|
| 98 |
+
None,
|
| 99 |
+
host_yaml["wsgiPath"].as_str().map(|s| s.to_string()),
|
| 100 |
+
locations,
|
| 101 |
+
));
|
| 102 |
+
}
|
| 103 |
+
}
|
| 104 |
+
}
|
| 105 |
+
|
| 106 |
+
Ok(Box::new(WsgiModule::new(
|
| 107 |
+
global_wsgi_application,
|
| 108 |
+
global_wsgi_path,
|
| 109 |
+
Arc::new(host_wsgi_applications),
|
| 110 |
+
)))
|
| 111 |
+
}
|
| 112 |
+
|
| 113 |
+
struct WsgiModule {
|
| 114 |
+
global_wsgi_application: Option<Arc<Py<PyAny>>>,
|
| 115 |
+
global_wsgi_path: Option<String>,
|
| 116 |
+
host_wsgi_applications: Arc<Vec<WsgiApplicationWrap>>,
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
impl WsgiModule {
|
| 120 |
+
fn new(
|
| 121 |
+
global_wsgi_application: Option<Arc<Py<PyAny>>>,
|
| 122 |
+
global_wsgi_path: Option<String>,
|
| 123 |
+
host_wsgi_applications: Arc<Vec<WsgiApplicationWrap>>,
|
| 124 |
+
) -> Self {
|
| 125 |
+
Self {
|
| 126 |
+
global_wsgi_application,
|
| 127 |
+
global_wsgi_path,
|
| 128 |
+
host_wsgi_applications,
|
| 129 |
+
}
|
| 130 |
+
}
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
impl ServerModule for WsgiModule {
|
| 134 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 135 |
+
Box::new(WsgiModuleHandlers {
|
| 136 |
+
handle,
|
| 137 |
+
global_wsgi_application: self.global_wsgi_application.clone(),
|
| 138 |
+
global_wsgi_path: self.global_wsgi_path.clone(),
|
| 139 |
+
host_wsgi_applications: self.host_wsgi_applications.clone(),
|
| 140 |
+
})
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
struct WsgiModuleHandlers {
|
| 145 |
+
handle: Handle,
|
| 146 |
+
global_wsgi_application: Option<Arc<Py<PyAny>>>,
|
| 147 |
+
global_wsgi_path: Option<String>,
|
| 148 |
+
host_wsgi_applications: Arc<Vec<WsgiApplicationWrap>>,
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
#[async_trait]
|
| 152 |
+
impl ServerModuleHandlers for WsgiModuleHandlers {
|
| 153 |
+
async fn request_handler(
|
| 154 |
+
&mut self,
|
| 155 |
+
request: RequestData,
|
| 156 |
+
config: &ServerConfig,
|
| 157 |
+
socket_data: &SocketData,
|
| 158 |
+
error_logger: &ErrorLogger,
|
| 159 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 160 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 161 |
+
let hyper_request = request.get_hyper_request();
|
| 162 |
+
|
| 163 |
+
// Use .take() instead of .clone(), since the values in Options will only be used once.
|
| 164 |
+
let mut wsgi_application = self.global_wsgi_application.take();
|
| 165 |
+
let mut wsgi_path = self.global_wsgi_path.take();
|
| 166 |
+
|
| 167 |
+
// Should have used a HashMap instead of iterating over an array for better performance...
|
| 168 |
+
for host_wsgi_application_wrap in self.host_wsgi_applications.iter() {
|
| 169 |
+
if match_hostname(
|
| 170 |
+
match &host_wsgi_application_wrap.domain {
|
| 171 |
+
Some(value) => Some(value as &str),
|
| 172 |
+
None => None,
|
| 173 |
+
},
|
| 174 |
+
match hyper_request.headers().get(header::HOST) {
|
| 175 |
+
Some(value) => value.to_str().ok(),
|
| 176 |
+
None => None,
|
| 177 |
+
},
|
| 178 |
+
) && match &host_wsgi_application_wrap.ip {
|
| 179 |
+
Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
|
| 180 |
+
None => true,
|
| 181 |
+
} {
|
| 182 |
+
wsgi_application = host_wsgi_application_wrap.wsgi_application.clone();
|
| 183 |
+
wsgi_path = host_wsgi_application_wrap.wsgi_path.clone();
|
| 184 |
+
if let Ok(path_decoded) = urlencoding::decode(
|
| 185 |
+
request
|
| 186 |
+
.get_original_url()
|
| 187 |
+
.unwrap_or(request.get_hyper_request().uri())
|
| 188 |
+
.path(),
|
| 189 |
+
) {
|
| 190 |
+
for location_wrap in host_wsgi_application_wrap.locations.iter() {
|
| 191 |
+
if match_location(&location_wrap.path, &path_decoded) {
|
| 192 |
+
wsgi_application = Some(location_wrap.wsgi_application.clone());
|
| 193 |
+
wsgi_path = location_wrap.wsgi_path.clone();
|
| 194 |
+
break;
|
| 195 |
+
}
|
| 196 |
+
}
|
| 197 |
+
}
|
| 198 |
+
break;
|
| 199 |
+
}
|
| 200 |
+
}
|
| 201 |
+
|
| 202 |
+
let request_path = hyper_request.uri().path();
|
| 203 |
+
let mut request_path_bytes = request_path.bytes();
|
| 204 |
+
if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
|
| 205 |
+
return Ok(
|
| 206 |
+
ResponseData::builder(request)
|
| 207 |
+
.status(StatusCode::BAD_REQUEST)
|
| 208 |
+
.build(),
|
| 209 |
+
);
|
| 210 |
+
}
|
| 211 |
+
|
| 212 |
+
if let Some(wsgi_application) = wsgi_application {
|
| 213 |
+
let wsgi_path = wsgi_path.unwrap_or("/".to_string());
|
| 214 |
+
let mut canonical_wsgi_path: &str = &wsgi_path;
|
| 215 |
+
if canonical_wsgi_path.bytes().last() == Some(b'/') {
|
| 216 |
+
canonical_wsgi_path = &canonical_wsgi_path[..(canonical_wsgi_path.len() - 1)];
|
| 217 |
+
}
|
| 218 |
+
|
| 219 |
+
let request_path_with_slashes = match request_path == canonical_wsgi_path {
|
| 220 |
+
true => format!("{}/", request_path),
|
| 221 |
+
false => request_path.to_string(),
|
| 222 |
+
};
|
| 223 |
+
if let Some(stripped_request_path) =
|
| 224 |
+
request_path_with_slashes.strip_prefix(canonical_wsgi_path)
|
| 225 |
+
{
|
| 226 |
+
let wwwroot_yaml = &config["wwwroot"];
|
| 227 |
+
let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
|
| 228 |
+
|
| 229 |
+
let wwwroot_unknown = PathBuf::from(wwwroot);
|
| 230 |
+
let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
|
| 231 |
+
true => wwwroot_unknown,
|
| 232 |
+
false => match fs::canonicalize(&wwwroot_unknown).await {
|
| 233 |
+
Ok(pathbuf) => pathbuf,
|
| 234 |
+
Err(_) => wwwroot_unknown,
|
| 235 |
+
},
|
| 236 |
+
};
|
| 237 |
+
let wwwroot = wwwroot_pathbuf.as_path();
|
| 238 |
+
|
| 239 |
+
let mut relative_path = &request_path[1..];
|
| 240 |
+
while relative_path.as_bytes().first().copied() == Some(b'/') {
|
| 241 |
+
relative_path = &relative_path[1..];
|
| 242 |
+
}
|
| 243 |
+
|
| 244 |
+
let decoded_relative_path = match urlencoding::decode(relative_path) {
|
| 245 |
+
Ok(path) => path.to_string(),
|
| 246 |
+
Err(_) => {
|
| 247 |
+
return Ok(
|
| 248 |
+
ResponseData::builder(request)
|
| 249 |
+
.status(StatusCode::BAD_REQUEST)
|
| 250 |
+
.build(),
|
| 251 |
+
);
|
| 252 |
+
}
|
| 253 |
+
};
|
| 254 |
+
|
| 255 |
+
let joined_pathbuf = wwwroot.join(decoded_relative_path);
|
| 256 |
+
let execute_pathbuf = joined_pathbuf;
|
| 257 |
+
let execute_path_info = stripped_request_path
|
| 258 |
+
.strip_prefix("/")
|
| 259 |
+
.map(|s| s.to_string());
|
| 260 |
+
|
| 261 |
+
return execute_wsgi_with_environment_variables(
|
| 262 |
+
request,
|
| 263 |
+
socket_data,
|
| 264 |
+
error_logger,
|
| 265 |
+
wwwroot,
|
| 266 |
+
execute_pathbuf,
|
| 267 |
+
execute_path_info,
|
| 268 |
+
config["serverAdministratorEmail"].as_str(),
|
| 269 |
+
wsgi_application,
|
| 270 |
+
)
|
| 271 |
+
.await;
|
| 272 |
+
}
|
| 273 |
+
}
|
| 274 |
+
Ok(ResponseData::builder(request).build())
|
| 275 |
+
})
|
| 276 |
+
.await
|
| 277 |
+
}
|
| 278 |
+
|
| 279 |
+
async fn proxy_request_handler(
|
| 280 |
+
&mut self,
|
| 281 |
+
request: RequestData,
|
| 282 |
+
_config: &ServerConfig,
|
| 283 |
+
_socket_data: &SocketData,
|
| 284 |
+
_error_logger: &ErrorLogger,
|
| 285 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 286 |
+
Ok(ResponseData::builder(request).build())
|
| 287 |
+
}
|
| 288 |
+
|
| 289 |
+
async fn response_modifying_handler(
|
| 290 |
+
&mut self,
|
| 291 |
+
response: HyperResponse,
|
| 292 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 293 |
+
Ok(response)
|
| 294 |
+
}
|
| 295 |
+
|
| 296 |
+
async fn proxy_response_modifying_handler(
|
| 297 |
+
&mut self,
|
| 298 |
+
response: HyperResponse,
|
| 299 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 300 |
+
Ok(response)
|
| 301 |
+
}
|
| 302 |
+
|
| 303 |
+
async fn connect_proxy_request_handler(
|
| 304 |
+
&mut self,
|
| 305 |
+
_upgraded_request: HyperUpgraded,
|
| 306 |
+
_connect_address: &str,
|
| 307 |
+
_config: &ServerConfig,
|
| 308 |
+
_socket_data: &SocketData,
|
| 309 |
+
_error_logger: &ErrorLogger,
|
| 310 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 311 |
+
Ok(())
|
| 312 |
+
}
|
| 313 |
+
|
| 314 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 315 |
+
false
|
| 316 |
+
}
|
| 317 |
+
|
| 318 |
+
async fn websocket_request_handler(
|
| 319 |
+
&mut self,
|
| 320 |
+
_websocket: HyperWebsocket,
|
| 321 |
+
_uri: &hyper::Uri,
|
| 322 |
+
_config: &ServerConfig,
|
| 323 |
+
_socket_data: &SocketData,
|
| 324 |
+
_error_logger: &ErrorLogger,
|
| 325 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 326 |
+
Ok(())
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 330 |
+
false
|
| 331 |
+
}
|
| 332 |
+
}
|
| 333 |
+
|
| 334 |
+
struct ResponseHead {
|
| 335 |
+
status: StatusCode,
|
| 336 |
+
headers: Option<HeaderMap>,
|
| 337 |
+
is_set: bool,
|
| 338 |
+
is_sent: bool,
|
| 339 |
+
}
|
| 340 |
+
|
| 341 |
+
impl ResponseHead {
|
| 342 |
+
fn new() -> Self {
|
| 343 |
+
Self {
|
| 344 |
+
status: StatusCode::OK,
|
| 345 |
+
headers: None,
|
| 346 |
+
is_set: false,
|
| 347 |
+
is_sent: false,
|
| 348 |
+
}
|
| 349 |
+
}
|
| 350 |
+
}
|
| 351 |
+
|
| 352 |
+
#[allow(clippy::too_many_arguments)]
|
| 353 |
+
async fn execute_wsgi_with_environment_variables(
|
| 354 |
+
request: RequestData,
|
| 355 |
+
socket_data: &SocketData,
|
| 356 |
+
error_logger: &ErrorLogger,
|
| 357 |
+
wwwroot: &Path,
|
| 358 |
+
execute_pathbuf: PathBuf,
|
| 359 |
+
path_info: Option<String>,
|
| 360 |
+
server_administrator_email: Option<&str>,
|
| 361 |
+
wsgi_application: Arc<Py<PyAny>>,
|
| 362 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 363 |
+
let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
|
| 364 |
+
|
| 365 |
+
let hyper_request = request.get_hyper_request();
|
| 366 |
+
let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
|
| 367 |
+
|
| 368 |
+
if let Some(auth_user) = request.get_auth_user() {
|
| 369 |
+
if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
|
| 370 |
+
let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
|
| 371 |
+
let mut authorization_value_split = authorization_value.split(" ");
|
| 372 |
+
if let Some(authorization_type) = authorization_value_split.next() {
|
| 373 |
+
environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
|
| 374 |
+
}
|
| 375 |
+
}
|
| 376 |
+
environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
|
| 377 |
+
}
|
| 378 |
+
|
| 379 |
+
environment_variables.insert(
|
| 380 |
+
"QUERY_STRING".to_string(),
|
| 381 |
+
match hyper_request.uri().query() {
|
| 382 |
+
Some(query) => query.to_string(),
|
| 383 |
+
None => "".to_string(),
|
| 384 |
+
},
|
| 385 |
+
);
|
| 386 |
+
|
| 387 |
+
environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
|
| 388 |
+
environment_variables.insert(
|
| 389 |
+
"SERVER_PROTOCOL".to_string(),
|
| 390 |
+
match hyper_request.version() {
|
| 391 |
+
hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
|
| 392 |
+
hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
|
| 393 |
+
hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
|
| 394 |
+
hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
|
| 395 |
+
hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
|
| 396 |
+
_ => "HTTP/Unknown".to_string(),
|
| 397 |
+
},
|
| 398 |
+
);
|
| 399 |
+
environment_variables.insert(
|
| 400 |
+
"SERVER_PORT".to_string(),
|
| 401 |
+
socket_data.local_addr.port().to_string(),
|
| 402 |
+
);
|
| 403 |
+
environment_variables.insert(
|
| 404 |
+
"SERVER_ADDR".to_string(),
|
| 405 |
+
socket_data.local_addr.ip().to_canonical().to_string(),
|
| 406 |
+
);
|
| 407 |
+
if let Some(server_administrator_email) = server_administrator_email {
|
| 408 |
+
environment_variables.insert(
|
| 409 |
+
"SERVER_ADMIN".to_string(),
|
| 410 |
+
server_administrator_email.to_string(),
|
| 411 |
+
);
|
| 412 |
+
}
|
| 413 |
+
if let Some(host) = hyper_request.headers().get(header::HOST) {
|
| 414 |
+
environment_variables.insert(
|
| 415 |
+
"SERVER_NAME".to_string(),
|
| 416 |
+
String::from_utf8_lossy(host.as_bytes()).to_string(),
|
| 417 |
+
);
|
| 418 |
+
}
|
| 419 |
+
|
| 420 |
+
environment_variables.insert(
|
| 421 |
+
"DOCUMENT_ROOT".to_string(),
|
| 422 |
+
wwwroot.to_string_lossy().to_string(),
|
| 423 |
+
);
|
| 424 |
+
environment_variables.insert(
|
| 425 |
+
"PATH_INFO".to_string(),
|
| 426 |
+
match &path_info {
|
| 427 |
+
Some(path_info) => format!("/{}", path_info),
|
| 428 |
+
None => "".to_string(),
|
| 429 |
+
},
|
| 430 |
+
);
|
| 431 |
+
environment_variables.insert(
|
| 432 |
+
"PATH_TRANSLATED".to_string(),
|
| 433 |
+
match &path_info {
|
| 434 |
+
Some(path_info) => {
|
| 435 |
+
let mut path_translated = execute_pathbuf.clone();
|
| 436 |
+
path_translated.push(path_info);
|
| 437 |
+
path_translated.to_string_lossy().to_string()
|
| 438 |
+
}
|
| 439 |
+
None => "".to_string(),
|
| 440 |
+
},
|
| 441 |
+
);
|
| 442 |
+
environment_variables.insert(
|
| 443 |
+
"REQUEST_METHOD".to_string(),
|
| 444 |
+
hyper_request.method().to_string(),
|
| 445 |
+
);
|
| 446 |
+
environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
|
| 447 |
+
environment_variables.insert(
|
| 448 |
+
"REQUEST_URI".to_string(),
|
| 449 |
+
format!(
|
| 450 |
+
"{}{}",
|
| 451 |
+
original_request_uri.path(),
|
| 452 |
+
match original_request_uri.query() {
|
| 453 |
+
Some(query) => format!("?{}", query),
|
| 454 |
+
None => String::from(""),
|
| 455 |
+
}
|
| 456 |
+
),
|
| 457 |
+
);
|
| 458 |
+
|
| 459 |
+
environment_variables.insert(
|
| 460 |
+
"REMOTE_PORT".to_string(),
|
| 461 |
+
socket_data.remote_addr.port().to_string(),
|
| 462 |
+
);
|
| 463 |
+
environment_variables.insert(
|
| 464 |
+
"REMOTE_ADDR".to_string(),
|
| 465 |
+
socket_data.remote_addr.ip().to_canonical().to_string(),
|
| 466 |
+
);
|
| 467 |
+
|
| 468 |
+
environment_variables.insert(
|
| 469 |
+
"SCRIPT_FILENAME".to_string(),
|
| 470 |
+
execute_pathbuf.to_string_lossy().to_string(),
|
| 471 |
+
);
|
| 472 |
+
if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
|
| 473 |
+
environment_variables.insert(
|
| 474 |
+
"SCRIPT_NAME".to_string(),
|
| 475 |
+
format!(
|
| 476 |
+
"/{}",
|
| 477 |
+
match cfg!(windows) {
|
| 478 |
+
true => script_path.to_string_lossy().to_string().replace("\\", "/"),
|
| 479 |
+
false => script_path.to_string_lossy().to_string(),
|
| 480 |
+
}
|
| 481 |
+
),
|
| 482 |
+
);
|
| 483 |
+
}
|
| 484 |
+
|
| 485 |
+
if socket_data.encrypted {
|
| 486 |
+
environment_variables.insert("HTTPS".to_string(), "ON".to_string());
|
| 487 |
+
}
|
| 488 |
+
|
| 489 |
+
let mut content_length_set = false;
|
| 490 |
+
for (header_name, header_value) in hyper_request.headers().iter() {
|
| 491 |
+
let env_header_name = match *header_name {
|
| 492 |
+
header::CONTENT_LENGTH => {
|
| 493 |
+
content_length_set = true;
|
| 494 |
+
"CONTENT_LENGTH".to_string()
|
| 495 |
+
}
|
| 496 |
+
header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
|
| 497 |
+
_ => {
|
| 498 |
+
let mut result = String::new();
|
| 499 |
+
|
| 500 |
+
result.push_str("HTTP_");
|
| 501 |
+
|
| 502 |
+
for c in header_name.as_str().to_uppercase().chars() {
|
| 503 |
+
if c.is_alphanumeric() {
|
| 504 |
+
result.push(c);
|
| 505 |
+
} else {
|
| 506 |
+
result.push('_');
|
| 507 |
+
}
|
| 508 |
+
}
|
| 509 |
+
|
| 510 |
+
result
|
| 511 |
+
}
|
| 512 |
+
};
|
| 513 |
+
if environment_variables.contains_key(&env_header_name) {
|
| 514 |
+
let value = environment_variables.get_mut(&env_header_name);
|
| 515 |
+
if let Some(value) = value {
|
| 516 |
+
if env_header_name == "HTTP_COOKIE" {
|
| 517 |
+
value.push_str("; ");
|
| 518 |
+
} else {
|
| 519 |
+
// See https://stackoverflow.com/a/1801191
|
| 520 |
+
value.push_str(", ");
|
| 521 |
+
}
|
| 522 |
+
value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
|
| 523 |
+
} else {
|
| 524 |
+
environment_variables.insert(
|
| 525 |
+
env_header_name,
|
| 526 |
+
String::from_utf8_lossy(header_value.as_bytes()).to_string(),
|
| 527 |
+
);
|
| 528 |
+
}
|
| 529 |
+
} else {
|
| 530 |
+
environment_variables.insert(
|
| 531 |
+
env_header_name,
|
| 532 |
+
String::from_utf8_lossy(header_value.as_bytes()).to_string(),
|
| 533 |
+
);
|
| 534 |
+
}
|
| 535 |
+
}
|
| 536 |
+
|
| 537 |
+
if !content_length_set {
|
| 538 |
+
environment_variables.insert("CONTENT_LENGTH".to_string(), "0".to_string());
|
| 539 |
+
}
|
| 540 |
+
|
| 541 |
+
let (hyper_request, _, _) = request.into_parts();
|
| 542 |
+
|
| 543 |
+
execute_wsgi(
|
| 544 |
+
hyper_request,
|
| 545 |
+
error_logger,
|
| 546 |
+
wsgi_application,
|
| 547 |
+
environment_variables,
|
| 548 |
+
)
|
| 549 |
+
.await
|
| 550 |
+
}
|
| 551 |
+
|
| 552 |
+
async fn execute_wsgi(
|
| 553 |
+
hyper_request: HyperRequest,
|
| 554 |
+
error_logger: &ErrorLogger,
|
| 555 |
+
wsgi_application: Arc<Py<PyAny>>,
|
| 556 |
+
environment_variables: LinkedHashMap<String, String>,
|
| 557 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 558 |
+
let (_, body) = hyper_request.into_parts();
|
| 559 |
+
let body_reader = StreamReader::new(body.into_data_stream().map_err(std::io::Error::other));
|
| 560 |
+
let wsgi_head = Arc::new(Mutex::new(ResponseHead::new()));
|
| 561 |
+
let wsgi_head_clone = wsgi_head.clone();
|
| 562 |
+
let error_logger_owned = error_logger.to_owned();
|
| 563 |
+
let body_iterator = tokio::task::spawn_blocking(move || {
|
| 564 |
+
Python::with_gil(move |py| -> PyResult<Py<PyIterator>> {
|
| 565 |
+
let start_response = PyCFunction::new_closure(
|
| 566 |
+
py,
|
| 567 |
+
None,
|
| 568 |
+
None,
|
| 569 |
+
move |args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>| -> PyResult<_> {
|
| 570 |
+
let args_native = args.extract::<(String, Vec<(String, String)>)>()?;
|
| 571 |
+
let exc_info = kwargs.map_or(Ok(None), |kwargs| {
|
| 572 |
+
let exc_info = kwargs.get_item("exc_info");
|
| 573 |
+
if let Ok(Some(exc_info)) = exc_info {
|
| 574 |
+
if exc_info.is_none() {
|
| 575 |
+
Ok(None)
|
| 576 |
+
} else {
|
| 577 |
+
Ok(Some(exc_info))
|
| 578 |
+
}
|
| 579 |
+
} else {
|
| 580 |
+
exc_info
|
| 581 |
+
}
|
| 582 |
+
})?;
|
| 583 |
+
let mut wsgi_head_locked = wsgi_head_clone.blocking_lock();
|
| 584 |
+
if let Some(exc_info) = exc_info {
|
| 585 |
+
if wsgi_head_locked.is_sent {
|
| 586 |
+
let exc_info_tuple = exc_info.downcast::<PyTuple>()?;
|
| 587 |
+
let exc_info_exception = exc_info_tuple
|
| 588 |
+
.get_item(1)?
|
| 589 |
+
.getattr("with_traceback")?
|
| 590 |
+
.call((exc_info_tuple.get_item(2)?,), None)?
|
| 591 |
+
.downcast::<PyException>()?
|
| 592 |
+
.clone();
|
| 593 |
+
Err(exc_info_exception)?
|
| 594 |
+
}
|
| 595 |
+
} else if wsgi_head_locked.is_set {
|
| 596 |
+
Err(PyAssertionError::new_err("Headers already set"))?
|
| 597 |
+
}
|
| 598 |
+
let status_code_string_option = args_native.0.split(" ").next();
|
| 599 |
+
if let Some(status_code_string) = status_code_string_option {
|
| 600 |
+
wsgi_head_locked.status =
|
| 601 |
+
StatusCode::from_u16(status_code_string.parse()?).map_err(|e| anyhow::anyhow!(e))?;
|
| 602 |
+
} else {
|
| 603 |
+
Err(anyhow::anyhow!("Can't extract status code"))?;
|
| 604 |
+
}
|
| 605 |
+
let mut header_map = HeaderMap::new();
|
| 606 |
+
for header in args_native.1 {
|
| 607 |
+
header_map.append(
|
| 608 |
+
HeaderName::from_str(&header.0).map_err(|e| anyhow::anyhow!(e))?,
|
| 609 |
+
HeaderValue::from_str(&header.1).map_err(|e| anyhow::anyhow!(e))?,
|
| 610 |
+
);
|
| 611 |
+
}
|
| 612 |
+
wsgi_head_locked.headers = Some(header_map);
|
| 613 |
+
wsgi_head_locked.is_set = true;
|
| 614 |
+
Ok(())
|
| 615 |
+
},
|
| 616 |
+
)?;
|
| 617 |
+
let mut environment: HashMap<String, Bound<'_, PyAny>> = HashMap::new();
|
| 618 |
+
let is_https = environment_variables.contains_key("HTTPS");
|
| 619 |
+
let content_length = if let Some(content_length) = environment_variables.get("CONTENT_LENGTH")
|
| 620 |
+
{
|
| 621 |
+
content_length.parse::<u64>().ok()
|
| 622 |
+
} else {
|
| 623 |
+
None
|
| 624 |
+
};
|
| 625 |
+
for (environment_variable, environment_variable_value) in environment_variables {
|
| 626 |
+
environment.insert(
|
| 627 |
+
environment_variable,
|
| 628 |
+
PyString::new(py, &environment_variable_value).into_any(),
|
| 629 |
+
);
|
| 630 |
+
}
|
| 631 |
+
environment.insert(
|
| 632 |
+
"wsgi.version".to_string(),
|
| 633 |
+
PyTuple::new(py, [1, 0])?.into_any(),
|
| 634 |
+
);
|
| 635 |
+
environment.insert(
|
| 636 |
+
"wsgi.url_scheme".to_string(),
|
| 637 |
+
PyString::new(py, if is_https { "https" } else { "http" }).into_any(),
|
| 638 |
+
);
|
| 639 |
+
environment.insert(
|
| 640 |
+
"wsgi.input".to_string(),
|
| 641 |
+
(if let Some(content_length) = content_length {
|
| 642 |
+
WsgiInputStream::new(body_reader.take(content_length))
|
| 643 |
+
} else {
|
| 644 |
+
WsgiInputStream::new(body_reader)
|
| 645 |
+
})
|
| 646 |
+
.into_pyobject(py)?
|
| 647 |
+
.into_any(),
|
| 648 |
+
);
|
| 649 |
+
environment.insert(
|
| 650 |
+
"wsgi.errors".to_string(),
|
| 651 |
+
WsgiErrorStream::new(error_logger_owned)
|
| 652 |
+
.into_pyobject(py)?
|
| 653 |
+
.into_any(),
|
| 654 |
+
);
|
| 655 |
+
environment.insert(
|
| 656 |
+
"wsgi.multithread".to_string(),
|
| 657 |
+
PyBool::new(py, true).as_any().clone(),
|
| 658 |
+
);
|
| 659 |
+
environment.insert(
|
| 660 |
+
"wsgi.multiprocess".to_string(),
|
| 661 |
+
PyBool::new(py, false).as_any().clone(),
|
| 662 |
+
);
|
| 663 |
+
environment.insert(
|
| 664 |
+
"wsgi.run_once".to_string(),
|
| 665 |
+
PyBool::new(py, false).as_any().clone(),
|
| 666 |
+
);
|
| 667 |
+
let body_unknown = wsgi_application.call(py, (environment, start_response), None)?;
|
| 668 |
+
let body_iterator = body_unknown
|
| 669 |
+
.downcast_bound::<PyIterator>(py)?
|
| 670 |
+
.clone()
|
| 671 |
+
.unbind();
|
| 672 |
+
Ok(body_iterator)
|
| 673 |
+
})
|
| 674 |
+
})
|
| 675 |
+
.await??;
|
| 676 |
+
|
| 677 |
+
let wsgi_head_clone = wsgi_head.clone();
|
| 678 |
+
let mut response_stream =
|
| 679 |
+
futures_util::stream::unfold(Arc::new(body_iterator), move |body_iterator_arc| {
|
| 680 |
+
let wsgi_head_clone = wsgi_head_clone.clone();
|
| 681 |
+
Box::pin(async move {
|
| 682 |
+
let body_iterator_arc_clone = body_iterator_arc.clone();
|
| 683 |
+
let blocking_thread_result = tokio::task::spawn_blocking(move || {
|
| 684 |
+
Python::with_gil(|py| -> PyResult<Option<Bytes>> {
|
| 685 |
+
let mut body_iterator_bound = body_iterator_arc_clone.bind(py).clone();
|
| 686 |
+
if let Some(body_chunk) = body_iterator_bound.next() {
|
| 687 |
+
Ok(Some(Bytes::from(body_chunk?.extract::<Vec<u8>>()?)))
|
| 688 |
+
} else {
|
| 689 |
+
Ok(None)
|
| 690 |
+
}
|
| 691 |
+
})
|
| 692 |
+
})
|
| 693 |
+
.await;
|
| 694 |
+
|
| 695 |
+
match blocking_thread_result {
|
| 696 |
+
Err(error) => Some((Err(std::io::Error::other(error)), body_iterator_arc)),
|
| 697 |
+
Ok(Err(error)) => Some((Err(std::io::Error::other(error)), body_iterator_arc)),
|
| 698 |
+
Ok(Ok(None)) => None,
|
| 699 |
+
Ok(Ok(Some(chunk))) => {
|
| 700 |
+
let wsgi_head_locked = wsgi_head_clone.lock().await;
|
| 701 |
+
if !wsgi_head_locked.is_set {
|
| 702 |
+
Some((
|
| 703 |
+
Err(std::io::Error::other(
|
| 704 |
+
"The \"start_response\" function hasn't been called.",
|
| 705 |
+
)),
|
| 706 |
+
body_iterator_arc,
|
| 707 |
+
))
|
| 708 |
+
} else {
|
| 709 |
+
Some((Ok(chunk), body_iterator_arc))
|
| 710 |
+
}
|
| 711 |
+
}
|
| 712 |
+
}
|
| 713 |
+
})
|
| 714 |
+
});
|
| 715 |
+
|
| 716 |
+
let first_chunk = response_stream.next().await;
|
| 717 |
+
let response_body = if let Some(Err(first_chunk_error)) = first_chunk {
|
| 718 |
+
Err(first_chunk_error)?
|
| 719 |
+
} else if let Some(Ok(first_chunk)) = first_chunk {
|
| 720 |
+
let response_stream_first_item = futures_util::stream::once(async move { Ok(first_chunk) });
|
| 721 |
+
let response_stream_combined = response_stream_first_item.chain(response_stream);
|
| 722 |
+
let stream_body = StreamBody::new(response_stream_combined.map_ok(Frame::data));
|
| 723 |
+
|
| 724 |
+
BodyExt::boxed(stream_body)
|
| 725 |
+
} else {
|
| 726 |
+
BodyExt::boxed(Empty::new().map_err(|e| match e {}))
|
| 727 |
+
};
|
| 728 |
+
|
| 729 |
+
let mut wsgi_head_locked = wsgi_head.lock().await;
|
| 730 |
+
let mut hyper_response = Response::new(response_body);
|
| 731 |
+
*hyper_response.status_mut() = wsgi_head_locked.status;
|
| 732 |
+
if let Some(headers) = wsgi_head_locked.headers.take() {
|
| 733 |
+
*hyper_response.headers_mut() = headers;
|
| 734 |
+
}
|
| 735 |
+
wsgi_head_locked.is_sent = true;
|
| 736 |
+
|
| 737 |
+
Ok(
|
| 738 |
+
ResponseData::builder_without_request()
|
| 739 |
+
.response(hyper_response)
|
| 740 |
+
.build(),
|
| 741 |
+
)
|
| 742 |
+
}
|
ferron/src/optional_modules/wsgid.rs
ADDED
|
@@ -0,0 +1,1035 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#[cfg(not(unix))]
|
| 2 |
+
compile_error!("This module is supported only on Unix and Unix-like systems.");
|
| 3 |
+
|
| 4 |
+
use std::collections::HashMap;
|
| 5 |
+
use std::error::Error;
|
| 6 |
+
use std::io::{BufReader, Read};
|
| 7 |
+
use std::path::{Path, PathBuf};
|
| 8 |
+
use std::str::FromStr;
|
| 9 |
+
use std::sync::Arc;
|
| 10 |
+
use std::thread;
|
| 11 |
+
|
| 12 |
+
use crate::ferron_common::{
|
| 13 |
+
ErrorLogger, HyperRequest, HyperUpgraded, RequestData, ResponseData, ServerConfig, ServerModule,
|
| 14 |
+
ServerModuleHandlers, SocketData,
|
| 15 |
+
};
|
| 16 |
+
use crate::ferron_common::{HyperResponse, WithRuntime};
|
| 17 |
+
use crate::ferron_res::server_software::SERVER_SOFTWARE;
|
| 18 |
+
use crate::ferron_util::ip_match::ip_match;
|
| 19 |
+
use crate::ferron_util::match_hostname::match_hostname;
|
| 20 |
+
use crate::ferron_util::match_location::match_location;
|
| 21 |
+
use crate::ferron_util::preforked_process_pool::{
|
| 22 |
+
read_ipc_message, read_ipc_message_async, write_ipc_message, write_ipc_message_async,
|
| 23 |
+
PreforkedProcessPool,
|
| 24 |
+
};
|
| 25 |
+
use crate::ferron_util::wsgi_load_application::load_wsgi_application;
|
| 26 |
+
use crate::ferron_util::wsgid_body_reader::WsgidBodyReader;
|
| 27 |
+
use crate::ferron_util::wsgid_error_stream::WsgidErrorStream;
|
| 28 |
+
use crate::ferron_util::wsgid_input_stream::WsgidInputStream;
|
| 29 |
+
use crate::ferron_util::wsgid_message_structs::{
|
| 30 |
+
ProcessPoolToServerMessage, ServerToProcessPoolMessage,
|
| 31 |
+
};
|
| 32 |
+
use crate::ferron_util::wsgid_structs::{WsgidApplicationLocationWrap, WsgidApplicationWrap};
|
| 33 |
+
use async_trait::async_trait;
|
| 34 |
+
use futures_util::{StreamExt, TryStreamExt};
|
| 35 |
+
use hashlink::LinkedHashMap;
|
| 36 |
+
use http::{HeaderMap, HeaderName, HeaderValue, StatusCode};
|
| 37 |
+
use http_body_util::{BodyExt, Empty, StreamBody};
|
| 38 |
+
use hyper::body::{Bytes, Frame};
|
| 39 |
+
use hyper::header;
|
| 40 |
+
use hyper::Response;
|
| 41 |
+
use hyper_tungstenite::HyperWebsocket;
|
| 42 |
+
use interprocess::unnamed_pipe::{Recver, Sender};
|
| 43 |
+
use pyo3::exceptions::{PyAssertionError, PyException};
|
| 44 |
+
use pyo3::prelude::*;
|
| 45 |
+
use pyo3::types::{PyBool, PyCFunction, PyDict, PyIterator, PyString, PyTuple};
|
| 46 |
+
//use postcard::{DeOptions, SerOptions};
|
| 47 |
+
use tokio::fs;
|
| 48 |
+
use tokio::runtime::Handle;
|
| 49 |
+
use tokio::sync::Mutex;
|
| 50 |
+
|
| 51 |
+
struct ResponseHead {
|
| 52 |
+
status: u16,
|
| 53 |
+
headers: Option<LinkedHashMap<String, Vec<String>>>,
|
| 54 |
+
is_set: bool,
|
| 55 |
+
is_sent: bool,
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
impl ResponseHead {
|
| 59 |
+
fn new() -> Self {
|
| 60 |
+
Self {
|
| 61 |
+
status: 200,
|
| 62 |
+
headers: None,
|
| 63 |
+
is_set: false,
|
| 64 |
+
is_sent: false,
|
| 65 |
+
}
|
| 66 |
+
}
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
fn wsgi_pool_fn(tx: Sender, rx: Recver, wsgi_script_path: PathBuf) {
|
| 70 |
+
let wsgi_application_result: Result<Py<PyAny>, Box<dyn Error + Send + Sync>> =
|
| 71 |
+
load_wsgi_application(wsgi_script_path.as_path(), false);
|
| 72 |
+
let mut body_iterators = HashMap::new();
|
| 73 |
+
let mut application_id = 0;
|
| 74 |
+
let mut wsgi_head = Arc::new(Mutex::new(ResponseHead::new()));
|
| 75 |
+
let rx_mutex = Arc::new(Mutex::new(rx));
|
| 76 |
+
let tx_mutex = Arc::new(Mutex::new(tx));
|
| 77 |
+
|
| 78 |
+
loop {
|
| 79 |
+
let received_raw_message = match read_ipc_message(&mut rx_mutex.blocking_lock()) {
|
| 80 |
+
Ok(message) => message,
|
| 81 |
+
Err(_) => break,
|
| 82 |
+
};
|
| 83 |
+
|
| 84 |
+
let received_message =
|
| 85 |
+
match postcard::from_bytes::<ServerToProcessPoolMessage>(&received_raw_message) {
|
| 86 |
+
Ok(message) => message,
|
| 87 |
+
Err(_) => continue,
|
| 88 |
+
};
|
| 89 |
+
|
| 90 |
+
if let Some(error) = (|| -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 91 |
+
let wsgi_application = wsgi_application_result
|
| 92 |
+
.as_ref()
|
| 93 |
+
.map_err(|x| anyhow::anyhow!(x.to_string()))?;
|
| 94 |
+
if let Some(environment_variables) = received_message.environment_variables {
|
| 95 |
+
wsgi_head = Arc::new(Mutex::new(ResponseHead::new()));
|
| 96 |
+
let wsgi_head_clone = wsgi_head.clone();
|
| 97 |
+
let tx_mutex_clone = tx_mutex.clone();
|
| 98 |
+
let rx_mutex_clone = rx_mutex.clone();
|
| 99 |
+
let body_iterator = Python::with_gil(move |py| -> PyResult<Py<PyIterator>> {
|
| 100 |
+
let start_response = PyCFunction::new_closure(
|
| 101 |
+
py,
|
| 102 |
+
None,
|
| 103 |
+
None,
|
| 104 |
+
move |args: &Bound<'_, PyTuple>, kwargs: Option<&Bound<'_, PyDict>>| -> PyResult<_> {
|
| 105 |
+
let args_native = args.extract::<(String, Vec<(String, String)>)>()?;
|
| 106 |
+
let exc_info = kwargs.map_or(Ok(None), |kwargs| {
|
| 107 |
+
let exc_info = kwargs.get_item("exc_info");
|
| 108 |
+
if let Ok(Some(exc_info)) = exc_info {
|
| 109 |
+
if exc_info.is_none() {
|
| 110 |
+
Ok(None)
|
| 111 |
+
} else {
|
| 112 |
+
Ok(Some(exc_info))
|
| 113 |
+
}
|
| 114 |
+
} else {
|
| 115 |
+
exc_info
|
| 116 |
+
}
|
| 117 |
+
})?;
|
| 118 |
+
let mut wsgi_head_locked = wsgi_head_clone.blocking_lock();
|
| 119 |
+
if let Some(exc_info) = exc_info {
|
| 120 |
+
if wsgi_head_locked.is_sent {
|
| 121 |
+
let exc_info_tuple = exc_info.downcast::<PyTuple>()?;
|
| 122 |
+
let exc_info_exception = exc_info_tuple
|
| 123 |
+
.get_item(1)?
|
| 124 |
+
.getattr("with_traceback")?
|
| 125 |
+
.call((exc_info_tuple.get_item(2)?,), None)?
|
| 126 |
+
.downcast::<PyException>()?
|
| 127 |
+
.clone();
|
| 128 |
+
Err(exc_info_exception)?
|
| 129 |
+
}
|
| 130 |
+
} else if wsgi_head_locked.is_set {
|
| 131 |
+
Err(PyAssertionError::new_err("Headers already set"))?
|
| 132 |
+
}
|
| 133 |
+
let status_code_string_option = args_native.0.split(" ").next();
|
| 134 |
+
if let Some(status_code_string) = status_code_string_option {
|
| 135 |
+
wsgi_head_locked.status = status_code_string
|
| 136 |
+
.parse()
|
| 137 |
+
.map_err(|e: std::num::ParseIntError| anyhow::anyhow!(e))?;
|
| 138 |
+
} else {
|
| 139 |
+
Err(anyhow::anyhow!("Can't extract status code"))?;
|
| 140 |
+
}
|
| 141 |
+
let mut header_map: LinkedHashMap<String, Vec<String>> = LinkedHashMap::new();
|
| 142 |
+
for header in args_native.1 {
|
| 143 |
+
let header_name = header.0.to_lowercase();
|
| 144 |
+
let header_value = header.1;
|
| 145 |
+
if let Some(header_values) = header_map.get_mut(&header_name) {
|
| 146 |
+
header_values.push(header_value);
|
| 147 |
+
} else {
|
| 148 |
+
header_map.insert(header_name, vec![header_value]);
|
| 149 |
+
}
|
| 150 |
+
}
|
| 151 |
+
wsgi_head_locked.headers = Some(header_map);
|
| 152 |
+
wsgi_head_locked.is_set = true;
|
| 153 |
+
Ok(())
|
| 154 |
+
},
|
| 155 |
+
)?;
|
| 156 |
+
let mut environment: HashMap<String, Bound<'_, PyAny>> = HashMap::new();
|
| 157 |
+
let is_https = environment_variables.contains_key("HTTPS");
|
| 158 |
+
let content_length =
|
| 159 |
+
if let Some(content_length) = environment_variables.get("CONTENT_LENGTH") {
|
| 160 |
+
content_length.parse::<u64>().ok()
|
| 161 |
+
} else {
|
| 162 |
+
None
|
| 163 |
+
};
|
| 164 |
+
for (environment_variable, environment_variable_value) in environment_variables {
|
| 165 |
+
environment.insert(
|
| 166 |
+
environment_variable,
|
| 167 |
+
PyString::new(py, &environment_variable_value).into_any(),
|
| 168 |
+
);
|
| 169 |
+
}
|
| 170 |
+
environment.insert(
|
| 171 |
+
"wsgi.version".to_string(),
|
| 172 |
+
PyTuple::new(py, [1, 0])?.into_any(),
|
| 173 |
+
);
|
| 174 |
+
environment.insert(
|
| 175 |
+
"wsgi.url_scheme".to_string(),
|
| 176 |
+
PyString::new(py, if is_https { "https" } else { "http" }).into_any(),
|
| 177 |
+
);
|
| 178 |
+
environment.insert(
|
| 179 |
+
"wsgi.input".to_string(),
|
| 180 |
+
(if let Some(content_length) = content_length {
|
| 181 |
+
WsgidInputStream::new(
|
| 182 |
+
BufReader::new(WsgidBodyReader::new(
|
| 183 |
+
tx_mutex_clone.clone(),
|
| 184 |
+
rx_mutex_clone.clone(),
|
| 185 |
+
))
|
| 186 |
+
.take(content_length),
|
| 187 |
+
)
|
| 188 |
+
} else {
|
| 189 |
+
WsgidInputStream::new(BufReader::new(WsgidBodyReader::new(
|
| 190 |
+
tx_mutex_clone.clone(),
|
| 191 |
+
rx_mutex_clone.clone(),
|
| 192 |
+
)))
|
| 193 |
+
})
|
| 194 |
+
.into_pyobject(py)?
|
| 195 |
+
.into_any(),
|
| 196 |
+
);
|
| 197 |
+
environment.insert(
|
| 198 |
+
"wsgi.errors".to_string(),
|
| 199 |
+
WsgidErrorStream::new(tx_mutex_clone.clone())
|
| 200 |
+
.into_pyobject(py)?
|
| 201 |
+
.into_any(),
|
| 202 |
+
);
|
| 203 |
+
environment.insert(
|
| 204 |
+
"wsgi.multithread".to_string(),
|
| 205 |
+
PyBool::new(py, false).as_any().clone(),
|
| 206 |
+
);
|
| 207 |
+
environment.insert(
|
| 208 |
+
"wsgi.multiprocess".to_string(),
|
| 209 |
+
PyBool::new(py, true).as_any().clone(),
|
| 210 |
+
);
|
| 211 |
+
environment.insert(
|
| 212 |
+
"wsgi.run_once".to_string(),
|
| 213 |
+
PyBool::new(py, false).as_any().clone(),
|
| 214 |
+
);
|
| 215 |
+
let body_unknown = wsgi_application.call(py, (environment, start_response), None)?;
|
| 216 |
+
let body_iterator = body_unknown
|
| 217 |
+
.downcast_bound::<PyIterator>(py)?
|
| 218 |
+
.clone()
|
| 219 |
+
.unbind();
|
| 220 |
+
Ok(body_iterator)
|
| 221 |
+
})?;
|
| 222 |
+
let current_application_id = application_id;
|
| 223 |
+
body_iterators.insert(current_application_id, Arc::new(body_iterator));
|
| 224 |
+
application_id += 1;
|
| 225 |
+
write_ipc_message(
|
| 226 |
+
&mut tx_mutex.blocking_lock(),
|
| 227 |
+
&postcard::to_allocvec::<ProcessPoolToServerMessage>(&ProcessPoolToServerMessage {
|
| 228 |
+
application_id: Some(current_application_id),
|
| 229 |
+
status_code: None,
|
| 230 |
+
headers: None,
|
| 231 |
+
body_chunk: None,
|
| 232 |
+
error_log_line: None,
|
| 233 |
+
error_message: None,
|
| 234 |
+
requests_body_chunk: false,
|
| 235 |
+
})?,
|
| 236 |
+
)?
|
| 237 |
+
} else if received_message.requests_body_chunk {
|
| 238 |
+
if let Some(application_id) = received_message.application_id {
|
| 239 |
+
if let Some(body_iterator_arc) = body_iterators.get(&application_id) {
|
| 240 |
+
let wsgi_head_clone = wsgi_head.clone();
|
| 241 |
+
let body_iterator_arc_clone = body_iterator_arc.clone();
|
| 242 |
+
let body_chunk_result = Python::with_gil(|py| -> PyResult<Option<Vec<u8>>> {
|
| 243 |
+
let mut body_iterator_bound = body_iterator_arc_clone.bind(py).clone();
|
| 244 |
+
if let Some(body_chunk) = body_iterator_bound.next() {
|
| 245 |
+
Ok(Some(body_chunk?.extract::<Vec<u8>>()?))
|
| 246 |
+
} else {
|
| 247 |
+
Ok(None)
|
| 248 |
+
}
|
| 249 |
+
});
|
| 250 |
+
|
| 251 |
+
let body_chunk = (match body_chunk_result {
|
| 252 |
+
Err(error) => Err(std::io::Error::other(error)),
|
| 253 |
+
Ok(None) => Ok(None),
|
| 254 |
+
Ok(Some(chunk)) => {
|
| 255 |
+
let wsgi_head_locked = wsgi_head_clone.blocking_lock();
|
| 256 |
+
if !wsgi_head_locked.is_set {
|
| 257 |
+
Err(std::io::Error::other(
|
| 258 |
+
"The \"start_response\" function hasn't been called.",
|
| 259 |
+
))
|
| 260 |
+
} else {
|
| 261 |
+
Ok(Some(chunk))
|
| 262 |
+
}
|
| 263 |
+
}
|
| 264 |
+
})?;
|
| 265 |
+
|
| 266 |
+
let status_code;
|
| 267 |
+
let headers;
|
| 268 |
+
|
| 269 |
+
let mut wsgi_head_locked = wsgi_head_clone.blocking_lock();
|
| 270 |
+
if wsgi_head_locked.is_sent {
|
| 271 |
+
status_code = None;
|
| 272 |
+
headers = None;
|
| 273 |
+
} else {
|
| 274 |
+
status_code = Some(wsgi_head_locked.status);
|
| 275 |
+
headers = wsgi_head_locked.headers.take();
|
| 276 |
+
wsgi_head_locked.is_sent = true;
|
| 277 |
+
}
|
| 278 |
+
drop(wsgi_head_locked);
|
| 279 |
+
|
| 280 |
+
if body_chunk.is_none() {
|
| 281 |
+
body_iterators.remove(&application_id);
|
| 282 |
+
}
|
| 283 |
+
|
| 284 |
+
write_ipc_message(
|
| 285 |
+
&mut tx_mutex.blocking_lock(),
|
| 286 |
+
&postcard::to_allocvec::<ProcessPoolToServerMessage>(&ProcessPoolToServerMessage {
|
| 287 |
+
application_id: None,
|
| 288 |
+
status_code,
|
| 289 |
+
headers,
|
| 290 |
+
body_chunk,
|
| 291 |
+
error_log_line: None,
|
| 292 |
+
error_message: None,
|
| 293 |
+
requests_body_chunk: false,
|
| 294 |
+
})?,
|
| 295 |
+
)?
|
| 296 |
+
} else {
|
| 297 |
+
Err(anyhow::anyhow!("The WSGI request wasn't initialized"))?
|
| 298 |
+
}
|
| 299 |
+
} else {
|
| 300 |
+
Err(anyhow::anyhow!("The WSGI request wasn't initialized"))?
|
| 301 |
+
}
|
| 302 |
+
}
|
| 303 |
+
|
| 304 |
+
Ok(())
|
| 305 |
+
})()
|
| 306 |
+
.err()
|
| 307 |
+
{
|
| 308 |
+
if write_ipc_message(
|
| 309 |
+
&mut tx_mutex.blocking_lock(),
|
| 310 |
+
&postcard::to_allocvec::<ProcessPoolToServerMessage>(&ProcessPoolToServerMessage {
|
| 311 |
+
application_id: None,
|
| 312 |
+
status_code: None,
|
| 313 |
+
headers: None,
|
| 314 |
+
body_chunk: None,
|
| 315 |
+
error_log_line: None,
|
| 316 |
+
error_message: Some(error.to_string()),
|
| 317 |
+
requests_body_chunk: false,
|
| 318 |
+
})
|
| 319 |
+
.unwrap_or_default(),
|
| 320 |
+
)
|
| 321 |
+
.is_err()
|
| 322 |
+
{
|
| 323 |
+
break;
|
| 324 |
+
}
|
| 325 |
+
}
|
| 326 |
+
}
|
| 327 |
+
}
|
| 328 |
+
|
| 329 |
+
fn init_wsgi_process_pool(
|
| 330 |
+
wsgi_script_path: PathBuf,
|
| 331 |
+
) -> Result<PreforkedProcessPool, Box<dyn Error + Send + Sync>> {
|
| 332 |
+
let available_parallelism = thread::available_parallelism()?.get();
|
| 333 |
+
// Safety: The function depends on `nix::unistd::fork`, which is executed before any threads are spawned.
|
| 334 |
+
// The forking function is safe to call for single-threaded applications.
|
| 335 |
+
unsafe {
|
| 336 |
+
PreforkedProcessPool::new(available_parallelism, move |tx, rx| {
|
| 337 |
+
let wsgi_script_path_clone = wsgi_script_path.clone();
|
| 338 |
+
wsgi_pool_fn(tx, rx, wsgi_script_path_clone)
|
| 339 |
+
})
|
| 340 |
+
}
|
| 341 |
+
}
|
| 342 |
+
|
| 343 |
+
pub fn server_module_init(
|
| 344 |
+
config: &ServerConfig,
|
| 345 |
+
) -> Result<Box<dyn ServerModule + Send + Sync>, Box<dyn Error + Send + Sync>> {
|
| 346 |
+
let mut global_wsgi_process_pool = None;
|
| 347 |
+
let mut host_wsgi_process_pools = Vec::new();
|
| 348 |
+
if let Some(wsgi_process_pool_path) = config["global"]["wsgidApplicationPath"].as_str() {
|
| 349 |
+
global_wsgi_process_pool = Some(Arc::new(init_wsgi_process_pool(PathBuf::from_str(
|
| 350 |
+
wsgi_process_pool_path,
|
| 351 |
+
)?)?));
|
| 352 |
+
}
|
| 353 |
+
let global_wsgi_path = config["global"]["wsgidPath"]
|
| 354 |
+
.as_str()
|
| 355 |
+
.map(|s| s.to_string());
|
| 356 |
+
|
| 357 |
+
if let Some(hosts) = config["hosts"].as_vec() {
|
| 358 |
+
for host_yaml in hosts.iter() {
|
| 359 |
+
let domain = host_yaml["domain"].as_str().map(String::from);
|
| 360 |
+
let ip = host_yaml["ip"].as_str().map(String::from);
|
| 361 |
+
let mut locations = Vec::new();
|
| 362 |
+
if let Some(locations_yaml) = host_yaml["locations"].as_vec() {
|
| 363 |
+
for location_yaml in locations_yaml.iter() {
|
| 364 |
+
if let Some(path_str) = location_yaml["path"].as_str() {
|
| 365 |
+
let path = String::from(path_str);
|
| 366 |
+
if let Some(wsgi_process_pool_path) = location_yaml["wsgidApplicationPath"].as_str() {
|
| 367 |
+
locations.push(WsgidApplicationLocationWrap::new(
|
| 368 |
+
path,
|
| 369 |
+
Arc::new(init_wsgi_process_pool(PathBuf::from_str(
|
| 370 |
+
wsgi_process_pool_path,
|
| 371 |
+
)?)?),
|
| 372 |
+
location_yaml["wsgidPath"].as_str().map(|s| s.to_string()),
|
| 373 |
+
));
|
| 374 |
+
}
|
| 375 |
+
}
|
| 376 |
+
}
|
| 377 |
+
}
|
| 378 |
+
if let Some(wsgi_process_pool_path) = host_yaml["wsgidApplicationPath"].as_str() {
|
| 379 |
+
host_wsgi_process_pools.push(WsgidApplicationWrap::new(
|
| 380 |
+
domain,
|
| 381 |
+
ip,
|
| 382 |
+
Some(Arc::new(init_wsgi_process_pool(PathBuf::from_str(
|
| 383 |
+
wsgi_process_pool_path,
|
| 384 |
+
)?)?)),
|
| 385 |
+
host_yaml["wsgiPath"].as_str().map(|s| s.to_string()),
|
| 386 |
+
locations,
|
| 387 |
+
));
|
| 388 |
+
} else if !locations.is_empty() {
|
| 389 |
+
host_wsgi_process_pools.push(WsgidApplicationWrap::new(
|
| 390 |
+
domain,
|
| 391 |
+
ip,
|
| 392 |
+
None,
|
| 393 |
+
host_yaml["wsgiPath"].as_str().map(|s| s.to_string()),
|
| 394 |
+
locations,
|
| 395 |
+
));
|
| 396 |
+
}
|
| 397 |
+
}
|
| 398 |
+
}
|
| 399 |
+
|
| 400 |
+
Ok(Box::new(WsgidModule::new(
|
| 401 |
+
global_wsgi_process_pool,
|
| 402 |
+
global_wsgi_path,
|
| 403 |
+
Arc::new(host_wsgi_process_pools),
|
| 404 |
+
)))
|
| 405 |
+
}
|
| 406 |
+
|
| 407 |
+
struct WsgidModule {
|
| 408 |
+
global_wsgi_process_pool: Option<Arc<PreforkedProcessPool>>,
|
| 409 |
+
global_wsgi_path: Option<String>,
|
| 410 |
+
host_wsgi_process_pools: Arc<Vec<WsgidApplicationWrap>>,
|
| 411 |
+
}
|
| 412 |
+
|
| 413 |
+
impl WsgidModule {
|
| 414 |
+
fn new(
|
| 415 |
+
global_wsgi_process_pool: Option<Arc<PreforkedProcessPool>>,
|
| 416 |
+
global_wsgi_path: Option<String>,
|
| 417 |
+
host_wsgi_process_pools: Arc<Vec<WsgidApplicationWrap>>,
|
| 418 |
+
) -> Self {
|
| 419 |
+
Self {
|
| 420 |
+
global_wsgi_process_pool,
|
| 421 |
+
global_wsgi_path,
|
| 422 |
+
host_wsgi_process_pools,
|
| 423 |
+
}
|
| 424 |
+
}
|
| 425 |
+
}
|
| 426 |
+
|
| 427 |
+
impl ServerModule for WsgidModule {
|
| 428 |
+
fn get_handlers(&self, handle: Handle) -> Box<dyn ServerModuleHandlers + Send> {
|
| 429 |
+
Box::new(WsgidModuleHandlers {
|
| 430 |
+
handle,
|
| 431 |
+
global_wsgi_process_pool: self.global_wsgi_process_pool.clone(),
|
| 432 |
+
global_wsgi_path: self.global_wsgi_path.clone(),
|
| 433 |
+
host_wsgi_process_pools: self.host_wsgi_process_pools.clone(),
|
| 434 |
+
})
|
| 435 |
+
}
|
| 436 |
+
}
|
| 437 |
+
|
| 438 |
+
struct WsgidModuleHandlers {
|
| 439 |
+
handle: Handle,
|
| 440 |
+
global_wsgi_process_pool: Option<Arc<PreforkedProcessPool>>,
|
| 441 |
+
global_wsgi_path: Option<String>,
|
| 442 |
+
host_wsgi_process_pools: Arc<Vec<WsgidApplicationWrap>>,
|
| 443 |
+
}
|
| 444 |
+
|
| 445 |
+
#[async_trait]
|
| 446 |
+
impl ServerModuleHandlers for WsgidModuleHandlers {
|
| 447 |
+
async fn request_handler(
|
| 448 |
+
&mut self,
|
| 449 |
+
request: RequestData,
|
| 450 |
+
config: &ServerConfig,
|
| 451 |
+
socket_data: &SocketData,
|
| 452 |
+
error_logger: &ErrorLogger,
|
| 453 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 454 |
+
WithRuntime::new(self.handle.clone(), async move {
|
| 455 |
+
let hyper_request = request.get_hyper_request();
|
| 456 |
+
|
| 457 |
+
// Use .take() instead of .clone(), since the values in Options will only be used once.
|
| 458 |
+
let mut wsgi_process_pool = self.global_wsgi_process_pool.take();
|
| 459 |
+
let mut wsgi_path = self.global_wsgi_path.take();
|
| 460 |
+
|
| 461 |
+
// Should have used a HashMap instead of iterating over an array for better performance...
|
| 462 |
+
for host_wsgi_process_pool_wrap in self.host_wsgi_process_pools.iter() {
|
| 463 |
+
if match_hostname(
|
| 464 |
+
match &host_wsgi_process_pool_wrap.domain {
|
| 465 |
+
Some(value) => Some(value as &str),
|
| 466 |
+
None => None,
|
| 467 |
+
},
|
| 468 |
+
match hyper_request.headers().get(header::HOST) {
|
| 469 |
+
Some(value) => value.to_str().ok(),
|
| 470 |
+
None => None,
|
| 471 |
+
},
|
| 472 |
+
) && match &host_wsgi_process_pool_wrap.ip {
|
| 473 |
+
Some(value) => ip_match(value as &str, socket_data.remote_addr.ip()),
|
| 474 |
+
None => true,
|
| 475 |
+
} {
|
| 476 |
+
wsgi_process_pool = host_wsgi_process_pool_wrap.wsgi_process_pool.clone();
|
| 477 |
+
wsgi_path = host_wsgi_process_pool_wrap.wsgi_path.clone();
|
| 478 |
+
if let Ok(path_decoded) = urlencoding::decode(
|
| 479 |
+
request
|
| 480 |
+
.get_original_url()
|
| 481 |
+
.unwrap_or(request.get_hyper_request().uri())
|
| 482 |
+
.path(),
|
| 483 |
+
) {
|
| 484 |
+
for location_wrap in host_wsgi_process_pool_wrap.locations.iter() {
|
| 485 |
+
if match_location(&location_wrap.path, &path_decoded) {
|
| 486 |
+
wsgi_process_pool = Some(location_wrap.wsgi_process_pool.clone());
|
| 487 |
+
wsgi_path = location_wrap.wsgi_path.clone();
|
| 488 |
+
break;
|
| 489 |
+
}
|
| 490 |
+
}
|
| 491 |
+
}
|
| 492 |
+
break;
|
| 493 |
+
}
|
| 494 |
+
}
|
| 495 |
+
|
| 496 |
+
let request_path = hyper_request.uri().path();
|
| 497 |
+
let mut request_path_bytes = request_path.bytes();
|
| 498 |
+
if request_path_bytes.len() < 1 || request_path_bytes.nth(0) != Some(b'/') {
|
| 499 |
+
return Ok(
|
| 500 |
+
ResponseData::builder(request)
|
| 501 |
+
.status(StatusCode::BAD_REQUEST)
|
| 502 |
+
.build(),
|
| 503 |
+
);
|
| 504 |
+
}
|
| 505 |
+
|
| 506 |
+
if let Some(wsgi_process_pool) = wsgi_process_pool {
|
| 507 |
+
let wsgi_path = wsgi_path.unwrap_or("/".to_string());
|
| 508 |
+
let mut canonical_wsgi_path: &str = &wsgi_path;
|
| 509 |
+
if canonical_wsgi_path.bytes().last() == Some(b'/') {
|
| 510 |
+
canonical_wsgi_path = &canonical_wsgi_path[..(canonical_wsgi_path.len() - 1)];
|
| 511 |
+
}
|
| 512 |
+
|
| 513 |
+
let request_path_with_slashes = match request_path == canonical_wsgi_path {
|
| 514 |
+
true => format!("{}/", request_path),
|
| 515 |
+
false => request_path.to_string(),
|
| 516 |
+
};
|
| 517 |
+
if let Some(stripped_request_path) =
|
| 518 |
+
request_path_with_slashes.strip_prefix(canonical_wsgi_path)
|
| 519 |
+
{
|
| 520 |
+
let wwwroot_yaml = &config["wwwroot"];
|
| 521 |
+
let wwwroot = wwwroot_yaml.as_str().unwrap_or("/nonexistent");
|
| 522 |
+
|
| 523 |
+
let wwwroot_unknown = PathBuf::from(wwwroot);
|
| 524 |
+
let wwwroot_pathbuf = match wwwroot_unknown.as_path().is_absolute() {
|
| 525 |
+
true => wwwroot_unknown,
|
| 526 |
+
false => match fs::canonicalize(&wwwroot_unknown).await {
|
| 527 |
+
Ok(pathbuf) => pathbuf,
|
| 528 |
+
Err(_) => wwwroot_unknown,
|
| 529 |
+
},
|
| 530 |
+
};
|
| 531 |
+
let wwwroot = wwwroot_pathbuf.as_path();
|
| 532 |
+
|
| 533 |
+
let mut relative_path = &request_path[1..];
|
| 534 |
+
while relative_path.as_bytes().first().copied() == Some(b'/') {
|
| 535 |
+
relative_path = &relative_path[1..];
|
| 536 |
+
}
|
| 537 |
+
|
| 538 |
+
let decoded_relative_path = match urlencoding::decode(relative_path) {
|
| 539 |
+
Ok(path) => path.to_string(),
|
| 540 |
+
Err(_) => {
|
| 541 |
+
return Ok(
|
| 542 |
+
ResponseData::builder(request)
|
| 543 |
+
.status(StatusCode::BAD_REQUEST)
|
| 544 |
+
.build(),
|
| 545 |
+
);
|
| 546 |
+
}
|
| 547 |
+
};
|
| 548 |
+
|
| 549 |
+
let joined_pathbuf = wwwroot.join(decoded_relative_path);
|
| 550 |
+
let execute_pathbuf = joined_pathbuf;
|
| 551 |
+
let execute_path_info = stripped_request_path
|
| 552 |
+
.strip_prefix("/")
|
| 553 |
+
.map(|s| s.to_string());
|
| 554 |
+
|
| 555 |
+
return execute_wsgi_with_environment_variables(
|
| 556 |
+
request,
|
| 557 |
+
socket_data,
|
| 558 |
+
error_logger,
|
| 559 |
+
wwwroot,
|
| 560 |
+
execute_pathbuf,
|
| 561 |
+
execute_path_info,
|
| 562 |
+
config["serverAdministratorEmail"].as_str(),
|
| 563 |
+
wsgi_process_pool,
|
| 564 |
+
)
|
| 565 |
+
.await;
|
| 566 |
+
}
|
| 567 |
+
}
|
| 568 |
+
Ok(ResponseData::builder(request).build())
|
| 569 |
+
})
|
| 570 |
+
.await
|
| 571 |
+
}
|
| 572 |
+
|
| 573 |
+
async fn proxy_request_handler(
|
| 574 |
+
&mut self,
|
| 575 |
+
request: RequestData,
|
| 576 |
+
_config: &ServerConfig,
|
| 577 |
+
_socket_data: &SocketData,
|
| 578 |
+
_error_logger: &ErrorLogger,
|
| 579 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 580 |
+
Ok(ResponseData::builder(request).build())
|
| 581 |
+
}
|
| 582 |
+
|
| 583 |
+
async fn response_modifying_handler(
|
| 584 |
+
&mut self,
|
| 585 |
+
response: HyperResponse,
|
| 586 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 587 |
+
Ok(response)
|
| 588 |
+
}
|
| 589 |
+
|
| 590 |
+
async fn proxy_response_modifying_handler(
|
| 591 |
+
&mut self,
|
| 592 |
+
response: HyperResponse,
|
| 593 |
+
) -> Result<HyperResponse, Box<dyn Error + Send + Sync>> {
|
| 594 |
+
Ok(response)
|
| 595 |
+
}
|
| 596 |
+
|
| 597 |
+
async fn connect_proxy_request_handler(
|
| 598 |
+
&mut self,
|
| 599 |
+
_upgraded_request: HyperUpgraded,
|
| 600 |
+
_connect_address: &str,
|
| 601 |
+
_config: &ServerConfig,
|
| 602 |
+
_socket_data: &SocketData,
|
| 603 |
+
_error_logger: &ErrorLogger,
|
| 604 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 605 |
+
Ok(())
|
| 606 |
+
}
|
| 607 |
+
|
| 608 |
+
fn does_connect_proxy_requests(&mut self) -> bool {
|
| 609 |
+
false
|
| 610 |
+
}
|
| 611 |
+
|
| 612 |
+
async fn websocket_request_handler(
|
| 613 |
+
&mut self,
|
| 614 |
+
_websocket: HyperWebsocket,
|
| 615 |
+
_uri: &hyper::Uri,
|
| 616 |
+
_config: &ServerConfig,
|
| 617 |
+
_socket_data: &SocketData,
|
| 618 |
+
_error_logger: &ErrorLogger,
|
| 619 |
+
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
| 620 |
+
Ok(())
|
| 621 |
+
}
|
| 622 |
+
|
| 623 |
+
fn does_websocket_requests(&mut self, _config: &ServerConfig, _socket_data: &SocketData) -> bool {
|
| 624 |
+
false
|
| 625 |
+
}
|
| 626 |
+
}
|
| 627 |
+
|
| 628 |
+
struct ResponseHeadHyper {
|
| 629 |
+
status: StatusCode,
|
| 630 |
+
headers: Option<HeaderMap>,
|
| 631 |
+
}
|
| 632 |
+
|
| 633 |
+
impl ResponseHeadHyper {
|
| 634 |
+
fn new() -> Self {
|
| 635 |
+
Self {
|
| 636 |
+
status: StatusCode::OK,
|
| 637 |
+
headers: None,
|
| 638 |
+
}
|
| 639 |
+
}
|
| 640 |
+
}
|
| 641 |
+
|
| 642 |
+
#[allow(clippy::too_many_arguments)]
|
| 643 |
+
async fn execute_wsgi_with_environment_variables(
|
| 644 |
+
request: RequestData,
|
| 645 |
+
socket_data: &SocketData,
|
| 646 |
+
error_logger: &ErrorLogger,
|
| 647 |
+
wwwroot: &Path,
|
| 648 |
+
execute_pathbuf: PathBuf,
|
| 649 |
+
path_info: Option<String>,
|
| 650 |
+
server_administrator_email: Option<&str>,
|
| 651 |
+
wsgi_process_pool: Arc<PreforkedProcessPool>,
|
| 652 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 653 |
+
let mut environment_variables: LinkedHashMap<String, String> = LinkedHashMap::new();
|
| 654 |
+
|
| 655 |
+
let hyper_request = request.get_hyper_request();
|
| 656 |
+
let original_request_uri = request.get_original_url().unwrap_or(hyper_request.uri());
|
| 657 |
+
|
| 658 |
+
if let Some(auth_user) = request.get_auth_user() {
|
| 659 |
+
if let Some(authorization) = hyper_request.headers().get(header::AUTHORIZATION) {
|
| 660 |
+
let authorization_value = String::from_utf8_lossy(authorization.as_bytes()).to_string();
|
| 661 |
+
let mut authorization_value_split = authorization_value.split(" ");
|
| 662 |
+
if let Some(authorization_type) = authorization_value_split.next() {
|
| 663 |
+
environment_variables.insert("AUTH_TYPE".to_string(), authorization_type.to_string());
|
| 664 |
+
}
|
| 665 |
+
}
|
| 666 |
+
environment_variables.insert("REMOTE_USER".to_string(), auth_user.to_string());
|
| 667 |
+
}
|
| 668 |
+
|
| 669 |
+
environment_variables.insert(
|
| 670 |
+
"QUERY_STRING".to_string(),
|
| 671 |
+
match hyper_request.uri().query() {
|
| 672 |
+
Some(query) => query.to_string(),
|
| 673 |
+
None => "".to_string(),
|
| 674 |
+
},
|
| 675 |
+
);
|
| 676 |
+
|
| 677 |
+
environment_variables.insert("SERVER_SOFTWARE".to_string(), SERVER_SOFTWARE.to_string());
|
| 678 |
+
environment_variables.insert(
|
| 679 |
+
"SERVER_PROTOCOL".to_string(),
|
| 680 |
+
match hyper_request.version() {
|
| 681 |
+
hyper::Version::HTTP_09 => "HTTP/0.9".to_string(),
|
| 682 |
+
hyper::Version::HTTP_10 => "HTTP/1.0".to_string(),
|
| 683 |
+
hyper::Version::HTTP_11 => "HTTP/1.1".to_string(),
|
| 684 |
+
hyper::Version::HTTP_2 => "HTTP/2.0".to_string(),
|
| 685 |
+
hyper::Version::HTTP_3 => "HTTP/3.0".to_string(),
|
| 686 |
+
_ => "HTTP/Unknown".to_string(),
|
| 687 |
+
},
|
| 688 |
+
);
|
| 689 |
+
environment_variables.insert(
|
| 690 |
+
"SERVER_PORT".to_string(),
|
| 691 |
+
socket_data.local_addr.port().to_string(),
|
| 692 |
+
);
|
| 693 |
+
environment_variables.insert(
|
| 694 |
+
"SERVER_ADDR".to_string(),
|
| 695 |
+
socket_data.local_addr.ip().to_canonical().to_string(),
|
| 696 |
+
);
|
| 697 |
+
if let Some(server_administrator_email) = server_administrator_email {
|
| 698 |
+
environment_variables.insert(
|
| 699 |
+
"SERVER_ADMIN".to_string(),
|
| 700 |
+
server_administrator_email.to_string(),
|
| 701 |
+
);
|
| 702 |
+
}
|
| 703 |
+
if let Some(host) = hyper_request.headers().get(header::HOST) {
|
| 704 |
+
environment_variables.insert(
|
| 705 |
+
"SERVER_NAME".to_string(),
|
| 706 |
+
String::from_utf8_lossy(host.as_bytes()).to_string(),
|
| 707 |
+
);
|
| 708 |
+
}
|
| 709 |
+
|
| 710 |
+
environment_variables.insert(
|
| 711 |
+
"DOCUMENT_ROOT".to_string(),
|
| 712 |
+
wwwroot.to_string_lossy().to_string(),
|
| 713 |
+
);
|
| 714 |
+
environment_variables.insert(
|
| 715 |
+
"PATH_INFO".to_string(),
|
| 716 |
+
match &path_info {
|
| 717 |
+
Some(path_info) => format!("/{}", path_info),
|
| 718 |
+
None => "".to_string(),
|
| 719 |
+
},
|
| 720 |
+
);
|
| 721 |
+
environment_variables.insert(
|
| 722 |
+
"PATH_TRANSLATED".to_string(),
|
| 723 |
+
match &path_info {
|
| 724 |
+
Some(path_info) => {
|
| 725 |
+
let mut path_translated = execute_pathbuf.clone();
|
| 726 |
+
path_translated.push(path_info);
|
| 727 |
+
path_translated.to_string_lossy().to_string()
|
| 728 |
+
}
|
| 729 |
+
None => "".to_string(),
|
| 730 |
+
},
|
| 731 |
+
);
|
| 732 |
+
environment_variables.insert(
|
| 733 |
+
"REQUEST_METHOD".to_string(),
|
| 734 |
+
hyper_request.method().to_string(),
|
| 735 |
+
);
|
| 736 |
+
environment_variables.insert("GATEWAY_INTERFACE".to_string(), "CGI/1.1".to_string());
|
| 737 |
+
environment_variables.insert(
|
| 738 |
+
"REQUEST_URI".to_string(),
|
| 739 |
+
format!(
|
| 740 |
+
"{}{}",
|
| 741 |
+
original_request_uri.path(),
|
| 742 |
+
match original_request_uri.query() {
|
| 743 |
+
Some(query) => format!("?{}", query),
|
| 744 |
+
None => String::from(""),
|
| 745 |
+
}
|
| 746 |
+
),
|
| 747 |
+
);
|
| 748 |
+
|
| 749 |
+
environment_variables.insert(
|
| 750 |
+
"REMOTE_PORT".to_string(),
|
| 751 |
+
socket_data.remote_addr.port().to_string(),
|
| 752 |
+
);
|
| 753 |
+
environment_variables.insert(
|
| 754 |
+
"REMOTE_ADDR".to_string(),
|
| 755 |
+
socket_data.remote_addr.ip().to_canonical().to_string(),
|
| 756 |
+
);
|
| 757 |
+
|
| 758 |
+
environment_variables.insert(
|
| 759 |
+
"SCRIPT_FILENAME".to_string(),
|
| 760 |
+
execute_pathbuf.to_string_lossy().to_string(),
|
| 761 |
+
);
|
| 762 |
+
if let Ok(script_path) = execute_pathbuf.as_path().strip_prefix(wwwroot) {
|
| 763 |
+
environment_variables.insert(
|
| 764 |
+
"SCRIPT_NAME".to_string(),
|
| 765 |
+
format!(
|
| 766 |
+
"/{}",
|
| 767 |
+
match cfg!(windows) {
|
| 768 |
+
true => script_path.to_string_lossy().to_string().replace("\\", "/"),
|
| 769 |
+
false => script_path.to_string_lossy().to_string(),
|
| 770 |
+
}
|
| 771 |
+
),
|
| 772 |
+
);
|
| 773 |
+
}
|
| 774 |
+
|
| 775 |
+
if socket_data.encrypted {
|
| 776 |
+
environment_variables.insert("HTTPS".to_string(), "ON".to_string());
|
| 777 |
+
}
|
| 778 |
+
|
| 779 |
+
let mut content_length_set = false;
|
| 780 |
+
for (header_name, header_value) in hyper_request.headers().iter() {
|
| 781 |
+
let env_header_name = match *header_name {
|
| 782 |
+
header::CONTENT_LENGTH => {
|
| 783 |
+
content_length_set = true;
|
| 784 |
+
"CONTENT_LENGTH".to_string()
|
| 785 |
+
}
|
| 786 |
+
header::CONTENT_TYPE => "CONTENT_TYPE".to_string(),
|
| 787 |
+
_ => {
|
| 788 |
+
let mut result = String::new();
|
| 789 |
+
|
| 790 |
+
result.push_str("HTTP_");
|
| 791 |
+
|
| 792 |
+
for c in header_name.as_str().to_uppercase().chars() {
|
| 793 |
+
if c.is_alphanumeric() {
|
| 794 |
+
result.push(c);
|
| 795 |
+
} else {
|
| 796 |
+
result.push('_');
|
| 797 |
+
}
|
| 798 |
+
}
|
| 799 |
+
|
| 800 |
+
result
|
| 801 |
+
}
|
| 802 |
+
};
|
| 803 |
+
if environment_variables.contains_key(&env_header_name) {
|
| 804 |
+
let value = environment_variables.get_mut(&env_header_name);
|
| 805 |
+
if let Some(value) = value {
|
| 806 |
+
if env_header_name == "HTTP_COOKIE" {
|
| 807 |
+
value.push_str("; ");
|
| 808 |
+
} else {
|
| 809 |
+
// See https://stackoverflow.com/a/1801191
|
| 810 |
+
value.push_str(", ");
|
| 811 |
+
}
|
| 812 |
+
value.push_str(String::from_utf8_lossy(header_value.as_bytes()).as_ref());
|
| 813 |
+
} else {
|
| 814 |
+
environment_variables.insert(
|
| 815 |
+
env_header_name,
|
| 816 |
+
String::from_utf8_lossy(header_value.as_bytes()).to_string(),
|
| 817 |
+
);
|
| 818 |
+
}
|
| 819 |
+
} else {
|
| 820 |
+
environment_variables.insert(
|
| 821 |
+
env_header_name,
|
| 822 |
+
String::from_utf8_lossy(header_value.as_bytes()).to_string(),
|
| 823 |
+
);
|
| 824 |
+
}
|
| 825 |
+
}
|
| 826 |
+
|
| 827 |
+
if !content_length_set {
|
| 828 |
+
environment_variables.insert("CONTENT_LENGTH".to_string(), "0".to_string());
|
| 829 |
+
}
|
| 830 |
+
|
| 831 |
+
let (hyper_request, _, _) = request.into_parts();
|
| 832 |
+
|
| 833 |
+
execute_wsgi(
|
| 834 |
+
hyper_request,
|
| 835 |
+
error_logger,
|
| 836 |
+
wsgi_process_pool,
|
| 837 |
+
environment_variables,
|
| 838 |
+
)
|
| 839 |
+
.await
|
| 840 |
+
}
|
| 841 |
+
|
| 842 |
+
async fn execute_wsgi(
|
| 843 |
+
hyper_request: HyperRequest,
|
| 844 |
+
error_logger: &ErrorLogger,
|
| 845 |
+
wsgi_process_pool: Arc<PreforkedProcessPool>,
|
| 846 |
+
environment_variables: LinkedHashMap<String, String>,
|
| 847 |
+
) -> Result<ResponseData, Box<dyn Error + Send + Sync>> {
|
| 848 |
+
let ipc_mutex = wsgi_process_pool
|
| 849 |
+
.obtain_process_with_init_async_ipc()
|
| 850 |
+
.await?;
|
| 851 |
+
let (_, body) = hyper_request.into_parts();
|
| 852 |
+
let mut body_stream = body.into_data_stream().map_err(std::io::Error::other);
|
| 853 |
+
let application_id = {
|
| 854 |
+
let (tx, rx) = &mut *ipc_mutex.lock().await;
|
| 855 |
+
write_ipc_message_async(
|
| 856 |
+
tx,
|
| 857 |
+
&postcard::to_allocvec(&ServerToProcessPoolMessage {
|
| 858 |
+
application_id: None,
|
| 859 |
+
environment_variables: Some(environment_variables),
|
| 860 |
+
body_chunk: None,
|
| 861 |
+
body_error_message: None,
|
| 862 |
+
requests_body_chunk: false,
|
| 863 |
+
})?,
|
| 864 |
+
)
|
| 865 |
+
.await?;
|
| 866 |
+
|
| 867 |
+
let application_id;
|
| 868 |
+
loop {
|
| 869 |
+
let received_message =
|
| 870 |
+
postcard::from_bytes::<ProcessPoolToServerMessage>(&read_ipc_message_async(rx).await?)?;
|
| 871 |
+
|
| 872 |
+
if let Some(error_message) = received_message.error_message {
|
| 873 |
+
Err(anyhow::anyhow!(error_message))?
|
| 874 |
+
}
|
| 875 |
+
|
| 876 |
+
if let Some(application_id_obtained) = received_message.application_id {
|
| 877 |
+
application_id = application_id_obtained;
|
| 878 |
+
break;
|
| 879 |
+
}
|
| 880 |
+
|
| 881 |
+
if let Some(error_log_line) = received_message.error_log_line {
|
| 882 |
+
error_logger.log(&error_log_line).await;
|
| 883 |
+
} else if received_message.requests_body_chunk {
|
| 884 |
+
let body_chunk;
|
| 885 |
+
let body_error_message;
|
| 886 |
+
match body_stream.next().await {
|
| 887 |
+
None => {
|
| 888 |
+
body_chunk = None;
|
| 889 |
+
body_error_message = None;
|
| 890 |
+
}
|
| 891 |
+
Some(Err(err)) => {
|
| 892 |
+
body_chunk = None;
|
| 893 |
+
body_error_message = Some(err.to_string());
|
| 894 |
+
}
|
| 895 |
+
Some(Ok(chunk)) => {
|
| 896 |
+
body_chunk = Some(chunk.to_vec());
|
| 897 |
+
body_error_message = None;
|
| 898 |
+
}
|
| 899 |
+
};
|
| 900 |
+
write_ipc_message_async(
|
| 901 |
+
tx,
|
| 902 |
+
&postcard::to_allocvec(&ServerToProcessPoolMessage {
|
| 903 |
+
application_id: None,
|
| 904 |
+
environment_variables: None,
|
| 905 |
+
body_chunk,
|
| 906 |
+
body_error_message,
|
| 907 |
+
requests_body_chunk: false,
|
| 908 |
+
})?,
|
| 909 |
+
)
|
| 910 |
+
.await?;
|
| 911 |
+
}
|
| 912 |
+
}
|
| 913 |
+
|
| 914 |
+
application_id
|
| 915 |
+
};
|
| 916 |
+
|
| 917 |
+
let wsgi_head = Arc::new(Mutex::new(ResponseHeadHyper::new()));
|
| 918 |
+
let wsgi_head_clone = wsgi_head.clone();
|
| 919 |
+
let error_logger_arc = Arc::new(error_logger.clone());
|
| 920 |
+
let body_stream_mutex = Arc::new(Mutex::new(body_stream));
|
| 921 |
+
let mut response_stream = futures_util::stream::unfold(ipc_mutex, move |ipc_mutex| {
|
| 922 |
+
let wsgi_head_clone = wsgi_head_clone.clone();
|
| 923 |
+
let error_logger_arc_clone = error_logger_arc.clone();
|
| 924 |
+
let body_stream_mutex_clone = body_stream_mutex.clone();
|
| 925 |
+
Box::pin(async move {
|
| 926 |
+
let ipc_mutex_borrowed = &ipc_mutex;
|
| 927 |
+
let chunk_result: Result<Option<Bytes>, Box<dyn Error + Send + Sync>> = async {
|
| 928 |
+
let (tx, rx) = &mut *ipc_mutex_borrowed.lock().await;
|
| 929 |
+
write_ipc_message_async(
|
| 930 |
+
tx,
|
| 931 |
+
&postcard::to_allocvec(&ServerToProcessPoolMessage {
|
| 932 |
+
application_id: Some(application_id),
|
| 933 |
+
environment_variables: None,
|
| 934 |
+
body_chunk: None,
|
| 935 |
+
body_error_message: None,
|
| 936 |
+
requests_body_chunk: true,
|
| 937 |
+
})?,
|
| 938 |
+
)
|
| 939 |
+
.await?;
|
| 940 |
+
|
| 941 |
+
loop {
|
| 942 |
+
let received_message =
|
| 943 |
+
postcard::from_bytes::<ProcessPoolToServerMessage>(&read_ipc_message_async(rx).await?)?;
|
| 944 |
+
|
| 945 |
+
if let Some(error_message) = received_message.error_message {
|
| 946 |
+
Err(anyhow::anyhow!(error_message))?
|
| 947 |
+
} else if let Some(body_chunk) = received_message.body_chunk {
|
| 948 |
+
if let Some(status_code) = received_message.status_code {
|
| 949 |
+
let mut wsgi_head_locked = wsgi_head_clone.lock().await;
|
| 950 |
+
wsgi_head_locked.status = StatusCode::from_u16(status_code)?;
|
| 951 |
+
if let Some(headers) = received_message.headers {
|
| 952 |
+
let mut header_map = HeaderMap::new();
|
| 953 |
+
for (key, value) in headers {
|
| 954 |
+
for value in value {
|
| 955 |
+
header_map.append(
|
| 956 |
+
HeaderName::from_str(&key)?,
|
| 957 |
+
HeaderValue::from_bytes(value.as_bytes())?,
|
| 958 |
+
);
|
| 959 |
+
}
|
| 960 |
+
}
|
| 961 |
+
wsgi_head_locked.headers = Some(header_map);
|
| 962 |
+
}
|
| 963 |
+
}
|
| 964 |
+
return Ok(Some(Bytes::from(body_chunk)));
|
| 965 |
+
} else if let Some(error_log_line) = received_message.error_log_line {
|
| 966 |
+
error_logger_arc_clone.log(&error_log_line).await;
|
| 967 |
+
} else if received_message.requests_body_chunk {
|
| 968 |
+
let body_chunk;
|
| 969 |
+
let body_error_message;
|
| 970 |
+
match body_stream_mutex_clone.lock().await.next().await {
|
| 971 |
+
None => {
|
| 972 |
+
body_chunk = None;
|
| 973 |
+
body_error_message = None;
|
| 974 |
+
}
|
| 975 |
+
Some(Err(err)) => {
|
| 976 |
+
body_chunk = None;
|
| 977 |
+
body_error_message = Some(err.to_string());
|
| 978 |
+
}
|
| 979 |
+
Some(Ok(chunk)) => {
|
| 980 |
+
body_chunk = Some(chunk.to_vec());
|
| 981 |
+
body_error_message = None;
|
| 982 |
+
}
|
| 983 |
+
};
|
| 984 |
+
write_ipc_message_async(
|
| 985 |
+
tx,
|
| 986 |
+
&postcard::to_allocvec(&ServerToProcessPoolMessage {
|
| 987 |
+
application_id: None,
|
| 988 |
+
environment_variables: None,
|
| 989 |
+
body_chunk,
|
| 990 |
+
body_error_message,
|
| 991 |
+
requests_body_chunk: false,
|
| 992 |
+
})?,
|
| 993 |
+
)
|
| 994 |
+
.await?;
|
| 995 |
+
} else {
|
| 996 |
+
return Ok(None);
|
| 997 |
+
}
|
| 998 |
+
}
|
| 999 |
+
}
|
| 1000 |
+
.await;
|
| 1001 |
+
|
| 1002 |
+
match chunk_result {
|
| 1003 |
+
Err(error) => Some((Err(std::io::Error::other(error.to_string())), ipc_mutex)),
|
| 1004 |
+
Ok(None) => None,
|
| 1005 |
+
Ok(Some(chunk)) => Some((Ok(chunk), ipc_mutex)),
|
| 1006 |
+
}
|
| 1007 |
+
})
|
| 1008 |
+
});
|
| 1009 |
+
|
| 1010 |
+
let first_chunk = response_stream.next().await;
|
| 1011 |
+
let response_body = if let Some(Err(first_chunk_error)) = first_chunk {
|
| 1012 |
+
Err(first_chunk_error)?
|
| 1013 |
+
} else if let Some(Ok(first_chunk)) = first_chunk {
|
| 1014 |
+
let response_stream_first_item = futures_util::stream::once(async move { Ok(first_chunk) });
|
| 1015 |
+
let response_stream_combined = response_stream_first_item.chain(response_stream);
|
| 1016 |
+
let stream_body = StreamBody::new(response_stream_combined.map_ok(Frame::data));
|
| 1017 |
+
|
| 1018 |
+
BodyExt::boxed(stream_body)
|
| 1019 |
+
} else {
|
| 1020 |
+
BodyExt::boxed(Empty::new().map_err(|e| match e {}))
|
| 1021 |
+
};
|
| 1022 |
+
|
| 1023 |
+
let mut wsgi_head_locked = wsgi_head.lock().await;
|
| 1024 |
+
let mut hyper_response = Response::new(response_body);
|
| 1025 |
+
*hyper_response.status_mut() = wsgi_head_locked.status;
|
| 1026 |
+
if let Some(headers) = wsgi_head_locked.headers.take() {
|
| 1027 |
+
*hyper_response.headers_mut() = headers;
|
| 1028 |
+
}
|
| 1029 |
+
|
| 1030 |
+
Ok(
|
| 1031 |
+
ResponseData::builder_without_request()
|
| 1032 |
+
.response(hyper_response)
|
| 1033 |
+
.build(),
|
| 1034 |
+
)
|
| 1035 |
+
}
|
ferron/src/request_handler.rs
ADDED
|
@@ -0,0 +1,2211 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
use std::convert::Infallible;
|
| 2 |
+
use std::net::{IpAddr, SocketAddr};
|
| 3 |
+
use std::str::FromStr;
|
| 4 |
+
use std::sync::Arc;
|
| 5 |
+
use std::time::Duration;
|
| 6 |
+
|
| 7 |
+
use crate::ferron_res::server_software::SERVER_SOFTWARE;
|
| 8 |
+
use crate::ferron_util::combine_config::combine_config;
|
| 9 |
+
use crate::ferron_util::error_pages::generate_default_error_page;
|
| 10 |
+
use crate::ferron_util::url_sanitizer::sanitize_url;
|
| 11 |
+
|
| 12 |
+
use crate::ferron_common::{
|
| 13 |
+
ErrorLogger, LogMessage, RequestData, ServerModuleHandlers, SocketData,
|
| 14 |
+
};
|
| 15 |
+
use async_channel::Sender;
|
| 16 |
+
use chrono::prelude::*;
|
| 17 |
+
use futures_util::TryStreamExt;
|
| 18 |
+
use http::header::CONTENT_TYPE;
|
| 19 |
+
use http_body_util::combinators::BoxBody;
|
| 20 |
+
use http_body_util::{BodyExt, Empty, Full, StreamBody};
|
| 21 |
+
use hyper::body::{Body, Bytes, Frame};
|
| 22 |
+
use hyper::header::{self, HeaderName, HeaderValue};
|
| 23 |
+
use hyper::{HeaderMap, Method, Request, Response, StatusCode};
|
| 24 |
+
use hyper_tungstenite::is_upgrade_request;
|
| 25 |
+
use rustls_acme::ResolvesServerCertAcme;
|
| 26 |
+
use tokio::fs;
|
| 27 |
+
use tokio::io::BufReader;
|
| 28 |
+
use tokio::time::timeout;
|
| 29 |
+
use tokio_util::io::ReaderStream;
|
| 30 |
+
use yaml_rust2::Yaml;
|
| 31 |
+
|
| 32 |
+
async fn generate_error_response(
|
| 33 |
+
status_code: StatusCode,
|
| 34 |
+
config: &Yaml,
|
| 35 |
+
headers: &Option<HeaderMap>,
|
| 36 |
+
) -> Response<BoxBody<Bytes, std::io::Error>> {
|
| 37 |
+
let bare_body =
|
| 38 |
+
generate_default_error_page(status_code, config["serverAdministratorEmail"].as_str());
|
| 39 |
+
let mut content_length: Option<u64> = bare_body.len().try_into().ok();
|
| 40 |
+
let mut response_body = Full::new(Bytes::from(bare_body))
|
| 41 |
+
.map_err(|e| match e {})
|
| 42 |
+
.boxed();
|
| 43 |
+
|
| 44 |
+
if let Some(error_pages) = config["errorPages"].as_vec() {
|
| 45 |
+
for error_page_yaml in error_pages {
|
| 46 |
+
if let Some(page_status_code) = error_page_yaml["scode"].as_i64() {
|
| 47 |
+
let page_status_code = match StatusCode::from_u16(match page_status_code.try_into() {
|
| 48 |
+
Ok(status_code) => status_code,
|
| 49 |
+
Err(_) => continue,
|
| 50 |
+
}) {
|
| 51 |
+
Ok(status_code) => status_code,
|
| 52 |
+
Err(_) => continue,
|
| 53 |
+
};
|
| 54 |
+
if status_code != page_status_code {
|
| 55 |
+
continue;
|
| 56 |
+
}
|
| 57 |
+
if let Some(page_path) = error_page_yaml["path"].as_str() {
|
| 58 |
+
let file = fs::File::open(page_path).await;
|
| 59 |
+
|
| 60 |
+
let file = match file {
|
| 61 |
+
Ok(file) => file,
|
| 62 |
+
Err(_) => continue,
|
| 63 |
+
};
|
| 64 |
+
|
| 65 |
+
content_length = match file.metadata().await {
|
| 66 |
+
Ok(metadata) => Some(metadata.len()),
|
| 67 |
+
Err(_) => None,
|
| 68 |
+
};
|
| 69 |
+
|
| 70 |
+
// Use BufReader for better performance.
|
| 71 |
+
let reader_stream = ReaderStream::new(BufReader::with_capacity(12800, file));
|
| 72 |
+
|
| 73 |
+
let stream_body = StreamBody::new(reader_stream.map_ok(Frame::data));
|
| 74 |
+
let boxed_body = stream_body.boxed();
|
| 75 |
+
|
| 76 |
+
response_body = boxed_body;
|
| 77 |
+
|
| 78 |
+
break;
|
| 79 |
+
}
|
| 80 |
+
}
|
| 81 |
+
}
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
let mut response_builder = Response::builder().status(status_code);
|
| 85 |
+
|
| 86 |
+
if let Some(headers) = headers {
|
| 87 |
+
let headers_iter = headers.iter();
|
| 88 |
+
for (name, value) in headers_iter {
|
| 89 |
+
if name != header::CONTENT_TYPE && name != header::CONTENT_LENGTH {
|
| 90 |
+
response_builder = response_builder.header(name, value);
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
if let Some(content_length) = content_length {
|
| 96 |
+
response_builder = response_builder.header(header::CONTENT_LENGTH, content_length);
|
| 97 |
+
}
|
| 98 |
+
response_builder = response_builder.header(header::CONTENT_TYPE, "text/html");
|
| 99 |
+
|
| 100 |
+
response_builder.body(response_body).unwrap_or_default()
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
#[allow(clippy::too_many_arguments)]
|
| 104 |
+
async fn log_combined(
|
| 105 |
+
logger: &Sender<LogMessage>,
|
| 106 |
+
client_ip: IpAddr,
|
| 107 |
+
auth_user: Option<String>,
|
| 108 |
+
method: String,
|
| 109 |
+
request_path: String,
|
| 110 |
+
protocol: String,
|
| 111 |
+
status_code: u16,
|
| 112 |
+
content_length: Option<u64>,
|
| 113 |
+
referrer: Option<String>,
|
| 114 |
+
user_agent: Option<String>,
|
| 115 |
+
) {
|
| 116 |
+
let now: DateTime<Local> = Local::now();
|
| 117 |
+
let formatted_time = now.format("%d/%b/%Y:%H:%M:%S %z").to_string();
|
| 118 |
+
logger
|
| 119 |
+
.send(LogMessage::new(
|
| 120 |
+
format!(
|
| 121 |
+
"{} - {} [{}] \"{} {} {}\" {} {} {} {}",
|
| 122 |
+
client_ip,
|
| 123 |
+
match auth_user {
|
| 124 |
+
Some(auth_user) => auth_user,
|
| 125 |
+
None => String::from("-"),
|
| 126 |
+
},
|
| 127 |
+
formatted_time,
|
| 128 |
+
method,
|
| 129 |
+
request_path,
|
| 130 |
+
protocol,
|
| 131 |
+
status_code,
|
| 132 |
+
match content_length {
|
| 133 |
+
Some(content_length) => format!("{}", content_length),
|
| 134 |
+
None => String::from("-"),
|
| 135 |
+
},
|
| 136 |
+
match referrer {
|
| 137 |
+
Some(referrer) => format!(
|
| 138 |
+
"\"{}\"",
|
| 139 |
+
referrer.replace("\\", "\\\\").replace("\"", "\\\"")
|
| 140 |
+
),
|
| 141 |
+
None => String::from("-"),
|
| 142 |
+
},
|
| 143 |
+
match user_agent {
|
| 144 |
+
Some(user_agent) => format!(
|
| 145 |
+
"\"{}\"",
|
| 146 |
+
user_agent.replace("\\", "\\\\").replace("\"", "\\\"")
|
| 147 |
+
),
|
| 148 |
+
None => String::from("-"),
|
| 149 |
+
},
|
| 150 |
+
),
|
| 151 |
+
false,
|
| 152 |
+
))
|
| 153 |
+
.await
|
| 154 |
+
.unwrap_or_default();
|
| 155 |
+
}
|
| 156 |
+
|
| 157 |
+
#[allow(clippy::too_many_arguments)]
|
| 158 |
+
async fn request_handler_wrapped(
|
| 159 |
+
mut request: Request<BoxBody<Bytes, std::io::Error>>,
|
| 160 |
+
remote_address: SocketAddr,
|
| 161 |
+
local_address: SocketAddr,
|
| 162 |
+
encrypted: bool,
|
| 163 |
+
config: Arc<Yaml>,
|
| 164 |
+
logger: Sender<LogMessage>,
|
| 165 |
+
handlers_vec: Vec<Box<dyn ServerModuleHandlers + Send>>,
|
| 166 |
+
acme_http01_resolver_option: Option<Arc<ResolvesServerCertAcme>>,
|
| 167 |
+
http3_alt_port: Option<u16>,
|
| 168 |
+
) -> Result<Response<BoxBody<Bytes, std::io::Error>>, Infallible> {
|
| 169 |
+
let is_proxy_request = match request.version() {
|
| 170 |
+
hyper::Version::HTTP_2 | hyper::Version::HTTP_3 => {
|
| 171 |
+
request.method() == hyper::Method::CONNECT && request.uri().host().is_some()
|
| 172 |
+
}
|
| 173 |
+
_ => request.uri().host().is_some(),
|
| 174 |
+
};
|
| 175 |
+
let is_connect_proxy_request = request.method() == hyper::Method::CONNECT;
|
| 176 |
+
|
| 177 |
+
// Collect request data for logging
|
| 178 |
+
let log_method = String::from(request.method().as_str());
|
| 179 |
+
let log_request_path = match is_proxy_request {
|
| 180 |
+
true => request.uri().to_string(),
|
| 181 |
+
false => format!(
|
| 182 |
+
"{}{}",
|
| 183 |
+
request.uri().path(),
|
| 184 |
+
match request.uri().query() {
|
| 185 |
+
Some(query) => format!("?{}", query),
|
| 186 |
+
None => String::from(""),
|
| 187 |
+
}
|
| 188 |
+
),
|
| 189 |
+
};
|
| 190 |
+
let log_protocol = String::from(match request.version() {
|
| 191 |
+
hyper::Version::HTTP_09 => "HTTP/0.9",
|
| 192 |
+
hyper::Version::HTTP_10 => "HTTP/1.0",
|
| 193 |
+
hyper::Version::HTTP_11 => "HTTP/1.1",
|
| 194 |
+
hyper::Version::HTTP_2 => "HTTP/2.0",
|
| 195 |
+
hyper::Version::HTTP_3 => "HTTP/3.0",
|
| 196 |
+
_ => "HTTP/Unknown",
|
| 197 |
+
});
|
| 198 |
+
let log_referrer = match request.headers().get(header::REFERER) {
|
| 199 |
+
Some(header_value) => match header_value.to_str() {
|
| 200 |
+
Ok(header_value) => Some(String::from(header_value)),
|
| 201 |
+
Err(_) => None,
|
| 202 |
+
},
|
| 203 |
+
None => None,
|
| 204 |
+
};
|
| 205 |
+
let log_user_agent = match request.headers().get(header::USER_AGENT) {
|
| 206 |
+
Some(header_value) => match header_value.to_str() {
|
| 207 |
+
Ok(header_value) => Some(String::from(header_value)),
|
| 208 |
+
Err(_) => None,
|
| 209 |
+
},
|
| 210 |
+
None => None,
|
| 211 |
+
};
|
| 212 |
+
let log_enabled = config["global"]["logFilePath"].as_str().is_some();
|
| 213 |
+
let error_log_enabled = config["global"]["errorLogFilePath"].as_str().is_some();
|
| 214 |
+
|
| 215 |
+
// Construct SocketData
|
| 216 |
+
let mut socket_data = SocketData::new(remote_address, local_address, encrypted);
|
| 217 |
+
|
| 218 |
+
match request.version() {
|
| 219 |
+
hyper::Version::HTTP_2 | hyper::Version::HTTP_3 => {
|
| 220 |
+
// Set "Host" request header for HTTP/2 and HTTP/3 connections
|
| 221 |
+
if let Some(authority) = request.uri().authority() {
|
| 222 |
+
let authority = authority.to_owned();
|
| 223 |
+
let headers = request.headers_mut();
|
| 224 |
+
if !headers.contains_key(header::HOST) {
|
| 225 |
+
if let Ok(authority_value) = HeaderValue::from_bytes(authority.as_str().as_bytes()) {
|
| 226 |
+
headers.append(header::HOST, authority_value);
|
| 227 |
+
}
|
| 228 |
+
}
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
// Normalize the Cookie header for HTTP/2 and HTTP/3
|
| 232 |
+
let mut cookie_normalized = String::new();
|
| 233 |
+
let mut cookie_set = false;
|
| 234 |
+
let headers = request.headers_mut();
|
| 235 |
+
for cookie in headers.get_all(header::COOKIE) {
|
| 236 |
+
if let Ok(cookie) = cookie.to_str() {
|
| 237 |
+
if cookie_set {
|
| 238 |
+
cookie_normalized.push_str("; ");
|
| 239 |
+
}
|
| 240 |
+
cookie_set = true;
|
| 241 |
+
cookie_normalized.push_str(cookie);
|
| 242 |
+
}
|
| 243 |
+
}
|
| 244 |
+
if cookie_set {
|
| 245 |
+
if let Ok(cookie_value) = HeaderValue::from_bytes(cookie_normalized.as_bytes()) {
|
| 246 |
+
headers.insert(header::COOKIE, cookie_value);
|
| 247 |
+
}
|
| 248 |
+
}
|
| 249 |
+
}
|
| 250 |
+
_ => (),
|
| 251 |
+
}
|
| 252 |
+
|
| 253 |
+
let host_header_option = request.headers().get(header::HOST);
|
| 254 |
+
if let Some(header_data) = host_header_option {
|
| 255 |
+
match header_data.to_str() {
|
| 256 |
+
Ok(host_header) => {
|
| 257 |
+
let host_header_lower_case = host_header.to_lowercase();
|
| 258 |
+
if host_header_lower_case != *host_header {
|
| 259 |
+
let host_header_value = match HeaderValue::from_str(&host_header_lower_case) {
|
| 260 |
+
Ok(host_header_value) => host_header_value,
|
| 261 |
+
Err(err) => {
|
| 262 |
+
if error_log_enabled {
|
| 263 |
+
logger
|
| 264 |
+
.send(LogMessage::new(
|
| 265 |
+
format!("Host header sanitation error: {}", err),
|
| 266 |
+
true,
|
| 267 |
+
))
|
| 268 |
+
.await
|
| 269 |
+
.unwrap_or_default();
|
| 270 |
+
}
|
| 271 |
+
let response = Response::builder()
|
| 272 |
+
.status(StatusCode::BAD_REQUEST)
|
| 273 |
+
.header(header::CONTENT_TYPE, "text/html")
|
| 274 |
+
.body(
|
| 275 |
+
Full::new(Bytes::from(generate_default_error_page(
|
| 276 |
+
StatusCode::BAD_REQUEST,
|
| 277 |
+
None,
|
| 278 |
+
)))
|
| 279 |
+
.map_err(|e| match e {})
|
| 280 |
+
.boxed(),
|
| 281 |
+
)
|
| 282 |
+
.unwrap_or_default();
|
| 283 |
+
|
| 284 |
+
if log_enabled {
|
| 285 |
+
log_combined(
|
| 286 |
+
&logger,
|
| 287 |
+
socket_data.remote_addr.ip(),
|
| 288 |
+
None,
|
| 289 |
+
log_method,
|
| 290 |
+
log_request_path,
|
| 291 |
+
log_protocol,
|
| 292 |
+
response.status().as_u16(),
|
| 293 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 294 |
+
Some(header_value) => match header_value.to_str() {
|
| 295 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 296 |
+
Ok(content_length) => Some(content_length),
|
| 297 |
+
Err(_) => response.body().size_hint().exact(),
|
| 298 |
+
},
|
| 299 |
+
Err(_) => response.body().size_hint().exact(),
|
| 300 |
+
},
|
| 301 |
+
None => response.body().size_hint().exact(),
|
| 302 |
+
},
|
| 303 |
+
log_referrer,
|
| 304 |
+
log_user_agent,
|
| 305 |
+
)
|
| 306 |
+
.await;
|
| 307 |
+
}
|
| 308 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 309 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 310 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 311 |
+
Some(value) => HeaderValue::from_bytes(
|
| 312 |
+
format!(
|
| 313 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 314 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 315 |
+
http3_alt_port,
|
| 316 |
+
http3_alt_port
|
| 317 |
+
)
|
| 318 |
+
.as_bytes(),
|
| 319 |
+
),
|
| 320 |
+
None => HeaderValue::from_bytes(
|
| 321 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 322 |
+
),
|
| 323 |
+
} {
|
| 324 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 325 |
+
}
|
| 326 |
+
}
|
| 327 |
+
response_parts
|
| 328 |
+
.headers
|
| 329 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 330 |
+
|
| 331 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 332 |
+
}
|
| 333 |
+
};
|
| 334 |
+
|
| 335 |
+
request
|
| 336 |
+
.headers_mut()
|
| 337 |
+
.insert(header::HOST, host_header_value);
|
| 338 |
+
}
|
| 339 |
+
}
|
| 340 |
+
Err(err) => {
|
| 341 |
+
if error_log_enabled {
|
| 342 |
+
logger
|
| 343 |
+
.send(LogMessage::new(
|
| 344 |
+
format!("Host header sanitation error: {}", err),
|
| 345 |
+
true,
|
| 346 |
+
))
|
| 347 |
+
.await
|
| 348 |
+
.unwrap_or_default();
|
| 349 |
+
}
|
| 350 |
+
let response = Response::builder()
|
| 351 |
+
.status(StatusCode::BAD_REQUEST)
|
| 352 |
+
.header(header::CONTENT_TYPE, "text/html")
|
| 353 |
+
.body(
|
| 354 |
+
Full::new(Bytes::from(generate_default_error_page(
|
| 355 |
+
StatusCode::BAD_REQUEST,
|
| 356 |
+
None,
|
| 357 |
+
)))
|
| 358 |
+
.map_err(|e| match e {})
|
| 359 |
+
.boxed(),
|
| 360 |
+
)
|
| 361 |
+
.unwrap_or_default();
|
| 362 |
+
if log_enabled {
|
| 363 |
+
log_combined(
|
| 364 |
+
&logger,
|
| 365 |
+
socket_data.remote_addr.ip(),
|
| 366 |
+
None,
|
| 367 |
+
log_method,
|
| 368 |
+
log_request_path,
|
| 369 |
+
log_protocol,
|
| 370 |
+
response.status().as_u16(),
|
| 371 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 372 |
+
Some(header_value) => match header_value.to_str() {
|
| 373 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 374 |
+
Ok(content_length) => Some(content_length),
|
| 375 |
+
Err(_) => response.body().size_hint().exact(),
|
| 376 |
+
},
|
| 377 |
+
Err(_) => response.body().size_hint().exact(),
|
| 378 |
+
},
|
| 379 |
+
None => response.body().size_hint().exact(),
|
| 380 |
+
},
|
| 381 |
+
log_referrer,
|
| 382 |
+
log_user_agent,
|
| 383 |
+
)
|
| 384 |
+
.await;
|
| 385 |
+
}
|
| 386 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 387 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 388 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 389 |
+
Some(value) => HeaderValue::from_bytes(
|
| 390 |
+
format!(
|
| 391 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 392 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 393 |
+
http3_alt_port,
|
| 394 |
+
http3_alt_port
|
| 395 |
+
)
|
| 396 |
+
.as_bytes(),
|
| 397 |
+
),
|
| 398 |
+
None => HeaderValue::from_bytes(
|
| 399 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 400 |
+
),
|
| 401 |
+
} {
|
| 402 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 403 |
+
}
|
| 404 |
+
}
|
| 405 |
+
response_parts
|
| 406 |
+
.headers
|
| 407 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 408 |
+
|
| 409 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 410 |
+
}
|
| 411 |
+
}
|
| 412 |
+
};
|
| 413 |
+
|
| 414 |
+
// Combine the server configuration
|
| 415 |
+
let combined_config = match combine_config(
|
| 416 |
+
config,
|
| 417 |
+
match is_proxy_request || is_connect_proxy_request {
|
| 418 |
+
false => match request.headers().get(header::HOST) {
|
| 419 |
+
Some(value) => value.to_str().ok(),
|
| 420 |
+
None => None,
|
| 421 |
+
},
|
| 422 |
+
true => None,
|
| 423 |
+
},
|
| 424 |
+
local_address.ip(),
|
| 425 |
+
request.uri().path(),
|
| 426 |
+
) {
|
| 427 |
+
Some(config) => config,
|
| 428 |
+
None => {
|
| 429 |
+
if error_log_enabled {
|
| 430 |
+
logger
|
| 431 |
+
.send(LogMessage::new(
|
| 432 |
+
String::from("Cannot determine server configuration"),
|
| 433 |
+
true,
|
| 434 |
+
))
|
| 435 |
+
.await
|
| 436 |
+
.unwrap_or_default();
|
| 437 |
+
}
|
| 438 |
+
let response = Response::builder()
|
| 439 |
+
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
| 440 |
+
.header(header::CONTENT_TYPE, "text/html")
|
| 441 |
+
.body(
|
| 442 |
+
Full::new(Bytes::from(generate_default_error_page(
|
| 443 |
+
StatusCode::INTERNAL_SERVER_ERROR,
|
| 444 |
+
None,
|
| 445 |
+
)))
|
| 446 |
+
.map_err(|e| match e {})
|
| 447 |
+
.boxed(),
|
| 448 |
+
)
|
| 449 |
+
.unwrap_or_default();
|
| 450 |
+
if log_enabled {
|
| 451 |
+
log_combined(
|
| 452 |
+
&logger,
|
| 453 |
+
socket_data.remote_addr.ip(),
|
| 454 |
+
None,
|
| 455 |
+
log_method,
|
| 456 |
+
log_request_path,
|
| 457 |
+
log_protocol,
|
| 458 |
+
response.status().as_u16(),
|
| 459 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 460 |
+
Some(header_value) => match header_value.to_str() {
|
| 461 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 462 |
+
Ok(content_length) => Some(content_length),
|
| 463 |
+
Err(_) => response.body().size_hint().exact(),
|
| 464 |
+
},
|
| 465 |
+
Err(_) => response.body().size_hint().exact(),
|
| 466 |
+
},
|
| 467 |
+
None => response.body().size_hint().exact(),
|
| 468 |
+
},
|
| 469 |
+
log_referrer,
|
| 470 |
+
log_user_agent,
|
| 471 |
+
)
|
| 472 |
+
.await;
|
| 473 |
+
}
|
| 474 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 475 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 476 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 477 |
+
Some(value) => HeaderValue::from_bytes(
|
| 478 |
+
format!(
|
| 479 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 480 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 481 |
+
http3_alt_port,
|
| 482 |
+
http3_alt_port
|
| 483 |
+
)
|
| 484 |
+
.as_bytes(),
|
| 485 |
+
),
|
| 486 |
+
None => HeaderValue::from_bytes(
|
| 487 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 488 |
+
),
|
| 489 |
+
} {
|
| 490 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 491 |
+
}
|
| 492 |
+
}
|
| 493 |
+
response_parts
|
| 494 |
+
.headers
|
| 495 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 496 |
+
|
| 497 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 498 |
+
}
|
| 499 |
+
};
|
| 500 |
+
|
| 501 |
+
let url_pathname = request.uri().path();
|
| 502 |
+
let sanitized_url_pathname = match sanitize_url(
|
| 503 |
+
url_pathname,
|
| 504 |
+
combined_config["allowDoubleSlashes"]
|
| 505 |
+
.as_bool()
|
| 506 |
+
.unwrap_or_default(),
|
| 507 |
+
) {
|
| 508 |
+
Ok(sanitized_url) => sanitized_url,
|
| 509 |
+
Err(err) => {
|
| 510 |
+
if error_log_enabled {
|
| 511 |
+
logger
|
| 512 |
+
.send(LogMessage::new(
|
| 513 |
+
format!("URL sanitation error: {}", err),
|
| 514 |
+
true,
|
| 515 |
+
))
|
| 516 |
+
.await
|
| 517 |
+
.unwrap_or_default();
|
| 518 |
+
}
|
| 519 |
+
let response =
|
| 520 |
+
generate_error_response(StatusCode::BAD_REQUEST, &combined_config, &None).await;
|
| 521 |
+
if log_enabled {
|
| 522 |
+
log_combined(
|
| 523 |
+
&logger,
|
| 524 |
+
socket_data.remote_addr.ip(),
|
| 525 |
+
None,
|
| 526 |
+
log_method,
|
| 527 |
+
log_request_path,
|
| 528 |
+
log_protocol,
|
| 529 |
+
response.status().as_u16(),
|
| 530 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 531 |
+
Some(header_value) => match header_value.to_str() {
|
| 532 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 533 |
+
Ok(content_length) => Some(content_length),
|
| 534 |
+
Err(_) => response.body().size_hint().exact(),
|
| 535 |
+
},
|
| 536 |
+
Err(_) => response.body().size_hint().exact(),
|
| 537 |
+
},
|
| 538 |
+
None => response.body().size_hint().exact(),
|
| 539 |
+
},
|
| 540 |
+
log_referrer,
|
| 541 |
+
log_user_agent,
|
| 542 |
+
)
|
| 543 |
+
.await;
|
| 544 |
+
}
|
| 545 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 546 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 547 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 548 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 549 |
+
if let Some(header_name) = header_name.as_str() {
|
| 550 |
+
if let Some(header_value) = header_value.as_str() {
|
| 551 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 552 |
+
if let Ok(header_value) =
|
| 553 |
+
HeaderValue::from_str(&header_value.replace("{path}", url_pathname))
|
| 554 |
+
{
|
| 555 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 556 |
+
response_parts.headers.insert(header_name, header_value);
|
| 557 |
+
}
|
| 558 |
+
}
|
| 559 |
+
}
|
| 560 |
+
}
|
| 561 |
+
}
|
| 562 |
+
}
|
| 563 |
+
}
|
| 564 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 565 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 566 |
+
Some(value) => HeaderValue::from_bytes(
|
| 567 |
+
format!(
|
| 568 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 569 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 570 |
+
http3_alt_port,
|
| 571 |
+
http3_alt_port
|
| 572 |
+
)
|
| 573 |
+
.as_bytes(),
|
| 574 |
+
),
|
| 575 |
+
None => HeaderValue::from_bytes(
|
| 576 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 577 |
+
),
|
| 578 |
+
} {
|
| 579 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 580 |
+
}
|
| 581 |
+
}
|
| 582 |
+
response_parts
|
| 583 |
+
.headers
|
| 584 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 585 |
+
|
| 586 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 587 |
+
}
|
| 588 |
+
};
|
| 589 |
+
|
| 590 |
+
if sanitized_url_pathname != url_pathname {
|
| 591 |
+
let (mut parts, body) = request.into_parts();
|
| 592 |
+
let mut url_parts = parts.uri.into_parts();
|
| 593 |
+
url_parts.path_and_query = Some(
|
| 594 |
+
match format!(
|
| 595 |
+
"{}{}",
|
| 596 |
+
sanitized_url_pathname,
|
| 597 |
+
match url_parts.path_and_query {
|
| 598 |
+
Some(path_and_query) => {
|
| 599 |
+
match path_and_query.query() {
|
| 600 |
+
Some(query) => format!("?{}", query),
|
| 601 |
+
None => String::from(""),
|
| 602 |
+
}
|
| 603 |
+
}
|
| 604 |
+
None => String::from(""),
|
| 605 |
+
}
|
| 606 |
+
)
|
| 607 |
+
.parse()
|
| 608 |
+
{
|
| 609 |
+
Ok(path_and_query) => path_and_query,
|
| 610 |
+
Err(err) => {
|
| 611 |
+
if error_log_enabled {
|
| 612 |
+
logger
|
| 613 |
+
.send(LogMessage::new(
|
| 614 |
+
format!("URL sanitation error: {}", err),
|
| 615 |
+
true,
|
| 616 |
+
))
|
| 617 |
+
.await
|
| 618 |
+
.unwrap_or_default();
|
| 619 |
+
}
|
| 620 |
+
let response =
|
| 621 |
+
generate_error_response(StatusCode::BAD_REQUEST, &combined_config, &None).await;
|
| 622 |
+
if log_enabled {
|
| 623 |
+
log_combined(
|
| 624 |
+
&logger,
|
| 625 |
+
socket_data.remote_addr.ip(),
|
| 626 |
+
None,
|
| 627 |
+
log_method,
|
| 628 |
+
log_request_path,
|
| 629 |
+
log_protocol,
|
| 630 |
+
response.status().as_u16(),
|
| 631 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 632 |
+
Some(header_value) => match header_value.to_str() {
|
| 633 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 634 |
+
Ok(content_length) => Some(content_length),
|
| 635 |
+
Err(_) => response.body().size_hint().exact(),
|
| 636 |
+
},
|
| 637 |
+
Err(_) => response.body().size_hint().exact(),
|
| 638 |
+
},
|
| 639 |
+
None => response.body().size_hint().exact(),
|
| 640 |
+
},
|
| 641 |
+
log_referrer,
|
| 642 |
+
log_user_agent,
|
| 643 |
+
)
|
| 644 |
+
.await;
|
| 645 |
+
}
|
| 646 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 647 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 648 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 649 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 650 |
+
if let Some(header_name) = header_name.as_str() {
|
| 651 |
+
if let Some(header_value) = header_value.as_str() {
|
| 652 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 653 |
+
if let Ok(header_value) = HeaderValue::from_str(
|
| 654 |
+
&header_value.replace("{path}", &sanitized_url_pathname),
|
| 655 |
+
) {
|
| 656 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 657 |
+
response_parts.headers.insert(header_name, header_value);
|
| 658 |
+
}
|
| 659 |
+
}
|
| 660 |
+
}
|
| 661 |
+
}
|
| 662 |
+
}
|
| 663 |
+
}
|
| 664 |
+
}
|
| 665 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 666 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 667 |
+
Some(value) => HeaderValue::from_bytes(
|
| 668 |
+
format!(
|
| 669 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 670 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 671 |
+
http3_alt_port,
|
| 672 |
+
http3_alt_port
|
| 673 |
+
)
|
| 674 |
+
.as_bytes(),
|
| 675 |
+
),
|
| 676 |
+
None => HeaderValue::from_bytes(
|
| 677 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 678 |
+
),
|
| 679 |
+
} {
|
| 680 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 681 |
+
}
|
| 682 |
+
}
|
| 683 |
+
response_parts
|
| 684 |
+
.headers
|
| 685 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 686 |
+
|
| 687 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 688 |
+
}
|
| 689 |
+
},
|
| 690 |
+
);
|
| 691 |
+
parts.uri = match hyper::Uri::from_parts(url_parts) {
|
| 692 |
+
Ok(uri) => uri,
|
| 693 |
+
Err(err) => {
|
| 694 |
+
if error_log_enabled {
|
| 695 |
+
logger
|
| 696 |
+
.send(LogMessage::new(
|
| 697 |
+
format!("URL sanitation error: {}", err),
|
| 698 |
+
true,
|
| 699 |
+
))
|
| 700 |
+
.await
|
| 701 |
+
.unwrap_or_default();
|
| 702 |
+
}
|
| 703 |
+
let response =
|
| 704 |
+
generate_error_response(StatusCode::BAD_REQUEST, &combined_config, &None).await;
|
| 705 |
+
if log_enabled {
|
| 706 |
+
log_combined(
|
| 707 |
+
&logger,
|
| 708 |
+
socket_data.remote_addr.ip(),
|
| 709 |
+
None,
|
| 710 |
+
log_method,
|
| 711 |
+
log_request_path,
|
| 712 |
+
log_protocol,
|
| 713 |
+
response.status().as_u16(),
|
| 714 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 715 |
+
Some(header_value) => match header_value.to_str() {
|
| 716 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 717 |
+
Ok(content_length) => Some(content_length),
|
| 718 |
+
Err(_) => response.body().size_hint().exact(),
|
| 719 |
+
},
|
| 720 |
+
Err(_) => response.body().size_hint().exact(),
|
| 721 |
+
},
|
| 722 |
+
None => response.body().size_hint().exact(),
|
| 723 |
+
},
|
| 724 |
+
log_referrer,
|
| 725 |
+
log_user_agent,
|
| 726 |
+
)
|
| 727 |
+
.await;
|
| 728 |
+
}
|
| 729 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 730 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 731 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 732 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 733 |
+
if let Some(header_name) = header_name.as_str() {
|
| 734 |
+
if let Some(header_value) = header_value.as_str() {
|
| 735 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 736 |
+
if let Ok(header_value) =
|
| 737 |
+
HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
|
| 738 |
+
{
|
| 739 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 740 |
+
response_parts.headers.insert(header_name, header_value);
|
| 741 |
+
}
|
| 742 |
+
}
|
| 743 |
+
}
|
| 744 |
+
}
|
| 745 |
+
}
|
| 746 |
+
}
|
| 747 |
+
}
|
| 748 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 749 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 750 |
+
Some(value) => HeaderValue::from_bytes(
|
| 751 |
+
format!(
|
| 752 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 753 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 754 |
+
http3_alt_port,
|
| 755 |
+
http3_alt_port
|
| 756 |
+
)
|
| 757 |
+
.as_bytes(),
|
| 758 |
+
),
|
| 759 |
+
None => HeaderValue::from_bytes(
|
| 760 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 761 |
+
),
|
| 762 |
+
} {
|
| 763 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 764 |
+
}
|
| 765 |
+
}
|
| 766 |
+
response_parts
|
| 767 |
+
.headers
|
| 768 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 769 |
+
|
| 770 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 771 |
+
}
|
| 772 |
+
};
|
| 773 |
+
request = Request::from_parts(parts, body);
|
| 774 |
+
}
|
| 775 |
+
|
| 776 |
+
if request.uri().path() == "*" {
|
| 777 |
+
let response = match request.method() {
|
| 778 |
+
&Method::OPTIONS => Response::builder()
|
| 779 |
+
.status(StatusCode::NO_CONTENT)
|
| 780 |
+
.header(header::ALLOW, "GET, POST, HEAD, OPTIONS")
|
| 781 |
+
.body(Empty::new().map_err(|e| match e {}).boxed())
|
| 782 |
+
.unwrap_or_default(),
|
| 783 |
+
_ => {
|
| 784 |
+
let mut header_map = HeaderMap::new();
|
| 785 |
+
if let Ok(header_value) = HeaderValue::from_str("GET, POST, HEAD, OPTIONS") {
|
| 786 |
+
header_map.insert(header::ALLOW, header_value);
|
| 787 |
+
};
|
| 788 |
+
generate_error_response(StatusCode::BAD_REQUEST, &combined_config, &Some(header_map)).await
|
| 789 |
+
}
|
| 790 |
+
};
|
| 791 |
+
if log_enabled {
|
| 792 |
+
log_combined(
|
| 793 |
+
&logger,
|
| 794 |
+
socket_data.remote_addr.ip(),
|
| 795 |
+
None,
|
| 796 |
+
log_method,
|
| 797 |
+
log_request_path,
|
| 798 |
+
log_protocol,
|
| 799 |
+
response.status().as_u16(),
|
| 800 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 801 |
+
Some(header_value) => match header_value.to_str() {
|
| 802 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 803 |
+
Ok(content_length) => Some(content_length),
|
| 804 |
+
Err(_) => response.body().size_hint().exact(),
|
| 805 |
+
},
|
| 806 |
+
Err(_) => response.body().size_hint().exact(),
|
| 807 |
+
},
|
| 808 |
+
None => response.body().size_hint().exact(),
|
| 809 |
+
},
|
| 810 |
+
log_referrer,
|
| 811 |
+
log_user_agent,
|
| 812 |
+
)
|
| 813 |
+
.await;
|
| 814 |
+
}
|
| 815 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 816 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 817 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 818 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 819 |
+
if let Some(header_name) = header_name.as_str() {
|
| 820 |
+
if let Some(header_value) = header_value.as_str() {
|
| 821 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 822 |
+
if let Ok(header_value) =
|
| 823 |
+
HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
|
| 824 |
+
{
|
| 825 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 826 |
+
response_parts.headers.insert(header_name, header_value);
|
| 827 |
+
}
|
| 828 |
+
}
|
| 829 |
+
}
|
| 830 |
+
}
|
| 831 |
+
}
|
| 832 |
+
}
|
| 833 |
+
}
|
| 834 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 835 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 836 |
+
Some(value) => HeaderValue::from_bytes(
|
| 837 |
+
format!(
|
| 838 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 839 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 840 |
+
http3_alt_port,
|
| 841 |
+
http3_alt_port
|
| 842 |
+
)
|
| 843 |
+
.as_bytes(),
|
| 844 |
+
),
|
| 845 |
+
None => HeaderValue::from_bytes(
|
| 846 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 847 |
+
),
|
| 848 |
+
} {
|
| 849 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 850 |
+
}
|
| 851 |
+
}
|
| 852 |
+
response_parts
|
| 853 |
+
.headers
|
| 854 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 855 |
+
|
| 856 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 857 |
+
}
|
| 858 |
+
|
| 859 |
+
// HTTP-01 ACME challenge for automatic TLS
|
| 860 |
+
if let Some(acme_http01_resolver) = acme_http01_resolver_option {
|
| 861 |
+
if let Some(challenge_token) = request
|
| 862 |
+
.uri()
|
| 863 |
+
.path()
|
| 864 |
+
.strip_prefix("/.well-known/acme-challenge/")
|
| 865 |
+
{
|
| 866 |
+
if let Some(acme_response) = acme_http01_resolver.get_http_01_key_auth(challenge_token) {
|
| 867 |
+
let response = Response::builder()
|
| 868 |
+
.status(StatusCode::OK)
|
| 869 |
+
.header(
|
| 870 |
+
CONTENT_TYPE,
|
| 871 |
+
HeaderValue::from_static("application/octet-stream"),
|
| 872 |
+
)
|
| 873 |
+
.body(
|
| 874 |
+
Full::new(Bytes::from(acme_response))
|
| 875 |
+
.map_err(|e| match e {})
|
| 876 |
+
.boxed(),
|
| 877 |
+
)
|
| 878 |
+
.unwrap_or_default();
|
| 879 |
+
|
| 880 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 881 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 882 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 883 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 884 |
+
if let Some(header_name) = header_name.as_str() {
|
| 885 |
+
if let Some(header_value) = header_value.as_str() {
|
| 886 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 887 |
+
if let Ok(header_value) =
|
| 888 |
+
HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
|
| 889 |
+
{
|
| 890 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 891 |
+
response_parts.headers.insert(header_name, header_value);
|
| 892 |
+
}
|
| 893 |
+
}
|
| 894 |
+
}
|
| 895 |
+
}
|
| 896 |
+
}
|
| 897 |
+
}
|
| 898 |
+
}
|
| 899 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 900 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 901 |
+
Some(value) => HeaderValue::from_bytes(
|
| 902 |
+
format!(
|
| 903 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 904 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 905 |
+
http3_alt_port,
|
| 906 |
+
http3_alt_port
|
| 907 |
+
)
|
| 908 |
+
.as_bytes(),
|
| 909 |
+
),
|
| 910 |
+
None => HeaderValue::from_bytes(
|
| 911 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 912 |
+
),
|
| 913 |
+
} {
|
| 914 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 915 |
+
}
|
| 916 |
+
}
|
| 917 |
+
response_parts
|
| 918 |
+
.headers
|
| 919 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 920 |
+
|
| 921 |
+
let response = Response::from_parts(response_parts, response_body);
|
| 922 |
+
|
| 923 |
+
if log_enabled {
|
| 924 |
+
log_combined(
|
| 925 |
+
&logger,
|
| 926 |
+
socket_data.remote_addr.ip(),
|
| 927 |
+
None,
|
| 928 |
+
log_method,
|
| 929 |
+
log_request_path,
|
| 930 |
+
log_protocol,
|
| 931 |
+
response.status().as_u16(),
|
| 932 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 933 |
+
Some(header_value) => match header_value.to_str() {
|
| 934 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 935 |
+
Ok(content_length) => Some(content_length),
|
| 936 |
+
Err(_) => response.body().size_hint().exact(),
|
| 937 |
+
},
|
| 938 |
+
Err(_) => response.body().size_hint().exact(),
|
| 939 |
+
},
|
| 940 |
+
None => response.body().size_hint().exact(),
|
| 941 |
+
},
|
| 942 |
+
log_referrer,
|
| 943 |
+
log_user_agent,
|
| 944 |
+
)
|
| 945 |
+
.await;
|
| 946 |
+
}
|
| 947 |
+
return Ok(response);
|
| 948 |
+
}
|
| 949 |
+
}
|
| 950 |
+
};
|
| 951 |
+
|
| 952 |
+
let cloned_logger = logger.clone();
|
| 953 |
+
let error_logger = match error_log_enabled {
|
| 954 |
+
true => ErrorLogger::new(cloned_logger),
|
| 955 |
+
false => ErrorLogger::without_logger(),
|
| 956 |
+
};
|
| 957 |
+
|
| 958 |
+
if is_connect_proxy_request {
|
| 959 |
+
let mut connect_proxy_handlers = None;
|
| 960 |
+
for mut handlers in handlers_vec {
|
| 961 |
+
if handlers.does_connect_proxy_requests() {
|
| 962 |
+
connect_proxy_handlers = Some(handlers);
|
| 963 |
+
break;
|
| 964 |
+
}
|
| 965 |
+
}
|
| 966 |
+
|
| 967 |
+
if let Some(mut connect_proxy_handlers) = connect_proxy_handlers {
|
| 968 |
+
if let Some(connect_address) = request.uri().authority().map(|auth| auth.to_string()) {
|
| 969 |
+
// Variables moved to before "tokio::spawn" to avoid issues with moved values
|
| 970 |
+
let client_ip = socket_data.remote_addr.ip();
|
| 971 |
+
let custom_headers_yaml = combined_config["customHeaders"].clone();
|
| 972 |
+
|
| 973 |
+
tokio::spawn(async move {
|
| 974 |
+
match hyper::upgrade::on(request).await {
|
| 975 |
+
Ok(upgraded_request) => {
|
| 976 |
+
let result = connect_proxy_handlers
|
| 977 |
+
.connect_proxy_request_handler(
|
| 978 |
+
upgraded_request,
|
| 979 |
+
&connect_address,
|
| 980 |
+
&combined_config,
|
| 981 |
+
&socket_data,
|
| 982 |
+
&error_logger,
|
| 983 |
+
)
|
| 984 |
+
.await;
|
| 985 |
+
match result {
|
| 986 |
+
Ok(_) => (),
|
| 987 |
+
Err(err) => {
|
| 988 |
+
error_logger
|
| 989 |
+
.log(&format!("Unexpected error for CONNECT request: {}", err))
|
| 990 |
+
.await;
|
| 991 |
+
}
|
| 992 |
+
}
|
| 993 |
+
}
|
| 994 |
+
Err(err) => {
|
| 995 |
+
error_logger
|
| 996 |
+
.log(&format!(
|
| 997 |
+
"Error while upgrading HTTP CONNECT request: {}",
|
| 998 |
+
err
|
| 999 |
+
))
|
| 1000 |
+
.await
|
| 1001 |
+
}
|
| 1002 |
+
}
|
| 1003 |
+
});
|
| 1004 |
+
|
| 1005 |
+
let response = Response::builder()
|
| 1006 |
+
.body(Empty::new().map_err(|e| match e {}).boxed())
|
| 1007 |
+
.unwrap_or_default();
|
| 1008 |
+
|
| 1009 |
+
if log_enabled {
|
| 1010 |
+
log_combined(
|
| 1011 |
+
&logger,
|
| 1012 |
+
client_ip,
|
| 1013 |
+
None,
|
| 1014 |
+
log_method,
|
| 1015 |
+
log_request_path,
|
| 1016 |
+
log_protocol,
|
| 1017 |
+
response.status().as_u16(),
|
| 1018 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 1019 |
+
Some(header_value) => match header_value.to_str() {
|
| 1020 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 1021 |
+
Ok(content_length) => Some(content_length),
|
| 1022 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1023 |
+
},
|
| 1024 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1025 |
+
},
|
| 1026 |
+
None => response.body().size_hint().exact(),
|
| 1027 |
+
},
|
| 1028 |
+
log_referrer,
|
| 1029 |
+
log_user_agent,
|
| 1030 |
+
)
|
| 1031 |
+
.await;
|
| 1032 |
+
}
|
| 1033 |
+
|
| 1034 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1035 |
+
if let Some(custom_headers_hash) = custom_headers_yaml.as_hash() {
|
| 1036 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1037 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 1038 |
+
if let Some(header_name) = header_name.as_str() {
|
| 1039 |
+
if let Some(header_value) = header_value.as_str() {
|
| 1040 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 1041 |
+
if let Ok(header_value) =
|
| 1042 |
+
HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
|
| 1043 |
+
{
|
| 1044 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 1045 |
+
response_parts.headers.insert(header_name, header_value);
|
| 1046 |
+
}
|
| 1047 |
+
}
|
| 1048 |
+
}
|
| 1049 |
+
}
|
| 1050 |
+
}
|
| 1051 |
+
}
|
| 1052 |
+
}
|
| 1053 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 1054 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 1055 |
+
Some(value) => HeaderValue::from_bytes(
|
| 1056 |
+
format!(
|
| 1057 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 1058 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 1059 |
+
http3_alt_port,
|
| 1060 |
+
http3_alt_port
|
| 1061 |
+
)
|
| 1062 |
+
.as_bytes(),
|
| 1063 |
+
),
|
| 1064 |
+
None => HeaderValue::from_bytes(
|
| 1065 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 1066 |
+
),
|
| 1067 |
+
} {
|
| 1068 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 1069 |
+
}
|
| 1070 |
+
}
|
| 1071 |
+
response_parts
|
| 1072 |
+
.headers
|
| 1073 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 1074 |
+
|
| 1075 |
+
Ok(Response::from_parts(response_parts, response_body))
|
| 1076 |
+
} else {
|
| 1077 |
+
let response = Response::builder()
|
| 1078 |
+
.status(StatusCode::BAD_REQUEST)
|
| 1079 |
+
.body(Empty::new().map_err(|e| match e {}).boxed())
|
| 1080 |
+
.unwrap_or_default();
|
| 1081 |
+
|
| 1082 |
+
if log_enabled {
|
| 1083 |
+
log_combined(
|
| 1084 |
+
&logger,
|
| 1085 |
+
socket_data.remote_addr.ip(),
|
| 1086 |
+
None,
|
| 1087 |
+
log_method,
|
| 1088 |
+
log_request_path,
|
| 1089 |
+
log_protocol,
|
| 1090 |
+
response.status().as_u16(),
|
| 1091 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 1092 |
+
Some(header_value) => match header_value.to_str() {
|
| 1093 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 1094 |
+
Ok(content_length) => Some(content_length),
|
| 1095 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1096 |
+
},
|
| 1097 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1098 |
+
},
|
| 1099 |
+
None => response.body().size_hint().exact(),
|
| 1100 |
+
},
|
| 1101 |
+
log_referrer,
|
| 1102 |
+
log_user_agent,
|
| 1103 |
+
)
|
| 1104 |
+
.await;
|
| 1105 |
+
}
|
| 1106 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1107 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 1108 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1109 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 1110 |
+
if let Some(header_name) = header_name.as_str() {
|
| 1111 |
+
if let Some(header_value) = header_value.as_str() {
|
| 1112 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 1113 |
+
if let Ok(header_value) =
|
| 1114 |
+
HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
|
| 1115 |
+
{
|
| 1116 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 1117 |
+
response_parts.headers.insert(header_name, header_value);
|
| 1118 |
+
}
|
| 1119 |
+
}
|
| 1120 |
+
}
|
| 1121 |
+
}
|
| 1122 |
+
}
|
| 1123 |
+
}
|
| 1124 |
+
}
|
| 1125 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 1126 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 1127 |
+
Some(value) => HeaderValue::from_bytes(
|
| 1128 |
+
format!(
|
| 1129 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 1130 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 1131 |
+
http3_alt_port,
|
| 1132 |
+
http3_alt_port
|
| 1133 |
+
)
|
| 1134 |
+
.as_bytes(),
|
| 1135 |
+
),
|
| 1136 |
+
None => HeaderValue::from_bytes(
|
| 1137 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 1138 |
+
),
|
| 1139 |
+
} {
|
| 1140 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 1141 |
+
}
|
| 1142 |
+
}
|
| 1143 |
+
response_parts
|
| 1144 |
+
.headers
|
| 1145 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 1146 |
+
|
| 1147 |
+
Ok(Response::from_parts(response_parts, response_body))
|
| 1148 |
+
}
|
| 1149 |
+
} else {
|
| 1150 |
+
let response = Response::builder()
|
| 1151 |
+
.status(StatusCode::NOT_IMPLEMENTED)
|
| 1152 |
+
.body(Empty::new().map_err(|e| match e {}).boxed())
|
| 1153 |
+
.unwrap_or_default();
|
| 1154 |
+
|
| 1155 |
+
if log_enabled {
|
| 1156 |
+
log_combined(
|
| 1157 |
+
&logger,
|
| 1158 |
+
socket_data.remote_addr.ip(),
|
| 1159 |
+
None,
|
| 1160 |
+
log_method,
|
| 1161 |
+
log_request_path,
|
| 1162 |
+
log_protocol,
|
| 1163 |
+
response.status().as_u16(),
|
| 1164 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 1165 |
+
Some(header_value) => match header_value.to_str() {
|
| 1166 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 1167 |
+
Ok(content_length) => Some(content_length),
|
| 1168 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1169 |
+
},
|
| 1170 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1171 |
+
},
|
| 1172 |
+
None => response.body().size_hint().exact(),
|
| 1173 |
+
},
|
| 1174 |
+
log_referrer,
|
| 1175 |
+
log_user_agent,
|
| 1176 |
+
)
|
| 1177 |
+
.await;
|
| 1178 |
+
}
|
| 1179 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1180 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 1181 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1182 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 1183 |
+
if let Some(header_name) = header_name.as_str() {
|
| 1184 |
+
if let Some(header_value) = header_value.as_str() {
|
| 1185 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 1186 |
+
if let Ok(header_value) =
|
| 1187 |
+
HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
|
| 1188 |
+
{
|
| 1189 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 1190 |
+
response_parts.headers.insert(header_name, header_value);
|
| 1191 |
+
}
|
| 1192 |
+
}
|
| 1193 |
+
}
|
| 1194 |
+
}
|
| 1195 |
+
}
|
| 1196 |
+
}
|
| 1197 |
+
}
|
| 1198 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 1199 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 1200 |
+
Some(value) => HeaderValue::from_bytes(
|
| 1201 |
+
format!(
|
| 1202 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 1203 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 1204 |
+
http3_alt_port,
|
| 1205 |
+
http3_alt_port
|
| 1206 |
+
)
|
| 1207 |
+
.as_bytes(),
|
| 1208 |
+
),
|
| 1209 |
+
None => HeaderValue::from_bytes(
|
| 1210 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 1211 |
+
),
|
| 1212 |
+
} {
|
| 1213 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 1214 |
+
}
|
| 1215 |
+
}
|
| 1216 |
+
response_parts
|
| 1217 |
+
.headers
|
| 1218 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 1219 |
+
|
| 1220 |
+
Ok(Response::from_parts(response_parts, response_body))
|
| 1221 |
+
}
|
| 1222 |
+
} else {
|
| 1223 |
+
let is_websocket_request = is_upgrade_request(&request);
|
| 1224 |
+
let mut request_data = RequestData::new(request, None, None);
|
| 1225 |
+
let mut latest_auth_data = None;
|
| 1226 |
+
let mut executed_handlers = Vec::new();
|
| 1227 |
+
for mut handlers in handlers_vec {
|
| 1228 |
+
if is_websocket_request && handlers.does_websocket_requests(&combined_config, &socket_data) {
|
| 1229 |
+
let (request, _, _) = request_data.into_parts();
|
| 1230 |
+
|
| 1231 |
+
// Variables moved to before "tokio::spawn" to avoid issues with moved values
|
| 1232 |
+
let client_ip = socket_data.remote_addr.ip();
|
| 1233 |
+
let custom_headers_yaml = combined_config["customHeaders"].clone();
|
| 1234 |
+
let request_uri = request.uri().to_owned();
|
| 1235 |
+
|
| 1236 |
+
let (original_response, websocket) = match hyper_tungstenite::upgrade(request, None) {
|
| 1237 |
+
Ok(data) => data,
|
| 1238 |
+
Err(err) => {
|
| 1239 |
+
error_logger
|
| 1240 |
+
.log(&format!("Error while upgrading WebSocket request: {}", err))
|
| 1241 |
+
.await;
|
| 1242 |
+
let response = Response::builder()
|
| 1243 |
+
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
| 1244 |
+
.body(
|
| 1245 |
+
Full::new(Bytes::from(generate_default_error_page(
|
| 1246 |
+
StatusCode::INTERNAL_SERVER_ERROR,
|
| 1247 |
+
None,
|
| 1248 |
+
)))
|
| 1249 |
+
.map_err(|e| match e {})
|
| 1250 |
+
.boxed(),
|
| 1251 |
+
)
|
| 1252 |
+
.unwrap_or_default();
|
| 1253 |
+
|
| 1254 |
+
if log_enabled {
|
| 1255 |
+
log_combined(
|
| 1256 |
+
&logger,
|
| 1257 |
+
socket_data.remote_addr.ip(),
|
| 1258 |
+
None,
|
| 1259 |
+
log_method,
|
| 1260 |
+
log_request_path,
|
| 1261 |
+
log_protocol,
|
| 1262 |
+
response.status().as_u16(),
|
| 1263 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 1264 |
+
Some(header_value) => match header_value.to_str() {
|
| 1265 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 1266 |
+
Ok(content_length) => Some(content_length),
|
| 1267 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1268 |
+
},
|
| 1269 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1270 |
+
},
|
| 1271 |
+
None => response.body().size_hint().exact(),
|
| 1272 |
+
},
|
| 1273 |
+
log_referrer,
|
| 1274 |
+
log_user_agent,
|
| 1275 |
+
)
|
| 1276 |
+
.await;
|
| 1277 |
+
}
|
| 1278 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1279 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 1280 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1281 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 1282 |
+
if let Some(header_name) = header_name.as_str() {
|
| 1283 |
+
if let Some(header_value) = header_value.as_str() {
|
| 1284 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 1285 |
+
if let Ok(header_value) = HeaderValue::from_str(
|
| 1286 |
+
&header_value.replace("{path}", &sanitized_url_pathname),
|
| 1287 |
+
) {
|
| 1288 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 1289 |
+
response_parts.headers.insert(header_name, header_value);
|
| 1290 |
+
}
|
| 1291 |
+
}
|
| 1292 |
+
}
|
| 1293 |
+
}
|
| 1294 |
+
}
|
| 1295 |
+
}
|
| 1296 |
+
}
|
| 1297 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 1298 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 1299 |
+
Some(value) => HeaderValue::from_bytes(
|
| 1300 |
+
format!(
|
| 1301 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 1302 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 1303 |
+
http3_alt_port,
|
| 1304 |
+
http3_alt_port
|
| 1305 |
+
)
|
| 1306 |
+
.as_bytes(),
|
| 1307 |
+
),
|
| 1308 |
+
None => HeaderValue::from_bytes(
|
| 1309 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 1310 |
+
),
|
| 1311 |
+
} {
|
| 1312 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 1313 |
+
}
|
| 1314 |
+
}
|
| 1315 |
+
response_parts
|
| 1316 |
+
.headers
|
| 1317 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 1318 |
+
|
| 1319 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 1320 |
+
}
|
| 1321 |
+
};
|
| 1322 |
+
|
| 1323 |
+
tokio::spawn(async move {
|
| 1324 |
+
let result = handlers
|
| 1325 |
+
.websocket_request_handler(
|
| 1326 |
+
websocket,
|
| 1327 |
+
&request_uri,
|
| 1328 |
+
&combined_config,
|
| 1329 |
+
&socket_data,
|
| 1330 |
+
&error_logger,
|
| 1331 |
+
)
|
| 1332 |
+
.await;
|
| 1333 |
+
match result {
|
| 1334 |
+
Ok(_) => (),
|
| 1335 |
+
Err(err) => {
|
| 1336 |
+
error_logger
|
| 1337 |
+
.log(&format!("Unexpected error for WebSocket request: {}", err))
|
| 1338 |
+
.await;
|
| 1339 |
+
}
|
| 1340 |
+
}
|
| 1341 |
+
});
|
| 1342 |
+
|
| 1343 |
+
let response = original_response.map(|body| body.map_err(|err| match err {}).boxed());
|
| 1344 |
+
|
| 1345 |
+
if log_enabled {
|
| 1346 |
+
log_combined(
|
| 1347 |
+
&logger,
|
| 1348 |
+
client_ip,
|
| 1349 |
+
None,
|
| 1350 |
+
log_method,
|
| 1351 |
+
log_request_path,
|
| 1352 |
+
log_protocol,
|
| 1353 |
+
response.status().as_u16(),
|
| 1354 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 1355 |
+
Some(header_value) => match header_value.to_str() {
|
| 1356 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 1357 |
+
Ok(content_length) => Some(content_length),
|
| 1358 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1359 |
+
},
|
| 1360 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1361 |
+
},
|
| 1362 |
+
None => response.body().size_hint().exact(),
|
| 1363 |
+
},
|
| 1364 |
+
log_referrer,
|
| 1365 |
+
log_user_agent,
|
| 1366 |
+
)
|
| 1367 |
+
.await;
|
| 1368 |
+
}
|
| 1369 |
+
|
| 1370 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1371 |
+
if let Some(custom_headers_hash) = custom_headers_yaml.as_hash() {
|
| 1372 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1373 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 1374 |
+
if let Some(header_name) = header_name.as_str() {
|
| 1375 |
+
if let Some(header_value) = header_value.as_str() {
|
| 1376 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 1377 |
+
if let Ok(header_value) =
|
| 1378 |
+
HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
|
| 1379 |
+
{
|
| 1380 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 1381 |
+
response_parts.headers.insert(header_name, header_value);
|
| 1382 |
+
}
|
| 1383 |
+
}
|
| 1384 |
+
}
|
| 1385 |
+
}
|
| 1386 |
+
}
|
| 1387 |
+
}
|
| 1388 |
+
}
|
| 1389 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 1390 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 1391 |
+
Some(value) => HeaderValue::from_bytes(
|
| 1392 |
+
format!(
|
| 1393 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 1394 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 1395 |
+
http3_alt_port,
|
| 1396 |
+
http3_alt_port
|
| 1397 |
+
)
|
| 1398 |
+
.as_bytes(),
|
| 1399 |
+
),
|
| 1400 |
+
None => HeaderValue::from_bytes(
|
| 1401 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 1402 |
+
),
|
| 1403 |
+
} {
|
| 1404 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 1405 |
+
}
|
| 1406 |
+
}
|
| 1407 |
+
response_parts
|
| 1408 |
+
.headers
|
| 1409 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 1410 |
+
|
| 1411 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 1412 |
+
}
|
| 1413 |
+
|
| 1414 |
+
let response_result = match is_proxy_request {
|
| 1415 |
+
true => {
|
| 1416 |
+
handlers
|
| 1417 |
+
.proxy_request_handler(request_data, &combined_config, &socket_data, &error_logger)
|
| 1418 |
+
.await
|
| 1419 |
+
}
|
| 1420 |
+
false => {
|
| 1421 |
+
handlers
|
| 1422 |
+
.request_handler(request_data, &combined_config, &socket_data, &error_logger)
|
| 1423 |
+
.await
|
| 1424 |
+
}
|
| 1425 |
+
};
|
| 1426 |
+
|
| 1427 |
+
executed_handlers.push(handlers);
|
| 1428 |
+
match response_result {
|
| 1429 |
+
Ok(response) => {
|
| 1430 |
+
let (
|
| 1431 |
+
request_option,
|
| 1432 |
+
auth_data,
|
| 1433 |
+
original_url,
|
| 1434 |
+
response,
|
| 1435 |
+
status,
|
| 1436 |
+
headers,
|
| 1437 |
+
new_remote_address,
|
| 1438 |
+
parallel_fn,
|
| 1439 |
+
) = response.into_parts();
|
| 1440 |
+
latest_auth_data = auth_data.clone();
|
| 1441 |
+
if let Some(new_remote_address) = new_remote_address {
|
| 1442 |
+
socket_data.remote_addr = new_remote_address;
|
| 1443 |
+
};
|
| 1444 |
+
if let Some(parallel_fn) = parallel_fn {
|
| 1445 |
+
// Spawn the function in the web server's Tokio runtime.
|
| 1446 |
+
// We have implemented parallel_fn parameter in the ResponseData
|
| 1447 |
+
// because tokio::spawn doesn't work on dynamic libraries,
|
| 1448 |
+
// see https://github.com/tokio-rs/tokio/issues/6927
|
| 1449 |
+
tokio::spawn(parallel_fn);
|
| 1450 |
+
}
|
| 1451 |
+
match response {
|
| 1452 |
+
Some(response) => {
|
| 1453 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1454 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 1455 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1456 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 1457 |
+
if let Some(header_name) = header_name.as_str() {
|
| 1458 |
+
if let Some(header_value) = header_value.as_str() {
|
| 1459 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 1460 |
+
if let Ok(header_value) = HeaderValue::from_str(
|
| 1461 |
+
&header_value.replace("{path}", &sanitized_url_pathname),
|
| 1462 |
+
) {
|
| 1463 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 1464 |
+
response_parts.headers.insert(header_name, header_value);
|
| 1465 |
+
}
|
| 1466 |
+
}
|
| 1467 |
+
}
|
| 1468 |
+
}
|
| 1469 |
+
}
|
| 1470 |
+
}
|
| 1471 |
+
}
|
| 1472 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 1473 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 1474 |
+
Some(value) => HeaderValue::from_bytes(
|
| 1475 |
+
format!(
|
| 1476 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 1477 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 1478 |
+
http3_alt_port,
|
| 1479 |
+
http3_alt_port
|
| 1480 |
+
)
|
| 1481 |
+
.as_bytes(),
|
| 1482 |
+
),
|
| 1483 |
+
None => HeaderValue::from_bytes(
|
| 1484 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 1485 |
+
),
|
| 1486 |
+
} {
|
| 1487 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 1488 |
+
}
|
| 1489 |
+
}
|
| 1490 |
+
response_parts
|
| 1491 |
+
.headers
|
| 1492 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 1493 |
+
|
| 1494 |
+
let mut response = Response::from_parts(response_parts, response_body);
|
| 1495 |
+
|
| 1496 |
+
while let Some(mut executed_handler) = executed_handlers.pop() {
|
| 1497 |
+
let response_status = match is_proxy_request {
|
| 1498 |
+
true => {
|
| 1499 |
+
executed_handler
|
| 1500 |
+
.proxy_response_modifying_handler(response)
|
| 1501 |
+
.await
|
| 1502 |
+
}
|
| 1503 |
+
false => executed_handler.response_modifying_handler(response).await,
|
| 1504 |
+
};
|
| 1505 |
+
response = match response_status {
|
| 1506 |
+
Ok(response) => response,
|
| 1507 |
+
Err(err) => {
|
| 1508 |
+
if error_log_enabled {
|
| 1509 |
+
logger
|
| 1510 |
+
.send(LogMessage::new(
|
| 1511 |
+
format!("Unexpected error while serving a request: {}", err),
|
| 1512 |
+
true,
|
| 1513 |
+
))
|
| 1514 |
+
.await
|
| 1515 |
+
.unwrap_or_default();
|
| 1516 |
+
}
|
| 1517 |
+
|
| 1518 |
+
let response = generate_error_response(
|
| 1519 |
+
StatusCode::INTERNAL_SERVER_ERROR,
|
| 1520 |
+
&combined_config,
|
| 1521 |
+
&headers,
|
| 1522 |
+
)
|
| 1523 |
+
.await;
|
| 1524 |
+
if log_enabled {
|
| 1525 |
+
log_combined(
|
| 1526 |
+
&logger,
|
| 1527 |
+
socket_data.remote_addr.ip(),
|
| 1528 |
+
auth_data,
|
| 1529 |
+
log_method,
|
| 1530 |
+
log_request_path,
|
| 1531 |
+
log_protocol,
|
| 1532 |
+
response.status().as_u16(),
|
| 1533 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 1534 |
+
Some(header_value) => match header_value.to_str() {
|
| 1535 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 1536 |
+
Ok(content_length) => Some(content_length),
|
| 1537 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1538 |
+
},
|
| 1539 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1540 |
+
},
|
| 1541 |
+
None => response.body().size_hint().exact(),
|
| 1542 |
+
},
|
| 1543 |
+
log_referrer,
|
| 1544 |
+
log_user_agent,
|
| 1545 |
+
)
|
| 1546 |
+
.await;
|
| 1547 |
+
}
|
| 1548 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1549 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 1550 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1551 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 1552 |
+
if let Some(header_name) = header_name.as_str() {
|
| 1553 |
+
if let Some(header_value) = header_value.as_str() {
|
| 1554 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 1555 |
+
if let Ok(header_value) = HeaderValue::from_str(
|
| 1556 |
+
&header_value.replace("{path}", &sanitized_url_pathname),
|
| 1557 |
+
) {
|
| 1558 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 1559 |
+
response_parts.headers.insert(header_name, header_value);
|
| 1560 |
+
}
|
| 1561 |
+
}
|
| 1562 |
+
}
|
| 1563 |
+
}
|
| 1564 |
+
}
|
| 1565 |
+
}
|
| 1566 |
+
}
|
| 1567 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 1568 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 1569 |
+
Some(value) => HeaderValue::from_bytes(
|
| 1570 |
+
format!(
|
| 1571 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 1572 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 1573 |
+
http3_alt_port,
|
| 1574 |
+
http3_alt_port
|
| 1575 |
+
)
|
| 1576 |
+
.as_bytes(),
|
| 1577 |
+
),
|
| 1578 |
+
None => HeaderValue::from_bytes(
|
| 1579 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port)
|
| 1580 |
+
.as_bytes(),
|
| 1581 |
+
),
|
| 1582 |
+
} {
|
| 1583 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 1584 |
+
}
|
| 1585 |
+
}
|
| 1586 |
+
response_parts
|
| 1587 |
+
.headers
|
| 1588 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 1589 |
+
|
| 1590 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 1591 |
+
}
|
| 1592 |
+
};
|
| 1593 |
+
}
|
| 1594 |
+
|
| 1595 |
+
if log_enabled {
|
| 1596 |
+
log_combined(
|
| 1597 |
+
&logger,
|
| 1598 |
+
socket_data.remote_addr.ip(),
|
| 1599 |
+
auth_data,
|
| 1600 |
+
log_method,
|
| 1601 |
+
log_request_path,
|
| 1602 |
+
log_protocol,
|
| 1603 |
+
response.status().as_u16(),
|
| 1604 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 1605 |
+
Some(header_value) => match header_value.to_str() {
|
| 1606 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 1607 |
+
Ok(content_length) => Some(content_length),
|
| 1608 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1609 |
+
},
|
| 1610 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1611 |
+
},
|
| 1612 |
+
None => response.body().size_hint().exact(),
|
| 1613 |
+
},
|
| 1614 |
+
log_referrer,
|
| 1615 |
+
log_user_agent,
|
| 1616 |
+
)
|
| 1617 |
+
.await;
|
| 1618 |
+
}
|
| 1619 |
+
|
| 1620 |
+
return Ok(response);
|
| 1621 |
+
}
|
| 1622 |
+
None => match status {
|
| 1623 |
+
Some(status) => {
|
| 1624 |
+
let response = generate_error_response(status, &combined_config, &headers).await;
|
| 1625 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1626 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 1627 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1628 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 1629 |
+
if let Some(header_name) = header_name.as_str() {
|
| 1630 |
+
if let Some(header_value) = header_value.as_str() {
|
| 1631 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 1632 |
+
if let Ok(header_value) = HeaderValue::from_str(
|
| 1633 |
+
&header_value.replace("{path}", &sanitized_url_pathname),
|
| 1634 |
+
) {
|
| 1635 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 1636 |
+
response_parts.headers.insert(header_name, header_value);
|
| 1637 |
+
}
|
| 1638 |
+
}
|
| 1639 |
+
}
|
| 1640 |
+
}
|
| 1641 |
+
}
|
| 1642 |
+
}
|
| 1643 |
+
}
|
| 1644 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 1645 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 1646 |
+
Some(value) => HeaderValue::from_bytes(
|
| 1647 |
+
format!(
|
| 1648 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 1649 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 1650 |
+
http3_alt_port,
|
| 1651 |
+
http3_alt_port
|
| 1652 |
+
)
|
| 1653 |
+
.as_bytes(),
|
| 1654 |
+
),
|
| 1655 |
+
None => HeaderValue::from_bytes(
|
| 1656 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port)
|
| 1657 |
+
.as_bytes(),
|
| 1658 |
+
),
|
| 1659 |
+
} {
|
| 1660 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 1661 |
+
}
|
| 1662 |
+
}
|
| 1663 |
+
response_parts
|
| 1664 |
+
.headers
|
| 1665 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 1666 |
+
|
| 1667 |
+
let mut response = Response::from_parts(response_parts, response_body);
|
| 1668 |
+
|
| 1669 |
+
while let Some(mut executed_handler) = executed_handlers.pop() {
|
| 1670 |
+
let response_status = match is_proxy_request {
|
| 1671 |
+
true => {
|
| 1672 |
+
executed_handler
|
| 1673 |
+
.proxy_response_modifying_handler(response)
|
| 1674 |
+
.await
|
| 1675 |
+
}
|
| 1676 |
+
false => executed_handler.response_modifying_handler(response).await,
|
| 1677 |
+
};
|
| 1678 |
+
response = match response_status {
|
| 1679 |
+
Ok(response) => response,
|
| 1680 |
+
Err(err) => {
|
| 1681 |
+
if error_log_enabled {
|
| 1682 |
+
logger
|
| 1683 |
+
.send(LogMessage::new(
|
| 1684 |
+
format!("Unexpected error while serving a request: {}", err),
|
| 1685 |
+
true,
|
| 1686 |
+
))
|
| 1687 |
+
.await
|
| 1688 |
+
.unwrap_or_default();
|
| 1689 |
+
}
|
| 1690 |
+
|
| 1691 |
+
let response = generate_error_response(
|
| 1692 |
+
StatusCode::INTERNAL_SERVER_ERROR,
|
| 1693 |
+
&combined_config,
|
| 1694 |
+
&headers,
|
| 1695 |
+
)
|
| 1696 |
+
.await;
|
| 1697 |
+
if log_enabled {
|
| 1698 |
+
log_combined(
|
| 1699 |
+
&logger,
|
| 1700 |
+
socket_data.remote_addr.ip(),
|
| 1701 |
+
auth_data,
|
| 1702 |
+
log_method,
|
| 1703 |
+
log_request_path,
|
| 1704 |
+
log_protocol,
|
| 1705 |
+
response.status().as_u16(),
|
| 1706 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 1707 |
+
Some(header_value) => match header_value.to_str() {
|
| 1708 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 1709 |
+
Ok(content_length) => Some(content_length),
|
| 1710 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1711 |
+
},
|
| 1712 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1713 |
+
},
|
| 1714 |
+
None => response.body().size_hint().exact(),
|
| 1715 |
+
},
|
| 1716 |
+
log_referrer,
|
| 1717 |
+
log_user_agent,
|
| 1718 |
+
)
|
| 1719 |
+
.await;
|
| 1720 |
+
}
|
| 1721 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1722 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash()
|
| 1723 |
+
{
|
| 1724 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1725 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 1726 |
+
if let Some(header_name) = header_name.as_str() {
|
| 1727 |
+
if let Some(header_value) = header_value.as_str() {
|
| 1728 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 1729 |
+
if let Ok(header_value) = HeaderValue::from_str(
|
| 1730 |
+
&header_value.replace("{path}", &sanitized_url_pathname),
|
| 1731 |
+
) {
|
| 1732 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 1733 |
+
response_parts.headers.insert(header_name, header_value);
|
| 1734 |
+
}
|
| 1735 |
+
}
|
| 1736 |
+
}
|
| 1737 |
+
}
|
| 1738 |
+
}
|
| 1739 |
+
}
|
| 1740 |
+
}
|
| 1741 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 1742 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC)
|
| 1743 |
+
{
|
| 1744 |
+
Some(value) => HeaderValue::from_bytes(
|
| 1745 |
+
format!(
|
| 1746 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 1747 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 1748 |
+
http3_alt_port,
|
| 1749 |
+
http3_alt_port
|
| 1750 |
+
)
|
| 1751 |
+
.as_bytes(),
|
| 1752 |
+
),
|
| 1753 |
+
None => HeaderValue::from_bytes(
|
| 1754 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port)
|
| 1755 |
+
.as_bytes(),
|
| 1756 |
+
),
|
| 1757 |
+
} {
|
| 1758 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 1759 |
+
}
|
| 1760 |
+
}
|
| 1761 |
+
response_parts
|
| 1762 |
+
.headers
|
| 1763 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 1764 |
+
|
| 1765 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 1766 |
+
}
|
| 1767 |
+
};
|
| 1768 |
+
}
|
| 1769 |
+
|
| 1770 |
+
if log_enabled {
|
| 1771 |
+
log_combined(
|
| 1772 |
+
&logger,
|
| 1773 |
+
socket_data.remote_addr.ip(),
|
| 1774 |
+
auth_data,
|
| 1775 |
+
log_method,
|
| 1776 |
+
log_request_path,
|
| 1777 |
+
log_protocol,
|
| 1778 |
+
response.status().as_u16(),
|
| 1779 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 1780 |
+
Some(header_value) => match header_value.to_str() {
|
| 1781 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 1782 |
+
Ok(content_length) => Some(content_length),
|
| 1783 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1784 |
+
},
|
| 1785 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1786 |
+
},
|
| 1787 |
+
None => response.body().size_hint().exact(),
|
| 1788 |
+
},
|
| 1789 |
+
log_referrer,
|
| 1790 |
+
log_user_agent,
|
| 1791 |
+
)
|
| 1792 |
+
.await;
|
| 1793 |
+
}
|
| 1794 |
+
return Ok(response);
|
| 1795 |
+
}
|
| 1796 |
+
None => match request_option {
|
| 1797 |
+
Some(request) => {
|
| 1798 |
+
request_data = RequestData::new(request, auth_data, original_url);
|
| 1799 |
+
continue;
|
| 1800 |
+
}
|
| 1801 |
+
None => {
|
| 1802 |
+
break;
|
| 1803 |
+
}
|
| 1804 |
+
},
|
| 1805 |
+
},
|
| 1806 |
+
}
|
| 1807 |
+
}
|
| 1808 |
+
Err(err) => {
|
| 1809 |
+
let response =
|
| 1810 |
+
generate_error_response(StatusCode::INTERNAL_SERVER_ERROR, &combined_config, &None)
|
| 1811 |
+
.await;
|
| 1812 |
+
|
| 1813 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1814 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 1815 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1816 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 1817 |
+
if let Some(header_name) = header_name.as_str() {
|
| 1818 |
+
if let Some(header_value) = header_value.as_str() {
|
| 1819 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 1820 |
+
if let Ok(header_value) = HeaderValue::from_str(
|
| 1821 |
+
&header_value.replace("{path}", &sanitized_url_pathname),
|
| 1822 |
+
) {
|
| 1823 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 1824 |
+
response_parts.headers.insert(header_name, header_value);
|
| 1825 |
+
}
|
| 1826 |
+
}
|
| 1827 |
+
}
|
| 1828 |
+
}
|
| 1829 |
+
}
|
| 1830 |
+
}
|
| 1831 |
+
}
|
| 1832 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 1833 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 1834 |
+
Some(value) => HeaderValue::from_bytes(
|
| 1835 |
+
format!(
|
| 1836 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 1837 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 1838 |
+
http3_alt_port,
|
| 1839 |
+
http3_alt_port
|
| 1840 |
+
)
|
| 1841 |
+
.as_bytes(),
|
| 1842 |
+
),
|
| 1843 |
+
None => HeaderValue::from_bytes(
|
| 1844 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 1845 |
+
),
|
| 1846 |
+
} {
|
| 1847 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 1848 |
+
}
|
| 1849 |
+
}
|
| 1850 |
+
response_parts
|
| 1851 |
+
.headers
|
| 1852 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 1853 |
+
|
| 1854 |
+
let mut response = Response::from_parts(response_parts, response_body);
|
| 1855 |
+
|
| 1856 |
+
while let Some(mut executed_handler) = executed_handlers.pop() {
|
| 1857 |
+
let response_status = match is_proxy_request {
|
| 1858 |
+
true => {
|
| 1859 |
+
executed_handler
|
| 1860 |
+
.proxy_response_modifying_handler(response)
|
| 1861 |
+
.await
|
| 1862 |
+
}
|
| 1863 |
+
false => executed_handler.response_modifying_handler(response).await,
|
| 1864 |
+
};
|
| 1865 |
+
response = match response_status {
|
| 1866 |
+
Ok(response) => response,
|
| 1867 |
+
Err(err) => {
|
| 1868 |
+
if error_log_enabled {
|
| 1869 |
+
logger
|
| 1870 |
+
.send(LogMessage::new(
|
| 1871 |
+
format!("Unexpected error while serving a request: {}", err),
|
| 1872 |
+
true,
|
| 1873 |
+
))
|
| 1874 |
+
.await
|
| 1875 |
+
.unwrap_or_default();
|
| 1876 |
+
}
|
| 1877 |
+
|
| 1878 |
+
let response = generate_error_response(
|
| 1879 |
+
StatusCode::INTERNAL_SERVER_ERROR,
|
| 1880 |
+
&combined_config,
|
| 1881 |
+
&None,
|
| 1882 |
+
)
|
| 1883 |
+
.await;
|
| 1884 |
+
if log_enabled {
|
| 1885 |
+
log_combined(
|
| 1886 |
+
&logger,
|
| 1887 |
+
socket_data.remote_addr.ip(),
|
| 1888 |
+
latest_auth_data,
|
| 1889 |
+
log_method,
|
| 1890 |
+
log_request_path,
|
| 1891 |
+
log_protocol,
|
| 1892 |
+
response.status().as_u16(),
|
| 1893 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 1894 |
+
Some(header_value) => match header_value.to_str() {
|
| 1895 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 1896 |
+
Ok(content_length) => Some(content_length),
|
| 1897 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1898 |
+
},
|
| 1899 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1900 |
+
},
|
| 1901 |
+
None => response.body().size_hint().exact(),
|
| 1902 |
+
},
|
| 1903 |
+
log_referrer,
|
| 1904 |
+
log_user_agent,
|
| 1905 |
+
)
|
| 1906 |
+
.await;
|
| 1907 |
+
}
|
| 1908 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1909 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 1910 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1911 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 1912 |
+
if let Some(header_name) = header_name.as_str() {
|
| 1913 |
+
if let Some(header_value) = header_value.as_str() {
|
| 1914 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 1915 |
+
if let Ok(header_value) = HeaderValue::from_str(
|
| 1916 |
+
&header_value.replace("{path}", &sanitized_url_pathname),
|
| 1917 |
+
) {
|
| 1918 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 1919 |
+
response_parts.headers.insert(header_name, header_value);
|
| 1920 |
+
}
|
| 1921 |
+
}
|
| 1922 |
+
}
|
| 1923 |
+
}
|
| 1924 |
+
}
|
| 1925 |
+
}
|
| 1926 |
+
}
|
| 1927 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 1928 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 1929 |
+
Some(value) => HeaderValue::from_bytes(
|
| 1930 |
+
format!(
|
| 1931 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 1932 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 1933 |
+
http3_alt_port,
|
| 1934 |
+
http3_alt_port
|
| 1935 |
+
)
|
| 1936 |
+
.as_bytes(),
|
| 1937 |
+
),
|
| 1938 |
+
None => HeaderValue::from_bytes(
|
| 1939 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port)
|
| 1940 |
+
.as_bytes(),
|
| 1941 |
+
),
|
| 1942 |
+
} {
|
| 1943 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 1944 |
+
}
|
| 1945 |
+
}
|
| 1946 |
+
response_parts
|
| 1947 |
+
.headers
|
| 1948 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 1949 |
+
|
| 1950 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 1951 |
+
}
|
| 1952 |
+
};
|
| 1953 |
+
}
|
| 1954 |
+
|
| 1955 |
+
if error_log_enabled {
|
| 1956 |
+
logger
|
| 1957 |
+
.send(LogMessage::new(
|
| 1958 |
+
format!("Unexpected error while serving a request: {}", err),
|
| 1959 |
+
true,
|
| 1960 |
+
))
|
| 1961 |
+
.await
|
| 1962 |
+
.unwrap_or_default();
|
| 1963 |
+
}
|
| 1964 |
+
|
| 1965 |
+
if log_enabled {
|
| 1966 |
+
log_combined(
|
| 1967 |
+
&logger,
|
| 1968 |
+
socket_data.remote_addr.ip(),
|
| 1969 |
+
latest_auth_data,
|
| 1970 |
+
log_method,
|
| 1971 |
+
log_request_path,
|
| 1972 |
+
log_protocol,
|
| 1973 |
+
response.status().as_u16(),
|
| 1974 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 1975 |
+
Some(header_value) => match header_value.to_str() {
|
| 1976 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 1977 |
+
Ok(content_length) => Some(content_length),
|
| 1978 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1979 |
+
},
|
| 1980 |
+
Err(_) => response.body().size_hint().exact(),
|
| 1981 |
+
},
|
| 1982 |
+
None => response.body().size_hint().exact(),
|
| 1983 |
+
},
|
| 1984 |
+
log_referrer,
|
| 1985 |
+
log_user_agent,
|
| 1986 |
+
)
|
| 1987 |
+
.await;
|
| 1988 |
+
}
|
| 1989 |
+
return Ok(response);
|
| 1990 |
+
}
|
| 1991 |
+
}
|
| 1992 |
+
}
|
| 1993 |
+
|
| 1994 |
+
let response = generate_error_response(StatusCode::NOT_FOUND, &combined_config, &None).await;
|
| 1995 |
+
|
| 1996 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 1997 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 1998 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 1999 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 2000 |
+
if let Some(header_name) = header_name.as_str() {
|
| 2001 |
+
if let Some(header_value) = header_value.as_str() {
|
| 2002 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 2003 |
+
if let Ok(header_value) =
|
| 2004 |
+
HeaderValue::from_str(&header_value.replace("{path}", &sanitized_url_pathname))
|
| 2005 |
+
{
|
| 2006 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 2007 |
+
response_parts.headers.insert(header_name, header_value);
|
| 2008 |
+
}
|
| 2009 |
+
}
|
| 2010 |
+
}
|
| 2011 |
+
}
|
| 2012 |
+
}
|
| 2013 |
+
}
|
| 2014 |
+
}
|
| 2015 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 2016 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 2017 |
+
Some(value) => HeaderValue::from_bytes(
|
| 2018 |
+
format!(
|
| 2019 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 2020 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 2021 |
+
http3_alt_port,
|
| 2022 |
+
http3_alt_port
|
| 2023 |
+
)
|
| 2024 |
+
.as_bytes(),
|
| 2025 |
+
),
|
| 2026 |
+
None => HeaderValue::from_bytes(
|
| 2027 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 2028 |
+
),
|
| 2029 |
+
} {
|
| 2030 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 2031 |
+
}
|
| 2032 |
+
}
|
| 2033 |
+
response_parts
|
| 2034 |
+
.headers
|
| 2035 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 2036 |
+
|
| 2037 |
+
let mut response = Response::from_parts(response_parts, response_body);
|
| 2038 |
+
|
| 2039 |
+
while let Some(mut executed_handler) = executed_handlers.pop() {
|
| 2040 |
+
let response_status = match is_proxy_request {
|
| 2041 |
+
true => {
|
| 2042 |
+
executed_handler
|
| 2043 |
+
.proxy_response_modifying_handler(response)
|
| 2044 |
+
.await
|
| 2045 |
+
}
|
| 2046 |
+
false => executed_handler.response_modifying_handler(response).await,
|
| 2047 |
+
};
|
| 2048 |
+
response = match response_status {
|
| 2049 |
+
Ok(response) => response,
|
| 2050 |
+
Err(err) => {
|
| 2051 |
+
if error_log_enabled {
|
| 2052 |
+
logger
|
| 2053 |
+
.send(LogMessage::new(
|
| 2054 |
+
format!("Unexpected error while serving a request: {}", err),
|
| 2055 |
+
true,
|
| 2056 |
+
))
|
| 2057 |
+
.await
|
| 2058 |
+
.unwrap_or_default();
|
| 2059 |
+
}
|
| 2060 |
+
|
| 2061 |
+
let response =
|
| 2062 |
+
generate_error_response(StatusCode::INTERNAL_SERVER_ERROR, &combined_config, &None)
|
| 2063 |
+
.await;
|
| 2064 |
+
if log_enabled {
|
| 2065 |
+
log_combined(
|
| 2066 |
+
&logger,
|
| 2067 |
+
socket_data.remote_addr.ip(),
|
| 2068 |
+
latest_auth_data,
|
| 2069 |
+
log_method,
|
| 2070 |
+
log_request_path,
|
| 2071 |
+
log_protocol,
|
| 2072 |
+
response.status().as_u16(),
|
| 2073 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 2074 |
+
Some(header_value) => match header_value.to_str() {
|
| 2075 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 2076 |
+
Ok(content_length) => Some(content_length),
|
| 2077 |
+
Err(_) => response.body().size_hint().exact(),
|
| 2078 |
+
},
|
| 2079 |
+
Err(_) => response.body().size_hint().exact(),
|
| 2080 |
+
},
|
| 2081 |
+
None => response.body().size_hint().exact(),
|
| 2082 |
+
},
|
| 2083 |
+
log_referrer,
|
| 2084 |
+
log_user_agent,
|
| 2085 |
+
)
|
| 2086 |
+
.await;
|
| 2087 |
+
}
|
| 2088 |
+
let (mut response_parts, response_body) = response.into_parts();
|
| 2089 |
+
if let Some(custom_headers_hash) = combined_config["customHeaders"].as_hash() {
|
| 2090 |
+
let custom_headers_hash_iter = custom_headers_hash.iter();
|
| 2091 |
+
for (header_name, header_value) in custom_headers_hash_iter {
|
| 2092 |
+
if let Some(header_name) = header_name.as_str() {
|
| 2093 |
+
if let Some(header_value) = header_value.as_str() {
|
| 2094 |
+
if !response_parts.headers.contains_key(header_name) {
|
| 2095 |
+
if let Ok(header_value) = HeaderValue::from_str(
|
| 2096 |
+
&header_value.replace("{path}", &sanitized_url_pathname),
|
| 2097 |
+
) {
|
| 2098 |
+
if let Ok(header_name) = HeaderName::from_str(header_name) {
|
| 2099 |
+
response_parts.headers.insert(header_name, header_value);
|
| 2100 |
+
}
|
| 2101 |
+
}
|
| 2102 |
+
}
|
| 2103 |
+
}
|
| 2104 |
+
}
|
| 2105 |
+
}
|
| 2106 |
+
}
|
| 2107 |
+
if let Some(http3_alt_port) = http3_alt_port {
|
| 2108 |
+
if let Ok(header_value) = match response_parts.headers.get(header::ALT_SVC) {
|
| 2109 |
+
Some(value) => HeaderValue::from_bytes(
|
| 2110 |
+
format!(
|
| 2111 |
+
"{}, h3=\":{}\", h3-29=\":{}\"",
|
| 2112 |
+
String::from_utf8_lossy(value.as_bytes()),
|
| 2113 |
+
http3_alt_port,
|
| 2114 |
+
http3_alt_port
|
| 2115 |
+
)
|
| 2116 |
+
.as_bytes(),
|
| 2117 |
+
),
|
| 2118 |
+
None => HeaderValue::from_bytes(
|
| 2119 |
+
format!("h3=\":{}\", h3-29=\":{}\"", http3_alt_port, http3_alt_port).as_bytes(),
|
| 2120 |
+
),
|
| 2121 |
+
} {
|
| 2122 |
+
response_parts.headers.insert(header::ALT_SVC, header_value);
|
| 2123 |
+
}
|
| 2124 |
+
}
|
| 2125 |
+
response_parts
|
| 2126 |
+
.headers
|
| 2127 |
+
.insert(header::SERVER, HeaderValue::from_static(SERVER_SOFTWARE));
|
| 2128 |
+
|
| 2129 |
+
return Ok(Response::from_parts(response_parts, response_body));
|
| 2130 |
+
}
|
| 2131 |
+
};
|
| 2132 |
+
}
|
| 2133 |
+
|
| 2134 |
+
if log_enabled {
|
| 2135 |
+
log_combined(
|
| 2136 |
+
&logger,
|
| 2137 |
+
socket_data.remote_addr.ip(),
|
| 2138 |
+
latest_auth_data,
|
| 2139 |
+
log_method,
|
| 2140 |
+
log_request_path,
|
| 2141 |
+
log_protocol,
|
| 2142 |
+
response.status().as_u16(),
|
| 2143 |
+
match response.headers().get(header::CONTENT_LENGTH) {
|
| 2144 |
+
Some(header_value) => match header_value.to_str() {
|
| 2145 |
+
Ok(header_value) => match header_value.parse::<u64>() {
|
| 2146 |
+
Ok(content_length) => Some(content_length),
|
| 2147 |
+
Err(_) => response.body().size_hint().exact(),
|
| 2148 |
+
},
|
| 2149 |
+
Err(_) => response.body().size_hint().exact(),
|
| 2150 |
+
},
|
| 2151 |
+
None => response.body().size_hint().exact(),
|
| 2152 |
+
},
|
| 2153 |
+
log_referrer,
|
| 2154 |
+
log_user_agent,
|
| 2155 |
+
)
|
| 2156 |
+
.await;
|
| 2157 |
+
}
|
| 2158 |
+
Ok(response)
|
| 2159 |
+
}
|
| 2160 |
+
}
|
| 2161 |
+
|
| 2162 |
+
#[allow(clippy::too_many_arguments)]
|
| 2163 |
+
pub async fn request_handler(
|
| 2164 |
+
request: Request<BoxBody<Bytes, std::io::Error>>,
|
| 2165 |
+
remote_address: SocketAddr,
|
| 2166 |
+
local_address: SocketAddr,
|
| 2167 |
+
encrypted: bool,
|
| 2168 |
+
config: Arc<Yaml>,
|
| 2169 |
+
logger: Sender<LogMessage>,
|
| 2170 |
+
handlers_vec: Vec<Box<dyn ServerModuleHandlers + Send>>,
|
| 2171 |
+
acme_http01_resolver_option: Option<Arc<ResolvesServerCertAcme>>,
|
| 2172 |
+
http3_alt_port: Option<u16>,
|
| 2173 |
+
) -> Result<Response<BoxBody<Bytes, std::io::Error>>, anyhow::Error> {
|
| 2174 |
+
let timeout_yaml = &config["global"]["timeout"];
|
| 2175 |
+
if timeout_yaml.is_null() {
|
| 2176 |
+
request_handler_wrapped(
|
| 2177 |
+
request,
|
| 2178 |
+
remote_address,
|
| 2179 |
+
local_address,
|
| 2180 |
+
encrypted,
|
| 2181 |
+
config,
|
| 2182 |
+
logger,
|
| 2183 |
+
handlers_vec,
|
| 2184 |
+
acme_http01_resolver_option,
|
| 2185 |
+
http3_alt_port,
|
| 2186 |
+
)
|
| 2187 |
+
.await
|
| 2188 |
+
.map_err(|e| anyhow::anyhow!(e))
|
| 2189 |
+
} else {
|
| 2190 |
+
let timeout_millis = timeout_yaml.as_i64().unwrap_or(300000) as u64;
|
| 2191 |
+
match timeout(
|
| 2192 |
+
Duration::from_millis(timeout_millis),
|
| 2193 |
+
request_handler_wrapped(
|
| 2194 |
+
request,
|
| 2195 |
+
remote_address,
|
| 2196 |
+
local_address,
|
| 2197 |
+
encrypted,
|
| 2198 |
+
config,
|
| 2199 |
+
logger,
|
| 2200 |
+
handlers_vec,
|
| 2201 |
+
acme_http01_resolver_option,
|
| 2202 |
+
http3_alt_port,
|
| 2203 |
+
),
|
| 2204 |
+
)
|
| 2205 |
+
.await
|
| 2206 |
+
{
|
| 2207 |
+
Ok(response) => response.map_err(|e| anyhow::anyhow!(e)),
|
| 2208 |
+
Err(_) => Err(anyhow::anyhow!("The client or server has timed out")),
|
| 2209 |
+
}
|
| 2210 |
+
}
|
| 2211 |
+
}
|
ferron/src/res/server_software.rs
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
pub const SERVER_SOFTWARE: &str = "Ferron";
|