메인 콘텐츠로 이동하기
  1. Posts/

C 언어로 작성한 간단한 웹서버

·546 자

서론 #

이번 글에서는 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));
}