GoodCam Device Proxy

Documentation Status

This library simplifies creating HTTP proxies that can be used to communicate with GoodCam devices in various networks. GoodCam devices contain a built-in client that can be configured to connect automatically to a given proxy. Once connected, the devices will wait for incoming HTTP requests. The proxy simply forwards incoming HTTP requests to the connected devices.

Installation

This library is just a wrapper over the Rust version of the same library. You can install this library using pip:

pip install gcdevproxy

If there is no binary wheel available, pip will try to build also the underlying Rust library. You will need the Rust compiler installed on your system to do this. You can install it easily using:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

See https://www.rust-lang.org/tools/install for more information.

Even though the Rust version of this library can be built for any platform supported by Rust, this Python wrapper is currently available only for Linux systems.

The Rust version of this library also requires OpenSSL (version 1.0.1 or newer) or LibreSSL (version 2.5 or newer). Ubuntu/Debian users can install OpenSSL development files using:

sudo apt-get install libssl-dev

Fedora users can install them using:

sudo dnf install openssl-devel

Usage example

The library supports both blocking and asynchronous API, though the asynchronous API should be preferred due to a better performance. To use the asynchronous API, simply use the create_proxy and RequestHandler equivalents from the gcdevproxy.aio module.

Please keep in mind that when using the blocking API, your request handler MUST be thread-safe! The proxy runtime may call your handler from multiple threads at the same time. You don’t have to worry about this when using the asynchronous API because your handler will be called only from the thread running the Python’s asyncio event loop (usually the main thread).

Asynchronous API

from gcdevproxy.aio import RequestHandler

...

class MyRequestHandler(RequestHandler):
    async def handle_device_request(self, authorization: Authorization) -> 'DeviceHandlerResult':
        ...

    async def handle_client_request(self, request: Request) -> 'ClientHandlerResult':
        ...

async def main():
    config = ProxyConfig()
    config.http_bindings = [('0.0.0.0', 8080)]
    config.request_handler = MyRequestHandler()

    proxy = await gcdevproxy.aio.create_proxy(config)

    await proxy.run()

if __name__ == '__main__':
    asyncio.run(main())

Blocking API

from gcdevproxy import RequestHandler

...

class MyRequestHandler(RequestHandler):
    def handle_device_request(self, authorization: Authorization) -> 'DeviceHandlerResult':
        ...

    def handle_client_request(self, request: Request) -> 'ClientHandlerResult':
        ...

def main():
    config = ProxyConfig()
    config.http_bindings = [('0.0.0.0', 8080)]
    config.request_handler = MyRequestHandler()

    proxy = gcdevproxy.create_proxy(config)

    proxy.run()

if __name__ == '__main__':
    main()

More examples

See the examples directory in the root of this repository for ready-to-use examples.

Reference documentation

gcdevproxy.create_proxy(config: ProxyConfig) Proxy

Create a new instance of the device proxy from a given configuration.

Parameters

config – proxy configuration

Returns

proxy handle

async gcdevproxy.aio.create_proxy(config: ProxyConfig) Proxy

Create a new instance of the device proxy from a given configuration.

Parameters

config – proxy configuration

Returns

proxy handle

class gcdevproxy.ProxyConfig

Device proxy configuration.

request_handler

Your request handler.

It must be a subclass of either gcdevproxy.RequestHandler or gcdevproxy.aio.RequestHandler.

Type

gcdevproxy.RequestHandler, gcdevproxy.aio.RequestHandler

private_key

TLS private key in PEM format (used for HTTPS).

Type

bytes

Value

None

cert_chain

Certificate chain in PEM format (used for HTTPS).

Type

bytes

Value

None

lets_encrypt

Generate the TLS private key and the corresponding certificate chain automatically using Let’s Encrypt.

Please note that Let’s Encrypt requires the proxy to be available on a public domain name and the HTTP server must be available on TCP port 80. Make sure that you set the hostname attribute to the corresponding domain name and that you add at least the ('0.0.0.0', 80) binding to http_bindings.

Type

bool

Value

False

hostname

Hostname where your proxy is available.

Type

str

Value

‘localhost’

http_bindings

HTTP bindings where your proxy will be available. For example:

config.http_bindings = [
    ('0.0.0.0', 80),
    ('::', 80),
]
Type

List[Tuple[str, int]]

Value

[]

https_bindings

HTTPS bindings where your proxy will be available. For example:

config.http_bindings = [
    ('0.0.0.0', 443),
    ('::', 443),
]
Type

List[Tuple[str, int]]

Value

[]

__init__() None

Create a new proxy configuration.

class gcdevproxy.RequestHandler

Base class for request handlers.

handle_client_request(request: Request) ClientHandlerResult

Handle a given client requests.

The method is responsible for authentication of a given client request. It is called every time a client is attempting to send an HTTP request to a GoodCam device. The implementation should verify the client identity and permission to access a given device. It is also responsible for extracting the target device ID from the request.

The method must return an instance of one of the following classes:

  • ForwardRequest - to forward the client request to a given device

  • BlockRequest - to block the client request and return a given response instead

Parameters

request – client request

Returns

any subclass of ClientHandlerResult

handle_device_request(authorization: Authorization) DeviceHandlerResult

Handle a given device request.

The method is responsible for device authentication and (optionally) load balancing. It is called every time a GoodCam device connects to the proxy. The implementation should check the device ID and key in the authorization object.

The method must return an instance of one of the following classes:

  • AcceptDevice - to accept the device connection

  • UnauthorizedDevice - to reject the device connection

  • RedirectDevice - to redirect the device to another proxy

Parameters

authorization – device authorization object containing the device ID and key

Returns

any subclass of DeviceHandlerResult

class gcdevproxy.aio.RequestHandler

Base class for asynchronous request handlers.

async handle_client_request(request: Request) ClientHandlerResult

Handle a given client requests.

The method is responsible for authentication of a given client request. It is called every time a client is attempting to send an HTTP request to a GoodCam device. The implementation should verify the client identity and permission to access a given device. It is also responsible for extracting the target device ID from the request.

The method must return an instance of one of the following classes:

  • ForwardRequest - to forward the client request to a given device

  • BlockRequest - to block the client request and return a given response instead

The method is asynchronous. Do not use any blocking calls here! Always use the asynchronous alternatives (e.g. use aiohttp instead of requests).

Parameters

request – client request

Returns

any subclass of ClientHandlerResult

async handle_device_request(authorization: Authorization) DeviceHandlerResult

Handle a given device request.

The method is responsible for device authentication and (optionally) load balancing. It is called every time a GoodCam device connects to the proxy. The implementation should check the device ID and key in the authorization object.

The method must return an instance of one of the following classes:

  • AcceptDevice - to accept the device connection

  • UnauthorizedDevice - to reject the device connection

  • RedirectDevice - to redirect the device to another proxy

The method is asynchronous. Do not use any blocking calls here! Always use the asynchronous alternatives (e.g. use aiohttp instead of requests).

Parameters

authorization – device authorization object containing the device ID and key

Returns

any subclass of DeviceHandlerResult

class gcdevproxy.Proxy

Device proxy handle.

run() None

Run the proxy.

This method will keep the current thread busy until the proxy stops or until the KeyboardInterrupt exception occurs.

stop(timeout: float) None

Stop the proxy and wait until the background threads stop.

If stopping the proxy takes more than a given amount of time, the proxy execution will be aborted.

Parameters

timeout – maximum amount of time (in seconds) to wait until the proxy stops

class gcdevproxy.aio.Proxy

Device proxy handle.

async run() None

Run the proxy.

This method will keep the current task busy until the proxy stops.

stop(timeout: float) None

Stop the proxy.

This method only signals the proxy to stop. It does not wait until the proxy actually stops. Use the run method to join the background threads.

If stopping the proxy takes more than a given amount of time, the proxy execution will be aborted.

Parameters

timeout – maximum amount of time (in seconds) to wait until the proxy stops

class gcdevproxy.Authorization

Device authorization.

property device_id: str

Device ID

property device_key: str

Device key

class gcdevproxy.Request

Client request.

uri

Request URI.

Type

str

method

HTTP method.

Type

str

headers

HTTP headers.

Type

List[Tuple[str, str]]

get_header_value(name: str) Optional[str]

Get value of a given HTTP header (if present).

Parameters

name – name of the header field

Returns

value of the first HTTP header field with a given name (ignoring case) or None

class gcdevproxy.Response

Custom client response.

status_code

HTTP status code.

Type

int

headers

HTTP headers.

Type

List[Tuple[str, str]]

body

HTTP response body. The body must be a bytes object.

Type

bytes

__init__(status_code: int, headers: Optional[List[Tuple[str, str]]] = None, body: Optional[bytes] = None) None

Create a new response with empty body and a given status code.

Parameters
  • status_code – HTTP status code

  • body – response body

  • headers – response headers

append_header(name: str, value: str) None

Append a given HTTP header.

Parameters
  • name – header name

  • value – header value

set_header(name: str, value: str) None

Replace all HTTP headers with a given name (ignoring case).

Parameters
  • name – header name

  • value – header value

class gcdevproxy.DeviceHandlerResult

Base class for all possible device handler results.

class gcdevproxy.AcceptDevice

This result will tell the proxy to accept connection from a given device.

class gcdevproxy.UnauthorizedDevice

This result will tell the proxy to reject connection from a given device.

class gcdevproxy.RedirectDevice

This result will tell the proxy to redirect the corresponding device to a given location.

__init__(location: str) None

Create a new redirect result.

Parameters

location – redirect location

class gcdevproxy.ClientHandlerResult

Base class for all possible client handler results.

class gcdevproxy.ForwardRequest

This result will tell the proxy to forward a given client request to device with a given ID.

__init__(device_id: str, request: Request) None

Create a new forward request result.

Parameters
  • device_id – target device ID

  • request – client request

class gcdevproxy.BlockRequest

This result will tell the proxy to block the original client request and return a given response instead.

__init__(response: Response) None

Create a new block request result.

Parameters

response – response to be returned to the client

Indices and tables