proxy-tunnel / server.py
tanbushi's picture
cleanup: 移除调试代码和未使用的wstunnel下载
54c36c0
Raw
History Blame Contribute Delete
4.03 kB
import asyncio
import os
import signal
import sys
from urllib.parse import urlparse
PROXY_PORT = 7860
async def health_check(writer):
writer.write(b"HTTP/1.1 200 OK\r\nContent-Length: 3\r\n\r\nOK\n")
await writer.drain()
writer.close()
async def error(writer, status, message):
body = message.encode()
writer.write(f"HTTP/1.1 {status} {message}\r\nContent-Length: {len(body)}\r\n\r\n{message}\n".encode())
await writer.drain()
writer.close()
async def forward(src, dst):
try:
while True:
data = await src.read(65536)
if not data:
break
dst.write(data)
await dst.drain()
except (ConnectionResetError, BrokenPipeError, OSError):
pass
finally:
try:
dst.close()
except OSError:
pass
async def handle_tunnel(target_host, target_port, reader, writer):
try:
backend_reader, backend_writer = await asyncio.wait_for(
asyncio.open_connection(target_host, target_port), timeout=15
)
except (OSError, asyncio.TimeoutError):
await error(writer, 502, "Cannot connect to target")
return
writer.write(b"HTTP/1.1 200 Connection Established\r\n\r\n")
await writer.drain()
await asyncio.gather(
forward(reader, backend_writer),
forward(backend_reader, writer),
)
async def forward_request(host, port, data, reader, writer):
try:
backend_reader, backend_writer = await asyncio.wait_for(
asyncio.open_connection(host, port), timeout=15
)
except (OSError, asyncio.TimeoutError):
await error(writer, 502, "Cannot connect to target")
return
backend_writer.write(data)
await backend_writer.drain()
await asyncio.gather(
forward(reader, backend_writer),
forward(backend_reader, writer),
)
async def handle_client(reader, writer):
try:
data = await asyncio.wait_for(reader.read(4096), timeout=10)
if not data:
writer.close()
return
parts = data.split(b" ", 2)
if len(parts) < 2:
writer.close()
return
method = parts[0]
raw_path = parts[1]
if method == b"CONNECT":
target = raw_path.split(b":")
if len(target) == 2:
try:
await handle_tunnel(target[0].decode(), int(target[1]), reader, writer)
return
except (ValueError, UnicodeDecodeError):
pass
path = raw_path.split(b"?")[0]
if path in (b"/", b"/health"):
await health_check(writer)
return
if raw_path.startswith(b"http://") or raw_path.startswith(b"https://"):
parsed = urlparse(raw_path.decode())
host = parsed.hostname
port = parsed.port or (443 if parsed.scheme == "https" else 80)
if host:
await forward_request(host, port, data, reader, writer)
return
await error(writer, 400, "Bad Request")
except (ConnectionResetError, BrokenPipeError, OSError, asyncio.TimeoutError):
pass
finally:
try:
writer.close()
except OSError:
pass
async def main():
print(f"starting proxy on port {PROXY_PORT}...", flush=True)
server = await asyncio.start_server(handle_client, "0.0.0.0", PROXY_PORT)
async def shutdown():
server.close()
loop = asyncio.get_event_loop()
for sig in (signal.SIGTERM, signal.SIGINT):
try:
loop.add_signal_handler(sig, lambda: asyncio.create_task(shutdown()))
except NotImplementedError:
signal.signal(sig, lambda n, f: asyncio.run_coroutine_threadsafe(shutdown(), loop))
async with server:
print(f"proxy server ready on port {PROXY_PORT}", flush=True)
await server.serve_forever()
if __name__ == "__main__":
asyncio.run(main())