├── .envrc ├── .github └── workflows │ ├── ci.yml │ ├── docs.yml │ ├── release-perform.yml │ ├── release-prepare.yml │ └── rust-cache │ └── action.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── assets └── ngrok.png ├── flake.lock ├── flake.nix ├── ngrok-java-17 ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── ngrok │ └── net │ ├── ListenerServerSocket.java │ └── ListenerSocketImpl.java ├── ngrok-java-native ├── Cargo.lock ├── Cargo.toml ├── build.rs ├── pom.xml └── src │ ├── lib.rs │ ├── main │ ├── java │ │ └── com │ │ │ └── ngrok │ │ │ ├── AbstractEdge.java │ │ │ ├── AbstractEndpoint.java │ │ │ ├── NativeEdgeConnection.java │ │ │ ├── NativeEdgeForwarder.java │ │ │ ├── NativeEdgeListener.java │ │ │ ├── NativeEndpointConnection.java │ │ │ ├── NativeHttpForwarder.java │ │ │ ├── NativeHttpListener.java │ │ │ ├── NativeSession.java │ │ │ ├── NativeTcpForwarder.java │ │ │ ├── NativeTcpListener.java │ │ │ ├── NativeTlsForwarder.java │ │ │ ├── NativeTlsListener.java │ │ │ └── Runtime.java │ └── resources │ │ └── native.properties │ └── test │ ├── java │ └── com │ │ └── ngrok │ │ ├── DataTest.java │ │ └── ForwardTest.java │ └── resources │ └── policy.json ├── ngrok-java ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── ngrok │ │ ├── Connection.java │ │ ├── EdgeBuilder.java │ │ ├── EndpointBuilder.java │ │ ├── Forwarder.java │ │ ├── Http.java │ │ ├── HttpBuilder.java │ │ ├── Listener.java │ │ ├── ListenerInfo.java │ │ ├── MetadataBuilder.java │ │ ├── NgrokException.java │ │ ├── ProxyProto.java │ │ ├── Session.java │ │ ├── TcpBuilder.java │ │ ├── TlsBuilder.java │ │ └── net │ │ ├── AbstractSocketImpl.java │ │ ├── ConnectionInputStream.java │ │ ├── ConnectionOutputStream.java │ │ ├── ConnectionSocket.java │ │ └── ConnectionSocketImpl.java │ └── test │ └── java │ └── com │ └── ngrok │ ├── ConnectionTest.java │ └── net │ └── ConnectionOutputStreamTest.java ├── ngrok-jetty ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── ngrok │ └── jetty │ ├── NgrokConnector.java │ └── NgrokEndpoint.java ├── pom.xml └── toolchains.xml /.envrc: -------------------------------------------------------------------------------- 1 | dotenv_if_exists 2 | use flake 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | build: 13 | name: Build Ngrok Java 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | - uses: dtolnay/rust-toolchain@master 18 | with: 19 | toolchain: stable 20 | targets: x86_64-unknown-linux-gnu 21 | - uses: ./.github/workflows/rust-cache 22 | - uses: actions/setup-java@v3 23 | with: 24 | java-version: | 25 | 17 26 | 11 27 | distribution: 'temurin' 28 | cache: 'maven' 29 | - name: Verification 30 | run: mvn --batch-mode verify 31 | env: 32 | NGROK_AUTHTOKEN: ${{ secrets.NGROK_AUTHTOKEN }} 33 | 34 | # We need to target older glibc (as old as possible) to support customers on very old Linux versions. 35 | # If we end up needing to target older than 2.8, we will have to do a special workaround, as actions/checkout 36 | # won't work without newer glibc versions: 37 | # https://github.com/actions/checkout/issues/1809 38 | build-glibc-2_28: 39 | name: Build Ngrok Java (glibc 2.28) 40 | runs-on: ubuntu-latest 41 | container: quay.io/pypa/manylinux_2_28_x86_64 42 | steps: 43 | - uses: actions/checkout@v3 44 | - uses: dtolnay/rust-toolchain@master 45 | with: 46 | toolchain: stable 47 | targets: x86_64-unknown-linux-gnu 48 | - uses: ./.github/workflows/rust-cache 49 | - uses: actions/setup-java@v3 50 | with: 51 | java-version: | 52 | 17 53 | 11 54 | distribution: 'temurin' 55 | cache: 'maven' 56 | - name: Install maven 57 | run: | 58 | curl -sL https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz -o maven.tar.gz 59 | echo "332088670d14fa9ff346e6858ca0acca304666596fec86eea89253bd496d3c90deae2be5091be199f48e09d46cec817c6419d5161fb4ee37871503f472765d00 maven.tar.gz" | sha512sum -c - 60 | tar xvf maven.tar.gz 61 | rm maven.tar.gz 62 | echo "JAVA_11_HOME=$JAVA_HOME_11_X64" >> $GITHUB_ENV 63 | echo "JAVA_17_HOME=$JAVA_HOME_17_X64" >> $GITHUB_ENV 64 | echo "./apache-maven-3.8.8/bin/" >> $GITHUB_PATH 65 | - name: Verification 66 | run: mvn --global-toolchains toolchains.xml --batch-mode verify 67 | env: 68 | NGROK_AUTHTOKEN: ${{ secrets.NGROK_AUTHTOKEN }} 69 | 70 | udeps: 71 | name: Udeps 72 | runs-on: ubuntu-latest 73 | needs: 74 | - build 75 | steps: 76 | - uses: actions/checkout@v3 77 | - uses: jrobsonchase/direnv-action@v0.7 78 | - uses: ./.github/workflows/rust-cache 79 | - uses: actions/setup-java@v3 80 | with: 81 | java-version: '11' 82 | distribution: 'temurin' 83 | cache: 'maven' 84 | - name: generate rust files 85 | run: mvn --batch-mode --projects ngrok-java-native --also-make process-classes 86 | - uses: actions-rs/cargo@v1 87 | with: 88 | command: udeps 89 | args: '--workspace --all-targets --all-features --manifest-path ngrok-java-native/Cargo.toml' 90 | 91 | fmt: 92 | name: Rustfmt 93 | runs-on: ubuntu-latest 94 | needs: 95 | - build 96 | steps: 97 | - uses: actions/checkout@v3 98 | - uses: jrobsonchase/direnv-action@v0.7 99 | - uses: actions-rs/cargo@v1 100 | with: 101 | command: fmt 102 | args: '--all --manifest-path ngrok-java-native/Cargo.toml -- --check' 103 | 104 | clippy: 105 | name: Clippy 106 | runs-on: ubuntu-latest 107 | needs: 108 | - build 109 | steps: 110 | - uses: actions/checkout@v3 111 | - uses: jrobsonchase/direnv-action@v0.7 112 | - uses: ./.github/workflows/rust-cache 113 | - uses: actions/setup-java@v3 114 | with: 115 | java-version: '11' 116 | distribution: 'temurin' 117 | cache: 'maven' 118 | - name: generate rust files 119 | run: mvn --batch-mode --projects ngrok-java-native --also-make process-classes 120 | - uses: actions-rs/cargo@v1 121 | with: 122 | command: clippy 123 | args: '--all-targets --all-features --workspace --manifest-path ngrok-java-native/Cargo.toml -- -D warnings' 124 | -------------------------------------------------------------------------------- /.github/workflows/docs.yml: -------------------------------------------------------------------------------- 1 | name: Javadocs 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | gen-javadocs: 10 | name: Javadocs 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v3 14 | - uses: actions/setup-java@v3 15 | with: 16 | java-version: | 17 | 17 18 | 11 19 | distribution: "temurin" 20 | cache: "maven" 21 | - name: Build Javadocs 22 | run: mvn javadoc:aggregate -Dmaven.javadoc.skippedModules=ngrok-java-native 23 | - name: Archive Javadoc site 24 | run: tar --directory target/site/apidocs -cvf "$RUNNER_TEMP/artifact.tar" . 25 | - name: Upload Archive 26 | uses: actions/upload-artifact@v1 27 | with: 28 | name: github-pages 29 | path: ${{ runner.temp }}/artifact.tar 30 | retention-days: ${{ inputs.retention-days }} 31 | 32 | deploy: 33 | needs: gen-javadocs 34 | runs-on: ubuntu-latest 35 | 36 | # Grant GITHUB_TOKEN the permissions required to make a Pages deployment 37 | permissions: 38 | pages: write # to deploy to Pages 39 | id-token: write # to verify the deployment originates from an appropriate source 40 | 41 | # Deploy to the github-pages environment 42 | environment: 43 | name: github-pages 44 | url: ${{ steps.deployment.outputs.page_url }} 45 | 46 | steps: 47 | - name: Deploy to GitHub Pages 48 | id: deployment 49 | uses: actions/deploy-pages@v1 50 | -------------------------------------------------------------------------------- /.github/workflows/release-perform.yml: -------------------------------------------------------------------------------- 1 | name: Release Perform 2 | run-name: Release Perform v${{ github.event.inputs.releaseVersion }} 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | deploy: 8 | type: boolean 9 | required: true 10 | default: true 11 | releaseVersion: 12 | type: string 13 | required: true 14 | 15 | jobs: 16 | build-native: 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - host: ubuntu-latest 22 | target: i686-unknown-linux-gnu 23 | artifact: linux-x86_32 24 | linker: gcc-multilib 25 | pkgFlags: "--define 'skipTests'" 26 | - host: ubuntu-latest 27 | target: x86_64-unknown-linux-gnu 28 | artifact: linux-x86_64 29 | container: quay.io/pypa/manylinux_2_28_x86_64 30 | - host: ubuntu-latest 31 | target: aarch64-unknown-linux-gnu 32 | artifact: linux-aarch_64 33 | linker: gcc-aarch64-linux-gnu 34 | pkgFlags: "--define 'skipTests'" 35 | - host: ubuntu-latest 36 | target: armv7-linux-androideabi 37 | artifact: linux-android-armv7 38 | linker: gcc-arm-linux-gnueabihf 39 | pkgFlags: "--define 'skipTests'" 40 | - host: ubuntu-latest 41 | target: aarch64-linux-android 42 | artifact: linux-android-aarch_64 43 | linker: gcc-aarch64-linux-gnu 44 | pkgFlags: "--define 'skipTests'" 45 | - host: windows-latest 46 | target: i686-pc-windows-msvc 47 | artifact: windows-x86_32 48 | pkgFlags: "--define 'skipTests'" 49 | - host: windows-latest 50 | target: x86_64-pc-windows-msvc 51 | artifact: windows-x86_64 52 | - host: macos-13 53 | target: x86_64-apple-darwin 54 | artifact: osx-x86_64 55 | - host: macos-latest 56 | target: aarch64-apple-darwin 57 | artifact: osx-aarch_64 58 | runs-on: ${{ matrix.config.host }} 59 | container: ${{ matrix.config.container }} 60 | steps: 61 | - uses: actions/checkout@v3 62 | with: 63 | ref: v${{ github.event.inputs.releaseVersion }} 64 | - uses: dtolnay/rust-toolchain@master 65 | with: 66 | toolchain: stable 67 | targets: ${{ matrix.config.target }} 68 | - uses: ./.github/workflows/rust-cache 69 | - name: Install Linker 70 | if: matrix.config.linker 71 | run: | 72 | sudo apt update 73 | sudo apt install ${{ matrix.config.linker }} 74 | - name: Prepare aarch64 Linker 75 | if: matrix.config.target == 'aarch64-unknown-linux-gnu' 76 | run: | 77 | echo "CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc" >> $GITHUB_ENV 78 | echo "AR_aarch64_unknown_linux_gnu=llvm-ar-14" >> $GITHUB_ENV 79 | echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV 80 | - name: Prepare Android Linker 81 | if: contains(matrix.config.target, 'android') 82 | run: | 83 | ndk_root=${ANDROID_NDK}/toolchains/llvm/prebuilt/linux-x86_64/bin/ 84 | adjusted_target=$(echo "${{ matrix.config.target }}" | tr '-' '_') 85 | adjusted_target_upper=$(echo "$adjusted_target" | tr '[:lower:]' '[:upper:]') 86 | 87 | compiler="" 88 | if [[ "$adjusted_target" == *"aarch64"* ]]; then 89 | compiler="aarch64-linux-android21-clang" 90 | else 91 | compiler="armv7a-linux-androideabi21-clang" 92 | fi 93 | 94 | echo "CC_${adjusted_target}=${ndk_root}/${compiler}" >> $GITHUB_ENV 95 | echo "AR_${adjusted_target}=${ndk_root}/llvm-ar" >> $GITHUB_ENV 96 | echo "CARGO_TARGET_${adjusted_target_upper}_LINKER=${ndk_root}/${compiler}" >> $GITHUB_ENV 97 | - name: Setup Java 98 | uses: actions/setup-java@v3 99 | with: 100 | java-version: "11" 101 | distribution: "temurin" 102 | - name: Run mvn package for native 103 | if: ${{ ! matrix.config.container }} 104 | run: mvn --batch-mode --projects ngrok-java-native --also-make package --activate-profiles ci-native --define 'ngrok.native.classifier=${{ matrix.config.artifact }}' --define 'ngrok.native.target=${{ matrix.config.target }}' ${{ matrix.config.pkgFlags }} 105 | env: 106 | NGROK_AUTHTOKEN: ${{ secrets.NGROK_AUTHTOKEN }} 107 | - name: Install maven (with container) 108 | if: matrix.config.container 109 | run: | 110 | curl -sL https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.tar.gz -o maven.tar.gz 111 | echo "332088670d14fa9ff346e6858ca0acca304666596fec86eea89253bd496d3c90deae2be5091be199f48e09d46cec817c6419d5161fb4ee37871503f472765d00 maven.tar.gz" | sha512sum -c - 112 | tar xvf maven.tar.gz 113 | rm maven.tar.gz 114 | echo "JAVA_11_HOME=$JAVA_HOME_11_X64" >> $GITHUB_ENV 115 | echo "JAVA_17_HOME=$JAVA_HOME_11_X64" >> $GITHUB_ENV 116 | echo "./apache-maven-3.8.8/bin/" >> $GITHUB_PATH 117 | - name: Run mvn package for native (with container) 118 | if: matrix.config.container 119 | run: mvn --global-toolchains toolchains.xml --batch-mode --projects ngrok-java-native --also-make package --activate-profiles ci-native --define 'ngrok.native.classifier=${{ matrix.config.artifact }}' --define 'ngrok.native.target=${{ matrix.config.target }}' ${{ matrix.config.pkgFlags }} 120 | env: 121 | NGROK_AUTHTOKEN: ${{ secrets.NGROK_AUTHTOKEN }} 122 | - name: Upload Artifacts 123 | uses: actions/upload-artifact@v3 124 | with: 125 | name: ngrok-java-native-${{ github.event.inputs.releaseVersion }}-${{ matrix.config.artifact }} 126 | path: ngrok-java-native/target/ngrok-java-native-${{ github.event.inputs.releaseVersion }}-${{ matrix.config.artifact }}.jar 127 | retention-days: 1 128 | 129 | deploy-central: 130 | if: ${{ github.event.inputs.deploy }} 131 | runs-on: ubuntu-latest 132 | needs: 133 | - build-native 134 | steps: 135 | - uses: actions/checkout@v3 136 | with: 137 | ref: v${{ github.event.inputs.releaseVersion }} 138 | - uses: actions/setup-java@v3 139 | with: 140 | java-version: | 141 | 17 142 | 11 143 | distribution: "temurin" 144 | server-id: ossrh 145 | server-username: MAVEN_USERNAME 146 | server-password: MAVEN_PASSWORD 147 | gpg-private-key: ${{ secrets.OSSRH_GPG_PRIVATE_KEY }} 148 | gpg-passphrase: MAVEN_GPG_PASSPHRASE 149 | - uses: actions/download-artifact@v3 150 | with: 151 | path: ngrok-java-native/target/ 152 | - run: mv ngrok-java-native/target/*/* ngrok-java-native/target/ 153 | - name: Run mvn deploy 154 | run: mvn --batch-mode deploy --activate-profiles ci-distro,central-distro --fail-at-end 155 | env: 156 | MAVEN_USERNAME: ${{ secrets.OSSRH_USERNAME }} 157 | MAVEN_PASSWORD: ${{ secrets.OSSRH_TOKEN }} 158 | MAVEN_GPG_PASSPHRASE: ${{ secrets.OSSRH_GPG_PASSPHRASE }} 159 | NGROK_AUTHTOKEN: ${{ secrets.NGROK_AUTHTOKEN }} 160 | 161 | deploy-github: 162 | if: ${{ github.event.inputs.deploy }} 163 | runs-on: ubuntu-latest 164 | needs: 165 | - build-native 166 | steps: 167 | - uses: actions/checkout@v3 168 | with: 169 | ref: v${{ github.event.inputs.releaseVersion }} 170 | - uses: actions/setup-java@v3 171 | with: 172 | java-version: | 173 | 17 174 | 11 175 | distribution: "temurin" 176 | - uses: actions/download-artifact@v3 177 | with: 178 | path: ngrok-java-native/target/ 179 | - run: mv ngrok-java-native/target/*/* ngrok-java-native/target/ 180 | - name: Run mvn deploy 181 | run: mvn --batch-mode deploy --activate-profiles ci-distro,github-distro --fail-at-end 182 | env: 183 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 184 | NGROK_AUTHTOKEN: ${{ secrets.NGROK_AUTHTOKEN }} 185 | -------------------------------------------------------------------------------- /.github/workflows/release-prepare.yml: -------------------------------------------------------------------------------- 1 | name: Release Prepare 2 | run-name: Release Prepare v${{ github.event.inputs.releaseVersion }} 3 | 4 | on: 5 | workflow_dispatch: 6 | inputs: 7 | branch: 8 | required: true 9 | default: main 10 | releaseVersion: 11 | required: true 12 | developmentVersion: 13 | required: true 14 | 15 | jobs: 16 | release: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: actions/checkout@v3 20 | with: 21 | ref: ${{ github.event.inputs.branch }} 22 | - uses: dtolnay/rust-toolchain@master 23 | with: 24 | toolchain: stable 25 | targets: x86_64-unknown-linux-gnu 26 | - uses: ./.github/workflows/rust-cache 27 | - uses: actions/setup-java@v3 28 | with: 29 | java-version: | 30 | 17 31 | 11 32 | distribution: "temurin" 33 | - name: Configure Git User 34 | run: | 35 | git config user.email 'actions@github.com' 36 | git config user.name 'GitHub Actions' 37 | - name: Run mvn release 38 | run: mvn --batch-mode release:prepare -DreleaseVersion=${{ github.event.inputs.releaseVersion }} -DdevelopmentVersion=${{ github.event.inputs.developmentVersion }} 39 | env: 40 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 41 | NGROK_AUTHTOKEN: ${{ secrets.NGROK_AUTHTOKEN }} 42 | -------------------------------------------------------------------------------- /.github/workflows/rust-cache/action.yml: -------------------------------------------------------------------------------- 1 | name: 'rust cache setup' 2 | description: 'Set up cargo and sccache caches' 3 | inputs: {} 4 | outputs: {} 5 | runs: 6 | using: "composite" 7 | steps: 8 | - name: configure sccache 9 | uses: actions/github-script@v6 10 | with: 11 | script: | 12 | core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || ''); 13 | core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || ''); 14 | core.exportVariable('SCCACHE_GHA_CACHE_TO', 'sccache-${{ runner.os }}-${{ github.ref_name }}'); 15 | core.exportVariable('SCCACHE_GHA_CACHE_FROM', 'sccache-${{ runner.os }}-main,sccache-${{ runner.os }}-'); 16 | - name: cargo registry cache 17 | uses: actions/cache@v3 18 | with: 19 | key: cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}-${{ github.sha }} 20 | restore-keys: | 21 | cargo-${{ runner.os }}-${{ hashFiles('**/Cargo.toml') }}- 22 | cargo-${{ runner.os }}- 23 | path: | 24 | ~/.cargo/registry 25 | ~/.cargo/git 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings/ 4 | target/ 5 | .idea/ 6 | .vscode/ 7 | .direnv 8 | *.iml 9 | *.DS_Store 10 | *.releaseBackup 11 | release.properties 12 | .env 13 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # ngrok Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | The ngrok documentation team is responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | The ngrok documentation team has the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the ngrok docs project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [support@ngrok.com](mailto:support@ngrok.com). All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the Contributor Covenant, version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 44 | 45 | For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This project is licensed under either of 2 | 3 | - [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 4 | - [MIT license](http://opensource.org/licenses/MIT) 5 | 6 | at your option. 7 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: build 3 | 4 | .PHONY: build 5 | build: 6 | mvn package --global-toolchains toolchains.xml 7 | 8 | .PHONY: clean 9 | clean: 10 | mvn clean 11 | 12 | .PHONY: rebuild 13 | rebuild: 14 | mvn clean package --global-toolchains toolchains.xml 15 | 16 | .PHONY: install 17 | install: 18 | mvn install --global-toolchains toolchains.xml 19 | 20 | .PHONY: reinstall 21 | reinstall: 22 | mvn clean install --global-toolchains toolchains.xml 23 | 24 | .PHONY: javadoc 25 | javadoc: 26 | mvn javadoc:aggregate --global-toolchains toolchains.xml -Dmaven.javadoc.skippedModules=ngrok-java-native 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | ngrok Logo 4 | 5 |

6 | 7 | # The ngrok Agent SDK for Java 8 | 9 | [![MIT licensed][mit-badge]][mit-url] 10 | [![Apache-2.0 licensed][apache-badge]][apache-url] 11 | [![Continuous integration][ci-badge]][ci-url] 12 | ![Java](https://img.shields.io/badge/Java-11+-orange) 13 | [![Javadoc](https://img.shields.io/badge/JavaDoc-Online-green)](https://ngrok.github.io/ngrok-java/) 14 | ![Status](https://img.shields.io/badge/Status-Beta-yellow) 15 | 16 | [mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg 17 | [mit-url]: https://github.com/ngrok/ngrok-rs/blob/main/LICENSE-MIT 18 | [apache-badge]: https://img.shields.io/badge/license-Apache_2.0-blue.svg 19 | [apache-url]: https://github.com/ngrok/ngrok-rs/blob/main/LICENSE-APACHE 20 | [ci-badge]: https://github.com/ngrok/ngrok-java/actions/workflows/ci.yml/badge.svg 21 | [ci-url]: https://github.com/ngrok/ngrok-java/actions/workflows/ci.yml 22 | 23 | ngrok is a globally distributed reverse proxy commonly used for quickly getting a public URL to a 24 | service running inside a private network, such as on your local laptop. The ngrok agent is usually 25 | deployed inside a private network and is used to communicate with the ngrok cloud service. 26 | 27 | This is the ngrok agent in library form, suitable for integrating directly into Java applications. 28 | This allows you to quickly build ngrok into your application with no separate process to manage. 29 | 30 | # Installation 31 | 32 | ## Maven 33 | 34 | To use `ngrok-java`, you need to add the following to your `pom.xml`: 35 | 36 | ```xml 37 | 38 | 39 | 40 | 41 | 42 | com.ngrok 43 | ngrok-java 44 | ${ngrok.version} 45 | 46 | 47 | com.ngrok 48 | ngrok-java-native 49 | ${ngrok.version} 50 | 51 | ${os.detected.classifier} 52 | runtime 53 | 54 | 55 | 56 | 57 | 58 | 59 | kr.motd.maven 60 | os-maven-plugin 61 | 1.7.0 62 | 63 | 64 | 65 | 66 | ``` 67 | 68 | If you want to use [jetty](https://www.eclipse.org/jetty/) integration, also add: 69 | 70 | ```xml 71 | 72 | com.ngrok 73 | ngrok-jetty 74 | ${ngrok.version} 75 | 76 | ``` 77 | 78 | (Java 17+) If you wish to use ngrok listeners as a [server socket](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/net/ServerSocket.html), also add: 79 | 80 | ```xml 81 | 82 | com.ngrok 83 | ngrok-java-17 84 | ${ngrok.version} 85 | 86 | ``` 87 | 88 | For example of how to setup your project, check out [ngrok-java-demo](https://github.com/ngrok/ngrok-java-demo/blob/main/pom.xml) 89 | 90 | ## Gradle 91 | 92 | If you use gradle as a build system, you need to add to your build the following: 93 | 94 | ```kotlin 95 | plugins { 96 | id("com.google.osdetector").version("1.7.3") 97 | } 98 | 99 | var ngrokVersion = "0.4.1" 100 | 101 | dependencies { 102 | implementation("com.ngrok:ngrok-java:${ngrokVersion}") 103 | implementation("com.ngrok:ngrok-java-native:${ngrokVersion}:${osdetector.classifier}") 104 | } 105 | ``` 106 | 107 | # Documentation 108 | 109 | ## Quickstart 110 | 111 | ```java 112 | package com.ngrok.quickstart; 113 | 114 | import com.ngrok.Session; 115 | import com.ngrok.Http; 116 | 117 | import java.io.IOException; 118 | import java.nio.ByteBuffer; 119 | 120 | public class Quickstart { 121 | public static void main(String[] args) throws IOException { 122 | // Session.withAuthtokenFromEnv() will create a new session builder, pulling NGROK_AUTHTOKEN env variable. 123 | // You can get your authtoken by registering at https://dashboard.ngrok.com 124 | var sessionBuilder = Session.withAuthtokenFromEnv().metadata("my session"); 125 | // Session.Builder let you customize different aspects of the session, see docs for details. 126 | // After customizing the builder, you connect: 127 | try (var session = sessionBuilder.connect()) { 128 | // Creates and configures http listener that will be using oauth to secure it 129 | var listenerBuilder = session.httpEndpoint().metadata("my listener") 130 | .oauthOptions(new Http.OAuth("google")); 131 | // Now start listening with the above configuration 132 | try (var listener = listenerBuilder.listen()) { 133 | System.out.println("ngrok url: " + listener.getUrl()); 134 | var buf = ByteBuffer.allocateDirect(1024); 135 | 136 | while (true) { 137 | // Accept a new connection 138 | var conn = listener.accept(); 139 | 140 | // Read from the connection 141 | conn.read(buf); 142 | 143 | System.out.println(buf.asCharBuffer()); 144 | 145 | // Or write to it 146 | conn.write(buf); 147 | } 148 | } 149 | } 150 | } 151 | } 152 | ``` 153 | 154 | ## Setting Up Java Toolchain 155 | 156 | You may either: 157 | 158 | 1. Copy `./toolchains.xml` into `~/.m2/`, or 159 | 2. When running `mvn`, run as `mvn --global-toolchains ./toolchains.xml` 160 | 161 | ## Configuring Logging 162 | 163 | Log level is set from the your `slf4j` implementation's configuration. This level must be assigned before creating a session, as it is read on creation. 164 | 165 | As an example, to configure `slf4j-simple`, you can do: 166 | 167 | ```java 168 | System.setProperty("org.slf4j.simpleLogger.log.com.ngrok.Runtime", "debug"); 169 | ``` 170 | 171 | You can then log through the `Runtime` API: 172 | 173 | ```java 174 | Runtime.getLogger().log("info", "myClass", "Hello World"); 175 | ``` 176 | 177 | ## Connection Callbacks 178 | 179 | You may subscribe to session events from ngrok: 180 | 181 | ```java 182 | var builder = Session.newBuilder() 183 | .stopCallback(new Session.StopCallback() { 184 | @Override 185 | public void onStopCommand() { 186 | System.out.println("onStopCommand"); 187 | } 188 | }) 189 | .updateCallback(new Session.UpdateCallback() { 190 | @Override 191 | public void onUpdateCommand() { 192 | System.out.println("onUpdateCommand"); 193 | } 194 | }) 195 | .restartCallback(new Session.RestartCallback() { 196 | @Override 197 | public void onRestartCommand() { 198 | System.out.println("onRestartCommand"); 199 | } 200 | }); 201 | ``` 202 | 203 | These callbacks may be useful to your application in order to invoke custom logic in response to changes in your active session. 204 | 205 | # Platform Support 206 | 207 | JARs are provided on GitHub and Maven Central for the following platforms: 208 | 209 | | OS | i686 | x64 | aarch64 | armv7 | 210 | | ------- | ---- | --- | ------- | ----- | 211 | | Windows | ✓ | ✓ | | | 212 | | MacOS | | ✓ | ✓ | | 213 | | Linux | ✓ | ✓ | ✓ | | 214 | | Android | | | ✓ | ✓ | 215 | 216 | # Join the ngrok Community 217 | 218 | - Check out [our official docs](https://docs.ngrok.com) 219 | - Read about updates on [our blog](https://ngrok.com/blog) 220 | - Open an [issue](https://github.com/ngrok/ngrok-java/issues) or [pull request](https://github.com/ngrok/ngrok-java/pulls) 221 | - Join our [Slack community](https://ngrok.com/slack) 222 | - Follow us on [X / Twitter (@ngrokHQ)](https://twitter.com/ngrokhq) 223 | - Subscribe to our [Youtube channel (@ngrokHQ)](https://www.youtube.com/@ngrokhq) 224 | 225 | # License 226 | 227 | This project is licensed under either of 228 | 229 | - [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0) 230 | - [MIT license](http://opensource.org/licenses/MIT) 231 | 232 | at your option. 233 | 234 | # Contribution 235 | 236 | Unless you explicitly state otherwise, any contribution intentionally submitted 237 | for inclusion in ngrok-java by you, as defined in the Apache-2.0 license, shall be 238 | dual licensed as above, without any additional terms or conditions. 239 | -------------------------------------------------------------------------------- /assets/ngrok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ngrok/ngrok-java/33eaa835e9bf035c84ec52bf16cc0eab7572cd3a/assets/ngrok.png -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "fenix-flake": { 4 | "inputs": { 5 | "nixpkgs": [ 6 | "nixpkgs" 7 | ], 8 | "rust-analyzer-src": "rust-analyzer-src" 9 | }, 10 | "locked": { 11 | "lastModified": 1677565517, 12 | "narHash": "sha256-hcRc6u8DcbNniMFfEvDZUDvuJNG0DOmCRi0R1Gn0Sgo=", 13 | "owner": "nix-community", 14 | "repo": "fenix", 15 | "rev": "c0a51ae10a96bdfd097d64c3dd81ae04900d793d", 16 | "type": "github" 17 | }, 18 | "original": { 19 | "owner": "nix-community", 20 | "repo": "fenix", 21 | "type": "github" 22 | } 23 | }, 24 | "flake-utils": { 25 | "locked": { 26 | "lastModified": 1676283394, 27 | "narHash": "sha256-XX2f9c3iySLCw54rJ/CZs+ZK6IQy7GXNY4nSOyu2QG4=", 28 | "owner": "numtide", 29 | "repo": "flake-utils", 30 | "rev": "3db36a8b464d0c4532ba1c7dda728f4576d6d073", 31 | "type": "github" 32 | }, 33 | "original": { 34 | "owner": "numtide", 35 | "repo": "flake-utils", 36 | "type": "github" 37 | } 38 | }, 39 | "nixpkgs": { 40 | "locked": { 41 | "lastModified": 1677534593, 42 | "narHash": "sha256-PuZSAHeq4/9pP/uYH1FcagQ3nLm/DrDrvKi/xC9glvw=", 43 | "owner": "nixos", 44 | "repo": "nixpkgs", 45 | "rev": "3ad64d9e2d5bf80c877286102355b1625891ae9a", 46 | "type": "github" 47 | }, 48 | "original": { 49 | "owner": "nixos", 50 | "ref": "nixpkgs-unstable", 51 | "repo": "nixpkgs", 52 | "type": "github" 53 | } 54 | }, 55 | "root": { 56 | "inputs": { 57 | "fenix-flake": "fenix-flake", 58 | "flake-utils": "flake-utils", 59 | "nixpkgs": "nixpkgs" 60 | } 61 | }, 62 | "rust-analyzer-src": { 63 | "flake": false, 64 | "locked": { 65 | "lastModified": 1677510110, 66 | "narHash": "sha256-YgA4c+DsyZgproyDNEs0mrwccrcU0AU/y7fK+Rvkr3g=", 67 | "owner": "rust-lang", 68 | "repo": "rust-analyzer", 69 | "rev": "c867cbf9b6aea8d73c433ed85c6619e7714f3f7f", 70 | "type": "github" 71 | }, 72 | "original": { 73 | "owner": "rust-lang", 74 | "ref": "nightly", 75 | "repo": "rust-analyzer", 76 | "type": "github" 77 | } 78 | } 79 | }, 80 | "root": "root", 81 | "version": 7 82 | } 83 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "ngrok agent library in Java"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable"; 6 | 7 | # Note: fenix packages are cached via cachix: 8 | # cachix use nix-community 9 | fenix-flake = { 10 | url = "github:nix-community/fenix"; 11 | inputs.nixpkgs.follows = "nixpkgs"; 12 | }; 13 | 14 | flake-utils = { 15 | url = "github:numtide/flake-utils"; 16 | inputs.nixpkgs.follows = "nixpkgs"; 17 | }; 18 | }; 19 | 20 | outputs = { self, nixpkgs, fenix-flake, flake-utils }: 21 | flake-utils.lib.eachDefaultSystem (system: 22 | let 23 | pkgs = import nixpkgs { 24 | inherit system; 25 | overlays = [ 26 | fenix-flake.overlays.default 27 | ]; 28 | }; 29 | rust-toolchain = pkgs.fenix.complete.withComponents [ 30 | "cargo" 31 | "clippy" 32 | "rust-src" 33 | "rustc" 34 | "rustfmt" 35 | "rust-analyzer" 36 | ]; 37 | java-toolchain = with pkgs; [ 38 | openjdk17_headless 39 | openjdk11_headless 40 | maven 41 | ]; 42 | fix-n-fmt = pkgs.writeShellScriptBin "fix-n-fmt" '' 43 | set -euf -o pipefail 44 | ${rust-toolchain}/bin/cargo clippy --fix --allow-staged --allow-dirty --all-targets --all-features 45 | ${rust-toolchain}/bin/cargo fmt 46 | ''; 47 | pre-commit = pkgs.writeShellScript "pre-commit" '' 48 | cargo clippy --workspace --all-targets --all-features -- -D warnings 49 | result=$? 50 | 51 | if [[ ''${result} -ne 0 ]] ; then 52 | cat <<\EOF 53 | There are some linting issues, try `fix-n-fmt` to fix. 54 | EOF 55 | exit 1 56 | fi 57 | 58 | # Use a dedicated sub-target-dir for udeps. For some reason, it fights with clippy over the cache. 59 | CARGO_TARGET_DIR=$(git rev-parse --show-toplevel)/target/udeps cargo udeps --workspace --all-targets --all-features 60 | result=$? 61 | 62 | if [[ ''${result} -ne 0 ]] ; then 63 | cat <<\EOF 64 | There are some unused dependencies. 65 | EOF 66 | exit 1 67 | fi 68 | 69 | diff=$(cargo fmt -- --check) 70 | result=$? 71 | 72 | if [[ ''${result} -ne 0 ]] ; then 73 | cat <<\EOF 74 | There are some code style issues, run `fix-n-fmt` first. 75 | EOF 76 | exit 1 77 | fi 78 | 79 | exit 0 80 | ''; 81 | setup-hooks = pkgs.writeShellScriptBin "setup-hooks" '' 82 | repo_root=$(git rev-parse --git-dir) 83 | 84 | ${toString (map (h: '' 85 | ln -sf ${h} ''${repo_root}/hooks/${h.name} 86 | '') [ 87 | pre-commit 88 | ])} 89 | ''; 90 | # Make sure that cargo semver-checks uses the stable toolchain rather 91 | # than the nightly one that we normally develop with. 92 | semver-checks = with pkgs; symlinkJoin { 93 | name = "cargo-semver-checks"; 94 | paths = [ cargo-semver-checks ]; 95 | buildInputs = [ makeWrapper ]; 96 | postBuild = '' 97 | wrapProgram $out/bin/cargo-semver-checks \ 98 | --prefix PATH : ${rustc}/bin \ 99 | --prefix PATH : ${cargo}/bin 100 | ''; 101 | }; 102 | extract-version = with pkgs; writeShellScriptBin "extract-crate-version" '' 103 | ${cargo}/bin/cargo metadata --format-version 1 --no-deps | \ 104 | ${jq}/bin/jq -r ".packages[] | select(.name == \"$1\") | .version" 105 | ''; 106 | in 107 | { 108 | devShell = pkgs.mkShell { 109 | CHALK_OVERFLOW_DEPTH = 3000; 110 | CHALK_SOLVER_MAX_SIZE = 1500; 111 | OPENSSL_LIB_DIR = "${pkgs.openssl.out}/lib"; 112 | OPENSSL_INCLUDE_DIR = "${pkgs.openssl.dev}/include"; 113 | RUSTC_WRAPPER="${pkgs.sccache}/bin/sccache"; 114 | JAVA_11_HOME = "${pkgs.openjdk11_headless}"; 115 | JAVA_17_HOME = "${pkgs.openjdk17_headless}"; 116 | buildInputs = with pkgs; [ 117 | rust-toolchain 118 | java-toolchain 119 | fix-n-fmt 120 | setup-hooks 121 | cargo-udeps 122 | semver-checks 123 | extract-version 124 | ] ++ lib.optionals stdenv.isDarwin [ 125 | # nix darwin stdenv has broken libiconv: https://github.com/NixOS/nixpkgs/issues/158331 126 | libiconv 127 | pkgs.darwin.apple_sdk.frameworks.Security 128 | ]; 129 | }; 130 | }); 131 | } 132 | -------------------------------------------------------------------------------- /ngrok-java-17/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.ngrok 5 | ngrok-project 6 | 1.2.0-SNAPSHOT 7 | 8 | 9 | 4.0.0 10 | ngrok-java-17 11 | ngrok :: Java (17) 12 | jar 13 | 14 | 15 | 16 | 17 17 | 17 18 | UTF-8 19 | 20 | 21 | 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-toolchains-plugin 26 | 3.1.0 27 | 28 | 29 | 30 | toolchain 31 | 32 | 33 | 34 | 35 | 17 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | com.ngrok 48 | ngrok-java 49 | 1.2.0-SNAPSHOT 50 | 51 | 52 | junit 53 | junit 54 | ${junit.version} 55 | test 56 | 57 | 58 | -------------------------------------------------------------------------------- /ngrok-java-17/src/main/java/com/ngrok/net/ListenerServerSocket.java: -------------------------------------------------------------------------------- 1 | package com.ngrok.net; 2 | 3 | import com.ngrok.Listener; 4 | 5 | import java.io.IOException; 6 | import java.net.ServerSocket; 7 | import java.net.Socket; 8 | import java.net.SocketException; 9 | 10 | /** 11 | * A server socket for accepting connections from a {@link Listener}. 12 | */ 13 | public class ListenerServerSocket extends ServerSocket { 14 | /** 15 | * Creates a new server socket for the given listener. 16 | * 17 | * @param listener the listener to accept connections for 18 | * @throws IOException if an I/O error occurs 19 | */ 20 | public ListenerServerSocket(Listener listener) throws IOException { 21 | super(new ListenerSocketImpl(listener)); 22 | } 23 | 24 | /** 25 | * Accepts a connection to the server socket. 26 | * 27 | * @return A {@link Socket} for the accepted connection 28 | * @throws IOException if an I/O error occurs 29 | * @throws SocketException if the socket is closed 30 | */ 31 | @Override 32 | public Socket accept() throws IOException { 33 | if (isClosed()) 34 | throw new SocketException("Socket is closed"); 35 | var s = new ConnectionSocket(); 36 | implAccept(s); 37 | return s; 38 | } 39 | } -------------------------------------------------------------------------------- /ngrok-java-17/src/main/java/com/ngrok/net/ListenerSocketImpl.java: -------------------------------------------------------------------------------- 1 | package com.ngrok.net; 2 | 3 | import com.ngrok.Listener; 4 | 5 | import java.io.IOException; 6 | import java.net.SocketImpl; 7 | 8 | /** 9 | * An implementation of the {@link AbstractSocketImpl} interface for 10 | * accepting connections on a {@link Listener} 11 | */ 12 | public class ListenerSocketImpl extends AbstractSocketImpl { 13 | private final Listener listener; 14 | 15 | /** 16 | * Creates a new listener socket implementation for the given listener. 17 | * 18 | * @param listener the listener 19 | */ 20 | public ListenerSocketImpl(Listener listener) { 21 | this.listener = listener; 22 | } 23 | 24 | /** 25 | * Accepts a listener connection to the socket. 26 | * 27 | * @param s the socket to accept the connection on 28 | * @throws IOException if an I/O error occurs 29 | */ 30 | @Override 31 | protected void accept(SocketImpl s) throws IOException { 32 | var csi = (ConnectionSocketImpl) s; 33 | csi.setConnection(listener.accept()); 34 | } 35 | } -------------------------------------------------------------------------------- /ngrok-java-native/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ngrok-java" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [build-dependencies] 9 | jaffi = "0.2.0" 10 | 11 | [dependencies] 12 | jaffi_support = "0.2.0" 13 | once_cell = "1.17.1" 14 | futures = "0.3.25" 15 | bytes = "1.4.0" 16 | ngrok = "0.14.0-pre.14" 17 | tokio = { version = "1.26.0", features = ["full"] } 18 | async-trait = "0.1.59" 19 | tracing = "0.1.37" 20 | tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } 21 | url = "2.4.1" 22 | 23 | [lib] 24 | crate_type = ["cdylib"] 25 | 26 | [patch.crates-io] 27 | jaffi_support = { git = 'https://github.com/ngrok-oss/jaffi.git', branch = 'josh/lower-jni-version' } 28 | jaffi = { git = 'https://github.com/ngrok-oss/jaffi.git', branch = 'josh/lower-jni-version' } 29 | -------------------------------------------------------------------------------- /ngrok-java-native/build.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | borrow::Cow, 3 | error::Error, 4 | io::Write, 5 | path::{Path, PathBuf}, 6 | process::Command, 7 | }; 8 | 9 | use jaffi::Jaffi; 10 | 11 | fn main() -> Result<(), Box> { 12 | let class_path = vec![ 13 | Cow::from(PathBuf::from("../ngrok-java/target/classes")), 14 | Cow::from(PathBuf::from("target/classes")), 15 | ]; 16 | let classes = vec![ 17 | Cow::from("com.ngrok.Runtime"), 18 | Cow::from("com.ngrok.NativeSession"), 19 | Cow::from("com.ngrok.NativeTcpListener"), 20 | Cow::from("com.ngrok.NativeTcpForwarder"), 21 | Cow::from("com.ngrok.NativeTlsListener"), 22 | Cow::from("com.ngrok.NativeTlsForwarder"), 23 | Cow::from("com.ngrok.NativeHttpListener"), 24 | Cow::from("com.ngrok.NativeHttpForwarder"), 25 | Cow::from("com.ngrok.NativeEdgeListener"), 26 | Cow::from("com.ngrok.NativeEdgeForwarder"), 27 | Cow::from("com.ngrok.NativeEdgeConnection"), 28 | Cow::from("com.ngrok.NativeEndpointConnection"), 29 | ]; 30 | let classes_to_wrap = vec![ 31 | Cow::from("com.ngrok.Runtime$Logger"), 32 | Cow::from("com.ngrok.Session"), 33 | Cow::from("com.ngrok.Session$Builder"), 34 | Cow::from("com.ngrok.Session$ClientInfo"), 35 | Cow::from("com.ngrok.Session$CommandHandler"), 36 | Cow::from("com.ngrok.Session$HeartbeatHandler"), 37 | Cow::from("com.ngrok.MetadataBuilder"), 38 | Cow::from("com.ngrok.EdgeBuilder"), 39 | Cow::from("com.ngrok.EndpointBuilder"), 40 | Cow::from("com.ngrok.HttpBuilder"), 41 | Cow::from("com.ngrok.Http$Header"), 42 | Cow::from("com.ngrok.Http$BasicAuth"), 43 | Cow::from("com.ngrok.Http$OAuth"), 44 | Cow::from("com.ngrok.Http$OIDC"), 45 | Cow::from("com.ngrok.Http$WebhookVerification"), 46 | Cow::from("com.ngrok.TcpBuilder"), 47 | Cow::from("com.ngrok.TlsBuilder"), 48 | Cow::from("com.ngrok.AbstractEdge"), 49 | Cow::from("com.ngrok.AbstractEndpoint"), 50 | Cow::from("com.ngrok.NgrokException"), 51 | ]; 52 | let output_dir = PathBuf::from(std::env::var("OUT_DIR").expect("OUT_DIR not set")); 53 | 54 | let jaffi = Jaffi::builder() 55 | .classpath(class_path) 56 | .classes_to_wrap(classes_to_wrap) 57 | .native_classes(classes) 58 | .output_dir(&output_dir) 59 | .build(); 60 | 61 | jaffi.generate()?; 62 | 63 | let output_file = Cow::from(Path::new("generated_jaffi.rs")); 64 | let jaffi_file = output_dir.join(output_file); 65 | 66 | let mut cmd = Command::new("rustfmt"); 67 | cmd.arg("--emit").arg("files").arg(jaffi_file); 68 | 69 | eprintln!("cargo fmt: {cmd:?}"); 70 | let output = cmd.output(); 71 | 72 | match output { 73 | Ok(output) => { 74 | std::io::stderr().write_all(&output.stdout)?; 75 | std::io::stderr().write_all(&output.stderr)?; 76 | } 77 | Err(e) => { 78 | eprintln!("cargo fmt failed to execute: {e}"); 79 | } 80 | } 81 | 82 | Ok(()) 83 | } 84 | -------------------------------------------------------------------------------- /ngrok-java-native/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.ngrok 5 | ngrok-project 6 | 1.2.0-SNAPSHOT 7 | 8 | 9 | 4.0.0 10 | ngrok-java-native 11 | ngrok :: Java Native 12 | jar 13 | 14 | 15 | 16 | com.ngrok 17 | ngrok-java 18 | 1.2.0-SNAPSHOT 19 | 20 | 21 | org.slf4j 22 | slf4j-api 23 | ${slf4j.version} 24 | 25 | 26 | org.slf4j 27 | slf4j-simple 28 | ${slf4j.version} 29 | test 30 | 31 | 32 | junit 33 | junit 34 | ${junit.version} 35 | test 36 | 37 | 38 | 39 | 40 | 11 41 | 11 42 | UTF-8 43 | 44 | 45 | ${os.detected.classifier} 46 | 47 | 48 | 49 | 50 | 51 | kr.motd.maven 52 | os-maven-plugin 53 | 1.7.0 54 | 55 | 56 | 57 | 58 | src/main/resources 59 | true 60 | 61 | 62 | 63 | 64 | 65 | 66 | development 67 | 68 | true 69 | 70 | 71 | 72 | 73 | org.codehaus.mojo 74 | exec-maven-plugin 75 | 3.1.0 76 | 77 | 78 | build-rust 79 | process-classes 80 | 81 | exec 82 | 83 | 84 | cargo 85 | 86 | build 87 | 88 | 89 | 90 | 91 | 92 | 93 | org.apache.maven.plugins 94 | maven-toolchains-plugin 95 | 3.1.0 96 | 97 | 98 | 99 | toolchain 100 | 101 | 102 | 103 | 104 | 11 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | org.apache.maven.plugins 113 | maven-resources-plugin 114 | 3.0.2 115 | 116 | 117 | copy-rust 118 | process-classes 119 | 120 | copy-resources 121 | 122 | 123 | ${project.basedir}/target/classes/ 124 | 125 | 126 | ${project.basedir}/target/debug/ 127 | 128 | *.dylib 129 | *.dll 130 | *.so 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | maven-jar-plugin 140 | 141 | ${os.detected.classifier} 142 | 143 | 144 | 145 | 146 | 147 | 148 | ci-native 149 | 150 | 151 | 152 | org.codehaus.mojo 153 | exec-maven-plugin 154 | 3.1.0 155 | 156 | 157 | build-rust 158 | process-classes 159 | 160 | exec 161 | 162 | 163 | cargo 164 | 165 | build 166 | --release 167 | --target 168 | ${ngrok.native.target} 169 | 170 | 171 | 172 | 173 | 174 | 175 | org.apache.maven.plugins 176 | maven-resources-plugin 177 | 3.0.2 178 | 179 | 180 | copy-rust 181 | process-classes 182 | 183 | copy-resources 184 | 185 | 186 | ${project.basedir}/target/classes/ 187 | 188 | 189 | ${project.basedir}/target/${ngrok.native.target}/release/ 190 | 191 | *.dylib 192 | *.dll 193 | *.so 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | maven-jar-plugin 203 | 204 | ${ngrok.native.classifier} 205 | 206 | 207 | 208 | maven-javadoc-plugin 209 | 210 | ${ngrok.native.classifier}-javadoc 211 | 212 | 213 | 214 | maven-source-plugin 215 | 216 | ${ngrok.native.classifier}-sources 217 | 218 | 219 | 220 | 221 | 222 | 223 | ci-distro 224 | 225 | 226 | 227 | maven-surefire-plugin 228 | 229 | true 230 | 231 | 232 | 233 | org.codehaus.mojo 234 | build-helper-maven-plugin 235 | 3.3.0 236 | 237 | 238 | attach-artifacts 239 | package 240 | 241 | attach-artifact 242 | 243 | 244 | 245 | 246 | target/ngrok-java-native-${project.version}-linux-x86_32.jar 247 | jar 248 | linux-x86_32 249 | 250 | 251 | target/ngrok-java-native-${project.version}-linux-x86_64.jar 252 | jar 253 | linux-x86_64 254 | 255 | 256 | target/ngrok-java-native-${project.version}-linux-aarch_64.jar 257 | jar 258 | linux-aarch_64 259 | 260 | 261 | target/ngrok-java-native-${project.version}-linux-android-armv7.jar 262 | jar 263 | linux-android-armv7 264 | 265 | 266 | target/ngrok-java-native-${project.version}-linux-android-aarch_64.jar 267 | jar 268 | linux-android-aarch_64 269 | 270 | 271 | target/ngrok-java-native-${project.version}-windows-x86_32.jar 272 | jar 273 | windows-x86_32 274 | 275 | 276 | target/ngrok-java-native-${project.version}-windows-x86_64.jar 277 | jar 278 | windows-x86_64 279 | 280 | 281 | target/ngrok-java-native-${project.version}-osx-x86_64.jar 282 | jar 283 | osx-x86_64 284 | 285 | 286 | target/ngrok-java-native-${project.version}-osx-aarch_64.jar 287 | jar 288 | osx-aarch_64 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/AbstractEdge.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.util.Map; 4 | 5 | public class AbstractEdge { 6 | private final String id; 7 | private final String metadata; 8 | private final String forwardsTo; 9 | private final Map labels; 10 | 11 | public AbstractEdge(String id, String metadata, String forwardsTo, Map labels) { 12 | this.id = id; 13 | this.metadata = metadata; 14 | this.forwardsTo = forwardsTo; 15 | this.labels = labels; 16 | } 17 | 18 | public String getId() { 19 | return id; 20 | } 21 | 22 | public String getMetadata() { 23 | return metadata; 24 | } 25 | 26 | public String getForwardsTo() { 27 | return forwardsTo; 28 | } 29 | 30 | public Map getLabels() { 31 | return labels; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/AbstractEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | public abstract class AbstractEndpoint { 4 | private final String id; 5 | private final String metadata; 6 | private final String forwardsTo; 7 | private final String proto; 8 | private final String url; 9 | 10 | public AbstractEndpoint(String id, String metadata, String forwardsTo, String proto, String url) { 11 | this.id = id; 12 | this.metadata = metadata; 13 | this.forwardsTo = forwardsTo; 14 | this.proto = proto; 15 | this.url = url; 16 | } 17 | 18 | public String getId() { 19 | return id; 20 | } 21 | 22 | public String getMetadata() { 23 | return metadata; 24 | } 25 | 26 | public String getForwardsTo() { 27 | return forwardsTo; 28 | } 29 | 30 | public String getProto() { 31 | return proto; 32 | } 33 | 34 | public String getUrl() { 35 | return url; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/NativeEdgeConnection.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.nio.ByteBuffer; 5 | 6 | /** 7 | * An implementation of {@link Connection.Edge} that delegates implementation to a native library. 8 | */ 9 | public class NativeEdgeConnection implements Connection.Edge { 10 | private long native_address; 11 | 12 | private final String remoteAddr; 13 | private final String edgeType; 14 | private final boolean passthroughTls; 15 | 16 | public NativeEdgeConnection(String remoteAddr, String edgeType, boolean passthroughTls) { 17 | this.remoteAddr = remoteAddr; 18 | this.edgeType = edgeType; 19 | this.passthroughTls = passthroughTls; 20 | } 21 | 22 | @Override 23 | public String getRemoteAddr() { 24 | return remoteAddr; 25 | } 26 | 27 | @Override 28 | public String getEdgeType() { 29 | return edgeType; 30 | } 31 | 32 | @Override 33 | public boolean isPassthroughTls() { 34 | return passthroughTls; 35 | } 36 | 37 | @Override 38 | public int read(ByteBuffer dst) throws IOException { 39 | var sz = readNative(dst); 40 | dst.position(0); 41 | dst.limit(sz); 42 | return sz; 43 | } 44 | 45 | private native int readNative(ByteBuffer dst) throws IOException; 46 | 47 | @Override 48 | public int write(ByteBuffer src) throws IOException { 49 | return writeNative(src, src.limit()); 50 | } 51 | 52 | private native int writeNative(ByteBuffer src, int limit) throws IOException; 53 | 54 | @Override 55 | public native void close() throws IOException; 56 | } 57 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/NativeEdgeForwarder.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | 6 | /** 7 | * An implementation of {@link Forwarder.Edge} that delegates implementation to a native library. 8 | */ 9 | public class NativeEdgeForwarder extends AbstractEdge implements Forwarder.Edge { 10 | private long native_address; 11 | 12 | public NativeEdgeForwarder(String id, String metadata, String forwardsTo, Map labels) { 13 | super(id, metadata, forwardsTo, labels); 14 | } 15 | 16 | @Override 17 | public native void join() throws IOException; 18 | 19 | @Override 20 | public native void close() throws IOException; 21 | } 22 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/NativeEdgeListener.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.util.Map; 5 | 6 | /** 7 | * An implementation of {@link Listener.Edge} that delegates implementation to a native library. 8 | */ 9 | public class NativeEdgeListener extends AbstractEdge implements Listener.Edge { 10 | private long native_address; 11 | 12 | public NativeEdgeListener(String id, String metadata, String forwardsTo, Map labels) { 13 | super(id, metadata, forwardsTo, labels); 14 | } 15 | 16 | @Override 17 | public native NativeEdgeConnection accept() throws IOException; 18 | 19 | @Override 20 | public native void close() throws IOException; 21 | } 22 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/NativeEndpointConnection.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.nio.ByteBuffer; 5 | 6 | /** 7 | * An implementation of {@link Connection.Endpoint} that delegates implementation to a native library. 8 | */ 9 | public class NativeEndpointConnection implements Connection.Endpoint { 10 | private long native_address; 11 | 12 | private final String proto; 13 | 14 | private final String remoteAddr; 15 | 16 | public NativeEndpointConnection(String remoteAddr, String proto) { 17 | this.proto = proto; 18 | this.remoteAddr = remoteAddr; 19 | } 20 | 21 | public String getProto() { 22 | return proto; 23 | } 24 | 25 | @Override 26 | public String getRemoteAddr() { 27 | return remoteAddr; 28 | } 29 | 30 | @Override 31 | public int read(ByteBuffer dst) throws IOException { 32 | var sz = readNative(dst); 33 | dst.position(0); 34 | dst.limit(sz); 35 | return sz; 36 | } 37 | 38 | private native int readNative(ByteBuffer dst) throws IOException; 39 | 40 | @Override 41 | public int write(ByteBuffer src) throws IOException { 42 | return writeNative(src, src.limit()); 43 | } 44 | 45 | private native int writeNative(ByteBuffer src, int limit) throws IOException; 46 | 47 | public native void close() throws IOException; 48 | } 49 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/NativeHttpForwarder.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * An implementation of {@link Forwarder.Endpoint} that delegates implementation to a native library. 7 | */ 8 | public class NativeHttpForwarder extends AbstractEndpoint implements Forwarder.Endpoint { 9 | private long native_address; 10 | 11 | public NativeHttpForwarder(String id, String metadata, String forwardsTo, String proto, String url) { 12 | super(id, metadata, forwardsTo, proto, url); 13 | } 14 | 15 | @Override 16 | public native void join() throws IOException; 17 | 18 | @Override 19 | public native void close() throws IOException; 20 | } 21 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/NativeHttpListener.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * An implementation of {@link Listener.Endpoint} that delegates implementation to a native library. 7 | */ 8 | public class NativeHttpListener extends AbstractEndpoint implements Listener.Endpoint { 9 | private long native_address; 10 | 11 | public NativeHttpListener(String id, String metadata, String forwardsTo, String proto, String url) { 12 | super(id, metadata, forwardsTo, proto, url); 13 | } 14 | 15 | @Override 16 | public native NativeEndpointConnection accept() throws IOException; 17 | 18 | @Override 19 | public native void close() throws IOException; 20 | } 21 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/NativeSession.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | import java.util.Properties; 6 | 7 | /** 8 | * An implementation of {@link Session} that delegates implementation to a native library. 9 | */ 10 | public class NativeSession implements Session { 11 | private static String version = "0.0.0-UNKNOWN"; 12 | 13 | static { 14 | try { 15 | Runtime.load(); 16 | Runtime.init(Runtime.getLogger()); 17 | } catch (Throwable th) { 18 | // TODO better error handling here? 19 | th.printStackTrace(); 20 | } 21 | 22 | try { 23 | Properties props = new Properties(); 24 | props.load(NativeSession.class.getResourceAsStream("/native.properties")); 25 | version = props.getProperty("agent.version", "0.0.0-SNAPSHOT"); 26 | } catch (Throwable th) { 27 | // TODO better error handling here? 28 | th.printStackTrace(); 29 | } 30 | } 31 | 32 | private long native_address; 33 | 34 | private final String id; 35 | private final String metadata; 36 | 37 | public NativeSession(String id, String metadata) { 38 | this.id = id; 39 | this.metadata = metadata; 40 | } 41 | 42 | public static NativeSession connect(Session.Builder builder) throws IOException { 43 | var jver = System.getProperty("java.version"); 44 | builder.getClientInfos().add(0, new ClientInfo("ngrok-java", version, jver)); 45 | return connectNative(builder); 46 | } 47 | 48 | private static native NativeSession connectNative(Session.Builder builder) throws IOException; 49 | 50 | @Override 51 | public String getId() { 52 | return id; 53 | } 54 | 55 | @Override 56 | public String getMetadata() { 57 | return metadata; 58 | } 59 | 60 | @Override 61 | public native NativeTcpListener listenTcp(TcpBuilder builder) throws IOException; 62 | 63 | @Override 64 | public native NativeTcpForwarder forwardTcp(TcpBuilder builder, URL url) throws IOException; 65 | 66 | @Override 67 | public native NativeTlsListener listenTls(TlsBuilder builder) throws IOException; 68 | 69 | @Override 70 | public native NativeTlsForwarder forwardTls(TlsBuilder builder, URL url) throws IOException; 71 | 72 | @Override 73 | public native NativeHttpListener listenHttp(HttpBuilder builder) throws IOException; 74 | 75 | @Override 76 | public native NativeHttpForwarder forwardHttp(HttpBuilder builder, URL url) throws IOException; 77 | 78 | @Override 79 | public native NativeEdgeListener listenEdge(EdgeBuilder builder) throws IOException; 80 | 81 | @Override 82 | public native NativeEdgeForwarder forwardEdge(EdgeBuilder builder, URL url) throws IOException; 83 | 84 | @Override 85 | public native void closeListener(String id) throws IOException; 86 | 87 | @Override 88 | public native void closeForwarder(String id) throws IOException; 89 | 90 | @Override 91 | public native void close() throws IOException; 92 | } -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/NativeTcpForwarder.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * An implementation of {@link Forwarder.Endpoint} that delegates implementation to a native library. 7 | */ 8 | public class NativeTcpForwarder extends AbstractEndpoint implements Forwarder.Endpoint { 9 | private long native_address; 10 | 11 | public NativeTcpForwarder(String id, String metadata, String forwardsTo, String proto, String url) { 12 | super(id, metadata, forwardsTo, proto, url); 13 | } 14 | 15 | @Override 16 | public native void join() throws IOException; 17 | 18 | @Override 19 | public native void close() throws IOException; 20 | } 21 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/NativeTcpListener.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * An implementation of {@link Listener.Endpoint} that delegates implementation to a native library. 7 | */ 8 | public class NativeTcpListener extends AbstractEndpoint implements Listener.Endpoint { 9 | private long native_address; 10 | 11 | public NativeTcpListener(String id, String metadata, String forwardsTo, String proto, String url) { 12 | super(id, metadata, forwardsTo, proto, url); 13 | } 14 | 15 | @Override 16 | public native NativeEndpointConnection accept() throws IOException; 17 | 18 | @Override 19 | public native void close() throws IOException; 20 | } 21 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/NativeTlsForwarder.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * An implementation of {@link Forwarder.Endpoint} that delegates implementation to a native library. 7 | */ 8 | public class NativeTlsForwarder extends AbstractEndpoint implements Forwarder.Endpoint { 9 | private long native_address; 10 | 11 | public NativeTlsForwarder(String id, String metadata, String forwardsTo, String proto, String url) { 12 | super(id, metadata, forwardsTo, proto, url); 13 | } 14 | 15 | @Override 16 | public native void join() throws IOException; 17 | 18 | @Override 19 | public native void close() throws IOException; 20 | } 21 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/NativeTlsListener.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * An implementation of {@link Listener.Endpoint} that delegates implementation to a native library. 7 | */ 8 | public class NativeTlsListener extends AbstractEndpoint implements Listener.Endpoint { 9 | private long native_address; 10 | 11 | public NativeTlsListener(String id, String metadata, String forwardsTo, String proto, String url) { 12 | super(id, metadata, forwardsTo, proto, url); 13 | } 14 | 15 | @Override 16 | public native NativeEndpointConnection accept() throws IOException; 17 | 18 | @Override 19 | public native void close() throws IOException; 20 | } 21 | -------------------------------------------------------------------------------- /ngrok-java-native/src/main/java/com/ngrok/Runtime.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import org.slf4j.LoggerFactory; 4 | import org.slf4j.event.Level; 5 | 6 | import java.io.File; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.nio.file.*; 10 | import java.util.Locale; 11 | 12 | /** 13 | * A class representing the runtime environment for the ngrok Java client. 14 | */ 15 | class Runtime { 16 | private static final Logger LOGGER = new Logger(); 17 | 18 | /** 19 | * Returns the {@link Runtime.Logger} used by the Runtime class. 20 | * 21 | * @return the singleton logger 22 | */ 23 | public static Logger getLogger() { 24 | return LOGGER; 25 | } 26 | 27 | /** 28 | * Returns the name of the native library to load based on the current operating 29 | * system. 30 | * 31 | * @return the name of the native library to load 32 | * @throws RuntimeException if the operating system is unknown 33 | */ 34 | private static String getLibname() { 35 | // TODO better logic here/use lib 36 | var osname = System.getProperty("os.name").toLowerCase(Locale.ENGLISH); 37 | if (osname.contains("nix") || osname.contains("nux")) { 38 | return "libngrok_java.so"; 39 | } else if (osname.contains("win")) { 40 | return "ngrok_java.dll"; 41 | } else if (osname.contains("mac")) { 42 | return "libngrok_java.dylib"; 43 | } 44 | throw new RuntimeException("unknown OS: " + osname); 45 | } 46 | 47 | /** 48 | * Loads the native library for the ngrok Java client. 49 | * 50 | * @throws RuntimeException if an I/O error occurs or the native library fails 51 | * to load 52 | */ 53 | public static void load() { 54 | String filename = getLibname(); 55 | String tempDir = System.getProperty("java.io.tmpdir"); 56 | File temporaryDir = new File(tempDir, "libngrok_" + System.nanoTime()); 57 | temporaryDir.mkdir(); 58 | temporaryDir.deleteOnExit(); 59 | 60 | File temp = new File(temporaryDir, filename); 61 | try (InputStream is = Runtime.class.getResourceAsStream("/" + filename)) { 62 | Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING); 63 | } catch (IOException | NullPointerException e) { 64 | temp.delete(); 65 | throw new RuntimeException(e); 66 | } 67 | 68 | try { 69 | System.load(temp.getAbsolutePath()); 70 | } finally { 71 | if (isPosixCompliant()) { 72 | // Assume POSIX compliant file system, can be deleted after loading 73 | temp.delete(); 74 | } else { 75 | // Assume non-POSIX, and don't delete until last file descriptor closed 76 | temp.deleteOnExit(); 77 | } 78 | } 79 | } 80 | 81 | /** 82 | * Returns whether the current file system is POSIX compliant. 83 | * 84 | * @return true if the current file system is POSIX compliant, false otherwise 85 | */ 86 | private static boolean isPosixCompliant() { 87 | try { 88 | return FileSystems.getDefault() 89 | .supportedFileAttributeViews() 90 | .contains("posix"); 91 | } catch (FileSystemNotFoundException 92 | | ProviderNotFoundException 93 | | SecurityException e) { 94 | return false; 95 | } 96 | } 97 | 98 | /** 99 | * Initializes the logger for the native library. 100 | * 101 | * @param logger the logger to be initialized for the native library 102 | */ 103 | static native void init(Logger logger); 104 | 105 | /** 106 | * A class representing a logger for the runtime environment. 107 | */ 108 | /** 109 | * A static class representing a logger for the Runtime class. 110 | */ 111 | static class Logger { 112 | private static final org.slf4j.Logger logger = LoggerFactory.getLogger(Runtime.class); 113 | private static final String format = "[{}] {}"; 114 | 115 | private Logger() {} 116 | 117 | /** 118 | * Returns the logging level of the logger. 119 | * 120 | * @return the logging level of the logger 121 | */ 122 | public String getLevel() { 123 | Level logLevel = Level.INFO; 124 | 125 | Level[] levels = new Level[] { Level.ERROR, Level.WARN, Level.INFO, Level.DEBUG, Level.TRACE }; 126 | for (Level level : levels) { 127 | if (logger.isEnabledForLevel(level)) { 128 | logLevel = level; 129 | } 130 | } 131 | 132 | return logLevel.toString(); 133 | } 134 | 135 | /** 136 | * Logs a message with the specified level and target. 137 | * 138 | * @param level the level of the message 139 | * @param target the target of the message 140 | * @param message the message to log 141 | */ 142 | public void log(String level, String target, String message) { 143 | Level lvl = Level.valueOf(level.toUpperCase()); 144 | logger.atLevel(lvl).log(format, target, message); 145 | } 146 | } 147 | } -------------------------------------------------------------------------------- /ngrok-java-native/src/main/resources/native.properties: -------------------------------------------------------------------------------- 1 | agent.version=${project.version} 2 | agent.classifier=${ngrok.native.classifier} -------------------------------------------------------------------------------- /ngrok-java-native/src/test/java/com/ngrok/DataTest.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import org.junit.Before; 4 | import org.junit.Test; 5 | 6 | import java.nio.ByteBuffer; 7 | 8 | import static org.junit.Assert.*; 9 | 10 | public class DataTest { 11 | 12 | @Before 13 | public void setup() { 14 | System.setProperty("org.slf4j.simpleLogger.log.com.ngrok.Runtime", "trace"); 15 | } 16 | 17 | @Test 18 | public void testSessionClose() throws Exception { 19 | try (var session = Session.withAuthtokenFromEnv().metadata("java-session").connect()) { 20 | assertEquals("java-session", session.getMetadata()); 21 | } 22 | } 23 | 24 | @Test 25 | public void testTunnelClose() throws Exception { 26 | try (var session = Session.withAuthtokenFromEnv().connect(); 27 | var listener = session.httpEndpoint().metadata("java-endpoint").listen()) { 28 | assertEquals("java-endpoint", listener.getMetadata()); 29 | Runtime.getLogger().log("info", "session", listener.getUrl()); 30 | } 31 | } 32 | 33 | // @Test 34 | public void testPingPong() throws Exception { 35 | var session = Session.withAuthtokenFromEnv().connect(); 36 | assertNotNull(session); 37 | 38 | var listener = session.tcpEndpoint().listen(); 39 | assertNotNull(listener); 40 | 41 | var conn = listener.accept(); 42 | 43 | var buf = ByteBuffer.allocateDirect(10); 44 | conn.read(buf); 45 | 46 | System.out.println(buf.asCharBuffer()); 47 | conn.write(buf); 48 | 49 | assertTrue(true); 50 | } 51 | 52 | @Test 53 | public void testPolicy() throws Exception { 54 | final ClassLoader classLoader = getClass().getClassLoader(); 55 | final String policy = new String(classLoader.getResourceAsStream("policy.json").readAllBytes()); 56 | 57 | try (var session = Session.withAuthtokenFromEnv().connect(); 58 | var listener = session.httpEndpoint().metadata("java-endpoint").policy(policy).listen()) { 59 | assertEquals("java-endpoint", listener.getMetadata()); 60 | Runtime.getLogger().log("info", "session", listener.getUrl()); 61 | } 62 | } 63 | 64 | @Test 65 | public void testTrafficPolicy() throws Exception { 66 | final ClassLoader classLoader = getClass().getClassLoader(); 67 | final String trafficPolicy = new String(classLoader.getResourceAsStream("policy.json").readAllBytes()); 68 | 69 | try (var session = Session.withAuthtokenFromEnv().connect(); 70 | var listener = session.httpEndpoint().metadata("java-endpoint").trafficPolicy(trafficPolicy).listen()) { 71 | assertEquals("java-endpoint", listener.getMetadata()); 72 | Runtime.getLogger().log("info", "session", listener.getUrl()); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /ngrok-java-native/src/test/java/com/ngrok/ForwardTest.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import org.junit.Test; 4 | 5 | import java.net.URL; 6 | 7 | import static org.junit.Assert.assertNotNull; 8 | import static org.junit.Assert.assertTrue; 9 | 10 | public class ForwardTest { 11 | // @Test 12 | public void testForward() throws Exception { 13 | var session = Session.withAuthtokenFromEnv().connect(); 14 | assertNotNull(session); 15 | 16 | var listener = session.httpEndpoint().domain("ngrok-java-test.ngrok.io") 17 | .forward(new URL("http://127.0.0.1:8000")); 18 | assertNotNull(listener); 19 | 20 | Thread.sleep(10000); 21 | session.closeListener(listener.getId()); 22 | 23 | assertTrue(true); 24 | } 25 | } -------------------------------------------------------------------------------- /ngrok-java-native/src/test/resources/policy.json: -------------------------------------------------------------------------------- 1 | { 2 | "inbound": [ 3 | { 4 | "name": "test_in", 5 | "expressions": ["req.Method == 'PUT'"], 6 | "actions": [ 7 | { 8 | "type": "deny" 9 | } 10 | ] 11 | } 12 | ], 13 | "outbound": [ 14 | { 15 | "name": "test_out", 16 | "expressions": ["res.StatusCode == '200'"], 17 | "actions": [ 18 | { 19 | "type": "custom-response", 20 | "config": { 21 | "status_code": 201 22 | } 23 | } 24 | ] 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /ngrok-java/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.ngrok 5 | ngrok-project 6 | 1.2.0-SNAPSHOT 7 | 8 | 9 | 4.0.0 10 | ngrok-java 11 | ngrok :: Java 12 | jar 13 | 14 | 15 | 16 | 11 17 | 11 18 | UTF-8 19 | 20 | 21 | 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-toolchains-plugin 26 | 3.1.0 27 | 28 | 29 | 30 | toolchain 31 | 32 | 33 | 34 | 35 | 11 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | junit 48 | junit 49 | ${junit.version} 50 | test 51 | 52 | 53 | -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/Connection.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.nio.ByteBuffer; 6 | 7 | /** 8 | * Represents a connection established over a listener. 9 | */ 10 | public interface Connection extends AutoCloseable { 11 | /** 12 | * Returns the remote address that established this connection. 13 | * 14 | * @return an internet address, in IP:port form 15 | */ 16 | String getRemoteAddr(); 17 | 18 | /** 19 | * Creates an {@link InetSocketAddress} for this connection's remote address. 20 | * 21 | * @return {@link InetSocketAddress} representing the internet address 22 | */ 23 | default InetSocketAddress inetAddress() { 24 | var addr = getRemoteAddr(); 25 | var lastColumn = addr.lastIndexOf(":"); 26 | var host = addr.substring(0, lastColumn); 27 | var port = addr.substring(lastColumn + 1); 28 | return new InetSocketAddress(host, Integer.parseInt(port)); 29 | } 30 | 31 | /** 32 | * Reads the next available bytes from this connection, up to the buffer capacity. 33 | * 34 | * @param dst the buffer to read bytes into 35 | * @return the number of bytes read, or -1 if the end of the stream has been reached 36 | * @throws IOException if an I/O error occurs 37 | */ 38 | int read(ByteBuffer dst) throws IOException; 39 | 40 | /** 41 | * Writes a sequence of bytes to this connection from the given buffer. 42 | * 43 | * @param src the buffer containing bytes to write 44 | * @return the number of bytes written 45 | * @throws IOException if an I/O error occurs 46 | */ 47 | int write(ByteBuffer src) throws IOException; 48 | 49 | /** 50 | * Closes this connection and releases any system resources associated with it. 51 | * 52 | * @throws IOException if an I/O error occurs 53 | */ 54 | void close() throws IOException; 55 | 56 | /** 57 | * Represents a connection establish over an endpoint listener. 58 | */ 59 | interface Endpoint extends Connection { 60 | /** 61 | * Returns the protocol of this connection. 62 | * 63 | * @return the protocol, for example {@code http} or {@code tcp} 64 | */ 65 | String getProto(); 66 | } 67 | 68 | /** 69 | * Represents a connection established over an edge listener 70 | */ 71 | interface Edge extends Connection { 72 | /** 73 | * Returns the edge type for this connection. 74 | * 75 | * @return the edge type, for example {@code https} or {@code tcp} 76 | */ 77 | String getEdgeType(); 78 | 79 | /** 80 | * Returns if this connection is passthrough TLS connection 81 | * 82 | * @return true if passthrough TLS, false otherwise 83 | */ 84 | boolean isPassthroughTls(); 85 | } 86 | } -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/EdgeBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.Map; 8 | import java.util.Objects; 9 | 10 | /** 11 | * A builder for creating an edge listener 12 | */ 13 | public class EdgeBuilder extends MetadataBuilder 14 | implements Listener.Builder, Forwarder.Builder { 15 | private final Session session; 16 | 17 | private final Map labels = new HashMap<>(); 18 | 19 | /** 20 | * Creates a new {@link EdgeBuilder} with a given session. 21 | * 22 | * @param session the session over which this listener will connect. 23 | * If {@code null}, {@link #listen()} and {@link #forward(URL)} 24 | * will throw {@link NullPointerException}, use the corresponding 25 | * methods on the {@link Session} object directly. 26 | */ 27 | public EdgeBuilder(Session session) { 28 | this.session = session; 29 | } 30 | 31 | /** 32 | * Adds a label with the specified key and value to this builder. 33 | * 34 | * @param key the key of the label 35 | * @param value the value of the label 36 | * @return the builder instance 37 | */ 38 | public EdgeBuilder label(String key, String value) { 39 | labels.put(Objects.requireNonNull(key), Objects.requireNonNull(value)); 40 | return this; 41 | } 42 | 43 | /** 44 | * Returns the label map for this builder. 45 | * 46 | * @return a label map, string keys and values 47 | */ 48 | public Map getLabels() { 49 | return Collections.unmodifiableMap(labels); 50 | } 51 | 52 | @Override 53 | public Listener.Edge listen() throws IOException { 54 | return session.listenEdge(this); 55 | } 56 | 57 | @Override 58 | public Forwarder.Edge forward(URL url) throws IOException { 59 | return session.forwardEdge(this, url); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/EndpointBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Objects; 6 | import java.util.Optional; 7 | 8 | /** 9 | * An abstract builder sharing common attributes of endpoint listener builders. 10 | * 11 | * @param the concrete builder impl to return to satisfy the builder pattern 12 | */ 13 | public abstract class EndpointBuilder> extends MetadataBuilder { 14 | private final List allowCIDR = new ArrayList<>(); 15 | private final List denyCIDR = new ArrayList<>(); 16 | private ProxyProto proxyProto = ProxyProto.None; 17 | private Optional trafficPolicy = Optional.empty(); 18 | 19 | /** 20 | * Adds a CIDR to the list of allowed CIDRs for this endpoint. 21 | * 22 | * @param allowCIDR The parameter "allowCIDR" is a string that represents a 23 | * Classless Inter-Domain Routing (CIDR) notation. It is used to specify 24 | * a range of IP addresses that are allowed. For example, 10.0.0.0/24 25 | * @return An instance the builder represented by type T 26 | * 27 | * @see IP Restrictions 28 | * in the ngrok docs for additional details. 29 | */ 30 | public T allowCIDR(String allowCIDR) { 31 | this.allowCIDR.add(allowCIDR); 32 | return (T) this; 33 | } 34 | 35 | /** 36 | * Adds a CIDR to the list of denied CIDRs for this endpoint. 37 | * 38 | * @param denyCIDR The parameter "denyCIDR" is a string that represents a 39 | * Classless Inter-Domain Routing (CIDR) notation. It is used to specify a 40 | * range of IP addresses that should be denied access. For example, 10.0.0.0/24 41 | * @return An instance the builder represented by type T 42 | * 43 | * @see IP Restrictions 44 | * in the ngrok docs for additional details. 45 | */ 46 | public T denyCIDR(String denyCIDR) { 47 | this.denyCIDR.add(denyCIDR); 48 | return (T) this; 49 | } 50 | 51 | /** 52 | * Sets the proxy protocol for this endpoint. 53 | * 54 | * @param proxyProto the proxy protocol for the builder 55 | * @return An instance the builder represented by type T 56 | */ 57 | public T proxyProto(ProxyProto proxyProto) { 58 | this.proxyProto = Objects.requireNonNull(proxyProto); 59 | return (T) this; 60 | } 61 | 62 | /** 63 | * DEPRECATED: use trafficPolicy instead. 64 | * 65 | * @param policy the policy for the builder 66 | * @return An instance the builder represented by type T 67 | */ 68 | public T policy(final String policy) { 69 | this.trafficPolicy = Optional.ofNullable(policy); 70 | return (T) this; 71 | } 72 | 73 | /** 74 | * Sets the policy for this endpoint. 75 | * 76 | * @param trafficPolicy the policy for the builder 77 | * @return An instance the builder represented by type T 78 | */ 79 | public T trafficPolicy(final String trafficPolicy) { 80 | this.trafficPolicy = Optional.ofNullable(trafficPolicy); 81 | return (T) this; 82 | } 83 | 84 | /** 85 | * Returns a list of strings representing allowed CIDR addresses for this endpoint. 86 | * 87 | * @return the currently set allow CIDR addresses 88 | */ 89 | public List getAllowCIDR() { 90 | return allowCIDR; 91 | } 92 | 93 | /** 94 | * Returns a list of strings representing denied CIDR addresses for this endpoint. 95 | * 96 | * @return the currently set deny CIDR addresses 97 | */ 98 | public List getDenyCIDR() { 99 | return denyCIDR; 100 | } 101 | 102 | /** 103 | * Returns the proxy protocol for this builder. 104 | * 105 | * @return the currently set proxy protocol 106 | */ 107 | public ProxyProto getProxyProto() { 108 | return proxyProto; 109 | } 110 | 111 | /** 112 | * Returns the version of the proxy protocol for this endpoint. 113 | * 114 | * @return the currently set version of the proxy protocol 115 | */ 116 | public long getProxyProtoVersion() { 117 | return proxyProto.version(); 118 | } 119 | 120 | /** 121 | * DEPRECATED: use getTrafficPolicy instead. 122 | * 123 | * @return the currently set policy 124 | */ 125 | public Optional getPolicy() { 126 | return this.trafficPolicy; 127 | } 128 | 129 | /** 130 | * Returns the policy for this endpoint. 131 | * 132 | * @return the currently set policy 133 | */ 134 | public Optional getTrafficPolicy() { 135 | return this.trafficPolicy; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/Forwarder.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | 6 | /** 7 | * Forwarder is a type of listener which automatically forwards the 8 | * incoming {@link Connection}s to another url 9 | */ 10 | public interface Forwarder extends ListenerInfo, AutoCloseable { 11 | /** 12 | * Waits for this forwarder to complete. After join returns, this 13 | * forwarder is considered closed. 14 | * 15 | * @throws IOException if an I/O error occurs 16 | */ 17 | void join() throws IOException; 18 | 19 | /** 20 | * Closes this {@link Forwarder}. 21 | * 22 | * @throws IOException if an I/O error occurs 23 | */ 24 | @Override 25 | void close() throws IOException; 26 | 27 | /** 28 | * Represents a builder that can create new {@link Forwarder} instances. 29 | * 30 | * @param the concrete type for the forwarder. 31 | */ 32 | interface Builder { 33 | /** 34 | * Start listening and forwarding connections to given url. 35 | * 36 | * @param url to forward connections to 37 | * @return the concrete {@link Forwarder} instance 38 | * @throws IOException if an I/O error occurs 39 | */ 40 | F forward(URL url) throws IOException; 41 | } 42 | 43 | /** 44 | * Represents an endpoint {@link Forwarder} 45 | */ 46 | interface Endpoint extends Forwarder, ListenerInfo.Endpoint {} 47 | 48 | /** 49 | * Represents an edge {@link Forwarder} 50 | */ 51 | interface Edge extends Forwarder, ListenerInfo.Edge {} 52 | } 53 | -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/Http.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * A set of data classes to support creation of HTTP endpoint listeners. 7 | */ 8 | public interface Http { 9 | /** 10 | * Represents the scheme for an HTTP listener. 11 | */ 12 | enum Scheme { 13 | HTTP("HTTP"), 14 | HTTPS("HTTPS"); 15 | 16 | /** 17 | * The name of the scheme. 18 | */ 19 | public final String name; 20 | 21 | Scheme(String name) { 22 | this.name = name; 23 | } 24 | } 25 | 26 | /** 27 | * Represents an HTTP header. 28 | */ 29 | class Header { 30 | private final String name; 31 | private final String value; 32 | 33 | /** 34 | * Constructs a new header with the specified name and value. 35 | * 36 | * @param name the name of the header 37 | * @param value the value of the header 38 | */ 39 | public Header(String name, String value) { 40 | this.name = Objects.requireNonNull(name); 41 | this.value = Objects.requireNonNull(value); 42 | } 43 | 44 | /** 45 | * Returns the name of the header. 46 | * 47 | * @return the name of the header 48 | */ 49 | public String getName() { 50 | return name; 51 | } 52 | 53 | /** 54 | * Returns the value of the header. 55 | * 56 | * @return the value of the header 57 | */ 58 | public String getValue() { 59 | return value; 60 | } 61 | } 62 | 63 | /** 64 | * Represents basic authentication options for an HTTP listener. 65 | * 66 | * @see Basic Auth 67 | * in the ngrok docs for additional details. 68 | */ 69 | class BasicAuth { 70 | private final String username; 71 | private final String password; 72 | 73 | /** 74 | * Constructs a new set of basic authentication options with the specified 75 | * username and password. 76 | * 77 | * @param username the username 78 | * @param password the password 79 | */ 80 | public BasicAuth(String username, String password) { 81 | this.username = Objects.requireNonNull(username); 82 | this.password = Objects.requireNonNull(password); 83 | } 84 | 85 | /** 86 | * Returns the username for basic auth. 87 | * 88 | * @return the username 89 | */ 90 | public String getUsername() { 91 | return username; 92 | } 93 | 94 | /** 95 | * Returns the password for basic auth. 96 | * 97 | * @return the password 98 | */ 99 | public String getPassword() { 100 | return password; 101 | } 102 | } 103 | 104 | /** 105 | * Represents OAuth configuration for an HTTP listener. 106 | * 107 | * @see OAuth 108 | * in the ngrok docs for additional details. 109 | */ 110 | class OAuth { 111 | private final String provider; 112 | private String clientId; 113 | private String clientSecret; 114 | private final List allowEmails = new ArrayList<>(); 115 | private final List allowDomains = new ArrayList<>(); 116 | private final List scopes = new ArrayList<>(); 117 | 118 | /** 119 | * Constructs new OAuth configuration with the specified provider. 120 | * 121 | * @param provider the provider for OAuth 122 | */ 123 | public OAuth(String provider) { 124 | this.provider = Objects.requireNonNull(provider); 125 | } 126 | 127 | /** 128 | * Sets the client ID and client secret for OAuth. 129 | * 130 | * @param id the client ID for the OAuth 131 | * @param secret the client secret for the OAuth 132 | * @return this OAuth object 133 | */ 134 | public OAuth client(String id, String secret) { 135 | this.clientId = Objects.requireNonNull(id); 136 | this.clientSecret = Objects.requireNonNull(secret); 137 | return this; 138 | } 139 | 140 | /** 141 | * Sets the email address allowed by OAuth. 142 | * 143 | * @param email the email address allowed by OAuth 144 | * @return this OAuth object 145 | */ 146 | public OAuth allowEmail(String email) { 147 | allowEmails.add(Objects.requireNonNull(email)); 148 | return this; 149 | } 150 | 151 | /** 152 | * Sets the domain allowed by the OAuth. 153 | * 154 | * @param domain the domain allowed by OAuth 155 | * @return this OAuth object 156 | */ 157 | public OAuth allowDomain(String domain) { 158 | allowDomains.add(Objects.requireNonNull(domain)); 159 | return this; 160 | } 161 | 162 | /** 163 | * Sets the scope for OAuth. 164 | * 165 | * @param scope the scope for OAuth 166 | * @return this OAuth object 167 | */ 168 | public OAuth scope(String scope) { 169 | scopes.add(Objects.requireNonNull(scope)); 170 | return this; 171 | } 172 | 173 | /** 174 | * Returns the OAuth provider. 175 | * 176 | * @return the provider 177 | */ 178 | public String getProvider() { 179 | return provider; 180 | } 181 | 182 | /** 183 | * Returns of client ID and secret have been configured for OAuth 184 | * 185 | * @return true if both client ID and secret has been set, false otherwise 186 | */ 187 | public boolean hasClientConfigured() { 188 | return clientId != null && clientSecret != null; 189 | } 190 | 191 | /** 192 | * Returns the client ID for OAuth. 193 | * 194 | * @return the client ID 195 | */ 196 | public String getClientId() { 197 | return clientId; 198 | } 199 | 200 | /** 201 | * Returns the client secret for OAuth. 202 | * 203 | * @return the client secret 204 | */ 205 | public String getClientSecret() { 206 | return clientSecret; 207 | } 208 | 209 | /** 210 | * Returns the email address to be allowed by OAuth. 211 | * 212 | * @return the email address 213 | */ 214 | public List getAllowEmails() { 215 | return allowEmails; 216 | } 217 | 218 | /** 219 | * Returns the domain to be allowed by OAuth. 220 | * 221 | * @return the domain 222 | */ 223 | public List getAllowDomains() { 224 | return allowDomains; 225 | } 226 | 227 | /** 228 | * Returns the scope to be used by OAuth. 229 | * 230 | * @return the scope 231 | */ 232 | public List getScopes() { 233 | return scopes; 234 | } 235 | } 236 | 237 | /** 238 | * Represents OIDC configuration for an HTTP listener. 239 | * 240 | * @see OpenID Connect 241 | * in the ngrok docs for additional details. 242 | */ 243 | class OIDC { 244 | private final String issuerUrl; 245 | private final String clientId; 246 | private final String clientSecret; 247 | private final List allowEmails = new ArrayList<>(); 248 | private final List allowDomains = new ArrayList<>(); 249 | private final List scopes = new ArrayList<>(); 250 | 251 | /** 252 | * Constructs a new OIDC configuration with the specified 253 | * issuer URL, client ID, and client secret. 254 | * 255 | * @param issuerUrl the issuer URL 256 | * @param clientId the client ID 257 | * @param clientSecret the client secret 258 | */ 259 | public OIDC(String issuerUrl, String clientId, String clientSecret) { 260 | this.issuerUrl = Objects.requireNonNull(issuerUrl); 261 | this.clientId = Objects.requireNonNull(clientId); 262 | this.clientSecret = Objects.requireNonNull(clientSecret); 263 | } 264 | 265 | /** 266 | * Sets the email address that will be allowed by OIDC. 267 | * 268 | * @param email the email address, unused if {@code null} 269 | * @return this OIDC object 270 | */ 271 | public OIDC allowEmail(String email) { 272 | allowEmails.add(Objects.requireNonNull(email)); 273 | return this; 274 | } 275 | 276 | /** 277 | * Sets the domain that will be allowed by OIDC. 278 | * 279 | * @param domain the domain, unused if {@code null} 280 | * @return this OIDC object 281 | */ 282 | public OIDC allowDomain(String domain) { 283 | allowDomains.add(Objects.requireNonNull(domain)); 284 | return this; 285 | } 286 | 287 | /** 288 | * Sets the scope to be used by OIDC. 289 | * 290 | * @param scope the scope, unused if {@code null} 291 | * @return this OIDC object 292 | */ 293 | public OIDC scope(String scope) { 294 | scopes.add(Objects.requireNonNull(scope)); 295 | return this; 296 | } 297 | 298 | /** 299 | * Returns the issuer URL for OIDC. 300 | * 301 | * @return the issuer URL 302 | */ 303 | public String getIssuerUrl() { 304 | return issuerUrl; 305 | } 306 | 307 | /** 308 | * Returns the client ID for OIDC. 309 | * 310 | * @return the client ID 311 | */ 312 | public String getClientId() { 313 | return clientId; 314 | } 315 | 316 | /** 317 | * Returns the client secret for OIDC. 318 | * 319 | * @return the client secret 320 | */ 321 | public String getClientSecret() { 322 | return clientSecret; 323 | } 324 | 325 | /** 326 | * Returns the email address to be allowed by OIDC. 327 | * 328 | * @return the email address 329 | */ 330 | public List getAllowEmail() { 331 | return allowEmails; 332 | } 333 | 334 | /** 335 | * Returns the domain to be allowed by OIDC. 336 | * 337 | * @return the domain 338 | */ 339 | public List getAllowDomain() { 340 | return allowDomains; 341 | } 342 | 343 | /** 344 | * Returns the scope to be used by OIDC. 345 | * 346 | * @return the scope 347 | */ 348 | public List getScope() { 349 | return scopes; 350 | } 351 | } 352 | 353 | /** 354 | * Represents webhook verification options for an HTTP listener. 355 | * 356 | * @see Webhook Verification 357 | * in the ngrok docs for additional details. 358 | */ 359 | class WebhookVerification { 360 | private final String provider; 361 | private final String secret; 362 | 363 | /** 364 | * Constructs a new set of webhook verification options with the specified 365 | * provider and secret. 366 | * 367 | * @param provider the provider 368 | * @param secret the secret 369 | */ 370 | public WebhookVerification(String provider, String secret) { 371 | this.provider = Objects.requireNonNull(provider); 372 | this.secret = Objects.requireNonNull(secret); 373 | } 374 | 375 | /** 376 | * Returns the provider for the webhook verification. 377 | * 378 | * @return the provider 379 | */ 380 | public String getProvider() { 381 | return provider; 382 | } 383 | 384 | /** 385 | * Returns the secret for the webhook verification. 386 | * 387 | * @return the secret 388 | */ 389 | public String getSecret() { 390 | return secret; 391 | } 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/HttpBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.Objects; 8 | import java.util.Optional; 9 | 10 | /** 11 | * A builder for creating a HTTP endpoint listener 12 | */ 13 | public class HttpBuilder extends EndpointBuilder 14 | implements Listener.Builder, Forwarder.Builder { 15 | private final Session session; 16 | 17 | private Http.Scheme scheme; 18 | private Optional domain = Optional.empty(); 19 | private byte[] mutualTLSCA; 20 | private boolean compression = false; 21 | private boolean websocketTcpConversion = false; 22 | private Optional circuitBreaker = Optional.empty(); 23 | private final List requestHeaders = new ArrayList<>(); 24 | private final List responseHeaders = new ArrayList<>(); 25 | private final List removeRequestHeaders = new ArrayList<>(); 26 | private final List removeResponseHeaders = new ArrayList<>(); 27 | private Http.BasicAuth basicAuthOptions; 28 | private Http.OAuth oauthOptions; 29 | private Http.OIDC oidcOptions; 30 | private Http.WebhookVerification webhookVerification; 31 | 32 | /** 33 | * Creates a new {@link HttpBuilder} with a given session. 34 | * 35 | * @param session the session over which this listener will connect. 36 | * If {@code null}, {@link #listen()} and {@link #forward(URL)} 37 | * will throw {@link NullPointerException}, use the corresponding 38 | * methods on the {@link Session} object directly. 39 | */ 40 | public HttpBuilder(Session session) { 41 | this.session = session; 42 | } 43 | 44 | /** 45 | * Sets the scheme for this HTTP endpoint. The default scheme is {@code Http.Scheme.HTTPS}. 46 | * 47 | * @param scheme the scheme 48 | * @return the builder instance 49 | */ 50 | public HttpBuilder scheme(Http.Scheme scheme) { 51 | this.scheme = Objects.requireNonNull(scheme); 52 | return this; 53 | } 54 | 55 | /** 56 | * Sets the domain to request for this HTTP endpoint. Any valid domain or hostname 57 | * that you have previously registered with ngrok. If using a custom domain, this requires 58 | * registering in the ngrok dashboard 59 | * and setting a DNS CNAME value. 60 | * 61 | * @param domain the domain 62 | * @return the builder instance 63 | */ 64 | public HttpBuilder domain(String domain) { 65 | this.domain = Optional.of(domain); 66 | return this; 67 | } 68 | 69 | /** 70 | * Sets the certificates to use for client authentication for this HTTP endpoint. 71 | * 72 | * @param mutualTLSCA the TLS certificate, in bytes 73 | * @return the builder instance 74 | * 75 | * @see Mutual TLS 76 | * in the ngrok docs for additional details. 77 | */ 78 | public HttpBuilder mutualTLSCA(byte[] mutualTLSCA) { 79 | this.mutualTLSCA = Objects.requireNonNull(mutualTLSCA); 80 | return this; 81 | } 82 | 83 | /** 84 | * Enables HTTP response compression for this HTTP endpoint. 85 | * 86 | * @return the builder instance 87 | * 88 | * @see Compression 89 | * in the ngrok docs for additional details. 90 | */ 91 | public HttpBuilder compression() { 92 | this.compression = true; 93 | return this; 94 | } 95 | 96 | /** 97 | * Enables Websocket to TCP conversion for this HTTP endpoint. 98 | * 99 | * @return the builder instance 100 | * 101 | * @see Websocket TCP Converter 102 | * in the ngrok docs for additional details. 103 | */ 104 | public HttpBuilder websocketTcpConversion() { 105 | this.websocketTcpConversion = true; 106 | return this; 107 | } 108 | 109 | /** 110 | * Sets the circuit breaker value for this HTTP endpoint. ngrok will reject requests 111 | * when 5XX responses exceed this ratio. 112 | * 113 | * @param value the circuit breaker value, between 0 and 1 114 | * @return the builder instance 115 | * 116 | * @see Circuit Breaker 117 | * in the ngrok docs for additional details. 118 | */ 119 | public HttpBuilder circuitBreaker(double value) { 120 | this.circuitBreaker = Optional.of(value); 121 | return this; 122 | } 123 | 124 | /** 125 | * Adds a header to the list of added request headers for this HTTP endpoint. 126 | * 127 | * @param name the name of the header to add 128 | * @param value the value of the header to add 129 | * @return the builder instance 130 | * 131 | * @see Request Headers 132 | * in the ngrok docs for additional details. 133 | */ 134 | public HttpBuilder addRequestHeader(String name, String value) { 135 | this.requestHeaders.add(new Http.Header(name, value)); 136 | return this; 137 | } 138 | 139 | /** 140 | * Adds a header to the list of added response headers for this HTTP endpoint. 141 | * 142 | * @param name the name of the header to add 143 | * @param value the value of the header to add 144 | * @return the builder instance 145 | * 146 | * @see Response Headers 147 | * in the ngrok docs for additional details. 148 | */ 149 | public HttpBuilder addResponseHeader(String name, String value) { 150 | this.responseHeaders.add(new Http.Header(name, value)); 151 | return this; 152 | } 153 | 154 | /** 155 | * Adds a header to the list of removed request headers for this HTTP endpoint. 156 | * 157 | * @param name the name of the header to remove 158 | * @return the builder instance 159 | * 160 | * @see Request Headers 161 | * in the ngrok docs for additional details. 162 | */ 163 | public HttpBuilder removeRequestHeader(String name) { 164 | this.removeRequestHeaders.add(Objects.requireNonNull(name)); 165 | return this; 166 | } 167 | 168 | /** 169 | * Adds a header to the list of removed response headers for this HTTP endpoint. 170 | * 171 | * @param name the name of the header to remove 172 | * @return the builder instance 173 | * 174 | * @see Response Headers 175 | * in the ngrok docs for additional details. 176 | */ 177 | public HttpBuilder removeResponseHeader(String name) { 178 | this.removeResponseHeaders.add(Objects.requireNonNull(name)); 179 | return this; 180 | } 181 | 182 | /** 183 | * Sets basic authentication for this HTTP endpoint. 184 | * 185 | * @param options the basic authentication options 186 | * @return the builder instance 187 | * 188 | * @see Basic Auth 189 | * in the ngrok docs for additional details. 190 | */ 191 | public HttpBuilder basicAuthOptions(Http.BasicAuth options) { 192 | this.basicAuthOptions = options; 193 | return this; 194 | } 195 | 196 | /** 197 | * Sets OAuth for this HTTP endpoint. 198 | * 199 | * @param options the OAuth options 200 | * @return the builder instance 201 | * 202 | * @see OAuth 203 | * in the ngrok docs for additional details. 204 | */ 205 | public HttpBuilder oauthOptions(Http.OAuth options) { 206 | this.oauthOptions = options; 207 | return this; 208 | } 209 | 210 | /** 211 | * Sets OIDC for this HTTP endpoint. 212 | * 213 | * @param options the OIDC options 214 | * @return the builder instance 215 | * 216 | * @see OpenID Connect 217 | * in the ngrok docs for additional details. 218 | */ 219 | public HttpBuilder oidcOptions(Http.OIDC options) { 220 | this.oidcOptions = options; 221 | return this; 222 | } 223 | 224 | /** 225 | * Sets webhook verification for this HTTP endpoint. 226 | * 227 | * @param webhookVerification the webhook verification options 228 | * @return the builder instance 229 | * 230 | * @see Webhook Verification 231 | * in the ngrok docs for additional details. 232 | */ 233 | public HttpBuilder webhookVerification(Http.WebhookVerification webhookVerification) { 234 | this.webhookVerification = webhookVerification; 235 | return this; 236 | } 237 | 238 | /** 239 | * Returns the scheme for this HTTP endpoint. 240 | * 241 | * @return the scheme 242 | */ 243 | public Http.Scheme getScheme() { 244 | return scheme; 245 | } 246 | 247 | /** 248 | * Returns the scheme name for this HTTP endpoint. 249 | * 250 | * @return the scheme name, either empty, HTTPS or HTTP 251 | */ 252 | public Optional getSchemeName() { 253 | return Optional.ofNullable(scheme).map((s) -> s.name); 254 | } 255 | 256 | /** 257 | * Returns the domain on this HTTP endpoint. 258 | * 259 | * @return the domain 260 | */ 261 | public Optional getDomain() { 262 | return domain; 263 | } 264 | 265 | /** 266 | * Returns the certificates to use for client authentication for this HTTP endpoint. 267 | * 268 | * @return the TLS certificates, in bytes. 269 | */ 270 | public byte[] getMutualTLSCA() { 271 | return mutualTLSCA; 272 | } 273 | 274 | /** 275 | * Returns whether compression is enabled for this HTTP endpoint. 276 | * 277 | * @return {@code true} if compression is enabled, {@code false} otherwise 278 | */ 279 | public boolean isCompression() { 280 | return compression; 281 | } 282 | 283 | /** 284 | * Returns whether WebSocket to TCP conversion is enabled for this HTTP endpoint. 285 | * 286 | * @return {@code true} if WebSocket to TCP conversion is enabled, {@code false} otherwise 287 | */ 288 | public boolean isWebsocketTcpConversion() { 289 | return websocketTcpConversion; 290 | } 291 | 292 | /** 293 | * Returns the circuit breaker value for this HTTP endpoint. 294 | * 295 | * @return the circuit breaker value 296 | */ 297 | public Optional getCircuitBreaker() { 298 | return circuitBreaker; 299 | } 300 | 301 | /** 302 | * Returns the list of request headers to add for this HTTP endpoint. 303 | * 304 | * @return the list of headers 305 | */ 306 | public List getRequestHeaders() { 307 | return requestHeaders; 308 | } 309 | 310 | /** 311 | * Returns the list of response headers to add for this HTTP endpoint. 312 | * 313 | * @return the list of headers 314 | */ 315 | public List getResponseHeaders() { 316 | return responseHeaders; 317 | } 318 | 319 | /** 320 | * Returns the list of request headers to remove for this HTTP endpoint. 321 | * 322 | * @return the list of headers 323 | */ 324 | public List getRemoveRequestHeaders() { 325 | return removeRequestHeaders; 326 | } 327 | 328 | /** 329 | * Returns the list of response headers to remove for this HTTP endpoint. 330 | * 331 | * @return the list of headers 332 | */ 333 | public List getRemoveResponseHeaders() { 334 | return removeResponseHeaders; 335 | } 336 | 337 | /** 338 | * Returns the basic authentication options for this HTTP endpoint. 339 | * 340 | * @return the basic authentication options 341 | */ 342 | public Http.BasicAuth getBasicAuth() { 343 | return basicAuthOptions; 344 | } 345 | 346 | /** 347 | * Returns the OAuth options for this HTTP endpoint. 348 | * 349 | * @return the OAuth options 350 | */ 351 | public Http.OAuth getOauth() { 352 | return oauthOptions; 353 | } 354 | 355 | /** 356 | * Returns the OIDC options for this HTTP endpoint. 357 | * 358 | * @return the OIDC options 359 | */ 360 | public Http.OIDC getOidc() { 361 | return oidcOptions; 362 | } 363 | 364 | /** 365 | * Returns the webhook verification options for this HTTP endpoint. 366 | * 367 | * @return the webhook verification options 368 | */ 369 | public Http.WebhookVerification getWebhookVerification() { 370 | return webhookVerification; 371 | } 372 | 373 | @Override 374 | public Listener.Endpoint listen() throws IOException { 375 | return session.listenHttp(this); 376 | } 377 | 378 | @Override 379 | public Forwarder.Endpoint forward(URL url) throws IOException { 380 | return session.forwardHttp(this, url); 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/Listener.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | 5 | /** 6 | * Listener enables applications to handle incoming traffic proxied by ngrok. Each 7 | * connection to ngrok is forwarded to an instance of a {@link Listener} for processing 8 | * the implementation specific logic. 9 | * 10 | * @param the type of {@link Connection}s this listener accepts 11 | */ 12 | public interface Listener extends ListenerInfo, AutoCloseable { 13 | /** 14 | * Waits for the next connection and returns it. 15 | * 16 | * @return the connection 17 | * @throws IOException if an I/O error occurs 18 | */ 19 | C accept() throws IOException; 20 | 21 | /** 22 | * Closes this {@link Listener}. 23 | * 24 | * @throws IOException if an I/O error occurs 25 | */ 26 | @Override 27 | void close() throws IOException; 28 | 29 | /** 30 | * Represents a builder that can create new {@link Listener} instances. 31 | * 32 | * @param the concrete type for the listener. 33 | */ 34 | interface Builder { 35 | /** 36 | * Starts listening and accepting new connections. 37 | * 38 | * @return the concrete {@link Listener} instance 39 | * @throws IOException if an I/O error occurs 40 | */ 41 | L listen() throws IOException; 42 | } 43 | 44 | /** 45 | * Represents an endpoint {@link Listener}. This is a listener that is configured 46 | * by the application on demand, as it is starting to listen. 47 | */ 48 | interface Endpoint extends Listener, ListenerInfo.Endpoint {} 49 | 50 | /** 51 | * Represents an edge {@link Listener}. This is a listener that is statically 52 | * configured in ngrok either through the Dashboard or via the API. 53 | */ 54 | interface Edge extends Listener, ListenerInfo.Edge {} 55 | } 56 | -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/ListenerInfo.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.util.Map; 4 | 5 | /** 6 | * Represents information about a running {@link Listener}. 7 | */ 8 | public interface ListenerInfo { 9 | /** 10 | * Returns the id associated with this listener 11 | * 12 | * @return the id 13 | */ 14 | String getId(); 15 | 16 | /** 17 | * Returns the metadata associated with this listener 18 | * 19 | * @return the metadata 20 | */ 21 | String getMetadata(); 22 | 23 | /** 24 | * Returns the target of that listener 25 | * 26 | * @return the target 27 | */ 28 | String getForwardsTo(); 29 | 30 | /** 31 | * Represents information about a running {@link Listener.Endpoint}. 32 | */ 33 | interface Endpoint extends ListenerInfo { 34 | /** 35 | * Returns the protocol associated with this listener. 36 | * 37 | * @return the protocol, for example {@code http} or {@code tcp} 38 | */ 39 | String getProto(); 40 | 41 | /** 42 | * Returns the URL at which this listener receives new connections. 43 | * 44 | * @return the url 45 | */ 46 | String getUrl(); 47 | } 48 | 49 | /** 50 | * Represents information about a running {@link Listener.Edge}. 51 | */ 52 | interface Edge extends ListenerInfo { 53 | /** 54 | * Returns the labels associated with this listener. 55 | * 56 | * @return the labels 57 | */ 58 | Map getLabels(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/MetadataBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.net.URL; 4 | import java.util.Optional; 5 | 6 | /** 7 | * An abstract builder sharing common attributes of all listener builders. 8 | * 9 | * @param the concrete builder impl to return to satisfy the builder pattern 10 | */ 11 | public abstract class MetadataBuilder { 12 | private Optional metadata = Optional.empty(); 13 | private Optional forwardsTo = Optional.empty(); 14 | 15 | /** 16 | * Sets the metadata for this endpoint. 17 | * 18 | * @param metadata the metadata 19 | * @return An instance the builder represented by type T 20 | */ 21 | public T metadata(String metadata) { 22 | this.metadata = Optional.of(metadata); 23 | return (T) this; 24 | } 25 | 26 | /** 27 | * Sets the forwarding information for this endpoint. 28 | * 29 | * If you need to automatically forward connections, you can use {@link Forwarder}, either 30 | * through using {@link Forwarder.Builder} or directly calling methods on {@link Session} 31 | * such as {@link Session#forwardHttp(HttpBuilder, URL)}. 32 | * 33 | * NOTE: Using the {@link Forwarder} will override what is set here 34 | * with the actual URL you're forwarding to. 35 | * 36 | * @param forwardsTo the forwarding information 37 | * @return An instance the builder represented by type T 38 | */ 39 | public T forwardsTo(String forwardsTo) { 40 | this.forwardsTo = Optional.of(forwardsTo); 41 | return (T) this; 42 | } 43 | 44 | /** 45 | * Returns the metadata for this endpoint. 46 | * 47 | * @return the metadata 48 | */ 49 | public Optional getMetadata() { 50 | return metadata; 51 | } 52 | 53 | /** 54 | * Returns the forwarding information for this endpoint. 55 | * 56 | * @return the forwarding information 57 | */ 58 | public Optional getForwardsTo() { 59 | return forwardsTo; 60 | } 61 | } -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/NgrokException.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | 5 | public class NgrokException extends IOException { 6 | private final String code; 7 | private final String details; 8 | 9 | public NgrokException(String code, String details) { 10 | super(String.format("%s\n\n%s", code, details)); 11 | this.code = code; 12 | this.details = details; 13 | } 14 | 15 | public String getCode() { 16 | return code; 17 | } 18 | 19 | public String getDetails() { 20 | return details; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/ProxyProto.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | /** 4 | * Enum representing the proxy protocol version. 5 | */ 6 | public enum ProxyProto { 7 | None(0), 8 | V1(1), 9 | V2(2); 10 | 11 | /** 12 | * The version of the proxy protocol. 13 | */ 14 | public final long version; 15 | 16 | /** 17 | * Constructs a new `ProxyProto` instance with the specified version. 18 | * 19 | * @param version the version of the proxy protocol 20 | */ 21 | ProxyProto(int version) { 22 | this.version = version; 23 | } 24 | 25 | /** 26 | * Returns the version of the proxy protocol. 27 | * 28 | * @return the version 29 | */ 30 | public long version() { 31 | return version; 32 | } 33 | } -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/Session.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.lang.reflect.InvocationTargetException; 5 | import java.net.URL; 6 | import java.time.Duration; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Objects; 10 | import java.util.Optional; 11 | 12 | /** 13 | * A session with the ngrok service. 14 | */ 15 | public interface Session extends AutoCloseable { 16 | /** 17 | * Creates a new session {@link Builder} with specified ngrok authtoken 18 | * 19 | * @param authtoken the authtoken 20 | * @return the builder 21 | */ 22 | static Builder withAuthtoken(String authtoken) { 23 | return new Builder(authtoken); 24 | } 25 | 26 | /** 27 | * Creates a new session {@link Builder} resolving 28 | * the ngrok authtoken from {@code NGROK_AUTHTOKEN} env variable 29 | * 30 | * @return the builder 31 | */ 32 | static Builder withAuthtokenFromEnv() { 33 | return new Builder(System.getenv("NGROK_AUTHTOKEN")); 34 | } 35 | 36 | /** 37 | * Connects a session with specified {@link Builder} 38 | * 39 | * @param builder the builder 40 | * @return newly created session 41 | * @throws IOException if an I/O error occurs 42 | */ 43 | static Session connect(Builder builder) throws IOException { 44 | try { 45 | var clazz = Class.forName("com.ngrok.NativeSession"); 46 | var method = clazz.getMethod("connect", Builder.class); 47 | return (Session) method.invoke(null, builder); 48 | } catch (InvocationTargetException e) { 49 | var cause = e.getCause(); 50 | if (cause instanceof IOException) { 51 | throw (IOException) cause; 52 | } 53 | throw new RuntimeException(cause); 54 | } catch (Exception e) { 55 | throw new RuntimeException(e); 56 | } 57 | } 58 | 59 | /** 60 | * Returns the ID of this session 61 | * 62 | * @return session ID 63 | */ 64 | String getId(); 65 | 66 | /** 67 | * Returns the metadata of this session 68 | * 69 | * @return session metadata 70 | */ 71 | String getMetadata(); 72 | 73 | /** 74 | * Creates a new {@link TcpBuilder} associated with this session. 75 | * 76 | * @return the builder 77 | */ 78 | default TcpBuilder tcpEndpoint() { 79 | return new TcpBuilder(this); 80 | } 81 | 82 | /** 83 | * Configures and starts a TCP {@link Listener.Endpoint} 84 | * 85 | * @param builder the builder 86 | * @return the running listener 87 | * @throws IOException if an I/O error occurs 88 | */ 89 | Listener.Endpoint listenTcp(TcpBuilder builder) throws IOException; 90 | 91 | /** 92 | * Configures and starts a TCP {@link Forwarder.Endpoint} 93 | * 94 | * @param builder the builder 95 | * @param url the url to forward to 96 | * @return the running forwarder 97 | * @throws IOException if an I/O error occurs 98 | */ 99 | Forwarder.Endpoint forwardTcp(TcpBuilder builder, URL url) throws IOException; 100 | 101 | /** 102 | * Creates a new {@link TlsBuilder} associated with this session. 103 | * 104 | * @return the builder 105 | */ 106 | default TlsBuilder tlsEndpoint() { 107 | return new TlsBuilder(this); 108 | } 109 | 110 | /** 111 | * Configures and starts a TLS {@link Listener.Endpoint} 112 | * 113 | * @param builder the builder 114 | * @return the running listener 115 | * @throws IOException if an I/O error occurs 116 | */ 117 | Listener.Endpoint listenTls(TlsBuilder builder) throws IOException; 118 | 119 | /** 120 | * Configures and starts a TLS {@link Forwarder.Endpoint} 121 | * 122 | * @param builder the builder 123 | * @param url the url to forward to 124 | * @return the running forwarder 125 | * @throws IOException if an I/O error occurs 126 | */ 127 | Forwarder.Endpoint forwardTls(TlsBuilder builder, URL url) throws IOException; 128 | 129 | /** 130 | * Creates a new {@link HttpBuilder} associated with this session. 131 | * 132 | * @return the builder 133 | */ 134 | default HttpBuilder httpEndpoint() { 135 | return new HttpBuilder(this); 136 | } 137 | 138 | /** 139 | * Configures and starts a HTTP {@link Listener.Endpoint} 140 | * 141 | * @param builder the builder 142 | * @return the running listener 143 | * @throws IOException if an I/O error occurs 144 | */ 145 | Listener.Endpoint listenHttp(HttpBuilder builder) throws IOException; 146 | 147 | /** 148 | * Configures and starts a HTTP {@link Forwarder.Endpoint} 149 | * 150 | * @param builder the builder 151 | * @param url the url to forward to 152 | * @return the running forwarder 153 | * @throws IOException if an I/O error occurs 154 | */ 155 | Forwarder.Endpoint forwardHttp(HttpBuilder builder, URL url) throws IOException; 156 | 157 | /** 158 | * Creates a new {@link EdgeBuilder} associated with this session. 159 | * 160 | * @return the builder 161 | */ 162 | default EdgeBuilder edge() { 163 | return new EdgeBuilder(this); 164 | } 165 | 166 | /** 167 | * Configures and starts a {@link Listener.Edge} 168 | * 169 | * @param builder the builder 170 | * @return the running listener 171 | * @throws IOException if an I/O error occurs 172 | */ 173 | Listener.Edge listenEdge(EdgeBuilder builder) throws IOException; 174 | 175 | /** 176 | * Configures and starts a {@link Forwarder.Edge} 177 | * 178 | * @param builder the builder 179 | * @param url the url to forward to 180 | * @return the running forwarder 181 | * @throws IOException if an I/O error occurs 182 | */ 183 | Forwarder.Edge forwardEdge(EdgeBuilder builder, URL url) throws IOException; 184 | 185 | /** 186 | * Closes a listener by its ID 187 | * 188 | * @param listenerId the listener ID 189 | * @throws IOException if an I/O error occurs 190 | */ 191 | void closeListener(String listenerId) throws IOException; 192 | 193 | /** 194 | * Closes a forwarder by its ID 195 | * 196 | * @param forwarderId the forwarder ID 197 | * @throws IOException if an I/O error occurs 198 | */ 199 | void closeForwarder(String forwarderId) throws IOException; 200 | 201 | @Override 202 | void close() throws IOException; 203 | 204 | /** 205 | * Provides a way to listen for specific server side events. 206 | */ 207 | interface CommandHandler { 208 | /** 209 | * Called when the associated event triggers. 210 | */ 211 | void onCommand(); 212 | } 213 | 214 | /** 215 | * Provides a way to monitor current session's heartbeats and disconnects. 216 | */ 217 | interface HeartbeatHandler { 218 | /** 219 | * Called on each successful heartbeat, with the duration it took to execute it. 220 | * 221 | * @param durationMs the duration of the heartbeat in milliseconds 222 | */ 223 | void heartbeat(long durationMs); 224 | 225 | /** 226 | * Called when session times out (e.g. the heartbeat fails). The session will 227 | * automatically reconnect, but this gives the application a chance to react. 228 | */ 229 | default void timeout() {} 230 | } 231 | 232 | /** 233 | * Represents additional information about the client. Use it to describe your application. 234 | * 235 | * This library also injects its own client information, describing lower levels of the stack. 236 | */ 237 | class ClientInfo { 238 | private final String type; 239 | 240 | private final String version; 241 | 242 | private final Optional comments; 243 | 244 | /** 245 | * Creates a new client information with a given type, version and comment. 246 | * 247 | * @param type the type of the client, required 248 | * @param version the version of the client, required 249 | * @param comments additional comments, optional 250 | */ 251 | public ClientInfo(String type, String version, String comments) { 252 | this.type = Objects.requireNonNull(type); 253 | this.version = Objects.requireNonNull(version); 254 | this.comments = Optional.ofNullable(comments); 255 | } 256 | 257 | /** 258 | * Returns the type of this client. 259 | * 260 | * @return the type 261 | */ 262 | public String getType() { 263 | return type; 264 | } 265 | 266 | /** 267 | * Returns the version of this client. 268 | * 269 | * @return the version 270 | */ 271 | public String getVersion() { 272 | return version; 273 | } 274 | 275 | /** 276 | * Returns the comments for this client. 277 | * 278 | * @return the comments 279 | */ 280 | public Optional getComments() { 281 | return comments; 282 | } 283 | } 284 | 285 | /** 286 | * A builder for creating a session 287 | */ 288 | class Builder { 289 | 290 | private final String authtoken; 291 | 292 | private Optional heartbeatInterval = Optional.empty(); 293 | private Optional heartbeatTolerance = Optional.empty(); 294 | 295 | private Optional metadata = Optional.empty(); 296 | 297 | private Optional serverAddr = Optional.empty(); 298 | private byte[] caCert; 299 | 300 | private CommandHandler stopCallback; 301 | private CommandHandler restartCallback; 302 | private CommandHandler updateCallback; 303 | 304 | private HeartbeatHandler heartbeatHandler; 305 | 306 | private final List clientInfos = new ArrayList<>(); 307 | 308 | private Builder(String authtoken) { 309 | this.authtoken = Objects.requireNonNullElse(authtoken, ""); 310 | } 311 | 312 | /** 313 | * Sets the heartbeat interval for this builder 314 | * 315 | * @param duration the interval duration 316 | * @return the builder instance 317 | */ 318 | public Builder heartbeatInterval(Duration duration) { 319 | this.heartbeatInterval = Optional.of(duration); 320 | return this; 321 | } 322 | 323 | /** 324 | * Sets the heartbeat tolerance for this builder 325 | * 326 | * @param duration the tolerance duration 327 | * @return the builder instance 328 | */ 329 | public Builder heartbeatTolerance(Duration duration) { 330 | this.heartbeatTolerance = Optional.of(duration); 331 | return this; 332 | } 333 | 334 | /** 335 | * Sets the metadata for this builder 336 | * 337 | * @param metadata the metadata 338 | * @return the builder instance 339 | */ 340 | public Builder metadata(String metadata) { 341 | this.metadata = Optional.of(metadata); 342 | return this; 343 | } 344 | 345 | /** 346 | * Sets the server address for this builder 347 | * 348 | * @param addr the server address 349 | * @return the builder instance 350 | */ 351 | public Builder serverAddr(String addr) { 352 | this.serverAddr = Optional.of(addr); 353 | return this; 354 | } 355 | 356 | /** 357 | * Sets the ca certificate for this builder 358 | * 359 | * @param data the ca certificate 360 | * @return the builder instance 361 | */ 362 | public Builder caCert(byte[] data) { 363 | this.caCert = data; 364 | return this; 365 | } 366 | 367 | /** 368 | * Sets the stop callback handler for this builder 369 | * 370 | * @param callback the stop callback 371 | * @return the builder instance 372 | */ 373 | public Builder stopCallback(CommandHandler callback) { 374 | this.stopCallback = callback; 375 | return this; 376 | } 377 | 378 | /** 379 | * Sets the restart callback handler for this builder 380 | * 381 | * @param callback the restart callback 382 | * @return the builder instance 383 | */ 384 | public Builder restartCallback(CommandHandler callback) { 385 | this.restartCallback = callback; 386 | return this; 387 | } 388 | 389 | /** 390 | * Sets the update callback handler for this builder 391 | * 392 | * @param callback the update callback 393 | * @return the builder instance 394 | */ 395 | public Builder updateCallback(CommandHandler callback) { 396 | this.updateCallback = callback; 397 | return this; 398 | } 399 | 400 | /** 401 | * Sets the heartbeat handler for this builder 402 | * 403 | * @param heartbeatHandler the heartbeat callback 404 | * @return the builder instance 405 | */ 406 | public Builder heartbeatHandler(HeartbeatHandler heartbeatHandler) { 407 | this.heartbeatHandler = heartbeatHandler; 408 | return this; 409 | } 410 | 411 | /** 412 | * Adds a client info to the list of client info objects for this builder 413 | * 414 | * @param name the client name 415 | * @param version the client version 416 | * @return the builder instance 417 | */ 418 | public Builder addClientInfo(String name, String version) { 419 | this.clientInfos.add(new ClientInfo(name, version, null)); 420 | return this; 421 | } 422 | 423 | /** 424 | * Adds a client info to the list of client info objects for this builder 425 | * 426 | * @param name the client name 427 | * @param version the client version 428 | * @param comments the comments 429 | * @return the builder instance 430 | */ 431 | public Builder addClientInfo(String name, String version, String comments) { 432 | this.clientInfos.add(new ClientInfo(name, version, comments)); 433 | return this; 434 | } 435 | 436 | /** 437 | * Returns the ngrok authtoken associated with this builder. 438 | * 439 | * @return the authtoken 440 | */ 441 | public String getAuthtoken() { 442 | return authtoken; 443 | } 444 | 445 | /** 446 | * Returns the heartbeat interval for this builder 447 | * 448 | * @return the heartbeat interval 449 | */ 450 | public Optional getHeartbeatInterval() { 451 | return heartbeatInterval; 452 | } 453 | 454 | /** 455 | * Returns the heartbeat tolerance for this builder 456 | * 457 | * @return the heartbeat tolerance 458 | */ 459 | public Optional getHeartbeatTolerance() { 460 | return heartbeatTolerance; 461 | } 462 | 463 | /** 464 | * Returns the metadata for this builder. 465 | * 466 | * @return the metadata 467 | */ 468 | public Optional getMetadata() { 469 | return metadata; 470 | } 471 | 472 | /** 473 | * Returns the server address for this builder. 474 | * 475 | * @return the server address 476 | */ 477 | public Optional getServerAddr() { 478 | return serverAddr; 479 | } 480 | 481 | /** 482 | * Returns the certificate for this builder. 483 | * 484 | * @return the certificate 485 | */ 486 | public byte[] getCaCert() { 487 | return caCert; 488 | } 489 | 490 | /** 491 | * Returns the stop callback handler for this builder. 492 | * 493 | * @return the stop handler 494 | */ 495 | public CommandHandler stopCallback() { 496 | return stopCallback; 497 | } 498 | 499 | /** 500 | * Returns the restart callback handler for this builder. 501 | * 502 | * @return the restart handler 503 | */ 504 | public CommandHandler restartCallback() { 505 | return restartCallback; 506 | } 507 | 508 | /** 509 | * Returns the update callback handler for this builder. 510 | * 511 | * @return the update handler 512 | */ 513 | public CommandHandler updateCallback() { 514 | return updateCallback; 515 | } 516 | 517 | /** 518 | * Returns the heartbeat handler for this builder. 519 | * 520 | * @return the heartbeat handler 521 | */ 522 | public HeartbeatHandler heartbeatHandler() { 523 | return heartbeatHandler; 524 | } 525 | 526 | /** 527 | * Returns the list of client info objects to add for this builder 528 | * 529 | * @return the list of client info objects 530 | */ 531 | public List getClientInfos() { 532 | return clientInfos; 533 | } 534 | 535 | /** 536 | * Connects a session with the current {@link Builder} 537 | * 538 | * @return newly created session 539 | * @throws IOException if an I/O error occurs 540 | */ 541 | public Session connect() throws IOException { 542 | return Session.connect(this); 543 | } 544 | } 545 | } -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/TcpBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | import java.util.Optional; 6 | 7 | /** 8 | * A builder for creating a TCP endpoint listener 9 | */ 10 | public class TcpBuilder extends EndpointBuilder 11 | implements Listener.Builder, Forwarder.Builder { 12 | 13 | private final Session session; 14 | 15 | private Optional remoteAddress = Optional.empty(); 16 | 17 | /** 18 | * Creates a new {@link TcpBuilder} with a given session. 19 | * 20 | * @param session the session over which this listener will connect. 21 | * If {@code null}, {@link #listen()} and {@link #forward(URL)} 22 | * will throw {@link NullPointerException}, use the corresponding 23 | * methods on the {@link Session} object directly. 24 | */ 25 | public TcpBuilder(Session session) { 26 | this.session = session; 27 | } 28 | 29 | /** 30 | * Sets the TCP address to request for this TCP endpoint. 31 | * 32 | * @param remoteAddress the remote address 33 | * @return the builder instance 34 | */ 35 | public TcpBuilder remoteAddress(String remoteAddress) { 36 | this.remoteAddress = Optional.of(remoteAddress); 37 | return this; 38 | } 39 | 40 | /** 41 | * Returns the remote address on this builder. 42 | * 43 | * @return the remote address 44 | */ 45 | public Optional getRemoteAddress() { 46 | return remoteAddress; 47 | } 48 | 49 | @Override 50 | public Listener.Endpoint listen() throws IOException { 51 | return session.listenTcp(this); 52 | } 53 | 54 | @Override 55 | public Forwarder.Endpoint forward(URL url) throws IOException { 56 | return session.forwardTcp(this, url); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/TlsBuilder.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | import java.util.Objects; 6 | import java.util.Optional; 7 | 8 | /** 9 | * A builder for creating a TLS endpoint listener 10 | */ 11 | public class TlsBuilder extends EndpointBuilder 12 | implements Listener.Builder, Forwarder.Builder { 13 | private final Session session; 14 | 15 | private Optional domain = Optional.empty(); 16 | 17 | private byte[] mutualTLSCA; 18 | 19 | private byte[] terminationCertPEM; 20 | private byte[] terminationKeyPEM; 21 | 22 | /** 23 | * Creates a new {@link TlsBuilder} with a given session. 24 | * 25 | * @param session the session over which this listener will connect. 26 | * If {@code null}, {@link #listen()} and {@link #forward(URL)} 27 | * will throw {@link NullPointerException}, use the corresponding 28 | * methods on the {@link Session} object directly. 29 | */ 30 | public TlsBuilder(Session session) { 31 | this.session = session; 32 | } 33 | 34 | /** 35 | * Sets the domain to request for this TLS endpoint. Any valid domain or hostname 36 | * that you have previously registered with ngrok. If using a custom domain, this requires 37 | * registering in the ngrok dashboard 38 | * and setting a DNS CNAME value. 39 | * 40 | * @param domain the domain 41 | * @return the builder instance 42 | */ 43 | public TlsBuilder domain(String domain) { 44 | this.domain = Optional.of(domain); 45 | return this; 46 | } 47 | 48 | /** 49 | * Sets the certificates to use for client authentication for this TLS endpoint. 50 | * 51 | * @param mutualTLSCA the TLS certificate, in bytes 52 | * @return the builder instance 53 | * 54 | * @see Mutual TLS 55 | * in the ngrok docs for additional details. 56 | */ 57 | public TlsBuilder mutualTLSCA(byte[] mutualTLSCA) { 58 | this.mutualTLSCA = Objects.requireNonNull(mutualTLSCA); 59 | return this; 60 | } 61 | 62 | /** 63 | * Sets the certificate and key to use for TLS termination for this TLS endpoint. 64 | * 65 | * @param terminationCertPEM the TLS certificate, in bytes 66 | * @param terminationKeyPEM the TLS key, in bytes 67 | * @return the builder instance 68 | * 69 | * @see TLS Termination 70 | * in the ngrok docs for additional details. 71 | */ 72 | public TlsBuilder termination(byte[] terminationCertPEM, byte[] terminationKeyPEM) { 73 | this.terminationCertPEM = Objects.requireNonNull(terminationCertPEM); 74 | this.terminationKeyPEM = Objects.requireNonNull(terminationKeyPEM); 75 | return this; 76 | } 77 | 78 | /** 79 | * Returns the domain to request for this TLS endpoint. 80 | * 81 | * @return the domain 82 | */ 83 | public Optional getDomain() { 84 | return domain; 85 | } 86 | 87 | /** 88 | * Returns the certificates to use for client authentication for this TLS endpoint. 89 | * 90 | * @return the TLS certificates, in bytes. 91 | */ 92 | public byte[] getMutualTLSCA() { 93 | return mutualTLSCA; 94 | } 95 | 96 | /** 97 | * Sets the certificate to use for TLS termination for this TLS endpoint. 98 | * 99 | * @return the TLS termination certificate, in bytes. 100 | */ 101 | public byte[] getTerminationCertPEM() { 102 | return terminationCertPEM; 103 | } 104 | 105 | /** 106 | * Sets the key to use for TLS termination for this TLS endpoint. 107 | * 108 | * @return the TLS termination key, in bytes. 109 | */ 110 | public byte[] getTerminationKeyPEM() { 111 | return terminationKeyPEM; 112 | } 113 | 114 | @Override 115 | public Listener.Endpoint listen() throws IOException { 116 | return session.listenTls(this); 117 | } 118 | 119 | @Override 120 | public Forwarder.Endpoint forward(URL url) throws IOException { 121 | return session.forwardTls(this, url); 122 | } 123 | } -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/net/AbstractSocketImpl.java: -------------------------------------------------------------------------------- 1 | package com.ngrok.net; 2 | 3 | import java.io.IOException; 4 | import java.io.InputStream; 5 | import java.io.OutputStream; 6 | import java.net.InetAddress; 7 | import java.net.SocketAddress; 8 | import java.net.SocketException; 9 | import java.net.SocketImpl; 10 | 11 | /** 12 | * Abstract implementation of the {@link SocketImpl} interface. 13 | */ 14 | public class AbstractSocketImpl extends SocketImpl { 15 | /** 16 | * See {@link SocketImpl#create(boolean)} 17 | */ 18 | @Override 19 | protected void create(boolean stream) throws IOException { 20 | throw new UnsupportedOperationException(); 21 | } 22 | 23 | /** 24 | * See {@link SocketImpl#connect(String, int)} 25 | */ 26 | @Override 27 | protected void connect(String host, int port) throws IOException { 28 | throw new UnsupportedOperationException(); 29 | } 30 | 31 | /** 32 | * See {@link SocketImpl#connect(InetAddress, int)} 33 | */ 34 | @Override 35 | protected void connect(InetAddress address, int port) throws IOException { 36 | throw new UnsupportedOperationException(); 37 | } 38 | 39 | /** 40 | * See {@link SocketImpl#connect(SocketAddress, int)} 41 | */ 42 | @Override 43 | protected void connect(SocketAddress address, int timeout) throws IOException { 44 | throw new UnsupportedOperationException(); 45 | } 46 | 47 | /** 48 | * See {@link SocketImpl#bind(InetAddress, int)} 49 | */ 50 | @Override 51 | protected void bind(InetAddress host, int port) throws IOException { 52 | throw new UnsupportedOperationException(); 53 | } 54 | 55 | /** 56 | * See {@link SocketImpl#listen(int)} 57 | */ 58 | @Override 59 | protected void listen(int backlog) throws IOException { 60 | throw new UnsupportedOperationException(); 61 | } 62 | 63 | /** 64 | * See {@link SocketImpl#accept(SocketImpl)} 65 | */ 66 | @Override 67 | protected void accept(SocketImpl s) throws IOException { 68 | throw new UnsupportedOperationException(); 69 | } 70 | 71 | /** 72 | * See {@link SocketImpl#getInputStream()} 73 | */ 74 | @Override 75 | protected InputStream getInputStream() throws IOException { 76 | throw new UnsupportedOperationException(); 77 | } 78 | 79 | /** 80 | * See {@link SocketImpl#getOutputStream()} 81 | */ 82 | @Override 83 | protected OutputStream getOutputStream() throws IOException { 84 | throw new UnsupportedOperationException(); 85 | } 86 | 87 | /** 88 | * See {@link SocketImpl#available()} 89 | */ 90 | @Override 91 | protected int available() throws IOException { 92 | throw new UnsupportedOperationException(); 93 | } 94 | 95 | /** 96 | * See {@link SocketImpl#close()} 97 | */ 98 | @Override 99 | protected void close() throws IOException { 100 | throw new UnsupportedOperationException(); 101 | } 102 | 103 | /** 104 | * See {@link SocketImpl#sendUrgentData(int)} 105 | */ 106 | @Override 107 | protected void sendUrgentData(int data) throws IOException { 108 | throw new UnsupportedOperationException(); 109 | } 110 | 111 | /** 112 | * See {@link SocketImpl#setOption(int, Object)} 113 | */ 114 | @Override 115 | public void setOption(int optID, Object value) throws SocketException { 116 | throw new UnsupportedOperationException(); 117 | } 118 | 119 | /** 120 | * See {@link SocketImpl#getOption(int)} 121 | */ 122 | @Override 123 | public Object getOption(int optID) throws SocketException { 124 | throw new UnsupportedOperationException(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/net/ConnectionInputStream.java: -------------------------------------------------------------------------------- 1 | package com.ngrok.net; 2 | 3 | import com.ngrok.Connection; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.nio.ByteBuffer; 8 | 9 | /** 10 | * An input stream for reading data from {@link Connection}. 11 | */ 12 | public class ConnectionInputStream extends InputStream { 13 | private final Connection connection; 14 | 15 | private final ByteBuffer buffer; 16 | 17 | /** 18 | * Creates a new input stream for the given connection with the specified buffer 19 | * size. 20 | * 21 | * @param connection the connection to read from 22 | * @param bufferSize the size of the buffer to use to read data from the 23 | * connection 24 | */ 25 | public ConnectionInputStream(Connection connection, int bufferSize) { 26 | this.connection = connection; 27 | this.buffer = ByteBuffer.allocateDirect(bufferSize); 28 | this.buffer.flip(); 29 | } 30 | 31 | /** 32 | * Prepares the buffer for reading by clearing it and then reading data from the 33 | * connection into the buffer. Ignored if the buffer is not empty. 34 | * Automatically called by {@link #read()} and {@link #read(byte[], int, int)}. 35 | * 36 | * @throws IOException if an I/O error occurs 37 | */ 38 | private void prepare() throws IOException { 39 | if (buffer.hasRemaining()) { 40 | return; 41 | } 42 | 43 | buffer.clear(); 44 | connection.read(buffer); 45 | } 46 | 47 | /** 48 | * Reads a single byte of data from the input stream. 49 | * 50 | * @return the byte of data, or -1 if the end of the stream has been reached 51 | * @throws IOException if an I/O error occurs 52 | */ 53 | @Override 54 | public int read() throws IOException { 55 | prepare(); 56 | return buffer.get(); 57 | } 58 | 59 | /** 60 | * Reads up to len bytes of data from the input stream into an array of bytes. 61 | * 62 | * @param b the array of bytes to read the data into 63 | * @param off the offset within the buffer to start reading the data from 64 | * @param len the maximum number of bytes to read 65 | * @return the total number of bytes read into the buffer, or -1 if the end of 66 | * the stream has been reached 67 | * @throws IOException if an I/O error occurs 68 | */ 69 | @Override 70 | public int read(byte[] b, int off, int len) throws IOException { 71 | prepare(); 72 | 73 | var readLen = Math.min(len, buffer.remaining()); 74 | buffer.get(b, off, readLen); 75 | return readLen; 76 | } 77 | } -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/net/ConnectionOutputStream.java: -------------------------------------------------------------------------------- 1 | package com.ngrok.net; 2 | 3 | import com.ngrok.Connection; 4 | 5 | import java.io.IOException; 6 | import java.io.OutputStream; 7 | import java.nio.ByteBuffer; 8 | 9 | /** 10 | * An output stream for writing data to {@link Connection}. 11 | */ 12 | public class ConnectionOutputStream extends OutputStream { 13 | private final Connection connection; 14 | 15 | private final ByteBuffer buffer; 16 | 17 | /** 18 | * Creates a new output stream for the given connection, backed by a 19 | * direct buffer with the specified buffer size. 20 | * 21 | * @param connection the connection to write to 22 | * @param bufferSize the size of the buffer to use to write data to the 23 | * connection 24 | */ 25 | public ConnectionOutputStream(Connection connection, int bufferSize) { 26 | this.connection = connection; 27 | this.buffer = ByteBuffer.allocateDirect(bufferSize); 28 | } 29 | 30 | /** 31 | * Writes a single byte of data to the output stream. 32 | * 33 | * @param b the byte to write 34 | * @throws IOException if an I/O error occurs 35 | */ 36 | @Override 37 | public void write(int b) throws IOException { 38 | buffer.put((byte) b); 39 | flush(); 40 | } 41 | 42 | /** 43 | * Writes bytes from the specified byte array starting at offset off to the 44 | * output stream. 45 | * 46 | * @param b the array of bytes to write 47 | * @param off the offset within the buffer to start writing from 48 | * @param len the number of bytes to write 49 | * @throws IOException if an I/O error occurs 50 | */ 51 | @Override 52 | public void write(byte[] b, int off, int len) throws IOException { 53 | for (int pos = 0, delta = Math.min(buffer.capacity(), len); pos < len; pos += delta) { 54 | delta = Math.min(delta, len-pos); 55 | buffer.put(b, off+pos, delta); 56 | flush(); 57 | } 58 | } 59 | 60 | /** 61 | * Flushes the output stream, forcing any buffered output bytes to be written 62 | * out. 63 | * Automatically called by {@link #write(int)} and 64 | * {@link #write(byte[], int, int)}. 65 | * 66 | * @throws IOException if an I/O error occurs 67 | */ 68 | @Override 69 | public void flush() throws IOException { 70 | buffer.flip(); 71 | connection.write(buffer); 72 | buffer.clear(); 73 | } 74 | } -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/net/ConnectionSocket.java: -------------------------------------------------------------------------------- 1 | package com.ngrok.net; 2 | 3 | import java.io.IOException; 4 | import java.net.InetAddress; 5 | import java.net.Socket; 6 | 7 | /** 8 | * A socket for establishing a connection to a remote server. 9 | */ 10 | public class ConnectionSocket extends Socket { 11 | /** 12 | * Creates a new connection socket. 13 | * 14 | * @throws IOException if an I/O error occurs 15 | */ 16 | protected ConnectionSocket() throws IOException { 17 | super(new ConnectionSocketImpl()); 18 | } 19 | 20 | /** 21 | * Returns the {@link InetAddress} of the remote endpoint of this socket. 22 | * 23 | * @return the {@link InetAddress} of the remote endpoint 24 | */ 25 | @Override 26 | public InetAddress getInetAddress() { 27 | return super.getInetAddress(); 28 | } 29 | } -------------------------------------------------------------------------------- /ngrok-java/src/main/java/com/ngrok/net/ConnectionSocketImpl.java: -------------------------------------------------------------------------------- 1 | package com.ngrok.net; 2 | 3 | import com.ngrok.Connection; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.OutputStream; 8 | 9 | /** 10 | * An implementation of {@link AbstractSocketImpl} for establishing a socket 11 | * connection to a remote server. 12 | */ 13 | public class ConnectionSocketImpl extends AbstractSocketImpl { 14 | protected Connection connection; 15 | 16 | protected ConnectionSocketImpl() { 17 | } 18 | 19 | protected void setConnection(Connection connection) { 20 | this.connection = connection; 21 | 22 | var addr = connection.inetAddress(); 23 | this.address = addr.getAddress(); 24 | this.port = addr.getPort(); 25 | } 26 | 27 | /** 28 | * Creates and returns a {@link ConnectionInputStream} for reading data from the 29 | * connection. 30 | * 31 | * @return an {@link InputStream} for reading data 32 | * @throws IOException if an I/O error occurs 33 | */ 34 | @Override 35 | protected InputStream getInputStream() throws IOException { 36 | return new ConnectionInputStream(connection, 4096); 37 | } 38 | 39 | /** 40 | * Creates and returns a {@link ConnectionOutputStream} for writing data to the 41 | * connection. 42 | * 43 | * @return an {@link OutputStream} for writing data 44 | * @throws IOException if an I/O error occurs 45 | */ 46 | @Override 47 | protected OutputStream getOutputStream() throws IOException { 48 | return new ConnectionOutputStream(connection, 4096); 49 | } 50 | } -------------------------------------------------------------------------------- /ngrok-java/src/test/java/com/ngrok/ConnectionTest.java: -------------------------------------------------------------------------------- 1 | package com.ngrok; 2 | 3 | import com.ngrok.Connection; 4 | import org.junit.Test; 5 | 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | 9 | import static org.junit.Assert.assertEquals; 10 | import static org.junit.Assert.assertNotNull; 11 | 12 | public class ConnectionTest { 13 | @Test 14 | public void testInetAddress() throws Exception { 15 | var conn = new ConnectionAddressMock(); 16 | 17 | conn.remoteAddr = "4.3.2.1:8000"; 18 | var addr = conn.inetAddress(); 19 | assertNotNull(addr); 20 | assertEquals("4.3.2.1", addr.getHostName()); 21 | assertEquals(8000, addr.getPort()); 22 | 23 | conn.remoteAddr = "[2a00:23c8:a8dd:e501:b02c:ea1a:83cf:395e]:64440"; 24 | addr = conn.inetAddress(); 25 | assertNotNull(addr); 26 | assertEquals("2a00:23c8:a8dd:e501:b02c:ea1a:83cf:395e", addr.getHostName()); 27 | assertEquals(64440, addr.getPort()); 28 | } 29 | 30 | class ConnectionAddressMock implements Connection { 31 | String remoteAddr; 32 | 33 | @Override 34 | public String getRemoteAddr() { 35 | return remoteAddr; 36 | } 37 | 38 | @Override 39 | public int read(ByteBuffer dst) throws IOException { 40 | throw new UnsupportedOperationException(); 41 | } 42 | 43 | @Override 44 | public int write(ByteBuffer src) throws IOException { 45 | throw new UnsupportedOperationException(); 46 | } 47 | 48 | @Override 49 | public void close() throws IOException { 50 | throw new UnsupportedOperationException(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /ngrok-java/src/test/java/com/ngrok/net/ConnectionOutputStreamTest.java: -------------------------------------------------------------------------------- 1 | package com.ngrok.net; 2 | 3 | import com.ngrok.Connection; 4 | import org.junit.Test; 5 | 6 | import java.io.IOException; 7 | import java.nio.ByteBuffer; 8 | import java.nio.charset.StandardCharsets; 9 | 10 | import static junit.framework.TestCase.assertEquals; 11 | 12 | public class ConnectionOutputStreamTest { 13 | @Test 14 | public void testStreamChunking() throws Exception { 15 | var conn = new CollectingConnection(); 16 | var os = new ConnectionOutputStream(conn, 8); 17 | 18 | // an array of 32 bytes 19 | var data = "0123456789 0123456789 0123456789".getBytes(StandardCharsets.UTF_8); 20 | assertEquals(32, data.length); 21 | 22 | for (int low = 0; low < data.length; low++) { 23 | for (int high = low; high < data.length; high++) { 24 | os.write(data, low, high-low); 25 | conn.data.flip(); 26 | 27 | assertEquals(high-low, conn.data.limit()); 28 | for (int k = low; k < high; k++) { 29 | assertEquals(data[k], conn.data.get()); 30 | } 31 | conn.data.clear(); 32 | } 33 | } 34 | } 35 | 36 | private static class CollectingConnection implements Connection { 37 | private final ByteBuffer data = ByteBuffer.allocate(1024); 38 | 39 | @Override 40 | public String getRemoteAddr() { 41 | return "local"; 42 | } 43 | 44 | @Override 45 | public int read(ByteBuffer dst) throws IOException { 46 | return 0; 47 | } 48 | 49 | @Override 50 | public int write(ByteBuffer src) throws IOException { 51 | data.put(src); 52 | return src.limit(); 53 | } 54 | 55 | @Override 56 | public void close() throws IOException { 57 | 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /ngrok-jetty/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | com.ngrok 5 | ngrok-project 6 | 1.2.0-SNAPSHOT 7 | 8 | 9 | 4.0.0 10 | ngrok-jetty 11 | ngrok :: Java Jetty integration 12 | jar 13 | 14 | 15 | 16 | 11 17 | 11 18 | UTF-8 19 | 20 | 21 | 22 | 23 | 24 | org.apache.maven.plugins 25 | maven-toolchains-plugin 26 | 3.1.0 27 | 28 | 29 | 30 | toolchain 31 | 32 | 33 | 34 | 35 | 11 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | com.ngrok 48 | ngrok-java 49 | 1.2.0-SNAPSHOT 50 | 51 | 52 | org.eclipse.jetty 53 | jetty-server 54 | ${jetty.version} 55 | compile 56 | 57 | 58 | -------------------------------------------------------------------------------- /ngrok-jetty/src/main/java/com/ngrok/jetty/NgrokConnector.java: -------------------------------------------------------------------------------- 1 | package com.ngrok.jetty; 2 | 3 | import com.ngrok.Session; 4 | 5 | import org.eclipse.jetty.http.HttpVersion; 6 | import org.eclipse.jetty.server.AbstractConnector; 7 | import org.eclipse.jetty.server.HttpConnectionFactory; 8 | import org.eclipse.jetty.server.Server; 9 | 10 | import java.io.IOException; 11 | import java.util.function.Function; 12 | import java.util.function.Supplier; 13 | 14 | /** 15 | * A class representing a connector implementation for ngrok listeners. 16 | */ 17 | public class NgrokConnector extends AbstractConnector { 18 | private final Supplier sessionSupplier; 19 | private final Function listenerFunction; 20 | private Session session; 21 | private com.ngrok.Listener listener; 22 | 23 | /** 24 | * Constructs a new ngrok connector with the specified server, session supplier, 25 | * and listener function. 26 | * 27 | * @param server the server to use for the connector 28 | * @param sessionSupplier the supplier for the session used by the connector 29 | * @param listenerFunction the function for creating the listener 30 | */ 31 | public NgrokConnector(Server server, Supplier sessionSupplier, Function listenerFunction) { 32 | super(server, null, null, null, -1, new HttpConnectionFactory()); 33 | setDefaultProtocol(HttpVersion.HTTP_1_1.asString()); 34 | 35 | this.sessionSupplier = sessionSupplier; 36 | this.listenerFunction = listenerFunction; 37 | } 38 | 39 | /** 40 | * Starts this ngrok connector. 41 | * 42 | * @throws Exception if an error occurs while starting the connector 43 | */ 44 | @Override 45 | protected void doStart() throws Exception { 46 | this.session = sessionSupplier.get(); 47 | this.listener = listenerFunction.apply(this.session); 48 | super.doStart(); 49 | } 50 | 51 | /** 52 | * Accepts a new connection on this ngrok connector. 53 | * 54 | * @param i the ID of the connection 55 | * @throws IOException if an I/O error occurs 56 | * @throws InterruptedException if the thread is interrupted 57 | */ 58 | @Override 59 | protected void accept(int i) throws IOException, InterruptedException { 60 | var ngrokConnection = listener.accept(); 61 | var ep = new NgrokEndpoint(getScheduler(), ngrokConnection); 62 | 63 | var connection = getDefaultConnectionFactory().newConnection(this, ep); 64 | ep.setConnection(connection); 65 | 66 | connection.onOpen(); 67 | } 68 | 69 | /** 70 | * Stops this ngrok connector. 71 | * 72 | * @throws Exception if an error occurs while stopping the connector 73 | */ 74 | @Override 75 | protected void doStop() throws Exception { 76 | super.doStop(); 77 | this.listener.close(); 78 | } 79 | 80 | /** 81 | * Throws an {@link UnsupportedOperationException}, as the transport used by ngrok 82 | * connector is not supported. 83 | * 84 | * @throws UnsupportedOperationException if the method is called 85 | */ 86 | @Override 87 | public Object getTransport() { 88 | throw new UnsupportedOperationException("ohnoe"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ngrok-jetty/src/main/java/com/ngrok/jetty/NgrokEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.ngrok.jetty; 2 | 3 | import java.io.IOException; 4 | import java.net.SocketAddress; 5 | import java.nio.ByteBuffer; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | import org.eclipse.jetty.io.AbstractEndPoint; 9 | import org.eclipse.jetty.util.thread.Scheduler; 10 | 11 | import com.ngrok.Connection; 12 | 13 | /** 14 | * A class representing an endpoint for ngrok connection. 15 | */ 16 | public class NgrokEndpoint extends AbstractEndPoint { 17 | private final Connection conn; 18 | 19 | /** 20 | * Constructs a new ngrok endpoint with the specified scheduler and connection. 21 | * 22 | * @param scheduler the scheduler to use for the endpoint 23 | * @param conn the connection to use for the endpoint 24 | */ 25 | public NgrokEndpoint(Scheduler scheduler, Connection conn) { 26 | super(scheduler); 27 | this.conn = conn; 28 | 29 | onOpen(); 30 | } 31 | 32 | /** 33 | * Throws an {@link UnsupportedOperationException}, as incomplete flush is not supported 34 | * by ngrok endpoints. 35 | * 36 | * @throws UnsupportedOperationException if the method is called 37 | */ 38 | @Override 39 | protected void onIncompleteFlush() { 40 | throw new UnsupportedOperationException("noful"); 41 | } 42 | 43 | /** 44 | * Schedules the endpoint to be filled with interest. 45 | * 46 | * @throws IOException if an I/O error occurs 47 | */ 48 | @Override 49 | protected void needsFillInterest() throws IOException { 50 | getScheduler().schedule(() -> getFillInterest().fillable(), 0, TimeUnit.NANOSECONDS); 51 | } 52 | 53 | /** 54 | * Fills the endpoint with data from the connection. 55 | * 56 | * @param buffer the buffer to fill with data 57 | * @return the number of bytes read from the connection 58 | * @throws IOException if an I/O error occurs 59 | */ 60 | @Override 61 | public int fill(ByteBuffer buffer) throws IOException { 62 | return conn.read(buffer); 63 | } 64 | 65 | /** 66 | * Flushes the endpoint with data from the connection. 67 | * 68 | * @param buffer the buffer to flush with data 69 | * @return true if the buffer was completely flushed, false otherwise 70 | * @throws IOException if an I/O error occurs 71 | */ 72 | @Override 73 | public boolean flush(ByteBuffer... buffer) throws IOException { 74 | for (var b : buffer) { 75 | int sz = conn.write(b); 76 | if (sz < b.limit()) { 77 | return false; 78 | } 79 | } 80 | return true; 81 | } 82 | 83 | /** 84 | * Throws an {@link UnsupportedOperationException}, as the transport used by ngrok 85 | * endpoints is not supported. 86 | * 87 | * @throws UnsupportedOperationException if the method is called 88 | */ 89 | @Override 90 | public Object getTransport() { 91 | throw new UnsupportedOperationException("ohnoe"); 92 | } 93 | 94 | @Override 95 | public SocketAddress getLocalSocketAddress() { 96 | return null; 97 | } 98 | 99 | @Override 100 | public SocketAddress getRemoteSocketAddress() { 101 | return conn.inetAddress(); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4.0.0 5 | com.ngrok 6 | ngrok-project 7 | 1.2.0-SNAPSHOT 8 | pom 9 | 10 | ngrok :: Project 11 | https://ngrok.com 12 | This is the ngrok agent in library form, suitable for integrating directly into Java applications. 13 | 14 | 15 | 16 | 2.0.6 17 | 4.13.2 18 | 11.0.14 19 | 20 | 21 | 2.8.2 22 | 3.0.0 23 | 24 | 25 | 26 | https://github.com/ngrok/ngrok-java 27 | scm:git:${project.scm.url} 28 | scm:git:${project.scm.url} 29 | 0.2.0-SNAPSHOT 30 | 31 | 32 | 33 | ngrok-java 34 | ngrok-java-17 35 | ngrok-java-native 36 | ngrok-jetty 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | maven-clean-plugin 45 | 3.1.0 46 | 47 | 48 | 49 | maven-resources-plugin 50 | 3.0.2 51 | 52 | 53 | maven-compiler-plugin 54 | 3.8.0 55 | 56 | 57 | maven-surefire-plugin 58 | 2.22.1 59 | 60 | true 61 | 62 | 63 | 64 | maven-jar-plugin 65 | 3.0.2 66 | 67 | 68 | maven-install-plugin 69 | 2.5.2 70 | 71 | 72 | maven-deploy-plugin 73 | ${maven.deploy.plugin.version} 74 | 75 | 76 | 77 | maven-site-plugin 78 | 3.7.1 79 | 80 | 81 | maven-project-info-reports-plugin 82 | 3.0.0 83 | 84 | 85 | org.apache.maven.plugins 86 | maven-release-plugin 87 | ${maven.release.plugin.version} 88 | 89 | true 90 | v@{project.version} 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-javadoc-plugin 96 | 3.5.0 97 | 98 | 99 | attach-javadocs 100 | 101 | jar 102 | 103 | 104 | 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-source-plugin 109 | 3.2.1 110 | 111 | 112 | attach-source 113 | 114 | jar 115 | 116 | 117 | 118 | 119 | 120 | org.apache.maven.plugins 121 | maven-gpg-plugin 122 | 3.0.1 123 | 124 | 125 | sign-artifacts 126 | verify 127 | 128 | sign 129 | 130 | 131 | 132 | 133 | 134 | org.sonatype.plugins 135 | nexus-staging-maven-plugin 136 | 1.6.13 137 | true 138 | 139 | ossrh 140 | https://s01.oss.sonatype.org/ 141 | true 142 | 143 | 144 | 145 | 146 | 147 | 148 | org.apache.maven.plugins 149 | maven-release-plugin 150 | 151 | 152 | org.apache.maven.plugins 153 | maven-toolchains-plugin 154 | 3.1.0 155 | 156 | 157 | 158 | toolchain 159 | 160 | 161 | 162 | 163 | 11 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | github-distro 176 | 177 | 178 | github 179 | GitHub Packages 180 | https://maven.pkg.github.com/ngrok/ngrok-java 181 | 182 | 183 | 184 | 185 | central-distro 186 | 187 | 188 | ossrh 189 | https://s01.oss.sonatype.org/content/repositories/snapshots 190 | 191 | 192 | ossrh 193 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 194 | 195 | 196 | 197 | 198 | 199 | org.apache.maven.plugins 200 | maven-javadoc-plugin 201 | 202 | 203 | org.apache.maven.plugins 204 | maven-source-plugin 205 | 206 | 207 | org.apache.maven.plugins 208 | maven-gpg-plugin 209 | 210 | 211 | org.sonatype.plugins 212 | nexus-staging-maven-plugin 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | ngrok 221 | https://ngrok.com 222 | 223 | 224 | 225 | GitHub issues 226 | https://github.com/ngrok/ngrok-java/issues 227 | 228 | 229 | 230 | 231 | Apache Software License - Version 2.0 232 | https://www.apache.org/licenses/LICENSE-2.0 233 | 234 | 235 | MIT License 236 | http://opensource.org/licenses/MIT 237 | 238 | 239 | 240 | 241 | 242 | niki 243 | Nikolay Petrov 244 | nikolay@ngrok.com 245 | ngrok 246 | https://ngrok.com 247 | 248 | 249 | carl 250 | Carl Amko 251 | carl@ngrok.com 252 | ngrok 253 | https://ngrok.com 254 | 255 | 256 | 257 | -------------------------------------------------------------------------------- /toolchains.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | jdk 4 | 5 | 11 6 | 7 | 8 | ${env.JAVA_11_HOME} 9 | 10 | 11 | 12 | jdk 13 | 14 | 17 15 | 16 | 17 | ${env.JAVA_17_HOME} 18 | 19 | 20 | --------------------------------------------------------------------------------