Quay lại danh sách
Cấu hình Proxy cho GraphQL Subscriptions qua WebSocket với Rotating Proxy

Cấu hình Proxy cho GraphQL Subscriptions qua WebSocket với Rotating Proxy

16 tháng 9, 2026

Giới thiệu

GraphQL Subscriptions sử dụng WebSocket để đẩy dữ liệu thời gian thực từ server đến client. Khi chạy ở quy mô lớn hoặc cần kiểm thử từ nhiều vị trí địa lý, việc che giấu IP thật và xoay IP định kỳ trở nên bắt buộc. Bài viết này đi sâu vào cách tích hợp Rotating Proxy (cụ thể là residential proxy của RoProxy) vào client GraphQL để:

  • Đảm bảo mỗi kết nối WebSocket xuất phát từ IP khác nhau.
  • Giữ sticky session đủ lâu cho subscription sống sót.
  • Xử lý xác thực proxy và tự động thay thế khi proxy lỗi.

Tại sao cần Proxy cho GraphQL Subscriptions?

  1. Geo‑testing: Kiểm tra tính năng realtime (chat, giá cổ phiếu, thông báo) từ nhiều quốc gia mà không cần máy chủ vật lý.
  2. Rate‑limit & IP ban: Nhiều API GraphQL giới hạn số kết nối WebSocket trên một IP. Xoay IP giúp tránh chặn.
  3. Anonymity & Compliance: Tuân thủ chính sách bảo mật dữ liệu khi thu thập dữ liệu realtime từ đối thủ.
  4. CI/CD & Contract Testing: Chạy test subscription trong pipeline với IP đa dạng để phát hiện lỗi chỉ xuất hiện ở một số region.

Kiến trúc tổng quan

+----------------+      +-------------------+      +-------------------+
|  Client App    | ---> |  RoProxy Rotating | ---> |  GraphQL Server   |
| (Node/Python)  |      |  Proxy Pool       |      | (WebSocket)       |
+----------------+      +-------------------+      +-------------------+
  • RoProxy Rotating Proxy Pool: Cung cấp endpoint http://user:pass@gateway.roproxy.com:port với tự động xoay IP sau mỗi request hoặc theo thời gian (sticky).
  • Client GraphQL: Cấu hình transport WebSocket để đi qua proxy (HTTP CONNECT cho WS).
  • Sticky Session Manager: Giữ cùng một IP trong vòng session_ttl giây cho một subscription.

Chuẩn bị môi trường

Cài đặt RoProxy SDK

npm i @roproxy/sdk   # Node.js
pip install roproxy-sdk   # Python

Tạo rotating proxy pool

// Node.js – proxy-pool.js
const { RoProxy } = require('@roproxy/sdk');

const pool = new RoProxy.Pool({
  gateway: 'gateway.roproxy.com',
  username: process.env.ROPROXY_USER,
  password: process.env.ROPROXY_PASS,
  // Mỗi proxy sống 5 phút, sau đó tự động thay IP mới
  stickyTtl: 300_000,
  maxConcurrent: 20
});

module.exports = pool;
# Python – proxy_pool.py
import os
from roproxy import Pool

pool = Pool(
    gateway='gateway.roproxy.com',
    username=os.getenv('ROPROXY_USER'),
    password=os.getenv('ROPROXY_PASS'),
    sticky_ttl=300,          # giây
    max_concurrent=20
)

Cấu hình client GraphQL (Apollo Client) với proxy

Ví dụ Node.js

// apollo-proxy-setup.js
const { ApolloClient, InMemoryCache, split, HttpLink } = require('@apollo/client');
const { WebSocketLink } = require('@apollo/client/link/ws');
const { getMainDefinition } = require('@apollo/client/utilities');
const { createProxyAgent } = require('proxy-agent'); // hỗ trợ HTTP CONNECT cho WS
const pool = require('./proxy-pool');

// Hàm lấy proxy URL cho một request/subscription
async function getProxyAgent() {
  const proxy = await pool.acquire(); // trả về { host, port, auth }
  const proxyUrl = `http://${proxy.auth.username}:${proxy.auth.password}@${proxy.host}:${proxy.port}`;
  return createProxyAgent(proxyUrl);
}

// HTTP link cho query/mutation (tùy chọn)
const httpLink = new HttpLink({
  uri: 'https://api.example.com/graphql',
  fetch: async (url, opts) => {
    const agent = await getProxyAgent();
    return fetch(url, { ...opts, agent });
  }
});

// WebSocket link cho subscription
const wsLink = new WebSocketLink({
  uri: 'wss://api.example.com/graphql',
  options: {
    reconnect: true,
    connectionParams: async () => ({
      Authorization: `Bearer ${await getAccessToken()}`
    }),
    // Tự định nghĩa WebSocket constructor để inject proxy agent
    webSocketImpl: class ProxiedWebSocket extends WebSocket {
      constructor(url, protocols) {
        super(url, protocols);
      }
      // Ghi đè phương thức connect để dùng proxy agent
      static async connect(url, protocols, options) {
        const agent = await getProxyAgent();
        // proxy-agent hỗ trợ WS qua HTTP CONNECT
        const ws = new WebSocket(url, protocols);
        // agent sẽ được dùng bởi thư viện ws nội bộ nếu set `agent` option
        return ws;
      }
    }
  }
});

// Tách luồng: subscription dùng wsLink, còn lại dùng httpLink
const link = split(
  ({ query }) => {
    const def = getMainDefinition(query);
    return def.kind === 'OperationDefinition' && def.operation === 'subscription';
  },
  wsLink,
  httpLink
);

const client = new ApolloClient({
  link,
  cache: new InMemoryCache()
});

module.exports = client;

Ví dụ Python (gql + aiohttp)

# graphql_client.py
import asyncio
import os
from gql import Client, gql
from gql.transport.aiohttp import AIOHTTPTransport
from gql.transport.websockets import WebsocketsTransport
from roproxy import Pool

pool = Pool(
    gateway='gateway.roproxy.com',
    username=os.getenv('ROPROXY_USER'),
    password=os.getenv('ROPROXY_PASS'),
    sticky_ttl=300,
    max_concurrent=20
)

async def get_proxy_url() -> str:
    proxy = await pool.acquire()
    return f"http://{proxy.auth.username}:{proxy.auth.password}@{proxy.host}:{proxy.port}"

async def make_transports():
    proxy_url = await get_proxy_url()
    # HTTP transport cho query/mutation
    http_transport = AIOHTTPTransport(
        url="https://api.example.com/graphql",
        headers={"Authorization": f"Bearer {await get_access_token()}"},
        proxy=proxy_url
    )
    # WebSocket transport cho subscription
    ws_transport = WebsocketsTransport(
        url="wss://api.example.com/graphql",
        init_payload={"Authorization": f"Bearer {await get_access_token()}"},
        proxy=proxy_url
    )
    return http_transport, ws_transport

async def main():
    http_transport, ws_transport = await make_transports()
    # Client cho query/mutation
    http_client = Client(transport=http_transport, fetch_schema_from_transport=True)
    # Client cho subscription (gql hỗ trợ async subscription)
    ws_client = Client(transport=ws_transport, fetch_schema_from_transport=True)

    # Ví dụ subscription
    SUB = gql("""
    subscription OnPriceChanged($symbol: String!) {
      priceChanged(symbol: $symbol) {
        symbol
        price
        timestamp
      }
    }
    """)
    async for result in ws_client.subscribe_async(SUB, variable_values={"symbol": "AAPL"}):
        print(result)

if __name__ == "__main__":
    asyncio.run(main())

Xử lý Sticky Session cho Subscription

  • Mục tiêu: Giữ nguyên IP trong suốt vòng đời subscription (thường kéo dài hàng phút đến giờ).
  • Cách thực hiện:
    1. Khi pool.acquire() được gọi, RoProxy trả về một proxy với sticky_ttl.
    2. Client lưu trữ proxy object cùng với expires_at = now + sticky_ttl.
    3. Mọi lần reconnect WebSocket (do network hiccup) đều dùng lại proxy đó nếu chưa hết hạn.
    4. Khi hết hạn, pool.release(proxy)acquire() mới.
// sticky-manager.js
class StickySession {
  constructor(pool, ttlMs = 300_000) {
    this.pool = pool;
    this.ttl = ttlMs;
    this.current = null;
    this.expires = 0;
  }
  async get() {
    const now = Date.now();
    if (this.current && now < this.expires) return this.current;
    if (this.current) await this.pool.release(this.current);
    this.current = await this.pool.acquire();
    this.expires = now + this.ttl;
    return this.current;
  }
}
module.exports = StickySession;

Xác thực và bảo mật

  1. Proxy Authentication: Sử dụng username/password do RoProxy cấp. Không hard‑code; lưu trong secret manager hoặc env var.
  2. mTLS (tùy chọn): Nếu GraphQL server yêu cầu client cert, kết hợp tls options trong transport và đảm bảo proxy hỗ trợ CONNECT với TLS passthrough (RoProxy đã bật).
  3. IP Whitelist: Trên RoProxy dashboard, thêm CIDR của VPC/CI runner để chỉ cho phép traffic từ hạ tầng bạn kiểm soát.
  4. Audit Log: Bật logging trên RoProxy để theo dõi IP nào được dùng cho subscription nào – hữu ích cho compliance.

Giám sát và debug

  • Metrics: Export proxy_acquire_total, proxy_release_total, proxy_error_total lên Prometheus (RoProxy SDK đã có hook).
  • Health Check: Chạy cron mỗi 5 phút gọi pool.health_check(); loại proxy lỗi khỏi pool.
  • Logging: Bật debug: true trong Apollo/WebSocket link để xem handshake qua proxy.
  • Replay HAR: Sử dụng mitmproxy kết hợp với proxy để capture handshake WebSocket và replay khi debug lỗi subscription.

Kết luận

Việc đưa Rotating Proxy vào luồng GraphQL Subscriptions qua WebSocket mang lại ba lợi ích cốt lõi: độ tin cậy cao (tránh IP ban), khả năng kiểm thử đa vùng (geo‑testing thực tế) và tuân thủ bảo mật (ẩn IP thật). Các bước chính:

  1. Tạo pool rotating proxy với sticky_ttl phù hợp.
  2. Cấu hình transport WebSocket (Apollo, gql, hoặc custom) để sử dụng proxy agent hỗ trợ HTTP CONNECT.
  3. Quản lý sticky session thủ công hoặc qua wrapper để giữ IP ổn định trong suốt subscription.
  4. Bảo mật bằng auth, mTLS, whitelist và audit log.
  5. Giám sát liên tục để phát hiện proxy down sớm.

Áp dụng mẫu mã trên vào dự án của bạn sẽ cho phép chạy subscription realtime từ hàng chục quốc gia chỉ với vài dòng cấu hình – lý tưởng cho CI/CD, contract testing và sản phẩm SaaS cần độ tin cậy toàn cầu.