.. rst-class:: hide-header

************************************
pyngrok - a Python wrapper for ngrok
************************************

.. image:: _html/logo.png
   :alt: pyngrok - a Python wrapper for ngrok
   :align: center

.. image:: https://badge.fury.io/py/pyngrok.svg
   :target: https://badge.fury.io/py/pyngrok
.. image:: https://travis-ci.com/alexdlaird/pyngrok.svg?branch=master
   :target: https://travis-ci.com/alexdlaird/pyngrok
.. image:: https://codecov.io/gh/alexdlaird/pyngrok/branch/master/graph/badge.svg
   :target: https://codecov.io/gh/alexdlaird/pyngrok
.. image:: https://readthedocs.org/projects/pyngrok/badge/?version=latest
   :target: https://pyngrok.readthedocs.io/en/latest/?badge=latest
.. image:: https://img.shields.io/pypi/pyversions/pyngrok.svg
   :target: https://pypi.org/project/pyngrok/
.. image:: https://img.shields.io/pypi/l/pyngrok.svg
   :target: https://pypi.org/project/pyngrok/
.. image:: https://img.shields.io/twitter/url/http/shields.io.svg?style=social
   :target: https://twitter.com/intent/tweet?text=Check+out+%23pyngrok%2C+a+Python+wrapper+for+%23ngrok+that+lets+you+programmatically+open+secure+%23tunnels+to+local+web+servers%2C+build+%23webhook+integrations%2C+enable+SSH+access%2C+test+chatbots%2C+demo+from+your+own+machine%2C+and+more.%0D%0A%0D%0A&url=https://github.com/alexdlaird/pyngrok&via=alexdlaird

``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 <https://ngrok.com>`_ 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 <https://pypi.org/project/pyngrok/>`_ and can be installed
using ``pip``:

.. code-block:: sh

    pip install pyngrok

or ``conda``:

.. code-block:: sh

    conda install -c conda-forge pyngrok

That's it! ``pyngrok`` is now available `as a package to our Python projects <#open-a-tunnel>`_,
and ``ngrok`` is now available `from the command line <#command-line-usage>`_.

Open a Tunnel
=============

To open a tunnel, use the :func:`~pyngrok.ngrok.connect` method, which returns a :class:`~pyngrok.ngrok.NgrokTunnel`,
and this returned object has a reference to the public URL generated by ``ngrok`` in its ``public_url`` attribute.

.. code-block:: python

    from pyngrok import ngrok

    # Open a HTTP tunnel on the default port 80
    # <NgrokTunnel: "http://<public_sub>.ngrok.io" -> "http://localhost:80">
    http_tunnel = ngrok.connect()
    # Open a SSH tunnel
    # <NgrokTunnel: "tcp://0.tcp.ngrok.io:12345" -> "localhost:22">
    ssh_tunnel = ngrok.connect(22, "tcp")

The :func:`~pyngrok.ngrok.connect` method takes ``kwargs`` as well, which allows us to pass
additional properties that are `supported by ngrok <https://ngrok.com/docs#tunnel-definitions>`_,
`as shown below <#passing-options-as-kwargs>`__.

.. note::

    ``ngrok``'s default behavior for ``http`` when no additional properties are passed is to open *two* tunnels,
    one ``http`` and one ``https``. ``pyngrok``'s :func:`~pyngrok.ngrok.connect` method will return a reference to
    the ``http`` tunnel in this case. If only a single tunnel is needed, pass ``bind_tls=True``.

Get Active Tunnels
==================

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

.. code-block:: python

    from pyngrok import ngrok

    # [<NgrokTunnel: "http://<public_sub>.ngrok.io" -> "http://localhost:80">]
    tunnels = ngrok.get_tunnels()

Close a Tunnel
==============

All open tunnels will automatically be closed when the Python process terminates, but we can
also close them manually with :func:`~pyngrok.ngrok.disconnect`.

.. code-block:: python

    from pyngrok import ngrok

    # The NgrokTunnel returned from methods like connect(),
    # get_tunnels(), etc. contains the public URL
    ngrok.disconnect(ngrok_tunnel.public_url)

The ``ngrok`` Process
=====================

Opening a tunnel will start the ``ngrok`` process. This process will remain alive, and the tunnels
open, until :func:`~pyngrok.ngrok.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
:class:`~pyngrok.process.NgrokProcess`.

.. code-block:: python

    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 :class:`~pyngrok.process.NgrokProcess` contains an ``api_url`` variable, usually initialized to
http://127.0.0.1:4040, from which we can access the `ngrok client API <https://ngrok.com/docs#client-api>`_.

.. note::

    If some feature we need is not available in this package, the client API is accessible to us via the
    :func:`~pyngrok.ngrok.api_request` method. Additionally, the :class:`~pyngrok.ngrok.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 <#command-line-usage>`__.

Event Logs
==========

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

.. code-block:: python

    from pyngrok import conf, ngrok

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

    conf.get_default().log_event_callback = log_event_callback

    # <NgrokTunnel: "http://<public_sub>.ngrok.io" -> "http://localhost:80">
    ngrok_tunnel = ngrok.connect()

If these events aren't necessary for our use case, some resources can be freed up by turning them off. Set
``monitor_thread`` to ``False`` in :class:`~pyngrok.conf.PyngrokConfig`:

.. code-block:: python

    from pyngrok import conf, ngrok

    conf.get_default().monitor_thread = False

    # <NgrokTunnel: "http://<public_sub>.ngrok.io" -> "http://localhost:80">
    ngrok_tunnel = ngrok.connect()

Alternatively, :func:`~pyngrok.process.NgrokProcess.stop_monitor_thread` can be used to stop monitoring on a
running process:

.. code-block:: python

    import time

    from pyngrok import ngrok

    # <NgrokTunnel: "http://<public_sub>.ngrok.io" -> "http://localhost:80">
    ngrok_tunnel = 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.

.. code-block:: python

    from pyngrok import ngrok

    # Open a tunnel to MySQL with a Reserved TCP Address
    # <NgrokTunnel: "tcp://1.tcp.ngrok.io:12345" -> "localhost:3306">
    ngrok.connect(3306, "tcp",
                  remote_addr="1.tcp.ngrok.io:12345")


We can also serve up local directories via `ngrok's built-in fileserver <https://ngrok.com/docs#http-file-urls>`_.

.. code-block:: python

    from pyngrok import ngrok

    # Open a tunnel to a local file server
    # <NgrokTunnel: "http://<public_sub>.ngrok.io" -> "file:///">
    ngrok.connect("file:///")


Configuration
=============

``PyngrokConfig``
-----------------

``pyngrok``'s interactions with the ``ngrok`` binary can be configured using :class:`~pyngrok.conf.PyngrokConfig`.
The default ``pyngrok_config`` object can updated with our own object using :func:`~pyngrok.conf.set_default`:

.. code-block:: python

    from pyngrok import conf

    pyngrok_config = conf.PyngrokConfig(log_event_callback=log_event_callback,
                                        max_logs=10)
    conf.set_default(pyngrok_config)


Most methods in the :mod:`~pyngrok.ngrok` module also accept a ``pyngrok_config`` keyword arg, which can be used
to pass in the config rather than updating the default as shown above.

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

.. note::

    If ``ngrok`` is not already installed at the ``ngrok_path`` in :class:`~pyngrok.conf.PyngrokConfig`, it
    will be installed the first time most methods in the :mod:`~pyngrok.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 :mod:`~pyngrok.installer` module. Keyword arguments in this
    module are ultimately passed down to :py:func:`urllib.request.urlopen`, so as long as we use the
    :mod:`~pyngrok.installer` module ourselves prior to invoking any :mod:`~pyngrok.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 <https://dashboard.ngrok.com>`_ and install it to ``ngrok``'s config file like this:

.. code-block:: python

    from pyngrok import ngrok

    # Setting an auth token allows us to open multiple
    # tunnels at the same time
    ngrok.set_auth_token("<NGROK_AUTH_TOKEN>")

    # <NgrokTunnel: "http://<public_sub1>.ngrok.io" -> "http://localhost:80">
    ngrok_tunnel1 = ngrok.connect()
    # <NgrokTunnel: "http://<public_sub2>.ngrok.io" -> "http://localhost:8000">
    ngrok_tunnel2 = ngrok.connect(8000)

We can also override ``ngrok``'s installed auth token using :class:`~pyngrok.conf.PyngrokConfig`:

.. code-block:: python

    from pyngrok import conf, ngrok

    conf.get_default().auth_token = "<NGROK_AUTH_TOKEN>"

    # <NgrokTunnel: "http://<public_sub>.ngrok.io" -> "http://localhost:80">
    ngrok_tunnel = ngrok.connect()

Setting the ``region``
----------------------

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

.. code-block:: python

    from pyngrok import conf, ngrok

    conf.get_default().region = "au"

    # <NgrokTunnel: "http://<public_sub>.au.ngrok.io" -> "http://localhost:80">
    ngrok_tunnel = ngrok.connect()

Passing ``options`` as ``kwargs``
---------------------------------

It is possible to configure the tunnel when it is created, for instance adding authentication,
a subdomain, or other tunnel properties `supported by ngrok <https://ngrok.com/docs#tunnel-definitions>`_.
This is accomplished by passing these ``options`` as additional ``kwargs`` to :func:`~pyngrok.ngrok.connect`,
then they will be used as properties for the tunnel when it is created.

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

.. code-block:: python

    from pyngrok import conf, ngrok

    conf.get_default().region = "au"

    # <NgrokTunnel: "http://foo.au.ngrok.io" -> "http://localhost:80">
    ngrok_tunnel = ngrok.connect(subdomain="foo",
                                 auth="username:password")

Config File
-----------

By default, `ngrok will look for its config file <https://ngrok.com/docs#config>`_ in the home directory's ``.ngrok2``
folder. We can override this behavior by updating our default :class:`~pyngrok.conf.PyngrokConfig`:

.. code-block:: python

    from pyngrok import conf, ngrok

    conf.get_default().config_path = "/opt/ngrok/config.yml"

    # <NgrokTunnel: "http://<public_sub>.ngrok.io" -> "http://localhost:80">
    ngrok_tunnel = ngrok.connect()

Binary Path
-----------

The ``pyngrok`` package manages its own ``ngrok`` binary. We can use our ``ngrok`` binary if we
want by updating the default :class:`~pyngrok.conf.PyngrokConfig`:

.. code-block:: python

    from pyngrok import conf, ngrok

    conf.get_default().ngrok_path = "/usr/local/bin/ngrok"

    # <NgrokTunnel: "http://<public_sub>.ngrok.io" -> "http://localhost:80">
    ngrok_tunnel = 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.

.. code-block:: sh

    ngrok http 80

For details on how to fully leverage ``ngrok`` from the command line, see `ngrok's official documentation <https://ngrok.com/docs>`_.

Python 2.7
==========

The last version of ``pyngrok`` that supports Python 2.7 is 4.1.x, so we need to pin ``pyngrok>=4.1,<4.2`` if we still
want to use ``pyngrok`` with this version of Python. Its legacy documentation can be found `here <https://pyngrok.readthedocs.io/en/4.1.x/>`_.

Dive Deeper
===========

For more advanced usage, integration examples, and tips to troubleshoot common issues, dive deeper in to the rest of
the documentation.

.. toctree::
   :maxdepth: 2

   api
   integrations
   troubleshooting

.. include:: ../CONTRIBUTING.rst
