summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--setup.cfg2
-rw-r--r--ttun/__init__.py5
-rw-r--r--ttun/__main__.py6
-rw-r--r--ttun/client.py47
-rw-r--r--ttun/types.py14
5 files changed, 61 insertions, 13 deletions
diff --git a/setup.cfg b/setup.cfg
index 8a52ffc..d58f78f 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,7 +1,7 @@
1[metadata] 1[metadata]
2name = ttun 2name = ttun
3author = Tom van der Lee 3author = Tom van der Lee
4description = Expose a localport via your selfhosted TTUN Server 4description = Expose a local port via your selfhosted TTUN Server
5long_description = file: README.rst 5long_description = file: README.rst
6license = BSD-3-Clause 6license = BSD-3-Clause
7 7
diff --git a/ttun/__init__.py b/ttun/__init__.py
index e69de29..d6256a0 100644
--- a/ttun/__init__.py
+++ b/ttun/__init__.py
@@ -0,0 +1,5 @@
1from importlib.metadata import version
2
3
4__version__ = version('ttun')
5
diff --git a/ttun/__main__.py b/ttun/__main__.py
index b14d0e7..4e693fb 100644
--- a/ttun/__main__.py
+++ b/ttun/__main__.py
@@ -9,6 +9,8 @@ from asyncio.exceptions import TimeoutError
9from typing import Dict 9from typing import Dict
10from typing import Tuple 10from typing import Tuple
11 11
12from websockets.exceptions import ConnectionClosedError
13
12from ttun.client import Client 14from ttun.client import Client
13from ttun.inspect_server import Server 15from ttun.inspect_server import Server
14from ttun.settings import SERVER_HOSTNAME 16from ttun.settings import SERVER_HOSTNAME
@@ -71,6 +73,7 @@ def main():
71 headers=args.header, 73 headers=args.header,
72 ) 74 )
73 75
76
74 try: 77 try:
75 loop = asyncio.get_running_loop() 78 loop = asyncio.get_running_loop()
76 except RuntimeError: 79 except RuntimeError:
@@ -96,7 +99,8 @@ def main():
96 99
97 try: 100 try:
98 loop.run_until_complete(asyncio.wait(tasks, return_when=FIRST_EXCEPTION)) 101 loop.run_until_complete(asyncio.wait(tasks, return_when=FIRST_EXCEPTION))
99 except (CancelledError, TimeoutError): 102 except (CancelledError, TimeoutError) as e:
103 print(e)
100 for task in tasks: 104 for task in tasks:
101 task.cancel() 105 task.cancel()
102 loop.close() 106 loop.close()
diff --git a/ttun/client.py b/ttun/client.py
index 1218283..a5c6e10 100644
--- a/ttun/client.py
+++ b/ttun/client.py
@@ -1,8 +1,11 @@
1import asyncio 1import asyncio
2import json 2import json
3import logging
4from asyncio import create_task, get_running_loop
3from base64 import b64decode 5from base64 import b64decode
4from base64 import b64encode 6from base64 import b64encode
5from datetime import datetime 7from datetime import datetime
8from pprint import pformat
6from time import perf_counter 9from time import perf_counter
7from typing import Awaitable 10from typing import Awaitable
8from typing import Callable 11from typing import Callable
@@ -21,10 +24,12 @@ from websockets import WebSocketClientProtocol
21from websockets.exceptions import ConnectionClosed 24from websockets.exceptions import ConnectionClosed
22 25
23from ttun.pubsub import PubSub 26from ttun.pubsub import PubSub
24from ttun.types import Config 27from ttun.types import Config, Message, MessageType
25from ttun.types import RequestData 28from ttun.types import RequestData
26from ttun.types import ResponseData 29from ttun.types import ResponseData
27 30
31from ttun import __version__
32
28 33
29class Client: 34class Client:
30 def __init__( 35 def __init__(
@@ -36,6 +41,7 @@ class Client:
36 https: bool = False, 41 https: bool = False,
37 headers: List[Tuple[str, str]] = None, 42 headers: List[Tuple[str, str]] = None,
38 ): 43 ):
44 self.version = __version__
39 self.server = server 45 self.server = server
40 self.subdomain = subdomain 46 self.subdomain = subdomain
41 47
@@ -47,10 +53,13 @@ class Client:
47 self.headers = [] if headers is None else headers 53 self.headers = [] if headers is None else headers
48 54
49 async def send(self, data: dict): 55 async def send(self, data: dict):
56 print('send {}'.format(pformat(data)))
50 await self.connection.send(json.dumps(data)) 57 await self.connection.send(json.dumps(data))
51 58
52 async def receive(self) -> dict: 59 async def receive(self) -> dict:
53 return json.loads(await self.connection.recv()) 60 data = json.loads(await self.connection.recv())
61 print('receive {}'.format(pformat(data)))
62 return data
54 63
55 @staticmethod 64 @staticmethod
56 def loop(sleep: int = None): 65 def loop(sleep: int = None):
@@ -69,8 +78,12 @@ class Client:
69 78
70 async def connect(self) -> WebSocketClientProtocol: 79 async def connect(self) -> WebSocketClientProtocol:
71 self.connection = await websockets.connect(f"{self.server}/tunnel/") 80 self.connection = await websockets.connect(f"{self.server}/tunnel/")
81 print(self.version)
72 82
73 await self.send({"subdomain": self.subdomain}) 83 await self.send({
84 "subdomain": self.subdomain,
85 "version": self.version
86 })
74 87
75 self.config = await self.receive() 88 self.config = await self.receive()
76 89
@@ -81,20 +94,40 @@ class Client:
81 return ClientSession(base_url=self.proxy_origin, cookie_jar=DummyCookieJar()) 94 return ClientSession(base_url=self.proxy_origin, cookie_jar=DummyCookieJar())
82 95
83 async def handle_messages(self): 96 async def handle_messages(self):
97 loop = get_running_loop()
84 async with self.session() as session: 98 async with self.session() as session:
85 while True: 99 while True:
86 try: 100 try:
87 request: RequestData = await self.receive() 101 message: Message = await self.receive()
102
103 try:
104 if MessageType(message['type']) != MessageType.request:
105 continue
106 except ValueError:
107 continue
108
109 request: RequestData = message['payload']
88 110
89 request["headers"] = [ 111 request["headers"] = [
90 *request["headers"], 112 *request["headers"],
91 *self.headers, 113 *self.headers,
92 ] 114 ]
93 await self.proxy_request( 115
116 async def response_handler(
117 response: ResponseData,
118 identifier=message['identifier']
119 ):
120 await self.send(Message(
121 type=MessageType.response.value,
122 identifier=identifier,
123 payload=response
124 ))
125
126 loop.create_task(self.proxy_request(
94 session=session, 127 session=session,
95 request=request, 128 request=request,
96 on_response=lambda response: self.send(response), 129 on_response=response_handler,
97 ) 130 ))
98 except ConnectionClosed: 131 except ConnectionClosed:
99 break 132 break
100 133
diff --git a/ttun/types.py b/ttun/types.py
index 3f4d9c9..59bfa79 100644
--- a/ttun/types.py
+++ b/ttun/types.py
@@ -1,10 +1,10 @@
1from enum import Enum
1from typing import Optional 2from typing import Optional
2from typing import TypedDict 3from typing import TypedDict
3 4
4 5class MessageType(Enum):
5class Message(TypedDict): 6 request = 'request'
6 type: str 7 response = 'response'
7
8 8
9class RequestData(TypedDict): 9class RequestData(TypedDict):
10 method: str 10 method: str
@@ -19,5 +19,11 @@ class ResponseData(TypedDict):
19 body: Optional[str] 19 body: Optional[str]
20 20
21 21
22class Message(TypedDict):
23 type: MessageType
24 identifier: str
25 payload: RequestData | ResponseData
26
27
22class Config(TypedDict): 28class Config(TypedDict):
23 url: str 29 url: str