외부 API를 호출할 때 PHP 개발자들은 보통 두 가지 방법을 떠올린다. file_get_contents() 함수로 간단하게 처리하거나, cURL 라이브러리를 사용하는 것이다. 다만 많은 개발자들은 둘의 차이를 제대로 알지 못한 채 상황에 맞지 않는 도구를 선택해서 나중에 본운영 서버에서 타임아웃이나 SSL 오류를 겪는다. 이번에는 두 방식의 정확한 동작 원리와 언제 어떤 방식을 써야 하는지, 실제 코드로 어떻게 구현하는지 완벽하게 정리해서 소개하겠다.

 

1단계. 기초 개념 이해하기

file_get_contents()는 PHP에 내장된 파일/스트림 읽기 함수다. 로컬 파일을 읽을 때도 쓰지만, 'allow_url_fopen' 설정을 활성화하면 HTTP/HTTPS URL도 직접 읽을 수 있다. 코드가 짧고 간단해서 작은 API 호출에는 매력적이다.

cURL은 CLI 기반 데이터 전송 도구인데, PHP의 curl 익스텐션을 통해 프로그래밍 방식으로 제어할 수 있다. 요청 헤더, 타임아웃, SSL 검증, 리다이렉트 등을 세밀하게 조정할 수 있어서 본운영 환경에서는 거의 필수다.

 

주요 차이점 한눈에 보기
항목 file_get_contents() cURL
코드 간결성 매우 짧음(1줄) 보일러플레이트 필요
타임아웃 제어 제한적(context 필요) 매우 세밀함(timeout 옵션)
리다이렉트 자동 처리 없음(별도 구현) FOLLOWLOCATION 옵션으로 자동
SSL/TLS 검증 기본값이 검증 안함 기본값이 검증함(안전)
성능 약간 느림(컨텍스트 오버헤드) 더 빠름(최적화됨)
프로덕션 권장 아니오 예

 

2단계. file_get_contents()의 문제점과 기초 사용법

file_get_contents()는 언뜻 편해 보이지만, 프로덕션 환경에서는 여러 문제를 일으킨다.

✗ 잘못된 코드(문제 있음)

<?php
// 단순히 이렇게만 쓰면 안 된다
$response = file_get_contents('https://api.example.com/data');
echo $response;
?>

문제점. 타임아웃이 설정되지 않아서 응답이 없으면 script가 무한 대기한다. allow_url_fopen이 비활성화되어 있으면 FALSE를 반환할 뿐 에러 메시지도 없다. SSL 인증서 검증을 하지 않아서 중간자 공격(MITM)에 취약하다. 리다이렉트(301/302)를 따라가지 않는다.

✓ 올바른 코드(file_get_contents 사용 시)

<?php
$url = 'https://api.example.com/data';

// 타임아웃과 SSL 검증을 포함한 컨텍스트 생성
$context = stream_context_create([
    'http' => [
        'timeout' => 5,
        'method' => 'GET',
        'header' => "Authorization: Bearer token123\r\n",
    ],
    'ssl' => [
        'verify_peer' => true,
        'verify_peer_name' => true,
    ],
]);

$response = @file_get_contents($url, false, $context);

if ($response === false) {
    echo 'API 호출 실패';
    exit;
}

echo $response;
?>

이렇게 stream_context_create()로 컨텍스트를 만들어서 타임아웃과 SSL 검증을 설정해야 한다. 하지만 이 정도 코드면 이미 cURL만큼 복잡해진다.

 

3단계. cURL을 사용한 올바른 API 호출

cURL은 프로덕션 환경에서 필수다. 타임아웃, SSL 검증, 리다이렉트, 헤더 제어, 응답 코드 확인까지 모두 가능하다.

✓ 올바른 코드(기본 GET 요청)

<?php
function callApi($url, $headers = []) {
    $ch = curl_init();
    
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 5,
        CURLOPT_CONNECTTIMEOUT => 3,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_SSL_VERIFYHOST => 2,
        CURLOPT_HTTPHEADER => array_merge([
            'User-Agent: MyApp/1.0',
            'Accept: application/json',
        ], $headers),
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $error = curl_error($ch);
    
    curl_close($ch);
    
    if ($error) {
        throw new Exception('cURL Error: ' . $error);
    }
    
    if ($httpCode >= 400) {
        throw new Exception('HTTP Error ' . $httpCode . ': ' . $response);
    }
    
    return $response;
}

try {
    $data = callApi('https://api.example.com/data', [
        'Authorization: Bearer token123',
    ]);
    echo $data;
} catch (Exception $e) {
    echo 'API 호출 실패: ' . $e->getMessage();
}
?>

주요 옵션 설명. CURLOPT_RETURNTRANSFER는 true로 설정해야 응답을 변수로 반환한다(false면 echo로 출력됨). CURLOPT_TIMEOUT은 전체 작업 타임아웃(초), CURLOPT_CONNECTTIMEOUT은 연결만의 타임아웃이다. FOLLOWLOCATION으로 리다이렉트를 자동으로 따라간다. SSL_VERIFYPEER와 SSL_VERIFYHOST는 HTTPS 검증을 하므로 프로덕션에서는 항상 true로 둬야 한다.

 

POST 요청과 JSON 데이터 전송

✓ 올바른 코드(POST with JSON)

<?php
function postApi($url, $data, $headers = []) {
    $ch = curl_init();
    $jsonData = json_encode($data);
    
    curl_setopt_array($ch, [
        CURLOPT_URL => $url,
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $jsonData,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 5,
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_HTTPHEADER => array_merge([
            'Content-Type: application/json',
            'Content-Length: ' . strlen($jsonData),
        ], $headers),
    ]);
    
    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
    curl_close($ch);
    
    if (curl_errno($ch)) {
        throw new Exception('cURL Error: ' . curl_error($ch));
    }
    
    if ($httpCode !== 200 && $httpCode !== 201) {
        throw new Exception('HTTP ' . $httpCode . ': ' . $response);
    }
    
    return json_decode($response, true);
}

try {
    $result = postApi('https://api.example.com/create', [
        'name' => 'John',
        'email' => 'john@example.com',
    ], [
        'Authorization: Bearer token123',
    ]);
    var_dump($result);
} catch (Exception $e) {
    echo 'API 호출 실패: ' . $e->getMessage();
}
?>

 

4단계. 주의사항과 흔한 실수

✗ 실수 1. SSL 검증을 무시하기

<?php
// 절대 하지 말 것
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
?>

이렇게 하면 중간자 공격에 노출된다. 개발 환경에서만 필요하고 프로덕션에서는 항상 true여야 한다. 만약 자체 서명 인증서를 써야 한다면 CAINFO 옵션으로 ca-bundle.crt 경로를 지정하는 게 올바른 방법이다.

✓ 올바른 방법

<?php
// PHP에 포함된 기본 CA 번들 사용
curl_setopt($ch, CURLOPT_CAINFO, '/etc/ssl/certs/ca-bundle.crt');

// 또는 Composer의 cacert 패키지 사용
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . '/vendor/cacert/cacert.pem');
?>

✗ 실수 2. 타임아웃 설정 안 함

<?php
// 타임아웃 없으면 응답이 30초 이상 안 오면 기본값으로 대기
// 호출자 입장에서는 script가 멈춘 것 같이 보임
$response = file_get_contents($url);
?>

항상 CONNECTTIMEOUT(연결)과 TIMEOUT(전체)을 동시에 설정해야 한다. 일반적으로 연결은 3~5초, 전체는 10~30초 정도가 적당하다.

✗ 실수 3. HTTP 응답 코드를 확인하지 않음

<?php
// 이렇게 하면 400/500 에러도 성공으로 취급
$response = curl_exec($ch);
echo $response; // 에러 메시지가 담겨있을 수 있음
?>

✓ 항상 HTTP 코드를 확인

<?php
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($httpCode !== 200) {
    // 에러 처리
    throw new Exception('API 호출 실패: HTTP ' . $httpCode);
}
?>

 

5단계. 성능 비교와 최종 선택 기준

간단한 측정 결과, cURL은 file_get_contents()보다 평균 10~20% 더 빠르다. 이유는 cURL이 C 레벨에서 최적화되어 있고, file_get_contents()는 stream context 오버헤드를 갖고 있기 때문이다.

언제 뭘 쓸까. file_get_contents()는 오직 개발 환경의 간단한 테스트에만 쓴다. 프로덕션 코드는 항상 cURL을 써야 한다. 대규모 API 호출이 필요하면 비동기 라이브러리(Guzzle, Curl 멀티, ReactPHP 등)를 고려하되, 작은 규모라면 위의 기본 cURL 패턴으로 충분하다.

 

마무리

PHP에서 외부 API를 호출할 때 file_get_contents()와 cURL 중 어떤 걸 고를지는 상황에 따라 갈린다. 다만 프로덕션 환경에서는 타임아웃 제어, SSL 검증, 에러 처리가 필수이므로 cURL이 사실상 유일한 선택지다. file_get_contents()는 코드가 짧지만, 그만큼 제어할 수 없는 부분이 많아서 나중에 API 타임아웃이나 보안 문제로 돌아온다. 이 글의 cURL 기본 패턴을 저장해두고, 앞으로 모든 외부 API 호출에 적용하면 예측 불가능한 오류를 대부분 방지할 수 있을 것이다.