Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a py sdk example for a proxy backend #847

Merged
merged 9 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
3 changes: 1 addition & 2 deletions sdk/python/sdk/zrok/zrok/overview.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from zrok_api.models.environment_and_resources import EnvironmentAndResources
from zrok_api.models.frontends import Frontends
from zrok_api.models.share import Share
from zrok_api.models.shares import Shares


@dataclass
Expand Down Expand Up @@ -48,7 +47,7 @@ def create(cls, root: Root) -> 'Overview':
created_at=env_dict.get('createdAt'),
updated_at=env_dict.get('updatedAt')
)

# Create Shares object from share data
share_list = []
for share_data in env_data.get('shares', []):
Expand Down
Loading