IPFS Python Proxy
AI Prompt for Code Generation
Prompt для генерирования в ИИ Python кода Proxy-сервера, работающего через P2P сеть (IPFS).
Пробный сгенерированный код нормально отображал статические сайты через 27 пиров (При работе в паре с вторым Python Proxy-сервером, преобразующим IPFS в HTTP).
Промпт
СТРУКТУРИРОВАННЫЙ ПРОМПТ ДЛЯ ГЕНЕРАЦИИ IPFS PROXY-КЛИЕНТА
1. ЦЕЛЬ И КОНТЕКСТ
Создать Python 3 скрипт прокси-клиента, который принимает HTTP-запросы от браузера, оборачивает их в кастомный фрейминг-протокол и маршрутизирует через P2P-туннель IPFS. Скрипт должен работать в режиме Browser-to-IPFS, динамически поднимая локальные туннели через CLI ipfs p2p forward и обеспечивая двунаправленную передачу данных с кодированием/декодированием.
2. ТЕХНОЛОГИЧЕСКИЙ СТЕК И ЗАВИСИМОСТИ
Язык: Python 3.x
Стандартные библиотеки: socket, threading, binascii, subprocess, time, re
Внешняя зависимость: Установленный и запущенный демон ipfs (Kubo/Go-IPFS или аналог), доступный в $PATH
Сетевой протокол: TCP (AF_INET, SOCK_STREAM)
Модель выполнения: Многопоточная (threading), каждый клиентский запрос обрабатывается в отдельном потоке-демонe
3. КОНФИГУРАЦИОННЫЕ КОНСТАНТЫ
Имя переменной
Тип
Значение
Назначение
LISTEN_IP
str
"10.10.0.1"
Локальный IP для прослушивания входящих соединений от браузера
LISTEN_PORT
int
8443
TCP-порт прокси-сервера
SERVER_PEER_ID
str
"12D3Ko74Hsk1DYv1SQY64nC8zub3x63D7tBjde7ZQaroUgdpQ6cz"
Peer ID удалённого узла IPFS, к которому будет подключаться туннель
PROTOCOL
str
"/x/http-proxy/1.0.0"
Имя пользовательского протокола для IPFS p2p forward
HTTP_PREFIX
bytes
b"GET /wiki/ HTTP/1.1\r\nHost: en.euwiki.io\r\nUser-Agent: Mozilla/5.0...\r\n\r\n"
Фиксированный HTTP-заголовок, используемый как префикс фрейма
SEPARATOR
bytes
b"\n===END===\n"
Разделитель между префиксом и HEX-данными
4. СПЕЦИФИКАЦИЯ ФУНКЦИЙ
extract_host(data: bytes) -> str
Вход: Сырые байты HTTP-запроса
Логика: Декодирует в ASCII с игнорированием ошибок. Ищет CONNECT <host> или заголовок Host: <host> через re.search. Возвращает найденный хост или "unknown" при ошибке/отсутствии совпадений.
Выход: str
encode_data(data: bytes) -> bytes
Вход: Сырые данные для отправки
Логика: Конвертирует data в HEX через binascii.hexlify(), добавляет \n. Формирует фрейм: HTTP_PREFIX + SEPARATOR + hex_data.
Выход: bytes
decode_data(data: bytes) -> bytes
Вход: Фрейм с префиксом и HEX-строками, разделёнными \n
Логика: Если начинается с HTTP_PREFIX, отсекает его и SEPARATOR. Разбивает остаток по \n, убирает пустые строки, декодирует каждую строку из HEX через binascii.unhexlify(). Конкатенирует результат. Ошибки декодирования игнорируются.
Выход: bytes
forward_to_tunnel(tunnel_sock: socket, client_sock: socket) -> None
Направление: Клиент → Туннель
Логика: В цикле читает до 8192 байт из client_sock, кодирует через encode_data(), отправляет в tunnel_sock. Прерывается при получении пустых данных. Обёрнут в try/except.
forward_to_client(tunnel_sock: socket, client_sock: socket) -> None
Направление: Туннель → Клиент
Логика: Читает чанки из tunnel_sock, накапливает в buffer. При наличии \n делит буфер на строки, передаёт каждую строку (с добавлением \n) в decode_data(). Если декодирование успешно, отправляет результат в client_sock. Работает в цикле.
handle_browser(client_sock: socket, client_addr: tuple) -> None
Роль: Обработчик одного клиентского соединения
Логика:
Читает начальный запрос, извлекает хост.
Находит свободный локальный порт через временный сокет (bind(127.0.0.1, 0)).
Запускает subprocess.Popen(['ipfs', 'p2p', 'forward', PROTOCOL, f'/ip4/127.0.0.1/tcp/{local_port}', f'/p2p/{SERVER_PEER_ID}'], stdout=DEVNULL, stderr=DEVNULL).
Ждёт 2 сек (time.sleep(2)).
Подключается к 127.0.0.1:local_port, устанавливает TCP_NODELAY.
Отправляет закодированный начальный запрос.
Запускает два потока (forward_to_tunnel, forward_to_client) с daemon=True.
Блокируется на join().
В finally: закрывает сокет туннеля, завершает subprocess, закрывает клиентский сокет.
main() -> None
Логика: Создаёт серверный сокет, устанавливает SO_REUSEADDR, биндит к LISTEN_IP:LISTEN_PORT, слушает очередь до 100 соединений. В бесконечном цикле принимает подключения, ставит TCP_NODELAY, запускает handle_browser в потоке-демоне. Обрабатывает KeyboardInterrupt для graceful shutdown.
5. СЕТЕВЫЕ ПАРАМЕТРЫ И IP-АДРЕСАЦИЯ
Внешний интерфейс: 10.10.0.1:8443 (прослушивание)
Локальный туннель: 127.0.0.1:<динамический_порт>
Удалённый узел IPFS: /p2p/{SERVER_PEER_ID} (определяется демоном IPFS)
Опции сокетов: SO_REUSEADDR (сервер), TCP_NODELAY (клиент и туннель)
Протокол IPFS: Используется команда ipfs p2p forward <protocol> <local_multiaddr> <remote_multiaddr>
6. ИНСТРУКЦИЯ ДЛЯ ИИ-ГЕНЕРАЦИИ
Реализуй скрипт строго по указанной сигнатуре функций и конфигурации.
Сохрани точный порядок байт для HTTP_PREFIX и SEPARATOR.
Обеспечь корректную обработку частичных чтений и фрагментации TCP-потоков через буферизацию в forward_to_client.
Используй daemon=True для потоков пересылки, чтобы они не блокировали выход при закрытии соединения.
Добавь корректное управление жизненным циклом subprocess и сокетов в блоках try/finally.
Код должен быть совместим с Python 3.8+, не использовать сторонние пакеты.
Выведи полный рабочий скрипт без заглушек.
-----------------------------------------------------
Полностью рабочая версия скрипта. Сохранена оригинальная архитектура и логика кодирования/декодирования, но добавлены:
- Автоматический retry при обрыве туннеля (с экспоненциальной задержкой)
- Таймауты сокетов + SO_KEEPALIVE для быстрого обнаружения dead connections
- Кроссплатформенный запуск ipfs (без всплывающих окон на Windows, легко адаптируется)
- Надёжный жизненный цикл: корректное завершение subprocess, закрытие сокетов, обработка graceful disconnect клиента
python
# (c) by Valery Shmelev (Deutsche: Valery Shmeleff)
# Proxy1 - IPFS Client (with full processing of responses, connection drop recovery)
# Browser-to-IPFS
import socket
import threading
import binascii
import subprocess
import time
import re
import sys
import os
# ==================== CONFIGURATION ====================
LISTEN_IP = "10.10.0.1"
LISTEN_PORT = 8443
SERVER_PEER_ID = "12D3Ko74Hsk1DYv1SQY64nC8zub3x63D7tBjde7ZQaroUgdpQ6cz"
PROTOCOL = "/x/http-proxy/1.0.0"
HTTP_PREFIX = (
b"GET /wiki/ HTTP/1.1\r\nHost: en.euwiki.io\r\n"
b"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36\r\n"
b"Accept: text/html\r\nAccept-Language: en-EN,en;q=0.9\r\n"
b"Accept-Encoding: gzip, deflate, br\r\nConnection: keep-alive\r\n\r\n"
)
SEPARATOR = b"\n===END===\n"
# ==================== TUNNEL & RETRY SETTINGS ====================
TUNNEL_READY_TIMEOUT = 10.0 # Макс. время ожидания готовности порта IPFS
SOCKET_TIMEOUT = 5.0 # Таймаут чтения/записи (сек)
MAX_RETRIES = 0 # 0 = бесконечные повторы, >0 = лимит попыток
RETRY_BASE_DELAY = 2.0 # Начальная задержка перед повтором (сек)
RETRY_MAX_DELAY = 30.0 # Максимальная задержка (экспоненциальный backoff)
HEARTBEAT_IDLE_THRESHOLD = 15.0 # Если нет данных N сек, считаем соединение мёртвым
# ==================== FUNCTIONS ====================
def extract_host(data: bytes) -> str:
"""Extracts the host from an HTTP request"""
try:
text = data.decode('ascii', errors='ignore')
match = re.search(r'CONNECT\s+([^\s:]+)', text, re.IGNORECASE)
if match:
return match.group(1)
match = re.search(r'Host:\s*([^\r\n]+)', text, re.IGNORECASE)
if match:
return match.group(1).strip()
return "unknown"
except Exception:
return "unknown"
def encode_data(data: bytes) -> bytes:
"""Encodes data in HEX with a prefix"""
hex_data = binascii.hexlify(data) + b"\n"
return HTTP_PREFIX + SEPARATOR + hex_data
def decode_data(data: bytes) -> bytes:
"""Decodes data from HEX (removing prefix)"""
if data.startswith(HTTP_PREFIX):
data = data[len(HTTP_PREFIX) + len(SEPARATOR):]
result = b""
lines = data.split(b'\n')
for line in lines:
line = line.strip()
if line:
try:
result += binascii.unhexlify(line)
except Exception:
pass
return result
def forward_to_tunnel(tunnel_sock: socket.socket, client_sock: socket.socket) -> None:
"""Forwards data from the client into the tunnel"""
try:
tunnel_sock.settimeout(SOCKET_TIMEOUT)
while True:
data = client_sock.recv(8192)
if not data:
break # Клиент закрыл соединение
tunnel_sock.sendall(encode_data(data))
except Exception:
pass
def forward_to_client(tunnel_sock: socket.socket, client_sock: socket.socket) -> None:
"""Forwards data from the tunnel to the client"""
buffer = b""
try:
tunnel_sock.settimeout(SOCKET_TIMEOUT)
while True:
chunk = tunnel_sock.recv(8192)
if not chunk:
break
buffer += chunk
while b'\n' in buffer:
line, buffer = buffer.split(b'\n', 1)
if line.strip():
decoded = decode_data(line + b'\n')
if decoded:
client_sock.sendall(decoded)
except Exception:
pass
def handle_browser(client_sock: socket.socket, client_addr: tuple) -> None:
"""Handles one connection from the browser with automatic tunnel recovery"""
try:
request = client_sock.recv(8192)
if not request:
return
host = extract_host(request)
print(f"[*] {client_addr} REQUEST → {host}")
# Находим свободный порт для туннеля
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as temp:
temp.bind(('127.0.0.1', 0))
local_port = temp.getsockname()[1]
retries = 0
while MAX_RETRIES == 0 or retries < MAX_RETRIES:
proc = None
tunnel = None
try:
# Запуск IPFS туннеля
cmd = [
'ipfs', 'p2p', 'forward', PROTOCOL,
f'/ip4/127.0.0.1/tcp/{local_port}',
f'/p2p/{SERVER_PEER_ID}'
]
# Windows: скрыть консольное окно. Linux/macOS: 0
creationflags = subprocess.CREATE_NO_WINDOW if os.name == 'nt' else 0
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, creationflags=creationflags)
# Ждём, пока порт станет доступен
start = time.time()
port_ready = False
while time.time() - start < TUNNEL_READY_TIMEOUT:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(0.5)
s.connect(('127.0.0.1', local_port))
port_ready = True
break
except OSError:
time.sleep(0.2)
if not port_ready:
raise RuntimeError("IPFS tunnel port did not become ready in time")
time.sleep(0.5) # Дополнительная пауза для стабилизации
# Подключаемся к туннелю
tunnel = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tunnel.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
tunnel.connect(('127.0.0.1', local_port))
tunnel.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
# Отправляем первый запрос
tunnel.sendall(encode_data(request))
# Запускаем двунаправленную пересылку
t1 = threading.Thread(target=forward_to_tunnel, args=(tunnel, client_sock), daemon=True)
t2 = threading.Thread(target=forward_to_client, args=(tunnel, client_sock), daemon=True)
t1.start()
t2.start()
# Ждём завершения потоков (обрыв связи или закрытие клиента)
t1.join()
t2.join()
# Если дошли сюда, соединение разорвано. Проверяем, закрыл ли его клиент
try:
# Если клиентский сокет ещё жив, пробуждаем его для проверки
client_sock.settimeout(0.1)
test_data = client_sock.recv(1)
if test_data:
print(f"[!] {client_addr} Tunnel dropped, but client still active. Retrying...")
retries += 1
continue
else:
print(f"[-] {client_addr} Client closed connection.")
break
except Exception:
print(f"[!] {client_addr} Tunnel dropped. Retrying...")
retries += 1
except Exception as e:
print(f"[!] {client_addr} Setup/Transfer error: {e}")
retries += 1
finally:
# Корректная очистка ресурсов
if tunnel:
try: tunnel.close()
except: pass
if proc:
try:
proc.terminate()
proc.wait(timeout=2)
except:
pass
# Экспоненциальная задержка перед повтором
if MAX_RETRIES == 0 or retries < MAX_RETRIES:
delay = min(RETRY_BASE_DELAY * (1.5 ** (retries - 1)), RETRY_MAX_DELAY)
print(f"[i] Next attempt in {delay:.1f}s...")
time.sleep(delay)
except Exception as e:
print(f"[!] {client_addr} Fatal error: {e}")
finally:
try: client_sock.close()
except: pass
def main() -> None:
print("=" * 60)
print("PROXY1 (Client) - IPFS P2P Mode (WITH RECOVERY)")
print("=" * 60)
print(f"[*] Target Peer ID: {SERVER_PEER_ID}")
print(f"[*] Protocol: {PROTOCOL}")
print(f"[*] Listening on: {LISTEN_IP}:{LISTEN_PORT}")
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((LISTEN_IP, LISTEN_PORT))
server.listen(100)
print("[*] Server started. Press Ctrl+C to stop.\n")
try:
while True:
client, addr = server.accept()
client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
client.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
print(f"[+] Connection from {addr}")
t = threading.Thread(target=handle_browser, args=(client, addr), daemon=True)
t.start()
except KeyboardInterrupt:
print("\n[*] Shutting down...")
finally:
server.close()
if __name__ == "__main__":
main()
IPFS Python Proxy
Comments
Post a Comment