Перейти до вмісту
Гайды 7 хв читання 33 переглядів

Асинхронний Проксі на Python

Дослідіть створення високопродуктивних асинхронних проксі на Python. Цей посібник охоплю

Python
Асинхронний Проксі на Python

Асинхронні проксі в Python використовують такі бібліотеки, як aiohttp та httpx, для ефективного керування численними одночасними мережевими запитами, запобігаючи блокуванню основного потоку виконання операціями вводу/виводу.

Проксі-сервіси за своєю суттю є I/O-bound, витрачаючи більшу частину свого операційного часу на очікування мережевих відповідей від вищестоящих серверів або клієнтських запитів. Традиційні синхронні (блокуючі) моделі вводу/виводу обробляють один запит за раз на потік, що призводить до неефективного використання ресурсів та обмеженої масштабованості. Асинхронний ввід/вивід, використовуючи фреймворк Python asyncio, дозволяє одному потоку керувати численними одночасними з'єднаннями, перемикаючи контекст під час очікування завершення операцій вводу/виводу. Ця архітектура значно підвищує пропускну здатність та чутливість проксі.

Основні асинхронні концепції

Бібліотека Python asyncio забезпечує основу для асинхронного програмування. Ключові елементи включають:

  • Цикл подій (Event Loop): Центральний компонент, який планує та виконує корутини, обробляючи події вводу/виводу та зворотні виклики.
  • Корутини (async def): Функції, які можуть бути призупинені та відновлені. Вони визначаються за допомогою async def та виконуються за допомогою await.
  • Ключове слово await: Використовується для призупинення виконання корутини до завершення awaitable об'єкта (іншої корутини, Future або Task). Це повертає керування циклу подій.

aiohttp для асинхронних проксі-сервісів

aiohttp — це асинхронний HTTP-клієнт/серверний фреймворк для asyncio. Він добре підходить для створення як вхідних (серверних), так і вихідних (клієнтських) компонентів проксі.

aiohttp як проксі-сервер

aiohttp.web надає необхідні інструменти для створення веб-сервера, який прослуховує вхідні клієнтські запити.

import aiohttp.web

async def handle_request(request):
    """
    A placeholder handler for incoming requests.
    In a real proxy, this would forward the request.
    """
    return aiohttp.web.Response(text=f"Received: {request.method} {request.url}")

async def main():
    app = aiohttp.web.Application()
    app.router.add_route('*', '/{path:.*}', handle_request) # Catch all routes
    runner = aiohttp.web.AppRunner(app)
    await runner.setup()
    site = aiohttp.web.TCPSite(runner, '0.0.0.0', 8080)
    await site.start()
    print("aiohttp proxy server started on port 8080")
    while True:
        await asyncio.sleep(3600) # Keep the server running

if __name__ == '__main__':
    import asyncio
    asyncio.run(main())

aiohttp як асинхронний HTTP-клієнт

aiohttp.ClientSession використовується для виконання вихідних HTTP-запитів, що є критично важливим для пересилання клієнтських запитів до вищестоящих серверів. Він керує пулом з'єднань та файлами cookie.

import aiohttp
import asyncio

async def fetch_url(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            response.raise_for_status() # Raise an exception for HTTP errors
            return await response.text()

async def example_client_usage():
    content = await fetch_url('http://httpbin.org/get')
    print(f"Fetched content: {content[:100]}...")

if __name__ == '__main__':
    asyncio.run(example_client_usage())

httpx для асинхронних проксі-сервісів

httpx — це сучасний, повнофункціональний HTTP-клієнт для Python, який надає як синхронні, так і асинхронні API. Його асинхронні можливості побудовані на asyncio.

httpx як асинхронний HTTP-клієнт

httpx.AsyncClient є основним інтерфейсом для виконання асинхронних запитів. Він пропонує API, схожий на requests, що робить його інтуїтивно зрозумілим для розробників, знайомих з бібліотекою requests.

import httpx
import asyncio

async def fetch_url_httpx(url):
    async with httpx.AsyncClient() as client:
        response = await client.get(url)
        response.raise_for_status() # Raise an exception for HTTP errors
        return response.text

async def example_httpx_client_usage():
    content = await fetch_url_httpx('http://httpbin.org/get')
    print(f"Fetched content (httpx): {content[:100]}...")

if __name__ == '__main__':
    asyncio.run(example_httpx_client_usage())

httpx не надає серверних можливостей; це суто клієнтська бібліотека.

Порівняння клієнтів aiohttp та httpx

Функція aiohttp.ClientSession httpx.AsyncClient
Призначення Асинхронний HTTP-клієнт та серверний фреймворк. Асинхронний (і синхронний) HTTP-клієнт.
Стиль API Нижчий рівень інтеграції asyncio, більш багатослівний. API, схожий на requests, зазвичай більш лаконічний.
Підтримка HTTP/2 Немає вбудованої підтримки HTTP/2 клієнта. Вбудована підтримка HTTP/2 клієнта.
Підтримка HTTP/3 (QUIC) Ні. Експериментальна підтримка через quic-go (Rust).
Клієнт WebSocket Так. Ні.
Залежності multidict, yarl, async_timeout, attrs. httpcore, idna, certifi, `sniffio (мінімальні).
Пул з'єднань Керується ClientSession. Керується AsyncClient.
Обробка перенаправлень Автоматична, настроювана. Автоматична, настроювана.
Потокові відповіді Так, за допомогою response.content.read(). Так, за допомогою response.aiter_bytes().
Конфігурація проксі Прямий параметр proxy для методів ClientSession. Прямий параметр proxies для AsyncClient та запитів.

Для створення проксі-сервера aiohttp необхідний через його серверні можливості. Для вихідного клієнтського компонента обидва варіанти є життєздатними. httpx часто пропонує простіший API та вбудовану підтримку HTTP/2, що може бути перевагою.

Створення асинхронного проксі за допомогою aiohttp (сервер) та httpx (клієнт)

Цей підхід використовує aiohttp для обробки вхідних проксі-запитів та httpx для їх пересилання на цільовий сервер. Ця комбінація часто забезпечує хороший баланс між контролем сервера та простотою/функціональністю клієнта.

import aiohttp.web
import httpx
import asyncio
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# Initialize httpx.AsyncClient once for connection pooling
# This client will be used for all outgoing requests
# Set a default timeout to prevent hanging connections
OUTGOING_CLIENT = httpx.AsyncClient(timeout=30.0) 

async def proxy_handler(request):
    """
    Handles incoming client requests, forwards them using httpx,
    and returns the response to the client.
    """
    target_url = str(request.url).lstrip('/') # Remove leading slash from path

    # Reconstruct target URL, preserving scheme, host, and query parameters
    # For a typical forward proxy, the client sends full URLs (e.g., GET http://example.com/path)
    # For a reverse proxy, the server might only get the path, and needs a base URL.
    # This example assumes a forward proxy where the full URL is in the path.
    # For a reverse proxy, you'd prepend a fixed base URL:
    # target_url = f"http://upstream.example.com{request.url.path_qs}"

    # Extract headers, excluding hop-by-hop headers and proxy-specific headers
    headers = {
        k: v for k, v in request.headers.items() 
        if k.lower() not in ['host', 'connection', 'keep-alive', 'proxy-authenticate', 
                             'proxy-authorization', 'te', 'trailers', 'transfer-encoding', 
                             'upgrade', 'via', 'x-forwarded-for', 'x-real-ip']
    }

    # Add X-Forwarded-For if not already present
    client_ip = request.remote
    if client_ip:
        headers['X-Forwarded-For'] = headers.get('X-Forwarded-For', '') + (', ' if headers.get('X-Forwarded-For') else '') + client_ip

    request_method = request.method
    request_body = await request.read() if request_method in ('POST', 'PUT', 'PATCH') else None

    logger.info(f"Proxying {request_method} {target_url} from {request.remote}")

    try:
        # Forward the request using httpx
        proxy_response = await OUTGOING_CLIENT.request(
            method=request_method,
            url=target_url,
            headers=headers,
            content=request_body,
            params=request.query # Pass query parameters separately
        )
        proxy_response.raise_for_status() # Raise for 4xx/5xx responses

        # Prepare response for the client
        response_headers = {
            k: v for k, v in proxy_response.headers.items()
            if k.lower() not in ['content-encoding', 'transfer-encoding', 'connection'] # Hop-by-hop headers
        }

        # Stream response body to avoid loading large responses into memory
        response = aiohttp.web.StreamResponse(status=proxy_response.status, headers=response_headers)
        await response.prepare(request)
        async for chunk in proxy_response.aiter_bytes():
            await response.write(chunk)
        await response.write_eof()

        logger.info(f"Forwarded {request_method} {target_url} with status {proxy_response.status}")
        return response

    except httpx.HTTPStatusError as e:
        logger.error(f"HTTP error proxying {target_url}: {e.response.status_code} - {e.response.text}")
        return aiohttp.web.Response(
            status=e.response.status_code,
            text=f"Upstream HTTP Error: {e.response.status_code}\n{e.response.text}",
            content_type="text/plain"
        )
    except httpx.RequestError as e:
        logger.error(f"Network error proxying {target_url}: {e}")
        return aiohttp.web.Response(
            status=502, # Bad Gateway
            text=f"Proxy Network Error: {e}",
            content_type="text/plain"
        )
    except Exception as e:
        logger.exception(f"Unexpected error in proxy_handler for {target_url}")
        return aiohttp.web.Response(
            status=500,
            text=f"Proxy Internal Error: {e}",
            content_type="text/plain"
        )

async def start_proxy_server():
    app = aiohttp.web.Application()
    # This route handles all methods and paths
    # For a forward proxy, client requests look like: GET http://example.com/path
    # aiohttp parses the path as '/http://example.com/path'
    # We strip the leading '/' in proxy_handler to get the full URL.
    app.router.add_route('*', '/{path:.*}', proxy_handler) 

    runner = aiohttp.web.AppRunner(app)
    await runner.setup()
    site = aiohttp.web.TCPSite(runner, '0.0.0.0', 8080)
    await site.start()
    logger.info("Asynchronous proxy server started on http://0.0.0.0:8080")

    # Keep the server running indefinitely
    try:
        while True:
            await asyncio.sleep(3600) 
    finally:
        await OUTGOING_CLIENT.aclose() # Ensure httpx client is closed
        await runner.cleanup()

if __name__ == '__main__':
    asyncio.run(start_proxy_server())

Практичні міркування для проксі-сервісів

Управління заголовками

Проксі повинні ретельно керувати HTTP-заголовками.
* Заголовки "hop-by-hop" (Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailer, Transfer-Encoding, Upgrade) є специфічними для з'єднання між двома вузлами і не повинні пересилатися.
* X-Forwarded-For / X-Real-IP: Додайте або доповніть IP-адресу клієнта до цих заголовків, щоб повідомити вищестоящий сервер про початкового запитувача.
* Заголовок Via: За бажанням додайте заголовок Via, щоб вказати на участь проксі.

Потокова передача тіла запиту/відповіді

Для великих тіл запитів або відповідей критично важливо передавати дані потоково, а не завантажувати їх повністю в пам'ять. Як aiohttp (через request.read() для вхідних, response.write() для вихідних), так і httpx (через параметр content та response.aiter_bytes()) підтримують потокову передачу, що запобігає вичерпанню пам'яті та зменшує затримку.

Пул з'єднань

Як aiohttp.ClientSession, так і httpx.AsyncClient реалізують пул з'єднань. Створення цих клієнтів один раз і їх повторне використання для кількох запитів (як показано з OUTGOING_CLIENT) є вирішальним для продуктивності. Це зменшує накладні витрати на встановлення нових TCP-з'єднань для кожного запиту.

Тайм-аути

Проксі-сервіси чутливі до затримок або збоїв вищестоящих серверів. Впровадження суворих тайм-аутів для вихідних запитів є важливим для запобігання виснаженню ресурсів та забезпечення чутливого сервісу. httpx.AsyncClient та aiohttp.ClientSession дозволяють налаштовувати тайм-аути для підключення, читання та загальні тайм-аути.

Обробка помилок та повторні спроби

Необхідна надійна обробка помилок для мережевих проблем (наприклад, відмова у з'єднанні, помилки DNS) та HTTP-помилок (наприклад, відповіді 5xx від вищестоящих серверів). Впроваджуйте механізми повторних спроб з експоненційною затримкою для тимчасових помилок, щоб підвищити надійність.

Проксіювання WebSocket

aiohttp надає вбудовану підтримку WebSockets на стороні сервера (aiohttp.web.WebSocketResponse). Проксіювання WebSockets вимагає обробки заголовка Upgrade та встановлення двонаправленого потоку даних між клієнтом, проксі та цільовим сервером WebSocket. httpx не підтримує клієнтські з'єднання WebSocket.

Налаштування продуктивності

  • uvloop: Для додатків aiohttp встановлення uvloop (замінник циклу подій asyncio, написаний на Cython) може значно підвищити продуктивність.
  • Обмеження операційної системи: Часто потрібно налаштувати обмеження на кількість відкритих файлових дескрипторів (ulimit -n) в операційній системі для висококонкурентних проксі-сервісів.
  • Моніторинг ресурсів: Моніторинг ЦП, пам'яті та мережевого вводу/виводу для виявлення вузьких місць та оптимізації розподілу ресурсів.
Оновлено: 03.03.2026
Назад до категорії

Читайте також

Гайды 1 хв

Налаштування проксі в Cypress для E2E тестування

Налаштування проксі в Cypress: змінні HTTP_PROXY, cy-proxy-middleware та тестування геозалежного контенту.

Гайды 1 хв

Як автоматизувати купівлю проксі через API

Автоматизація купівлі та управління проксі через API провайдерів: інтеграція, моніторинг використання та автопоновлення.

Гайды 1 хв

Створення інформаційної панелі моніторингу проксі в Grafana

Покрокове створення інформаційної панелі для моніторингу проксі в Grafana: метрики,

Гайды 1 хв

Як тестувати проксі перед покупкою

Чек-лист тестування проксі перед покупкою: швидкість, стабільність, анонімність, гео та сумісність з ціллю

Гайды 1 хв

Як налаштувати липкі сесії через проксі

Липкі сесії: підтримка однієї IP-адреси протягом усієї сесії, налаштовуються через провайдера та самостійно.

Гайды 1 хв

Використання проксі з Camoufox

Camoufox — це модифікований Firefox для обходу антиботів. Налаштування проксі, відбиток та режим невидимості.

Спробуйте наші проксі

20,000+ проксі в 100+ країнах світу

support_agent
GProxy Support
Usually replies within minutes
Hi there!
Send us a message and we'll reply as soon as possible.