├── .cargo ├── audit.toml └── config.toml ├── .dockerignore ├── .fossa.yml ├── .git-blame-ignore-revs ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yml └── workflows │ ├── cargo-deny.yaml │ ├── ci.yaml │ ├── fossa.yaml │ ├── nginx.yaml │ └── sanitizers.yaml ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── Containerfile.debian ├── Dockerfile ├── GNUmakefile ├── LICENSE ├── README.md ├── SECURITY.md ├── build.rs ├── build ├── changelog.sh ├── container.mk ├── github.mk └── release.mk ├── deny.toml ├── examples ├── Cargo.toml ├── README.md ├── async.conf ├── async.rs ├── auto │ └── rust ├── awssig.conf ├── awssig.rs ├── awssig_curl_combined.conf ├── config ├── config.make ├── curl.conf ├── curl.rs ├── httporigdst.conf ├── httporigdst.rs ├── t │ ├── async.t │ ├── awssig.t │ ├── curl.t │ └── upstream.t ├── upstream.conf └── upstream.rs ├── misc ├── lsan-suppressions.txt ├── nginx-sanitizer-support.patch ├── ubsan-suppressions.txt └── update-action-sha.sh ├── nginx-sys ├── Cargo.toml ├── LICENSE ├── README.md ├── build │ ├── main.rs │ ├── vendored.rs │ └── wrapper.h └── src │ ├── detail.rs │ ├── event.rs │ ├── lib.rs │ ├── queue.rs │ └── string.rs ├── rustfmt.toml ├── src ├── core │ ├── buffer.rs │ ├── mod.rs │ ├── pool.rs │ ├── status.rs │ └── string.rs ├── ffi │ └── mod.rs ├── http │ ├── conf.rs │ ├── mod.rs │ ├── module.rs │ ├── request.rs │ ├── status.rs │ └── upstream.rs ├── lib.rs └── log.rs └── tests ├── build.rs ├── conditional-compilation.rs ├── config └── nginx.conf ├── log_test.rs ├── module ├── config ├── config.make └── ngx_http_rust_module.c └── nginx.conf /.cargo/audit.toml: -------------------------------------------------------------------------------- 1 | # audit config file 2 | # 3 | # It may be located in the user home (`~/.cargo/audit.toml`) or in the project 4 | # root (`.cargo/audit.toml`). 5 | # 6 | # All of the options which can be passed via CLI arguments can also be 7 | # permanently specified in this file. 8 | # It may be located in the user home (`~/.cargo/audit.toml`) or in the project 9 | # root (`.cargo/audit.toml`). 10 | 11 | [advisories] 12 | ignore = ["RUSTSEC-2020-0071"] 13 | 14 | # # Advisory Database Configuration 15 | # [database] 16 | # path = "~/.cargo/advisory-db" # Path where advisory git repo will be cloned 17 | # url = "https://github.com/RustSec/advisory-db.git" # URL to git repo 18 | # fetch = true # Perform a `git fetch` before auditing (default: true) 19 | # stale = false # Allow stale advisory DB (i.e. no commits for 90 days, default: false) 20 | 21 | # # Output Configuration 22 | # [output] 23 | # deny = ["unmaintained"] # exit on error if unmaintained dependencies are found 24 | # format = "terminal" # "terminal" (human readable report) or "json" 25 | # quiet = false # Only print information on error 26 | # show_tree = true # Show inverse dependency trees along with advisories (default: true) 27 | 28 | # # Target Configuration 29 | # [target] 30 | # arch = "x86_64" # Ignore advisories for CPU architectures other than this one 31 | # os = "linux" # Ignore advisories for operating systems other than this one 32 | 33 | # [yanked] 34 | # enabled = true # Warn for yanked crates in Cargo.lock (default: true) 35 | # update_index = true # Auto-update the crates.io index (default: true) 36 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | CACHE_DIR = { value = ".cache", relative = true } 3 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/.idea 2 | .cargo 3 | .dockerignore 4 | .fossa.yml 5 | .git 6 | .github 7 | CHANGELOG.md 8 | CODE_OF_CONDUCT.md 9 | CONTRIBUTING.md 10 | Containerfile.debian 11 | Dockerfile 12 | GNUmakefile 13 | LICENSE.txt 14 | README.md 15 | SECURITY.md 16 | build 17 | 18 | .vscode/** 19 | 20 | **/target 21 | target 22 | 23 | .cache/ 24 | .nginx/ 25 | -------------------------------------------------------------------------------- /.fossa.yml: -------------------------------------------------------------------------------- 1 | version: 3 2 | 3 | project: 4 | id: github.com/nginxinc/ngx-rust 5 | name: ngx-rust 6 | url: github.com/nginxinc/ngx-rust 7 | 8 | paths: 9 | exclude: 10 | - ./cache 11 | - ./nginx 12 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # This file specifies the revisions hidden from the blame view in GitHub UI. 2 | # 3 | # You can achieve the same effect locally with "--ignore-revs-file": 4 | # 5 | # git blame --ignore-revs-file .git-blame-ignore-revs 6 | # 7 | # or "blame.ignoreRevsFile" configuration option 8 | # 9 | # git config --local blame.ignoreRevsFile .git-blame-ignore-revs 10 | # 11 | # See also: 12 | # https://docs.github.com/en/repositories/working-with-files/using-files/viewing-and-understanding-files 13 | 14 | # Tree-wide formatting change: max-width 120 -> 100 15 | d866b64a3c83105c20b0c005392f8e8fcaaf2066 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. See my project that depends on this project 13 | 2. View logs on '....' 14 | 3. Run this test I created 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Your environment** 21 | * Version of the repo - a specific commit or tag 22 | * Version of ngx-rust 23 | * Version of Rust 24 | * Version of NGINX (and all dependencies if modified) 25 | * OS and distribution 26 | * Details about containerization, virtualization, or physical environment 27 | 28 | **Additional context** 29 | Add any other context about the problem here. Any log files you want to share. 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Proposed changes 2 | Describe the use case and detail of the change. If this PR addresses an issue 3 | on GitHub, make sure to include a link to that issue here in this description 4 | (not in the title of the PR). 5 | 6 | ### Checklist 7 | Before creating a PR, run through this checklist and mark each as complete. 8 | 9 | - [ ] I have written my commit messages in the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format. 10 | - [ ] I have read the [CONTRIBUTING](/CONTRIBUTING.md) doc 11 | - [ ] I have added tests (when possible) that prove my fix is effective or that my feature works 12 | - [ ] I have checked that all unit tests pass after adding my changes 13 | - [ ] I have updated necessary documentation 14 | - [ ] I have rebased my branch onto master 15 | - [ ] I will ensure my PR is targeting the main branch and pulling from my branch from my own fork 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 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/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | 9 | - package-ecosystem: "cargo" 10 | directory: "/" # Location of package manifests 11 | schedule: 12 | interval: "weekly" 13 | groups: 14 | all-dependencies: 15 | patterns: 16 | - "*" 17 | # semver-incompatible updates will be handled manually 18 | versioning-strategy: "lockfile-only" 19 | 20 | - package-ecosystem: "github-actions" 21 | directory: "/" 22 | schedule: 23 | interval: "weekly" 24 | -------------------------------------------------------------------------------- /.github/workflows/cargo-deny.yaml: -------------------------------------------------------------------------------- 1 | name: cargo-deny 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | 8 | jobs: 9 | check: 10 | runs-on: ubuntu-latest 11 | strategy: 12 | matrix: 13 | checks: 14 | - advisories 15 | - bans licenses sources 16 | 17 | # Prevent sudden announcement of a new advisory from failing ci: 18 | continue-on-error: ${{ matrix.checks == 'advisories' }} 19 | 20 | steps: 21 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 22 | - uses: EmbarkStudios/cargo-deny-action@34899fc7ba81ca6268d5947a7a16b4649013fea1 # v2.0.11 23 | with: 24 | command: check ${{ matrix.checks }} 25 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [ push, pull_request ] 4 | 5 | env: 6 | CARGO_TERM_COLOR: 'always' 7 | CARGO_TERM_VERBOSE: 'true' 8 | RUSTDOCFLAGS: '-Dwarnings' 9 | 10 | jobs: 11 | rust-version: 12 | name: Minimal supported Rust version 13 | outputs: 14 | version: ${{ steps.read_version.outputs.msrv }} 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 18 | - id: read_version 19 | run: | 20 | awk -F '=' \ 21 | '/^rust-version[[:space:]]*=/ { gsub(/([" ]|#.*)/,"",$2); print ("msrv=" $2) }' \ 22 | Cargo.toml \ 23 | | tee -a "$GITHUB_OUTPUT" 24 | 25 | test-linux: 26 | name: Test (Linux) 27 | needs: rust-version 28 | runs-on: ubuntu-latest 29 | strategy: 30 | matrix: 31 | rust-version: 32 | - ${{ needs.rust-version.outputs.version }} 33 | - stable 34 | steps: 35 | - name: checkout source 36 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 37 | - name: set up cargo cache 38 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 39 | continue-on-error: false 40 | with: 41 | path: | 42 | ~/.cargo/bin/ 43 | ~/.cargo/registry/index/ 44 | ~/.cargo/registry/cache/ 45 | ~/.cargo/git/db/ 46 | target/ 47 | key: ${{ runner.os }}-cargo-${{ matrix.rust-version}}-${{ hashFiles('**/Cargo.lock') }} 48 | restore-keys: ${{ runner.os }}-cargo-${{ matrix.rust-version }}- 49 | - name: set up nginx deps cache 50 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 51 | continue-on-error: false 52 | with: 53 | path: | 54 | .cache/.gnupg 55 | .cache/nginx 56 | .cache/*.tar.gz 57 | .cache/*.tar.asc 58 | .cache/*.tar.sig 59 | key: ${{ runner.os }}-deps-${{ hashFiles('**/nginx-sys/build.rs') }} 60 | restore-keys: ${{ runner.os }}-deps- 61 | 62 | - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b 63 | with: 64 | components: rustfmt, clippy 65 | toolchain: ${{ matrix.rust-version }} 66 | 67 | - name: build 68 | id: build 69 | run: cargo build --workspace --all-targets --all-features 70 | 71 | - name: run clippy 72 | # always run if build succeeds 73 | if: ${{ !cancelled() && steps.build.outcome == 'success' }} 74 | run: cargo clippy --workspace --all-targets --all-features -- -Dwarnings 75 | 76 | - name: run tests 77 | # always run if build succeeds 78 | if: ${{ !cancelled() && steps.build.outcome == 'success' }} 79 | run: cargo test --workspace --all-features 80 | 81 | - name: rustdoc 82 | # always run if build succeeds 83 | if: ${{ !cancelled() && steps.build.outcome == 'success' }} 84 | run: cargo doc --no-deps 85 | 86 | examples-linux: 87 | name: Examples (Linux) 88 | runs-on: ubuntu-latest 89 | steps: 90 | - name: checkout source 91 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 92 | - name: set up cargo cache 93 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 94 | continue-on-error: false 95 | with: 96 | path: | 97 | ~/.cargo/bin/ 98 | ~/.cargo/registry/index/ 99 | ~/.cargo/registry/cache/ 100 | ~/.cargo/git/db/ 101 | target/ 102 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 103 | restore-keys: ${{ runner.os }}-cargo- 104 | - name: set up nginx deps cache 105 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 106 | continue-on-error: false 107 | with: 108 | path: | 109 | .cache/.gnupg 110 | .cache/nginx 111 | .cache/*.tar.gz 112 | .cache/*.tar.asc 113 | .cache/*.tar.sig 114 | key: ${{ runner.os }}-deps-${{ hashFiles('**/nginx-sys/build.rs') }} 115 | restore-keys: ${{ runner.os }}-deps- 116 | - name: compile examples 117 | run: cargo build --release --package examples --examples --all-features 118 | 119 | test-macos: 120 | name: Test (MacOS) 121 | runs-on: macos-latest 122 | steps: 123 | - name: install command line dependencies 124 | run: brew install make gnupg 125 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 126 | - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b 127 | with: 128 | toolchain: stable 129 | - name: set up cargo cache 130 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 131 | continue-on-error: false 132 | with: 133 | path: | 134 | ~/.cargo/bin/ 135 | ~/.cargo/registry/index/ 136 | ~/.cargo/registry/cache/ 137 | ~/.cargo/git/db/ 138 | target/ 139 | key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} 140 | restore-keys: ${{ runner.os }}-cargo- 141 | - name: set up nginx deps cache 142 | uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 143 | continue-on-error: false 144 | with: 145 | path: | 146 | .cache/*.tar.gz 147 | .cache/*.tar.asc 148 | .cache/*.tar.sig 149 | key: ${{ runner.os }}-deps-${{ hashFiles('**/nginx-sys/build.rs') }} 150 | restore-keys: ${{ runner.os }}-deps- 151 | - name: current directory 152 | run: pwd 153 | - name: make cache directory 154 | run: mkdir -p .cache/.gnupg 155 | - name: disable ipv6 for gpg 156 | run: echo "disable-ipv6" > .cache/.gnupg/dirmngr.conf 157 | - name: build 158 | run: cargo build 159 | - name: run tests 160 | run: cargo test --workspace 161 | 162 | fmt: 163 | name: Rustfmt 164 | runs-on: ubuntu-latest 165 | steps: 166 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 167 | - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b 168 | with: 169 | components: rustfmt 170 | toolchain: stable 171 | - name: rustfmt version 172 | run: rustfmt --version 173 | - name: cargo fmt 174 | run: cargo fmt --all --check 175 | -------------------------------------------------------------------------------- /.github/workflows/fossa.yaml: -------------------------------------------------------------------------------- 1 | name: License Scanning 2 | 3 | on: 4 | - push 5 | 6 | jobs: 7 | fossa: 8 | # This job is only useful when run on upstream 9 | if: github.repository == 'nginx/ngx-rust' || github.event_name == 'workflow_dispatch' 10 | name: FOSSA scan 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout code 14 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 15 | 16 | - name: Run FOSSA scan and upload build data 17 | uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0 18 | with: 19 | api-key: ${{ secrets.FOSSA_API_KEY }} 20 | -------------------------------------------------------------------------------- /.github/workflows/nginx.yaml: -------------------------------------------------------------------------------- 1 | name: NGINX 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | env: 10 | CARGO_TERM_COLOR: 'always' 11 | RUST_BACKTRACE: '1' 12 | NGX_CONFIGURE_CMD: >- 13 | auto/configure 14 | --with-compat 15 | --with-debug 16 | --with-http_realip_module 17 | --with-http_ssl_module 18 | --with-http_v2_module 19 | --with-stream 20 | --with-stream_realip_module 21 | --with-stream_ssl_module 22 | 23 | NGX_CONFIGURE_UNIX: >- 24 | --with-threads 25 | 26 | NGX_CONFIGURE_WINDOWS: >- 27 | --with-cc=cl 28 | --prefix= 29 | --conf-path=conf/nginx.conf 30 | --pid-path=logs/nginx.pid 31 | --http-log-path=logs/access.log 32 | --error-log-path=logs/error.log 33 | --sbin-path=nginx.exe 34 | --http-client-body-temp-path=temp/client_body_temp 35 | --http-proxy-temp-path=temp/proxy_temp 36 | --http-fastcgi-temp-path=temp/fastcgi_temp 37 | --http-scgi-temp-path=temp/scgi_temp 38 | --http-uwsgi-temp-path=temp/uwsgi_temp 39 | --with-cc-opt=-DFD_SETSIZE=1024 40 | --with-pcre=objs/lib/pcre 41 | --with-zlib=objs/lib/zlib 42 | --with-openssl=objs/lib/openssl 43 | --with-openssl-opt="no-asm no-module no-tests -D_WIN32_WINNT=0x0601" 44 | 45 | NGX_CONFIGURE_DYNAMIC_MODULES: >- 46 | --add-dynamic-module=${{ github.workspace }}/examples 47 | NGX_CONFIGURE_STATIC_MODULES: >- 48 | --add-module=${{ github.workspace }}/examples 49 | 50 | NGX_TEST_FILES: examples/t 51 | NGX_TEST_GLOBALS_DYNAMIC: >- 52 | load_module ${{ github.workspace }}/nginx/objs/ngx_http_async_module.so; 53 | load_module ${{ github.workspace }}/nginx/objs/ngx_http_awssigv4_module.so; 54 | load_module ${{ github.workspace }}/nginx/objs/ngx_http_curl_module.so; 55 | load_module ${{ github.workspace }}/nginx/objs/ngx_http_upstream_custom_module.so; 56 | 57 | OPENSSL_VERSION: '3.0.16' 58 | PCRE2_VERSION: '10.45' 59 | ZLIB_VERSION: '1.3.1' 60 | 61 | jobs: 62 | linux: 63 | runs-on: ubuntu-latest 64 | 65 | strategy: 66 | fail-fast: false 67 | matrix: 68 | nginx-ref: 69 | - master 70 | - stable-1.28 71 | module: 72 | - static 73 | - dynamic 74 | include: 75 | - nginx-ref: stable-1.24 76 | module: dynamic 77 | 78 | steps: 79 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 80 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 81 | with: 82 | ref: ${{ matrix.nginx-ref }} 83 | repository: 'nginx/nginx' 84 | path: 'nginx' 85 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 86 | with: 87 | repository: 'nginx/nginx-tests' 88 | path: 'nginx/tests' 89 | sparse-checkout: | 90 | lib 91 | 92 | - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b 93 | with: 94 | toolchain: stable 95 | 96 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 97 | with: 98 | path: | 99 | ~/.cargo/bin/ 100 | ~/.cargo/registry/index/ 101 | ~/.cargo/registry/cache/ 102 | ~/.cargo/git/db/ 103 | nginx/objs/**/CACHEDIR.TAG 104 | nginx/objs/**/ngx-debug 105 | nginx/objs/**/ngx-release 106 | key: ${{ runner.os }}-nginx-${{ hashFiles('**/Cargo.lock') }} 107 | restore-keys: ${{ runner.os }}-nginx- 108 | 109 | - name: Update configure arguments 110 | if: matrix.nginx-ref != 'stable-1.24' 111 | run: | 112 | echo NGX_CONFIGURE_UNIX="${NGX_CONFIGURE_UNIX} --with-http_v3_module" \ 113 | >> "$GITHUB_ENV" 114 | 115 | - name: Configure nginx with static modules 116 | if: matrix.module == 'static' 117 | working-directory: nginx 118 | run: | 119 | ${NGX_CONFIGURE_CMD} \ 120 | ${NGX_CONFIGURE_UNIX} \ 121 | ${NGX_CONFIGURE_STATIC_MODULES} 122 | 123 | - name: Configure nginx with dynamic modules 124 | if: matrix.module != 'static' 125 | working-directory: nginx 126 | run: | 127 | ${NGX_CONFIGURE_CMD} \ 128 | ${NGX_CONFIGURE_UNIX} \ 129 | ${NGX_CONFIGURE_DYNAMIC_MODULES} 130 | echo TEST_NGINX_GLOBALS="$NGX_TEST_GLOBALS_DYNAMIC" >> "$GITHUB_ENV" 131 | 132 | - name: Build nginx 133 | working-directory: nginx 134 | run: make -j$(nproc) 135 | 136 | - name: Run tests 137 | env: 138 | PERL5LIB: ${{ github.workspace }}/nginx/tests/lib 139 | TEST_NGINX_BINARY: ${{ github.workspace }}/nginx/objs/nginx 140 | TEST_NGINX_MODULES: ${{ github.workspace }}/nginx/objs 141 | TEST_NGINX_VERBOSE: 1 142 | run: | 143 | prove -j$(nproc) --state=save ${NGX_TEST_FILES} || prove -v --state=failed 144 | 145 | windows: 146 | runs-on: windows-2022 147 | env: 148 | TEMP: "C:\\TEMP" 149 | TMP: "C:\\TEMP" 150 | TMPDIR: "C:\\TEMP" 151 | VCARCH: x64 152 | 153 | strategy: 154 | fail-fast: false 155 | matrix: 156 | nginx-ref: 157 | - master 158 | module: 159 | - static 160 | 161 | steps: 162 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 163 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 164 | with: 165 | ref: ${{ matrix.nginx-ref }} 166 | repository: 'nginx/nginx' 167 | path: 'nginx' 168 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 169 | with: 170 | repository: 'nginx/nginx-tests' 171 | path: 'nginx/tests' 172 | sparse-checkout: | 173 | lib 174 | 175 | - uses: dtolnay/rust-toolchain@b3b07ba8b418998c39fb20f53e8b695cdcc8de1b 176 | with: 177 | toolchain: stable 178 | 179 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 180 | with: 181 | path: | 182 | ~/.cargo/bin/ 183 | ~/.cargo/registry/index/ 184 | ~/.cargo/registry/cache/ 185 | ~/.cargo/git/db/ 186 | nginx/objs/**/CACHEDIR.TAG 187 | nginx/objs/**/ngx-debug 188 | nginx/objs/**/ngx-release 189 | key: ${{ runner.os }}-nginx-${{ hashFiles('**/Cargo.lock') }} 190 | restore-keys: ${{ runner.os }}-nginx- 191 | 192 | - name: Prepare build environment 193 | shell: bash 194 | working-directory: nginx 195 | run: | 196 | # Disable dynamic lookup of WSAPoll(); it crashes if the symbol is already imported by 197 | # Rust stdlib. 198 | sed 's/\(_WIN32_WINNT\s*\) 0x0501/\1 0x0601/' -i src/os/win32/ngx_win32_config.h 199 | 200 | echo VCVARSALL="$('C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe' -products \* -latest -property installationPath)\\VC\\Auxiliary\\Build\\vcvarsall.bat" \ 201 | >> "$GITHUB_ENV" 202 | 203 | mkdir -p $TEMP 204 | mkdir -p objs/lib 205 | 206 | curl -sLO https://github.com/PCRE2Project/pcre2/releases/download/pcre2-$PCRE2_VERSION/pcre2-$PCRE2_VERSION.tar.gz 207 | tar -C objs/lib --transform "s/pcre2-$PCRE2_VERSION/pcre/" -xzf ./pcre2-$PCRE2_VERSION.tar.gz 208 | echo '#include ' > objs/lib/pcre/src/inttypes.h 209 | 210 | curl -sLO https://github.com/madler/zlib/releases/download/v$ZLIB_VERSION/zlib-$ZLIB_VERSION.tar.gz 211 | tar -C objs/lib --transform "s/zlib-$ZLIB_VERSION/zlib/" -xzf ./zlib-$ZLIB_VERSION.tar.gz 212 | 213 | curl -sLO https://github.com/openssl/openssl/releases/download/openssl-$OPENSSL_VERSION/openssl-$OPENSSL_VERSION.tar.gz 214 | tar -C objs/lib --transform "s/openssl-$OPENSSL_VERSION/openssl/" -xzf ./openssl-$OPENSSL_VERSION.tar.gz 215 | 216 | - name: Configure and build nginx 217 | shell: cmd 218 | working-directory: nginx 219 | run: | 220 | @echo on 221 | call "%VCVARSALL%" %VCARCH% 222 | bash.exe ^ 223 | %NGX_CONFIGURE_CMD% ^ 224 | %NGX_CONFIGURE_WINDOWS% ^ 225 | %NGX_CONFIGURE_STATIC_MODULES% 226 | nmake -f objs/Makefile 227 | 228 | - name: Run tests 229 | shell: cmd 230 | env: 231 | PERL5LIB: "${{ github.workspace }}\\nginx\\tests\\lib" 232 | TEST_NGINX_BINARY: "${{ github.workspace }}\\nginx\\objs\\nginx.exe" 233 | TEST_NGINX_VERBOSE: 1 234 | run: | 235 | prove --state=save %NGX_TEST_FILES% || prove -v --state=failed 236 | -------------------------------------------------------------------------------- /.github/workflows/sanitizers.yaml: -------------------------------------------------------------------------------- 1 | name: sanitizers 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | env: 10 | CARGO_TERM_COLOR: 'always' 11 | RUST_BACKTRACE: '1' 12 | BUILDREQUIRES: >- 13 | openssl-devel pcre2-devel zlib-devel 14 | cargo rust-src rustfmt 15 | clang compiler-rt 16 | git-core 17 | make patch 18 | perl-FindBin 19 | perl-IO-Socket-SSL 20 | perl-Test-Harness 21 | perl-Test-Simple 22 | perl-lib 23 | 24 | jobs: 25 | test: 26 | runs-on: ubuntu-latest 27 | container: ghcr.io/almalinux/almalinux:10 28 | 29 | strategy: 30 | fail-fast: false 31 | matrix: 32 | nginx-ref: 33 | # master 34 | - stable-1.28 35 | 36 | steps: 37 | - name: Install dependencies 38 | run: dnf install -y ${BUILDREQUIRES} 39 | 40 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 41 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 42 | with: 43 | ref: ${{ matrix.nginx-ref }} 44 | repository: 'nginx/nginx' 45 | path: 'nginx' 46 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 47 | with: 48 | repository: 'nginx/nginx-tests' 49 | path: 'nginx/tests' 50 | 51 | - uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 52 | with: 53 | path: | 54 | ~/.cargo/bin/ 55 | ~/.cargo/registry/index/ 56 | ~/.cargo/registry/cache/ 57 | ~/.cargo/git/db/ 58 | nginx/objs/ngx_rust_examples 59 | key: ${{ runner.os }}-cargo-asan-${{ hashFiles('**/Cargo.lock') }} 60 | restore-keys: ${{ runner.os }}-cargo-asan- 61 | 62 | - name: Configure and build nginx 63 | working-directory: nginx 64 | env: 65 | CFLAGS: >- 66 | -DNGX_DEBUG_PALLOC=1 67 | -DNGX_SUPPRESS_WARN=1 68 | -O1 69 | -fno-omit-frame-pointer 70 | -fsanitize=address,undefined 71 | LDFLAGS: -fsanitize=address,undefined 72 | RUST_TARGET: x86_64-unknown-linux-gnu 73 | RUSTFLAGS: -Zsanitizer=address -Zexternal-clangrt 74 | # Extra options passed to cargo rustc 75 | NGX_RUSTC_OPT: -Zbuild-std 76 | # Enable unstable features, such as the -Z options above, 77 | # in the stable toolchain. 78 | RUSTC_BOOTSTRAP: 1 79 | run: | 80 | patch -p1 < $GITHUB_WORKSPACE/misc/nginx-sanitizer-support.patch 81 | auto/configure \ 82 | --with-cc=clang \ 83 | --with-cc-opt="$CFLAGS" \ 84 | --with-ld-opt="$LDFLAGS" \ 85 | --with-compat \ 86 | --with-debug \ 87 | --with-http_ssl_module \ 88 | --with-http_v2_module \ 89 | --with-http_v3_module \ 90 | --with-stream \ 91 | --with-stream_ssl_module \ 92 | --with-threads \ 93 | --add-module=$(realpath ../examples) 94 | make -j$(nproc) 95 | 96 | - name: Run tests 97 | env: 98 | ASAN_OPTIONS: detect_stack_use_after_return=1:detect_odr_violation=0 99 | # `container` job steps are running as root, and thus all the files 100 | # created by the test scripts are owned by root. 101 | # But the worker processes are spawned as "nobody" by default, 102 | # resulting in permission errors. 103 | TEST_NGINX_GLOBALS: >- 104 | user root; 105 | run: | 106 | TEST_NGINX_BINARY="$PWD/nginx/objs/nginx" \ 107 | LSAN_OPTIONS="suppressions=$PWD/misc/lsan-suppressions.txt" \ 108 | UBSAN_OPTIONS="suppressions=$PWD/misc/ubsan-suppressions.txt" \ 109 | prove -v -Inginx/tests/lib examples/t 110 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.toptal.com/developers/gitignore/api/c,rust,intellij+all,visualstudiocode,vim,emacs 2 | # Edit at https://www.toptal.com/developers/gitignore?templates=c,rust,intellij+all,visualstudiocode,vim,emacs 3 | 4 | ### C ### 5 | # Prerequisites 6 | *.d 7 | 8 | # Object files 9 | *.o 10 | *.ko 11 | *.obj 12 | *.elf 13 | 14 | # Linker output 15 | *.ilk 16 | *.map 17 | *.exp 18 | 19 | # Precompiled Headers 20 | *.gch 21 | *.pch 22 | 23 | # Libraries 24 | *.lib 25 | *.a 26 | *.la 27 | *.lo 28 | 29 | # Shared objects (inc. Windows DLLs) 30 | *.dll 31 | *.so 32 | *.so.* 33 | *.dylib 34 | 35 | # Executables 36 | *.exe 37 | *.out 38 | *.app 39 | *.i*86 40 | *.x86_64 41 | *.hex 42 | 43 | # Debug files 44 | *.dSYM/ 45 | *.su 46 | *.idb 47 | *.pdb 48 | 49 | # Kernel Module Compile Results 50 | *.mod* 51 | *.cmd 52 | .tmp_versions/ 53 | modules.order 54 | Module.symvers 55 | Mkfile.old 56 | dkms.conf 57 | 58 | ### Emacs ### 59 | # -*- mode: gitignore; -*- 60 | *~ 61 | \#*\# 62 | /.emacs.desktop 63 | /.emacs.desktop.lock 64 | *.elc 65 | auto-save-list 66 | tramp 67 | .\#* 68 | 69 | # Org-mode 70 | .org-id-locations 71 | *_archive 72 | 73 | # flymake-mode 74 | *_flymake.* 75 | 76 | # eshell files 77 | /eshell/history 78 | /eshell/lastdir 79 | 80 | # elpa packages 81 | /elpa/ 82 | 83 | # reftex files 84 | *.rel 85 | 86 | # AUCTeX auto folder 87 | /auto/ 88 | 89 | # cask packages 90 | .cask/ 91 | dist/ 92 | 93 | # Flycheck 94 | flycheck_*.el 95 | 96 | # server auth directory 97 | /server/ 98 | 99 | # projectiles files 100 | .projectile 101 | 102 | # directory configuration 103 | .dir-locals.el 104 | 105 | # network security 106 | /network-security.data 107 | 108 | 109 | ### Intellij+all ### 110 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 111 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 112 | 113 | # User-specific stuff 114 | .idea/**/workspace.xml 115 | .idea/**/tasks.xml 116 | .idea/**/usage.statistics.xml 117 | .idea/**/dictionaries 118 | .idea/**/shelf 119 | 120 | # AWS User-specific 121 | .idea/**/aws.xml 122 | 123 | # Generated files 124 | .idea/**/contentModel.xml 125 | 126 | # Sensitive or high-churn files 127 | .idea/**/dataSources/ 128 | .idea/**/dataSources.ids 129 | .idea/**/dataSources.local.xml 130 | .idea/**/sqlDataSources.xml 131 | .idea/**/dynamic.xml 132 | .idea/**/uiDesigner.xml 133 | .idea/**/dbnavigator.xml 134 | 135 | # Gradle 136 | .idea/**/gradle.xml 137 | .idea/**/libraries 138 | 139 | # Gradle and Maven with auto-import 140 | # When using Gradle or Maven with auto-import, you should exclude module files, 141 | # since they will be recreated, and may cause churn. Uncomment if using 142 | # auto-import. 143 | # .idea/artifacts 144 | # .idea/compiler.xml 145 | # .idea/jarRepositories.xml 146 | # .idea/modules.xml 147 | # .idea/*.iml 148 | # .idea/modules 149 | # *.iml 150 | # *.ipr 151 | 152 | # CMake 153 | cmake-build-*/ 154 | 155 | # Mongo Explorer plugin 156 | .idea/**/mongoSettings.xml 157 | 158 | # File-based project format 159 | *.iws 160 | 161 | # IntelliJ 162 | out/ 163 | 164 | # mpeltonen/sbt-idea plugin 165 | .idea_modules/ 166 | 167 | # JIRA plugin 168 | atlassian-ide-plugin.xml 169 | 170 | # Cursive Clojure plugin 171 | .idea/replstate.xml 172 | 173 | # SonarLint plugin 174 | .idea/sonarlint/ 175 | 176 | # Crashlytics plugin (for Android Studio and IntelliJ) 177 | com_crashlytics_export_strings.xml 178 | crashlytics.properties 179 | crashlytics-build.properties 180 | fabric.properties 181 | 182 | # Editor-based Rest Client 183 | .idea/httpRequests 184 | 185 | # Android studio 3.1+ serialized cache file 186 | .idea/caches/build_file_checksums.ser 187 | 188 | ### Intellij+all Patch ### 189 | # Ignore everything but code style settings and run configurations 190 | # that are supposed to be shared within teams. 191 | 192 | .idea/* 193 | 194 | !.idea/codeStyles 195 | !.idea/runConfigurations 196 | 197 | ### Rust ### 198 | # Generated by Cargo 199 | # will have compiled files and executables 200 | debug/ 201 | target/ 202 | 203 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 204 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 205 | Cargo.lock 206 | 207 | # These are backup files generated by rustfmt 208 | **/*.rs.bk 209 | 210 | # MSVC Windows builds of rustc generate these, which store debugging information 211 | 212 | ### Vim ### 213 | # Swap 214 | [._]*.s[a-v][a-z] 215 | !*.svg # comment out if you don't need vector files 216 | [._]*.sw[a-p] 217 | [._]s[a-rt-v][a-z] 218 | [._]ss[a-gi-z] 219 | [._]sw[a-p] 220 | 221 | # Session 222 | Session.vim 223 | Sessionx.vim 224 | 225 | # Temporary 226 | .netrwhist 227 | # Auto-generated tag files 228 | tags 229 | # Persistent undo 230 | [._]*.un~ 231 | 232 | ### VisualStudioCode ### 233 | .vscode/* 234 | !.vscode/settings.json 235 | !.vscode/tasks.json 236 | !.vscode/launch.json 237 | !.vscode/extensions.json 238 | !.vscode/*.code-snippets 239 | 240 | # Local History for Visual Studio Code 241 | .history/ 242 | 243 | # Built Visual Studio Code Extensions 244 | *.vsix 245 | 246 | ### VisualStudioCode Patch ### 247 | # Ignore all local history of files 248 | .history 249 | .ionide 250 | 251 | # End of https://www.toptal.com/developers/gitignore/api/c,rust,intellij+all,visualstudiocode,vim,emacs 252 | 253 | .vscode/c_cpp_properties.json 254 | .cache/ 255 | .nginx/ 256 | .DS_Store 257 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | ## Release v0.4.1 6 | * release: ngx 0.4.1 (9d2ce0d) 7 | * release: nginx-sys 0.2.1 (89eb277) 8 | * fix(#50): user_agent method returns 'Option<&NgxStr>' and not '&NgxStr' (8706830) 9 | * fix(#41): zlib version update (89e0fcc) 10 | * fix: check user_agent for null (0ca0bc9) 11 | * feat: (2 of 2) Upstream module example (#37) (840d789) 12 | * feat: (1 of 2) Supporting changes for upstream example module. (#36) (8b3a119) 13 | * Revert "ci: use GH cache for .cache folder on MAC OS" (7972ae7) 14 | * docs: updated changelog (74604e2) 15 | 16 | ## Release 0.4.0-beta 17 | * realease: bump nginx-sys to 0.2.0 (ad093d8) 18 | * feat: unsafe updates for raw pointer arguments (1b88323) 19 | * feat: Add debug log mask support (#40) (57c68ff) 20 | * docs: add support badge to README (4aa2e35) 21 | * cargo: make macOS linker flags workaround apply to Apple silicon (f4ea26f) 22 | * docs: added repostatus and crates badges (dd687a4) 23 | 24 | ## Initial release 0.3.0-beta 25 | * docs: prepare ngx to be published to crates.io (a1bff29) 26 | * fix: nginx-sys enable a few modules by default (f23e4b1) 27 | * !misc: project refactor and new module structure (b3e8f45) 28 | * update README (#18) (d2c0b3a) 29 | * Fix usage example in README (#16) (8deaec0) 30 | * use nginxinc namespace (9bb9ef6) 31 | * upgrade to rust 1.26 (#15) (bbfc114) 32 | * 1.39 upgrade (#12) (be9d532) 33 | * add pkg-config (#11) (a33c329) 34 | * upgrade to nginx 1.13.7 (#10) (8c6b968) 35 | * update the README (#9) (7693ea2) 36 | * Rust 1.21 (#8) (4fa395c) 37 | * Rust 1.21 (#7) (9517b56) 38 | * add rust docker tooling (8b4d492) 39 | * revert back tool tag (82bd0d6) 40 | * update the tag for rust tool image (f2418c0) 41 | * bump up tool image to 1.22 (7db2ced) 42 | * add rust tool on top of the nginx (9aa4fa0) 43 | * add developer version of nginx which can do ps (994c703) 44 | * upgrade nginx to 1.13.5 bump up cargo version number respectively separate out nginx make file (7095777) 45 | * consolidate makefile by reducing duplicates simplify docker command (cfe1756) 46 | * set default rust tooling to 1.20 generate linux binding in the cargo build fix the linux configuration (d396803) 47 | * build nginx binary for darwin add target for darwin (d2e04ce) 48 | * add configuration for linux (b3bf4da) 49 | * remove unnecessary reference to libc invoke make for build.rs in the mac (8e1c544) 50 | * add unit test for nginx server (d0ff3df) 51 | * Merge branch 'master' into module (4f6ed43) 52 | * update the README (a8cfe50) 53 | * add license file (805e70b) 54 | * add license file (8c574be) 55 | * add nginx instance module where you can start and stop nginx sample test for instance (acfc545) 56 | * upgrade bindgen to latest version remove redudant c module code update the nginx config (3102b5a) 57 | * update the tools make file to use Dockerhub (36ae1ca) 58 | * update the README (ba563fc) 59 | * add tools directory to generate docker image for rust toolchain check for nginx directory when setting up source (6842329) 60 | * add targets for building on linux (6176272) 61 | * make target for setting up darwin source add os specific build path (fbc5e2f) 62 | * initial check in generate binding (a38b8ab) 63 | 64 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement as listed on the 64 | github project page. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 121 | 122 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 123 | enforcement ladder](https://github.com/mozilla/diversity). 124 | 125 | [homepage]: https://www.contributor-covenant.org 126 | 127 | For answers to common questions about this code of conduct, see the FAQ at 128 | https://www.contributor-covenant.org/faq. Translations are available at 129 | https://www.contributor-covenant.org/translations. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | The following is a set of guidelines for contributing. We really appreciate that you are considering contributing! 4 | 5 | #### Table Of Contents 6 | 7 | [Ask a Question](#ask-a-question) 8 | 9 | [Contributing](#contributing) 10 | 11 | [Style Guides](#style-guides) 12 | * [Git Style Guide](#git-style-guide) 13 | * [Rust Style Guide](#rust-style-guide) 14 | 15 | [Code of Conduct](CODE_OF_CONDUCT.md) 16 | 17 | ## Ask a Question 18 | 19 | Please ask your question on github using discussions. 20 | 21 | ## Contributing 22 | 23 | ### Report a Bug 24 | 25 | To report a bug, open an issue on GitHub with the label `bug` using the available bug report issue template. Please ensure the issue has not already been reported. 26 | 27 | ### Suggest an Enhancement 28 | 29 | To suggest an enhancement, please create an issue on GitHub with the label `enhancement` using the available feature issue template. 30 | 31 | ### Open a Pull Request 32 | 33 | * Fork the repo, create a branch, submit a PR when your changes are tested and ready for review. 34 | * Fill in [our pull request template](/.github/PULL_REQUEST_TEMPLATE.md) 35 | 36 | Note: if you’d like to implement a new feature, please consider creating a feature request issue first to start a discussion about the feature. 37 | 38 | ## Style Guides 39 | 40 | ### Git Style Guide 41 | 42 | * Keep a clean, concise and meaningful git commit history on your branch, rebasing locally and squashing before submitting a PR 43 | * Use the [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) format when writing a commit message, so that changelogs can be automatically generated 44 | * Follow the guidelines of writing a good commit message as described [here](https://chris.beams.io/posts/git-commit/) and summarised in the next few points 45 | * In the subject line, use the present tense ("Add feature" not "Added feature") 46 | * In the subject line, use the imperative mood ("Move cursor to..." not "Moves cursor to...") 47 | * Limit the subject line to 72 characters or less 48 | * Reference issues and pull requests liberally after the subject line 49 | * Add more detailed description in the body of the git message (`git commit -a` to give you more space and time in your text editor to write a good message instead of `git commit -am`) 50 | 51 | ### Rust Style Guide 52 | 53 | * Rust code should be checked in after `cargo fmt` has been run. 54 | * The code style broadly complies with the [official Rust Style Guide](https://doc.rust-lang.org/style-guide/index.html). 55 | * Where feasible, include unit tests. 56 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "nginx-sys", 4 | "examples", 5 | ] 6 | 7 | [workspace.package] 8 | edition = "2021" 9 | license = "Apache-2.0" 10 | homepage = "https://github.com/nginx/ngx-rust" 11 | repository = "https://github.com/nginx/ngx-rust" 12 | rust-version = "1.79.0" 13 | 14 | [package] 15 | name = "ngx" 16 | version = "0.5.0" 17 | autoexamples = false 18 | categories = ["api-bindings", "network-programming"] 19 | description = "FFI bindings to NGINX" 20 | keywords = ["nginx", "module", "sys"] 21 | edition.workspace = true 22 | license.workspace = true 23 | homepage.workspace = true 24 | repository.workspace = true 25 | rust-version.workspace = true 26 | 27 | [dependencies] 28 | nginx-sys = { path = "nginx-sys", default-features=false, version = "0.5.0"} 29 | 30 | [features] 31 | default = ["vendored","std"] 32 | # Enables the components using memory allocation. 33 | # If no `std` flag, `alloc` crate is internally used instead. This flag is mainly for `no_std` build. 34 | alloc = [] 35 | # Enables the components using `std` crate. 36 | # Currently the only difference to `alloc` flag is `std::error::Error` implementation. 37 | std = ["alloc"] 38 | # Build our own copy of the NGINX by default. 39 | # This could be disabled with `--no-default-features` to minimize the dependency 40 | # tree when building against an existing copy of the NGINX with the 41 | # NGINX_SOURCE_DIR/NGINX_BUILD_DIR variables. 42 | vendored = ["nginx-sys/vendored"] 43 | 44 | [badges] 45 | maintenance = { status = "experimental" } 46 | 47 | [dev-dependencies] 48 | target-triple = "0.1.2" 49 | -------------------------------------------------------------------------------- /Containerfile.debian: -------------------------------------------------------------------------------- 1 | FROM rust:slim-bullseye 2 | 3 | RUN set -eux \ 4 | export DEBIAN_FRONTEND=noninteractive; \ 5 | apt-get -qq update; \ 6 | apt-get -qq install --yes --no-install-recommends --no-install-suggests \ 7 | libclang-dev \ 8 | libssl-dev \ 9 | pkg-config \ 10 | git \ 11 | grep \ 12 | gawk \ 13 | gnupg2 \ 14 | sed \ 15 | make; \ 16 | git config --global --add safe.directory /project -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG NGX_VERSION=1.28.0 2 | ARG NGX_DEBUG=false 3 | 4 | # --- builder: build all examples 5 | FROM rust:slim-bullseye AS build 6 | ARG NGX_VERSION 7 | ARG NGX_DEBUG 8 | WORKDIR /project 9 | RUN --mount=type=cache,target=/var/cache/apt < /dev/null || command -v grep 2> /dev/null) 10 | SED ?= $(shell command -v gsed 2> /dev/null || command -v sed 2> /dev/null) 11 | AWK ?= $(shell command -v gawk 2> /dev/null || command -v awk 2> /dev/null) 12 | VERSION ?= $(shell $(GREP) -Po '^version\s+=\s+"\K.*?(?=")' $(CURDIR)/Cargo.toml) 13 | CARGO ?= cargo 14 | DOCKER ?= docker 15 | DOCKER_BUILD_FLAGS ?= --load 16 | COMMITSAR_DOCKER := $(DOCKER) run --tty --rm --workdir /src -v "$(CURDIR):/src" aevea/commitsar 17 | COMMITSAR ?= $(shell command -v commitsar 2> /dev/null) 18 | PROJECT_NAME ?= ngx-rust 19 | GITHUB_REPOSITORY ?= nginx/$(PROJECT_NAME) 20 | SRC_REPO := https://github.com/$(GITHUB_REPOSITORY) 21 | 22 | RELEASE_BUILD_FLAGS ?= --quiet --release 23 | 24 | Q = $(if $(filter 1,$V),,@) 25 | M = $(shell printf "\033[34;1m▶\033[0m") 26 | 27 | UNAME_S := $(shell uname -s) 28 | ifeq ($(UNAME_S),Linux) 29 | FEATURES += --features=linux 30 | endif 31 | 32 | # Use docker based commitsar if it isn't in the path 33 | ifeq ($(COMMITSAR),) 34 | COMMITSAR = $(COMMITSAR_DOCKER) 35 | endif 36 | 37 | .PHONY: help 38 | help: 39 | @$(GREP) --no-filename -E '^[ a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | \ 40 | $(AWK) 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-28s\033[0m %s\n", $$1, $$2}' | sort 41 | 42 | .PHONY: clean 43 | clean: clean-cache; $(info $(M) cleaning...) @ ## Cleanup everything 44 | $Q rm -rf $(CURDIR)/target 45 | $Q $(CARGO) clean 46 | 47 | .PHONY: clean-cache 48 | clean-cache: ## Remove all cached dependencies and build artifacts 49 | $(Q) rm -rf $(CACHE_DIR) 50 | 51 | .PHONY: commitsar 52 | commitsar: ## Run git commit linter 53 | $Q $(info $(M) running commitsar...) 54 | $(COMMITSAR) 55 | 56 | target: 57 | $Q mkdir -p $@ 58 | 59 | .PHONY: debug 60 | debug: target/debug ## Build current platform target in debug mode 61 | 62 | target/debug: 63 | $Q echo "$(M) building in debug mode for the current platform" 64 | $Q $(CARGO) build --quiet 65 | 66 | .PHONY: release 67 | release: target/release ## Build current platform target in release mode 68 | 69 | target/release: 70 | $Q echo "$(M) building in release mode for the current platform" 71 | $Q $(CARGO) build $(RELEASE_BUILD_FLAGS) 72 | 73 | .PHONY: test 74 | test: ## Run tests 75 | $Q $(CARGO) test 76 | 77 | .PHONY: format 78 | format: ## Run rustfmt 79 | $Q $(CARGO) fmt 80 | 81 | .PHONY: lint 82 | lint: ## Run clippy 83 | $Q $(CARGO) clippy 84 | 85 | .PHONY: examples-debug 86 | examples-debug: 87 | $Q echo "$(M) building all examples as debug" 88 | $Q $(CARGO) build --quiet --package=examples --examples $(FEATURES) 89 | 90 | include $(CURDIR)/build/container.mk 91 | include $(CURDIR)/build/release.mk 92 | include $(CURDIR)/build/github.mk 93 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2016 Nginx, Inc. 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Rust](https://github.com/nginx/ngx-rust/actions/workflows/ci.yaml/badge.svg)](https://github.com/nginx/ngx-rust/actions/workflows/ci.yaml) 2 | [![crates.io](https://img.shields.io/crates/v/ngx.svg)](https://crates.io/crates/ngx) 3 | [![Project Status: Concept – Minimal or no implementation has been done yet, or the repository is only intended to be a limited example, demo, or proof-of-concept.](https://www.repostatus.org/badges/latest/concept.svg)](https://www.repostatus.org/#concept) 4 | [![Community Support](https://badgen.net/badge/support/community/cyan?icon=awesome)](https://github.com/nginx/ngx-rust/discussions) 5 | 6 | 7 | ## Project status 8 | This project is still a work in progress and not production ready. 9 | 10 | # Description 11 | 12 | This project provides Rust SDK interfaces to the [NGINX](https://nginx.com) proxy allowing the creation of NGINX 13 | dynamic modules completely in Rust. 14 | 15 | In short, this SDK allows writing NGINX modules using the Rust language. 16 | 17 | ## Build 18 | 19 | NGINX modules can be built against a particular version of NGINX. The following environment variables can be used to specify a particular version of NGINX or an NGINX dependency: 20 | 21 | * `ZLIB_VERSION` (default 1.3.1) - zlib version 22 | * `PCRE2_VERSION` (default 10.45 for NGINX 1.22.0 and later, or 8.45 for earlier) - PCRE1 or PCRE2 version 23 | * `OPENSSL_VERSION` (default 3.5.0 for NGINX 1.22.0 and later, or 1.1.1w for earlier) - OpenSSL version 24 | * `NGX_VERSION` (default 1.28.0) - NGINX OSS version 25 | * `NGX_DEBUG` (default to false) - if set to true, then will compile NGINX `--with-debug` option 26 | 27 | For example, this is how you would compile the [examples](examples) using a specific version of NGINX and enabling 28 | debugging: 29 | ``` 30 | NGX_DEBUG=true NGX_VERSION=1.23.0 cargo build --package=examples --examples --release 31 | ``` 32 | 33 | To build Linux-only modules, use the "linux" feature: 34 | ``` 35 | cargo build --package=examples --examples --features=linux --release 36 | ``` 37 | 38 | After compilation, the modules can be found in the path `target/release/examples/` ( with the `.so` file extension for 39 | Linux or `.dylib` for MacOS). 40 | 41 | Additionally, the folder `.cache/nginx/{NGX_VERSION}/{TARGET}` (`{TARGET}` means rustc's target triple string) will contain the compiled version of NGINX used to build 42 | the SDK. You can start NGINX directly from this directory if you want to test the module. 43 | 44 | The following environment variables can be used to change locations of cache directory and NGINX directory: 45 | 46 | * `CACHE_DIR` (default `[nginx-sys's root directory]/.cache`) - the directory containing cache files, means PGP keys, tarballs, PGP signatures, and unpacked source codes. It also contains compiled NGINX in default configuration. 47 | * `NGINX_INSTALL_ROOT_DIR` (default `{CACHE_DIR}/nginx`) - the directory containing the series of compiled NGINX in its subdirectories 48 | * `NGINX_INSTALL_DIR` (default `{NGINX_INSTALL_BASE_DIR}/{NGX_VERSION}/{TARGET}`) - the directory containing the NGINX compiled in the build 49 | 50 | ### Mac OS dependencies 51 | 52 | In order to use the optional GNU make build process on MacOS, you will need to install additional tools. This can be 53 | done via [homebrew](https://brew.sh/) with the following command: 54 | ``` 55 | brew install make openssl grep 56 | ``` 57 | 58 | Additionally, you may need to set up LLVM and clang. Typically, this is done as follows: 59 | 60 | ``` 61 | # make sure xcode tools are installed 62 | xcode-select --install 63 | # instal llvm 64 | brew install --with-toolchain llvm 65 | ``` 66 | 67 | ### Linux dependencies 68 | 69 | See the [Dockerfile](Dockerfile) for dependencies as an example of required packages on Debian Linux. 70 | 71 | ### Build example 72 | 73 | Example modules are available in [examples](examples) folder. You can use `cargo build --package=examples --examples` to build these examples. After building, you can find the `.so` or `.dylib` in the `target/debug` folder. Add `--features=linux` to build linux specific modules. **NOTE**: adding the "linux" feature on MacOS will cause a build failure. 74 | 75 | For example (all examples plus linux specific): 76 | `cargo build --package=examples --examples --features=linux` 77 | 78 | ### Build with external NGINX source tree 79 | 80 | If you require a customized NGINX configuration, you can build a module against an existing pre-configured source tree. 81 | To do that, you need to set the `NGINX_BUILD_DIR` variable to an _absolute_ path of the NGINX build directory (`--builddir`, defaults to the `objs` in the source directory). 82 | Only the `./configure` step of the NGINX build is mandatory because bindings don't depend on any of the artifacts generated by `make`. 83 | 84 | 85 | ``` 86 | NGINX_BUILD_DIR=$PWD/../nginx/objs cargo build --package=examples --examples 87 | 88 | ``` 89 | 90 | Furthermore, this approach can be leveraged to build a module as a part of the NGINX build process by adding the `--add-module`/`--add-dynamic-module` options to the configure script. 91 | See the following example integration scripts: [`examples/config`](examples/config) and [`examples/config.make`](examples/config.make). 92 | 93 | ### Docker 94 | 95 | We provide a multistage [Dockerfile](Dockerfile): 96 | 97 | # build all dynamic modules examples and specify NGINX version to use 98 | docker buildx build --build-arg NGX_VERSION=1.23.3 -t ngx-rust . 99 | 100 | # start NGINX using [curl](examples/curl.conf) module example: 101 | docker run --rm -d -p 8000:8000 ngx-rust nginx -c examples/curl.conf 102 | 103 | # test it - you should see 403 Forbidden 104 | curl http://127.0.0.1:8000 -v -H "user-agent: curl" 105 | 106 | 107 | # test it - you should see 404 Not Found 108 | curl http://127.0.0.1:8000 -v -H "user-agent: foo" 109 | 110 | ## Usage 111 | 112 | A complete module example using the SDK can be found [here](examples/curl.rs). You can build it with 113 | `cargo build --package=examples --example=curl` then set up NGINX to use it: 114 | 115 | For example: 116 | ```nginx 117 | daemon off; 118 | master_process off; 119 | 120 | # unix: 121 | # load_module modules/libcurl.so; 122 | 123 | # error_log logs/error.log debug; 124 | error_log /dev/stdout debug; 125 | 126 | working_directory /tmp/cores/; 127 | worker_rlimit_core 500M; 128 | 129 | events { 130 | } 131 | 132 | http { 133 | access_log /dev/stdout; 134 | server { 135 | listen 8000; 136 | server_name localhost; 137 | location / { 138 | alias /srv/http; 139 | # ... Other config stuff ... 140 | 141 | curl on; 142 | } 143 | } 144 | } 145 | ``` 146 | 147 | ## Support 148 | This SDK is currently unstable. Right now, our primary goal is collect feedback and stabilize it be before 149 | offering support. Feel free [contributing](CONTRIBUTING.md) by creating issues, PRs, or github discussions. 150 | 151 | Currently, the only supported platforms are: 152 | * Darwin (Mac OSX) 153 | * Linux platform 154 | 155 | ## Roadmap 156 | If you have ideas for releases in the future, please suggest them in the github discussions. 157 | 158 | ## Contributing 159 | 160 | We welcome pull requests and issues! 161 | 162 | Please refer to the [Contributing Guidelines](CONTRIBUTING.md) when doing a PR. 163 | 164 | ## Authors and acknowledgment 165 | This project uses some great work from [dcoles/nginx-rs](https://github.com/dcoles/nginx-rs), 166 | [arvancloud/nginx-rs](https://github.com/arvancloud/nginx-rs). 167 | 168 | ## Projects using the SDK 169 | 170 | [ngx-strict-sni](https://github.com/JyJyJcr/ngx-strict-sni) - Strict SNI validator for Nginx 171 | 172 | ## License 173 | 174 | All code in this repository is licensed under the 175 | [Apache License v2 license](LICENSE). 176 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## General Guidance 4 | 5 | We advise users to run the most recent release of the NGINX Rust Module SDK, and we issue software updates to the 6 | most recent release. 7 | 8 | ## Support 9 | 10 | The NGINX Rust Module SDK is not officially supported. 11 | 12 | ## Reporting a Vulnerability 13 | 14 | The F5 Security Incident Response Team (F5 SIRT) has an email alias that makes it easy to report potential security 15 | vulnerabilities. 16 | 17 | - If you’re an F5 customer with an active support contract, please contact 18 | [F5 Technical Support](https://www.f5.com/services/support). 19 | - If you aren’t an F5 customer, please report any potential or current instances of security vulnerabilities with any 20 | F5 product to the F5 Security Incident Response Team at F5SIRT@f5.com 21 | 22 | For more information visit https://www.f5.com/services/support/report-a-vulnerability 23 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | /// Example buildscript for an nginx module. 2 | /// 3 | /// Due to the limitations of cargo[1], this buildscript _requires_ adding `nginx-sys` to the 4 | /// direct dependencies of your crate. 5 | /// 6 | /// [1]: https://github.com/rust-lang/cargo/issues/3544 7 | fn main() { 8 | // Generate `ngx_os` and `ngx_feature` cfg values 9 | 10 | // Specify acceptable values for `ngx_feature` 11 | println!("cargo::rerun-if-env-changed=DEP_NGINX_FEATURES_CHECK"); 12 | println!( 13 | "cargo::rustc-check-cfg=cfg(ngx_feature, values({}))", 14 | std::env::var("DEP_NGINX_FEATURES_CHECK").unwrap_or("any()".to_string()) 15 | ); 16 | // Read feature flags detected by nginx-sys and pass to the compiler. 17 | println!("cargo::rerun-if-env-changed=DEP_NGINX_FEATURES"); 18 | if let Ok(features) = std::env::var("DEP_NGINX_FEATURES") { 19 | for feature in features.split(',').map(str::trim) { 20 | println!("cargo::rustc-cfg=ngx_feature=\"{feature}\""); 21 | } 22 | } 23 | 24 | // Specify acceptable values for `ngx_os` 25 | println!("cargo::rerun-if-env-changed=DEP_NGINX_OS_CHECK"); 26 | println!( 27 | "cargo::rustc-check-cfg=cfg(ngx_os, values({}))", 28 | std::env::var("DEP_NGINX_OS_CHECK").unwrap_or("any()".to_string()) 29 | ); 30 | // Read operating system detected by nginx-sys and pass to the compiler. 31 | println!("cargo::rerun-if-env-changed=DEP_NGINX_OS"); 32 | if let Ok(os) = std::env::var("DEP_NGINX_OS") { 33 | println!("cargo::rustc-cfg=ngx_os=\"{os}\""); 34 | } 35 | 36 | // Generate cfg values for version checks 37 | const VERSION_CHECKS: &[(u64, &str)] = &[ 38 | // 39 | (1_021_001, "nginx1_21_1"), 40 | (1_025_001, "nginx1_25_1"), 41 | ]; 42 | VERSION_CHECKS 43 | .iter() 44 | .for_each(|check| println!("cargo::rustc-check-cfg=cfg({})", check.1)); 45 | println!("cargo::rerun-if-env-changed=DEP_NGINX_VERSION_NUMBER"); 46 | if let Ok(version) = std::env::var("DEP_NGINX_VERSION_NUMBER") { 47 | let version: u64 = version.parse().unwrap(); 48 | 49 | for check in VERSION_CHECKS { 50 | if version >= check.0 { 51 | println!("cargo::rustc-cfg={}", check.1); 52 | } 53 | } 54 | } 55 | 56 | // Generate required compiler flags 57 | if cfg!(target_os = "macos") { 58 | // https://stackoverflow.com/questions/28124221/error-linking-with-cc-failed-exit-code-1 59 | println!("cargo::rustc-link-arg=-undefined"); 60 | println!("cargo::rustc-link-arg=dynamic_lookup"); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /build/changelog.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script generates a changelog for the current version of the project. 4 | 5 | set -o errexit # abort on nonzero exit status 6 | set -o nounset # abort on unbound variable 7 | set -o pipefail # don't hide errors within pipes 8 | 9 | SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)" 10 | EXCLUDED_COMMIT_TYPES="ci|chore" 11 | 12 | pushd . > /dev/null 13 | cd "${SCRIPT_DIR}/.." 14 | 15 | if command -v ggrep > /dev/null; then 16 | GREP=ggrep 17 | else 18 | GREP=grep 19 | fi 20 | if command -v gsed > /dev/null; then 21 | SED=gsed 22 | else 23 | SED=sed 24 | fi 25 | 26 | # if gh is installed, use it to pull the last version number 27 | if command -v gh > /dev/null; then 28 | LAST_RELEASE="$(gh release list --exclude-drafts --exclude-pre-releases --limit 1 | ${GREP} -E 'v[0-9]+\.[0-9]+\.[0-9]+' | cut -f1 | ${GREP} -v "${VERSION}" || true)" 29 | else 30 | LAST_RELEASE="$(git tag --list 'v*' | ${GREP} -E '^v[0-9]+\.[0-9]+\.[0-9]+.*$' | sort --version-sort --field-separator=. --reverse | ${GREP} -v "${VERSION}" | head -n1 || true)" 31 | fi 32 | 33 | if [ -z "${LAST_RELEASE}" ]; then 34 | echo "## Initial release ${VERSION}" 35 | git log --format="%s (%h)" | \ 36 | ${GREP} -E -v "^(${EXCLUDED_COMMIT_TYPES}): .*" | \ 37 | ${SED} 's/: /:\t/g1' | \ 38 | column -s " " -t | \ 39 | ${SED} -e 's/^/ * /' 40 | else 41 | LAST_RELEASE_HASH="$(git show --format=%H "${LAST_RELEASE}" | head -n1 | ${SED} -e 's/^tag //')" 42 | 43 | echo "## Changes between ${LAST_RELEASE} [$LAST_RELEASE_HASH] and ${VERSION}:" 44 | git log --format="%s (%h)" "${LAST_RELEASE_HASH}..HEAD" | \ 45 | ${GREP} -E -v "^(${EXCLUDED_COMMIT_TYPES}): .*" | \ 46 | ${SED} 's/: /:\t/g1' | \ 47 | column -s " " -t | \ 48 | ${SED} -e 's/^/ * /' 49 | fi 50 | 51 | echo "" 52 | popd > /dev/null 53 | -------------------------------------------------------------------------------- /build/container.mk: -------------------------------------------------------------------------------- 1 | .PHONY: container-debian-build-image 2 | .ONESHELL: container-debian-build-image 3 | container-debian-build-image: 4 | container-debian-build-image: ## Builds a container image for building on Debian Linux 5 | $Q echo "$(M) building debian linux docker build image: $(@)" 6 | $(DOCKER) buildx build $(DOCKER_BUILD_FLAGS) -t debian-ngx-rust-builder -f Containerfile.debian $(CURDIR); 7 | 8 | .PHONY: container-test 9 | container-test: container-debian-build-image ## Run tests inside container 10 | $Q mkdir -p .cache/cargo nginx-sys/.nginx 11 | $(DOCKER) run --rm --volume "$(CURDIR):/project" --workdir /project --env 'CARGO_HOME=/project/.cache/cargo' debian-ngx-rust-builder make test 12 | # Reset permissions on the target directory to the current user 13 | if command -v id > /dev/null; then \ 14 | $(DOCKER) run --rm --volume "$(CURDIR):/project" --workdir /project debian-ngx-rust-builder chown --silent --recursive "$(shell id -u):$(shell id -g)" /project/target /project/.cache /project/nginx-sys/.nginx 15 | fi 16 | 17 | .PHONY: container-shell 18 | container-shell: container-debian-build-image ## Start a shell inside container 19 | $Q mkdir -p .cache/cargo nginx-sys/.nginx 20 | $(DOCKER) run -it --rm --volume "$(CURDIR):/project" --workdir /project --env 'CARGO_HOME=/project/.cache/cargo' debian-ngx-rust-builder bash 21 | # Reset permissions on the target directory to the current user 22 | if command -v id > /dev/null; then \ 23 | $(DOCKER) run --rm --volume "$(CURDIR):/project" --workdir /project debian-ngx-rust-builder chown --silent --recursive "$(shell id -u):$(shell id -g)" /project/target /project/.cache /project/nginx-sys/.nginx 24 | fi 25 | 26 | .PHONY: build-docker 27 | build-docker: ## build docker image with all example modules 28 | $(DOCKER) buildx build $(DOCKER_BUILD_FLAGS) -t $(PROJECT_NAME) . 29 | -------------------------------------------------------------------------------- /build/github.mk: -------------------------------------------------------------------------------- 1 | .PHONY: gh-make-release 2 | .ONESHELL: gh-make-release 3 | gh-make-release: 4 | ifndef CI 5 | $(error must be running in CI) 6 | endif 7 | ifneq ($(shell git rev-parse --abbrev-ref HEAD),release-v$(VERSION)) 8 | $(error must be running on release-v$(VERSION) branch) 9 | endif 10 | $(info $(M) updating files with release version [$(GIT_BRANCH)]) @ 11 | git commit -m "ci: update files to version $(VERSION)" Cargo.toml nginx-sys/Cargo.toml 12 | git push origin "release-v$(VERSION)" 13 | git tag -a "v$(VERSION)" -m "ci: tagging v$(VERSION)" 14 | git push origin --tags 15 | gh release create "v$(VERSION)" \ 16 | --title "v$(VERSION)" \ 17 | --notes-file $(CURDIR)/target/dist/release_notes.md -------------------------------------------------------------------------------- /build/release.mk: -------------------------------------------------------------------------------- 1 | target/dist: 2 | $Q mkdir -p target/dist 3 | 4 | .PHONY: changelog 5 | .ONESHELL: changelog 6 | changelog: ## Outputs the changes since the last version committed 7 | $Q VERSION="$(VERSION)" $(CURDIR)/build/changelog.sh 8 | 9 | .ONESHELL: target/dist/release_notes.md 10 | target/dist/release_notes.md: target/dist 11 | $(info $(M) building release notes) @ 12 | $Q echo "# Release Notes" > target/dist/release_notes.md 13 | VERSION="$(VERSION)" $(CURDIR)/build/changelog.sh >> target/dist/release_notes.md 14 | 15 | .PHONY: release-notes 16 | release-notes: target/dist/release_notes.md ## Build release notes 17 | 18 | .PHONY: version 19 | version: ## Outputs the current version 20 | $Q echo "Version: $(VERSION)" 21 | 22 | .PHONY: version-update 23 | .ONESHELL: version-update 24 | version-update: ## Prompts for a new version 25 | $(info $(M) updating repository to new version) @ 26 | $Q echo " last committed version: $(LAST_VERSION)" 27 | $Q echo " Cargo.toml file version : $(VERSION)" 28 | read -p " Enter new version in the format (MAJOR.MINOR.PATCH): " version 29 | $Q echo "$$version" | $(GREP) -qE '^[0-9]+\.[0-9]+\.[0-9]+-?.*$$' || \ 30 | (echo "invalid version identifier: $$version" && exit 1) && \ 31 | $(SED) -i "s/^version\s*=.*$$/version = \"$$version\"/" $(CURDIR)/Cargo.toml 32 | $(SED) -i "s/^version\s*=.*$$/version = \"$$version\"/" $(CURDIR)/nginx-sys/Cargo.toml 33 | @ VERSION=$(shell $(GREP) -Po '^version\s+=\s+"\K.*?(?=")' $(CURDIR)/Cargo.toml) 34 | 35 | .PHONY: version-release 36 | .ONESHELL: version-release 37 | version-release: ## Change from a pre-release to full release version 38 | $Q echo "$(VERSION)" | $(GREP) -qE '^[0-9]+\.[0-9]+\.[0-9]+-beta$$' || \ 39 | (echo "invalid version identifier - must contain suffix -beta: $(VERSION)" && exit 1) 40 | export NEW_VERSION="$(shell echo $(VERSION) | $(SED) -e 's/-beta$$//')" 41 | $(SED) -i "s/^version\s*=.*$$/version = \"$$version\"/" $(CURDIR)/Cargo.toml 42 | $(SED) -i "s/^version\s*=.*$$/version = \"$$version\"/" $(CURDIR)/nginx-sys/Cargo.toml 43 | @ VERSION=$(shell $(GREP) -Po '^version\s+=\s+"\K.*?(?=")' $(CURDIR)/Cargo.toml) 44 | 45 | .PHONY: cargo-release 46 | cargo-release: ## Releases a new version to crates.io 47 | $(info $(M) releasing version $(VERSION) to crates.io) @ 48 | $Q $(CARGO) publish -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | [bans] 2 | multiple-versions = "warn" 3 | 4 | [graph] 5 | all-features = true 6 | 7 | [licenses] 8 | allow = [ 9 | "Apache-2.0 WITH LLVM-exception", 10 | "Apache-2.0", 11 | "BSD-3-Clause", 12 | "ISC", 13 | "MIT", 14 | "Unicode-3.0", 15 | ] 16 | confidence-threshold = 0.8 17 | 18 | [[licenses.exceptions]] 19 | crate = "webpki-roots" 20 | allow = ["CDLA-Permissive-2.0", "MPL-2.0"] 21 | 22 | [[licenses.clarify]] 23 | crate = "ring" 24 | expression = "MIT AND ISC AND OpenSSL" 25 | license-files = [ 26 | { path = "LICENSE", hash = 0xbd0eed23 } 27 | ] 28 | -------------------------------------------------------------------------------- /examples/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "examples" 3 | version = "0.0.0" 4 | publish = false 5 | edition.workspace = true 6 | license.workspace = true 7 | homepage.workspace = true 8 | repository.workspace = true 9 | rust-version.workspace = true 10 | # https://github.com/dependabot/dependabot-core/issues/1156 11 | autobins = false 12 | 13 | build = "../build.rs" 14 | 15 | [dependencies] 16 | nginx-sys = { path = "../nginx-sys/", default-features = false } 17 | ngx = { path = "../", default-features = false, features = ["std"] } 18 | 19 | [dev-dependencies] 20 | aws-sign-v4 = "0.3.0" 21 | chrono = "0.4.23" 22 | http = "1.1.0" 23 | # use unicode-rs idna backend for lower MSRV and faster builds 24 | idna_adapter = "=1.1.0" 25 | libc = "0.2.140" 26 | tokio = { version = "1.33.0", features = ["full"] } 27 | 28 | [[example]] 29 | name = "curl" 30 | path = "curl.rs" 31 | crate-type = ["cdylib"] 32 | 33 | [[example]] 34 | name = "awssig" 35 | path = "awssig.rs" 36 | crate-type = ["cdylib"] 37 | 38 | [[example]] 39 | name = "httporigdst" 40 | path = "httporigdst.rs" 41 | crate-type = ["cdylib"] 42 | required-features = ["linux"] 43 | 44 | [[example]] 45 | name = "upstream" 46 | path = "upstream.rs" 47 | crate-type = ["cdylib"] 48 | 49 | [[example]] 50 | name = "async" 51 | path = "async.rs" 52 | crate-type = ["cdylib"] 53 | 54 | [features] 55 | default = ["export-modules", "ngx/vendored"] 56 | # Generate `ngx_modules` table with module exports 57 | # The exports table is required for building loadable modules with --crate-type cdylib 58 | # outside of the NGINX buildsystem. However, cargo currently does not detect 59 | # this configuration automatically. 60 | # See https://github.com/rust-lang/rust/issues/20267 61 | export-modules = [] 62 | linux = [] 63 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | - [Examples](#examples) 2 | - [CURL](#curl) 3 | - [AWSSIG](#awssig) 4 | - [HTTPORIGDST - NGINX Destination IP recovery module for HTTP](#httporigdst----nginx-destination-ip-recovery-module-for-http) 5 | - [Dependencies](#dependencies) 6 | - [Example Configuration](#example-configuration) 7 | - [HTTP](#http) 8 | - [Embedded Variables](#embedded-variables) 9 | - [Usage](#usage) 10 | - [Caveats](#caveats) 11 | 12 | 13 | # Examples 14 | This crate provides a couple of example using [ngx](https://crates.io/crates/ngx) crate: 15 | 16 | - [awssig.rs](./awssig.rs) - An example of NGINX dynamic module that can sign GET request using AWS Signature v4. 17 | - [curl](./curl.rs) - An example of the Access Phase NGINX dynamic module that blocks HTTP requests if `user-agent` header starts with `curl`. 18 | - [httporigdst](./httporigdst.rs) - A dynamic module recovers the original IP address and port number of the destination packet. 19 | - [upstream](./upstream.rs) - A dynamic module demonstrating the setup code to write an upstream filter or load balancer. 20 | 21 | To build all these examples simply run: 22 | 23 | ``` 24 | cargo build --package=examples --examples 25 | ``` 26 | 27 | 28 | ## CURL 29 | 30 | This module demonstrates how to create a minimal dynamic module with `http_request_handler`, that checks for User-Agent headers and returns status code 403 if UA starts with `curl`, if a module is disabled then uses `core::Status::NGX_DECLINED` to indicate the operation is rejected, for example, because it is disabled in the configuration (`curl off`). Additionally, it demonstrates how to write a defective parser. 31 | 32 | An example of nginx configuration file that uses that module can be found at [curl.conf](./curl.conf). 33 | 34 | How to build and run in a [Docker](../Dockerfile) container curl example: 35 | ``` 36 | # build all dynamic modules examples and specify NGINX version to use 37 | docker buildx build --build-arg NGX_VERSION=1.23.3 -t ngx-rust . 38 | 39 | # start NGINX using curl.conf module example: 40 | docker run --rm -d -p 8000:8000 ngx-rust nginx -c examples/curl.conf 41 | 42 | # test it - you should see 403 Forbidden 43 | curl http://127.0.0.1:8000 -v -H "user-agent: curl" 44 | 45 | 46 | # test it - you should see 404 Not Found 47 | curl http://127.0.0.1:8000 -v -H "user-agent: foo" 48 | ``` 49 | 50 | ## AWSSIG 51 | 52 | This module uses [NGX_HTTP_PRECONTENT_PHASE](https://nginx.org/en/docs/dev/development_guide.html#http_phases) and provides examples, of how to use external dependency and manipulate HTTP headers before sending client requests upstream. 53 | 54 | An example of nginx configuration file that uses that module can be found at [awssig.conf](./awssig.conf). 55 | 56 | ## HTTPORIGDST - NGINX Destination IP recovery module for HTTP 57 | 58 | This dynamic module recovers the original IP address and port number of the destination packet. It is useful, for example, with container sidecars where all outgoing traffic is redirected to a separate container with iptables before reaching the target. 59 | 60 | This module can only be built with the "linux" feature enabled, and will only successfully build on a Linux OS. 61 | 62 | ### Dependencies 63 | This module uses the Rust crate libc and Linux **getsockopt** socket API. 64 | 65 | ### Example Configuration 66 | #### HTTP 67 | 68 | ```nginx configuration 69 | load_module "modules/libhttporigdst.so"; 70 | 71 | http { 72 | server { 73 | # use iptables to capture all outgoing traffic and REDIRECT 74 | # to listening port 15501 75 | listen 15501; 76 | 77 | # binding variables provided by module will lazily activate it 78 | # and store a context 79 | # variables can be used in config 80 | location / { 81 | # Return if no backend is available or proxy_pass 82 | # return 200 "recv'd: $server_addr:$server_port\n\nproxy_pass http://$server_orig_addr:$server_orig_port\n"; 83 | proxy_pass http://$server_orig_addr:$server_orig_port; 84 | } 85 | } 86 | } 87 | ``` 88 | 89 | ### Embedded Variables 90 | 91 | The following embedded variables are provided: 92 | 93 | * **server_orig_addr** 94 | * Original IP address 95 | * **server_orig_port** 96 | * Original port 97 | 98 | ### Usage 99 | 100 | 1. Clone the git repository. 101 | ``` 102 | git clone git@github.com:nginx/ngx-rust.git 103 | ``` 104 | 105 | 2. Compile the module from the cloned repo. 106 | ``` 107 | cd ${CLONED_DIRECTORY}/ngx-rust 108 | cargo build --package=examples --example=httporigdst --features=linux 109 | ``` 110 | 111 | 3. Copy the shared object to the modules directory, /etc/nginx/modules. 112 | ``` 113 | cp ./target/debug/examples/libhttporigdst.so /etc/nginx/modules 114 | ``` 115 | 116 | 4. Add the `load_module` directive to your configuration. 117 | ``` 118 | load_module "modules/libhttporigdst.so"; 119 | ``` 120 | 121 | 5. Reload NGINX. 122 | ``` 123 | nginx -t && nginx -s reload 124 | ``` 125 | 126 | 6. Redirect traffic outbound. 127 | ``` 128 | iptables -t nat -N NGINX_REDIRECT && \ 129 | iptables -t nat -A NGINX_REDIRECT -p tcp -j REDIRECT --to-port 15501 --dport 15001 && \ 130 | iptables -t nat -N NGINX_OUTPUT && \ 131 | iptables -t nat -A OUTPUT -p tcp -j NGINX_OUTPUT && \ 132 | iptables -t nat -A NGINX_OUTPUT -j NGINX_REDIRECT 133 | ``` 134 | 135 | 7. Redirect traffic inbound. 136 | ``` 137 | iptables -t nat -N NGINX_IN_REDIRECT && \ 138 | iptables -t nat -A NGINX_IN_REDIRECT -p tcp -j REDIRECT --to-port 15501 --dport 15001 && \ 139 | iptables -t nat -N NGINX_INBOUND && \ 140 | iptables -t nat -A PREROUTING -p tcp -j NGINX_INBOUND && \ 141 | iptables -t nat -A NGINX_INBOUND -p tcp -j NGINX_IN_REDIRECT 142 | ``` 143 | 144 | 8. Test with `curl` (this step assumes you've uncommented the return directive). 145 | ``` 146 | curl --output - ${LISTEN_IP}:15001 147 | recv'd: ${LISTEN_IP}:15501 148 | 149 | proxy_pass http://${LISTEN_IP}:15001 150 | ``` 151 | ### Caveats 152 | 153 | This module only supports IPv4. 154 | 155 | ## UPSTREAM - Example upstream / load balancing module for HTTP 156 | 157 | This module simply proxies requests through a custom load balancer to the previously configured balancer. This is for demonstration purposes only. As a module writer, you can start with this structure and adjust to your needs, then implement the proper algorithm for your usage. 158 | 159 | The module replaces the `peer` callback functions with its own, logs, and then calls through to the originally saved `peer` functions. This may look confusing at first, but rest assured, it's intentionally not implementing an algorithm of its own. 160 | 161 | ### Attributions 162 | 163 | This module was converted from https://github.com/gabihodoroaga/nginx-upstream-module and also highly inspired by the same techniques used in NGINX source: `ngx_http_upstream_keepalive_module.c`. 164 | 165 | ### Example Configuration 166 | #### HTTP 167 | 168 | ```nginx configuration 169 | load_module "modules/upstream.so" 170 | 171 | http { 172 | upstream backend { 173 | server localhost:15501; 174 | custom 32; 175 | } 176 | 177 | server { 178 | listen 15500; 179 | server_name _; 180 | 181 | location / { 182 | proxy_pass http://backend; 183 | } 184 | } 185 | 186 | server { 187 | listen 15501; 188 | 189 | location / { 190 | return 418; 191 | } 192 | } 193 | } 194 | ``` 195 | 196 | ### Usage 197 | 198 | 1. Clone the git repository. 199 | ``` 200 | git clone git@github.com:nginx/ngx-rust.git 201 | ``` 202 | 203 | 2. Compile the module from the cloned repo. 204 | ``` 205 | cd ${CLONED_DIRECTORY}/ngx-rust 206 | cargo build --package=examples --example=upstream 207 | ``` 208 | 209 | 3. Copy the shared object to the modules directory, /etc/nginx/modules. 210 | ``` 211 | cp ./target/debug/examples/libupstream.so /etc/nginx/modules 212 | ``` 213 | 214 | 4. Add the `load_module` directive to your configuration. 215 | ``` 216 | load_module "modules/libupstream.so"; 217 | ``` 218 | 219 | 5. Add the example `server` and `upstream` block from the example above. 220 | 221 | 6. Reload NGINX. 222 | ``` 223 | nginx -t && nginx -s reload 224 | ``` 225 | 226 | 7. Test with `curl`. Traffic should pass to your listener on port 8081 (this could be another NGINX server for example). With debug logging enabled you should notice the upstream log messages (see the source code for log examples, prefixed with "CUSTOM UPSTREAM"). 227 | -------------------------------------------------------------------------------- /examples/async.conf: -------------------------------------------------------------------------------- 1 | daemon off; 2 | master_process off; 3 | # worker_processes 1; 4 | 5 | load_module modules/libasync.so; 6 | error_log error.log debug; 7 | 8 | events { } 9 | 10 | http { 11 | server { 12 | listen *:8000; 13 | server_name localhost; 14 | location / { 15 | root html; 16 | index index.html index.htm; 17 | async on; 18 | } 19 | error_page 500 502 503 504 /50x.html; 20 | location = /50x.html { 21 | root html; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/async.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, c_void}; 2 | use std::ptr::{addr_of, addr_of_mut}; 3 | use std::slice; 4 | use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering}; 5 | use std::sync::{Arc, OnceLock}; 6 | use std::time::Instant; 7 | 8 | use ngx::core; 9 | use ngx::ffi::{ 10 | ngx_array_push, ngx_command_t, ngx_conf_t, ngx_connection_t, ngx_event_t, ngx_http_handler_pt, 11 | ngx_http_module_t, ngx_http_phases_NGX_HTTP_ACCESS_PHASE, ngx_int_t, ngx_module_t, 12 | ngx_post_event, ngx_posted_events, ngx_posted_next_events, ngx_str_t, ngx_uint_t, 13 | NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF, NGX_HTTP_LOC_CONF_OFFSET, NGX_HTTP_MODULE, 14 | }; 15 | use ngx::http::{self, HttpModule, MergeConfigError}; 16 | use ngx::http::{HttpModuleLocationConf, HttpModuleMainConf, NgxHttpCoreModule}; 17 | use ngx::{http_request_handler, ngx_log_debug_http, ngx_string}; 18 | use tokio::runtime::Runtime; 19 | 20 | struct Module; 21 | 22 | impl http::HttpModule for Module { 23 | fn module() -> &'static ngx_module_t { 24 | unsafe { &*::core::ptr::addr_of!(ngx_http_async_module) } 25 | } 26 | 27 | unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t { 28 | // SAFETY: this function is called with non-NULL cf always 29 | let cf = &mut *cf; 30 | let cmcf = NgxHttpCoreModule::main_conf_mut(cf).expect("http core main conf"); 31 | 32 | let h = ngx_array_push( 33 | &mut cmcf.phases[ngx_http_phases_NGX_HTTP_ACCESS_PHASE as usize].handlers, 34 | ) as *mut ngx_http_handler_pt; 35 | if h.is_null() { 36 | return core::Status::NGX_ERROR.into(); 37 | } 38 | // set an Access phase handler 39 | *h = Some(async_access_handler); 40 | core::Status::NGX_OK.into() 41 | } 42 | } 43 | 44 | #[derive(Debug, Default)] 45 | struct ModuleConfig { 46 | enable: bool, 47 | } 48 | 49 | unsafe impl HttpModuleLocationConf for Module { 50 | type LocationConf = ModuleConfig; 51 | } 52 | 53 | static mut NGX_HTTP_ASYNC_COMMANDS: [ngx_command_t; 2] = [ 54 | ngx_command_t { 55 | name: ngx_string!("async"), 56 | type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t, 57 | set: Some(ngx_http_async_commands_set_enable), 58 | conf: NGX_HTTP_LOC_CONF_OFFSET, 59 | offset: 0, 60 | post: std::ptr::null_mut(), 61 | }, 62 | ngx_command_t::empty(), 63 | ]; 64 | 65 | static NGX_HTTP_ASYNC_MODULE_CTX: ngx_http_module_t = ngx_http_module_t { 66 | preconfiguration: Some(Module::preconfiguration), 67 | postconfiguration: Some(Module::postconfiguration), 68 | create_main_conf: None, 69 | init_main_conf: None, 70 | create_srv_conf: None, 71 | merge_srv_conf: None, 72 | create_loc_conf: Some(Module::create_loc_conf), 73 | merge_loc_conf: Some(Module::merge_loc_conf), 74 | }; 75 | 76 | // Generate the `ngx_modules` table with exported modules. 77 | // This feature is required to build a 'cdylib' dynamic module outside of the NGINX buildsystem. 78 | #[cfg(feature = "export-modules")] 79 | ngx::ngx_modules!(ngx_http_async_module); 80 | 81 | #[used] 82 | #[allow(non_upper_case_globals)] 83 | #[cfg_attr(not(feature = "export-modules"), no_mangle)] 84 | pub static mut ngx_http_async_module: ngx_module_t = ngx_module_t { 85 | ctx: std::ptr::addr_of!(NGX_HTTP_ASYNC_MODULE_CTX) as _, 86 | commands: unsafe { &NGX_HTTP_ASYNC_COMMANDS[0] as *const _ as *mut _ }, 87 | type_: NGX_HTTP_MODULE as _, 88 | ..ngx_module_t::default() 89 | }; 90 | 91 | impl http::Merge for ModuleConfig { 92 | fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> { 93 | if prev.enable { 94 | self.enable = true; 95 | }; 96 | Ok(()) 97 | } 98 | } 99 | 100 | unsafe extern "C" fn check_async_work_done(event: *mut ngx_event_t) { 101 | let ctx = ngx::ngx_container_of!(event, RequestCTX, event); 102 | let c: *mut ngx_connection_t = (*event).data.cast(); 103 | 104 | if (*ctx).done.load(Ordering::Relaxed) { 105 | // Triggering async_access_handler again 106 | ngx_post_event((*c).write, addr_of_mut!(ngx_posted_events)); 107 | } else { 108 | // this doesn't have have good performance but works as a simple thread-safe example and 109 | // doesn't causes segfault. The best method that provides both thread-safety and 110 | // performance requires an nginx patch. 111 | ngx_post_event(event, addr_of_mut!(ngx_posted_next_events)); 112 | } 113 | } 114 | 115 | struct RequestCTX { 116 | done: Arc, 117 | event: ngx_event_t, 118 | task: Option>, 119 | } 120 | 121 | impl Default for RequestCTX { 122 | fn default() -> Self { 123 | Self { 124 | done: AtomicBool::new(false).into(), 125 | event: unsafe { std::mem::zeroed() }, 126 | task: Default::default(), 127 | } 128 | } 129 | } 130 | 131 | impl Drop for RequestCTX { 132 | fn drop(&mut self) { 133 | if let Some(handle) = self.task.take() { 134 | handle.abort(); 135 | } 136 | 137 | if self.event.posted() != 0 { 138 | unsafe { ngx::ffi::ngx_delete_posted_event(&mut self.event) }; 139 | } 140 | } 141 | } 142 | 143 | http_request_handler!(async_access_handler, |request: &mut http::Request| { 144 | let co = Module::location_conf(request).expect("module config is none"); 145 | 146 | ngx_log_debug_http!(request, "async module enabled: {}", co.enable); 147 | 148 | if !co.enable { 149 | return core::Status::NGX_DECLINED; 150 | } 151 | 152 | if let Some(ctx) = 153 | unsafe { request.get_module_ctx::(&*addr_of!(ngx_http_async_module)) } 154 | { 155 | if !ctx.done.load(Ordering::Relaxed) { 156 | return core::Status::NGX_AGAIN; 157 | } 158 | 159 | return core::Status::NGX_OK; 160 | } 161 | 162 | let ctx = request.pool().allocate(RequestCTX::default()); 163 | if ctx.is_null() { 164 | return core::Status::NGX_ERROR; 165 | } 166 | request.set_module_ctx(ctx.cast(), unsafe { &*addr_of!(ngx_http_async_module) }); 167 | 168 | let ctx = unsafe { &mut *ctx }; 169 | ctx.event.handler = Some(check_async_work_done); 170 | ctx.event.data = request.connection().cast(); 171 | ctx.event.log = unsafe { (*request.connection()).log }; 172 | unsafe { ngx_post_event(&mut ctx.event, addr_of_mut!(ngx_posted_next_events)) }; 173 | 174 | // Request is no longer needed and can be converted to something movable to the async block 175 | let req = AtomicPtr::new(request.into()); 176 | let done_flag = ctx.done.clone(); 177 | 178 | let rt = ngx_http_async_runtime(); 179 | ctx.task = Some(rt.spawn(async move { 180 | let start = Instant::now(); 181 | tokio::time::sleep(std::time::Duration::from_secs(2)).await; 182 | let req = unsafe { http::Request::from_ngx_http_request(req.load(Ordering::Relaxed)) }; 183 | // not really thread safe, we should apply all these operation in nginx thread 184 | // but this is just an example. proper way would be storing these headers in the request ctx 185 | // and apply them when we get back to the nginx thread. 186 | req.add_header_out( 187 | "X-Async-Time", 188 | start.elapsed().as_millis().to_string().as_str(), 189 | ); 190 | 191 | done_flag.store(true, Ordering::Release); 192 | // there is a small issue here. If traffic is low we may get stuck behind a 300ms timer 193 | // in the nginx event loop. To workaround it we can notify the event loop using 194 | // pthread_kill( nginx_thread, SIGIO ) to wake up the event loop. (or patch nginx 195 | // and use the same trick as the thread pool) 196 | })); 197 | 198 | core::Status::NGX_AGAIN 199 | }); 200 | 201 | extern "C" fn ngx_http_async_commands_set_enable( 202 | cf: *mut ngx_conf_t, 203 | _cmd: *mut ngx_command_t, 204 | conf: *mut c_void, 205 | ) -> *mut c_char { 206 | unsafe { 207 | let conf = &mut *(conf as *mut ModuleConfig); 208 | let args = slice::from_raw_parts((*(*cf).args).elts as *mut ngx_str_t, (*(*cf).args).nelts); 209 | let val = args[1].to_str(); 210 | 211 | // set default value optionally 212 | conf.enable = false; 213 | 214 | if val.eq_ignore_ascii_case("on") { 215 | conf.enable = true; 216 | } else if val.eq_ignore_ascii_case("off") { 217 | conf.enable = false; 218 | } 219 | }; 220 | 221 | std::ptr::null_mut() 222 | } 223 | 224 | fn ngx_http_async_runtime() -> &'static Runtime { 225 | // Should not be called from the master process 226 | assert_ne!( 227 | unsafe { ngx::ffi::ngx_process }, 228 | ngx::ffi::NGX_PROCESS_MASTER as _ 229 | ); 230 | 231 | static RUNTIME: OnceLock = OnceLock::new(); 232 | RUNTIME.get_or_init(|| { 233 | tokio::runtime::Builder::new_multi_thread() 234 | .enable_all() 235 | .build() 236 | .expect("tokio runtime init") 237 | }) 238 | } 239 | -------------------------------------------------------------------------------- /examples/auto/rust: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # 3 | # Copyright 2025 Nginx, Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # 18 | # Utility library for integration of ngx-rust modules into the NGINX build 19 | # configuration. 20 | # 21 | # Usage: 22 | # 23 | # In "config", 24 | # 25 | # ```sh 26 | # . $ngx_addon_dir/auto/rust 27 | # 28 | # # ngx_addon_name determines the build directory and should be set before 29 | # # any modules are defined 30 | # 31 | # ngx_addon_name="example" 32 | # 33 | # if [ $HTTP = YES ]; then 34 | # # Regular NGINX module options, 35 | # # http://nginx.org/en/docs/dev/development_guide.html#adding_new_modules 36 | # 37 | # ngx_module_type=HTTP 38 | # # Should match the "ngx_module_t" static name(s) exported from the Rust code 39 | # ngx_module_name=ngx_http_example_module 40 | # ngx_module_incs= 41 | # ngx_module_deps= 42 | # ngx_module_libs= 43 | # 44 | # # Options specific to ngx-rust modules 45 | # 46 | # # Target type: LIB or EXAMPLE 47 | # ngx_rust_target_type=LIB 48 | # 49 | # # Target name: crate name, lib.name or example.name 50 | # ngx_rust_target_name=example 51 | # 52 | # # Whitespace-separated list of cargo features. 53 | # # "default" should be specified explicitly if required. 54 | # ngx_rust_target_features= 55 | # 56 | # ngx_rust_module 57 | # fi 58 | # ``` 59 | # 60 | # In "config.make", 61 | # 62 | # ```sh 63 | # ngx_addon_name="example" 64 | # ngx_cargo_manifest=$ngx_addon_dir/Cargo.toml 65 | # 66 | # # generate Makefile section for all the modules configured earlier 67 | # 68 | # ngx_rust_make_modules 69 | # ``` 70 | 71 | # Prevent duplicate invocation unless it is a newer library version 72 | if [ "${NGX_RUST_AUTO_VER:-0}" -ge 1 ]; then 73 | return 74 | fi 75 | 76 | NGX_RUST_AUTO_VER=1 77 | 78 | echo $ngx_n "checking for Rust toolchain ...$ngx_c" 79 | 80 | NGX_CARGO=${NGX_CARGO:-cargo} 81 | 82 | NGX_RUST_VER=$($NGX_CARGO version 2>&1 \ 83 | | grep 'cargo 1\.[0-9][0-9]*\.[0-9]*' 2>&1 \ 84 | | sed -e 's/^.* \(1\.[0-9][0-9]*\.[0-9][0.9]*.*\)/\1/') 85 | 86 | NGX_RUST_VERSION=${NGX_RUST_VER%% *} 87 | 88 | if [ -z "$NGX_RUST_VERSION" ]; then 89 | echo " not found" 90 | echo 91 | echo $0: error: cargo binary $NGX_CARGO is not found 92 | echo 93 | exit 1 94 | fi 95 | 96 | echo " found" 97 | echo " + Rust version: $NGX_RUST_VER" 98 | 99 | case "$NGX_MACHINE" in 100 | 101 | amd64) 102 | RUST_TARGET_ARCH=x86_64 103 | ;; 104 | 105 | arm64) 106 | RUST_TARGET_ARCH=aarch64 107 | ;; 108 | 109 | i?86) 110 | RUST_TARGET_ARCH=i686 111 | ;; 112 | 113 | *) 114 | RUST_TARGET_ARCH=$NGX_MACHINE 115 | ;; 116 | 117 | esac 118 | 119 | case "$NGX_PLATFORM" in 120 | 121 | OpenBSD:*) 122 | # ld: error: undefined symbol: _Unwind_... 123 | RUST_LIBS="$RUST_LIBS -lutil" 124 | RUST_LIBS="$RUST_LIBS -lexecinfo" 125 | RUST_LIBS="$RUST_LIBS -lc++abi" 126 | ;; 127 | 128 | win32) 129 | case "$NGX_CC_NAME" in 130 | 131 | msvc) 132 | # as suggested by rustc --print native-static-libs, 133 | # excluding entries already present in CORE_LIBS 134 | RUST_LIBS="$RUST_LIBS bcrypt.lib" # ??? 135 | RUST_LIBS="$RUST_LIBS ntdll.lib" # std::io, std::sys::pal::windows 136 | RUST_LIBS="$RUST_LIBS userenv.lib" # std::env::home_dir 137 | RUST_LIBS="$RUST_LIBS dbghelp.lib" # backtrace symbolization 138 | 139 | RUST_TARGET=$RUST_TARGET_ARCH-pc-windows-msvc 140 | ;; 141 | 142 | gcc | clang) 143 | RUST_LIBS="$RUST_LIBS -lbcrypt" 144 | RUST_LIBS="$RUST_LIBS -lntdll" 145 | RUST_LIBS="$RUST_LIBS -luserenv" 146 | RUST_LIBS="$RUST_LIBS -ldbghelp" 147 | # gnullvm on arm64? 148 | RUST_TARGET=$RUST_TARGET_ARCH-pc-windows-gnu 149 | ;; 150 | 151 | esac 152 | ;; 153 | 154 | esac 155 | 156 | 157 | # Prepare cargo configuration file 158 | 159 | if [ "$NGX_DEBUG" = YES ]; then 160 | ngx_cargo_default_profile=ngx-debug 161 | else 162 | ngx_cargo_default_profile=ngx-release 163 | fi 164 | 165 | ngx_cargo_config=$NGX_OBJS/.cargo/config.toml 166 | ngx_cargo_profile=${ngx_cargo_profile:-$ngx_cargo_default_profile} 167 | 168 | mkdir -p "$NGX_OBJS/.cargo" 169 | 170 | cat << END > "$ngx_cargo_config" 171 | 172 | [profile.ngx-debug] 173 | inherits = "dev" 174 | 175 | [profile.ngx-release] 176 | inherits = "release" 177 | strip = "none" 178 | 179 | # compatibility with LIBC=-MT set in auto/cc/msvc 180 | [target.aarch64-pc-windows-msvc] 181 | rustflags = ["-C", "target-feature=+crt-static"] 182 | 183 | [target.i686-pc-windows-msvc] 184 | rustflags = ["-C", "target-feature=+crt-static"] 185 | 186 | [target.x86_64-pc-windows-msvc] 187 | rustflags = ["-C", "target-feature=+crt-static"] 188 | 189 | [env] 190 | NGINX_BUILD_DIR = { value = ".", force = true, relative = true } 191 | END 192 | 193 | if [ "$NGX_PLATFORM" = win32 ] && command -v cygpath >/dev/null; then 194 | printf >> "$ngx_cargo_config" 'NGINX_SOURCE_DIR = "%s"\n' \ 195 | "$(cygpath -m "$PWD")" 196 | else 197 | printf >> "$ngx_cargo_config" 'NGINX_SOURCE_DIR = "%s"\n' "$PWD" 198 | fi 199 | 200 | 201 | # Reconstructs path to a static lib built with cargo rustc, 202 | # relative to the --target-dir 203 | 204 | ngx_rust_target_path () { 205 | ngx_rust_obj=$(echo "$ngx_rust_target_name" | tr - _) 206 | 207 | case "$NGX_CC_NAME" in 208 | 209 | msvc) 210 | ngx_rust_obj=${ngx_rust_obj}.lib 211 | ;; 212 | 213 | *) 214 | ngx_rust_obj=lib${ngx_rust_obj}.a 215 | ;; 216 | 217 | esac 218 | 219 | if [ "$ngx_rust_target_type" = EXAMPLE ]; then 220 | ngx_rust_obj=examples/$ngx_rust_obj 221 | fi 222 | 223 | echo "${RUST_TARGET:+$RUST_TARGET/}$ngx_cargo_profile/$ngx_rust_obj" 224 | } 225 | 226 | 227 | # Registers a module in the buildsystem. 228 | # In addition to the regular auto/module parameters, the following variables 229 | # are expected to be set: 230 | # 231 | # ngx_rust_target_type=LIB|EXAMPLE 232 | # ngx_rust_target_name= 233 | # ngx_rust_target_features= 234 | # 235 | # [^1]: https://doc.rust-lang.org/cargo/reference/cargo-targets.html#the-name-field) 236 | 237 | ngx_rust_module () { 238 | ngx_addon_id=$(echo "$ngx_addon_name" | sed -e 's/[^A-Za-z0-9_]/_/g') 239 | ngx_rust_obj=$NGX_OBJS/$ngx_addon_id/$(ngx_rust_target_path) 240 | 241 | ngx_module_deps_saved=$ngx_module_deps 242 | ngx_module_deps="$ngx_rust_obj $ngx_module_deps" 243 | 244 | ngx_module_libs_saved=$ngx_module_libs 245 | ngx_module_libs="$ngx_rust_obj $ngx_module_libs $RUST_LIBS" 246 | 247 | # Module deps are usually added to the object file targets, but we don't have any 248 | LINK_DEPS="$LINK_DEPS $ngx_rust_obj" 249 | 250 | eval ${ngx_addon_id}_RUST_TARGETS=\"\$${ngx_addon_id}_RUST_TARGETS \ 251 | $ngx_rust_target_type:$ngx_rust_target_name\" 252 | 253 | if [ -n "$ngx_rust_target_features" ]; then 254 | eval ${ngx_addon_id}_RUST_FEATURES=\"\$${ngx_addon_id}_RUST_FEATURES \ 255 | $ngx_rust_target_features\" 256 | fi 257 | 258 | . auto/module 259 | 260 | ngx_module_deps=$ngx_module_deps_saved 261 | ngx_module_libs=$ngx_module_libs_saved 262 | } 263 | 264 | 265 | # Writes a Makefile fragment for all the modules configured for "ngx_addon_name" 266 | 267 | ngx_rust_make_modules () { 268 | ngx_addon_id=$(echo "$ngx_addon_name" | sed -e 's/[^A-Za-z0-9_]/_/g') 269 | ngx_cargo_manifest=${ngx_cargo_manifest:-"$ngx_addon_dir/Cargo.toml"} 270 | 271 | eval ngx_rust_features="\$${ngx_addon_id}_RUST_FEATURES" 272 | eval ngx_rust_targets="\$${ngx_addon_id}_RUST_TARGETS" 273 | 274 | for target in $ngx_rust_targets; do 275 | ngx_rust_target_type=${target%%:*} 276 | ngx_rust_target_name=${target#*:} 277 | 278 | ngx_rust_make_module 279 | done 280 | } 281 | 282 | 283 | # Writes a Makefile fragment for a single module specified by 284 | # "ngx_addon_name", "ngx_rust_target_type" and "ngx_rust_target_name" 285 | 286 | ngx_rust_make_module () { 287 | ngx_addon_id=$(echo "$ngx_addon_name" | sed -e 's/[^A-Za-z0-9_]/_/g') 288 | ngx_rust_obj=$NGX_OBJS/$ngx_addon_id/$(ngx_rust_target_path) 289 | 290 | ngx_rustc_module_opt= 291 | if [ "$ngx_rust_target_type" = EXAMPLE ]; then 292 | ngx_rustc_module_opt="--example $ngx_rust_target_name" 293 | fi 294 | 295 | cat << END >> $NGX_MAKEFILE 296 | 297 | # always run cargo instead of trying to track the source modifications 298 | .PHONY: $ngx_rust_obj 299 | 300 | $ngx_rust_obj: 301 | $NGX_CARGO rustc \\ 302 | --config $ngx_cargo_config \\ 303 | --crate-type staticlib \\ 304 | --manifest-path "$ngx_cargo_manifest" \\ 305 | --no-default-features \\ 306 | --profile $ngx_cargo_profile \\ 307 | ${RUST_TARGET:+--target $RUST_TARGET} \\ 308 | --target-dir $NGX_OBJS/$ngx_addon_id \\ 309 | --features "$ngx_rust_features" \\ 310 | $ngx_rustc_module_opt $NGX_RUSTC_OPT 311 | 312 | END 313 | } 314 | -------------------------------------------------------------------------------- /examples/awssig.conf: -------------------------------------------------------------------------------- 1 | daemon off; 2 | master_process off; 3 | # worker_processes 1; 4 | 5 | # on linix load a module: 6 | load_module modules/libawssig.so; 7 | 8 | # error_log /dev/stdout debug; 9 | error_log error.log debug; 10 | 11 | # working_directory /tmp/cores/; 12 | # worker_rlimit_core 500M; 13 | 14 | events { } 15 | 16 | http { 17 | server { 18 | listen *:8000; 19 | server_name localhost; 20 | 21 | awssigv4_access_key my-access-key; 22 | awssigv4_secret_key my-secret-key; 23 | awssigv4_s3_bucket my-bucket; 24 | 25 | location / { 26 | awssigv4 on; 27 | proxy_pass http://localhost:8777; 28 | ## (on | off ) to enable aws sig v4 29 | location /some { 30 | awssigv4 off; 31 | } 32 | ## awssigv4_s3_endpoint if not set then 's3.amazonaws.com' 33 | # awssigv4_s3_endpoint s3.amazonaws.com; 34 | } 35 | 36 | error_page 500 502 503 504 /50x.html; 37 | location = /50x.html { 38 | root html; 39 | } 40 | } 41 | server { 42 | listen 8777; 43 | server_name localhost; 44 | location / { 45 | add_header x-authorization $http_authorization; 46 | add_header x-Amz-Date $http_x_amz_date; 47 | return 204; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /examples/awssig_curl_combined.conf: -------------------------------------------------------------------------------- 1 | daemon off; 2 | master_process off; 3 | # worker_processes 1; 4 | 5 | # on linix load a module: 6 | load_module modules/libawssig.so; 7 | load_module modules/libcurl.so; 8 | 9 | # error_log /dev/stdout debug; 10 | error_log error.log debug; 11 | 12 | # working_directory /tmp/cores/; 13 | # worker_rlimit_core 500M; 14 | 15 | events { } 16 | 17 | http { 18 | server { 19 | listen *:8000; 20 | server_name localhost; 21 | 22 | awssigv4_access_key my-access-key; 23 | awssigv4_secret_key my-secret-key; 24 | awssigv4_s3_bucket my-bucket; 25 | 26 | location / { 27 | awssigv4 on; 28 | curl on; 29 | 30 | proxy_pass http://localhost:8777; 31 | ## (on | off ) to enable aws sig v4 32 | location /some { 33 | awssigv4 off; 34 | } 35 | ## awssigv4_s3_endpoint if not set then 's3.amazonaws.com' 36 | # awssigv4_s3_endpoint s3.amazonaws.com; 37 | } 38 | 39 | error_page 500 502 503 504 /50x.html; 40 | location = /50x.html { 41 | root html; 42 | } 43 | } 44 | server { 45 | listen 8777; 46 | server_name localhost; 47 | location / { 48 | add_header x-authorization $http_authorization; 49 | add_header x-Amz-Date $http_x_amz_date; 50 | return 204; 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/config: -------------------------------------------------------------------------------- 1 | # shellcheck source=auto/rust 2 | . $ngx_addon_dir/auto/rust 3 | 4 | # ngx_addon_name determines the build directory and should be set before 5 | # any modules are defined 6 | 7 | ngx_addon_name=ngx_rust_examples 8 | 9 | if [ $HTTP = YES ]; then 10 | ngx_module_type=HTTP 11 | ngx_module_incs= 12 | ngx_module_deps= 13 | ngx_module_order= 14 | 15 | ngx_rust_target_type=EXAMPLE 16 | ngx_rust_target_features= 17 | 18 | if :; then 19 | ngx_module_name=ngx_http_async_module 20 | ngx_module_libs="-lm" 21 | ngx_rust_target_name=async 22 | 23 | ngx_rust_module 24 | fi 25 | 26 | if :; then 27 | ngx_module_name=ngx_http_awssigv4_module 28 | ngx_module_libs="-lm" 29 | ngx_rust_target_name=awssig 30 | 31 | ngx_rust_module 32 | fi 33 | 34 | if :; then 35 | ngx_module_name=ngx_http_curl_module 36 | ngx_module_libs= 37 | ngx_rust_target_name=curl 38 | 39 | ngx_rust_module 40 | fi 41 | 42 | if :; then 43 | ngx_module_name=ngx_http_upstream_custom_module 44 | ngx_module_libs= 45 | ngx_rust_target_name=upstream 46 | 47 | ngx_rust_module 48 | fi 49 | 50 | if [ "$NGX_SYSTEM" = Linux ]; then 51 | ngx_module_name=ngx_http_orig_dst_module 52 | ngx_module_libs= 53 | ngx_rust_target_name=httporigdst 54 | ngx_rust_target_features=linux 55 | 56 | ngx_rust_module 57 | fi 58 | fi 59 | -------------------------------------------------------------------------------- /examples/config.make: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_rust_examples 2 | ngx_cargo_manifest=$ngx_addon_dir/Cargo.toml 3 | 4 | # generate Makefile section for all the modules configured earlier 5 | 6 | ngx_rust_make_modules 7 | -------------------------------------------------------------------------------- /examples/curl.conf: -------------------------------------------------------------------------------- 1 | daemon off; 2 | master_process off; 3 | # worker_processes 1; 4 | 5 | # on linux load a module: 6 | load_module modules/libcurl.so; 7 | 8 | # on mac os it would be dylib 9 | # load_module modules/libcurl.dylib; 10 | 11 | # error_log /dev/stdout debug; 12 | error_log error.log debug; 13 | 14 | events { } 15 | 16 | http { 17 | server { 18 | listen *:8000; 19 | server_name localhost; 20 | location / { 21 | root html; 22 | index index.html index.htm; 23 | # libcurl module directive: 24 | curl on; 25 | } 26 | error_page 500 502 503 504 /50x.html; 27 | location = /50x.html { 28 | root html; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /examples/curl.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_char, c_void}; 2 | 3 | use ngx::core; 4 | use ngx::ffi::{ 5 | ngx_array_push, ngx_command_t, ngx_conf_t, ngx_http_handler_pt, ngx_http_module_t, 6 | ngx_http_phases_NGX_HTTP_ACCESS_PHASE, ngx_int_t, ngx_module_t, ngx_str_t, ngx_uint_t, 7 | NGX_CONF_TAKE1, NGX_HTTP_LOC_CONF, NGX_HTTP_LOC_CONF_OFFSET, NGX_HTTP_MODULE, 8 | }; 9 | use ngx::http::{self, HttpModule, MergeConfigError}; 10 | use ngx::http::{HttpModuleLocationConf, HttpModuleMainConf, NgxHttpCoreModule}; 11 | use ngx::{http_request_handler, ngx_log_debug_http, ngx_string}; 12 | 13 | struct Module; 14 | 15 | impl http::HttpModule for Module { 16 | fn module() -> &'static ngx_module_t { 17 | unsafe { &*::core::ptr::addr_of!(ngx_http_curl_module) } 18 | } 19 | 20 | unsafe extern "C" fn postconfiguration(cf: *mut ngx_conf_t) -> ngx_int_t { 21 | // SAFETY: this function is called with non-NULL cf always 22 | let cf = &mut *cf; 23 | let cmcf = NgxHttpCoreModule::main_conf_mut(cf).expect("http core main conf"); 24 | 25 | let h = ngx_array_push( 26 | &mut cmcf.phases[ngx_http_phases_NGX_HTTP_ACCESS_PHASE as usize].handlers, 27 | ) as *mut ngx_http_handler_pt; 28 | if h.is_null() { 29 | return core::Status::NGX_ERROR.into(); 30 | } 31 | // set an Access phase handler 32 | *h = Some(curl_access_handler); 33 | core::Status::NGX_OK.into() 34 | } 35 | } 36 | 37 | #[derive(Debug, Default)] 38 | struct ModuleConfig { 39 | enable: bool, 40 | } 41 | 42 | unsafe impl HttpModuleLocationConf for Module { 43 | type LocationConf = ModuleConfig; 44 | } 45 | 46 | static mut NGX_HTTP_CURL_COMMANDS: [ngx_command_t; 2] = [ 47 | ngx_command_t { 48 | name: ngx_string!("curl"), 49 | type_: (NGX_HTTP_LOC_CONF | NGX_CONF_TAKE1) as ngx_uint_t, 50 | set: Some(ngx_http_curl_commands_set_enable), 51 | conf: NGX_HTTP_LOC_CONF_OFFSET, 52 | offset: 0, 53 | post: std::ptr::null_mut(), 54 | }, 55 | ngx_command_t::empty(), 56 | ]; 57 | 58 | static NGX_HTTP_CURL_MODULE_CTX: ngx_http_module_t = ngx_http_module_t { 59 | preconfiguration: Some(Module::preconfiguration), 60 | postconfiguration: Some(Module::postconfiguration), 61 | create_main_conf: None, 62 | init_main_conf: None, 63 | create_srv_conf: None, 64 | merge_srv_conf: None, 65 | create_loc_conf: Some(Module::create_loc_conf), 66 | merge_loc_conf: Some(Module::merge_loc_conf), 67 | }; 68 | 69 | // Generate the `ngx_modules` table with exported modules. 70 | // This feature is required to build a 'cdylib' dynamic module outside of the NGINX buildsystem. 71 | #[cfg(feature = "export-modules")] 72 | ngx::ngx_modules!(ngx_http_curl_module); 73 | 74 | #[used] 75 | #[allow(non_upper_case_globals)] 76 | #[cfg_attr(not(feature = "export-modules"), no_mangle)] 77 | pub static mut ngx_http_curl_module: ngx_module_t = ngx_module_t { 78 | ctx: std::ptr::addr_of!(NGX_HTTP_CURL_MODULE_CTX) as _, 79 | commands: unsafe { &NGX_HTTP_CURL_COMMANDS[0] as *const _ as *mut _ }, 80 | type_: NGX_HTTP_MODULE as _, 81 | ..ngx_module_t::default() 82 | }; 83 | 84 | impl http::Merge for ModuleConfig { 85 | fn merge(&mut self, prev: &ModuleConfig) -> Result<(), MergeConfigError> { 86 | if prev.enable { 87 | self.enable = true; 88 | }; 89 | Ok(()) 90 | } 91 | } 92 | 93 | http_request_handler!(curl_access_handler, |request: &mut http::Request| { 94 | let co = Module::location_conf(request).expect("module config is none"); 95 | 96 | ngx_log_debug_http!(request, "curl module enabled: {}", co.enable); 97 | 98 | match co.enable { 99 | true => { 100 | if request 101 | .user_agent() 102 | .is_some_and(|ua| ua.as_bytes().starts_with(b"curl")) 103 | { 104 | http::HTTPStatus::FORBIDDEN.into() 105 | } else { 106 | core::Status::NGX_DECLINED 107 | } 108 | } 109 | false => core::Status::NGX_DECLINED, 110 | } 111 | }); 112 | 113 | extern "C" fn ngx_http_curl_commands_set_enable( 114 | cf: *mut ngx_conf_t, 115 | _cmd: *mut ngx_command_t, 116 | conf: *mut c_void, 117 | ) -> *mut c_char { 118 | unsafe { 119 | let conf = &mut *(conf as *mut ModuleConfig); 120 | let args = (*(*cf).args).elts as *mut ngx_str_t; 121 | 122 | let val = (*args.add(1)).to_str(); 123 | 124 | // set default value optionally 125 | conf.enable = false; 126 | 127 | if val.len() == 2 && val.eq_ignore_ascii_case("on") { 128 | conf.enable = true; 129 | } else if val.len() == 3 && val.eq_ignore_ascii_case("off") { 130 | conf.enable = false; 131 | } 132 | }; 133 | 134 | std::ptr::null_mut() 135 | } 136 | -------------------------------------------------------------------------------- /examples/httporigdst.conf: -------------------------------------------------------------------------------- 1 | http { 2 | server { 3 | # use iptables to capture all outgoing traffic and REDIRECT 4 | # to listening port 15501 5 | listen 15501; 6 | 7 | # binding variables provided by module will lazily activate it 8 | # and store a context 9 | # variables can be used in config 10 | location / { 11 | # Return if no backend is available or proxy_pass 12 | # return 200 "recv'd: $server_addr:$server_port\n\nproxy_pass http://$server_orig_addr:$server_orig_port\n"; 13 | proxy_pass http://$server_orig_addr:$server_orig_port; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /examples/t/async.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # (C) Nginx, Inc 4 | 5 | # Tests for ngx-rust example modules. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use Test::More; 13 | 14 | BEGIN { use FindBin; chdir($FindBin::Bin); } 15 | 16 | use lib 'lib'; 17 | use Test::Nginx; 18 | 19 | ############################################################################### 20 | 21 | select STDERR; $| = 1; 22 | select STDOUT; $| = 1; 23 | 24 | my $t = Test::Nginx->new()->has(qw/http/)->plan(1) 25 | ->write_file_expand('nginx.conf', <<'EOF'); 26 | 27 | %%TEST_GLOBALS%% 28 | 29 | daemon off; 30 | 31 | events { 32 | } 33 | 34 | http { 35 | %%TEST_GLOBALS_HTTP%% 36 | 37 | server { 38 | listen 127.0.0.1:8080; 39 | server_name localhost; 40 | 41 | location / { 42 | async on; 43 | } 44 | } 45 | } 46 | 47 | EOF 48 | 49 | $t->write_file('index.html', ''); 50 | $t->run(); 51 | 52 | ############################################################################### 53 | 54 | like(http_get('/index.html'), qr/X-Async-Time:/, 'async handler'); 55 | 56 | ############################################################################### 57 | -------------------------------------------------------------------------------- /examples/t/awssig.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # (C) Nginx, Inc 4 | 5 | # Tests for ngx-rust example modules. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use Test::More; 13 | 14 | BEGIN { use FindBin; chdir($FindBin::Bin); } 15 | 16 | use lib 'lib'; 17 | use Test::Nginx; 18 | 19 | ############################################################################### 20 | 21 | select STDERR; $| = 1; 22 | select STDOUT; $| = 1; 23 | 24 | my $t = Test::Nginx->new()->has(qw/http proxy/)->plan(1) 25 | ->write_file_expand('nginx.conf', <<"EOF"); 26 | 27 | %%TEST_GLOBALS%% 28 | 29 | daemon off; 30 | 31 | events { 32 | } 33 | 34 | http { 35 | %%TEST_GLOBALS_HTTP%% 36 | 37 | server { 38 | listen 127.0.0.1:8080; 39 | server_name localhost; 40 | 41 | awssigv4_access_key my-access-key; 42 | awssigv4_secret_key my-secret-key; 43 | awssigv4_s3_bucket my-bucket; 44 | awssigv4_s3_endpoint s3.example.com; 45 | 46 | location / { 47 | awssigv4 on; 48 | proxy_pass http://127.0.0.1:8081; 49 | } 50 | } 51 | 52 | server { 53 | listen 127.0.0.1:8081; 54 | server_name localhost; 55 | 56 | add_header x-amz-date \$http_x_amz_date; 57 | add_header x-authorization \$http_authorization; 58 | 59 | location / { } 60 | } 61 | } 62 | 63 | EOF 64 | 65 | $t->write_file('index.html', ''); 66 | $t->run(); 67 | 68 | ############################################################################### 69 | 70 | like(http_get('/'), qr/x-authorization: AWS4.*Credential=my-access-key/i, 71 | 'awssig header'); 72 | 73 | ############################################################################### 74 | -------------------------------------------------------------------------------- /examples/t/curl.t: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # (C) Nginx, Inc 4 | 5 | # Tests for ngx-rust example modules. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use Test::More; 13 | 14 | BEGIN { use FindBin; chdir($FindBin::Bin); } 15 | 16 | use lib 'lib'; 17 | use Test::Nginx; 18 | 19 | ############################################################################### 20 | 21 | select STDERR; $| = 1; 22 | select STDOUT; $| = 1; 23 | 24 | my $t = Test::Nginx->new()->has(qw/http/)->plan(2) 25 | ->write_file_expand('nginx.conf', <<"EOF"); 26 | 27 | %%TEST_GLOBALS%% 28 | 29 | daemon off; 30 | 31 | events { 32 | } 33 | 34 | http { 35 | %%TEST_GLOBALS_HTTP%% 36 | 37 | server { 38 | listen 127.0.0.1:8080; 39 | server_name localhost; 40 | 41 | location / { 42 | curl on; 43 | } 44 | } 45 | } 46 | 47 | EOF 48 | 49 | $t->write_file('index.html', ''); 50 | $t->run(); 51 | 52 | ############################################################################### 53 | 54 | like(get('/', 'curl/1.2.3'), qr/403 Forbidden/, 'curl UA forbidden'); 55 | like(get('/', 'MSIE 6.0'), qr/200 OK/, 'other UA allowed'); 56 | 57 | ############################################################################### 58 | 59 | sub get { 60 | my ($url, $ua, $extra) = @_; 61 | return http(<new()->has(qw/http proxy/)->plan(2) 25 | ->write_file_expand('nginx.conf', <<"EOF"); 26 | 27 | %%TEST_GLOBALS%% 28 | 29 | daemon off; 30 | 31 | events { 32 | } 33 | 34 | http { 35 | %%TEST_GLOBALS_HTTP%% 36 | 37 | upstream u { 38 | server 127.0.0.1:8081; 39 | custom 32; 40 | } 41 | 42 | server { 43 | listen 127.0.0.1:8080; 44 | server_name localhost; 45 | 46 | error_log %%TESTDIR%%/e_debug.log debug; 47 | 48 | location / { 49 | proxy_pass http://u; 50 | } 51 | } 52 | 53 | server { 54 | listen 127.0.0.1:8081; 55 | server_name localhost; 56 | 57 | location / { } 58 | } 59 | } 60 | 61 | EOF 62 | 63 | $t->write_file('index.html', ''); 64 | $t->run(); 65 | 66 | ############################################################################### 67 | 68 | like(http_get('/'), qr/200 OK/, 'custom upstream'); 69 | 70 | $t->stop(); 71 | 72 | SKIP: { 73 | skip "no --with-debug", 1 unless $t->has_module('--with-debug'); 74 | 75 | like($t->read_file('e_debug.log'), qr/CUSTOM UPSTREAM request/, 76 | 'log - custom upstream'); 77 | } 78 | 79 | ############################################################################### 80 | -------------------------------------------------------------------------------- /examples/upstream.conf: -------------------------------------------------------------------------------- 1 | # example configuration block to test upstream.rs 2 | http { 3 | upstream backend { 4 | server localhost:15501; 5 | custom 32; 6 | } 7 | 8 | server { 9 | listen 15500; 10 | server_name _; 11 | 12 | location / { 13 | proxy_pass http://backend; 14 | } 15 | } 16 | 17 | server { 18 | listen 15501; 19 | 20 | location / { 21 | return 418; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /misc/lsan-suppressions.txt: -------------------------------------------------------------------------------- 1 | # LeakSanitizer suppressions list for nginx 2 | # 3 | # To be used with -fsanitize=address and 4 | # LSAN_OPTIONS=suppressions=lsan-suppressions.txt. 5 | # 6 | # https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#suppressions 7 | 8 | # cycle->connections, cycle->read_events, cycle->write_events 9 | leak:ngx_event_process_init 10 | 11 | # XXX: can silence leaks from nginx SSL callbacks 12 | leak:SSL_do_handshake 13 | leak:SSL_read 14 | 15 | # rcf->ranges not freed at process exit 16 | leak:ngx_http_upstream_update_random 17 | leak:ngx_stream_upstream_update_random 18 | -------------------------------------------------------------------------------- /misc/nginx-sanitizer-support.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/core/ngx_string.h b/src/core/ngx_string.h 2 | index 713eb42a7..999c6b25f 100644 3 | --- a/src/core/ngx_string.h 4 | +++ b/src/core/ngx_string.h 5 | @@ -96,6 +96,23 @@ void ngx_explicit_memzero(void *buf, size_t n); 6 | void *ngx_memcpy(void *dst, const void *src, size_t n); 7 | #define ngx_cpymem(dst, src, n) (((u_char *) ngx_memcpy(dst, src, n)) + (n)) 8 | 9 | +#elif (NGX_SUPPRESS_WARN) 10 | + 11 | +/* 12 | + * Checked versions for sanitizers. 13 | + * See https://mailman.nginx.org/pipermail/nginx-devel/2023-December/7VNQZEBNXEKAYTYE4Y65FORF4HNELM6V.html 14 | + */ 15 | + 16 | +static ngx_inline void * 17 | +ngx_memcpy(void *dst, const void *src, size_t n) { 18 | + return (n == 0) ? dst : memcpy(dst, src, n); 19 | +} 20 | + 21 | +static ngx_inline void * 22 | +ngx_cpymem(void *dst, const void *src, size_t n) { 23 | + return (n == 0) ? dst : ((u_char *) memcpy(dst, src, n)) + n; 24 | +} 25 | + 26 | #else 27 | 28 | /* 29 | -------------------------------------------------------------------------------- /misc/ubsan-suppressions.txt: -------------------------------------------------------------------------------- 1 | # UndefinedBehaviorSanitizer suppressions list for nginx 2 | # 3 | # To be used with -fsanitize=undefined and 4 | # UBSAN_OPTIONS=suppressions=ubsan-suppressions.txt. 5 | # 6 | # https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html#runtime-suppressions 7 | 8 | # src/http/v2/ngx_http_v2.c:2747:17: runtime error: store to misaligned address 0x7b35a1a19885 for type 'uint32_t' (aka 'unsigned int'), which requires 4 byte alignment 9 | alignment:src/http/v2/ngx_http_v2.c 10 | alignment:src/http/v2/ngx_http_v2_filter_module.c 11 | alignment:ngx_http_huff_encode 12 | alignment:ngx_http_parse_request_line 13 | 14 | # ngx_quic_write_uint32 at src/event/quic/ngx_event_quic_transport.c:642 15 | alignment:ngx_quic_create_long_header 16 | alignment:ngx_quic_read_uint32 17 | 18 | # src/core/ngx_output_chain.c:70:20: runtime error: call to function ngx_http_trailers_filter through pointer to incorrect function type 'long (*)(void *, struct ngx_chain_s *)' 19 | # src/http/modules/ngx_http_headers_filter_module.c:249: note: ngx_http_trailers_filter defined here 20 | function:ngx_output_chain 21 | 22 | # violates nonnull on memcmp 23 | nonnull-attribute:ngx_http_upstream_zone_preresolve 24 | nonnull-attribute:ngx_stream_upstream_zone_preresolve 25 | 26 | # src/http/ngx_http_script.c:800:16: runtime error: applying non-zero offset 112 to null pointer 27 | pointer-overflow:ngx_http_script_add_code 28 | pointer-overflow:ngx_stream_script_add_code 29 | -------------------------------------------------------------------------------- /misc/update-action-sha.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | if [ $# -lt 2 ]; then 6 | grep "uses:[[:space:]]*$1" .github/workflows/*.yaml 7 | exit 8 | fi 9 | 10 | sed -e "s|\\(uses:[[:space:]]*$1@\\).*|\\1$2|" -i .github/workflows/*.yaml 11 | -------------------------------------------------------------------------------- /nginx-sys/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nginx-sys" 3 | version = "0.5.0" 4 | categories = ["external-ffi-bindings"] 5 | description = "FFI bindings to NGINX" 6 | keywords = ["nginx", "ffi", "sys"] 7 | build = "build/main.rs" 8 | # This field is required to export DEP_NGINX_ vars 9 | # See https://github.com/rust-lang/cargo/issues/3544 10 | links = "nginx" 11 | edition.workspace = true 12 | license.workspace = true 13 | homepage.workspace = true 14 | repository.workspace = true 15 | rust-version.workspace = true 16 | 17 | [dependencies] 18 | 19 | [build-dependencies] 20 | bindgen = "0.71" 21 | cc = "1.2.0" 22 | duct = { version = "1", optional = true } 23 | dunce = "1.0.5" 24 | flate2 = { version = "1.0.28", optional = true } 25 | regex = "1.11.1" 26 | tar = { version = "0.4.40", optional = true } 27 | ureq = { version = "3.0.10", optional = true } 28 | which = { version = "7.0.0", optional = true } 29 | 30 | [features] 31 | vendored = ["dep:which", "dep:duct", "dep:ureq", "dep:flate2", "dep:tar"] 32 | -------------------------------------------------------------------------------- /nginx-sys/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /nginx-sys/README.md: -------------------------------------------------------------------------------- 1 | # nginx-sys 2 | 3 | The `nginx-sys` crate provides low-level bindings for the nginx C API, allowing 4 | Rust applications to interact with nginx servers and modules. 5 | 6 | ## Usage 7 | 8 | Add `nginx-sys` as a dependency in your `Cargo.toml`: 9 | 10 | ```toml 11 | [dependencies] 12 | nginx-sys = "0.5.0" 13 | ``` 14 | 15 | Set the path to your nginx sources and build directory using the 16 | [environment variables](#input-variables), or enable the `nginx-sys/vendored` 17 | feature to use a downloaded copy of the nginx sources. 18 | 19 | Note that it is recommended to use the exact source and `configure` arguments 20 | of the nginx build you are planning to use in production. 21 | `configure` arguments alter the APIs and the symbols visible to the Rust code, 22 | and some OS distributions are known to ship nginx packages with API-breaking 23 | patches applied. 24 | 25 | ## Features 26 | 27 | - `vendored`: Enables the build scripts to download and build a copy of nginx 28 | source and link against it. 29 | 30 | ## Input variables 31 | 32 | `NGINX_SOURCE_DIR`, `NGINX_BUILD_DIR` control paths to an external nginx source 33 | tree and a configured build directory. Normally, specifying either of these is 34 | sufficient, as the build directory defaults to `objs` under the source dir. 35 | 36 | However, it's possible to set the latter to any valid path with 37 | `configure --builddir=<...>`, and we need _both_ paths to support such 38 | configuration. 39 | 40 | The variables above are optional, but take preference when the `vendored` crate 41 | feature is enabled. 42 | 43 | ## Output variables 44 | 45 | Following metadata variables are passed to the build scripts of any **direct** 46 | dependents of the package. 47 | 48 | Check the [using another sys crate] and the [links manifest key] sections of the 49 | Cargo Book for more details about passing metadata between packages. 50 | 51 | ### `DEP_NGINX_FEATURES` 52 | 53 | nginx has various configuration options that may affect the availability of 54 | functions, constants and structure fields. This is not something that can be 55 | detected at runtime, as an attempt to use a symbol unavailable in the bindings 56 | would result in compilation error. 57 | 58 | `DEP_NGINX_FEATURES_CHECK` contains the full list of feature flags supported 59 | by `nginx-sys`, i.e. everything that can be used for feature checks. 60 | The most common use for this variable is to specify [`cargo::rustc-check-cfg`]. 61 | 62 | `DEP_NGINX_FEATURES` contains the list of features enabled in the version of 63 | nginx being built against. 64 | 65 | An example of a build script with these variables: 66 | ```rust 67 | // Specify acceptable values for `ngx_feature`. 68 | println!("cargo::rerun-if-env-changed=DEP_NGINX_FEATURES_CHECK"); 69 | println!( 70 | "cargo::rustc-check-cfg=cfg(ngx_feature, values({}))", 71 | std::env::var("DEP_NGINX_FEATURES_CHECK").unwrap_or("any()".to_string()) 72 | ); 73 | // Read feature flags detected by nginx-sys and pass to the compiler. 74 | println!("cargo::rerun-if-env-changed=DEP_NGINX_FEATURES"); 75 | if let Ok(features) = std::env::var("DEP_NGINX_FEATURES") { 76 | for feature in features.split(',').map(str::trim) { 77 | println!("cargo::rustc-cfg=ngx_feature=\"{}\"", feature); 78 | } 79 | } 80 | ``` 81 | 82 | And an usage example: 83 | ```rust 84 | #[cfg(ngx_feature = "debug")] 85 | println!("this nginx binary was built with debug logging enabled"); 86 | ``` 87 | 88 | ### `DEP_NGINX_OS` 89 | 90 | Version, as detected by the nginx configuration script. 91 | 92 | `DEP_NGINX_OS_CHECK` contains the full list of supported values, and 93 | `DEP_NGINX_OS` the currently detected one. 94 | 95 | Usage examples: 96 | ```rust 97 | // Specify acceptable values for `ngx_os` 98 | println!("cargo::rerun-if-env-changed=DEP_NGINX_OS_CHECK"); 99 | println!( 100 | "cargo::rustc-check-cfg=cfg(ngx_os, values({}))", 101 | std::env::var("DEP_NGINX_OS_CHECK").unwrap_or("any()".to_string()) 102 | ); 103 | // Read operating system detected by nginx-sys and pass to the compiler. 104 | println!("cargo::rerun-if-env-changed=DEP_NGINX_OS"); 105 | if let Ok(os) = std::env::var("DEP_NGINX_OS") { 106 | println!("cargo::rustc-cfg=ngx_os=\"{}\"", os); 107 | } 108 | ``` 109 | 110 | ```rust 111 | #[cfg(ngx_os = "freebsd")] 112 | println!("this nginx binary was built on FreeBSD"); 113 | ``` 114 | 115 | ### Version and build information 116 | 117 | - `DEP_NGINX_VERSION_NUMBER`: 118 | a numeric representation with 3 digits for each component: `1026002` 119 | - `DEP_NGINX_VERSION`: 120 | human-readable string in a product/version format: `nginx/1.26.2` 121 | - `DEP_NGINX_BUILD`: 122 | version string with the optional build name (`--build=`) included: 123 | `nginx/1.25.5 (nginx-plus-r32)` 124 | 125 | Usage example: 126 | ```rust 127 | println!("cargo::rustc-check-cfg=cfg(nginx1_27_0)"); 128 | println!("cargo::rerun-if-env-changed=DEP_NGINX_VERSION_NUMBER"); 129 | if let Ok(version) = std::env::var("DEP_NGINX_VERSION_NUMBER") { 130 | let version: u64 = version.parse().unwrap(); 131 | 132 | if version >= 1_027_000 { 133 | println!("cargo::rustc-cfg=nginx1_27_0"); 134 | } 135 | } 136 | ``` 137 | 138 | ## Examples 139 | 140 | ### Get nginx Version 141 | 142 | This example demonstrates how to retrieve the version of the nginx server. 143 | 144 | ```rust,no_run 145 | use nginx_sys::nginx_version; 146 | 147 | println!("nginx version: {}", nginx_version); 148 | ``` 149 | [using another sys crate]: https://doc.rust-lang.org/nightly/cargo/reference/build-script-examples.html#using-another-sys-crate 150 | [links manifest key]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#the-links-manifest-key 151 | [`cargo::rustc-check-cfg`]: https://doc.rust-lang.org/nightly/cargo/reference/build-scripts.html#rustc-check-cfg 152 | -------------------------------------------------------------------------------- /nginx-sys/build/wrapper.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | const char *NGX_RS_MODULE_SIGNATURE = NGX_MODULE_SIGNATURE; 7 | 8 | // `--prefix=` results in not emitting the declaration 9 | #ifndef NGX_PREFIX 10 | #define NGX_PREFIX "" 11 | #endif 12 | 13 | #ifndef NGX_CONF_PREFIX 14 | #define NGX_CONF_PREFIX NGX_PREFIX 15 | #endif 16 | -------------------------------------------------------------------------------- /nginx-sys/src/detail.rs: -------------------------------------------------------------------------------- 1 | //! Implementation details shared between nginx-sys and ngx. 2 | #![allow(missing_docs)] 3 | 4 | use core::fmt; 5 | use core::ptr::copy_nonoverlapping; 6 | 7 | use crate::bindings::{ngx_pnalloc, ngx_pool_t, u_char}; 8 | 9 | /// Convert a byte slice to a raw pointer (`*mut u_char`) allocated in the given nginx memory pool. 10 | /// 11 | /// # Safety 12 | /// 13 | /// The caller must provide a valid pointer to the memory pool. 14 | pub unsafe fn bytes_to_uchar(pool: *mut ngx_pool_t, data: &[u8]) -> Option<*mut u_char> { 15 | let ptr: *mut u_char = ngx_pnalloc(pool, data.len()) as _; 16 | if ptr.is_null() { 17 | return None; 18 | } 19 | copy_nonoverlapping(data.as_ptr(), ptr, data.len()); 20 | Some(ptr) 21 | } 22 | 23 | /// Convert a string slice (`&str`) to a raw pointer (`*mut u_char`) allocated in the given nginx 24 | /// memory pool. 25 | /// 26 | /// # Arguments 27 | /// 28 | /// * `pool` - A pointer to the nginx memory pool (`ngx_pool_t`). 29 | /// * `data` - The string slice to convert to a raw pointer. 30 | /// 31 | /// # Safety 32 | /// This function is marked as unsafe because it involves raw pointer manipulation and direct memory 33 | /// allocation using `ngx_pnalloc`. 34 | /// 35 | /// # Returns 36 | /// A raw pointer (`*mut u_char`) to the allocated memory containing the converted string data. 37 | /// 38 | /// # Example 39 | /// ```rust,ignore 40 | /// let pool: *mut ngx_pool_t = ...; // Obtain a pointer to the nginx memory pool 41 | /// let data: &str = "example"; // The string to convert 42 | /// let ptr = str_to_uchar(pool, data); 43 | /// ``` 44 | pub unsafe fn str_to_uchar(pool: *mut ngx_pool_t, data: &str) -> *mut u_char { 45 | let ptr: *mut u_char = ngx_pnalloc(pool, data.len()) as _; 46 | debug_assert!(!ptr.is_null()); 47 | copy_nonoverlapping(data.as_ptr(), ptr, data.len()); 48 | ptr 49 | } 50 | 51 | #[inline] 52 | pub fn debug_bytes(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { 53 | if f.alternate() { 54 | match bytes.len() { 55 | 0 => Ok(()), 56 | 1 => write!(f, "{:02x}", bytes[0]), 57 | x => { 58 | for b in &bytes[..x - 1] { 59 | write!(f, "{b:02x},")?; 60 | } 61 | write!(f, "{:02x}", bytes[x - 1]) 62 | } 63 | } 64 | } else { 65 | f.write_str("\"")?; 66 | display_bytes(f, bytes)?; 67 | f.write_str("\"") 68 | } 69 | } 70 | 71 | #[inline] 72 | pub fn display_bytes(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { 73 | // The implementation is similar to an inlined `String::from_utf8_lossy`, with two 74 | // important differences: 75 | // 76 | // - it writes directly to the Formatter instead of allocating a temporary String 77 | // - invalid sequences are represented as escaped individual bytes 78 | for chunk in bytes.utf8_chunks() { 79 | f.write_str(chunk.valid())?; 80 | for byte in chunk.invalid() { 81 | write!(f, "\\x{byte:02x}")?; 82 | } 83 | } 84 | 85 | Ok(()) 86 | } 87 | 88 | #[cfg(test)] 89 | mod tests { 90 | extern crate alloc; 91 | use alloc::format; 92 | use alloc::string::ToString; 93 | 94 | use super::*; 95 | 96 | struct TestStr(&'static [u8]); 97 | 98 | impl fmt::Debug for TestStr { 99 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 100 | f.write_str("TestStr(")?; 101 | debug_bytes(f, self.0)?; 102 | f.write_str(")") 103 | } 104 | } 105 | 106 | impl fmt::Display for TestStr { 107 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 108 | display_bytes(f, self.0) 109 | } 110 | } 111 | 112 | #[test] 113 | fn test_display() { 114 | let cases: &[(&[u8], &str)] = &[ 115 | (b"", ""), 116 | (b"Ferris the \xf0\x9f\xa6\x80", "Ferris the 🦀"), 117 | (b"\xF0\x90\x80", "\\xf0\\x90\\x80"), 118 | (b"\xF0\x90\x80Hello World", "\\xf0\\x90\\x80Hello World"), 119 | (b"Hello \xF0\x90\x80World", "Hello \\xf0\\x90\\x80World"), 120 | (b"Hello World\xF0\x90\x80", "Hello World\\xf0\\x90\\x80"), 121 | ]; 122 | 123 | for (bytes, expected) in cases { 124 | let str = TestStr(bytes); 125 | assert_eq!(str.to_string(), *expected); 126 | } 127 | 128 | // Check that the formatter arguments are ignored correctly 129 | for (bytes, expected) in &cases[2..3] { 130 | let str = TestStr(bytes); 131 | assert_eq!(format!("{str:12.12}"), *expected); 132 | } 133 | } 134 | 135 | #[test] 136 | fn test_debug() { 137 | let cases: &[(&[u8], &str, &str)] = &[ 138 | (b"", "TestStr(\"\")", "TestStr()"), 139 | (b"a", "TestStr(\"a\")", "TestStr(61)"), 140 | ( 141 | b"Ferris the \xf0\x9f\xa6\x80", 142 | "TestStr(\"Ferris the 🦀\")", 143 | "TestStr(46,65,72,72,69,73,20,74,68,65,20,f0,9f,a6,80)", 144 | ), 145 | ( 146 | b"\xF0\x90\x80", 147 | "TestStr(\"\\xf0\\x90\\x80\")", 148 | "TestStr(f0,90,80)", 149 | ), 150 | ]; 151 | for (bytes, expected, alternate) in cases { 152 | let str = TestStr(bytes); 153 | assert_eq!(format!("{str:?}"), *expected); 154 | assert_eq!(format!("{str:#?}"), *alternate); 155 | } 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /nginx-sys/src/event.rs: -------------------------------------------------------------------------------- 1 | use core::ptr; 2 | 3 | use crate::{ 4 | ngx_current_msec, ngx_event_t, ngx_event_timer_rbtree, ngx_msec_t, ngx_queue_insert_before, 5 | ngx_queue_remove, ngx_queue_t, ngx_rbtree_delete, ngx_rbtree_insert, NGX_TIMER_LAZY_DELAY, 6 | }; 7 | 8 | /// Sets a timeout for an event. 9 | /// 10 | /// # Safety 11 | /// 12 | ///`ev` must be a valid pointer to an `ngx_event_t`. 13 | #[inline] 14 | pub unsafe fn ngx_add_timer(ev: *mut ngx_event_t, timer: ngx_msec_t) { 15 | let key: ngx_msec_t = ngx_current_msec.wrapping_add(timer); 16 | 17 | if (*ev).timer_set() != 0 { 18 | /* 19 | * Use a previous timer value if difference between it and a new 20 | * value is less than NGX_TIMER_LAZY_DELAY milliseconds: this allows 21 | * to minimize the rbtree operations for fast connections. 22 | */ 23 | if key.abs_diff((*ev).timer.key) < NGX_TIMER_LAZY_DELAY as _ { 24 | return; 25 | } 26 | 27 | ngx_del_timer(ev); 28 | } 29 | 30 | (*ev).timer.key = key; 31 | 32 | ngx_rbtree_insert( 33 | ptr::addr_of_mut!(ngx_event_timer_rbtree), 34 | ptr::addr_of_mut!((*ev).timer), 35 | ); 36 | 37 | (*ev).set_timer_set(1); 38 | } 39 | 40 | /// Deletes a previously set timeout. 41 | /// 42 | /// # Safety 43 | /// 44 | /// `ev` must be a valid pointer to an `ngx_event_t`, previously armed with [ngx_add_timer]. 45 | #[inline] 46 | pub unsafe fn ngx_del_timer(ev: *mut ngx_event_t) { 47 | ngx_rbtree_delete( 48 | ptr::addr_of_mut!(ngx_event_timer_rbtree), 49 | ptr::addr_of_mut!((*ev).timer), 50 | ); 51 | 52 | (*ev).timer.left = ptr::null_mut(); 53 | (*ev).timer.right = ptr::null_mut(); 54 | (*ev).timer.parent = ptr::null_mut(); 55 | 56 | (*ev).set_timer_set(0); 57 | } 58 | 59 | /// Post the event `ev` to the post queue `q`. 60 | /// 61 | /// # Safety 62 | /// 63 | /// `ev` must be a valid pointer to an `ngx_event_t`. 64 | /// `q` is a valid pointer to a queue head. 65 | #[inline] 66 | pub unsafe fn ngx_post_event(ev: *mut ngx_event_t, q: *mut ngx_queue_t) { 67 | if (*ev).posted() == 0 { 68 | (*ev).set_posted(1); 69 | ngx_queue_insert_before(q, ptr::addr_of_mut!((*ev).queue)); 70 | } 71 | } 72 | 73 | /// Deletes the event `ev` from the queue it's currently posted in. 74 | /// 75 | /// # Safety 76 | /// 77 | /// `ev` must be a valid pointer to an `ngx_event_t`. 78 | /// `ev.queue` is initialized with `ngx_queue_init`. 79 | #[inline] 80 | pub unsafe fn ngx_delete_posted_event(ev: *mut ngx_event_t) { 81 | (*ev).set_posted(0); 82 | ngx_queue_remove(ptr::addr_of_mut!((*ev).queue)); 83 | } 84 | -------------------------------------------------------------------------------- /nginx-sys/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![warn(missing_docs)] 3 | #![no_std] 4 | 5 | pub mod detail; 6 | mod event; 7 | mod queue; 8 | mod string; 9 | 10 | use core::mem::offset_of; 11 | use core::ptr; 12 | 13 | #[doc(hidden)] 14 | mod bindings { 15 | #![allow(unknown_lints)] // unnecessary_transmutes 16 | #![allow(missing_docs)] 17 | #![allow(non_upper_case_globals)] 18 | #![allow(non_camel_case_types)] 19 | #![allow(non_snake_case)] 20 | #![allow(dead_code)] 21 | #![allow(clippy::all)] 22 | #![allow(improper_ctypes)] 23 | #![allow(rustdoc::broken_intra_doc_links)] 24 | #![allow(unnecessary_transmutes)] 25 | include!(concat!(env!("OUT_DIR"), "/bindings.rs")); 26 | } 27 | #[doc(no_inline)] 28 | pub use bindings::*; 29 | pub use event::*; 30 | pub use queue::*; 31 | 32 | /// The offset of the `main_conf` field in the `ngx_http_conf_ctx_t` struct. 33 | /// 34 | /// This is used to access the main configuration context for an HTTP module. 35 | pub const NGX_HTTP_MAIN_CONF_OFFSET: usize = offset_of!(ngx_http_conf_ctx_t, main_conf); 36 | 37 | /// The offset of the `srv_conf` field in the `ngx_http_conf_ctx_t` struct. 38 | /// 39 | /// This is used to access the server configuration context for an HTTP module. 40 | pub const NGX_HTTP_SRV_CONF_OFFSET: usize = offset_of!(ngx_http_conf_ctx_t, srv_conf); 41 | 42 | /// The offset of the `loc_conf` field in the `ngx_http_conf_ctx_t` struct. 43 | /// 44 | /// This is used to access the location configuration context for an HTTP module. 45 | pub const NGX_HTTP_LOC_CONF_OFFSET: usize = offset_of!(ngx_http_conf_ctx_t, loc_conf); 46 | 47 | impl ngx_command_t { 48 | /// Creates a new empty [`ngx_command_t`] instance. 49 | /// 50 | /// This method replaces the `ngx_null_command` C macro. This is typically used to terminate an 51 | /// array of configuration directives. 52 | /// 53 | /// [`ngx_command_t`]: https://nginx.org/en/docs/dev/development_guide.html#config_directives 54 | pub const fn empty() -> Self { 55 | Self { 56 | name: ngx_str_t::empty(), 57 | type_: 0, 58 | set: None, 59 | conf: 0, 60 | offset: 0, 61 | post: ptr::null_mut(), 62 | } 63 | } 64 | } 65 | 66 | impl ngx_module_t { 67 | /// Create a new `ngx_module_t` instance with default values. 68 | pub const fn default() -> Self { 69 | Self { 70 | ctx_index: ngx_uint_t::MAX, 71 | index: ngx_uint_t::MAX, 72 | name: ptr::null_mut(), 73 | spare0: 0, 74 | spare1: 0, 75 | version: nginx_version as ngx_uint_t, 76 | signature: NGX_RS_MODULE_SIGNATURE.as_ptr(), 77 | ctx: ptr::null_mut(), 78 | commands: ptr::null_mut(), 79 | type_: 0, 80 | init_master: None, 81 | init_module: None, 82 | init_process: None, 83 | init_thread: None, 84 | exit_thread: None, 85 | exit_process: None, 86 | exit_master: None, 87 | spare_hook0: 0, 88 | spare_hook1: 0, 89 | spare_hook2: 0, 90 | spare_hook3: 0, 91 | spare_hook4: 0, 92 | spare_hook5: 0, 93 | spare_hook6: 0, 94 | spare_hook7: 0, 95 | } 96 | } 97 | } 98 | 99 | /// Add a key-value pair to an nginx table entry (`ngx_table_elt_t`) in the given nginx memory pool. 100 | /// 101 | /// # Arguments 102 | /// 103 | /// * `table` - A pointer to the nginx table entry (`ngx_table_elt_t`) to modify. 104 | /// * `pool` - A pointer to the nginx memory pool (`ngx_pool_t`) for memory allocation. 105 | /// * `key` - The key string to add to the table entry. 106 | /// * `value` - The value string to add to the table entry. 107 | /// 108 | /// # Safety 109 | /// This function is marked as unsafe because it involves raw pointer manipulation and direct memory 110 | /// allocation using `str_to_uchar`. 111 | /// 112 | /// # Returns 113 | /// An `Option<()>` representing the result of the operation. `Some(())` indicates success, while 114 | /// `None` indicates a null table pointer. 115 | /// 116 | /// # Example 117 | /// ```rust 118 | /// # use nginx_sys::*; 119 | /// # unsafe fn example(pool: *mut ngx_pool_t, headers: *mut ngx_list_t) { 120 | /// // Obtain a pointer to the nginx table entry 121 | /// let table: *mut ngx_table_elt_t = ngx_list_push(headers).cast(); 122 | /// assert!(!table.is_null()); 123 | /// let key: &str = "key"; // The key to add 124 | /// let value: &str = "value"; // The value to add 125 | /// let result = add_to_ngx_table(table, pool, key, value); 126 | /// # } 127 | /// ``` 128 | pub unsafe fn add_to_ngx_table( 129 | table: *mut ngx_table_elt_t, 130 | pool: *mut ngx_pool_t, 131 | key: impl AsRef<[u8]>, 132 | value: impl AsRef<[u8]>, 133 | ) -> Option<()> { 134 | if let Some(table) = table.as_mut() { 135 | let key = key.as_ref(); 136 | table.key = ngx_str_t::from_bytes(pool, key)?; 137 | table.value = ngx_str_t::from_bytes(pool, value.as_ref())?; 138 | table.lowcase_key = ngx_pnalloc(pool, table.key.len).cast(); 139 | if table.lowcase_key.is_null() { 140 | return None; 141 | } 142 | table.hash = ngx_hash_strlow(table.lowcase_key, table.key.data, table.key.len); 143 | return Some(()); 144 | } 145 | None 146 | } 147 | -------------------------------------------------------------------------------- /nginx-sys/src/queue.rs: -------------------------------------------------------------------------------- 1 | use core::ptr; 2 | 3 | use crate::bindings::ngx_queue_t; 4 | 5 | /// Get a reference to the beginning of a queue node data structure, 6 | /// considering the queue field offset in it. 7 | /// 8 | /// # Safety 9 | /// 10 | /// `$q` must be a valid pointer to the field `$link` in the struct `$type` 11 | #[macro_export] 12 | macro_rules! ngx_queue_data { 13 | ($q:expr, $type:path, $link:ident) => { 14 | $q.byte_sub(::core::mem::offset_of!($type, $link)) 15 | .cast::<$type>() 16 | }; 17 | } 18 | 19 | /// Initializes the queue head before use. 20 | /// 21 | /// # Safety 22 | /// 23 | /// `q` must be a valid pointer to [ngx_queue_t]. 24 | #[inline] 25 | pub unsafe fn ngx_queue_init(q: *mut ngx_queue_t) { 26 | (*q).prev = q; 27 | (*q).next = q; 28 | } 29 | 30 | /// Returns `true` if the queue contains no elements. 31 | /// 32 | /// # Safety 33 | /// 34 | /// `q` must be a valid pointer to [ngx_queue_t], initialized with [ngx_queue_init]. 35 | #[inline] 36 | pub unsafe fn ngx_queue_empty(q: *const ngx_queue_t) -> bool { 37 | ptr::eq(q, (*q).prev) 38 | } 39 | 40 | /// Inserts a new node after the current. 41 | /// 42 | /// # Safety 43 | /// 44 | /// Both `q` and `x` must be valid pointers to [ngx_queue_t] 45 | #[inline] 46 | pub unsafe fn ngx_queue_insert_after(q: *mut ngx_queue_t, x: *mut ngx_queue_t) { 47 | (*x).next = (*q).next; 48 | (*(*x).next).prev = x; 49 | (*x).prev = q; 50 | (*q).next = x; 51 | } 52 | 53 | /// Inserts a new node before the current. 54 | /// 55 | /// # Safety 56 | /// 57 | /// Both `q` and `x` must be valid pointers to [ngx_queue_t]. 58 | #[inline] 59 | pub unsafe fn ngx_queue_insert_before(q: *mut ngx_queue_t, x: *mut ngx_queue_t) { 60 | (*x).prev = (*q).prev; 61 | (*(*x).prev).next = x; 62 | (*x).next = q; 63 | (*q).prev = x; 64 | } 65 | 66 | /// Removes a node from the queue. 67 | /// 68 | /// # Safety 69 | /// 70 | /// `q` must be a valid pointer to an [ngx_queue_t] node. 71 | #[inline] 72 | pub unsafe fn ngx_queue_remove(q: *mut ngx_queue_t) { 73 | (*(*q).next).prev = (*q).prev; 74 | (*(*q).prev).next = (*q).next; 75 | (*q).prev = ptr::null_mut(); 76 | (*q).next = ptr::null_mut(); 77 | } 78 | 79 | /// Splits a queue at a node, returning the queue tail in a separate queue. 80 | /// 81 | /// # Safety 82 | /// 83 | /// `h` must be a valid pointer to a head queue node. 84 | /// `q` must be a node in the queue `h`. 85 | /// `n` must be a valid pointer to [ngx_queue_t]. 86 | #[inline] 87 | pub unsafe fn ngx_queue_split(h: *mut ngx_queue_t, q: *mut ngx_queue_t, n: *mut ngx_queue_t) { 88 | (*n).prev = (*h).prev; 89 | (*(*n).prev).next = n; 90 | (*n).next = q; 91 | (*h).prev = (*q).prev; 92 | (*(*h).prev).next = h; 93 | (*q).prev = n; 94 | } 95 | 96 | /// Adds a second queue to the first queue. 97 | /// 98 | /// # Safety 99 | /// 100 | /// Both `h` and `n` must be valid pointers to queue heads, initialized with [ngx_queue_init]. 101 | /// `n` will be left in invalid state, pointing to the subrange of `h` without back references. 102 | #[inline] 103 | pub unsafe fn ngx_queue_add(h: *mut ngx_queue_t, n: *mut ngx_queue_t) { 104 | (*(*h).prev).next = (*n).next; 105 | (*(*n).next).prev = (*h).prev; 106 | (*h).prev = (*n).prev; 107 | (*(*h).prev).next = h; 108 | } 109 | 110 | impl ngx_queue_t { 111 | /// Returns `true` if the queue contains no elements. 112 | pub fn is_empty(&self) -> bool { 113 | unsafe { ngx_queue_empty(self) } 114 | } 115 | } 116 | 117 | impl Default for ngx_queue_t { 118 | fn default() -> ngx_queue_t { 119 | ngx_queue_t { 120 | prev: ptr::null_mut(), 121 | next: ptr::null_mut(), 122 | } 123 | } 124 | } 125 | 126 | #[cfg(test)] 127 | mod tests { 128 | extern crate alloc; 129 | use alloc::boxed::Box; 130 | 131 | use super::*; 132 | 133 | struct TestData { 134 | value: usize, 135 | queue: ngx_queue_t, 136 | } 137 | 138 | impl TestData { 139 | pub fn new(value: usize) -> *mut Self { 140 | // We should be using `ngx_pool_t` here, but that is not possible without linking to 141 | // the nginx 142 | let mut x = Box::new(Self { 143 | value, 144 | queue: Default::default(), 145 | }); 146 | unsafe { ngx_queue_init(ptr::addr_of_mut!(x.queue)) }; 147 | Box::into_raw(x) 148 | } 149 | 150 | pub unsafe fn free(x: *mut Self) { 151 | let _ = Box::from_raw(x); 152 | } 153 | } 154 | 155 | impl Drop for TestData { 156 | fn drop(&mut self) { 157 | if !self.queue.next.is_null() && !self.queue.is_empty() { 158 | unsafe { ngx_queue_remove(ptr::addr_of_mut!(self.queue)) }; 159 | } 160 | } 161 | } 162 | 163 | struct Iter { 164 | h: *mut ngx_queue_t, 165 | q: *mut ngx_queue_t, 166 | next: fn(*mut ngx_queue_t) -> *mut ngx_queue_t, 167 | } 168 | 169 | impl Iter { 170 | pub fn new(h: *mut ngx_queue_t) -> Self { 171 | let next = |x: *mut ngx_queue_t| unsafe { (*x).next }; 172 | Self { 173 | h, 174 | q: next(h), 175 | next, 176 | } 177 | } 178 | 179 | pub fn new_reverse(h: *mut ngx_queue_t) -> Self { 180 | let next = |x: *mut ngx_queue_t| unsafe { (*x).prev }; 181 | Self { 182 | h, 183 | q: next(h), 184 | next, 185 | } 186 | } 187 | } 188 | 189 | impl Iterator for Iter { 190 | type Item = *mut ngx_queue_t; 191 | 192 | fn next(&mut self) -> Option { 193 | if ptr::eq(self.h, self.q) { 194 | return None; 195 | } 196 | 197 | let item = self.q; 198 | self.q = (self.next)(self.q); 199 | Some(item) 200 | } 201 | } 202 | 203 | #[test] 204 | fn test_queue() { 205 | fn value(q: *mut ngx_queue_t) -> usize { 206 | unsafe { (*ngx_queue_data!(q, TestData, queue)).value } 207 | } 208 | 209 | // Check forward and reverse iteration 210 | fn cmp(h: *mut ngx_queue_t, other: &[usize]) -> bool { 211 | Iter::new(h).map(value).eq(other.iter().cloned()) 212 | && Iter::new_reverse(h) 213 | .map(value) 214 | .eq(other.iter().rev().cloned()) 215 | } 216 | 217 | // Note how this test does not use references or borrows to avoid triggering UBs 218 | // detectable by Miri. This does not mean that the code is safe or sound. 219 | unsafe { 220 | // Initialize and fill the queue 221 | 222 | let mut h1 = ngx_queue_t::default(); 223 | ngx_queue_init(ptr::addr_of_mut!(h1)); 224 | 225 | let mut h2 = ngx_queue_t::default(); 226 | ngx_queue_init(ptr::addr_of_mut!(h2)); 227 | 228 | for i in 1..=5 { 229 | let elem = TestData::new(i); 230 | ngx_queue_insert_before(ptr::addr_of_mut!(h1), ptr::addr_of_mut!((*elem).queue)); 231 | 232 | let elem = TestData::new(i); 233 | ngx_queue_insert_after(ptr::addr_of_mut!(h2), ptr::addr_of_mut!((*elem).queue)); 234 | } 235 | 236 | // Iterate and test the values 237 | 238 | assert!(cmp(ptr::addr_of_mut!(h1), &[1, 2, 3, 4, 5])); 239 | assert!(cmp(ptr::addr_of_mut!(h2), &[5, 4, 3, 2, 1])); 240 | 241 | // Move nodes from h2 to h1 242 | 243 | // h2 still points to the subrange of h1 after this operation 244 | ngx_queue_add(ptr::addr_of_mut!(h1), ptr::addr_of_mut!(h2)); 245 | 246 | assert!(cmp(ptr::addr_of_mut!(h1), &[1, 2, 3, 4, 5, 5, 4, 3, 2, 1])); 247 | 248 | ngx_queue_split( 249 | ptr::addr_of_mut!(h1), 250 | (*h2.next).next, 251 | ptr::addr_of_mut!(h2), 252 | ); 253 | 254 | assert!(cmp(ptr::addr_of_mut!(h1), &[1, 2, 3, 4, 5, 5])); 255 | assert!(cmp(ptr::addr_of_mut!(h2), &[4, 3, 2, 1])); 256 | 257 | // Cleanup 258 | 259 | for q in Iter::new(ptr::addr_of_mut!(h1)) { 260 | let td = ngx_queue_data!(q, TestData, queue); 261 | TestData::free(td); 262 | } 263 | assert!(h1.is_empty()); 264 | 265 | for q in Iter::new(ptr::addr_of_mut!(h2)) { 266 | let td = ngx_queue_data!(q, TestData, queue); 267 | TestData::free(td); 268 | } 269 | assert!(h2.is_empty()); 270 | }; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /nginx-sys/src/string.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | use core::ptr; 3 | use core::slice; 4 | 5 | use crate::bindings::{ngx_pool_t, ngx_str_t}; 6 | use crate::detail; 7 | 8 | impl ngx_str_t { 9 | /// Returns the contents of this `ngx_str_t` as a byte slice. 10 | /// 11 | /// The returned slice will **not** contain the optional nul terminator that `ngx_str_t.data` 12 | /// may have. 13 | #[inline] 14 | pub fn as_bytes(&self) -> &[u8] { 15 | if self.is_empty() { 16 | &[] 17 | } else { 18 | // SAFETY: `ngx_str_t` with non-zero len must contain a valid correctly aligned pointer 19 | unsafe { slice::from_raw_parts(self.data, self.len) } 20 | } 21 | } 22 | 23 | /// Returns the contents of this `ngx_str_t` as a mutable byte slice. 24 | #[inline] 25 | pub fn as_bytes_mut(&mut self) -> &mut [u8] { 26 | if self.is_empty() { 27 | &mut [] 28 | } else { 29 | // SAFETY: `ngx_str_t` with non-zero len must contain a valid correctly aligned pointer 30 | unsafe { slice::from_raw_parts_mut(self.data, self.len) } 31 | } 32 | } 33 | 34 | /// Returns `true` if the string has a length of 0. 35 | #[inline] 36 | pub fn is_empty(&self) -> bool { 37 | self.len == 0 38 | } 39 | 40 | /// Convert the nginx string to a string slice (`&str`). 41 | /// 42 | /// # Panics 43 | /// This function panics if the `ngx_str_t` is not valid UTF-8. 44 | /// 45 | /// # Returns 46 | /// A string slice (`&str`) representing the nginx string. 47 | pub fn to_str(&self) -> &str { 48 | core::str::from_utf8(self.as_bytes()).unwrap() 49 | } 50 | 51 | /// Creates an empty `ngx_str_t` instance. 52 | /// 53 | /// This method replaces the `ngx_null_string` C macro. 54 | pub const fn empty() -> Self { 55 | ngx_str_t { 56 | len: 0, 57 | data: ptr::null_mut(), 58 | } 59 | } 60 | 61 | /// Create an `ngx_str_t` instance from a byte slice. 62 | /// 63 | /// # Safety 64 | /// 65 | /// The caller must provide a valid pointer to a memory pool. 66 | pub unsafe fn from_bytes(pool: *mut ngx_pool_t, src: &[u8]) -> Option { 67 | detail::bytes_to_uchar(pool, src).map(|data| Self { 68 | data, 69 | len: src.len(), 70 | }) 71 | } 72 | 73 | /// Create an `ngx_str_t` instance from a string slice (`&str`). 74 | /// 75 | /// # Arguments 76 | /// 77 | /// * `pool` - A pointer to the nginx memory pool (`ngx_pool_t`). 78 | /// * `data` - The string slice from which to create the nginx string. 79 | /// 80 | /// # Safety 81 | /// This function is marked as unsafe because it accepts a raw pointer argument. There is no 82 | /// way to know if `pool` is pointing to valid memory. The caller must provide a valid pool to 83 | /// avoid indeterminate behavior. 84 | /// 85 | /// # Returns 86 | /// An `ngx_str_t` instance representing the given string slice. 87 | pub unsafe fn from_str(pool: *mut ngx_pool_t, data: &str) -> Self { 88 | ngx_str_t { 89 | data: detail::str_to_uchar(pool, data), 90 | len: data.len(), 91 | } 92 | } 93 | } 94 | 95 | impl AsRef<[u8]> for ngx_str_t { 96 | fn as_ref(&self) -> &[u8] { 97 | self.as_bytes() 98 | } 99 | } 100 | 101 | impl AsMut<[u8]> for ngx_str_t { 102 | fn as_mut(&mut self) -> &mut [u8] { 103 | self.as_bytes_mut() 104 | } 105 | } 106 | 107 | impl Default for ngx_str_t { 108 | fn default() -> Self { 109 | Self::empty() 110 | } 111 | } 112 | 113 | impl From for &[u8] { 114 | fn from(s: ngx_str_t) -> Self { 115 | if s.len == 0 || s.data.is_null() { 116 | return Default::default(); 117 | } 118 | unsafe { slice::from_raw_parts(s.data, s.len) } 119 | } 120 | } 121 | 122 | impl fmt::Display for ngx_str_t { 123 | #[inline] 124 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 125 | detail::display_bytes(f, self.as_bytes()) 126 | } 127 | } 128 | 129 | impl PartialEq for ngx_str_t { 130 | fn eq(&self, other: &Self) -> bool { 131 | PartialEq::eq(self.as_bytes(), other.as_bytes()) 132 | } 133 | } 134 | 135 | impl Eq for ngx_str_t {} 136 | 137 | impl PartialOrd for ngx_str_t { 138 | fn partial_cmp(&self, other: &Self) -> Option { 139 | Some(self.cmp(other)) 140 | } 141 | } 142 | 143 | impl Ord for ngx_str_t { 144 | fn cmp(&self, other: &Self) -> core::cmp::Ordering { 145 | Ord::cmp(self.as_bytes(), other.as_bytes()) 146 | } 147 | } 148 | 149 | impl TryFrom for &str { 150 | type Error = core::str::Utf8Error; 151 | 152 | fn try_from(s: ngx_str_t) -> Result { 153 | core::str::from_utf8(s.into()) 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | max_width = 100 3 | 4 | # unstable options: 5 | # comment_width = 100 6 | # group_imports = "StdExternalCrate" 7 | # format_strings = true 8 | # wrap_comments = true -------------------------------------------------------------------------------- /src/core/buffer.rs: -------------------------------------------------------------------------------- 1 | use core::slice; 2 | 3 | use crate::ffi::*; 4 | 5 | /// The `Buffer` trait provides methods for working with an nginx buffer (`ngx_buf_t`). 6 | /// 7 | /// See 8 | pub trait Buffer { 9 | /// Returns a raw pointer to the underlying `ngx_buf_t` of the buffer. 10 | fn as_ngx_buf(&self) -> *const ngx_buf_t; 11 | 12 | /// Returns a mutable raw pointer to the underlying `ngx_buf_t` of the buffer. 13 | fn as_ngx_buf_mut(&mut self) -> *mut ngx_buf_t; 14 | 15 | /// Returns the buffer contents as a byte slice. 16 | fn as_bytes(&self) -> &[u8] { 17 | let buf = self.as_ngx_buf(); 18 | unsafe { slice::from_raw_parts((*buf).pos, self.len()) } 19 | } 20 | 21 | /// Returns the length of the buffer contents. 22 | fn len(&self) -> usize { 23 | let buf = self.as_ngx_buf(); 24 | unsafe { 25 | let pos = (*buf).pos; 26 | let last = (*buf).last; 27 | assert!(last >= pos); 28 | usize::wrapping_sub(last as _, pos as _) 29 | } 30 | } 31 | 32 | /// Returns `true` if the buffer is empty, i.e., it has zero length. 33 | fn is_empty(&self) -> bool { 34 | self.len() == 0 35 | } 36 | 37 | /// Sets the `last_buf` flag of the buffer. 38 | /// 39 | /// # Arguments 40 | /// 41 | /// * `last` - A boolean indicating whether the buffer is the last buffer in a request. 42 | fn set_last_buf(&mut self, last: bool) { 43 | let buf = self.as_ngx_buf_mut(); 44 | unsafe { 45 | (*buf).set_last_buf(if last { 1 } else { 0 }); 46 | } 47 | } 48 | 49 | /// Sets the `last_in_chain` flag of the buffer. 50 | /// 51 | /// # Arguments 52 | /// 53 | /// * `last` - A boolean indicating whether the buffer is the last buffer in a chain of buffers. 54 | fn set_last_in_chain(&mut self, last: bool) { 55 | let buf = self.as_ngx_buf_mut(); 56 | unsafe { 57 | (*buf).set_last_in_chain(if last { 1 } else { 0 }); 58 | } 59 | } 60 | } 61 | 62 | /// The `MutableBuffer` trait extends the `Buffer` trait and provides methods for working with a 63 | /// mutable buffer. 64 | pub trait MutableBuffer: Buffer { 65 | /// Returns a mutable reference to the buffer contents as a byte slice. 66 | fn as_bytes_mut(&mut self) -> &mut [u8] { 67 | let buf = self.as_ngx_buf_mut(); 68 | unsafe { slice::from_raw_parts_mut((*buf).pos, self.len()) } 69 | } 70 | } 71 | 72 | /// Wrapper struct for a temporary buffer, providing methods for working with an `ngx_buf_t`. 73 | pub struct TemporaryBuffer(*mut ngx_buf_t); 74 | 75 | impl TemporaryBuffer { 76 | /// Creates a new `TemporaryBuffer` from an `ngx_buf_t` pointer. 77 | /// 78 | /// # Panics 79 | /// Panics if the given buffer pointer is null. 80 | pub fn from_ngx_buf(buf: *mut ngx_buf_t) -> TemporaryBuffer { 81 | assert!(!buf.is_null()); 82 | TemporaryBuffer(buf) 83 | } 84 | } 85 | 86 | impl Buffer for TemporaryBuffer { 87 | /// Returns the underlying `ngx_buf_t` pointer as a raw pointer. 88 | fn as_ngx_buf(&self) -> *const ngx_buf_t { 89 | self.0 90 | } 91 | 92 | /// Returns a mutable reference to the underlying `ngx_buf_t` pointer. 93 | fn as_ngx_buf_mut(&mut self) -> *mut ngx_buf_t { 94 | self.0 95 | } 96 | } 97 | 98 | impl MutableBuffer for TemporaryBuffer { 99 | /// Returns a mutable reference to the buffer contents as a byte slice. 100 | fn as_bytes_mut(&mut self) -> &mut [u8] { 101 | unsafe { slice::from_raw_parts_mut((*self.0).pos, self.len()) } 102 | } 103 | } 104 | 105 | /// Wrapper struct for a memory buffer, providing methods for working with an `ngx_buf_t`. 106 | pub struct MemoryBuffer(*mut ngx_buf_t); 107 | 108 | impl MemoryBuffer { 109 | /// Creates a new `MemoryBuffer` from an `ngx_buf_t` pointer. 110 | /// 111 | /// # Panics 112 | /// Panics if the given buffer pointer is null. 113 | pub fn from_ngx_buf(buf: *mut ngx_buf_t) -> MemoryBuffer { 114 | assert!(!buf.is_null()); 115 | MemoryBuffer(buf) 116 | } 117 | } 118 | 119 | impl Buffer for MemoryBuffer { 120 | /// Returns the underlying `ngx_buf_t` pointer as a raw pointer. 121 | fn as_ngx_buf(&self) -> *const ngx_buf_t { 122 | self.0 123 | } 124 | 125 | /// Returns a mutable reference to the underlying `ngx_buf_t` pointer. 126 | fn as_ngx_buf_mut(&mut self) -> *mut ngx_buf_t { 127 | self.0 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | mod buffer; 2 | mod pool; 3 | mod status; 4 | mod string; 5 | 6 | pub use buffer::*; 7 | pub use pool::*; 8 | pub use status::*; 9 | pub use string::*; 10 | 11 | /// Gets an outer object pointer from a pointer to one of its fields. 12 | /// While there is no corresponding C macro, the pattern is common in the NGINX source. 13 | /// 14 | /// # Safety 15 | /// 16 | /// `$ptr` must be a valid pointer to the field `$field` of `$type`. 17 | #[macro_export] 18 | macro_rules! ngx_container_of { 19 | ($ptr:expr, $type:path, $field:ident) => { 20 | $ptr.byte_sub(::core::mem::offset_of!($type, $field)) 21 | .cast::<$type>() 22 | }; 23 | } 24 | -------------------------------------------------------------------------------- /src/core/pool.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::c_void; 2 | use core::{mem, ptr}; 3 | 4 | use crate::core::buffer::{Buffer, MemoryBuffer, TemporaryBuffer}; 5 | use crate::ffi::*; 6 | 7 | /// Wrapper struct for an [`ngx_pool_t`] pointer, providing methods for working with memory pools. 8 | /// 9 | /// See 10 | pub struct Pool(*mut ngx_pool_t); 11 | 12 | impl Pool { 13 | /// Creates a new `Pool` from an `ngx_pool_t` pointer. 14 | /// 15 | /// # Safety 16 | /// The caller must ensure that a valid `ngx_pool_t` pointer is provided, pointing to valid 17 | /// memory and non-null. A null argument will cause an assertion failure and panic. 18 | pub unsafe fn from_ngx_pool(pool: *mut ngx_pool_t) -> Pool { 19 | assert!(!pool.is_null()); 20 | Pool(pool) 21 | } 22 | 23 | /// Creates a buffer of the specified size in the memory pool. 24 | /// 25 | /// Returns `Some(TemporaryBuffer)` if the buffer is successfully created, or `None` if 26 | /// allocation fails. 27 | pub fn create_buffer(&mut self, size: usize) -> Option { 28 | let buf = unsafe { ngx_create_temp_buf(self.0, size) }; 29 | if buf.is_null() { 30 | return None; 31 | } 32 | 33 | Some(TemporaryBuffer::from_ngx_buf(buf)) 34 | } 35 | 36 | /// Creates a buffer from a string in the memory pool. 37 | /// 38 | /// Returns `Some(TemporaryBuffer)` if the buffer is successfully created, or `None` if 39 | /// allocation fails. 40 | pub fn create_buffer_from_str(&mut self, str: &str) -> Option { 41 | let mut buffer = self.create_buffer(str.len())?; 42 | unsafe { 43 | let buf = buffer.as_ngx_buf_mut(); 44 | ptr::copy_nonoverlapping(str.as_ptr(), (*buf).pos, str.len()); 45 | (*buf).last = (*buf).pos.add(str.len()); 46 | } 47 | Some(buffer) 48 | } 49 | 50 | /// Creates a buffer from a static string in the memory pool. 51 | /// 52 | /// Returns `Some(MemoryBuffer)` if the buffer is successfully created, or `None` if allocation 53 | /// fails. 54 | pub fn create_buffer_from_static_str(&mut self, str: &'static str) -> Option { 55 | let buf = self.calloc_type::(); 56 | if buf.is_null() { 57 | return None; 58 | } 59 | 60 | // We cast away const, but buffers with the memory flag are read-only 61 | let start = str.as_ptr() as *mut u8; 62 | let end = unsafe { start.add(str.len()) }; 63 | 64 | unsafe { 65 | (*buf).start = start; 66 | (*buf).pos = start; 67 | (*buf).last = end; 68 | (*buf).end = end; 69 | (*buf).set_memory(1); 70 | } 71 | 72 | Some(MemoryBuffer::from_ngx_buf(buf)) 73 | } 74 | 75 | /// Adds a cleanup handler for a value in the memory pool. 76 | /// 77 | /// Returns `Ok(())` if the cleanup handler is successfully added, or `Err(())` if the cleanup 78 | /// handler cannot be added. 79 | /// 80 | /// # Safety 81 | /// This function is marked as unsafe because it involves raw pointer manipulation. 82 | unsafe fn add_cleanup_for_value(&mut self, value: *mut T) -> Result<(), ()> { 83 | let cln = ngx_pool_cleanup_add(self.0, 0); 84 | if cln.is_null() { 85 | return Err(()); 86 | } 87 | (*cln).handler = Some(cleanup_type::); 88 | (*cln).data = value as *mut c_void; 89 | 90 | Ok(()) 91 | } 92 | 93 | /// Allocates memory from the pool of the specified size. 94 | /// The resulting pointer is aligned to a platform word size. 95 | /// 96 | /// Returns a raw pointer to the allocated memory. 97 | pub fn alloc(&mut self, size: usize) -> *mut c_void { 98 | unsafe { ngx_palloc(self.0, size) } 99 | } 100 | 101 | /// Allocates memory for a type from the pool. 102 | /// The resulting pointer is aligned to a platform word size. 103 | /// 104 | /// Returns a typed pointer to the allocated memory. 105 | pub fn alloc_type(&mut self) -> *mut T { 106 | self.alloc(mem::size_of::()) as *mut T 107 | } 108 | 109 | /// Allocates zeroed memory from the pool of the specified size. 110 | /// The resulting pointer is aligned to a platform word size. 111 | /// 112 | /// Returns a raw pointer to the allocated memory. 113 | pub fn calloc(&mut self, size: usize) -> *mut c_void { 114 | unsafe { ngx_pcalloc(self.0, size) } 115 | } 116 | 117 | /// Allocates zeroed memory for a type from the pool. 118 | /// The resulting pointer is aligned to a platform word size. 119 | /// 120 | /// Returns a typed pointer to the allocated memory. 121 | pub fn calloc_type(&mut self) -> *mut T { 122 | self.calloc(mem::size_of::()) as *mut T 123 | } 124 | 125 | /// Allocates unaligned memory from the pool of the specified size. 126 | /// 127 | /// Returns a raw pointer to the allocated memory. 128 | pub fn alloc_unaligned(&mut self, size: usize) -> *mut c_void { 129 | unsafe { ngx_pnalloc(self.0, size) } 130 | } 131 | 132 | /// Allocates unaligned memory for a type from the pool. 133 | /// 134 | /// Returns a typed pointer to the allocated memory. 135 | pub fn alloc_type_unaligned(&mut self) -> *mut T { 136 | self.alloc_unaligned(mem::size_of::()) as *mut T 137 | } 138 | 139 | /// Allocates memory for a value of a specified type and adds a cleanup handler to the memory 140 | /// pool. 141 | /// 142 | /// Returns a typed pointer to the allocated memory if successful, or a null pointer if 143 | /// allocation or cleanup handler addition fails. 144 | pub fn allocate(&mut self, value: T) -> *mut T { 145 | unsafe { 146 | let p = self.alloc(mem::size_of::()) as *mut T; 147 | ptr::write(p, value); 148 | if self.add_cleanup_for_value(p).is_err() { 149 | ptr::drop_in_place(p); 150 | return ptr::null_mut(); 151 | }; 152 | p 153 | } 154 | } 155 | } 156 | 157 | /// Cleanup handler for a specific type `T`. 158 | /// 159 | /// This function is called when cleaning up a value of type `T` in an FFI context. 160 | /// 161 | /// # Safety 162 | /// This function is marked as unsafe due to the raw pointer manipulation and the assumption that 163 | /// `data` is a valid pointer to `T`. 164 | /// 165 | /// # Arguments 166 | /// 167 | /// * `data` - A raw pointer to the value of type `T` to be cleaned up. 168 | unsafe extern "C" fn cleanup_type(data: *mut c_void) { 169 | ptr::drop_in_place(data as *mut T); 170 | } 171 | -------------------------------------------------------------------------------- /src/core/status.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use crate::ffi::*; 4 | 5 | /// Status 6 | /// 7 | /// Rust native wrapper for NGINX status codes. 8 | #[derive(Ord, PartialOrd, Eq, PartialEq)] 9 | pub struct Status(pub ngx_int_t); 10 | 11 | impl Status { 12 | /// Is this Status equivalent to NGX_OK? 13 | pub fn is_ok(&self) -> bool { 14 | self == &Status::NGX_OK 15 | } 16 | } 17 | 18 | impl fmt::Debug for Status { 19 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 20 | fmt::Debug::fmt(&self.0, f) 21 | } 22 | } 23 | 24 | impl From for ngx_int_t { 25 | fn from(val: Status) -> Self { 26 | val.0 27 | } 28 | } 29 | 30 | macro_rules! ngx_codes { 31 | ( 32 | $( 33 | $(#[$docs:meta])* 34 | ($konst:ident); 35 | )+ 36 | ) => { 37 | impl Status { 38 | $( 39 | $(#[$docs])* 40 | pub const $konst: Status = Status($konst as ngx_int_t); 41 | )+ 42 | 43 | } 44 | } 45 | } 46 | 47 | ngx_codes! { 48 | /// NGX_OK - Operation succeeded. 49 | (NGX_OK); 50 | /// NGX_ERROR - Operation failed. 51 | (NGX_ERROR); 52 | /// NGX_AGAIN - Operation incomplete; call the function again. 53 | (NGX_AGAIN); 54 | /// NGX_BUSY - Resource is not available. 55 | (NGX_BUSY); 56 | /// NGX_DONE - Operation complete or continued elsewhere. Also used as an alternative success code. 57 | (NGX_DONE); 58 | /// NGX_DECLINED - Operation rejected, for example, because it is disabled in the configuration. 59 | /// This is never an error. 60 | (NGX_DECLINED); 61 | /// NGX_ABORT - Function was aborted. Also used as an alternative error code. 62 | (NGX_ABORT); 63 | } 64 | 65 | /// NGX_CONF_ERROR - An error occurred while parsing and validating configuration. 66 | pub const NGX_CONF_ERROR: *const () = -1isize as *const (); 67 | // pub const CONF_OK: Status = Status(NGX_CONF_OK as ngx_int_t); 68 | -------------------------------------------------------------------------------- /src/core/string.rs: -------------------------------------------------------------------------------- 1 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 2 | use alloc::{borrow::Cow, string::String}; 3 | use core::cmp; 4 | use core::fmt; 5 | use core::str::{self, Utf8Error}; 6 | #[cfg(feature = "std")] 7 | use std::{borrow::Cow, string::String}; 8 | 9 | use crate::ffi::{ngx_str_t, u_char}; 10 | 11 | /// Static string initializer for [`ngx_str_t`]. 12 | /// 13 | /// The resulting byte string is always nul-terminated (just like a C string). 14 | /// 15 | /// [`ngx_str_t`]: https://nginx.org/en/docs/dev/development_guide.html#string_overview 16 | #[macro_export] 17 | macro_rules! ngx_string { 18 | ($s:expr) => {{ 19 | $crate::ffi::ngx_str_t { 20 | len: $s.len() as _, 21 | data: concat!($s, "\0").as_ptr() as *mut u8, 22 | } 23 | }}; 24 | } 25 | 26 | /// Representation of a borrowed [Nginx string]. 27 | /// 28 | /// [Nginx string]: https://nginx.org/en/docs/dev/development_guide.html#string_overview 29 | #[derive(Hash, PartialEq, Eq, PartialOrd, Ord)] 30 | #[repr(transparent)] 31 | pub struct NgxStr([u_char]); 32 | 33 | impl NgxStr { 34 | /// Create an [`NgxStr`] from an [`ngx_str_t`]. 35 | /// 36 | /// [`ngx_str_t`]: https://nginx.org/en/docs/dev/development_guide.html#string_overview 37 | /// 38 | /// # Safety 39 | /// 40 | /// The caller has provided a valid `ngx_str_t` with a `data` pointer that points 41 | /// to range of bytes of at least `len` bytes, whose content remains valid and doesn't 42 | /// change for the lifetime of the returned `NgxStr`. 43 | pub unsafe fn from_ngx_str<'a>(str: ngx_str_t) -> &'a NgxStr { 44 | let bytes: &[u8] = str.as_bytes(); 45 | &*(bytes as *const [u8] as *const NgxStr) 46 | } 47 | 48 | /// Create an [NgxStr] from a borrowed byte slice. 49 | #[inline] 50 | pub fn from_bytes(bytes: &[u8]) -> &Self { 51 | // SAFETY: An `NgxStr` is identical to a `[u8]` slice, given `u_char` is an alias for `u8` 52 | unsafe { &*(bytes as *const [u8] as *const NgxStr) } 53 | } 54 | 55 | /// Create a mutable [NgxStr] from a borrowed byte slice. 56 | #[inline] 57 | pub fn from_bytes_mut(bytes: &mut [u8]) -> &mut Self { 58 | // SAFETY: An `NgxStr` is identical to a `[u8]` slice, given `u_char` is an alias for `u8` 59 | unsafe { &mut *(bytes as *mut [u8] as *mut NgxStr) } 60 | } 61 | 62 | /// Access the [`NgxStr`] as a byte slice. 63 | pub fn as_bytes(&self) -> &[u8] { 64 | &self.0 65 | } 66 | 67 | /// Yields a `&str` slice if the [`NgxStr`] contains valid UTF-8. 68 | pub fn to_str(&self) -> Result<&str, Utf8Error> { 69 | str::from_utf8(self.as_bytes()) 70 | } 71 | 72 | /// Converts an [`NgxStr`] into a [`Cow`], replacing invalid UTF-8 sequences. 73 | /// 74 | /// See [`String::from_utf8_lossy`]. 75 | #[cfg(feature = "alloc")] 76 | pub fn to_string_lossy(&self) -> Cow { 77 | String::from_utf8_lossy(self.as_bytes()) 78 | } 79 | 80 | /// Returns `true` if the [`NgxStr`] is empty, otherwise `false`. 81 | pub fn is_empty(&self) -> bool { 82 | self.0.is_empty() 83 | } 84 | } 85 | 86 | impl AsRef<[u8]> for NgxStr { 87 | #[inline] 88 | fn as_ref(&self) -> &[u8] { 89 | self.as_bytes() 90 | } 91 | } 92 | 93 | impl AsMut<[u8]> for NgxStr { 94 | #[inline] 95 | fn as_mut(&mut self) -> &mut [u8] { 96 | &mut self.0 97 | } 98 | } 99 | 100 | impl fmt::Debug for NgxStr { 101 | #[inline] 102 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 103 | // XXX: Use debug_tuple() and feature(debug_closure_helpers) once it's stabilized 104 | f.write_str("NgxStr(")?; 105 | nginx_sys::detail::debug_bytes(f, &self.0)?; 106 | f.write_str(")") 107 | } 108 | } 109 | 110 | impl Default for &NgxStr { 111 | fn default() -> Self { 112 | NgxStr::from_bytes(&[]) 113 | } 114 | } 115 | 116 | impl fmt::Display for NgxStr { 117 | #[inline] 118 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 119 | nginx_sys::detail::display_bytes(f, &self.0) 120 | } 121 | } 122 | 123 | macro_rules! impl_partial_ord_eq_from { 124 | ($self:ty, $other:ty) => { impl_partial_ord_eq_from!($self, $other;); }; 125 | 126 | ($self:ty, $other:ty; $($args:tt)*) => { 127 | impl<'a, $($args)*> From<$other> for &'a NgxStr { 128 | #[inline] 129 | fn from(other: $other) -> Self { 130 | let other: &[u8] = other.as_ref(); 131 | NgxStr::from_bytes(other) 132 | } 133 | } 134 | 135 | impl_partial_eq!($self, $other; $($args)*); 136 | impl_partial_ord!($self, $other; $($args)*); 137 | }; 138 | } 139 | 140 | macro_rules! impl_partial_eq { 141 | ($self:ty, $other:ty) => { impl_partial_eq!($self, $other;); }; 142 | 143 | ($self:ty, $other:ty; $($args:tt)*) => { 144 | impl<'a, $($args)*> PartialEq<$other> for $self { 145 | #[inline] 146 | fn eq(&self, other: &$other) -> bool { 147 | let other: &[u8] = other.as_ref(); 148 | PartialEq::eq(self.as_bytes(), other) 149 | } 150 | } 151 | 152 | impl<'a, $($args)*> PartialEq<$self> for $other { 153 | #[inline] 154 | fn eq(&self, other: &$self) -> bool { 155 | let this: &[u8] = self.as_ref(); 156 | PartialEq::eq(this, other.as_bytes()) 157 | } 158 | } 159 | }; 160 | } 161 | 162 | macro_rules! impl_partial_ord { 163 | ($self:ty, $other:ty) => { impl_partial_ord!($self, $other;); }; 164 | 165 | ($self:ty, $other:ty; $($args:tt)*) => { 166 | impl<'a, $($args)*> PartialOrd<$other> for $self { 167 | #[inline] 168 | fn partial_cmp(&self, other: &$other) -> Option { 169 | let other: &[u8] = other.as_ref(); 170 | PartialOrd::partial_cmp(self.as_bytes(), other) 171 | } 172 | } 173 | 174 | impl<'a, $($args)*> PartialOrd<$self> for $other { 175 | #[inline] 176 | fn partial_cmp(&self, other: &$self) -> Option { 177 | let this: &[u8] = self.as_ref(); 178 | PartialOrd::partial_cmp(this, other.as_bytes()) 179 | } 180 | } 181 | }; 182 | } 183 | 184 | impl_partial_eq!(NgxStr, [u8]); 185 | impl_partial_eq!(NgxStr, [u8; N]; const N: usize); 186 | impl_partial_eq!(NgxStr, str); 187 | impl_partial_eq!(NgxStr, ngx_str_t); 188 | impl_partial_eq!(&'a NgxStr, ngx_str_t); 189 | impl_partial_ord!(NgxStr, [u8]); 190 | impl_partial_ord!(NgxStr, [u8; N]; const N: usize); 191 | impl_partial_ord!(NgxStr, str); 192 | impl_partial_ord!(NgxStr, ngx_str_t); 193 | impl_partial_ord!(&'a NgxStr, ngx_str_t); 194 | impl_partial_ord_eq_from!(NgxStr, &'a [u8]); 195 | impl_partial_ord_eq_from!(NgxStr, &'a [u8; N]; const N: usize); 196 | impl_partial_ord_eq_from!(NgxStr, &'a str); 197 | 198 | #[cfg(feature = "alloc")] 199 | mod _alloc_impls { 200 | use super::*; 201 | impl_partial_eq!(NgxStr, String); 202 | impl_partial_eq!(&'a NgxStr, String); 203 | impl_partial_ord!(NgxStr, String); 204 | impl_partial_ord!(&'a NgxStr, String); 205 | impl_partial_ord_eq_from!(NgxStr, &'a String); 206 | } 207 | 208 | #[cfg(test)] 209 | mod tests { 210 | extern crate alloc; 211 | 212 | use alloc::string::ToString; 213 | 214 | use super::*; 215 | 216 | #[test] 217 | fn test_comparisons() { 218 | let string = "test".to_string(); 219 | let ngx_string = ngx_str_t { 220 | data: string.as_ptr().cast_mut(), 221 | len: string.len(), 222 | }; 223 | let ns: &NgxStr = string.as_bytes().into(); 224 | 225 | #[cfg(feature = "alloc")] 226 | assert_eq!(string, ns); 227 | assert_eq!(ngx_string, ns); 228 | assert_eq!(string.as_bytes(), ns); 229 | assert_eq!(string.as_str(), ns); 230 | assert_eq!(b"test", ns); 231 | assert_eq!("test", ns); 232 | 233 | #[cfg(feature = "alloc")] 234 | assert_eq!(ns, string); 235 | assert_eq!(ns, ngx_string); 236 | assert_eq!(ns, string.as_bytes()); 237 | assert_eq!(ns, string.as_str()); 238 | assert_eq!(ns, b"test"); 239 | assert_eq!(ns, "test"); 240 | } 241 | 242 | #[test] 243 | fn test_lifetimes() { 244 | let a: &NgxStr = "Hello World!".into(); 245 | 246 | let s = "Hello World!".to_string(); 247 | let b: &NgxStr = s.as_bytes().into(); 248 | 249 | // The compiler should detect that s is borrowed and fail. 250 | // drop(s); // ☢️ 251 | 252 | assert_eq!(a.0, b.0); 253 | } 254 | } 255 | -------------------------------------------------------------------------------- /src/ffi/mod.rs: -------------------------------------------------------------------------------- 1 | pub use nginx_sys::*; 2 | -------------------------------------------------------------------------------- /src/http/mod.rs: -------------------------------------------------------------------------------- 1 | mod conf; 2 | mod module; 3 | mod request; 4 | mod status; 5 | mod upstream; 6 | 7 | pub use conf::*; 8 | pub use module::*; 9 | pub use request::*; 10 | pub use status::*; 11 | -------------------------------------------------------------------------------- /src/http/module.rs: -------------------------------------------------------------------------------- 1 | use core::ffi::{c_char, c_void}; 2 | use core::fmt; 3 | use core::ptr; 4 | 5 | use crate::core::NGX_CONF_ERROR; 6 | use crate::core::*; 7 | use crate::ffi::*; 8 | 9 | /// MergeConfigError - configuration cannot be merged with levels above. 10 | #[derive(Debug)] 11 | pub enum MergeConfigError { 12 | /// No value provided for configuration argument 13 | NoValue, 14 | } 15 | 16 | #[cfg(feature = "std")] 17 | impl std::error::Error for MergeConfigError {} 18 | 19 | impl fmt::Display for MergeConfigError { 20 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 21 | match self { 22 | MergeConfigError::NoValue => "no value".fmt(fmt), 23 | } 24 | } 25 | } 26 | 27 | /// The `Merge` trait provides a method for merging configuration down through each level. 28 | /// 29 | /// A module configuration should implement this trait for setting its configuration throughout 30 | /// each level. 31 | pub trait Merge { 32 | /// Module merge function. 33 | /// 34 | /// # Returns 35 | /// Result, Ok on success or MergeConfigError on failure. 36 | fn merge(&mut self, prev: &Self) -> Result<(), MergeConfigError>; 37 | } 38 | 39 | impl Merge for () { 40 | fn merge(&mut self, _prev: &Self) -> Result<(), MergeConfigError> { 41 | Ok(()) 42 | } 43 | } 44 | 45 | /// The `HTTPModule` trait provides the NGINX configuration stage interface. 46 | /// 47 | /// These functions allocate structures, initialize them, and merge through the configuration 48 | /// layers. 49 | /// 50 | /// See for details. 51 | pub trait HttpModule { 52 | /// Returns reference to a global variable of type [ngx_module_t] created for this module. 53 | fn module() -> &'static ngx_module_t; 54 | 55 | /// # Safety 56 | /// 57 | /// Callers should provide valid non-null `ngx_conf_t` arguments. Implementers must 58 | /// guard against null inputs or risk runtime errors. 59 | unsafe extern "C" fn preconfiguration(_cf: *mut ngx_conf_t) -> ngx_int_t { 60 | Status::NGX_OK.into() 61 | } 62 | 63 | /// # Safety 64 | /// 65 | /// Callers should provide valid non-null `ngx_conf_t` arguments. Implementers must 66 | /// guard against null inputs or risk runtime errors. 67 | unsafe extern "C" fn postconfiguration(_cf: *mut ngx_conf_t) -> ngx_int_t { 68 | Status::NGX_OK.into() 69 | } 70 | 71 | /// # Safety 72 | /// 73 | /// Callers should provide valid non-null `ngx_conf_t` arguments. Implementers must 74 | /// guard against null inputs or risk runtime errors. 75 | unsafe extern "C" fn create_main_conf(cf: *mut ngx_conf_t) -> *mut c_void 76 | where 77 | Self: super::HttpModuleMainConf, 78 | Self::MainConf: Default, 79 | { 80 | let mut pool = Pool::from_ngx_pool((*cf).pool); 81 | pool.allocate::(Default::default()) as *mut c_void 82 | } 83 | 84 | /// # Safety 85 | /// 86 | /// Callers should provide valid non-null `ngx_conf_t` arguments. Implementers must 87 | /// guard against null inputs or risk runtime errors. 88 | unsafe extern "C" fn init_main_conf(_cf: *mut ngx_conf_t, _conf: *mut c_void) -> *mut c_char 89 | where 90 | Self: super::HttpModuleMainConf, 91 | Self::MainConf: Default, 92 | { 93 | ptr::null_mut() 94 | } 95 | 96 | /// # Safety 97 | /// 98 | /// Callers should provide valid non-null `ngx_conf_t` arguments. Implementers must 99 | /// guard against null inputs or risk runtime errors. 100 | unsafe extern "C" fn create_srv_conf(cf: *mut ngx_conf_t) -> *mut c_void 101 | where 102 | Self: super::HttpModuleServerConf, 103 | Self::ServerConf: Default, 104 | { 105 | let mut pool = Pool::from_ngx_pool((*cf).pool); 106 | pool.allocate::(Default::default()) as *mut c_void 107 | } 108 | 109 | /// # Safety 110 | /// 111 | /// Callers should provide valid non-null `ngx_conf_t` arguments. Implementers must 112 | /// guard against null inputs or risk runtime errors. 113 | unsafe extern "C" fn merge_srv_conf( 114 | _cf: *mut ngx_conf_t, 115 | prev: *mut c_void, 116 | conf: *mut c_void, 117 | ) -> *mut c_char 118 | where 119 | Self: super::HttpModuleServerConf, 120 | Self::ServerConf: Merge, 121 | { 122 | let prev = &mut *(prev as *mut Self::ServerConf); 123 | let conf = &mut *(conf as *mut Self::ServerConf); 124 | match conf.merge(prev) { 125 | Ok(_) => ptr::null_mut(), 126 | Err(_) => NGX_CONF_ERROR as _, 127 | } 128 | } 129 | 130 | /// # Safety 131 | /// 132 | /// Callers should provide valid non-null `ngx_conf_t` arguments. Implementers must 133 | /// guard against null inputs or risk runtime errors. 134 | unsafe extern "C" fn create_loc_conf(cf: *mut ngx_conf_t) -> *mut c_void 135 | where 136 | Self: super::HttpModuleLocationConf, 137 | Self::LocationConf: Default, 138 | { 139 | let mut pool = Pool::from_ngx_pool((*cf).pool); 140 | pool.allocate::(Default::default()) as *mut c_void 141 | } 142 | 143 | /// # Safety 144 | /// 145 | /// Callers should provide valid non-null `ngx_conf_t` arguments. Implementers must 146 | /// guard against null inputs or risk runtime errors. 147 | unsafe extern "C" fn merge_loc_conf( 148 | _cf: *mut ngx_conf_t, 149 | prev: *mut c_void, 150 | conf: *mut c_void, 151 | ) -> *mut c_char 152 | where 153 | Self: super::HttpModuleLocationConf, 154 | Self::LocationConf: Merge, 155 | { 156 | let prev = &mut *(prev as *mut Self::LocationConf); 157 | let conf = &mut *(conf as *mut Self::LocationConf); 158 | match conf.merge(prev) { 159 | Ok(_) => ptr::null_mut(), 160 | Err(_) => NGX_CONF_ERROR as _, 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/http/status.rs: -------------------------------------------------------------------------------- 1 | use core::fmt; 2 | 3 | use crate::core::Status; 4 | use crate::ffi::*; 5 | 6 | /// Represents an HTTP status code. 7 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 8 | pub struct HTTPStatus(pub ngx_uint_t); 9 | 10 | /// A possible error value when converting a `HTTPStatus` from a `u16` or `&str` 11 | /// 12 | /// This error indicates that the supplied input was not a valid number, was less 13 | /// than 100, or was greater than 599. 14 | #[derive(Debug)] 15 | pub struct InvalidHTTPStatusCode { 16 | _priv: (), 17 | } 18 | 19 | impl InvalidHTTPStatusCode { 20 | fn new() -> InvalidHTTPStatusCode { 21 | InvalidHTTPStatusCode { _priv: () } 22 | } 23 | } 24 | 25 | impl fmt::Display for InvalidHTTPStatusCode { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 27 | f.write_str("invalid status code") 28 | } 29 | } 30 | 31 | #[cfg(feature = "std")] 32 | impl std::error::Error for InvalidHTTPStatusCode {} 33 | 34 | impl From for Status { 35 | fn from(val: HTTPStatus) -> Self { 36 | Status(val.0 as ngx_int_t) 37 | } 38 | } 39 | 40 | impl From for ngx_uint_t { 41 | fn from(val: HTTPStatus) -> Self { 42 | val.0 43 | } 44 | } 45 | 46 | impl fmt::Debug for HTTPStatus { 47 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 48 | fmt::Debug::fmt(&self.0, f) 49 | } 50 | } 51 | 52 | impl HTTPStatus { 53 | /// Convets a u16 to a status code. 54 | #[inline] 55 | pub fn from_u16(src: u16) -> Result { 56 | if !(100..600).contains(&src) { 57 | return Err(InvalidHTTPStatusCode::new()); 58 | } 59 | 60 | Ok(HTTPStatus(src.into())) 61 | } 62 | 63 | /// Converts a &[u8] to a status code. 64 | pub fn from_bytes(src: &[u8]) -> Result { 65 | if src.len() != 3 { 66 | return Err(InvalidHTTPStatusCode::new()); 67 | } 68 | 69 | let a = src[0].wrapping_sub(b'0') as u16; 70 | let b = src[1].wrapping_sub(b'0') as u16; 71 | let c = src[2].wrapping_sub(b'0') as u16; 72 | 73 | if a == 0 || a > 5 || b > 9 || c > 9 { 74 | return Err(InvalidHTTPStatusCode::new()); 75 | } 76 | 77 | let status = (a * 100) + (b * 10) + c; 78 | Ok(HTTPStatus(status.into())) 79 | } 80 | } 81 | 82 | macro_rules! http_status_codes { 83 | ( 84 | $( 85 | $(#[$docs:meta])* 86 | ($num:expr, $konst:ident, $phrase:expr); 87 | )+ 88 | ) => { 89 | impl HTTPStatus { 90 | $( 91 | $(#[$docs])* 92 | pub const $konst: HTTPStatus = HTTPStatus($num); 93 | )+ 94 | 95 | } 96 | } 97 | } 98 | 99 | http_status_codes! { 100 | /// 100 CONTINUE 101 | (100, CONTINUE, "Continue"); 102 | /// 101 SWITCHING_PROTOCOLS 103 | (101, SWITCHING_PROTOCOLS, "Switching Protocols"); 104 | /// 102 PROCESSING 105 | (102, PROCESSING, "Processing"); 106 | /// 200 OK 107 | (200, OK, "OK"); 108 | /// 201 Created 109 | (201, CREATED, "Created"); 110 | /// 202 Accepted 111 | (202, ACCEPTED, "Accepted"); 112 | /// 204 No Content 113 | (204, NO_CONTENT, "No Content"); 114 | /// 206 Partial Content 115 | (206, PARTIAL_CONTENT, "Partial Content"); 116 | 117 | /// 300 SPECIAL_RESPONSE 118 | (300, SPECIAL_RESPONSE, "SPECIAL_RESPONSE"); 119 | /// 301 Moved Permanently 120 | (301, MOVED_PERMANENTLY, "Moved Permanently"); 121 | /// 302 Moved Temporarily 122 | (302, MOVED_TEMPORARILY, "Moved Temporarily"); 123 | /// 303 See Other 124 | (303, SEE_OTHER, "See Other"); 125 | /// 304 Not Modified 126 | (304, NOT_MODIFIED, "Not Modified"); 127 | /// 307 Temporary Redirect 128 | (307, TEMPORARY_REDIRECT, "Temporary Redirect"); 129 | /// 308 Permanent Redirect 130 | (308, PERMANENT_REDIRECT, "Permanent Redirect"); 131 | 132 | /// 400 Bad Request 133 | (400, BAD_REQUEST, "Bad Request"); 134 | /// 401 Unauthorized 135 | (401, UNAUTHORIZED, "Unauthorized"); 136 | /// 403 Forbidden 137 | (403, FORBIDDEN, "Forbidden"); 138 | /// 404 Not Found 139 | (404, NOT_FOUND, "Not Found"); 140 | /// 405 Method Not Allowed 141 | (405, NOT_ALLOWED, "Method Not Allowed"); 142 | /// 408 Request Time Out 143 | (408, REQUEST_TIME_OUT, "Request Time Out"); 144 | /// 409 Conflict 145 | (409, CONFLICT, "Conflict"); 146 | /// 411 Length Required 147 | (411, LENGTH_REQUIRED, "Length Required"); 148 | /// 412 Precondition Failed 149 | (412, PRECONDITION_FAILED, "Precondition Failed"); 150 | /// 413 Payload Too Large 151 | (413, REQUEST_ENTITY_TOO_LARGE, "Payload Too Large"); 152 | /// 414 Request Uri Too Large 153 | (414, REQUEST_URI_TOO_LARGE, "Request Uri Too Large"); 154 | /// 415 Unsupported Media Type 155 | (415, UNSUPPORTED_MEDIA_TYPE, "Unsupported Media Type"); 156 | /// 416 Range Not Satisfiable 157 | (416, RANGE_NOT_SATISFIABLE, "Range Not Satisfiable"); 158 | /// 421 Misdirected Request 159 | (421, MISDIRECTED_REQUEST, "Misdirected Request"); 160 | /// 429 Too Many Requests 161 | (429, TOO_MANY_REQUESTS, "Too Many Requests"); 162 | 163 | // /* Our own HTTP codes */ 164 | // /* The special code to close connection without any response */ 165 | /// 444 CLOSE 166 | (444, CLOSE, "CLOSE"); 167 | 168 | /// 494 NGINX_CODES 169 | (494, NGINX_CODES, "NGINX_CODES"); 170 | 171 | /// 494 REQUEST_HEADER_TOO_LARGE 172 | (494, REQUEST_HEADER_TOO_LARGE, "REQUEST_HEADER_TOO_LARGE"); 173 | 174 | /// 495 NGX_HTTPS_CERT_ERROR 175 | (495, HTTPS_CERT_ERROR, "NGX_HTTPS_CERT_ERROR"); 176 | /// 496 NGX_HTTPS_NO_CERT 177 | (496, HTTPS_NO_CERT, "NGX_HTTPS_NO_CERT"); 178 | 179 | // /* 180 | // * We use the special code for the plain HTTP requests that are sent to 181 | // * HTTPS port to distinguish it from 4XX in an error page redirection 182 | // */ 183 | /// 497 TO_HTTPS 184 | (497, TO_HTTPS, "TO_HTTPS"); 185 | 186 | /// 499 CLIENT_CLOSED_REQUEST 187 | (499, CLIENT_CLOSED_REQUEST, "CLIENT_CLOSED_REQUEST"); 188 | 189 | /// 500 INTERNAL_SERVER_ERROR 190 | (500, INTERNAL_SERVER_ERROR, "INTERNAL_SERVER_ERROR"); 191 | /// 501 NOT_IMPLEMENTED 192 | (501, NOT_IMPLEMENTED, "NOT_IMPLEMENTED"); 193 | /// 502 BAD_GATEWAY 194 | (502, BAD_GATEWAY, "BAD_GATEWAY"); 195 | /// 503 SERVICE_UNAVAILABLE 196 | (503, SERVICE_UNAVAILABLE, "SERVICE_UNAVAILABLE"); 197 | /// 504 GATEWAY_TIME_OUT 198 | (504, GATEWAY_TIME_OUT, "GATEWAY_TIME_OUT"); 199 | /// 505 VERSION_NOT_SUPPORTED 200 | (505, VERSION_NOT_SUPPORTED, "VERSION_NOT_SUPPORTED"); 201 | /// 507 INSUFFICIENT_STORAGE 202 | (507, INSUFFICIENT_STORAGE, "INSUFFICIENT_STORAGE"); 203 | } 204 | -------------------------------------------------------------------------------- /src/http/upstream.rs: -------------------------------------------------------------------------------- 1 | /// Define a static upstream peer initializer 2 | /// 3 | /// Initializes the upstream 'get', 'free', and 'session' callbacks and gives the module writer an 4 | /// opportunity to set custom data. 5 | /// 6 | /// This macro will define the NGINX callback type: 7 | /// `typedef ngx_int_t (*ngx_http_upstream_init_peer_pt)(ngx_http_request_t *r, 8 | /// ngx_http_upstream_srv_conf_t *us)`, we keep this macro name in-sync with its underlying NGINX 9 | /// type, this callback is required to initialize your peer. 10 | /// 11 | /// Load Balancing: 12 | #[macro_export] 13 | macro_rules! http_upstream_init_peer_pt { 14 | ( $name: ident, $handler: expr ) => { 15 | extern "C" fn $name( 16 | r: *mut $crate::ffi::ngx_http_request_t, 17 | us: *mut $crate::ffi::ngx_http_upstream_srv_conf_t, 18 | ) -> $crate::ffi::ngx_int_t { 19 | let status: $crate::core::Status = $handler( 20 | unsafe { &mut $crate::http::Request::from_ngx_http_request(r) }, 21 | us, 22 | ); 23 | status.0 24 | } 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Bindings to NGINX 2 | //! This project provides Rust SDK interfaces to the [NGINX](https://nginx.com) proxy allowing the creation of NGINX 3 | //! dynamic modules completely in Rust. 4 | //! 5 | //! ## Build 6 | //! 7 | //! NGINX modules can be built against a particular version of NGINX. The following environment variables can be used 8 | //! to specify a particular version of NGINX or an NGINX dependency: 9 | //! 10 | //! * `ZLIB_VERSION` (default 1.3.1) - zlib version 11 | //! * `PCRE2_VERSION` (default 10.45 for NGINX 1.22.0 and later, or 8.45 for earlier) - PCRE1 or PCRE2 version 12 | //! * `OPENSSL_VERSION` (default 3.5.0 for NGINX 1.22.0 and later, or 1.1.1w for earlier) - OpenSSL version 13 | //! * `NGX_VERSION` (default 1.28.0) - NGINX OSS version 14 | //! * `NGX_DEBUG` (default to false) - if set to true, then will compile NGINX `--with-debug` option 15 | //! 16 | //! For example, this is how you would compile the [examples](https://github.com/nginx/ngx-rust/tree/master/examples) using a specific version of NGINX and enabling 17 | //! debugging: `NGX_DEBUG=true NGX_VERSION=1.23.0 cargo build --package=examples --examples --release` 18 | //! 19 | //! To build Linux-only modules, use the "linux" feature: `cargo build --package=examples --examples --features=linux --release` 20 | //! 21 | //! After compilation, the modules can be found in the path `target/release/examples/` ( with the `.so` file extension for 22 | //! Linux or `.dylib` for MacOS). 23 | //! 24 | //! Additionally, the folder `.cache/nginx/{NGX_VERSION}/{OS}/` will contain the compiled version of NGINX used to build 25 | //! the SDK. You can start NGINX directly from this directory if you want to test the module or add it to `$PATH` 26 | //! ```not_rust 27 | //! $ export NGX_VERSION=1.23.3 28 | //! $ cargo build --package=examples --examples --features=linux --release 29 | //! $ export PATH=$PATH:`pwd`/.cache/nginx/$NGX_VERSION/macos-x86_64/sbin 30 | //! $ nginx -V 31 | //! $ ls -la ./target/release/examples/ 32 | //! # now you can use dynamic modules with the NGINX 33 | //! ``` 34 | 35 | #![warn(missing_docs)] 36 | // support both std and no_std 37 | #![no_std] 38 | #[cfg(all(not(feature = "std"), feature = "alloc"))] 39 | extern crate alloc; 40 | #[cfg(feature = "std")] 41 | extern crate std; 42 | 43 | /// The core module. 44 | /// 45 | /// This module provides fundamental utilities needed to interface with many NGINX primitives. 46 | /// String conversions, the pool (memory interface) object, and buffer APIs are covered here. These 47 | /// utilities will generally align with the NGINX 'core' files and APIs. 48 | pub mod core; 49 | 50 | /// The ffi module. 51 | /// 52 | /// This module provides scoped FFI bindings for NGINX symbols. 53 | pub mod ffi; 54 | 55 | /// The http module. 56 | /// 57 | /// This modules provides wrappers and utilities to NGINX http APIs, such as requests, 58 | /// configuration access, and statuses. 59 | pub mod http; 60 | 61 | /// The log module. 62 | /// 63 | /// This module provides an interface into the NGINX logger framework. 64 | pub mod log; 65 | 66 | /// Define modules exported by this library. 67 | /// 68 | /// These are normally generated by the Nginx module system, but need to be 69 | /// defined when building modules outside of it. 70 | #[macro_export] 71 | macro_rules! ngx_modules { 72 | ($( $mod:ident ),+) => { 73 | #[no_mangle] 74 | #[allow(non_upper_case_globals)] 75 | pub static mut ngx_modules: [*const $crate::ffi::ngx_module_t; $crate::count!($( $mod, )+) + 1] = [ 76 | $( unsafe { &$mod } as *const $crate::ffi::ngx_module_t, )+ 77 | ::core::ptr::null() 78 | ]; 79 | 80 | #[no_mangle] 81 | #[allow(non_upper_case_globals)] 82 | pub static mut ngx_module_names: [*const ::core::ffi::c_char; $crate::count!($( $mod, )+) + 1] = [ 83 | $( concat!(stringify!($mod), "\0").as_ptr() as *const ::core::ffi::c_char, )+ 84 | ::core::ptr::null() 85 | ]; 86 | 87 | #[no_mangle] 88 | #[allow(non_upper_case_globals)] 89 | pub static mut ngx_module_order: [*const ::core::ffi::c_char; 1] = [ 90 | ::core::ptr::null() 91 | ]; 92 | }; 93 | } 94 | 95 | /// Count number of arguments 96 | #[macro_export] 97 | macro_rules! count { 98 | () => { 0usize }; 99 | ($x:tt, $( $xs:tt ),*) => { 1usize + $crate::count!($( $xs, )*) }; 100 | } 101 | -------------------------------------------------------------------------------- /tests/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!( 3 | "cargo:rustc-env=TARGET={}", 4 | std::env::var("TARGET").unwrap() 5 | ); 6 | } 7 | -------------------------------------------------------------------------------- /tests/conditional-compilation.rs: -------------------------------------------------------------------------------- 1 | use ngx::ffi; 2 | 3 | #[test] 4 | fn test_os_symbols() { 5 | #[cfg(ngx_os = "freebsd")] 6 | assert_eq!(ffi::NGX_FREEBSD, 1); 7 | 8 | #[cfg(ngx_os = "linux")] 9 | assert_eq!(ffi::NGX_LINUX, 1); 10 | 11 | #[cfg(ngx_os = "darwin")] 12 | assert_eq!(ffi::NGX_DARWIN, 1); 13 | } 14 | 15 | #[test] 16 | fn test_feature_symbols() { 17 | let ev: ffi::ngx_event_t = unsafe { std::mem::zeroed() }; 18 | 19 | assert_eq!(ev.available, 0); 20 | 21 | #[cfg(ngx_feature = "have_kqueue")] 22 | assert_eq!(ev.kq_errno, 0); 23 | } 24 | -------------------------------------------------------------------------------- /tests/config/nginx.conf: -------------------------------------------------------------------------------- 1 | #load_module modules/ngx_http_rust_module.so; 2 | 3 | #user nobody; 4 | worker_processes 1; 5 | 6 | error_log logs/error.log; 7 | #error_log logs/error.log notice; 8 | #error_log logs/error.log info; 9 | 10 | #pid logs/nginx.pid; 11 | 12 | 13 | events { 14 | worker_connections 1024; 15 | } 16 | 17 | 18 | http { 19 | include mime.types; 20 | default_type application/octet-stream; 21 | 22 | 23 | # rust test; 24 | 25 | 26 | 27 | server { 28 | listen 8080; 29 | server_name localhost; 30 | 31 | #charset koi8-r; 32 | 33 | #access_log logs/host.access.log main; 34 | 35 | location / { 36 | root html; 37 | index index.html index.htm; 38 | } 39 | 40 | 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /tests/log_test.rs: -------------------------------------------------------------------------------- 1 | use std::fs; 2 | use std::io::Result; 3 | #[cfg(unix)] 4 | use std::os::unix::ffi::OsStrExt; 5 | use std::path::{Path, PathBuf}; 6 | use std::process::Command; 7 | use std::process::Output; 8 | 9 | use ngx::ffi::{NGX_CONF_PATH, NGX_PREFIX, NGX_SBIN_PATH}; 10 | 11 | /// Convert a CStr to a PathBuf 12 | pub fn cstr_to_path(val: &std::ffi::CStr) -> Option { 13 | if val.is_empty() { 14 | return None; 15 | } 16 | 17 | #[cfg(unix)] 18 | let str = std::ffi::OsStr::from_bytes(val.to_bytes()); 19 | #[cfg(not(unix))] 20 | let str = std::str::from_utf8(val.to_bytes()).ok()?; 21 | 22 | Some(PathBuf::from(str)) 23 | } 24 | 25 | /// harness to test nginx 26 | pub struct Nginx { 27 | pub install_path: PathBuf, 28 | pub config_path: PathBuf, 29 | } 30 | 31 | impl Default for Nginx { 32 | /// create nginx with default 33 | fn default() -> Nginx { 34 | let install_path = cstr_to_path(NGX_PREFIX).expect("installation prefix"); 35 | Nginx::new(install_path) 36 | } 37 | } 38 | 39 | impl Nginx { 40 | pub fn new>(path: P) -> Nginx { 41 | let install_path = path.as_ref(); 42 | let config_path = cstr_to_path(NGX_CONF_PATH).expect("configuration path"); 43 | let config_path = install_path.join(config_path); 44 | 45 | Nginx { 46 | install_path: install_path.into(), 47 | config_path, 48 | } 49 | } 50 | 51 | /// get bin path to nginx instance 52 | pub fn bin_path(&mut self) -> PathBuf { 53 | let bin_path = cstr_to_path(NGX_SBIN_PATH).expect("binary path"); 54 | self.install_path.join(bin_path) 55 | } 56 | 57 | /// start nginx process with arguments 58 | pub fn cmd(&mut self, args: &[&str]) -> Result { 59 | let bin_path = self.bin_path(); 60 | let result = Command::new(bin_path).args(args).output(); 61 | 62 | match result { 63 | Err(e) => Err(e), 64 | 65 | Ok(output) => { 66 | println!("status: {}", output.status); 67 | println!("stdout: {}", String::from_utf8_lossy(&output.stdout)); 68 | println!("stderr: {}", String::from_utf8_lossy(&output.stderr)); 69 | Ok(output) 70 | } 71 | } 72 | } 73 | 74 | /// complete stop the nginx binary 75 | pub fn stop(&mut self) -> Result { 76 | self.cmd(&["-s", "stop"]) 77 | } 78 | 79 | /// start the nginx binary 80 | pub fn start(&mut self) -> Result { 81 | self.cmd(&[]) 82 | } 83 | 84 | // make sure we stop existing nginx and start new master process 85 | // intentinally ignore failure in stop 86 | pub fn restart(&mut self) -> Result { 87 | let _ = self.stop(); 88 | self.start() 89 | } 90 | 91 | // replace config with another config 92 | pub fn replace_config>(&mut self, from: P) -> Result { 93 | println!( 94 | "copying config from: {:?} to: {:?}", 95 | from.as_ref(), 96 | self.config_path 97 | ); // replace with logging 98 | fs::copy(from, &self.config_path) 99 | } 100 | } 101 | 102 | #[cfg(test)] 103 | mod tests { 104 | use std::env; 105 | 106 | use super::*; 107 | 108 | const TEST_NGINX_CONFIG: &str = "tests/nginx.conf"; 109 | 110 | #[test] 111 | fn test() { 112 | let mut nginx = Nginx::default(); 113 | 114 | let current_dir = env::current_dir().expect("Unable to get current directory"); 115 | let test_config_path = current_dir.join(TEST_NGINX_CONFIG); 116 | 117 | assert!( 118 | test_config_path.exists(), 119 | "Config file not found: {}\nCurrent directory: {}", 120 | test_config_path.to_string_lossy(), 121 | current_dir.to_string_lossy() 122 | ); 123 | 124 | nginx.replace_config(&test_config_path).unwrap_or_else(|_| { 125 | panic!( 126 | "Unable to load config file: {}", 127 | test_config_path.to_string_lossy() 128 | ) 129 | }); 130 | let output = nginx.restart().expect("Unable to restart NGINX"); 131 | assert!(output.status.success()); 132 | 133 | let output = nginx.stop().expect("Unable to stop NGINX"); 134 | assert!(output.status.success()); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /tests/module/config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_rust_module 2 | 3 | ngx_module_type=HTTP_FILTER 4 | ngx_module_name=ngx_http_rust_module 5 | ngx_module_srcs="$ngx_addon_dir/ngx_http_rust_module.c" 6 | ngx_module_deps="cargo" 7 | ngx_module_libs="$ngx_addon_dir/../target/debug/libngx_rust.a" 8 | 9 | 10 | . auto/module 11 | 12 | 13 | -------------------------------------------------------------------------------- /tests/module/config.make: -------------------------------------------------------------------------------- 1 | 2 | cat << END >> $NGX_MAKEFILE 3 | 4 | cargo: 5 | cargo build --manifest-path $ngx_addon_dir/../Cargo.toml --lib 6 | 7 | END 8 | -------------------------------------------------------------------------------- /tests/module/ngx_http_rust_module.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ngx_rust.c 3 | * @author Sehyo Chang 4 | * @date 5 | * 6 | * @brief Dummy module 7 | * 8 | * @section LICENSE 9 | * 10 | * Copyright (C) 2011 by Nginx 11 | * 12 | */ 13 | #include 14 | #include 15 | #include 16 | 17 | 18 | /** 19 | * @brief element mixer configuration 20 | */ 21 | typedef struct { 22 | ngx_str_t name; /**< test name */ 23 | } ngx_http_rust_main_conf_t; 24 | 25 | 26 | 27 | static void *ngx_http_rust_create_main_conf(ngx_conf_t *cf); 28 | 29 | 30 | /* 31 | * dummy rust 32 | */ 33 | static ngx_command_t ngx_http_rust_commands[] = { 34 | { 35 | ngx_string("rust"), /* dummy directive */ 36 | NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, // server takes 1 // 37 | ngx_conf_set_str_slot, /* configuration setup function */ 38 | NGX_HTTP_MAIN_CONF_OFFSET, 39 | offsetof(ngx_http_rust_main_conf_t, name), 40 | NULL 41 | }, 42 | ngx_null_command /* command termination */ 43 | }; 44 | 45 | 46 | /* The module context. */ 47 | static ngx_http_module_t ngx_http_rust_module_ctx = { 48 | NULL, /* preconfiguration */ 49 | NULL, /* postconfiguration */ 50 | ngx_http_rust_create_main_conf, /* create main configuration */ 51 | NULL, /* init main configuration */ 52 | 53 | NULL, /* create server configuration */ 54 | NULL, /* merge server configuration */ 55 | 56 | NULL, 57 | NULL 58 | }; 59 | 60 | /* Module definition. */ 61 | ngx_module_t ngx_http_rust_module = { 62 | NGX_MODULE_V1, 63 | &ngx_http_rust_module_ctx, /* module context */ 64 | ngx_http_rust_commands, /* module directives */ 65 | NGX_HTTP_MODULE, /* module type */ 66 | NULL, /* init master */ 67 | NULL, /* init module */ 68 | NULL, /* init process */ 69 | NULL, /* init thread */ 70 | NULL, /* exit thread */ 71 | NULL, /* exit process */ 72 | NULL, /* exit master */ 73 | NGX_MODULE_V1_PADDING 74 | }; 75 | 76 | 77 | 78 | static void *ngx_http_rust_create_main_conf(ngx_conf_t *cf) 79 | { 80 | ngx_http_rust_main_conf_t *conf; 81 | 82 | ngx_log_error(NGX_LOG_ERR, ngx_cycle->log, 0, "setting up main config"); 83 | 84 | 85 | conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_rust_main_conf_t)); 86 | if (conf == NULL) { 87 | return NULL; 88 | } 89 | 90 | 91 | return conf; 92 | } -------------------------------------------------------------------------------- /tests/nginx.conf: -------------------------------------------------------------------------------- 1 | #load_module modules/ngx_http_rust_module.so; 2 | 3 | #user nobody; 4 | worker_processes 1; 5 | 6 | error_log logs/error.log; 7 | #error_log logs/error.log notice; 8 | #error_log logs/error.log info; 9 | 10 | #pid logs/nginx.pid; 11 | 12 | 13 | events { 14 | worker_connections 1024; 15 | } 16 | 17 | 18 | http { 19 | include mime.types; 20 | default_type application/octet-stream; 21 | 22 | 23 | # rust test; 24 | 25 | 26 | 27 | server { 28 | listen 30000; 29 | server_name localhost; 30 | 31 | #charset koi8-r; 32 | 33 | #access_log logs/host.access.log main; 34 | 35 | location / { 36 | root html; 37 | index index.html index.htm; 38 | } 39 | 40 | 41 | } 42 | 43 | } 44 | --------------------------------------------------------------------------------