proxy-fetch.ts 2.1 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. /**
  2. * Proxy-aware fetch for server-side use.
  3. *
  4. * Automatically routes requests through HTTP/HTTPS proxy when
  5. * the standard environment variables are set:
  6. * - https_proxy / HTTPS_PROXY
  7. * - http_proxy / HTTP_PROXY
  8. *
  9. * Node.js's built-in fetch does NOT respect these env vars,
  10. * so we use undici's ProxyAgent when a proxy is configured.
  11. *
  12. * Usage: import { proxyFetch } from '@/lib/server/proxy-fetch';
  13. * const res = await proxyFetch('https://api.openai.com/v1/...', { ... });
  14. */
  15. import { ProxyAgent, fetch as undiciFetch, type RequestInit as UndiciRequestInit } from 'undici';
  16. import { createLogger } from '@/lib/logger';
  17. const log = createLogger('ProxyFetch');
  18. function getProxyUrl(): string | undefined {
  19. return (
  20. process.env.https_proxy ||
  21. process.env.HTTPS_PROXY ||
  22. process.env.http_proxy ||
  23. process.env.HTTP_PROXY ||
  24. undefined
  25. );
  26. }
  27. let cachedAgent: ProxyAgent | null = null;
  28. let cachedProxyUrl: string | undefined;
  29. function getProxyAgent(): ProxyAgent | undefined {
  30. const proxyUrl = getProxyUrl();
  31. if (!proxyUrl) return undefined;
  32. // Reuse agent if proxy URL hasn't changed
  33. if (cachedAgent && cachedProxyUrl === proxyUrl) {
  34. return cachedAgent;
  35. }
  36. cachedAgent = new ProxyAgent(proxyUrl);
  37. cachedProxyUrl = proxyUrl;
  38. return cachedAgent;
  39. }
  40. /**
  41. * Drop-in replacement for fetch() that respects proxy env vars.
  42. * Falls back to global fetch when no proxy is configured.
  43. */
  44. export async function proxyFetch(input: string | URL, init?: RequestInit): Promise<Response> {
  45. const agent = getProxyAgent();
  46. const url = typeof input === 'string' ? input : input.toString();
  47. if (!agent) {
  48. log.info('No proxy configured, using direct fetch for:', url.slice(0, 80));
  49. return fetch(input, init);
  50. }
  51. log.info('Using proxy', cachedProxyUrl, 'for:', url.slice(0, 80));
  52. // Use undici's fetch with the proxy dispatcher
  53. const res = await undiciFetch(input, {
  54. ...(init as UndiciRequestInit),
  55. dispatcher: agent,
  56. });
  57. // undici's Response is compatible with the global Response
  58. return res as unknown as Response;
  59. }