Pular para o conteúdo
Guides 8 min de leitura 806 visualizações

Proxy assíncrono em Python

Conheça a construção de proxies assíncronos de alto desempenho em Python. Este guia cobre aiohttp e httpx para operações de rede eficientes.

Python
Proxy assíncrono em Python

Proxies assíncronos em Python utilizam bibliotecas como aiohttp e httpx para gerenciar várias requisições de rede simultâneas de forma eficiente, evitando que operações de I/O bloqueiem a thread principal de execução.

Serviços de proxy são, por natureza, limitados por I/O: passam a maior parte do tempo de operação esperando respostas de rede de servidores upstream ou requisições de clientes. Os modelos tradicionais de I/O síncrono (bloqueante) atendem uma requisição por vez em cada thread, o que gera uso ineficiente de recursos e escalabilidade limitada. O I/O assíncrono, com o framework asyncio do Python, permite que uma única thread gerencie inúmeras conexões simultâneas trocando de contexto enquanto aguarda a conclusão das operações de I/O. Essa arquitetura aumenta significativamente a vazão e a capacidade de resposta de um proxy.

Conceitos assíncronos fundamentais

A biblioteca asyncio do Python fornece a base para a programação assíncrona. Os elementos principais são:

  • Event loop: o componente central que agenda e executa corrotinas, tratando eventos de I/O e callbacks.
  • Corrotinas (async def): funções que podem ser pausadas e retomadas. São definidas com async def e executadas com await.
  • Palavra-chave await: usada para pausar a execução de uma corrotina até que um awaitable (outra corrotina, um Future ou uma Task) seja concluído. Isso devolve o controle ao event loop.

aiohttp para serviços de proxy assíncronos

aiohttp é um framework HTTP cliente/servidor assíncrono para asyncio. Ele é adequado para construir tanto o componente de entrada (servidor) quanto o de saída (cliente) de um proxy.

aiohttp como servidor proxy

aiohttp.web fornece as ferramentas necessárias para construir um servidor web que escuta requisições de clientes.

import aiohttp.web

async def handle_request(request):
    """
    Um handler de exemplo para requisições recebidas.
    Em um proxy real, isso encaminharia a requisição.
    """
    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) # Captura todas as rotas
    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) # Mantém o servidor em execução

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

aiohttp como cliente HTTP assíncrono

aiohttp.ClientSession é usado para fazer requisições HTTP de saída, essenciais para encaminhar as requisições dos clientes aos servidores upstream. Ele gerencia o pool de conexões e os cookies.

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() # Lança uma exceção em caso de erro HTTP
            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 para serviços de proxy assíncronos

httpx é um cliente HTTP moderno e completo para Python que oferece APIs síncronas e assíncronas. Seus recursos assíncronos são construídos sobre asyncio.

httpx como cliente HTTP assíncrono

httpx.AsyncClient é a interface principal para fazer requisições assíncronas. Ele oferece uma API parecida com a do requests, o que a torna intuitiva para quem já conhece a biblioteca 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() # Lança uma exceção em caso de erro HTTP
        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 não oferece recursos de servidor; é puramente uma biblioteca cliente.

Comparação entre os clientes aiohttp e httpx

Recurso aiohttp.ClientSession httpx.AsyncClient
Finalidade Framework HTTP assíncrono cliente e servidor. Cliente HTTP assíncrono (e síncrono).
Estilo de API Integração asyncio de baixo nível, mais verbosa. API parecida com requests, geralmente mais concisa.
Suporte a HTTP/2 Sem suporte nativo a HTTP/2 no cliente. Suporte nativo a HTTP/2 no cliente.
Suporte a HTTP/3 (QUIC) Não. Suporte experimental via quic-go (Rust).
Cliente WebSocket Sim. Não.
Dependências multidict, yarl, async_timeout, attrs. httpcore, idna, certifi, `sniffio (mínimas).
Pool de conexões Gerenciado por ClientSession. Gerenciado por AsyncClient.
Tratamento de redirects Automático, configurável. Automático, configurável.
Respostas em streaming Sim, usando response.content.read(). Sim, usando response.aiter_bytes().
Configuração de proxy Parâmetro proxy direto nos métodos de ClientSession. Parâmetro proxies direto em AsyncClient e requisições.

Para construir um servidor proxy, aiohttp é necessário por seus recursos de servidor. Para o componente cliente de saída, ambos são viáveis. httpx costuma oferecer uma API mais simples e suporte nativo a HTTP/2, o que pode ser vantajoso.

Construindo um proxy assíncrono com aiohttp (servidor) e httpx (cliente)

Essa abordagem usa aiohttp para tratar as requisições de proxy recebidas e httpx para encaminhá-las ao servidor de destino. Essa combinação costuma oferecer um bom equilíbrio entre controle do servidor e simplicidade/recursos do cliente.

import aiohttp.web
import httpx
import asyncio
import logging

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

# Inicializa httpx.AsyncClient uma única vez para o pool de conexões
# Este cliente será usado em todas as requisições de saída
# Defina um timeout padrão para evitar conexões travadas
OUTGOING_CLIENT = httpx.AsyncClient(timeout=30.0) 

async def proxy_handler(request):
    """
    Trata as requisições recebidas dos clientes, encaminha usando httpx
    e devolve a resposta ao cliente.
    """
    target_url = str(request.url).lstrip('/') # Remove a barra inicial do caminho

    # Reconstrói a URL de destino, preservando esquema, host e parâmetros de query
    # Em um forward proxy típico, o cliente envia URLs completas (ex.: GET http://example.com/path)
    # Em um reverse proxy, o servidor pode receber apenas o caminho e precisa de uma URL base.
    # Este exemplo assume um forward proxy, em que a URL completa está no caminho.
    # Para um reverse proxy, você anteporia uma URL base fixa:
    # target_url = f"http://upstream.example.com{request.url.path_qs}"

    # Extrai os cabeçalhos, excluindo os hop-by-hop e os específicos de proxy
    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']
    }

    # Adiciona X-Forwarded-For se ainda não estiver presente
    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:
        # Encaminha a requisição usando httpx
        proxy_response = await OUTGOING_CLIENT.request(
            method=request_method,
            url=target_url,
            headers=headers,
            content=request_body,
            params=request.query # Passa os parâmetros de query separadamente
        )
        proxy_response.raise_for_status() # Lança exceção para respostas 4xx/5xx

        # Prepara a resposta para o cliente
        response_headers = {
            k: v for k, v in proxy_response.headers.items()
            if k.lower() not in ['content-encoding', 'transfer-encoding', 'connection'] # Cabeçalhos hop-by-hop
        }

        # Envia o corpo da resposta em streaming para não carregar respostas grandes na memória
        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()
    # Esta rota trata todos os métodos e caminhos
    # Em um forward proxy, as requisições do cliente são assim: GET http://example.com/path
    # o aiohttp interpreta o caminho como '/http://example.com/path'
    # Removemos a '/' inicial em proxy_handler para obter a URL completa.
    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")

    # Mantém o servidor rodando indefinidamente
    try:
        while True:
            await asyncio.sleep(3600) 
    finally:
        await OUTGOING_CLIENT.aclose() # Garante que o cliente httpx seja fechado
        await runner.cleanup()

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

Considerações práticas para serviços de proxy

Gerenciamento de cabeçalhos

Proxies precisam gerenciar os cabeçalhos HTTP com cuidado.
* Cabeçalhos hop-by-hop (Connection, Keep-Alive, Proxy-Authenticate, Proxy-Authorization, TE, Trailer, Transfer-Encoding, Upgrade) são específicos da conexão entre dois nós e não devem ser encaminhados.
* X-Forwarded-For / X-Real-IP: adicione ou acrescente o endereço IP do cliente a esses cabeçalhos para informar ao servidor upstream quem foi o solicitante original.
* Cabeçalho Via: opcionalmente, adicione um cabeçalho Via para indicar a participação do proxy.

Streaming do corpo

Para corpos grandes de requisição ou resposta, é fundamental transmitir os dados em streaming em vez de carregá-los inteiramente na memória. Tanto aiohttp (via request.read() na entrada e response.write() na saída) quanto httpx (via parâmetro content e response.aiter_bytes()) suportam streaming, o que evita o esgotamento de memória e reduz a latência.

Pool de conexões

Tanto aiohttp.ClientSession quanto httpx.AsyncClient implementam pool de conexões. Instanciar esses clientes uma única vez e reutilizá-los em várias requisições (como mostrado com OUTGOING_CLIENT) é crucial para o desempenho. Isso reduz o custo de estabelecer novas conexões TCP a cada requisição.

Timeouts

Serviços de proxy são vulneráveis a lentidão ou falhas dos servidores upstream. Implementar timeouts rígidos para as requisições de saída é essencial para evitar esgotamento de recursos e manter um serviço responsivo. httpx.AsyncClient e aiohttp.ClientSession permitem configurar timeouts de conexão, de leitura e total.

Tratamento de erros e retentativas

É necessário um tratamento robusto de erros de rede (por exemplo, conexão recusada, erros de DNS) e de erros HTTP (por exemplo, respostas 5xx do upstream). Implemente mecanismos de retentativa com backoff exponencial para erros transitórios, a fim de melhorar a confiabilidade.

Proxy de WebSocket

aiohttp oferece suporte nativo a WebSockets no lado do servidor (aiohttp.web.WebSocketResponse). Fazer proxy de WebSockets exige tratar o cabeçalho Upgrade e estabelecer um fluxo de dados bidirecional entre cliente, proxy e servidor WebSocket de destino. httpx não suporta conexões WebSocket no cliente.

Ajuste de desempenho

  • uvloop: em aplicações aiohttp, instalar o uvloop (um substituto direto do event loop do asyncio, escrito em Cython) pode aumentar significativamente o desempenho.
  • Limites do sistema operacional: ajustar o limite de descritores de arquivo abertos (ulimit -n) no sistema operacional costuma ser necessário em serviços de proxy de alta concorrência.
  • Monitoramento de recursos: monitore CPU, memória e I/O de rede para identificar gargalos e otimizar a alocação de recursos.
Atualizado: 03.03.2026
Voltar à categoria

Leia também

Experimente nossos proxies

20,000+ proxies em 100+ países do mundo

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