Tiny 웹 서버는 다음과 같은 주요 함수들로 구성:
main(): 서버 시작점, 클라이언트 연결 수락doit(): HTTP 요청 처리의 핵심 로직clienterror(): 에러 응답 생성read_requesthdrs(): HTTP 헤더 읽기parse_uri(): URI 파싱으로 정적/동적 콘텐츠 구분serve_static(): 정적 파일 서비스serve_dynamic(): 동적 콘텐츠(CGI) 서비스get_filetype(): 파일 확장자로 MIME 타입 결정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 요청을 처리한 후 연결을 닫음
doit() 함수는 웹 서버의 핵심 로직으로, 7단계로 나누어 HTTP 요청을 처리:
Rio_readinitb(&rio, fd);
Rio_readlineb(&rio, buf, MAXLINE);
sscanf(buf, "%s %s %s", method, uri, version);
클라이언트로부터 "GET /index.html HTTP/1.1" 형태의 요청 라인을 읽고 파싱
if (strcasecmp(method, "GET")) {
clienterror(fd, method, "501", "Not implemented",
"Tiny does not implement this method");
return;
}
Tiny는 GET 메서드만 지원하므로 다른 메서드는 501 에러를 반환
read_requesthdrs(&rio);
Host, Connection 등의 헤더를 읽고, 현재는 출력만 하고 별도 처리는 하지 않음
is_static = parse_uri(uri, filename, cgiargs);
URI를 분석하여 정적 파일인지 동적 콘텐츠인지 판단하고, 파일 경로와 CGI 인수를 추출
if (stat(filename, &sbuf) < 0) {
clienterror(fd, filename, "404", "Not found",
"Tiny couldn't find this file");
return;
}
요청된 파일이 존재하지 않으면 404 에러를 반환
if (is_static) {
// 정적 파일 권한 확인 후 서비스
serve_static(fd, filename, sbuf.st_size);
} else {
// CGI 프로그램 실행 권한 확인 후 서비스
serve_dynamic(fd, filename, cgiargs);
}
이 함수는 URI를 분석하여 정적/동적 콘텐츠를 구분하는 핵심 역할
if (!strstr(uri, "cgi-bin")) {
strcpy(cgiargs, "");
strcpy(filename, ".");
strcat(filename, uri);
if (uri[strlen(uri)-1] == '/')
strcat(filename, "home.html");
return 1;
}
else {
ptr = index(uri, '?');
if (ptr) {
strcpy(cgiargs, ptr+1);
*ptr = '\0';
} else {
strcpy(cgiargs, "");
}
strcpy(filename, ".");
strcat(filename, uri);
return 0;
}
정적 파일을 클라이언트에게 전송하는 과정:
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);
메모리 매핑을 사용하여 효율적으로 파일 내용을 전송
CGI 프로그램을 실행하여 동적 콘텐츠를 생성:
if (Fork() == 0) {
setenv("QUERY_STRING", cgiargs, 1);
Dup2(fd, STDOUT_FILENO);
Execve(filename, emptylist, environ);
}
Wait(NULL);
웹 서버의 에러 처리는 사용자 친화적인 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 상태 코드와 함께 상세한 에러 메시지를 제공
파일 확장자를 기반으로 적절한 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");

