C언어에서 libcurl을 사용한 REST API 클라이언트 구현

C언어에서 REST API를 사용하려면 HTTP 요청을 보내고 응답을 처리할 수 있는 라이브러리가 필요합니다. 대표적인 라이브러리로 libcurl이 있으며, 이를 활용하면 GET, POST, PUT, DELETE 등의 HTTP 요청을 손쉽게 수행할 수 있습니다.

본 기사에서는 libcurl을 사용하여 REST API 클라이언트를 구현하는 방법을 다룹니다. 기본적인 설치 방법부터 GET 및 POST 요청 처리, JSON 데이터 전송 및 응답 처리, 오류 디버깅 방법까지 상세히 설명합니다. 또한, 실제 REST API와 상호작용하는 예제를 제공하여 실전 활용에 도움을 드립니다.

이 글을 읽고 나면 C언어에서 REST API와 통신하는 클라이언트를 직접 구현할 수 있으며, 다양한 네트워크 기반 애플리케이션을 개발하는 데 필요한 기초 지식을 쌓을 수 있을 것입니다.

libcurl 소개 및 설치 방법

libcurl이란?

libcurl은 다양한 프로토콜(HTTP, HTTPS, FTP 등)을 지원하는 강력한 네트워크 통신 라이브러리입니다. C언어로 작성된 프로그램에서 쉽게 REST API와 통신할 수 있도록 도와줍니다. libcurl을 사용하면 GET, POST, PUT, DELETE와 같은 HTTP 요청을 쉽게 보낼 수 있으며, SSL/TLS 인증, 사용자 인증, 쿠키 관리 등의 기능도 제공합니다.

libcurl의 주요 기능

  • 다양한 프로토콜 지원: HTTP, HTTPS, FTP, SCP, SMTP 등
  • HTTP 요청 지원: GET, POST, PUT, DELETE, PATCH
  • SSL/TLS 지원: HTTPS 요청 가능
  • 사용자 인증: 기본 인증(Basic Auth), 토큰 인증(Bearer Token)
  • 쿠키 및 세션 관리
  • 프록시 서버 지원

libcurl 설치 방법

1. 리눅스(Ubuntu/Debian)

리눅스에서는 apt 패키지 관리자를 이용해 libcurl을 쉽게 설치할 수 있습니다.

sudo apt update
sudo apt install libcurl4-openssl-dev
2. CentOS/RHEL

CentOS와 RHEL에서는 yum을 이용해 libcurl을 설치할 수 있습니다.

sudo yum install libcurl-devel
3. macOS (Homebrew 사용)

macOS 사용자는 Homebrew를 이용해 설치할 수 있습니다.

brew install curl
4. Windows

Windows에서는 vcpkg 또는 MSYS2를 이용해 libcurl을 설치할 수 있습니다.

vcpkg 이용

vcpkg install curl

MSYS2 이용

pacman -S mingw-w64-x86_64-curl
5. 소스 코드에서 직접 빌드

libcurl을 직접 빌드하려면 공식 웹사이트에서 최신 소스를 다운로드한 후 다음과 같이 컴파일하면 됩니다.

./configure --with-ssl
make
sudo make install

설치 확인

libcurl이 올바르게 설치되었는지 확인하려면 다음 명령을 실행합니다.

curl-config --version

위 명령을 실행하면 libcurl의 버전이 출력됩니다. 만약 오류가 발생하면 환경 변수를 설정해야 할 수도 있습니다.

이제 libcurl이 준비되었으므로, 다음 단계에서는 기본적인 HTTP 요청 방식(GET, POST 등)에 대해 설명합니다.

HTTP 요청 기본 개념

REST API와 상호작용하려면 HTTP 요청의 개념을 이해해야 합니다. HTTP 요청은 웹 서버와 데이터를 주고받는 방식이며, RESTful API는 이를 활용하여 클라이언트와 서버 간 통신을 수행합니다.

HTTP 요청의 주요 메서드

메서드설명
GET서버에서 데이터를 조회할 때 사용 (예: 게시글 목록 조회)
POST서버에 새로운 데이터를 추가할 때 사용 (예: 회원 가입)
PUT기존 데이터를 수정할 때 사용 (예: 프로필 정보 수정)
DELETE데이터를 삭제할 때 사용 (예: 계정 삭제)
1. GET 요청
  • 데이터를 가져올 때 사용
  • 요청 본문(Body)이 필요하지 않음
  • 예제: https://api.example.com/users
GET /users HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>
2. POST 요청
  • 데이터를 서버에 전송하여 새로운 리소스를 생성할 때 사용
  • 본문(Body)에 JSON 데이터를 포함
POST /users HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <token>

{
  "name": "John Doe",
  "email": "john@example.com"
}
3. PUT 요청
  • 기존 리소스를 수정할 때 사용
  • 전체 데이터를 업데이트할 경우 활용
PUT /users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer <token>

{
  "name": "John Doe Updated",
  "email": "john.updated@example.com"
}
4. DELETE 요청
  • 특정 데이터를 삭제할 때 사용
  • 본문(Body)은 필요하지 않음
DELETE /users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer <token>

HTTP 응답 구조

서버는 클라이언트의 요청을 처리한 후 HTTP 응답을 반환합니다. 주요 요소는 다음과 같습니다.

  • 상태 코드(Status Code): 요청 성공 여부 표시
  • 응답 헤더(Headers): 응답 메타데이터 포함
  • 응답 본문(Body): 요청한 데이터 포함
주요 HTTP 상태 코드
상태 코드의미
200 OK요청이 성공적으로 처리됨
201 Created리소스가 성공적으로 생성됨 (POST 요청)
400 Bad Request요청이 잘못됨 (잘못된 파라미터 포함 등)
401 Unauthorized인증이 필요함
403 Forbidden권한이 부족함
404 Not Found요청한 리소스를 찾을 수 없음
500 Internal Server Error서버 내부 오류 발생

다음 단계에서는 libcurl을 사용하여 GET 요청을 수행하는 방법을 설명합니다.

libcurl을 사용한 GET 요청

GET 요청 개요

GET 요청은 REST API에서 가장 많이 사용되는 요청 방식으로, 데이터를 조회하는 데 사용됩니다. libcurl을 이용하면 간단한 코드로 GET 요청을 수행할 수 있습니다.

기본 GET 요청 예제

아래 코드는 https://jsonplaceholder.typicode.com/posts/1에 GET 요청을 보내고 응답을 출력하는 예제입니다.

#include <stdio.h>
#include <curl/curl.h>

// 응답 데이터를 저장할 함수
size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
    size_t total_size = size * nmemb;
    fwrite(ptr, size, nmemb, stdout); // 응답 데이터를 화면에 출력
    return total_size;
}

int main(void) {
    CURL *curl;
    CURLcode res;

    // libcurl 초기화
    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        // 요청할 URL 설정
        curl_easy_setopt(curl, CURLOPT_URL, "https://jsonplaceholder.typicode.com/posts/1");

        // 응답 데이터를 처리할 콜백 함수 설정
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);

        // 요청 실행
        res = curl_easy_perform(curl);

        // 오류 확인
        if (res != CURLE_OK) {
            fprintf(stderr, "curl_easy_perform() 실패: %s\n", curl_easy_strerror(res));
        }

        // 리소스 정리
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

코드 설명

  1. libcurl 초기화
  • curl_global_init(CURL_GLOBAL_ALL)을 호출하여 libcurl을 초기화합니다.
  • curl_easy_init()을 사용하여 CURL 핸들을 생성합니다.
  1. 요청 설정
  • curl_easy_setopt(curl, CURLOPT_URL, "URL")로 요청할 URL을 지정합니다.
  • CURLOPT_WRITEFUNCTION을 사용하여 응답을 처리할 콜백 함수를 설정합니다.
  1. 요청 실행
  • curl_easy_perform(curl)을 호출하면 GET 요청이 수행됩니다.
  1. 오류 처리
  • 요청이 실패할 경우 curl_easy_strerror(res)를 사용하여 오류 메시지를 출력합니다.
  1. 정리 및 종료
  • curl_easy_cleanup(curl)을 호출하여 CURL 핸들을 정리합니다.
  • curl_global_cleanup()을 호출하여 libcurl을 종료합니다.

실행 예제 출력

{
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum..."
}

이제 다음 단계에서는 libcurl을 이용한 POST 요청 및 JSON 데이터 전송을 다룹니다.

libcurl을 사용한 POST 요청

POST 요청 개요

POST 요청은 클라이언트가 서버에 데이터를 전송할 때 사용됩니다. 보통 새로운 리소스를 생성할 때 사용되며, 본문(Body)에 데이터를 포함할 수 있습니다.
libcurl을 사용하면 JSON 데이터를 포함한 POST 요청을 쉽게 보낼 수 있습니다.


기본 POST 요청 예제

아래 코드는 https://jsonplaceholder.typicode.com/posts에 JSON 데이터를 포함하여 POST 요청을 보내는 예제입니다.

#include <stdio.h>
#include <string.h>
#include <curl/curl.h>

int main(void) {
    CURL *curl;
    CURLcode res;

    // 전송할 JSON 데이터
    const char *json_data = "{\"title\": \"foo\", \"body\": \"bar\", \"userId\": 1}";

    // libcurl 초기화
    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        // 요청할 URL 설정
        curl_easy_setopt(curl, CURLOPT_URL, "https://jsonplaceholder.typicode.com/posts");

        // HTTP POST 요청 설정
        curl_easy_setopt(curl, CURLOPT_POST, 1L);

        // 요청 본문(JSON 데이터) 설정
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data);

        // 헤더 설정 (JSON 데이터임을 명시)
        struct curl_slist *headers = NULL;
        headers = curl_slist_append(headers, "Content-Type: application/json");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

        // 요청 실행
        res = curl_easy_perform(curl);

        // 오류 확인
        if (res != CURLE_OK) {
            fprintf(stderr, "curl_easy_perform() 실패: %s\n", curl_easy_strerror(res));
        }

        // 리소스 정리
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

코드 설명

  1. libcurl 초기화
  • curl_global_init(CURL_GLOBAL_ALL)을 호출하여 libcurl을 초기화합니다.
  • curl_easy_init()을 사용하여 CURL 핸들을 생성합니다.
  1. 요청 설정
  • curl_easy_setopt(curl, CURLOPT_URL, "URL")로 요청할 URL을 지정합니다.
  • curl_easy_setopt(curl, CURLOPT_POST, 1L)을 설정하여 POST 요청을 사용합니다.
  • curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data)을 사용하여 JSON 데이터를 요청 본문에 포함합니다.
  1. 헤더 설정
  • Content-Type: application/json 헤더를 설정하여 서버가 JSON 데이터임을 인식하도록 합니다.
  1. 요청 실행
  • curl_easy_perform(curl)을 호출하면 POST 요청이 수행됩니다.
  1. 오류 처리 및 정리
  • 요청이 실패할 경우 curl_easy_strerror(res)를 사용하여 오류 메시지를 출력합니다.
  • curl_slist_free_all(headers)를 호출하여 헤더를 정리합니다.
  • curl_easy_cleanup(curl)을 호출하여 CURL 핸들을 정리합니다.

실행 예제 출력

서버가 성공적으로 요청을 처리하면 다음과 같은 JSON 응답을 반환합니다.

{
  "title": "foo",
  "body": "bar",
  "userId": 1,
  "id": 101
}

이는 서버가 새로운 데이터를 생성했으며, id: 101로 저장되었음을 의미합니다.


이제 다음 단계에서는 libcurl에서 헤더 설정 및 인증 처리 방법을 다룹니다.

libcurl의 헤더 설정 및 인증 처리

헤더(Header) 설정 개요

HTTP 요청에서 헤더(Header) 는 서버와 클라이언트 간의 메타데이터를 포함하는 중요한 요소입니다. REST API를 호출할 때는 다음과 같은 헤더가 필요할 수 있습니다.

헤더설명
Content-Type요청 본문의 데이터 형식을 지정 (예: JSON)
Accept응답 데이터 형식을 지정
AuthorizationAPI 인증 토큰 또는 자격 증명 전달
User-Agent클라이언트의 정보를 전달
Cache-Control캐시 관련 정책 설정

헤더 추가 방법

libcurl에서는 curl_slist 구조체를 사용하여 여러 개의 HTTP 헤더를 추가할 수 있습니다.

#include <stdio.h>
#include <curl/curl.h>

int main(void) {
    CURL *curl;
    CURLcode res;

    // libcurl 초기화
    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        // 요청할 URL 설정
        curl_easy_setopt(curl, CURLOPT_URL, "https://api.example.com/data");

        // 헤더 설정
        struct curl_slist *headers = NULL;
        headers = curl_slist_append(headers, "Content-Type: application/json");
        headers = curl_slist_append(headers, "Accept: application/json");
        headers = curl_slist_append(headers, "User-Agent: libcurl-example/1.0");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

        // GET 요청 실행
        res = curl_easy_perform(curl);

        // 오류 확인
        if (res != CURLE_OK) {
            fprintf(stderr, "curl_easy_perform() 실패: %s\n", curl_easy_strerror(res));
        }

        // 리소스 정리
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

인증(Authentication) 처리

REST API는 보안 강화를 위해 인증(Authentication) 을 요구하는 경우가 많습니다. 일반적으로 사용되는 인증 방식은 다음과 같습니다.

인증 방식설명
Basic Authenticationusername:password를 Base64로 인코딩하여 전달
Bearer TokenJWT 또는 OAuth 토큰을 Authorization 헤더에 포함
API KeyAPI 키를 헤더 또는 URL 파라미터에 추가

1. Basic Authentication (기본 인증)

기본 인증 방식에서는 username:password를 Base64로 인코딩하여 Authorization 헤더에 포함해야 합니다.
libcurl에서는 CURLOPT_USERPWD 옵션을 사용하여 쉽게 설정할 수 있습니다.

curl_easy_setopt(curl, CURLOPT_USERPWD, "username:password");

예제 코드:

#include <stdio.h>
#include <curl/curl.h>

int main(void) {
    CURL *curl;
    CURLcode res;

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://api.example.com/secure-data");

        // 기본 인증 (Basic Auth)
        curl_easy_setopt(curl, CURLOPT_USERPWD, "myusername:mypassword");

        // 요청 실행
        res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
            fprintf(stderr, "curl_easy_perform() 실패: %s\n", curl_easy_strerror(res));
        }

        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

2. Bearer Token 인증 (OAuth 2.0 / JWT)

OAuth 2.0 또는 JWT 기반 인증에서는 Authorization 헤더에 Bearer 토큰을 추가해야 합니다.

#include <stdio.h>
#include <curl/curl.h>

int main(void) {
    CURL *curl;
    CURLcode res;

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://api.example.com/secure-data");

        // Bearer Token 설정
        struct curl_slist *headers = NULL;
        headers = curl_slist_append(headers, "Authorization: Bearer my_access_token");
        headers = curl_slist_append(headers, "Accept: application/json");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

        // 요청 실행
        res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
            fprintf(stderr, "curl_easy_perform() 실패: %s\n", curl_easy_strerror(res));
        }

        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

3. API Key 인증

일부 API는 API 키를 사용하여 요청을 인증합니다. API 키는 헤더 또는 URL에 포함될 수 있습니다.

헤더 방식

struct curl_slist *headers = NULL;
headers = curl_slist_append(headers, "x-api-key: YOUR_API_KEY");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

URL 파라미터 방식

curl_easy_setopt(curl, CURLOPT_URL, "https://api.example.com/data?api_key=YOUR_API_KEY");

정리

  • curl_slist_append()을 사용하여 여러 개의 HTTP 헤더를 추가할 수 있음.
  • 기본 인증(Basic Auth)은 CURLOPT_USERPWD 옵션을 사용하여 간단히 처리 가능.
  • OAuth 2.0 / JWT 기반 인증은 Authorization: Bearer <토큰> 헤더를 추가해야 함.
  • API Key는 헤더 또는 URL 파라미터 방식으로 전달 가능.

다음 단계에서는 응답 데이터 처리 및 JSON 파싱 방법을 설명합니다.

응답 처리 및 JSON 파싱

응답 처리 개요

libcurl을 사용하여 HTTP 요청을 보낼 때, 서버의 응답을 적절히 처리해야 합니다. 응답에는 HTTP 상태 코드, 헤더 정보, 본문(JSON, XML, HTML 등) 이 포함됩니다. REST API의 경우 응답 데이터가 일반적으로 JSON 형식으로 제공되므로, 이를 파싱하여 활용하는 것이 중요합니다.


1. 응답 데이터 저장하기

libcurl에서는 CURLOPT_WRITEFUNCTION을 사용하여 응답 데이터를 처리할 콜백 함수를 설정할 수 있습니다.

응답을 버퍼에 저장하는 예제

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <curl/curl.h>

// 응답 데이터를 저장할 구조체
struct ResponseData {
    char *memory;
    size_t size;
};

// 응답 데이터를 동적 할당하여 저장하는 콜백 함수
size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
    size_t total_size = size * nmemb;
    struct ResponseData *response = (struct ResponseData *)userdata;

    char *new_memory = realloc(response->memory, response->size + total_size + 1);
    if (new_memory == NULL) {
        fprintf(stderr, "메모리 할당 실패\n");
        return 0;
    }

    response->memory = new_memory;
    memcpy(&(response->memory[response->size]), ptr, total_size);
    response->size += total_size;
    response->memory[response->size] = '\0';

    return total_size;
}

int main(void) {
    CURL *curl;
    CURLcode res;
    struct ResponseData response = {NULL, 0};

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://jsonplaceholder.typicode.com/posts/1");

        // 응답을 write_callback 함수로 처리
        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);

        res = curl_easy_perform(curl);

        if (res == CURLE_OK) {
            printf("응답 데이터:\n%s\n", response.memory);
        } else {
            fprintf(stderr, "요청 실패: %s\n", curl_easy_strerror(res));
        }

        free(response.memory);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

2. JSON 데이터 파싱

C언어에서는 JSON 데이터를 다루기 위해 cJSON과 같은 외부 라이브러리를 사용할 수 있습니다.
cJSON은 경량 JSON 파서로, 간단한 API를 통해 JSON 데이터를 다룰 수 있습니다.

cJSON 설치

  • 리눅스 (Ubuntu/Debian)
  sudo apt install libcjson-dev
  • macOS (Homebrew 사용)
  brew install cjson
  • Windows (vcpkg 사용)
  vcpkg install cjson

JSON 데이터 파싱 예제 (cJSON 사용)

#include <stdio.h>
#include <stdlib.h>
#include <curl/curl.h>
#include <cjson/cJSON.h>

// 응답 데이터를 저장할 구조체
struct ResponseData {
    char *memory;
    size_t size;
};

// 응답 데이터를 저장하는 콜백 함수
size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
    size_t total_size = size * nmemb;
    struct ResponseData *response = (struct ResponseData *)userdata;

    char *new_memory = realloc(response->memory, response->size + total_size + 1);
    if (new_memory == NULL) {
        fprintf(stderr, "메모리 할당 실패\n");
        return 0;
    }

    response->memory = new_memory;
    memcpy(&(response->memory[response->size]), ptr, total_size);
    response->size += total_size;
    response->memory[response->size] = '\0';

    return total_size;
}

int main(void) {
    CURL *curl;
    CURLcode res;
    struct ResponseData response = {NULL, 0};

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://jsonplaceholder.typicode.com/posts/1");

        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);

        res = curl_easy_perform(curl);

        if (res == CURLE_OK) {
            printf("서버 응답: %s\n", response.memory);

            // JSON 파싱
            cJSON *json = cJSON_Parse(response.memory);
            if (json) {
                cJSON *title = cJSON_GetObjectItemCaseSensitive(json, "title");
                if (cJSON_IsString(title) && (title->valuestring != NULL)) {
                    printf("게시글 제목: %s\n", title->valuestring);
                }
                cJSON_Delete(json);
            } else {
                fprintf(stderr, "JSON 파싱 실패\n");
            }
        } else {
            fprintf(stderr, "요청 실패: %s\n", curl_easy_strerror(res));
        }

        free(response.memory);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

3. 코드 설명

  1. 응답을 저장하는 구조체(ResponseData)
  • memory: 응답 데이터를 저장할 메모리 버퍼
  • size: 현재 저장된 데이터 크기
  1. 응답을 처리하는 write_callback 함수
  • libcurl의 CURLOPT_WRITEFUNCTION을 설정하여 응답 데이터를 동적으로 저장
  • realloc을 사용하여 메모리를 동적으로 증가시킴
  1. 응답 데이터(JSON) 파싱
  • cJSON_Parse(response.memory): JSON 데이터를 파싱
  • cJSON_GetObjectItemCaseSensitive(json, "title"): 특정 JSON 키의 값을 가져옴
  • cJSON_Delete(json): JSON 객체 메모리 해제

4. 실행 결과 (예제 응답)

서버 응답: {
  "userId": 1,
  "id": 1,
  "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
  "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum..."
}

게시글 제목: sunt aut facere repellat provident occaecati excepturi optio reprehenderit

5. 정리

libcurl의 CURLOPT_WRITEFUNCTION을 활용해 응답 데이터를 저장
cJSON을 사용하여 JSON 응답 데이터를 파싱하고 필요한 정보 추출
REST API와 상호작용할 때 데이터를 효과적으로 처리하는 방법 습득

다음 단계에서는 libcurl을 사용한 오류 처리 및 디버깅 기법을 다룹니다.

오류 처리 및 디버깅 기법

1. libcurl에서 발생하는 주요 오류

libcurl을 사용하여 HTTP 요청을 수행할 때, 네트워크 문제나 잘못된 요청 등의 이유로 다양한 오류가 발생할 수 있습니다. 주요 오류 코드는 다음과 같습니다.

오류 코드설명
CURLE_OK요청이 정상적으로 수행됨
CURLE_UNSUPPORTED_PROTOCOL지원되지 않는 프로토콜을 사용함
CURLE_COULDNT_CONNECT서버에 연결할 수 없음
CURLE_HTTP_RETURNED_ERROR서버에서 HTTP 오류 코드 반환
CURLE_READ_ERROR데이터 읽기 실패
CURLE_OPERATION_TIMEDOUT요청이 시간 초과됨
CURLE_SSL_CONNECT_ERRORSSL/TLS 연결 오류

이러한 오류를 적절히 처리하지 않으면 프로그램이 예기치 않게 종료될 수 있습니다. 따라서, 에러 코드 확인 및 로깅이 중요합니다.


2. 오류 코드 확인 및 기본 처리

libcurl의 curl_easy_perform() 함수는 CURLcode 반환값을 제공하며, 이를 통해 오류를 감지할 수 있습니다.

기본 오류 처리 예제

#include <stdio.h>
#include <curl/curl.h>

int main(void) {
    CURL *curl;
    CURLcode res;

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://invalid-url.example.com");

        // 요청 실행
        res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
            // 오류 메시지 출력
            fprintf(stderr, "요청 실패: %s\n", curl_easy_strerror(res));
        }

        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

실행 결과 (유효하지 않은 URL 요청 시)

요청 실패: Couldn't resolve host name
  • curl_easy_strerror(res)를 사용하면 사람이 읽을 수 있는 오류 메시지를 출력할 수 있습니다.

3. HTTP 응답 코드 확인

libcurl을 사용하면 HTTP 응답 상태 코드(예: 200, 404, 500)를 확인할 수 있습니다.
이를 통해 서버에서 반환한 오류를 감지하고 적절한 대응을 할 수 있습니다.

HTTP 응답 코드 확인 예제

#include <stdio.h>
#include <curl/curl.h>

int main(void) {
    CURL *curl;
    CURLcode res;
    long http_code = 0;

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://jsonplaceholder.typicode.com/invalid_endpoint");

        // 요청 실행
        res = curl_easy_perform(curl);

        if (res == CURLE_OK) {
            // HTTP 응답 코드 확인
            curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);
            printf("HTTP 응답 코드: %ld\n", http_code);

            if (http_code == 404) {
                fprintf(stderr, "오류: 요청한 리소스를 찾을 수 없음 (404)\n");
            }
        } else {
            fprintf(stderr, "요청 실패: %s\n", curl_easy_strerror(res));
        }

        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

실행 결과 (존재하지 않는 리소스를 요청한 경우)

HTTP 응답 코드: 404
오류: 요청한 리소스를 찾을 수 없음 (404)
  • curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);를 사용하면 HTTP 응답 코드를 확인할 수 있습니다.

4. 요청 시간 초과 처리

네트워크가 불안정한 경우 요청이 무한히 대기하는 것을 방지하기 위해 타임아웃 설정이 필요합니다.

요청 시간 초과 설정 예제

curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);  // 전체 요청 타임아웃 10초
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);  // 연결 대기 시간 5초

이를 적용한 전체 코드:

#include <stdio.h>
#include <curl/curl.h>

int main(void) {
    CURL *curl;
    CURLcode res;

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
        curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);  // 전체 요청 타임아웃 10초
        curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);  // 연결 대기 시간 5초

        res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
            fprintf(stderr, "요청 실패: %s\n", curl_easy_strerror(res));
        }

        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

5. 요청 디버깅 (디버깅 모드 활성화)

libcurl은 디버깅 모드를 제공하여 요청 및 응답의 상세 정보를 출력할 수 있습니다.

curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);

전체 코드:

#include <stdio.h>
#include <curl/curl.h>

int main(void) {
    CURL *curl;
    CURLcode res;

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://example.com");
        curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);  // 디버깅 모드 활성화

        res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
            fprintf(stderr, "요청 실패: %s\n", curl_easy_strerror(res));
        }

        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

실행 결과 (디버깅 모드 활성화)

*   Trying 93.184.216.34...
* Connected to example.com (93.184.216.34) port 443 (#0)
> GET / HTTP/1.1
> Host: example.com
> User-Agent: curl/7.68.0
> Accept: */*
  • CURLOPT_VERBOSE를 활성화하면, 요청 및 응답 과정이 상세하게 출력됩니다.

6. 정리

curl_easy_perform()의 반환값을 확인하여 오류 감지
curl_easy_getinfo()를 사용하여 HTTP 응답 코드 확인 가능
CURLOPT_TIMEOUT을 설정하여 요청 시간 초과 방지
CURLOPT_VERBOSE를 사용하여 요청 디버깅 가능

다음 단계에서는 실제 REST API 예제 및 활용 방법을 설명합니다.

실제 REST API 예제 및 활용

1. REST API를 이용한 실제 시나리오

libcurl을 사용하여 REST API와 상호작용하는 예제를 실전 환경에 맞춰 구현해 보겠습니다.
다음과 같은 기능을 수행하는 예제를 작성합니다.

✅ 사용자 목록을 가져오는 GET 요청
✅ 새로운 사용자를 추가하는 POST 요청
✅ 특정 사용자의 정보를 수정하는 PUT 요청
✅ 사용자를 삭제하는 DELETE 요청

사용할 API: JSONPlaceholder (테스트용 무료 API)


2. GET 요청: 사용자 목록 조회

다음 코드는 https://jsonplaceholder.typicode.com/users API를 호출하여 사용자 목록을 가져옵니다.

#include <stdio.h>
#include <stdlib.h>
#include <curl/curl.h>

// 응답을 저장할 구조체
struct ResponseData {
    char *memory;
    size_t size;
};

// 응답 데이터를 저장하는 콜백 함수
size_t write_callback(void *ptr, size_t size, size_t nmemb, void *userdata) {
    size_t total_size = size * nmemb;
    struct ResponseData *response = (struct ResponseData *)userdata;

    char *new_memory = realloc(response->memory, response->size + total_size + 1);
    if (new_memory == NULL) {
        fprintf(stderr, "메모리 할당 실패\n");
        return 0;
    }

    response->memory = new_memory;
    memcpy(&(response->memory[response->size]), ptr, total_size);
    response->size += total_size;
    response->memory[response->size] = '\0';

    return total_size;
}

int main(void) {
    CURL *curl;
    CURLcode res;
    struct ResponseData response = {NULL, 0};

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://jsonplaceholder.typicode.com/users");

        curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
        curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&response);

        res = curl_easy_perform(curl);

        if (res == CURLE_OK) {
            printf("사용자 목록: \n%s\n", response.memory);
        } else {
            fprintf(stderr, "GET 요청 실패: %s\n", curl_easy_strerror(res));
        }

        free(response.memory);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

사용자 목록을 성공적으로 조회하면 JSON 형식의 응답이 반환됩니다.


3. POST 요청: 새로운 사용자 추가

새로운 사용자를 추가하는 API 호출 코드입니다.

#include <stdio.h>
#include <curl/curl.h>

int main(void) {
    CURL *curl;
    CURLcode res;
    const char *json_data = "{\"name\": \"John Doe\", \"email\": \"johndoe@example.com\"}";

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://jsonplaceholder.typicode.com/users");
        curl_easy_setopt(curl, CURLOPT_POST, 1L);
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data);

        struct curl_slist *headers = NULL;
        headers = curl_slist_append(headers, "Content-Type: application/json");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

        res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
            fprintf(stderr, "POST 요청 실패: %s\n", curl_easy_strerror(res));
        }

        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

✅ 실행하면 새로운 사용자가 생성되었음을 확인할 수 있습니다.


4. PUT 요청: 사용자 정보 수정

사용자의 이메일을 업데이트하는 예제입니다.

#include <stdio.h>
#include <curl/curl.h>

int main(void) {
    CURL *curl;
    CURLcode res;
    const char *json_data = "{\"name\": \"John Doe\", \"email\": \"john.updated@example.com\"}";

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://jsonplaceholder.typicode.com/users/1");
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "PUT");
        curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_data);

        struct curl_slist *headers = NULL;
        headers = curl_slist_append(headers, "Content-Type: application/json");
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);

        res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
            fprintf(stderr, "PUT 요청 실패: %s\n", curl_easy_strerror(res));
        }

        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

✅ 실행하면 특정 사용자의 정보가 업데이트됩니다.


5. DELETE 요청: 사용자 삭제

특정 사용자를 삭제하는 DELETE 요청 예제입니다.

#include <stdio.h>
#include <curl/curl.h>

int main(void) {
    CURL *curl;
    CURLcode res;

    curl_global_init(CURL_GLOBAL_ALL);
    curl = curl_easy_init();

    if (curl) {
        curl_easy_setopt(curl, CURLOPT_URL, "https://jsonplaceholder.typicode.com/users/1");
        curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "DELETE");

        res = curl_easy_perform(curl);

        if (res != CURLE_OK) {
            fprintf(stderr, "DELETE 요청 실패: %s\n", curl_easy_strerror(res));
        }

        curl_easy_cleanup(curl);
    }

    curl_global_cleanup();
    return 0;
}

✅ 실행하면 특정 사용자가 삭제됩니다.


6. 정리

libcurl을 사용하여 실제 REST API와 상호작용하는 방법을 배움
GET, POST, PUT, DELETE 요청을 활용하여 CRUD 작업 수행
JSON 데이터를 처리하고 HTTP 요청을 보낼 수 있도록 구현

다음 단계에서는 요약 및 최종 정리를 진행합니다.

요약

본 기사에서는 C언어에서 libcurl을 사용하여 REST API 클라이언트를 구현하는 방법을 다루었습니다.

libcurl 설치 및 기본 개념을 소개하고,
GET, POST, PUT, DELETE 요청을 수행하는 방법을 설명했으며,
헤더 설정 및 인증 처리, 오류 디버깅, JSON 파싱 기법까지 다뤘습니다.
✅ 마지막으로 실제 REST API와 상호작용하는 예제를 통해 CRUD 작업을 수행하는 방법을 실습했습니다.

이제 libcurl을 활용하여 C언어 기반의 네트워크 애플리케이션을 개발할 수 있으며, API를 활용한 다양한 서비스와 연동할 수 있습니다. RESTful API를 사용하는 프로젝트에서 libcurl을 적극적으로 활용해 보세요! 🚀

목차