
RESTful 서버를 C로 구현해야 해서 공부한 내용을 정리해보려고 한다.
Java로 배웠던 객체지향프로그래밍 과목을 다시 듣고 싶어지는 magic-☆😞
(WSL 환경 기준)
libmicrohttpd란?
경량 HTTP 서버 라이브러리
C언어로 작성된 애플리케이션에 웹 서버 기능을 추가할 수 있다.
libmicrohttpd 라이브러리를 설치해보자.
sudo apt install libmicrohttpd-dev
헤더 파일을 추가한다.
#include <microhttpd.h>
컴파일은 이렇게 하면 된다.
gcc -o server server.c -lmicrohttpd
./server
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,
...);
이 함수는 지정된 포트에서 웹 서버를 시작하는 함수이다.
인자는 다음과 같다.
unsigned int flagsMHD_USE_SELECT_INTERNALLY, MHD_USE_POLL, MHD_USE_EPOLL, MHD_USE_THREAD_PER_CONNECTION 등이 있다.2. unsigned short port
- 서버가 클라이언트 요청을 수신할 포트 번호를 지정
MHD_OPTION *optionsMHD_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);
void *clsanswer_to_connection 콜백 함수에 전달할 추가 데이터NULL로 설정...MHD_OPTION_END로 설정반환값은 MHD_Daemon 구조체 포인터이다. 실패할 경우 NULL을 반환한다.
오류의 원인을 알고 싶을 때는 MHD_get_error함수를 호출하여 자세한 오류 메시지를 확인하면 된다.
요청이 들어올 때마다 호출되는 콜백함수를 구현해보자.
요청이 들어올 때마다 "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