IPFS Python Proxy

 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

Popular posts from this blog

Perplexity Answer

Учим с ИИ

Генерирование МОДЕЛЕЙ