Neople LogoNeople SDK JS

Node-fetch 어댑터

Node-fetch HTTP 클라이언트 어댑터 설정 및 사용법

Node-fetch 어댑터

Node-fetch를 사용하는 어댑터입니다. 브라우저의 Fetch API와 호환되는 Node.js용 구현체입니다.

설치

먼저 node-fetch를 설치해야 합니다:

npm install node-fetch
# 또는
yarn add node-fetch

# TypeScript 사용 시
npm install @types/node-fetch

기본 사용법

import { NeopleDFClient, NeopleCyphersClient } from 'neople-sdk-js';

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
});

사용자 지정 설정

import fetch from 'node-fetch';
import { NeopleDFClient } from 'neople-sdk-js';

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  fetchInstance: fetch,
  fetchOptions: {
    timeout: 30000,
    headers: {
      'User-Agent': 'MyApp/1.0.0',
    },
  },
});

HTTP Agent 설정

Keep-Alive 연결

import fetch from 'node-fetch';
import http from 'http';
import https from 'https';
import { NeopleDFClient } from 'neople-sdk-js';

const httpAgent = new http.Agent({
  keepAlive: true,
  maxSockets: 10,
});

const httpsAgent = new https.Agent({
  keepAlive: true,
  maxSockets: 10,
});

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  fetchOptions: {
    agent: parsedURL => {
      return parsedURL.protocol === 'http:' ? httpAgent : httpsAgent;
    },
  },
});

프록시 설정

npm install https-proxy-agent
import fetch from 'node-fetch';
import { HttpsProxyAgent } from 'https-proxy-agent';
import { NeopleDFClient } from 'neople-sdk-js';

const proxyAgent = new HttpsProxyAgent('http://proxy.example.com:8080');

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  fetchOptions: {
    agent: proxyAgent,
  },
});

타임아웃 설정

AbortController 사용

import fetch from 'node-fetch';
import { NeopleDFClient } from 'neople-sdk-js';

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
});

// 요청별 타임아웃
async function searchWithTimeout(name: string, timeoutMs: number = 10000) {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  try {
    const result = await client.searchCharacter(name, {
      signal: controller.signal,
    });
    clearTimeout(timeoutId);
    return result;
  } catch (error) {
    clearTimeout(timeoutId);
    if (error.name === 'AbortError') {
      throw new Error('요청 타임아웃');
    }
    throw error;
  }
}

전역 타임아웃

import fetch from 'node-fetch';
import { NeopleDFClient } from 'neople-sdk-js';

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  fetchOptions: {
    timeout: 30000, // 30초
  },
});

스트리밍

응답 스트리밍

import fetch from 'node-fetch';
import { NeopleDFClient } from 'neople-sdk-js';
import { createWriteStream } from 'fs';
import { pipeline } from 'stream';

async function downloadStream(url: string, filePath: string) {
  const response = await fetch(url);

  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
  }

  const writeStream = createWriteStream(filePath);

  return new Promise((resolve, reject) => {
    pipeline(response.body, writeStream, error => {
      if (error) {
        reject(error);
      } else {
        resolve(void 0);
      }
    });
  });
}

쿠키 지원

npm install tough-cookie
import fetch from 'node-fetch';
import { CookieJar } from 'tough-cookie';
import { NeopleDFClient } from 'neople-sdk-js';

const cookieJar = new CookieJar();

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  fetchOptions: {
    headers: {
      Cookie: await cookieJar.getCookieString('https://api.neople.co.kr'),
    },
  },
});

에러 처리

상세한 에러 정보

import fetch from 'node-fetch';
import { NeopleDFClient } from 'neople-sdk-js';

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  onError: error => {
    if (error.name === 'FetchError') {
      console.error('Fetch 에러:', {
        message: error.message,
        type: error.type,
        errno: error.errno,
        code: error.code,
      });
    } else if (error.name === 'AbortError') {
      console.error('요청이 중단됨');
    } else {
      console.error('알 수 없는 에러:', error);
    }
  },
});

HTTP 상태 에러

import fetch from 'node-fetch';
import { NeopleDFClient } from 'neople-sdk-js';

class HTTPError extends Error {
  constructor(
    public status: number,
    public statusText: string,
    public response: Response
  ) {
    super(`HTTP ${status}: ${statusText}`);
    this.name = 'HTTPError';
  }
}

const customFetch = async (url: string, options: any) => {
  const response = await fetch(url, options);

  if (!response.ok) {
    throw new HTTPError(response.status, response.statusText, response);
  }

  return response;
};

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  fetchInstance: customFetch,
});

재시도 로직

지수 백오프

import fetch from 'node-fetch';
import { NeopleDFClient } from 'neople-sdk-js';

async function fetchWithRetry(
  url: string,
  options: any,
  maxRetries: number = 3
): Promise<any> {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      const response = await fetch(url, options);

      if (response.ok) {
        return response;
      }

      // 재시도 가능한 상태 코드 확인
      if (response.status >= 500 || response.status === 429) {
        if (attempt === maxRetries) {
          throw new Error(
            `HTTP ${response.status} after ${maxRetries} attempts`
          );
        }

        // 지수 백오프 대기
        const delay = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
        await new Promise(resolve => setTimeout(resolve, delay));
        continue;
      }

      throw new Error(`HTTP ${response.status}: ${response.statusText}`);
    } catch (error) {
      if (attempt === maxRetries) {
        throw error;
      }

      // 네트워크 오류에 대한 재시도
      const delay = Math.min(1000 * Math.pow(2, attempt - 1), 30000);
      await new Promise(resolve => setTimeout(resolve, delay));
    }
  }
}

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  fetchInstance: fetchWithRetry,
});

로깅 및 디버깅

요청/응답 로깅

import fetch from 'node-fetch';
import { NeopleDFClient } from 'neople-sdk-js';

const debugFetch = async (url: string, options: any) => {
  console.log('🚀 요청:', {
    url,
    method: options?.method || 'GET',
    headers: options?.headers,
  });

  const startTime = Date.now();

  try {
    const response = await fetch(url, options);
    const duration = Date.now() - startTime;

    console.log('✅ 응답:', {
      status: response.status,
      statusText: response.statusText,
      duration: `${duration}ms`,
      headers: Object.fromEntries(response.headers.entries()),
    });

    return response;
  } catch (error) {
    const duration = Date.now() - startTime;

    console.error('❌ 에러:', {
      error: error.message,
      duration: `${duration}ms`,
    });

    throw error;
  }
};

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  fetchInstance: debugFetch,
});

성능 최적화

Connection Pooling

import fetch from 'node-fetch';
import http from 'http';
import https from 'https';
import { NeopleDFClient } from 'neople-sdk-js';

// 글로벌 에이전트 설정
const httpAgent = new http.Agent({
  keepAlive: true,
  maxSockets: 20,
  maxFreeSockets: 10,
  timeout: 60000,
  freeSocketTimeout: 30000,
});

const httpsAgent = new https.Agent({
  keepAlive: true,
  maxSockets: 20,
  maxFreeSockets: 10,
  timeout: 60000,
  freeSocketTimeout: 30000,
});

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  fetchOptions: {
    agent: parsedURL => {
      return parsedURL.protocol === 'http:' ? httpAgent : httpsAgent;
    },
  },
});

요청 병렬 처리

import fetch from 'node-fetch';
import pLimit from 'p-limit';
import { NeopleDFClient } from 'neople-sdk-js';

const limit = pLimit(10); // 최대 10개 동시 요청

async function fetchMultipleCharacters(names: string[]) {
  const client = new NeopleDFClient(apiKey, {
    httpAdapter: 'node-fetch',
  });

  const promises = names.map(name => limit(() => client.searchCharacter(name)));

  try {
    return await Promise.all(promises);
  } catch (error) {
    console.error('병렬 요청 중 오류:', error);
    throw error;
  }
}

TypeScript 지원

타입 안전한 래퍼

import fetch, { Response } from 'node-fetch';
import { NeopleDFClient } from 'neople-sdk-js';

interface TypedResponse<T> extends Response {
  json(): Promise<T>;
}

async function typedFetch<T>(
  url: string,
  options?: any
): Promise<TypedResponse<T>> {
  const response = await fetch(url, options);
  return response as TypedResponse<T>;
}

const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  fetchInstance: typedFetch,
});

마이그레이션

내장 Fetch에서 Node-fetch로

// Before (내장 Fetch - Node.js 18+)
const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'fetch',
});

// After (Node-fetch - 하위 호환)
const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
});

Axios에서 Node-fetch로

// Before (Axios)
const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'axios',
  axiosInstance: axios.create({
    timeout: 30000,
    headers: {
      'User-Agent': 'MyApp/1.0.0',
    },
  }),
});

// After (Node-fetch)
const client = new NeopleDFClient(apiKey, {
  httpAdapter: 'node-fetch',
  fetchOptions: {
    timeout: 30000,
    headers: {
      'User-Agent': 'MyApp/1.0.0',
    },
  },
});