summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pyproject.toml3
-rw-r--r--requirements.txt4
-rw-r--r--setup.cfg13
-rw-r--r--ttun/__main__.py102
-rw-r--r--ttun/client.py76
-rw-r--r--ttun/types.py24
6 files changed, 142 insertions, 80 deletions
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..9787c3b
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
1[build-system]
2requires = ["setuptools", "wheel"]
3build-backend = "setuptools.build_meta"
diff --git a/requirements.txt b/requirements.txt
index 86290f8..4c59602 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1 +1,3 @@
1websockets ~= 10.0 1.
2setuptools
3build
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..e6fa217
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,13 @@
1[metadata]
2name = ttun
3version = 0.1.0
4
5[options]
6packages = ttun
7install_requires =
8 websockets ~= 10.0
9 aiohttp ~= 3.8
10
11[options.entry_points]
12console_scripts =
13 ttun = ttun.__main__:main
diff --git a/ttun/__main__.py b/ttun/__main__.py
index 5984286..f8a1978 100644
--- a/ttun/__main__.py
+++ b/ttun/__main__.py
@@ -1,88 +1,32 @@
1import asyncio 1import asyncio
2import json 2from argparse import ArgumentParser
3from dataclasses import dataclass
4from functools import wraps
5from typing import Coroutine, Callable, TypedDict
6 3
7import websockets 4from ttun.client import Client
8from websockets import WebSocketClientProtocol
9from websockets.exceptions import ConnectionClosed
10 5
11 6
12class Message(TypedDict): 7def main():
13 type: str 8 parser = ArgumentParser(prog='ttun')
9 parser.add_argument('port')
10 parser.add_argument('-s', '--subdomain', default=None)
11 args = parser.parse_args()
14 12
13 client = Client(port=args.port, subdomain=args.subdomain)
15 14
16class Request(TypedDict): 15 try:
17 path: str 16 loop = asyncio.get_running_loop()
17 except RuntimeError:
18 loop = asyncio.new_event_loop()
19 asyncio.set_event_loop(loop)
18 20
21 loop.run_until_complete(client.connect())
19 22
20class Reply(TypedDict): 23 print(client.config['url'])
21 path: str 24 try:
22 25 loop.run_until_complete(asyncio.wait([
23 26 asyncio.ensure_future(client.handle_messages()),
24@dataclass 27 ]))
25class Client: 28 except KeyboardInterrupt:
26 connection = WebSocketClientProtocol 29 pass
27
28 async def send(self, data: dict):
29 await self.connection.send(json.dumps(data))
30
31 async def receive(self) -> dict:
32 return json.loads(await self.connection.recv())
33
34 @staticmethod
35 def loop(sleep: int = None):
36 async def wrapper(callback: Callable[[], Coroutine]):
37 while True:
38 try:
39 await callback()
40
41 if sleep is not None:
42 await asyncio.sleep(sleep)
43
44 except ConnectionClosed:
45 break
46 return wrapper
47
48 async def connect(self) -> WebSocketClientProtocol:
49 self.connection = await websockets.connect('ws://localhost:8000/tunnel/')
50
51 await self.send({
52 'type': 'config',
53 'config': {
54 'subdomain': 'test'
55 }
56 })
57
58 config = await self.receive()
59 print(config)
60
61 if self.connection.open:
62 return self.connection
63
64 async def handle_messages(self):
65 while True:
66 try:
67 message: Request = await self.receive()
68 print(message)
69 await self.connection.send(json.dumps({
70 'type': 'response',
71 'response': {
72 'test': 'Test'
73 }
74 }))
75
76 except ConnectionClosed:
77 break
78
79client = Client()
80loop = asyncio.get_event_loop()
81
82connection = loop.run_until_complete(client.connect())
83
84loop.run_until_complete(asyncio.wait([
85 asyncio.ensure_future(client.handle_messages()),
86 # asyncio.ensure_future(client.heartbeat())
87]))
88 30
31if __name__ == '__main__':
32 main()
diff --git a/ttun/client.py b/ttun/client.py
new file mode 100644
index 0000000..defe023
--- /dev/null
+++ b/ttun/client.py
@@ -0,0 +1,76 @@
1import asyncio
2import json
3from base64 import b64encode
4from dataclasses import dataclass
5from typing import Optional, Callable, Coroutine
6
7import websockets
8from aiohttp import ClientSession
9from websockets import WebSocketClientProtocol
10from websockets.exceptions import ConnectionClosed
11
12from ttun.types import Config, RequestData, ResponseData
13
14
15@dataclass
16class Client:
17 port: int
18 subdomain: Optional[str]
19
20 url: Optional[Config] = None
21 connection = WebSocketClientProtocol
22
23 async def send(self, data: dict):
24 await self.connection.send(json.dumps(data))
25
26 async def receive(self) -> dict:
27 return json.loads(await self.connection.recv())
28
29 @staticmethod
30 def loop(sleep: int = None):
31 async def wrapper(callback: Callable[[], Coroutine]):
32 while True:
33 try:
34 await callback()
35
36 if sleep is not None:
37 await asyncio.sleep(sleep)
38
39 except ConnectionClosed:
40 break
41 return wrapper
42
43 async def connect(self) -> WebSocketClientProtocol:
44 self.connection = await websockets.connect('ws://localhost:8000/tunnel/')
45
46 await self.send({
47 'subdomain': self.subdomain
48 })
49
50 self.config = await self.receive()
51
52 if self.connection.open:
53 return self.connection
54
55 async def handle_messages(self):
56 while True:
57 try:
58 request: RequestData = await self.receive()
59
60 async with ClientSession() as session:
61 response = await session.request(
62 method=request['method'],
63 url=f'http://localhost:{self.port}{request["path"]}',
64 headers=request['headers'],
65 cookies=request['cookies'],
66 )
67
68 await self.send(ResponseData(
69 status=response.status,
70 headers=dict(response.headers),
71 cookies=dict(response.cookies),
72 body=b64encode(await response.read()).decode()
73 ))
74
75 except ConnectionClosed:
76 break
diff --git a/ttun/types.py b/ttun/types.py
new file mode 100644
index 0000000..cf94b9e
--- /dev/null
+++ b/ttun/types.py
@@ -0,0 +1,24 @@
1from typing import TypedDict, Optional
2
3
4class Message(TypedDict):
5 type: str
6
7
8class RequestData(TypedDict):
9 method: str
10 path: str
11 headers: dict
12 cookies: dict
13 body: Optional[str]
14
15
16class ResponseData(TypedDict):
17 status: int
18 headers: dict
19 cookies: dict
20 body: Optional[str]
21
22
23class Config(TypedDict):
24 url: str