[{"data":1,"prerenderedAt":22},["ShallowReactive",2],{"blog:post:vi:cau-hinh-proxy-theo-ten-mien":3},{"slug":4,"lang":5,"title":6,"summary":7,"date":8,"tags":9,"tag_slugs":15,"thumbnail_url":18,"translations":19,"body":20,"asset_base":21},"cau-hinh-proxy-theo-ten-mien","vi","Cấu hình proxy theo tên miền để chỉ định lưu lượng","Hướng dẫn chia lưu lượng theo tên miền qua proxy trong Python, Node.js, cURL và trình duyệt, kèm cách xử lý chuyển hướng, DNS và kiểm thử.","2026-10-21",[10,11,12,13,14],"proxy","định-tuyến","tên-miền","python","nodejs",[10,16,17,13,14],"dinh-tuyen","ten-mien","https://blog-api.ro-proxy.com/api/blog/posts/cau-hinh-proxy-theo-ten-mien/thumbnail.svg?lang=vi",[5],"## Vì sao cần định tuyến proxy theo tên miền?\n\nÁp proxy cho toàn bộ yêu cầu thường làm tăng độ trễ, tiêu hao băng thông và khiến lỗi của một dịch vụ bên ngoài ảnh hưởng đến mọi API trong ứng dụng. Trong thực tế, nhóm dữ liệu có thể cần residential proxy để lấy giá theo thị trường, nhưng vẫn muốn gọi dịch vụ nội bộ trực tiếp để có độ trễ thấp.\n\nĐịnh tuyến theo tên miền giải quyết chính xác vấn đề này: chỉ một danh sách tên miền được đi qua proxy, còn lưu lượng còn lại sử dụng kết nối trực tiếp. Cách này cũng giảm rủi ro làm lộ tên máy chủ nội bộ và tránh gửi yêu cầu chứa dữ liệu nhạy cảm qua một proxy không cần thiết.\n\n### Nguyên tắc xây dựng quy tắc\n\nHãy tuân theo bốn nguyên tắc sau:\n\n1. Khớp chính xác tên miền khi cần một đích cụ thể; dùng tiền tố dấu chấm cho các tên miền con, chẳng hạn `.partner-data.com`.\n2. Luôn ưu tiên quy tắc trực tiếp nếu một tên miền xuất hiện trong cả hai danh sách.\n3. Đánh giá lại quy tắc sau mỗi lần chuyển hướng vì đích mới có thể thuộc một nhóm khác.\n4. Mặc định gửi trực tiếp và chỉ cho phép đi qua proxy những tên miền đã xác định. Cách này an toàn hơn so với chuyển mọi lưu lượng lạ sang proxy.\n\nVí dụ cấu hình:\n\n```bash\nPROXY_DOMAINS=api.vendor.vn,.partner-data.com\nDIRECT_DOMAINS=localhost,127.0.0.1,.internal.company.vn\nRO_PROXY_URL=http://user:password@proxy.example.com:8000\n```\n\nVới cấu hình này, `api.vendor.vn` và mọi tên miền con của `partner-data.com` dùng proxy; API nội bộ vẫn kết nối trực tiếp.\n\n## Cấu hình định tuyến trong Python\n\nThư viện `requests` không tự so sánh tên miền với một danh sách. Ta cần chọn cấu hình proxy trước mỗi yêu cầu và cho phép `requests` tiếp tục chuyển hướng nếu cần.\n\n```python\nimport os\nfrom urllib.parse import urljoin, urlsplit\nimport requests\n\nPROXY_URL = os.environ['RO_PROXY_URL']\nPROXY_DOMAINS = [\n    value.strip().lower()\n    for value in os.environ.get('PROXY_DOMAINS', '').split(',')\n    if value.strip()\n]\nDIRECT_DOMAINS = [\n    value.strip().lower()\n    for value in os.environ.get(\n        'DIRECT_DOMAINS', 'localhost,127.0.0.1'\n    ).split(',')\n    if value.strip()\n]\n\ndef domain_match(host, rule):\n    host = host.lower().rstrip('.')\n    rule = rule.lower().rstrip('.')\n    if rule.startswith('.'):\n        return host == rule[1:] or host.endswith(rule)\n    return host == rule\n\ndef pick_proxies(url):\n    host = urlsplit(url).hostname or ''\n    if any(domain_match(host, rule) for rule in DIRECT_DOMAINS):\n        return None\n    if any(domain_match(host, rule) for rule in PROXY_DOMAINS):\n        return {'http': PROXY_URL, 'https': PROXY_URL}\n    return None\n\ndef routed_get(url, **options):\n    session = requests.Session()\n    session.trust_env = False\n    current = url\n\n    for _ in range(6):\n        response = session.get(\n            current,\n            proxies=pick_proxies(current),\n            allow_redirects=False,\n            timeout=20,\n            **options\n        )\n        if response.is_redirect or response.is_permanent_redirect:\n            location = response.headers.get('Location')\n            if not location:\n                return response\n            current = urljoin(current, location)\n            continue\n        return response\n\n    raise requests.TooManyRedirects('Vượt quá số lần chuyển hướng')\n```\n\nĐặt `trust_env=False` để các biến `HTTP_PROXY` hoặc `HTTPS_PROXY` của môi trường không vô tình thay đổi quy tắc. Khi `pick_proxies()` trả về `None`, yêu cầu sẽ đi trực tiếp. Nếu tài khoản hoặc mật khẩu chứa ký tự đặc biệt, phải mã hóa chúng trước khi đặt trong URL proxy.\n\n## Chia lưu lượng với cURL\n\n`cURL` không tự đọc quy tắc theo tên miền, nhưng có thể dùng một hàm bao quanh lệnh gọi:\n\n```bash\nroute_curl() {\n  local url=$1\n  shift\n  case \"$url\" in\n    https://api.vendor.vn/*|https://partner-data.com/*|https://*.partner-data.com/*)\n      curl --proxy \"$RO_PROXY_URL\" -- \"$url\" \"$@\"\n      ;;\n    *)\n      curl --noproxy '*' -- \"$url\" \"$@\"\n      ;;\n  esac\n}\n```\n\nTham số `--noproxy '*'` buộc lệnh không dùng proxy mặc định của hệ thống. Khi có nhiều tên miền nội bộ, biến `NO_PROXY` thuận tiện hơn, nhưng vẫn nên kiểm tra định tuyến trước khi triển khai.\n\n## Định tuyến trong Node.js\n\nVới `undici`, có thể chọn `ProxyAgent` hoặc `Agent` trực tiếp cho từng yêu cầu. `request()` không tự chuyển hướng, vì vậy vòng lặp bên dưới xử lý chuyển hướng và đánh giá lại tên miền đích.\n\n```javascript\nconst { Agent, ProxyAgent, request } = require('undici')\n\nconst proxyAgent = new ProxyAgent(process.env.RO_PROXY_URL)\nconst directAgent = new Agent()\nconst proxyDomains = (process.env.PROXY_DOMAINS || '')\n  .split(',').map(value => value.trim().toLowerCase()).filter(Boolean)\nconst directDomains = (process.env.DIRECT_DOMAINS || '')\n  .split(',').map(value => value.trim().toLowerCase()).filter(Boolean)\n\nfunction matches(host, rule) {\n  return rule.startsWith('.')\n    ? host === rule.slice(1) || host.endsWith(rule)\n    : host === rule\n}\n\nasync function routedRequest(url) {\n  let current = url\n\n  for (let attempt = 0; attempt \u003C 6; attempt += 1) {\n    const host = new URL(current).hostname.toLowerCase()\n    const isDirect = directDomains.some(rule => matches(host, rule))\n    const isProxied = proxyDomains.some(rule => matches(host, rule))\n    const dispatcher = !isDirect && isProxied\n      ? proxyAgent\n      : directAgent\n\n    const response = await request(current, { dispatcher })\n    const redirect = [301, 302, 303, 307, 308]\n      .includes(response.statusCode)\n\n    if (redirect && response.headers.location) {\n      const next = new URL(response.headers.location, current).toString()\n      await response.body.dump()\n      current = next\n      continue\n    }\n    return response\n  }\n\n  throw new Error('Vượt quá số lần chuyển hướng')\n}\n```\n\nKhi ứng dụng kết thúc, nên đóng cả hai tác nhân bằng `proxyAgent.close()` và `directAgent.close()` để giải phóng kết nối.\n\n## Dùng tệp PAC trong trình duyệt\n\nTệp Proxy Auto Configuration phù hợp khi cần cùng một quy tắc cho nhiều trang hoặc nhiều tác vụ trình duyệt:\n\n```javascript\nfunction FindProxyForURL(url, host) {\n  host = host.toLowerCase()\n\n  if (host === 'localhost' ||\n      host === '127.0.0.1' ||\n      dnsDomainIs(host, '.internal.company.vn')) {\n    return 'DIRECT'\n  }\n\n  if (host === 'api.vendor.vn' ||\n      host === 'partner-data.com' ||\n      shExpMatch(host, '*.partner-data.com')) {\n    return 'PROXY proxy.example.com:8080'\n  }\n\n  return 'DIRECT'\n}\n```\n\nLưu tệp trên máy cục bộ, cung cấp nó qua một máy chủ HTTP nội bộ rồi trỏ trình duyệt đến URL của tệp PAC. Trong môi trường doanh nghiệp, nên triển khai bằng chính sách quản lý trình duyệt. Không nên nhúng tên đăng nhập và mật khẩu trong tệp PAC; hãy dùng cơ chế xác thực của trình duyệt hoặc tiện ích quản lý proxy.\n\n## DNS, bảo mật và chuyển hướng\n\nVới proxy HTTP và kết nối HTTPS qua `CONNECT`, tên miền đích thường được gửi đến proxy để proxy tự phân giải. Khi dùng SOCKS5, nên chọn chế độ `socks5h` để DNS cũng được phân giải ở phía proxy.\n\nTuy nhiên, tên miền được gửi trực tiếp vẫn cần DNS cục bộ. Proxy không thể đồng thời cho phép kết nối trực tiếp và bảo đảm mọi truy vấn DNS đều chạy qua proxy. Nếu chính sách bảo mật yêu cầu DNS tập trung, hãy kết hợp định tuyến với bộ phân giải mã hóa cục bộ hoặc mạng riêng phù hợp.\n\nProxy không làm mất tác dụng của TLS. Với HTTPS, proxy chủ yếu thấy tên miền, cổng và thời gian kết nối; nội dung vẫn được mã hóa nếu không cài chứng thay thế. Không được tắt kiểm tra chứng thương mại, tránh gửi bí mật trong URL và không ghi toàn bộ chuỗi truy vấn vào nhật ký.\n\n## Kiểm thử quy tắc định tuyến\n\nHãy kiểm tra cả tuyến mặc định lẫn địa chỉ IP thoát ra:\n\n```bash\ncurl --noproxy '*' --silent --show-error \\\n  'https://api.ipify.org?format=json'\n\ncurl --proxy \"$RO_PROXY_URL\" --silent --show-error \\\n  'https://api.ipify.org?format=json'\n```\n\nHai kết quả thường phải khác nhau. Nếu giống nhau, hãy kiểm tra biến môi trường, thứ tự ưu tiên quy tắc và việc proxy có thực sự hỗ trợ kết nối hay không.\n\nCác kiểm thử tự động nên bao gồm:\n\n- Tên miền chính xác và tên miền con được gửi qua proxy.\n- Tên miên nội bộ được gửi trực tiếp.\n- Tên miền giống nhưng không phải tên miền con, chẳng hạn `api.vendor.vn.evil.test`, không được khớp.\n- Một chuyển hướng từ tên miền được phép sang tên miền không được phép được đánh giá lại.\n- Proxy trả về `407`, `429`, `502` hoặc hết thời gian chờ không làm ứng dụng gọi lại vô hạn.\n\nGhi lại tên miền, tuyến đã chọn, địa chỉ IP thoát ra, mã trạng thái và độ trễ. Không ghi thông tin xác thực hoặc toàn bộ URL. Cuối cùng, chỉ thu thập dữ liệu được phép, tuân thủ điều khoản dịch vụ, giới hạn tốc độ và chính sách của website đích.\n","https://blog-api.ro-proxy.com/api/blog/posts/cau-hinh-proxy-theo-ten-mien/assets",1792821467145]