Integration Examples
Spring
Configuration
Create a NgrokConfiguration
class that lets you use the config to enable ngrok
and pass it some useful parameters.
@Component
@Getter
@Setter
@ConfigurationProperties(prefix = "ngrok")
public class NgrokConfiguration {
private boolean enabled;
private String publicUrl;
}
And pass parameters to your Spring application through your config file:
spring.profiles.active=dev
ngrok.enabled=true
Application Integration
If ngrok.enabled
config flag is set, java-ngrok
will be initialized when Spring is booting. An easy way to do
this is by creating a Component
with an EventListener
that is executed when WebServerInitializedEvent
is emitted.
@Component
@Profile("dev")
public class NgrokWebServerEventListener {
private static final Logger LOGGER = LoggerFactory.getLogger(NgrokWebServerEventListener.class);
private final Environment environment;
private final NgrokConfiguration ngrokConfiguration;
@Autowired
public NgrokWebServerEventListener(final Environment environment,
final NgrokConfiguration ngrokConfiguration) {
this.environment = environment;
this.ngrokConfiguration = ngrokConfiguration;
}
@EventListener
public void onApplicationEvent(final WebServerInitializedEvent event) {
// Only install and initialize ngrok if we're actually using it
if (ngrokConfiguration.isEnabled()) {
final NgrokClient ngrokClient = new NgrokClient.Builder()
.build();
final int port = event.getWebServer().getPort();
final CreateTunnel createTunnel = new CreateTunnel.Builder()
.withAddr(port)
.build();
final Tunnel tunnel = ngrokClient.connect(createTunnel);
final String publicUrl = tunnel.getPublicUrl();
LOGGER.info(String.format("ngrok tunnel \"%s\" -> \"http://127.0.0.1:%d\"",publicUrl, port));
// Update any base URLs or webhooks to use the public ngrok URL
ngrokConfiguration.setPublicUrl(publicUrl);
initWebhooks(publicUrl);
}
}
private void initWebhooks(final String publicUrl) {
// ... Implement updates necessary so inbound traffic uses the public-facing ngrok URL
}
}
Now Spring can be started by the usual means, setting ngrok.enabled
in the config to open a tunnel.
- Run
./gradlew build
to build the application - Start application with
java -jar build/libs/java-ngrok-example-spring-1.0.0-SNAPSHOT.jar
- Check the logs for the
ngrok
tunnel's public URL, which should tunnel tohttp://localhost:8080
Dropwizard
Configuration
Create
a NgrokConfiguration
class that lets you use the config to enable ngrok
and pass it some useful parameters.
public class NgrokConfiguration {
@JsonProperty
private boolean enabled = false;
public boolean isEnabled() {
return enabled;
}
}
Then wire this class as a JsonProperty
to the Dropwizard Configuration for your Application.
public class JavaNgrokExampleDropwizardConfiguration extends Configuration {
@JsonProperty
private String environment = "dev";
@JsonProperty("ngrok")
private NgrokConfiguration ngrokConfiguration;
public String getEnvironment() {
return environment;
}
public NgrokConfiguration getNgrokConfiguration() {
return ngrokConfiguration;
}
}
And pass parameters to your Dropwizard application through your config file:
ngrok:
enabled: true
Now Dropwizard can be started by the usual means, setting ngrok.enabled
in the config to open a tunnel.
- Run
mvn install
to build the application - Start application with
java -jar target/java-ngrok-example-dropwizard-1.0.0-SNAPSHOT.jar server src/main/resources/config.yml
- Check the logs for the
ngrok
tunnel's public URL, which should tunnel tohttp://localhost:8080
Application Integration
If ngrok.enabled
config flag is set, java-ngrok
will be initialized when Dropwizard is booting. An easy place to do
this is in the run()
method of the Application.
public class JavaNgrokExampleDropwizardApplication extends Application<JavaNgrokExampleDropwizardConfiguration> {
private static final Logger LOGGER = Logger.getLogger(String.valueOf(JavaNgrokExampleDropwizardApplication.class));
@Override
public void run(final JavaNgrokExampleDropwizardConfiguration configuration,
final Environment environment) {
// Only install and initialize ngrok if we're actually using it
if (configuration.getEnvironment().equals("dev") &&
configuration.getNgrokConfiguration().isEnabled()) {
final NgrokClient ngrokClient = new NgrokClient.Builder()
.build();
final int port = getPort(configuration);
final CreateTunnel createTunnel = new CreateTunnel.Builder()
.withAddr(port)
.build();
final Tunnel tunnel = ngrokClient.connect(createTunnel);
final String publicUrl = tunnel.getPublicUrl();
LOGGER.info(String.format("ngrok tunnel \"%s\" -> \"http://127.0.0.1:%d\"", publicUrl, port));
// Update any base URLs or webhooks to use the public ngrok URL
configuration.setPublicUrl(publicUrl);
initWebhooks(publicUrl);
}
}
private int getPort(JavaNgrokExampleDropwizardConfiguration configuration) {
final Stream<ConnectorFactory> connectors = configuration.getServerFactory() instanceof DefaultServerFactory
? ((DefaultServerFactory) configuration.getServerFactory()).getApplicationConnectors().stream()
: Stream.of((SimpleServerFactory) configuration.getServerFactory()).map(SimpleServerFactory::getConnector);
return connectors.filter(connector -> connector.getClass().isAssignableFrom(HttpConnectorFactory.class))
.map(connector -> (HttpConnectorFactory) connector)
.mapToInt(HttpConnectorFactory::getPort)
.findFirst()
.orElseThrow(IllegalStateException::new);
}
private void initWebhooks(final String publicUrl) {
// ... Implement updates necessary so inbound traffic uses the public-facing ngrok URL
}
// ... Implement the rest of your Dropwizard application
}
Play (Scala)
Application Integration
Register an eager Singleton
in the app's base Module
.
class Module extends AbstractModule {
override def configure(): Unit = {
bind(classOf[NgrokApplicationLifecycle]).asEagerSingleton()
}
}
Then create a NgrokApplicatinLifecycle
class.
If ngrok.enabled
config flag is set, java-ngrok
will be initialized when Play is booting in a dev
environment.
@Singleton
class NgrokApplicationLifecycle @Inject()(config: Configuration, lifecycle: ApplicationLifecycle) {
private val environment: String = config.getOptional[String]("environment").getOrElse("production")
private val ngrokEnabled: Boolean = config.getOptional[Boolean]("ngrok.enabled").getOrElse(false)
// Only install and initialize ngrok if we're actually using it
if (environment.equals("dev") && ngrokEnabled) {
val ngrokClient: NgrokClient = new NgrokClient.Builder()
.build
val port: Int = config.getOptional[Int]("http.port").getOrElse(9000)
val createTunnel: CreateTunnel = new CreateTunnel.Builder()
.withAddr(port)
.build
val tunnel: Tunnel = ngrokClient.connect(createTunnel)
println(s" * ngrok tunnel \"${tunnel.getPublicUrl}\" -> \"http://localhost:$port\"")
}
}
Pass parameters to your Play application through
your config file (including
making .ngrok.io
an allowed host):
ngrok {
enabled=true
}
play.filters.hosts {
allowed = [".ngrok.io", "localhost:9000"]
}
Now Play can be started by the usual means, setting ngrok.enabled
in the config to open a tunnel.
- Run
sbt compile package
to build the application - Start application with
sbt run
- Check the logs for the
ngrok
tunnel's public URL, which should tunnel tohttp://localhost:9000
End-to-End Testing
Some testing use-cases might mean you want to temporarily expose a route via a java-ngrok
tunnel to fully validate a
workflow. For example, an internal end-to-end tester, a step in a pre-deployment validation pipeline, or a service that
automatically updates a status page.
Whatever the case may be, using JUnit's BeforeAll
and AfterAll
fixtures are a good place to hook java-ngrok
in to your integration tests. This snippet builds on the Dropwizard
example above, but it could be modified to work with other frameworks.
@ExtendWith(DropwizardExtensionsSupport.class)
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class JavaNgrokTestCase extends TestCase {
private final static DropwizardAppExtension<JavaNgrokExampleDropwizardConfiguration> dropwizardAppExtension = new DropwizardAppExtension<>(
JavaNgrokExampleDropwizardApplication.class,
ResourceHelpers.resourceFilePath("config.yml")
);
private NgrokClient ngrokClient;
private String baseUrl;
@BeforeAll
public void setUpClass() {
this.ngrokClient = new NgrokClient.Builder()
.build();
final CreateTunnel createTunnel = new CreateTunnel.Builder()
.withAddr(EXT.getLocalPort())
.build();
final Tunnel tunnel = ngrokClient.connect(createTunnel);
this.baseUrl = tunnel.getPublicUrl();
// ... Implement other initializes so you can assert against the inbound traffic through your tunnel
}
@AfterAll
public void tearUpClass() {
ngrokClient.kill();
}
}
Now, any test that needs to assert against responses through a java-ngrok
tunnel can simply extend
JavaNgrokTestCase
to inherit these fixtures.
Simple HTTP Server
Java's HttpServer
class also makes for a useful development server. You can use java-ngrok
to expose it to the web
via a tunnel, as shown here:
public class NgrokHttpServer {
private static final Logger LOGGER = Logger.getLogger(String.valueOf(NgrokHttpServer.class));
public static void main(String[] args) throws Exception {
final int port = Integer.parseInt(System.getenv().getOrDefault("PORT", "80"));
final HttpServer server = HttpServer.create(new InetSocketAddress(port), 0);
final NgrokClient ngrokClient = new NgrokClient.Builder().build();
final CreateTunnel createTunnel = new CreateTunnel.Builder()
.withAddr(port)
.build();
final Tunnel tunnel = ngrokClient.connect(createTunnel);
LOGGER.info(String.format("ngrok tunnel \"%s\" -> \"http://127.0.0.1:%d\"", tunnel.getPublicUrl(), port));
server.start();
}
}
Simple TCP Server and Client
This example project shows a simple TCP ping/pong server. It opens a local socket, uses ngrok
to tunnel to that
socket, then the client/server communicate via the publicly exposed address.