pyngrok - a Python wrapper for ngrok

pyngrok - a Python wrapper for ngrok https://badge.fury.io/py/pyngrok.svg https://travis-ci.org/alexdlaird/pyngrok.svg?branch=master https://codecov.io/gh/alexdlaird/pyngrok/branch/master/graph/badge.svg https://readthedocs.org/projects/pyngrok/badge/?version=latest https://img.shields.io/pypi/pyversions/pyngrok.svg https://img.shields.io/pypi/l/pyngrok.svg https://img.shields.io/twitter/url/http/shields.io.svg?style=social

pyngrok is a Python wrapper for ngrok that manages its own binary and puts it on your path, making ngrok readily available from anywhere on the command line and via a convenient Python API.

ngrok is a reverse proxy tool that opens secure tunnels from public URLs to localhost, perfect for exposing local web servers, building webhook integrations, enabling SSH access, testing chatbots, demoing from your own machine, and more, and its made even more powerful with native Python integration through pyngrok.

Installation

pyngrok is available on PyPI and can be installed using pip:

pip install pyngrok

or conda:

conda install -c conda-forge pyngrok

That’s it! pyngrok is now available as a package to our Python projects, and ngrok is now available from the command line.

Open a Tunnel

To open a tunnel, use the connect method, which returns the public URL generated by ngrok.

from pyngrok import ngrok

# Open a HTTP tunnel on the default port 80
public_url = ngrok.connect()
# Open a SSH tunnel
ssh_url = ngrok.connect(22, "tcp")

The connect method takes an optional options parameter, which allows us to pass additional properties that are supported by ngrok, as shown below.

Get Active Tunnels

It can be useful to ask the ngrok client what tunnels are currently open. This can be accomplished with the get_tunnels method, which returns a list of NgrokTunnel objects.

from pyngrok import ngrok

tunnels = ngrok.get_tunnels()
# A public ngrok URL that tunnels to port 80 (ex. http://<public_sub>.ngrok.io)
public_url = tunnels[0].public_url

Close a Tunnel

All open tunnels will automatically be closed when the Python process terminates, but we can also close them manually with disconnect.

from pyngrok import ngrok

public_url = "http://<public_sub>.ngrok.io"

ngrok.disconnect(public_url)

The ngrok Process

Opening a tunnel will start the ngrok process. This process will remain alive, and the tunnels open, until kill is invoked, or until the Python process terminates.

If we are building a short-lived app, for instance a CLI, we may want to block on the ngrok process so tunnels stay open until the user intervenes. We can do that by accessing the NgrokProcess.

from pyngrok import ngrok

ngrok_process = ngrok.get_ngrok_process()

try:
    # Block until CTRL-C or some other terminating event
    ngrok_process.proc.wait()
except KeyboardInterrupt:
    print(" Shutting down server.")

    ngrok.kill()

The NgrokProcess contains an api_url variable, usually initialized to http://127.0.0.1:4040, from which we can access the ngrok client API.

Note

If some feature we need is not available in this package, the client API is accessible to us via the api_request method. Additionally, the NgrokTunnel objects expose a uri variable, which contains the relative path used to manipulate that resource against the client API.

This package also gives us access to ngrok from the command line, as shown below.

Event Logs

When ngrok emits logs, pyngrok can surface them to a callback function. To register this callback, use PyngrokConfig and pass the function as log_event_callback. Each time a log is processed, this function will be called, passing a NgrokLog as its only parameter.

from pyngrok.ngrok import PyngrokConfig
from pyngrok import ngrok

def log_event_callback(log):
    print(str(log))

pyngrok_config = PyngrokConfig(log_event_callback=log_event_callback)

ngrok.connect(pyngrok_config=pyngrok_config)

If these events aren’t necessary for our use case, some resources can be freed up by turning them off.

Either use PyngrokConfig to not start the thread in the first place:

from pyngrok.ngrok import PyngrokConfig
from pyngrok import ngrok

pyngrok_config = PyngrokConfig(monitor_thread=False)

ngrok.connect(pyngrok_config=pyngrok_config)

or call the stop_monitor_thread method when we’re done using it:

import time

from pyngrok import ngrok

ngrok.connect()
time.sleep(1)
ngrok.get_ngrok_process().stop_monitor_thread()

Expose Other Services

Using ngrok we can expose any number of non-HTTP services, for instances databases, game servers, etc. This can be accomplished by using pyngrok to open a tcp tunnel to the desired service.

from pyngrok import ngrok

# Open a tunnel to MySQL with a Reserved TCP Address
ngrok.connect(3306, "tcp", options={"remote_addr": "1.tcp.ngrok.io:12345"})

We can also serve up local directories via ngrok’s built-in fileserver.

from pyngrok import ngrok

# Open a tunnel to a local file server
ngrok.connect("file:///")

Configuration

PyngrokConfig

pyngrok’s interactions with the ngrok binary (and other things) can be configured using PyngrokConfig. Most methods accept pyngrok_config as a keyword argument, and NgrokProcess will maintain a reference to its own PyngrokConfig once a process has been started. If pyngrok_config is not given, its documented defaults will be used.

The pyngrok_config argument is only used when the ngrok process is first started, which will only be the first time most methods in the ngrok module are called. You can check if a process is already or still running by calling its healthy method.

Note

If ngrok is not already installed at the ngrok_path in PyngrokConfig, it will be installed the first time most methods in the ngrok module are called.

If we need to customize the installation of ngrok, perhaps specifying a timeout, proxy, use a custom mirror for the download, etc. we can do so by leveraging the installer module. Keyword arguments in this module are ultimately passed down to urllib.request.urlopen, so as long as we use the installer module ourselves prior to invoking any ngrok methods, we can can control how ngrok is installed and from where.

Setting the authtoken

Running ngrok with an auth token enables additional features available on our account (for instance, the ability to open multiple tunnels concurrently). We can obtain our auth token from the ngrok dashboard and install it to ngrok’s config file like this:

from pyngrok import ngrok

ngrok.set_auth_token("<NGROK_AUTH_TOKEN>")

# Once an auth token is set, we are able to open multiple tunnels at the same time
ngrok.connect()
ngrok.connect(8000)

We can also override ngrok’s installed auth token using PyngrokConfig:

from pyngrok.conf import PyngrokConfig
from pyngrok import ngrok

pyngrok_config = PyngrokConfig(auth_token="<NGROK_AUTH_TOKEN>")

ngrok.connect(pyngrok_config=pyngrok_config)

Setting the region

By default, ngrok will open a tunnel in the us region. To override this, use the region parameter in PyngrokConfig:

from pyngrok.conf import PyngrokConfig
from pyngrok import ngrok

pyngrok_config = PyngrokConfig(region="au")

url = ngrok.connect(pyngrok_config=pyngrok_config)

Passing options

It is possible to configure the tunnel when it is created, for instance adding authentication, a subdomain, or other tunnel properties supported by ngrok. These can be passed to the tunnel with the options parameter.

Here is an example starting ngrok in Australia, then opening a tunnel with subdomain foo that requires basic authentication for requests.

from pyngrok.conf import PyngrokConfig
from pyngrok import ngrok

pyngrok_config = PyngrokConfig(region="au")

url = ngrok.connect(options={"subdomain": "foo", "auth": "username:password"}, pyngrok_config=pyngrok_config)

Config File

By default, ngrok will look for its config file in the home directory’s .ngrok2 folder. We can override this behavior in one of two ways.

Either use PyngrokConfig:

from pyngrok.conf import PyngrokConfig
from pyngrok import ngrok

pyngrok_config = PyngrokConfig(config_path="/opt/ngrok/config.yml")

ngrok.get_tunnels(pyngrok_config=pyngrok_config)

or override the default:

from pyngrok import ngrok, conf

conf.DEFAULT_PYNGROK_CONFIG.config_path = "/opt/ngrok/config.yml"

ngrok.get_tunnels()

Binary Path

The pyngrok package manages its own ngrok binary. However, we can use our ngrok binary if we want in one of two ways.

Either use PyngrokConfig:

from pyngrok.conf import PyngrokConfig
from pyngrok import ngrok

pyngrok_config = PyngrokConfig(ngrok_path="/usr/local/bin/ngrok")

ngrok.connect(pyngrok_config=pyngrok_config)

or override the default:

from pyngrok import ngrok, conf

conf.DEFAULT_PYNGROK_CONFIG.ngrok_path = "/usr/local/bin/ngrok"

ngrok.connect()

Command Line Usage

This package puts the default ngrok binary on our path, so all features of ngrok are also available on the command line.

ngrok http 80

For details on how to fully leverage ngrok from the command line, see ngrok’s official documentation.

Contributing

If you find issues, report them on GitHub.

If you would like to contribute to the code, the process is pretty simple:

  1. Familiarise yourself with this package, pyngrok’s APIs and other documentation, and ngrok’s documentation.
  2. Fork the repository on GitHub and start implementing changes.
  3. Write a test that plainly validates the changes made.
  4. Build and test locally with make local test
  5. Submit a pull requests to get the changes merged.

Also be sure to review the Code of Conduct before submitting issues or pull requests.

Want to contribute financially? If you’ve found pyngrok useful, a donation would also be greatly appreciated!