[C] RESTful server 구현

yjseo·2024년 10월 22일

RESTful 서버를 C로 구현해야 해서 공부한 내용을 정리해보려고 한다.
Java로 배웠던 객체지향프로그래밍 과목을 다시 듣고 싶어지는 magic-☆😞

RESTful server 구현하기

libmicrohttpd 설치하기

(WSL 환경 기준)

libmicrohttpd란?
경량 HTTP 서버 라이브러리
C언어로 작성된 애플리케이션에 웹 서버 기능을 추가할 수 있다.

libmicrohttpd 라이브러리를 설치해보자.

sudo apt install libmicrohttpd-dev

헤더 파일을 추가한다.

#include <microhttpd.h>

컴파일은 이렇게 하면 된다.

gcc -o server server.c -lmicrohttpd
./server

HTTP 서버 실행하기

int main()
{
    struct MHD_Daemon *daemon;

    daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, PORT, NULL, NULL, &answer_to_connection, NULL, MHD_OPTION_END);

    if (NULL == daemon)
    {
        printf("[ERROR] server start failed\n");
        return -1;
    }

    printf("server is running on port %d... \n", PORT);
    getchar();

    MHD_stop_daemon(daemon);
    return 0;
}

여기서 중요한 부분인 MHD_start_daemon 함수에 대해 알아보자.

struct MHD_Daemon *MHD_start_daemon(unsigned int flags,
                                     unsigned short port,
                                     MHD_OPTION *options,
                                     MHD_answer_to_connection_function answer_to_connection,
                                     void *cls,
                                     ...);

이 함수는 지정된 포트에서 웹 서버를 시작하는 함수이다.

인자는 다음과 같다.

  1. unsigned int flags
    • 서버 동작 방식을 설정하는 flag
    • 여러개의 플래그를 조합하여 사용 가능
    • MHD_USE_SELECT_INTERNALLY, MHD_USE_POLL, MHD_USE_EPOLL, MHD_USE_THREAD_PER_CONNECTION 등이 있다.

2. unsigned short port
- 서버가 클라이언트 요청을 수신할 포트 번호를 지정

  1. MHD_OPTION *options
    • 서버 동작을 제어할 수 있는 추가 옵션의 배열
    • 옵션 이름과 그에 대한 값을 쌍으로 나열
    • 마지막 옵션은 MHD_OPTION_END로 끝남
MHD_OPTION *options = {
        MHD_OPTION_NOTIFY_COMPLETED, notify_callback,
        MHD_OPTION_END
    };

4. MHD_answer_to_connection_function answer_to_connection
- 클라이언트의 요청을 처리하는 콜백 함수의 포인터
- 이 함수는 요청이 들어올 때마다 호출됨
- 요청에 대한 응답을 생성하고 반환

    static int answer_to_connection(void *cls,
                                 struct MHD_Connection *connection,
                                 const char *url,
                                 const char *method,
                                 const char *version,
                                 const char *upload_data,
                                 size_t *upload_data_size,
                                 void **con_cls);
  1. void *cls
    • answer_to_connection 콜백 함수에 전달할 추가 데이터
    • 서버에서 공유하거나 요청을 처리하는 데 필요
    • 추가 데이터가 필요하지 않으면 NULL로 설정
  2. ...
    • 추가적인 옵션을 설정할 수 있는 가변 인자 목록
    • 각 옵션의 값을 추가적으로 제공
    • 옵션이 더 이상 없으면 MHD_OPTION_END로 설정

반환값은 MHD_Daemon 구조체 포인터이다. 실패할 경우 NULL을 반환한다.
오류의 원인을 알고 싶을 때는 MHD_get_error함수를 호출하여 자세한 오류 메시지를 확인하면 된다.

callback 함수 구현하기

요청이 들어올 때마다 호출되는 콜백함수를 구현해보자.
요청이 들어올 때마다 "success"라는 응답을 보내는 간단한 함수이다.

// 요청 처리 함수
int answer_to_connection(void *cls, struct MHD_Connection *connection, 
const char *url, const char *method, const char *version,
const char *upload_data, size_t *upload_data_size, 
void **con_cls)
{
    struct MHD_Response *res;
    int ret;
    
    char message[8] = "success";

    res = MHD_create_response_from_buffer(strlen(message), (void *)message, MHD_RESPMEM_PERSISTENT);
    MHD_add_response_header(res, MHD_HTTP_HEADER_CONTENT_TYPE, content_type); // content-type 헤더 추가
    
    ret = MHD_queue_response(connection, MHD_HTTP_OK, res); // 응답 전송
    MHD_destroy_response(res);

    if (ret != MHD_YES)
    {
        printf("[ERROR] Failed to send response. \n");
        return -1;
    }

    return ret;
}

응답 보내는 부분을 send_response 함수로 구현하는게 더 나은 것 같다ㅋㅋ

int handle_connection(void *cls, struct MHD_Connection *connection,
                      const char *url, const char *method, const char *version,
                      const char *upload_data, size_t *upload_data_size, void **con_cls)
{
    return send_response(connection, "success", MHD_HTTP_OK, "text/plain", session->session_id);
}

int send_response(struct MHD_Connection *connection, const char *message, int status_code, const char *content_type, const char *session_id)
{
    struct MHD_Response *res;
    int ret;

    res = MHD_create_response_from_buffer(strlen(message), (void *)message, MHD_RESPMEM_PERSISTENT);
    MHD_add_response_header(res, MHD_HTTP_HEADER_CONTENT_TYPE, content_type); // content-type 헤더 추가
    ret = MHD_queue_response(connection, status_code, res); // 응답 전송
    MHD_destroy_response(res);

    return ret;
}

그래도 프로그램이 쓸모가 있으려면
메서드 (GET, POST, PUT, DELETE) 에 따라 응답을 다르게 처리해야 할 것이다.

그래서 각각의 메서드를 처리하는 함수를 만든다.

if (strcmp(method, "GET") == 0)
    {
        return handle_get(connection, url);
    }
    else if (strcmp(method, "POST") == 0)
    { 
        return handle_post(connection, upload_data, upload_data_size);  
    }
    else if (strcmp(method, "PUT") == 0)
    {
        return handle_put(connection, upload_data, upload_data_size);
    }
    else if (strcmp(method, "DELETE") == 0)
    {
        return handle_delete(connection, upload_data, upload_data_size);
    }

같은 메서드 요청이라도 url에 따라 다르게 응답을 보내고 싶으면

if (strcmp(url, "/login") == 0) 
{
    // handle_login()
}

이런 식으로 처리하면 된다.

참고
챗지피티
microhttpd 메뉴얼: https://www.gnu.org/software/libmicrohttpd/manual/libmicrohttpd.html

profile
저 뭐해먹고 살아요..🥺

0개의 댓글