본문 바로가기
KraftonJungle2기/Today I Learned

[TIL] CSAPP 11장 공부4 - tiny 서버 정리

by SooooooooS 2023. 5. 22.
728x90

1. 아침 문제 풀이

1. 20920번 - 영단어 암기는 괴로워

import sys

N, M = map(int, sys.stdin.readline().split())

words = {} # key = 영어단어 / value = 영어단어 입력 횟수
for _ in range(N) :
    w = sys.stdin.readline().strip()
    if len(w) >= M :
        if w in words :
            words[w] += 1
        else :
            words[w] = 1

# 단어를 사전 순으로 정렬
words = dict(sorted(words.items()))
# 단어를 입력 횟수가 많은 순으로 정렬하되 같으면 길이가 긴 것부터 정렬
words = dict(sorted(words.items(), key=lambda x:(x[1], len(x[0])), reverse=True))

for w  in words.keys() :
    print(w)
정렬을 어떻게 하느냐가 관건이었던 문제인 것 같다.
우선 우리가 필요한 정보는 단어가 나오는 횟수도 저장을 해 둘 필요가 있기 때문에 dict 을 사용했다.

그리고 정렬의 가장 마지막 기준인 알파벳 사전순으로 먼저 정렬을 했다.
그 후로 자주 나오는 단어, 길이는 모두 큰 것부터 이므로 기준을 같이 주어 정렬한다.

이번에는 딕셔너리를 정렬하는 법에 대해 알 수 있었다.

< 참고 >
https://dogsavestheworld.tistory.com/110

2. Tiny Server

1. doit

한 개의 HTTP Transaction을 처리하는 함수
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_t rio;

  //요청 읽어오기
  Rio_readinitb(&rio, fd);
  Rio_readlineb(&rio, buf, MAXLINE);
  printf("Request headers:\n");
  printf("%s", buf);
  sscanf(buf, "%s %s %s", method, uri, version);
  
  //strcasecmp = 대소문자를 구분하지 않고 두 문자열이 같을 경우 0을 반환한다.
  //GET Method가 아닐 경우 오류 발생
  if(!(strcasecmp(method, "GET")) && !(strcasecmp(method, "HEAD"))) {
    clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
    return;
  }
  read_requesthdrs(&rio); //Read request header

  is_static = parse_uri(uri, filename, cgiargs); //정적인지 동적인지 확인하기
  
  //원하는 파일이 없을 경우
  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");
      return;
    }
    serve_static(fd, filename, sbuf.st_size, method);
  }
  //동적 콘텐츠일 경우
  else {
    if(!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { //권한이 없을 경우
      clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run the CGI program");
      return;
    }
    serve_dynamic(fd, filename, cgiargs, method);
  }
}
  • GET, HEAD 메소드만 지원하는 코드이다.
  • 각각의 오류 상황을 확인하여 적절한 에러를 출력한다.
  • 정적 콘텐츠 vs 동적 콘텐츠 구분하여 알맞은 함수 호출하기

2. clienterror

HTTP 요청에 오류가 있을 경우 오류 메세지를 클라이언트에게 전송하는 함수
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) {
  char buf[MAXLINE], body[MAXBUF];

  //응답 본체(body) 생성 - 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);
  sprintf(body, "%s<hr><em>The Tiny Web server</em>\r\n", body);

  //응답 헤더(buf) 생성 후 헤더와 본체 모두 클라이언트에게 전송
  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));
}
  • 인자로 받은 오류가 발생한 클라이언트 연결 소켓에 인자로 받은 오류 정보를 적절히 보낸다.

3. read_requesthrs

Tiny server는 요청 헤더 내 어떠한 정보도 사용하지 않으므로 읽기만 하고 무시하는 함수
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;
}
  • 요청 헤더는 여러 개일 수도 있고 한 개일 수도 있다.
  • 현재는 사용할 일이 없으므로 무시한다.

4. parse_uri

Tiny server의 정책을 가정하는 함수
int parse_uri(char *uri, char *filename, char *cgiargs) {
  char *ptr;

  //strstr() 함수 = 문자열 내의 특정 문자열이 존재하는지 확인하는 함수
  //uri에 cgi-bin가 포함되어 있는지 확인
  if(!strstr(uri, "cgi-bin")) { //정적 콘텐츠일 경우 = cgi-bin이 포함되어 있지 않다.
    strcpy(cgiargs, "");
    strcpy(filename, "."); //현재 directory 경로(?) 복사
    strcat(filename, uri);
    if (uri[strlen(uri)-1] == '/') {
      strcat(filename, "home.html");
    }
    return 1;
  }
  else { //동적 콘텐츠일 경우 = cgi-bin이 포함되어 있다.
    ptr = index(uri, '?'); //파일이름과 인자를 구분하는 구분자 = ?
    if(ptr) {
      strcpy(cgiargs, ptr + 1);
      *ptr = '\0';
    }
    else {
      strcpy(cgiargs, "");
    }
    strcpy(filename, ".");
    strcat(filename, uri);
    return 0;
  }
}
  • 정적 콘텐츠의 위치 = 자신의 현재 디렉토리
  • 동적 콘텐츠의 위치 = cgi-bin 디렉토리
  • 정적 콘텐츠일 경우 home.html 파일을 보여주도록 한다.
  • 동적 콘텐츠일 경우 입력받은 cgi-bin 속 파일과 인자들의 결과를 보여주도록 한다
    • 아래의 동적 콘텐츠 사용해보기에 결과 확인 가능

4. serve_static

Tiny server에서 정적 콘텐츠를 처리하는 함수
void serve_static(int fd, char *filename, int filesize, char *method) {
  int srcfd;
  char *srcp, filetype[MAXLINE], buf[MAXBUF];

  get_filetype(filename, filetype); //파일 확장자로 type찾기

  //응답 헤더 생성
  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 header:\n");
  printf("%s", buf);

  if(strcasecmp(method, "GET") == 0) {
    srcfd = Open(filename, O_RDONLY, 0); //읽기 전용으로 파일 열기
    //Mamp() = 메모리 매핑 함수
    //파일 안의 내용을 메모리 매핑하여 srcp에 저장
    // srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
    srcp = (char *)malloc(filesize); //malloc사용하기
    rio_readn(srcfd, srcp, filesize);
    Close(srcfd);
    Rio_writen(fd, srcp, filesize);
    //Munmap(srcp, filesize);
    free(srcp); //malloc 해제하기
  }
}
  • 정적 콘텐츠 = HTML, Text, GIF, PNG, JPEG, MP4
  • HEAD 메소드도 사용할 수 있도록 하기 위해
    • 응답 헤더는 GET, HEAD 모든 메소드에서 출력
    • 응답 본체는 GET 메소드일 경우에만 출력
  • malloc 함수를 사용해 보았다.

5. get_filetype

입력 파일의 타입을 구하는 함수
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")) { //mp4영상을 위한 타입 설정
    strcpy(filetype, "video/mp4");
  }
  else {
    strcpy(filetype, "text/plain");
  }
}
  • 위의 가능한 정적 콘텐츠의 종류 모두 구분
  • mime type을 참고해서 MP4 를 추가했다.

6. serve_dynamic

동적 콘텐츠를 처리하는 함수
void serve_dynamic(int fd, char *filename, char *cgiargs, char *method) {
  char buf[MAXLINE], *emptylist[] = { NULL };

  //응답 헤더 생성
  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(Fork() == 0) {
    setenv("QUERY_STRING", cgiargs, 1); //환경변수 설정
    setenv("REQUEST_METHOD", method, 1);
    Dup2(fd, STDOUT_FILENO); //표준 출력을 클라이언트 소켓으로 redirection
    Execve(filename, emptylist, environ); //CGI 프로그램 실행
  }
  //부모 프로세스는 자식 프로세스가 종료될 때까지 대기
  Wait(NULL);
}
  • 자식 프로세서를 생성하여 동적 콘텐츠를 제공한다.
  • CGI 프로그램 실행
  • 동적 콘텐츠 역시 HEAD 메소드를 지원할 시 응답 헤더만 출력할 수 있어야 한다.
  • 이를 위해 환경변수 REQUEST_METHOD를 설정해준다.

 

< 참고 >

🔗 https://straw961030.tistory.com/246

🔗 https://www.codingfactory.net/11877

 

< fork - SIGCHLD 참고 공부 자료 >

🔗 https://plummmm.tistory.com/67

🔗 https://velog.io/@jungbumwoo/fork-를-알아보자

🔗 https://codetravel.tistory.com/42

🔗 http://egloos.zum.com/youmin3/v/2188455


3.  정적 콘텐츠 - 영상 띄우기

1. HTML 비디오 설정 추가하기

<video autoplay controls loop muted>
    <source src="jjangu.mp4" type="video/mp4">
    짱구 - 어떡하지
</video>

서버 접속 결과 화면


4. 동적 콘텐츠 사용해보기

실행 결과 확인

< 맥북에서 telnet 사용하기 >

🔗 https://velog.io/@stthunderl/telnet-%EB%A7%A5%EB%B6%81%EC%97%90%EC%84%9C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0-%EC%98%88%EC%A0%9C


학교를 다니면서 이론적으로만 배웠던 웹 서버의 동작 과정을 알 수 있는 시간이었다.

확실히 처음봤을 때 이해하려고 했지만 외운 내용이었지만 다시 공부하고 직접 해보니 이해하기 수월했다.

728x90