GoodCam Device Proxy
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
orgcdevproxy.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 tohttp_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 deviceBlockRequest
- 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 connectionUnauthorizedDevice
- to reject the device connectionRedirectDevice
- 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 deviceBlockRequest
- 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 connectionUnauthorizedDevice
- to reject the device connectionRedirectDevice
- 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.