웹서버 Tiny.c 코드 뜯어봄

Designated Hitter Jack·2023년 9월 16일

SW 사관학교 정글

목록 보기
19/44
post-thumbnail

int main()

//터미널에 ./tiny 4000 을 입력했을 때, argc = 2, argv[0] = tiny, argv[1] = 4000
int main(int argc, char **argv) {
  //listen socket 과 connection socket 선언
  int listenfd, connfd;
  //connection을 위한 hostname 과 port 선언
  char hostname[MAXLINE], port[MAXLINE]; 
  socklen_t clientlen;
  struct sockaddr_storage clientaddr;

  /* Check command line args */
  if (argc != 2) {
    fprintf(stderr, "usage: %s <port>\n", argv[0]);
    exit(1);
  }
  //listen socket open, 인자로 포트 번호를 넘겨줌
  //Open_listenfd는 요청받은 준비가 된 듣기 식별자를 리턴(listenfd)
  listenfd = Open_listenfd(argv[1]); 
	
    //무한루프 서버
  while (1) {
  	//accept 함수 인자에 넣기 위한 주소 길이 계산
    clientlen = sizeof(clientaddr);
    //반복적으로 연결 요청을 접수
    //accept함수 (듣기 식별자, 소켓주소구조체의 주소, 주소의 길이)
    connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
    //getnameinfo함수: 소켓주소구조체를 호스트주소, 포트 번호로 변환
    Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
    printf("Accepted connection from (%s, %s)\n", hostname, port);
    doit(connfd);   // transaction 실행
    Close(connfd);  // 연결 닫기
  }
}

Accept 함수 등에서 주소의 길이를 인자로 받는 이유

  1. IPv4인지, IPv6인지 판단하기 위함
  2. 한 번 더 검증을 거쳐 안정성을 높이기 위함
  3. 훗날 다른 기술이 도입되었을 때에도 사용할 수 있도록 확장성을 높이기 위함

getnameinfo / getaddrinfo

getaddrinfo 함수는 호스트 이름: 호스트 주소, 서비스 이름: 포트 번호의 스트링 표시를 소켓 주소 구조체로 변환하는 함수.
getnameinfo 함수는 반대로 소켓 주소 구조체를 호스트의 주소와 포트 번호의 스트링으로 변환하는 함수

void doit()

void doit(int fd) {
  int is_static;
  struct stat sbuf;
  char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
  char filename[MAXLINE], cgiargs[MAXLINE];
  //rio_readlineb를 위해 rio_t 타입 구조체의 읽기 버퍼를 선언
  rio_t rio;

  //req line, headers 읽기
  //&rio 주소를 가지는 읽기 버퍼와 식별자 connfd 연결
  Rio_readinitb(&rio, fd);
  //버퍼에서 읽은 것이 담겨있음
  Rio_readlineb(&rio, buf, MAXLINE);
  printf("Request headers:\n");
  //"GET / HTTP/1.1"
  printf("%s", buf);
  sscanf(buf, "%s %s %s", method, uri, version); //버퍼에서 자료형을 읽음
  //GET이 아닌 메소드를 req했다면 error를 return 
  if ((strcasecmp(method, "GET")) && (strcasecmp(method, "HEAD"))) { //숙제
    clienterror(fd, method, "501", "Not implementd", "Tiny does not implement this method.");
    return;
  }
  
  read_requesthdrs(&rio); //req headers 읽기

  //GET req로부터 URI parse
  //URI를 parse해서 파일 이름, 비어있을수 있는 CGI인자 스트링으로 분석
  //정적/동적 컨텐츠인지 판단
  is_static = parse_uri(uri, filename, cgiargs); 
  printf("uri: %s, filename: %s, cgiargs: %s \n", uri, filename, cgiargs);
  //file이 disk에 없다면 error를 return
  //stat은 파일 정보를 불러오고 sbuf에 내용을 적어줌. ok -> 0, error -> -1
  if (stat(filename, &sbuf) < 0) {
    clienterror(fd, filename, "404", "Not found", "tiny couldn't find this file.");
    return;
  }
  
  if (is_static) { //정적 콘텐츠 제공
    //(읽을 수 있는) 일반 파일인지, 파일에 대한 읽기 권한을 가지고 있는지 판단
    if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
      clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file."); 
      //못 읽으면 error return
      return;
    }
    //static content 제공
    serve_static(fd, filename, sbuf.st_size, method);
  } else { //동적 콘텐츠 제공
    //(읽을 수 있는) 일반 파일인지, CGI 프로그램을 실행할 수 없으면 error return
    if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
      clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run the CGI program.");
      return;
    }
    //dynamic content 제공
    serve_dynamic(fd, filename, cgiargs, method);
  }
}

UNIX I/O의 개념과 파일, Rio(Robust i/o)

참고한 글들
https://it-serial.tistory.com/entry/Linux-%EB%A6%AC%EB%88%85%EC%8A%A4-%ED%8C%8C%EC%9D%BC-%EC%A2%85%EB%A5%98%EC%99%80-%EB%94%94%EB%A0%89%ED%86%A0%EB%A6%AC-%EA%B5%AC%EC%A1%B0-%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%82%AC%EC%9A%A9-%EA%B5%AC%EC%A1%B0
https://coding-factory.tistory.com/499
https://suhwanc.tistory.com/131

리눅스의 파일

리눅스에서 파일은 연속된 m개의 바이트이다.
네트워크, 디스크, 터미널 같은 모든 I/O 디바이스들은 파일로 모델링 된다. 모든 입력과 출력은 해당 파일을 읽거나 쓰는 형식으로 수행된다.

읽기 연산은 현재 파일 위치 k에서 시작해서 n(n > 0)바이트를 파일에서 메모리로 복사하고 k를 n만큼 증가시킨다.
k > m(파일의 최대 길이) 의 읽기 연산을 수행하면 End-Of-File(EOF)라고 알려진 조건이 발생하며, 이는 응용 프로그램에서 감지할 수 있다. 하지만 실제 파일 끝에서 "EOF문자"가 명시적으로 존재하는 것은 아니다.

쓰기 연산은 현재 파일 k에서 시작해서 n(n > 0)바이트를 메모리에서 파일로 복사하고 k를 갱신시킨다.

리눅스 파일의 종류

  • 일반 파일: 데이터를 저장하는데 주로 사용된다. 실행파일이나 이미지 파일의 경우 데이터가 바이너리 형태로 저장되므로 바이너리 파일이라고도 한다.
  • 디렉토리: 디렉토리에는 해당 디렉토리에 저장된 파일이나 하위 디렉토리에 대한 정보가 저장된다.
  • 심벌릭 링크: 원본 파일을 대신하도록 원본 파일을 다른 파일명으로 지정한것으로 윈도우즈의 바로가기와 비슷하다.
  • 장치 파일: 하드웨어같은 장치들을 관리하기 위한 파일이다. 리눅스에서는 각종 장치를 관리하기 위해서 해당 장치 파일에 접근해야 한다.
  • pipe: 로컬 프로세스 간 커뮤니케이션 용도로 사용된다.
  • socket: 네트워크 커뮤니케이션 용도로 사용된다.

Robust i/o

우선 robust라는 단어는 건장한, 견고한 정도의 뜻을 가진 단어고 비단 Computer Science분야에서만 쓰이는 단어는 아니다.
Rio 패키지는 short count를 자동으로 처리한다. (Unix I/O 와 차이).
short count 가 발생할 수 있는 네트워크 프로그램 같은 어플리케이션에서 편리하고 안정적이고 효율적인 I/O 패키지이다.
일부의 경우에 Unix I/O의 Read, Write 함수는 어플리케이션이 요청하는 것보다 더 적은 바이트를 전송한다. 이를 short count라고 명시한다. (Short Count는 에러를 나타내는 것은 아니다.)
보통 short counts는 다음과 같은 상황일 때 발생한다.

  • EOF (end-of-file)을 읽는 도중 만난 경우
  • text lines 을 터미널로부터 읽어올 때 (예측이 힘듦)
  • 네트워크 소켓 통신 시

반면 절대 발생하지 않는 경우도 있는데 아래와 같은 상황일 때이다.

  • 디스크 파일을 읽고 쓸 때 (크기가 정해져 있는 상황)

void clienterror()

void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
  char buf[MAXLINE], body[MAXBUF];
  //브라우저 사용자에게 에러를 설명하는 res 본체에 HTML도 함께 보낸다.
  //HTML res는 본체에서 컨텐츠의 크기와 타입을 나타내야 하기 때문에,
  //HTML 컨텐츠를 한 개의 스트링으로 만들고 그 길이도 잼.
  //이는 sprintf를 통해 body는 인자에 스택되어 하나의 긴 스트링으로 저장된다.
  
  //HTTP res body 만들기
  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);
  sprintf(body, "%s<hr><em>The Tiny Web server</em>\r\n", body);

  //HTTP res 출력하기
  sprintf(buf, "HTTP/1.1 %s %s\r\n", errnum, shortmsg);
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "Content-type: text/html\r\n");
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
  Rio_writen(fd, buf, strlen(buf));
  Rio_writen(fd, body, strlen(body));
}

int sprintf()

참고한 글

#include <stdio.h>
int sprintf(char* str, const char* format, ...);

int fprintf()

int fprintf(FILE* stream, const char* format, ...)

void read_requesthdrs(rio_t *rp)

//tiny 웹서버는 req header를 읽고 무시함.
void read_requesthdrs(rio_t *rp) {
  char buf[MAXLINE];

  Rio_readlineb(rp, buf, MAXLINE);

  while (strcmp(buf, "\r\n")) {
    Rio_readlineb(rp, buf, MAXLINE);
    printf("%s", buf);
  }

  return;
}

int parse_uri()

int parse_uri(char *uri, char *filename, char *cgiargs) {
  char *ptr;
  //정적 콘텐츠: URI에 /cgi-bin을 포함하는 경로가 없다.
  if (!strstr(uri, "cgi-bin")) { 
    strcpy(cgiargs, "");
    strcpy(filename, ".");
    strcat(filename, uri);
    
    //uri 문자열 끝이 / 인 경우 home.html을 filename에 붙여줌
    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;
  }
}

strstr, strcpy, scrcat, index

strstr

char* strstr(char* str1, const char* str2);

str1에서 str2와 일치하는 문자열이 있는지 확인하는 함수.
일치하는 문자열이 있다면 해당 위치의 포인터(char*)를 return한다.
일치하는 문자열을 찾지 못했다면 NULL 포인터를 return 하기 때문에 NULL check를 해야한다.

strcpy, strncpy

char* strcpy(char* dest, const char* origin);

origin의 문자열 전체를 dest로 복사하는 함수이다.

char* strncpy(char* dest, const char* origin, size_t n);

n은 number를 의미한다.
origin의 문자열을 dest로 n번만큼 복사하는 함수이다.

주의할 점으로는 strcpy는 문자열 끝(다시말해 '\0'까지)까지 복사를 하기 때문에 문자열의 길이를 계산할때도, 문자열을 다른 곳으로 복사할 때에도 이를 기억해야 한다.

strcat, strncat

char* strcat(char* dest, const char* origin);

origin에 있는 문자열을 dest 뒤에 이어붙이는 함수이다.
dest 뒤에 있던 \0은 사라지고 그 위치에 origin의 문자열이 붙는다.

char* strncat(char*dest, const char* origin, size_t n);

origin에 있는 문자열 n개를 dest뒤에 이어붙이는 함수이다.
dest 뒤에 있던 \0은 사라지고 그 위치에 origin의 문자열 중 앞에서부터 n개까지가 붙는다.

index

char* index(const char *s, int c); 

문자열 s에서 검색할 문자 c를 찾아 문자가 있는 위치를 return 하는 함수이다.
찾는 문자가 없다면 NULL을 return 한다.

void serve_static()

void serve_static(int fd, char *filename, int filesize, char* method) {
  int srcfd;
  char *srcp, filetype[MAXLINE], buf[MAXBUF];

  //client에게 res headers 보내기
  //접미어를 통해 filetype 결정
  get_filetype(filename, filetype);
  //client 에게 req line, req headers 보내기
  //데이터를 보내기 전에 버퍼로 임시로 가지고 있다.
  sprintf(buf, "HTTP/1.1 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\r\n", buf, filetype);
  Rio_writen(fd, buf, strlen(buf));
  //서버에 출력하기
  printf("Response headers:\n");
  printf("%s", buf);

  if (strcasecmp(method, "HEAD") != 0) { //숙제: method 가 HEAD일 때 body를 보내지 않기 위함
    //client에게 res body 보내기
    //읽을 수 있는 파일로 열기(open read only)
    //숙제: malloc으로 바꾸기
    srcfd = Open(filename, O_RDONLY, 0);
    //malloc으로 가상 메모리 할당
    srcp = (char *)malloc(filesize);
    Rio_readn(srcfd, srcp, filesize);
    Close(srcfd);
    Rio_writen(fd, srcp, filesize);
    free(srcp);
    
    // srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
    // Close(srcfd);
    // Rio_writen(fd, srcp, filesize);
    // Munmap(srcp, filesize);
  }
}

HTTP method 'HEAD'

HTTP method HEAD는 GET과 기본적으로 유사한 res를 요청한다.
하지만 GET과는 달리 HTTP res의 header만 요청하고 body는 요청하지 않는다.

왜 쓰는가?

요청하는 데이터 양이 줄어들기 때문에 빠르게 서버의 상태를 조회할 수 있다.
res header의 content-length는 GET과 똑같기 때문에 resource 양에 대한 조회만 할때는 유용하게 사용할 수 있다.

strcasecmp

#include <strings.h>
int strcasecmp(const char* string1, const char* string2);

strcasecmp 함수는 string1과 string2를 대소문자를 구분하지 않고 비교한다. 문자열 내부의 모든 영문자는 비교전에 소문자로 변환된다.
두 문자열이 같다면 0을 return, string1이 더 작다면 음수, 더 크다면 양수를 return 한다.

void get_filetype()

//filename 으로부터 file 의 형식을 알아내는 함수
void get_filetype(char *filename, char *filetype) { 
  if (strstr(filename, ".html")) {
    strcpy(filetype, "text/html");
  } else if (strstr(filename, ".gif")) {
    strcpy(filetype, "image/gif");
  } else if (strstr(filename, ".png")) {
    strcpy(filetype, "image/png");
  } else if (strstr(filename, ".jpg")) {
    strcpy(filetype, "image/jpeg");
  } else if (strstr(filename, ".mp4")) { //숙제
    strcpy(filetype, "video/mp4");
  } else {
    strcpy(filetype, "text/plain");
  }
}

void serve_dynamic()

void serve_dynamic(int fd, char *filename, char *cgiargs, char *method) {
  char buf[MAXLINE], *emptylist[] = {NULL};

  //HTTP res의 first part return
  sprintf(buf, "HTTP/1.1 200 OK\r\n");
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "Server: Tiny Web Server\r\n");
  Rio_writen(fd, buf, strlen(buf));

  if (strcasecmp(method, "HEAD") != 0) { //숙제: method 가 HEAD일 때 body를 보내지 않기 위함
    
    if (Fork() == 0) { //동적 콘텐츠를 실행하고 결과를 return할 자식 프로세스를 포크
    setenv("QUERY_STRING", cgiargs, 1);
    Dup2(fd, STDOUT_FILENO); //redirect stdout to client
    Execve(filename, emptylist, environ); //CGI program 실행
    }

    Wait(NULL); //자식 프로세스가 실행되고 결과를 출력하고 종료될 때까지 기다림
  }

tiny.c 코드

/* $begin tinymain */
/*
 * tiny.c - A simple, iterative HTTP/1.0 Web server that uses the
 *     GET method to serve static and dynamic content.
 *
 * Updated 11/2019 droh
 *   - Fixed sprintf() aliasing issue in serve_static(), and clienterror().
 */
#include "csapp.h"

void doit(int fd);
void read_requesthdrs(rio_t *rp);
int parse_uri(char *uri, char *filename, char *cgiargs);
void serve_static(int fd, char *filename, int filesize, char* method);
void get_filetype(char *filename, char *filetype);
void serve_dynamic(int fd, char *filename, char *cgiargs, char* method);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);

//터미널에 ./tiny 4000 을 입력했을 때, argc = 2, argv[0] = tiny, argv[1] = 4000
int main(int argc, char **argv) {
  //listen socket 과 connection socket 선언
  int listenfd, connfd;
  //connection을 위한 hostname 과 port 선언
  char hostname[MAXLINE], port[MAXLINE]; 
  socklen_t clientlen;
  struct sockaddr_storage clientaddr;

  /* Check command line args */
  if (argc != 2) {
    fprintf(stderr, "usage: %s <port>\n", argv[0]);
    exit(1);
  }
	//listen socket open, 인자로 포트 번호를 넘겨줌
  //Open_listenfd는 요청받은 준비가 된 듣기 식별자를 리턴(listenfd)
  listenfd = Open_listenfd(argv[1]); 
	
    //무한루프 서버
  while (1) {
  	//accept 함수 인자에 넣기 위한 주소 길이 계산
    clientlen = sizeof(clientaddr);
    //반복적으로 연결 요청을 접수
    //accept함수 (듣기 식별자, 소켓주소구조체의 주소, 주소의 길이)
    connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
    //getnameinfo함수: 소켓주소구조체를 호스트주소, 포트 번호로 변환
    Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0);
    printf("Accepted connection from (%s, %s)\n", hostname, port);
    doit(connfd);   // transaction 실행
    Close(connfd);  // 연결 닫기
  }
}

void doit(int fd) {
  int is_static;
  struct stat sbuf;
  char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
  char filename[MAXLINE], cgiargs[MAXLINE];
  //rio_readlineb를 위해 rio_t 타입 구조체의 읽기 버퍼를 선언
  rio_t rio;

  //req line, headers 읽기
  //&rio 주소를 가지는 읽기 버퍼와 식별자 connfd 연결
  Rio_readinitb(&rio, fd);
  //버퍼에서 읽은 것이 담겨있음
  Rio_readlineb(&rio, buf, MAXLINE);
  printf("Request headers:\n");
  //"GET / HTTP/1.1"
  printf("%s", buf);
  sscanf(buf, "%s %s %s", method, uri, version); //버퍼에서 자료형을 읽음
  //GET이 아닌 메소드를 req했다면 error를 return 
  if ((strcasecmp(method, "GET")) && (strcasecmp(method, "HEAD"))) { //숙제
    clienterror(fd, method, "501", "Not implementd", "Tiny does not implement this method.");
    return;
  }
  
  read_requesthdrs(&rio); //req headers 읽기

  //GET req로부터 URI parse
  //URI를 parse해서 파일 이름, 비어있을수 있는 CGI인자 스트링으로 분석
  //정적/동적 컨텐츠인지 판단
  is_static = parse_uri(uri, filename, cgiargs); 
  printf("uri: %s, filename: %s, cgiargs: %s \n", uri, filename, cgiargs);
  //file이 disk에 없다면 error를 return
  //stat은 파일 정보를 불러오고 sbuf에 내용을 적어줌. ok -> 0, error -> -1
  if (stat(filename, &sbuf) < 0) {
    clienterror(fd, filename, "404", "Not found", "tiny couldn't find this file.");
    return;
  }
  
  if (is_static) { //정적 콘텐츠 제공
    //(읽을 수 있는) 일반 파일인지, 파일에 대한 읽기 권한을 가지고 있는지 판단
    if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
      clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file."); 
      //못 읽으면 error return
      return;
    }
    //static content 제공
    serve_static(fd, filename, sbuf.st_size, method);
  } else { //동적 콘텐츠 제공
    //(읽을 수 있는) 일반 파일인지, CGI 프로그램을 실행할 수 없으면 error return
    if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
      clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run the CGI program.");
      return;
    }
    //dynamic content 제공
    serve_dynamic(fd, filename, cgiargs, method);
  }
}

void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
  char buf[MAXLINE], body[MAXBUF];
  //브라우저 사용자에게 에러를 설명하는 res 본체에 HTML도 함께 보낸다.
  //HTML res는 본체에서 컨텐츠의 크기와 타입을 나타내야 하기 때문에,
  //HTML 컨텐츠를 한 개의 스트링으로 만들고 그 길이도 잼.
  //이는 sprintf를 통해 body는 인자에 스택되어 하나의 긴 스트링으로 저장된다.
  
  //HTTP res body 만들기
  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);
  sprintf(body, "%s<hr><em>The Tiny Web server</em>\r\n", body);

  //HTTP res 출력하기
  sprintf(buf, "HTTP/1.1 %s %s\r\n", errnum, shortmsg);
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "Content-type: text/html\r\n");
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body));
  Rio_writen(fd, buf, strlen(buf));
  Rio_writen(fd, body, strlen(body));
}

//tiny 웹서버는 req header를 읽고 무시함.
void read_requesthdrs(rio_t *rp) {
  char buf[MAXLINE];

  Rio_readlineb(rp, buf, MAXLINE);

  while (strcmp(buf, "\r\n")) {
    Rio_readlineb(rp, buf, MAXLINE);
    printf("%s", buf);
  }

  return;
}

int parse_uri(char *uri, char *filename, char *cgiargs) {
  char *ptr;
  //정적 콘텐츠: URI에 /cgi-bin을 포함하는 경로가 없다.
  if (!strstr(uri, "cgi-bin")) { 
    strcpy(cgiargs, "");
    strcpy(filename, ".");
    strcat(filename, uri);
    
    //uri 문자열 끝이 / 인 경우 home.html을 filename에 붙여줌
    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;
  }
}

void serve_static(int fd, char *filename, int filesize, char* method) {
  int srcfd;
  char *srcp, filetype[MAXLINE], buf[MAXBUF];

  //client에게 res headers 보내기
  //접미어를 통해 filetype 결정
  get_filetype(filename, filetype);
  //client 에게 req line, req headers 보내기
  //데이터를 보내기 전에 버퍼로 임시로 가지고 있다.
  sprintf(buf, "HTTP/1.1 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\r\n", buf, filetype);
  Rio_writen(fd, buf, strlen(buf));
  //서버에 출력하기
  printf("Response headers:\n");
  printf("%s", buf);

  if (strcasecmp(method, "HEAD") != 0) { //숙제: method 가 HEAD일 때 body를 보내지 않기 위함
    //client에게 res body 보내기
    //읽을 수 있는 파일로 열기(open read only)
    //숙제: malloc으로 바꾸기
    srcfd = Open(filename, O_RDONLY, 0);
    //malloc으로 가상 메모리 할당
    srcp = (char *)malloc(filesize);
    Rio_readn(srcfd, srcp, filesize);
    Close(srcfd);
    Rio_writen(fd, srcp, filesize);
    free(srcp);
    
    // srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
    // Close(srcfd);
    // Rio_writen(fd, srcp, filesize);
    // Munmap(srcp, filesize);
  }
}

//filename 으로부터 file 의 형식을 알아내는 함수
void get_filetype(char *filename, char *filetype) { 
  if (strstr(filename, ".html")) {
    strcpy(filetype, "text/html");
  } else if (strstr(filename, ".gif")) {
    strcpy(filetype, "image/gif");
  } else if (strstr(filename, ".png")) {
    strcpy(filetype, "image/png");
  } else if (strstr(filename, ".jpg")) {
    strcpy(filetype, "image/jpeg");
  } else if (strstr(filename, ".mp4")) { //숙제
    strcpy(filetype, "video/mp4");
  } else {
    strcpy(filetype, "text/plain");
  }
}

void serve_dynamic(int fd, char *filename, char *cgiargs, char *method) {
  char buf[MAXLINE], *emptylist[] = {NULL};

  //HTTP res의 first part return
  sprintf(buf, "HTTP/1.1 200 OK\r\n");
  Rio_writen(fd, buf, strlen(buf));
  sprintf(buf, "Server: Tiny Web Server\r\n");
  Rio_writen(fd, buf, strlen(buf));

  if (strcasecmp(method, "HEAD") != 0) { //숙제: method 가 HEAD일 때 body를 보내지 않기 위함
    
    if (Fork() == 0) { //동적 콘텐츠를 실행하고 결과를 return할 자식 프로세스를 포크
    setenv("QUERY_STRING", cgiargs, 1);
    Dup2(fd, STDOUT_FILENO); //redirect stdout to client
    Execve(filename, emptylist, environ); //CGI program 실행
    }

    Wait(NULL); //자식 프로세스가 실행되고 결과를 출력하고 종료될 때까지 기다림
  }
  
}
profile
Fear always springs from ignorance.

1개의 댓글

comment-user-thumbnail
2023년 9월 19일

다음 시리즈 언제나와요 ?

답글 달기