Skip to content

Commit

Permalink
Merge pull request #847 from openziti/py-sdk-example-proxy
Browse files Browse the repository at this point in the history
add a py sdk example for a proxy backend
  • Loading branch information
qrkourier authored Jan 29, 2025
2 parents f27ab0a + c471aef commit 621dfab
Show file tree
Hide file tree
Showing 6 changed files with 423 additions and 1 deletion.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@

CHANGE: Add usage hint in `zrok config get --help` to clarify how to list all valid `configName` and their current values by running `zrok status`.

CHANGE: The Python SDK's `Overview()` function was refactored as a class method (https://github.com/openziti/zrok/pull/846).

FEATURE: The Python SDK now includes a `ProxyShare` class providing an HTTP proxy for public and private shares and a
Jupyter notebook example (https://github.com/openziti/zrok/pull/847).

## v0.4.46

FEATURE: Linux service template for systemd user units (https://github.com/openziti/zrok/pull/818)
Expand Down
48 changes: 48 additions & 0 deletions sdk/python/examples/proxy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

# zrok Python Proxy Example

This demonstrates using the ProxyShare class to forward requests from the public frontend to a target URL.

## Run the Example

```bash
LOG_LEVEL=INFO python ./proxy.py http://127.0.0.1:3000
```

Expected output:

```txt
2025-01-29 06:37:00,884 - __main__ - INFO - === Starting proxy server ===
2025-01-29 06:37:00,884 - __main__ - INFO - Target URL: http://127.0.0.1:3000
2025-01-29 06:37:01,252 - __main__ - INFO - Access proxy at: https://24x0pq7s6jr0.zrok.example.com:443
2025-01-29 06:37:07,981 - zrok.proxy - INFO - Share 24x0pq7s6jr0 released
```

## Basic Usage

```python
from zrok.proxy import ProxyShare
import zrok

# Load the environment
root = zrok.environment.root.Load()

# Create a temporary proxy share (will be cleaned up on exit)
proxy = ProxyShare.create(root=root, target="http://my-target-service")

# Access the proxy's endpoints and token
print(f"Access proxy at: {proxy.endpoints}")
proxy.run()
```

## Creating a Reserved Proxy Share

To create a share token that persists and can be reused, run the example `proxy.py --unique-name my-persistent-proxy`. If the unique name already exists it will be reused. Here's how it works:

```python
proxy = ProxyShare.create(
root=root,
target="http://127.0.0.1:3000",
unique_name="my-persistent-proxy"
)
```
100 changes: 100 additions & 0 deletions sdk/python/examples/proxy/proxy.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"id": "52d42237",
"metadata": {},
"outputs": [],
"source": [
"! pip install zrok"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0a33915c",
"metadata": {},
"outputs": [],
"source": [
"\n",
"import zrok\n",
"from zrok.proxy import ProxyShare\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "0db6b615",
"metadata": {},
"outputs": [],
"source": [
"\n",
"target_url = \"http://127.0.0.1:8000/\"\n",
"unique_name = \"myuniquename\" # a name to reuse each run or 'None' for random\n",
"share_mode = \"public\" # \"public\" or \"private\"\n",
"frontend = \"public\" # custom domain frontend or \"public\"\n",
"\n",
"if unique_name.lower() == \"none\":\n",
" unique_name = None\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "d3efcfa5",
"metadata": {},
"outputs": [],
"source": [
"\n",
"zrok_env = zrok.environment.root.Load() # Load the environment from ~/.zrok\n",
"\n",
"proxy_share = ProxyShare.create(\n",
" root=zrok_env,\n",
" target=target_url,\n",
" frontends=[frontend],\n",
" share_mode=share_mode,\n",
" unique_name=unique_name,\n",
" verify_ssl=True # Set 'False' to skip SSL verification\n",
")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "21966557",
"metadata": {},
"outputs": [],
"source": [
"\n",
"if share_mode == \"public\":\n",
" print(f\"Access proxy at: {', '.join(proxy_share.endpoints)}\")\n",
"elif share_mode == \"private\":\n",
" print(f\"Run a private access frontend: 'zrok access private {proxy_share.token}'\")\n",
"\n",
"proxy_share.run()\n"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
62 changes: 62 additions & 0 deletions sdk/python/examples/proxy/proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
#!/usr/bin/env python3

"""
Example of using zrok's proxy facility to create an HTTP proxy server.
This example demonstrates how to:
1. Create a proxy share (optionally with a unique name for persistence)
2. Handle HTTP requests/responses through the proxy
3. Automatically clean up non-reserved shares on exit
"""

import argparse
import logging

import zrok
from zrok.proxy import ProxyShare

# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)


def main():
"""Main entry point."""
parser = argparse.ArgumentParser(description='Start a zrok proxy server')
parser.add_argument('target_url', help='Target URL to proxy requests to')
parser.add_argument('-n', '--unique-name', help='Unique name for the proxy instance')
parser.add_argument('-f', '--frontends', nargs='+', help='One or more space-separated frontends to use')
parser.add_argument('-k', '--insecure', action='store_false', dest='verify_ssl', default=True,
help='Skip SSL verification')
parser.add_argument('-s', '--share-mode', default='public', choices=['public', 'private'],
help='Share mode (default: public)')
args = parser.parse_args()

logger.info("=== Starting proxy server ===")
logger.info(f"Target URL: {args.target_url}")
logger.info(f"Share mode: {args.share_mode}")

# Load environment and create proxy share
root = zrok.environment.root.Load()
proxy_share = ProxyShare.create(
root=root,
target=args.target_url,
share_mode=args.share_mode,
unique_name=args.unique_name,
frontends=args.frontends,
verify_ssl=args.verify_ssl
)

# Log access information and start the proxy
if args.share_mode == "public":
logger.info(f"Access proxy at: {', '.join(proxy_share.endpoints)}")
elif args.share_mode == "private":
logger.info(f"Run a private access frontend: 'zrok access private {proxy_share.token}'")
proxy_share.run()


if __name__ == '__main__':
main()
6 changes: 5 additions & 1 deletion sdk/python/sdk/zrok/zrok/environment/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class Metadata:
@dataclass
class Config:
ApiEndpoint: str = ""
DefaultFrontend: str = ""


@dataclass
Expand Down Expand Up @@ -137,7 +138,10 @@ def __loadConfig() -> Config:
cf = configFile()
with open(cf) as f:
data = json.load(f)
return Config(ApiEndpoint=data["api_endpoint"])
return Config(
ApiEndpoint=data["api_endpoint"],
DefaultFrontend=data["default_frontend"]
)


def isEnabled() -> bool:
Expand Down
Loading

0 comments on commit 621dfab

Please sign in to comment.