[TIL/크래프톤 정글9기] 55일차 (Week 08 Tiny 서버 만들기)

blueprint·2025년 7월 5일

크래프톤정글9기

목록 보기
46/55

Tiny Server

개요

  • Tiny는 HTTP/1.0 프로토콜을 지원하는 간단한 웹 서버
  • GET 메서드만 지원하며, 정적 파일과 동적 콘텐츠(CGI) 모두 처리

전체 구조

Tiny 웹 서버는 다음과 같은 주요 함수들로 구성:

  • main(): 서버 시작점, 클라이언트 연결 수락
  • doit(): HTTP 요청 처리의 핵심 로직
  • clienterror(): 에러 응답 생성
  • read_requesthdrs(): HTTP 헤더 읽기
  • parse_uri(): URI 파싱으로 정적/동적 콘텐츠 구분
  • serve_static(): 정적 파일 서비스
  • serve_dynamic(): 동적 콘텐츠(CGI) 서비스
  • get_filetype(): 파일 확장자로 MIME 타입 결정

1. 서버 시작 (main 함수)

int main(int argc, char **argv) {
  int listenfd, connfd;
  char hostname[MAXLINE], port[MAXLINE];
  socklen_t clientlen;
  struct sockaddr_storage clientaddr;

메인 함수는 전형적인 서버 구조:
1. 명령행 인수 검사: 포트 번호가 제대로 입력되었는지 확인
2. 리슨 소켓 생성: 지정된 포트에서 클라이언트 연결 대기
3. 무한 루프: 클라이언트 연결을 계속 수락하고 처리

처리 반복문

while (1) {
  clientlen = sizeof(clientaddr);
  connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
  Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
  
  printf("Accepted connection from (%s, %s)\n", hostname, port);
  doit(connfd);
  Close(connfd);
}

각 클라이언트 연결에 대해 doit() 함수를 호출하여 HTTP 요청을 처리한 후 연결을 닫음

2. HTTP 요청 처리 (doit 함수)

doit() 함수는 웹 서버의 핵심 로직으로, 7단계로 나누어 HTTP 요청을 처리:

1단계: HTTP 요청 라인 읽기

Rio_readinitb(&rio, fd);
Rio_readlineb(&rio, buf, MAXLINE);
sscanf(buf, "%s %s %s", method, uri, version);

클라이언트로부터 "GET /index.html HTTP/1.1" 형태의 요청 라인을 읽고 파싱

2단계: GET 메서드 확인

if (strcasecmp(method, "GET")) {
  clienterror(fd, method, "501", "Not implemented", 
              "Tiny does not implement this method");
  return;
}

Tiny는 GET 메서드만 지원하므로 다른 메서드는 501 에러를 반환

3단계: HTTP 헤더 읽기

read_requesthdrs(&rio);

Host, Connection 등의 헤더를 읽고, 현재는 출력만 하고 별도 처리는 하지 않음

4단계: URI 파싱

is_static = parse_uri(uri, filename, cgiargs);

URI를 분석하여 정적 파일인지 동적 콘텐츠인지 판단하고, 파일 경로와 CGI 인수를 추출

5단계: 파일 존재 확인

if (stat(filename, &sbuf) < 0) {
  clienterror(fd, filename, "404", "Not found", 
              "Tiny couldn't find this file");
  return;
}

요청된 파일이 존재하지 않으면 404 에러를 반환

6-7단계: 정적/동적 콘텐츠 처리

if (is_static) {
  // 정적 파일 권한 확인 후 서비스
  serve_static(fd, filename, sbuf.st_size);
} else {
  // CGI 프로그램 실행 권한 확인 후 서비스
  serve_dynamic(fd, filename, cgiargs);
}

3. URI 파싱 (parse_uri 함수)

이 함수는 URI를 분석하여 정적/동적 콘텐츠를 구분하는 핵심 역할

정적 콘텐츠 처리

if (!strstr(uri, "cgi-bin")) {
  strcpy(cgiargs, "");
  strcpy(filename, ".");
  strcat(filename, uri);
  if (uri[strlen(uri)-1] == '/')
    strcat(filename, "home.html");
  return 1;
}
  • URI에 "cgi-bin"이 없으면 정적 파일로 판단
  • 루트 디렉터리("/")로 요청하면 기본 페이지 "home.html"로 리다이렉트

동적 콘텐츠 처리

else {
  ptr = index(uri, '?');
  if (ptr) {
    strcpy(cgiargs, ptr+1);
    *ptr = '\0';
  } else {
    strcpy(cgiargs, "");
  }
  strcpy(filename, ".");
  strcat(filename, uri);
  return 0;
}
  • URI에 "cgi-bin"이 있으면 동적 콘텐츠로 판단
  • '?' 뒤의 쿼리 스트링을 CGI 인수로 분리

4. 정적 파일 서비스 (serve_static 함수)

정적 파일을 클라이언트에게 전송하는 과정:

HTTP 응답 헤더 생성

get_filetype(filename, filetype);
sprintf(buf, "HTTP/1.0 200 OK\r\n");
sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
sprintf(buf, "%sConnection: close\r\n", buf);
sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
sprintf(buf, "%sContent-type: %s\r\n", buf, filetype);

메모리 매핑을 이용한 파일 전송

srcfd = Open(filename, O_RDONLY, 0);
srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
Close(srcfd);
Rio_writen(fd, srcp, filesize);
Munmap(srcp, filesize);

메모리 매핑을 사용하여 효율적으로 파일 내용을 전송

5. 동적 콘텐츠 서비스 (serve_dynamic 함수)

CGI 프로그램을 실행하여 동적 콘텐츠를 생성:

if (Fork() == 0) {
  setenv("QUERY_STRING", cgiargs, 1);
  Dup2(fd, STDOUT_FILENO);
  Execve(filename, emptylist, environ);
}
Wait(NULL);
  • 자식 프로세스를 생성하여 CGI 프로그램 실행
  • 쿼리 스트링을 환경 변수로 설정
  • 표준 출력을 클라이언트 소켓으로 리다이렉트

6. 에러 처리 (clienterror 함수)

웹 서버의 에러 처리는 사용자 친화적인 HTML 에러 페이지를 생성:

sprintf(body, "<html><title>Tiny Error</title>");
sprintf(body, "%s<body bgcolor=\"ffffff\">\r\n", body);
sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg);
sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause);

404, 403, 501 등의 HTTP 상태 코드와 함께 상세한 에러 메시지를 제공

7. MIME 타입 결정 (get_filetype 함수)

파일 확장자를 기반으로 적절한 MIME 타입을 결정:

if (strstr(filename, ".html"))
  strcpy(filetype, "text/html");
else if (strstr(filename, ".gif"))
  strcpy(filetype, "image/gif");
else if (strstr(filename, ".jpg"))
  strcpy(filetype, "image/jpeg");
// ... 기타 확장자들
else
  strcpy(filetype, "text/plain");

특징 및 제한사항

특징

  • 간단한 구조: 이해하기 쉬운 직관적인 코드
  • 정적/동적 모두 지원: 일반 파일과 CGI 프로그램 모두 처리
  • 메모리 매핑: 효율적인 파일 전송
  • 에러 처리: 친화적인 에러 페이지 제공

결론

  • HTTP 요청/응답 처리 방식
  • 정적/동적 콘텐츠 서비스 구분
  • CGI 프로그램 실행 과정
  • 메모리 매핑을 이용한 파일 I/O
  • 웹 서버의 기본 아키텍처

정적 페이지

동적 페이지

0개의 댓글