diff options
| -rw-r--r-- | ttun/__main__.py | 18 | ||||
| -rw-r--r-- | ttun/client.py | 138 |
2 files changed, 91 insertions, 65 deletions
diff --git a/ttun/__main__.py b/ttun/__main__.py index cee662a..cbf8e16 100644 --- a/ttun/__main__.py +++ b/ttun/__main__.py | |||
| @@ -27,12 +27,26 @@ def main(): | |||
| 27 | default=None, | 27 | default=None, |
| 28 | help="The subdomain of the ttun tunnel", | 28 | help="The subdomain of the ttun tunnel", |
| 29 | ) | 29 | ) |
| 30 | parser.add_argument( | ||
| 31 | "-t", | ||
| 32 | "--to", | ||
| 33 | default="127.0.0.1", | ||
| 34 | help="The host to proxy the request to", | ||
| 35 | ) | ||
| 36 | parser.add_argument( | ||
| 37 | "--https", | ||
| 38 | help="Use this if the proxied server uses https", | ||
| 39 | action="store_true", | ||
| 40 | default=False, | ||
| 41 | ) | ||
| 30 | args = parser.parse_args() | 42 | args = parser.parse_args() |
| 31 | 43 | ||
| 32 | client = Client( | 44 | client = Client( |
| 33 | port=args.port, | 45 | port=args.port, |
| 34 | subdomain=args.subdomain, | 46 | subdomain=args.subdomain, |
| 35 | server=args.server, | 47 | server=args.server, |
| 48 | to=args.to, | ||
| 49 | https=args.https, | ||
| 36 | ) | 50 | ) |
| 37 | 51 | ||
| 38 | try: | 52 | try: |
| @@ -45,14 +59,14 @@ def main(): | |||
| 45 | 59 | ||
| 46 | def print_info(server: Server): | 60 | def print_info(server: Server): |
| 47 | print("Tunnel created:") | 61 | print("Tunnel created:") |
| 48 | print(f'{client.config["url"]} -> http://localhost:{args.port}') | 62 | print(f'{client.config["url"]} -> {client.proxy_origin}') |
| 49 | print("") | 63 | print("") |
| 50 | print(f"Inspect requests:") | 64 | print(f"Inspect requests:") |
| 51 | print(f"http://localhost:{server.port}") | 65 | print(f"http://localhost:{server.port}") |
| 52 | 66 | ||
| 53 | server = Server( | 67 | server = Server( |
| 54 | config=client.config, | 68 | config=client.config, |
| 55 | on_resend=client.proxyRequest, | 69 | on_resend=client.proxy_request, |
| 56 | on_started=print_info, | 70 | on_started=print_info, |
| 57 | ) | 71 | ) |
| 58 | 72 | ||
diff --git a/ttun/client.py b/ttun/client.py index 6d05da7..9e597ab 100644 --- a/ttun/client.py +++ b/ttun/client.py | |||
| @@ -25,14 +25,22 @@ from ttun.types import ResponseData | |||
| 25 | 25 | ||
| 26 | 26 | ||
| 27 | class Client: | 27 | class Client: |
| 28 | def __init__(self, port: int, server: str, subdomain: str = None): | 28 | def __init__( |
| 29 | self.port = port | 29 | self, |
| 30 | port: int, | ||
| 31 | server: str, | ||
| 32 | subdomain: str = None, | ||
| 33 | to: str = "127.0.0.1", | ||
| 34 | https: bool = False, | ||
| 35 | ): | ||
| 30 | self.server = server | 36 | self.server = server |
| 31 | self.subdomain = subdomain | 37 | self.subdomain = subdomain |
| 32 | 38 | ||
| 33 | self.config: Optional[Config] = None | 39 | self.config: Optional[Config] = None |
| 34 | self.connection: WebSocketClientProtocol = None | 40 | self.connection: WebSocketClientProtocol = None |
| 35 | 41 | ||
| 42 | self.proxy_origin = f'{"https" if https else "http"}://{to}:{port}' | ||
| 43 | |||
| 36 | async def send(self, data: dict): | 44 | async def send(self, data: dict): |
| 37 | await self.connection.send(json.dumps(data)) | 45 | await self.connection.send(json.dumps(data)) |
| 38 | 46 | ||
| @@ -65,73 +73,77 @@ class Client: | |||
| 65 | return self.connection | 73 | return self.connection |
| 66 | 74 | ||
| 67 | async def handle_messages(self): | 75 | async def handle_messages(self): |
| 68 | while True: | 76 | async with ClientSession( |
| 69 | try: | 77 | base_url=self.proxy_origin, cookie_jar=DummyCookieJar() |
| 70 | request: RequestData = await self.receive() | 78 | ) as session: |
| 71 | await self.proxy_request( | 79 | while True: |
| 72 | request=request, on_response=lambda response: self.send(response) | 80 | try: |
| 73 | ) | 81 | request: RequestData = await self.receive() |
| 74 | 82 | await self.proxy_request( | |
| 75 | except ConnectionClosed: | 83 | session=session, |
| 76 | break | 84 | request=request, |
| 85 | on_response=lambda response: self.send(response), | ||
| 86 | ) | ||
| 87 | except ConnectionClosed: | ||
| 88 | break | ||
| 77 | 89 | ||
| 78 | async def proxy_request( | 90 | async def proxy_request( |
| 79 | self, | 91 | self, |
| 92 | session: ClientSession, | ||
| 80 | request: RequestData, | 93 | request: RequestData, |
| 81 | on_response: Callable[[ResponseData], Awaitable] = None, | 94 | on_response: Callable[[ResponseData], Awaitable] = None, |
| 82 | ): | 95 | ): |
| 83 | async with ClientSession(cookie_jar=DummyCookieJar()) as session: | 96 | request_id = uuid4() |
| 84 | request_id = uuid4() | 97 | await PubSub.publish( |
| 85 | await PubSub.publish( | 98 | { |
| 86 | { | 99 | "type": "request", |
| 87 | "type": "request", | 100 | "payload": { |
| 88 | "payload": { | 101 | "id": request_id.hex, |
| 89 | "id": request_id.hex, | 102 | "timestamp": datetime.now().isoformat(), |
| 90 | "timestamp": datetime.now().isoformat(), | 103 | **request, |
| 91 | **request, | 104 | }, |
| 92 | }, | 105 | } |
| 93 | } | 106 | ) |
| 107 | |||
| 108 | start = perf_counter() | ||
| 109 | try: | ||
| 110 | response = await session.request( | ||
| 111 | method=request["method"], | ||
| 112 | url=request["path"], | ||
| 113 | headers=request["headers"], | ||
| 114 | data=b64decode(request["body"].encode()), | ||
| 115 | allow_redirects=False, | ||
| 94 | ) | 116 | ) |
| 117 | end = perf_counter() | ||
| 118 | |||
| 119 | response_data = ResponseData( | ||
| 120 | status=response.status, | ||
| 121 | headers=[ | ||
| 122 | (key, value) | ||
| 123 | for key, value in response.headers.items() | ||
| 124 | if key.lower() not in ["transfer-encoding", "content-encoding"] | ||
| 125 | ], | ||
| 126 | body=b64encode(await response.read()).decode(), | ||
| 127 | ) | ||
| 128 | except ClientError as e: | ||
| 129 | end = perf_counter() | ||
| 95 | 130 | ||
| 96 | start = perf_counter() | 131 | response_data = ResponseData( |
| 97 | try: | 132 | status=(504 if isinstance(e, ClientConnectionError) else 502), |
| 98 | response = await session.request( | 133 | headers=[("content-type", "text/plain")], |
| 99 | method=request["method"], | 134 | body=b64encode(str(e).encode()).decode(), |
| 100 | url=f'http://localhost:{self.port}{request["path"]}', | ||
| 101 | headers=request["headers"], | ||
| 102 | data=b64decode(request["body"].encode()), | ||
| 103 | allow_redirects=False, | ||
| 104 | ) | ||
| 105 | end = perf_counter() | ||
| 106 | |||
| 107 | response_data = ResponseData( | ||
| 108 | status=response.status, | ||
| 109 | headers=[ | ||
| 110 | (key, value) | ||
| 111 | for key, value in response.headers.items() | ||
| 112 | if key.lower() not in ["transfer-encoding", "content-encoding"] | ||
| 113 | ], | ||
| 114 | body=b64encode(await response.read()).decode(), | ||
| 115 | ) | ||
| 116 | except ClientError as e: | ||
| 117 | end = perf_counter() | ||
| 118 | |||
| 119 | response_data = ResponseData( | ||
| 120 | status=(504 if isinstance(e, ClientConnectionError) else 502), | ||
| 121 | headers=[("content-type", "text/plain")], | ||
| 122 | body=b64encode(str(e).encode()).decode(), | ||
| 123 | ) | ||
| 124 | |||
| 125 | if on_response is not None: | ||
| 126 | await on_response(response_data) | ||
| 127 | |||
| 128 | await PubSub.publish( | ||
| 129 | { | ||
| 130 | "type": "response", | ||
| 131 | "payload": { | ||
| 132 | "id": request_id.hex, | ||
| 133 | "timing": end - start, | ||
| 134 | **response_data, | ||
| 135 | }, | ||
| 136 | } | ||
| 137 | ) | 135 | ) |
| 136 | |||
| 137 | if on_response is not None: | ||
| 138 | await on_response(response_data) | ||
| 139 | |||
| 140 | await PubSub.publish( | ||
| 141 | { | ||
| 142 | "type": "response", | ||
| 143 | "payload": { | ||
| 144 | "id": request_id.hex, | ||
| 145 | "timing": end - start, | ||
| 146 | **response_data, | ||
| 147 | }, | ||
| 148 | } | ||
| 149 | ) | ||
