├── .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 |
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 | 
13 | [](https://ngrok.github.io/ngrok-java/)
14 | 
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 |
--------------------------------------------------------------------------------