[{"data":1,"prerenderedAt":20},["ShallowReactive",2],{"blog:post:vi:cau-hinh-proxy-graphql-ws-rotating":3},{"slug":4,"lang":5,"title":6,"summary":7,"date":8,"tags":9,"tag_slugs":15,"thumbnail_url":16,"translations":17,"body":18,"asset_base":19},"cau-hinh-proxy-graphql-ws-rotating","vi","Cấu hình Proxy cho GraphQL Subscriptions qua WebSocket với Rotating Proxy","Hướng dẫn chi tiết cấu hình rotating residential proxy cho GraphQL Subscriptions qua WebSocket, bao gồm sticky session, xác thực và monitoring.","2026-09-16",[10,11,12,13,14],"graphql","websocket","proxy","rotating","subscriptions",[10,11,12,13,14],"https://blog-api.ro-proxy.com/api/blog/posts/cau-hinh-proxy-graphql-ws-rotating/thumbnail.svg?lang=vi",[5],"## Giới thiệu\nGraphQL 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 để:\n- Đảm bảo mỗi kết nối WebSocket xuất phát từ IP khác nhau.\n- Giữ sticky session đủ lâu cho subscription sống sót.\n- Xử lý xác thực proxy và tự động thay thế khi proxy lỗi.\n\n## Tại sao cần Proxy cho GraphQL Subscriptions?\n1. **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ý.\n2. **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.\n3. **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ủ.\n4. **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.\n\n## Kiến trúc tổng quan\n```\n+----------------+      +-------------------+      +-------------------+\n|  Client App    | ---> |  RoProxy Rotating | ---> |  GraphQL Server   |\n| (Node/Python)  |      |  Proxy Pool       |      | (WebSocket)       |\n+----------------+      +-------------------+      +-------------------+\n```\n- **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).\n- **Client GraphQL**: Cấu hình transport WebSocket để đi qua proxy (HTTP CONNECT cho WS).\n- **Sticky Session Manager**: Giữ cùng một IP trong vòng `session_ttl` giây cho một subscription.\n\n## Chuẩn bị môi trường\n### Cài đặt RoProxy SDK\n```bash\nnpm i @roproxy/sdk   # Node.js\npip install roproxy-sdk   # Python\n```\n### Tạo rotating proxy pool\n```js\n// Node.js – proxy-pool.js\nconst { RoProxy } = require('@roproxy/sdk');\n\nconst pool = new RoProxy.Pool({\n  gateway: 'gateway.roproxy.com',\n  username: process.env.ROPROXY_USER,\n  password: process.env.ROPROXY_PASS,\n  // Mỗi proxy sống 5 phút, sau đó tự động thay IP mới\n  stickyTtl: 300_000,\n  maxConcurrent: 20\n});\n\nmodule.exports = pool;\n```\n```python\n# Python – proxy_pool.py\nimport os\nfrom roproxy import Pool\n\npool = Pool(\n    gateway='gateway.roproxy.com',\n    username=os.getenv('ROPROXY_USER'),\n    password=os.getenv('ROPROXY_PASS'),\n    sticky_ttl=300,          # giây\n    max_concurrent=20\n)\n```\n\n## Cấu hình client GraphQL (Apollo Client) với proxy\n### Ví dụ Node.js\n```js\n// apollo-proxy-setup.js\nconst { ApolloClient, InMemoryCache, split, HttpLink } = require('@apollo/client');\nconst { WebSocketLink } = require('@apollo/client/link/ws');\nconst { getMainDefinition } = require('@apollo/client/utilities');\nconst { createProxyAgent } = require('proxy-agent'); // hỗ trợ HTTP CONNECT cho WS\nconst pool = require('./proxy-pool');\n\n// Hàm lấy proxy URL cho một request/subscription\nasync function getProxyAgent() {\n  const proxy = await pool.acquire(); // trả về { host, port, auth }\n  const proxyUrl = `http://${proxy.auth.username}:${proxy.auth.password}@${proxy.host}:${proxy.port}`;\n  return createProxyAgent(proxyUrl);\n}\n\n// HTTP link cho query/mutation (tùy chọn)\nconst httpLink = new HttpLink({\n  uri: 'https://api.example.com/graphql',\n  fetch: async (url, opts) => {\n    const agent = await getProxyAgent();\n    return fetch(url, { ...opts, agent });\n  }\n});\n\n// WebSocket link cho subscription\nconst wsLink = new WebSocketLink({\n  uri: 'wss://api.example.com/graphql',\n  options: {\n    reconnect: true,\n    connectionParams: async () => ({\n      Authorization: `Bearer ${await getAccessToken()}`\n    }),\n    // Tự định nghĩa WebSocket constructor để inject proxy agent\n    webSocketImpl: class ProxiedWebSocket extends WebSocket {\n      constructor(url, protocols) {\n        super(url, protocols);\n      }\n      // Ghi đè phương thức connect để dùng proxy agent\n      static async connect(url, protocols, options) {\n        const agent = await getProxyAgent();\n        // proxy-agent hỗ trợ WS qua HTTP CONNECT\n        const ws = new WebSocket(url, protocols);\n        // agent sẽ được dùng bởi thư viện ws nội bộ nếu set `agent` option\n        return ws;\n      }\n    }\n  }\n});\n\n// Tách luồng: subscription dùng wsLink, còn lại dùng httpLink\nconst link = split(\n  ({ query }) => {\n    const def = getMainDefinition(query);\n    return def.kind === 'OperationDefinition' && def.operation === 'subscription';\n  },\n  wsLink,\n  httpLink\n);\n\nconst client = new ApolloClient({\n  link,\n  cache: new InMemoryCache()\n});\n\nmodule.exports = client;\n```\n\n### Ví dụ Python (gql + aiohttp)\n```python\n# graphql_client.py\nimport asyncio\nimport os\nfrom gql import Client, gql\nfrom gql.transport.aiohttp import AIOHTTPTransport\nfrom gql.transport.websockets import WebsocketsTransport\nfrom roproxy import Pool\n\npool = Pool(\n    gateway='gateway.roproxy.com',\n    username=os.getenv('ROPROXY_USER'),\n    password=os.getenv('ROPROXY_PASS'),\n    sticky_ttl=300,\n    max_concurrent=20\n)\n\nasync def get_proxy_url() -> str:\n    proxy = await pool.acquire()\n    return f\"http://{proxy.auth.username}:{proxy.auth.password}@{proxy.host}:{proxy.port}\"\n\nasync def make_transports():\n    proxy_url = await get_proxy_url()\n    # HTTP transport cho query/mutation\n    http_transport = AIOHTTPTransport(\n        url=\"https://api.example.com/graphql\",\n        headers={\"Authorization\": f\"Bearer {await get_access_token()}\"},\n        proxy=proxy_url\n    )\n    # WebSocket transport cho subscription\n    ws_transport = WebsocketsTransport(\n        url=\"wss://api.example.com/graphql\",\n        init_payload={\"Authorization\": f\"Bearer {await get_access_token()}\"},\n        proxy=proxy_url\n    )\n    return http_transport, ws_transport\n\nasync def main():\n    http_transport, ws_transport = await make_transports()\n    # Client cho query/mutation\n    http_client = Client(transport=http_transport, fetch_schema_from_transport=True)\n    # Client cho subscription (gql hỗ trợ async subscription)\n    ws_client = Client(transport=ws_transport, fetch_schema_from_transport=True)\n\n    # Ví dụ subscription\n    SUB = gql(\"\"\"\n    subscription OnPriceChanged($symbol: String!) {\n      priceChanged(symbol: $symbol) {\n        symbol\n        price\n        timestamp\n      }\n    }\n    \"\"\")\n    async for result in ws_client.subscribe_async(SUB, variable_values={\"symbol\": \"AAPL\"}):\n        print(result)\n\nif __name__ == \"__main__\":\n    asyncio.run(main())\n```\n\n## Xử lý Sticky Session cho Subscription\n- **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ờ).\n- **Cách thực hiện**:\n  1. Khi `pool.acquire()` được gọi, RoProxy trả về một proxy với `sticky_ttl`.\n  2. Client lưu trữ proxy object cùng với `expires_at = now + sticky_ttl`.\n  3. Mọi lần reconnect WebSocket (do network hiccup) đều dùng lại proxy đó nếu chưa hết hạn.\n  4. Khi hết hạn, `pool.release(proxy)` và `acquire()` mới.\n\n```js\n// sticky-manager.js\nclass StickySession {\n  constructor(pool, ttlMs = 300_000) {\n    this.pool = pool;\n    this.ttl = ttlMs;\n    this.current = null;\n    this.expires = 0;\n  }\n  async get() {\n    const now = Date.now();\n    if (this.current && now \u003C this.expires) return this.current;\n    if (this.current) await this.pool.release(this.current);\n    this.current = await this.pool.acquire();\n    this.expires = now + this.ttl;\n    return this.current;\n  }\n}\nmodule.exports = StickySession;\n```\n\n## Xác thực và bảo mật\n1. **Proxy Authentication**: Sử dụng username/password do RoProxy cấp. Không hard‑code; lưu trong secret manager hoặc env var.\n2. **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).\n3. **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.\n4. **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.\n\n## Giám sát và debug\n- **Metrics**: Export `proxy_acquire_total`, `proxy_release_total`, `proxy_error_total` lên Prometheus (RoProxy SDK đã có hook).\n- **Health Check**: Chạy cron mỗi 5 phút gọi `pool.health_check()`; loại proxy lỗi khỏi pool.\n- **Logging**: Bật `debug: true` trong Apollo/WebSocket link để xem handshake qua proxy.\n- **Replay HAR**: Sử dụng `mitmproxy` kết hợp với proxy để capture handshake WebSocket và replay khi debug lỗi subscription.\n\n## Kết luận\nViệ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:\n1. Tạo pool rotating proxy với `sticky_ttl` phù hợp.\n2. Cấu hình transport WebSocket (Apollo, gql, hoặc custom) để sử dụng proxy agent hỗ trợ HTTP CONNECT.\n3. Quản lý sticky session thủ công hoặc qua wrapper để giữ IP ổn định trong suốt subscription.\n4. Bảo mật bằng auth, mTLS, whitelist và audit log.\n5. Giám sát liên tục để phát hiện proxy down sớm.\n\nÁ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.\n","https://blog-api.ro-proxy.com/api/blog/posts/cau-hinh-proxy-graphql-ws-rotating/assets",1790057931907]