aboutsummaryrefslogtreecommitdiffstats
path: root/ttun_server
diff options
context:
space:
mode:
authorGravatar Tom van der Lee <tom@vanderlee.io>2026-09-04 17:09:24 +0200
committerGravatar Tom van der Lee <tom@vanderlee.io>2026-09-04 17:09:24 +0200
commit6b6f20a032eb31d747366c8bc270ca81731eaf6a (patch)
treeada48080495267bebde4ee1251bbbe77d629745f /ttun_server
parent2d22158dd95f76399f5716d556be60c77da04a46 (diff)
downloadserver-v3.tar.gz
server-v3.tar.bz2
server-v3.zip
Fixed pre-commit issuesv3
Diffstat (limited to 'ttun_server')
-rw-r--r--ttun_server/__init__.py19
-rw-r--r--ttun_server/endpoints.py2
-rw-r--r--ttun_server/redis.py4
-rw-r--r--ttun_server/types.py32
-rw-r--r--ttun_server/websockets.py60
5 files changed, 61 insertions, 56 deletions
diff --git a/ttun_server/__init__.py b/ttun_server/__init__.py
index 388dd9e..6e1eabf 100644
--- a/ttun_server/__init__.py
+++ b/ttun_server/__init__.py
@@ -3,7 +3,7 @@ import os
3 3
4from fastapi import FastAPI 4from fastapi import FastAPI
5from starlette.middleware.authentication import AuthenticationMiddleware 5from starlette.middleware.authentication import AuthenticationMiddleware
6from starlette.routing import Host, Route, WebSocketRoute, Mount 6from starlette.routing import Host, Mount, Route, WebSocketRoute
7 7
8from authentication.backend import BearerTokenAuthBackend 8from authentication.backend import BearerTokenAuthBackend
9from authentication.endpoints import authentication, oauth 9from authentication.endpoints import authentication, oauth
@@ -12,20 +12,20 @@ from proxy import app as proxy_app
12from ttun_server.endpoints import health 12from ttun_server.endpoints import health
13from ttun_server.websockets import tunnel 13from ttun_server.websockets import tunnel
14 14
15logging.basicConfig(level=getattr(logging, os.environ.get('LOG_LEVEL', 'INFO'))) 15logging.basicConfig(level=getattr(logging, os.environ.get("LOG_LEVEL", "INFO")))
16 16
17app = FastAPI( 17app = FastAPI(
18 debug=True, 18 debug=True,
19 routes=[ 19 routes=[
20 Mount('/management/', app=management), 20 Mount("/management/", app=management),
21 Mount('/auth/', app=authentication), 21 Mount("/auth/", app=authentication),
22 Mount('/oauth/', app=oauth), 22 Mount("/oauth/", app=oauth),
23 WebSocketRoute( 23 WebSocketRoute(
24 '/tunnel/', 24 "/tunnel/",
25 endpoint=tunnel, 25 endpoint=tunnel,
26 ), 26 ),
27 Route('/health/', endpoint=health), 27 Route("/health/", endpoint=health),
28 Host(f'{{subdomain}}.{os.environ['TUNNEL_DOMAIN']}', app=proxy_app) 28 Host(f"{{subdomain}}.{os.environ['TUNNEL_DOMAIN']}", app=proxy_app),
29 ], 29 ],
30) 30)
31 31
@@ -33,6 +33,7 @@ app.add_middleware(AuthenticationMiddleware, backend=BearerTokenAuthBackend())
33 33
34try: 34try:
35 from ._version import version 35 from ._version import version
36
36 __version__ = version 37 __version__ = version
37except ImportError: 38except ImportError:
38 __version__ = 'development' 39 __version__ = "development"
diff --git a/ttun_server/endpoints.py b/ttun_server/endpoints.py
index 61e3f21..e058009 100644
--- a/ttun_server/endpoints.py
+++ b/ttun_server/endpoints.py
@@ -1,2 +1,2 @@
1async def health(): 1async def health():
2 return 'OK' 2 return "OK"
diff --git a/ttun_server/redis.py b/ttun_server/redis.py
index fb9ff81..dabe940 100644
--- a/ttun_server/redis.py
+++ b/ttun_server/redis.py
@@ -9,5 +9,5 @@ class RedisConnectionPool:
9 @classmethod 9 @classmethod
10 def get_connection(cls) -> Redis: 10 def get_connection(cls) -> Redis:
11 if cls._pool is None: 11 if cls._pool is None:
12 cls._pool = ConnectionPool.from_url(os.environ.get('REDIS_URL')) 12 cls._pool = ConnectionPool.from_url(os.environ.get("REDIS_URL"))
13 return Redis(connection_pool=cls._pool) \ No newline at end of file 13 return Redis(connection_pool=cls._pool)
diff --git a/ttun_server/types.py b/ttun_server/types.py
index a0c22a3..33095ff 100644
--- a/ttun_server/types.py
+++ b/ttun_server/types.py
@@ -1,11 +1,11 @@
1from asyncio import Queue 1from asyncio import Queue
2from enum import Enum 2from enum import Enum
3from typing import TypedDict, Optional 3from typing import TypedDict
4 4
5 5
6class HttpMessageType(Enum): 6class HttpMessageType(Enum):
7 request = 'request' 7 request = "request"
8 response = 'response' 8 response = "response"
9 9
10 10
11class Config(TypedDict): 11class Config(TypedDict):
@@ -18,13 +18,13 @@ class HttpRequestData(TypedDict):
18 method: str 18 method: str
19 path: str 19 path: str
20 headers: list[tuple[str, str]] 20 headers: list[tuple[str, str]]
21 body: Optional[str] 21 body: str | None
22 22
23 23
24class HttpResponseData(TypedDict): 24class HttpResponseData(TypedDict):
25 status: int 25 status: int
26 headers: list[tuple[str, str]] 26 headers: list[tuple[str, str]]
27 body: Optional[str] 27 body: str | None
28 28
29 29
30class HttpMessage(TypedDict): 30class HttpMessage(TypedDict):
@@ -34,10 +34,10 @@ class HttpMessage(TypedDict):
34 34
35 35
36class WebsocketMessageType(Enum): 36class WebsocketMessageType(Enum):
37 connect = 'connect' 37 connect = "connect"
38 disconnect = 'disconnect' 38 disconnect = "disconnect"
39 message = 'message' 39 message = "message"
40 ack = 'ack' 40 ack = "ack"
41 41
42 42
43class WebsocketConnectData(TypedDict): 43class WebsocketConnectData(TypedDict):
@@ -50,7 +50,7 @@ class WebsocketDisconnectData(TypedDict):
50 50
51 51
52class WebsocketMessageData(TypedDict): 52class WebsocketMessageData(TypedDict):
53 body: Optional[str] 53 body: str | None
54 54
55 55
56class WebsocketMessage(TypedDict): 56class WebsocketMessage(TypedDict):
@@ -60,13 +60,13 @@ class WebsocketMessage(TypedDict):
60 60
61 61
62class MessageType(Enum): 62class MessageType(Enum):
63 request = 'request' 63 request = "request"
64 response = 'response' 64 response = "response"
65 65
66 ws_connect = 'connect' 66 ws_connect = "connect"
67 ws_disconnect = 'disconnect' 67 ws_disconnect = "disconnect"
68 ws_message = 'message' 68 ws_message = "message"
69 ws_ack = 'ack' 69 ws_ack = "ack"
70 70
71 71
72Message = HttpMessage | WebsocketMessage 72Message = HttpMessage | WebsocketMessage
diff --git a/ttun_server/websockets.py b/ttun_server/websockets.py
index 88d6cac..a3cbc5f 100644
--- a/ttun_server/websockets.py
+++ b/ttun_server/websockets.py
@@ -16,24 +16,25 @@ from ttun_server.types import (
16) 16)
17 17
18logger = logging.getLogger(__name__) 18logger = logging.getLogger(__name__)
19logger.setLevel('DEBUG') 19logger.setLevel("DEBUG")
20
20 21
21async def assert_compatible_version(websocket: WebSocket, config: Config) -> None: 22async def assert_compatible_version(websocket: WebSocket, config: Config) -> None:
22 client_version = config.get('version', '1.0.0') 23 client_version = config.get("version", "1.0.0")
23 logger.debug('client_version %s', client_version) 24 logger.debug("client_version %s", client_version)
24 25
25 if 'git' not in client_version and ttun_server.__version__ != 'development': 26 if "git" not in client_version and ttun_server.__version__ != "development":
26 [client_major, *_] = [int(i) for i in client_version.split('.')[:3]] 27 [client_major, *_] = [int(i) for i in client_version.split(".")[:3]]
27 [server_major, *_] = [int(i) for i in ttun_server.__version__.split('.')] 28 [server_major, *_] = [int(i) for i in ttun_server.__version__.split(".")]
28 29
29 if client_major < server_major: 30 if client_major < server_major:
30 await websocket.close(4000, 'Your client is too old') 31 await websocket.close(4000, "Your client is too old")
31 32
32 if client_major > server_major: 33 if client_major > server_major:
33 await websocket.close(4001, 'Your client is too new') 34 await websocket.close(4001, "Your client is too new")
34 35
35 36
36@requires(['authenticated', 'token'] if conf.ENABLE_AUTH else []) 37@requires(["authenticated", "token"] if conf.ENABLE_AUTH else [])
37async def tunnel(websocket: WebSocket) -> None: 38async def tunnel(websocket: WebSocket) -> None:
38 request_tasks: dict[str, asyncio.Task] = {} 39 request_tasks: dict[str, asyncio.Task] = {}
39 proxy_queues: dict[str, ProxyQueue] = {} 40 proxy_queues: dict[str, ProxyQueue] = {}
@@ -43,36 +44,39 @@ async def tunnel(websocket: WebSocket) -> None:
43 44
44 await assert_compatible_version(websocket, config) 45 await assert_compatible_version(websocket, config)
45 46
46 if 'subdomains' not in config: 47 if "subdomains" not in config:
47 config['subdomains'] = [config['subdomain']] 48 config["subdomains"] = [config["subdomain"]]
48 elif config['subdomains'] is None: 49 elif config["subdomains"] is None:
49 config['subdomains'] = [None] 50 config["subdomains"] = [None]
50 51
51 for i, subdomain in enumerate(config['subdomains']): 52 for i, subdomain in enumerate(config["subdomains"]):
52 if subdomain is None or await ProxyQueue.has_connection(subdomain): 53 if subdomain is None or await ProxyQueue.has_connection(subdomain):
53 config['subdomains'][i] = uuid4().hex 54 config["subdomains"][i] = uuid4().hex
54 55
55 for subdomain in config['subdomains']: 56 for subdomain in config["subdomains"]:
56 proxy_queues[subdomain] = await ProxyQueue.create_for_identifier(subdomain) 57 proxy_queues[subdomain] = await ProxyQueue.create_for_identifier(subdomain)
57 58
58 hostname = os.environ.get('TUNNEL_DOMAIN') 59 hostname = os.environ.get("TUNNEL_DOMAIN")
59 protocol = 'https' if os.environ.get('SECURE', False) else 'http' 60 protocol = "https" if os.environ.get("SECURE") else "http"
60 61
61 urls = [ 62 urls = [
62 f'{protocol}://{subdomain}.{hostname}' 63 f"{protocol}://{subdomain}.{hostname}" for subdomain in config["subdomains"]
63 for subdomain in config['subdomains']
64 ] 64 ]
65 65
66 await websocket.send_json({ 66 await websocket.send_json(
67 'url': urls[0], 67 {
68 'urls': urls, 68 "url": urls[0],
69 }) 69 "urls": urls,
70 }
71 )
70 72
71 async def handle_requests(subdomain: str) -> None: 73 async def handle_requests(subdomain: str) -> None:
72 while request := await proxy_queues[subdomain].dequeue(): 74 while request := await proxy_queues[subdomain].dequeue():
73 asyncio.create_task(websocket.send_json(request), name=request['identifier']) 75 asyncio.create_task(
76 websocket.send_json(request), name=request["identifier"]
77 )
74 78
75 for subdomain in config['subdomains']: 79 for subdomain in config["subdomains"]:
76 request_tasks[subdomain] = asyncio.create_task( 80 request_tasks[subdomain] = asyncio.create_task(
77 handle_requests(subdomain), name=subdomain 81 handle_requests(subdomain), name=subdomain
78 ) 82 )
@@ -81,8 +85,8 @@ async def tunnel(websocket: WebSocket) -> None:
81 while True: 85 while True:
82 data: Message = await websocket.receive_json() 86 data: Message = await websocket.receive_json()
83 try: 87 try:
84 data['type'] = MessageType(data['type']).value 88 data["type"] = MessageType(data["type"]).value
85 response_queue = await ProxyQueue.get_for_identifier(data['identifier']) 89 response_queue = await ProxyQueue.get_for_identifier(data["identifier"])
86 await response_queue.enqueue(data) 90 await response_queue.enqueue(data)
87 except AssertionError: 91 except AssertionError:
88 pass 92 pass