C 언어로 작성한 간단한 웹서버
목차
서론 #
이번 글에서는 C의 멀티 프로세싱를 이용하여 간단한 정적 웹서버를 구현하고자 합니다.
header files #
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#define HEADER_FMT "HTTP/1.1 %d %s\nContent-Length: %ld\nContent-Type: %s\n\n"
#define NOT_FOUND_CONTENT "<h2>404 Not Found</h2>\n"
#define SERVER_ERROR_CONTENT "<h2>500 Internal Server Error</h2>\n"
#define BUF_SIZE 1000
main #
int main(int argc, char **argv) {
int port;
int lsock, asock;
struct sockaddr_in remote_sin;
socklen_t remote_sin_len;
// can specify your own port number
port = 3000;
// create listen socket using socket function
lsocket = socket(AF_INET, SOCK_STREAM, 0);
// lsock == -1 if failed creating socket
if (lsock < 0) {
perror("[ERR] failed to create lsock.\n");
}
if (bind_lsock(lsock, port) < 0) {
perror("[ERR] failed to listen lsock.\n");
exit(1);
}
if (listen(lsock, 10) < 0) {
perror("[ERR] failed to listen lsock.\n");
exit(1);
}
// to prevent zombie process
signal(SIGCHLD, SIG_IGN);
int pid;
while(1) {
// accept request from client
asock = accept(lsock, (struct sockaddr *)&remote_sin,
&remote_sin_len);
if (asock < 0) {
perror("[ERR] failed to accept.\n");
exit(1);
}
// multi-processing
pid = fork();
if (pid == 0) {
// handling client socket and close sockets
close(lsock); http_handler(asock); close(asock);
exit(0);
}
if (pid != 0) { close(asock); }
if (pid < 0){ perror("[ERR] failed to fork.\n"); }
}
}
http_handler #
빈 URI(" / “)에 대해 index.html을 제공하는 핸들러 handle_400
, handle_500
, find_mime
, fill_header
함수는 다음 블록에서 설명하겠습니다.
void http_handler(int asock) {
char buf[BUF_SIZE];
if (read(asock, buf, BUF_SIZE) < 0) {
perror("[ERR] Failed to read request.\n");
handle_500(asock);
}
char *method = strtok(buf, " ");
char *uri = strtok(NULL, " ");
if (method == NULL || uri == NULL) {
perror("[ERR] Failed to identify method, URI.\n");
handle_500(asock); return;
}
char safe_uri[BUF_SIZE];
char *local_uri;
struct stat st;
strcpy(safe_uri, uri);
// strcmp returns 0 if two strings are equal
if (!strcmp(safe_uri, "/")) {
strcpy(safe_uri, "./index.html");
}
// local_uri = "index.html"
local_uri = safe_uri + 1;
if (stat(safe_uri, &st) < 0) {
perror("[WARN] No File found matching URI.\n");
handle_404(asock); return;
}
int fd = open(safe_uri, O_RDONLY);
if (fd < 0) {
perror("[ERR] Failed to open file.\n");
handle_500(asock); return;
}
char header[BUF_SIZE];
// content_length
int ct_len = st.st_size;
char content_type[40];
find_mime(content_type, local_uri);
fill_header(header, 200, ct_len, content_type);
write(asock, header, strlen(header));
int content;
while ((content = read(fd, buf, BUF_SIZE)) > 0) {
write(asock, buf, content);
}
}
fill_header #
void fill_header(char *header, int status, long len, char *type) {
char status_text[40];
switch (status) {
case 200:
strcpy(status_text, "OK"); break;
case 404:
strcpy(status_text, "NOT FOUND"); break;
case 500:
default:
strcpy(status_text, "INTERNAL SERVER ERROR"); break;
}
// fill header using HEADER_FORMAT which is defined with header files
sprintf(header, HEADER_FMT, status, status_text, len, type);
}
find_mime #
컨텐츠의 MIME 유형(미디어 유형)을 찾는 함수
void find_mime(char *content_type, char *uri) {
// strrchr = locate last character and return pointer
char *ext = strrchr(uri, '.');
if (!strcmp(ext, ".html")) {
strcpy(content_type, "text/html");
} else if (!strcmp(ext, ".jpg") || !strcmp(ext, ".jpeg")) {
strcpy(content_type, "image/jpeg");
} else if (!strcmp(ext, ".png")) {
strcpy(content_type, "image/png");
} else if (!strcmp(ext, ".css")) {
strcpy(content_type, "text/css");
} else if (!strcmp(ext, ".js")) {
strcpy(content_type, "text/javascript");
} else strcpy(content_type, "text/plain");
}
handle_404, handle_500 #
void handle_404(int asock) {
char header[BUF_SIZE];
fill_header(header, 404, sizeof(NOT_FOUND_CONTENT), "text/html");
write(asock, header, strlen(header));
write(asock, NOT_FOUND_CONTENT, strlen(header));
}
void handle_500(int asock) {
char header[BUF_SIZE];
fill_header(header, 500, sizeof(SERVER_ERROR_CONTENT), "text/html");
write(asock, header, strlen(header));
write(asock, SERVER_ERROR_CONTENT, strlen(header));
}