크래프톤 정글 TIL : 0819

lazyArtisan·2024년 8월 19일
0

정글 TIL

목록 보기
50/147

🎯 To-do List


⌚ Time Tracking

  • 09:35 - 10:05 : 기상, 공부 세팅
  • 10:05 - 10:45 : tiny 웹서버 annotating 마저 하기
  • 10:45 - 14:45 : 병원, 점심, 휴식, 교육장 이동
  • 14:45 - 15:55 : 알고리즘 스터디
  • 16:10 - 17:25 : tiny 웹서버 homework
  • 17:25 - 18:20 : 저녁, 운동
  • 18:20 - 22:40 : tiny 웹서버 homework
  • 22:40 - : 기록 정리, 방 이동,

🔆 Today

  1. tiny 웹서버 annotating 마저 하기 ✅
  2. 숙제 문제 풀기 | 11.6c, 7, 9, 10, 11 ✅
  3. 알고리즘 스터디 | 3190 뱀 풀기 ☑️

🍪 Plan

  • 프록시 과제 도전
  • 퀴즈 봤던거 정리 (기술 면접 준비 +알파 느낌으로)
  • CS:APP 읽기 | 6장, 11.5 구현 전까지

✅☑️❌



📝 배운 것들


🏷️ atoi

#include <stdlib.h>  // C++ 에서는 <cstdlib>

int atoi(const char* str);

문자열을 정수로 변환한다.

🏷️ getenv

#include <stdlib.h>

char *getenv(const char *name);

getenv() 함수는 환경 변수 name의 값을 반환한다.

🏷️ setenv

#include <stdlib.h>

int setenv(const char *name, const char *value, int overwrite);
int unsetenv(const char *name);

setenv() 함수는 name이 없으면 환경 변수 name을 추가하고 value를 값으로 넣는다.

name이라는 환경 변수가 이미 있다면,
overwrite 값이 0이면 name의 값은 바뀌지 않고,
overwrite 값이 0이 아니면 name의 값은 value로 바뀐다.

unsetenv() 함수는 환경 변수에서 name을 지운다.

🏷️ dup2와 파일 식별자

파일 식별자

운영 체제는 프로세스가 파일을 열 때마다 식별자를 하나 만들고,
무슨 파일을 가리키고 있는지 등의 정보와 함께 테이블에 기록한다.

각 프로세스들도 파일을 열면 식별자를 만들고 자신의 파일 식별자 테이블에 기록한다.

기본적으로 파일 식별자 테이블의 0,1,2는 이미 할당되어 있다.
각각 표준 입력, 표준 출력, 표준 오류이다.
실제로 함수에서 사용하고 싶을 땐
기호 상수 STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO을 사용하는 것이 가독성을 위해 좋다.

dup

dup() 함수는 동일한 파일을 가리키는 식별자를 하나 더 만든다.

예를 들어, 파일 식별자 5파일 A를 가리키고 있다면,
dup(5)를 호출하면 5번 식별자와 동일한 파일 A를 가리키는 새로운 파일 식별자가 생성됩니다.

dup2

dup2() 함수는 파일 식별자가 가리키는 파일다른 파일 식별자에도 적용시킨다.
파일 a를 가리키는 파일 식별자 10가 있다면,
파일 b를 가리키던 파일 식별자 11도 a를 가리키게 할 수 있다.

이를 응용하면 특정 함수의 출력을 표준 출력에 리다이렉트할 수 있다.

int fw=open("사과상자.txt", O_APPEND|O_WRONLY);

'사과상자.txt'를 열고, 반환된 파일 식별자를 fw에 저장한다.

dup2(fw,1);

dup2로 파일 식별자 1이 가리키는 파일을
fw가 가리키는 '사과상자.txt'로 바꾼다.

printf("사과사과사과 \n");

이제 printf를 호출하면 화면에 출력되는 것이 아니라,
'사과상자.txt'에 입력된다.

참고 1: https://stackoverflow.com/questions/1720535/practical-examples-use-dup-or-dup2
참고 2: https://80000coding.oopy.io/b2ce62a9-502c-408b-9a76-262263a3b8b6

🏷️ fflush

#include <stdio.h>  // C++ 에서는 <cstdio>

int fflush(FILE* stream);

fflush(stdout)을 실행하면, 표준 출력 스트림(stdout)에 저장된 모든 데이터를 즉시 출력한다.

보통 printf 함수는 출력 내용을 버퍼에 저장하고, 버퍼가 꽉 차거나 프로그램이 종료될 때 실제로 화면에 출력한다. fflush(stdout)를 호출하면, 그 즉시 버퍼에 있는 내용을 모두 출력하여 버퍼를 비우게 된다.



🌐 Web Server


🔷 Tiny Web Server : Homework

📌 11.6 C.


Inspect the output from Tiny to determine the version of HTTP your
browser uses.

Tiny 서버 출력 보고 브라우저가 사용하는 HTTP 버전을 확인해라.

정확히 뭘 원하는진 모르겠는데, GET /favicon.ico HTTP/1.1 이 부분 읽고 1.1이라는 거 확인하고 끝내면 될듯?

📌 11.7


Extend Tiny so that it serves MPG video files. Check your work using a real
browser.

Tiny 서버 고쳐서 MPG 비디오 파일도 내놓을 수 있게 해라. 실제 브라우저로 확인해봐라.

아무도 안 쓰는 mpg 말고 mp4로 시도해봤다.

1. troubleshooting : mp4 does not show up

mime 타입 확인하는 리스트에 mp4 파일도 넣어줬고,

serve되는 html 파일에 video 태그도 추가해줬다.

하지만 브라우저에서 load되지 않았다.

ERR_CONTENT_LENGTH_MISMATCH 200 (OK)을 구글링해보니
HTTP 헤더에 정의된 데이터 크기와 실제로 전달된 데이터 크기가 달라서 그렇다고 함.

터미널 로그를 보니 content-type이 이상하게 돼있었음.

get_filetype() 함수를 확인해보니 filetype을 filename으로 잘못 적어놨음. 고쳤더니 정상 작동.

✅ 완성

동영상 정상 작동 확인.

📌 11.9


Modify Tiny so that when it serves static content, it copies the requested file to the connected descriptor using malloc, rio_readn, and rio_writen, instead of mmap and rio_writen.

정적 컨텐츠 내놓을 때 요청받은 파일을 식별자로 복사하는 걸 mmap + rio_writen 말고
malloc + rio_readn + rio_writen 으로 구현해봐라.

✅ 완성

// srcp = mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
srcp = malloc(filesize);
rio_readn(srcfd, srcp, filesize);

이렇게 했더니 정상 작동.

gpt한테 포인터만 물어봤더니 알아서 추측해서 답변을 줘버려서
read를 어떻게 써야 할지 알아버리긴 했는데,
답이 간단하기도 하고 쓰는 건 답 직접 안 보고 내가 이해하고 썼으니
그렇게까지 학습에 치명적이지는 않은듯.
무슨 짓을 한 건지 정도만 정리하고 넘어가자.

기존에 적혀있던 mmap()의 각 매개변수가 무슨 역할을 하는지 살펴보자면,

  1. 0에서 메모리 매핑을 시작한다
  2. 매핑할 메모리의 길이는 filesize만큼이다
  3. PROT_READ를 설정해서 매핑된 메모리 영역을 읽기 전용으로 설정한다
  4. MAP_PRIVATE를 설정해서 메모리 변경 사항이 파일에 반영되지 않고, 다른 프로세스와 공유되지 않게 한다
  5. srcfd라는 파일 식별자에서 데이터를 (filesize만큼) 매핑한다
  6. 시작 주소를 기준으로 0만큼 떨어진 곳에 매핑한다

1., 3., 4., 6.은 딱히 필요하지 않은 정보이다.

2. : 매핑할 메모리의 길이는 filesize만큼이다 (= malloc() 호출)
5. : srcfd에서 (filesize만큼) 데이터를 가져온다 (=rio_readn() 호출)

각각 대응되는 함수로 바꿔주면 된다.

📌 11.10


A. Write an HTML form for the CGI adder function in Figure 11.27. Your form
should include two text boxes that users fill in with the two numbers to be
added together. Your form should request content using the GET method.

그림 11.27.에 있는 CGI adder 함수를 위한 HTML form을 작성해라. form은 더해져야 할 2개의 숫자를 사용자가 채우면 된다. form은 GET method로 요청한다.

B. 는 만든거 실제 브라우저로 테스트해보라는거.

우선 adder.c를 만들어줘야 한다.

1. Annotating : adder.c

1.1. troubleshooting : Why did the buffer initialize?

sprintf(content, "QUERY_STRING=%s", buf);
sprintf(content, "Welcome to add.com: ");
sprintf(content, "%sTHE Internet addition portal.\r\n<p>", content);
sprintf(content, "%s The answer is: %d + %d = %d\r\n<p>", content, n1, n2, n1+n2);
sprintf(content, "%sThanks for visiting!\r\n", content);

앞에 문자열들 다 무시하고 "Thanks for visiting!\r\n" 만 출력되는 현상 발생.
%s를 통해 계속 이전 문자열을 갖고 가니까 초기화가 되면 안되는데,

도저히 모르겠어서 gpt한테 물어봤더니

sprintf(content, "QUERY_STRING=%s", buf); // 첫 번째 문자열 작성
sprintf(content + strlen(content), "Welcome to add.com: "); // 이어서 문자열 추가
sprintf(content + strlen(content), "THE Internet addition portal.\r\n<p>"); // 추가
sprintf(content + strlen(content), "The answer is: %d + %d = %d\r\n<p>", n1, n2, n1+n2); // 더한 결과 추가
sprintf(content + strlen(content), "Thanks for visiting!\r\n"); // 마지막 메시지 추가

이 코드가 맞다면서 던져줌.
이전게 왜 틀렸는지는 제대로 설명 못해줌.

테스트 하면 되기야 됨.

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\r\n", buf, filetype);

분명 tiny.c에서 이 부분 잘 작동했는데 왜 adder.c에서는 안되는건지 모르겠음.

마지막 것만 주석처리하면 역시 맨 마지막만 출력됨.

일단 작동하니까 넘어가면 될듯. 이거 말고 할 거 많음.

✅ 완성 코드

#include "csapp.h"

int main(void) {
  char *buf, *p;
  char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
  int n1 = 0, n2 = 0;

  /* 두 인수를 추출한다 */
  if ((buf = getenv("QUERY_STRING")) != NULL)
  {
    p = strchr(buf, '&'); // &가 있는 위치를 찾는다
    *p = '\0'; // 널 문자(문자열의 끝)를 & 자리에 넣어준다
    strcpy(arg1, buf); // buf의 첫 문자열을 arg1에 넣는다
    strcpy(arg2, p + 1); // buf의 두 번째 문자열을 arg2에 넣는다
    n1 = atoi(arg1); // arg1에 있는 걸 숫자로 바꾼다
    n2 = atoi(arg2); // arg2도 바꾼다
  }

  /* 응답 body 만들기 */
  sprintf(content, "QUERY_STRING=%s", buf); 
  sprintf(content + strlen(content), "Welcome to add.com: ");
  sprintf(content + strlen(content), "THE Internet addition portal.\r\n<p>"); 
  sprintf(content + strlen(content), "The answer is: %d + %d = %d\r\n<p>", n1, n2, n1+n2); 
  sprintf(content + strlen(content), "Thanks for visiting!\r\n"); 

  /* HTTP 응답을 만든다 */
  printf("Connection: close\r\n");
  printf("Content-length: %d\r\n", (int)strlen(content));
  printf("Content-type: text/html\r\n\r\n");
  printf("%s", content);
  fflush(stdout);

  exit(0);
}

주석 달기 완료

2. Implementing : HTML form

<form action="./cgi-bin/adder">
    <input type="number" name="x" min="2" max="10" step="2" value="2">
    +
    <input type="number" name="y" min="2" max="10" step="2" value="2">
    <input type="submit" value="Submit">
</form>

name이 필연적으로 추가돼야 하길래 adder.c 구현할 때 귀찮았다.

3. Implementing : adder.c

/* 두 인수를 추출한다 */
if ((buf = getenv("QUERY_STRING")) != NULL)
{
  p1 = strchr(buf, '&'); // &가 있는 위치를 찾는다
  *p1 = '\0'; // 널 문자(문자열의 끝)를 & 자리에 넣어준다
  strcpy(arg1, buf); // buf의 첫 문자열을 arg1에 넣는다
  p2 = strchr(arg1, '='); // 첫번째 문자열에서 = 찾기
  strcpy(arg1, p2 + 1); // = 뒤에 있는 첫 번째 인수 추출
  strcpy(arg2, p1 + 3); // buf의 두 번째 문자열을 arg2에 넣는다
  n1 = atoi(arg1); // arg1에 있는 걸 숫자로 바꾼다
  n2 = atoi(arg2); // arg2도 바꾼다
}

주소 해석 기능 구현 완료.
form 태그의 name이 필수라서 어쩔 수 없이 지저분해졌다.

가독성 꽝, 확장성 꽝인데 일단 됐으니 넘어가자.
실무였으면 절대 못 넘어갈 쓰레기 코드.

✅ 완성

입력하고 submit 누르면

더한 값을 서버가 보내준다.

📌 11.11


Extend Tiny to support the HTTP HEAD method. Check your work using telnet as a Web client.

Tiny 서버가 HTTP GET 요청 말고도 HEAD 요청도 받도록 바꿔봐라. telnet으로 되는지 체크해라.

HEAD 요청은 header만 달라는 요청이다.

1. troubleshooting : WHAT THE XXXX IS HAPPENING?

숙제 구현을 위해 코드를 적고 테스트를 해보니 갑자기 접속이 안됨.
adder 링크로 들어가면 잘 됨.
잘 되던 마지막 버전을 commit 해놨어야 했는데 안 해놓은게 후회가 됨.

tiny 구현한 다른 사람 코드 받아와서 해봄. 안됨.
git에서 잘 됐던 버전 불러와서 해봄. 안됨.
함수 하나하나 이전에 했던걸로 바꿔봄. 안됨.

그나마 실마리를 잡을 수 있는 건 Firefox에서 뜨는 결과.
이 데이터를 받았는데 왜??? 갑자기??? 코드가??? 다??? 똑같은데???
페이지를 못 불러오는지 모르겠음.

gpt 말대로 브라우저 F12 - Network를 확인했더니 응답 헤더가 안 옴.
안 왔다는 걸 gpt에게 다시 전달했더니

sprintf(buf, "HTTP/1.0 200 OK\r\n");
sprintf(buf + strlen(buf), "Server: Tiny Web Server\r\n");
sprintf(buf + strlen(buf), "Connection: close\r\n");
sprintf(buf + strlen(buf), "Content-length: %d\r\n", filesize);
sprintf(buf + strlen(buf), "Content-type: %s\r\n\r\n", filetype); // 두 개의 개행이 중요합니다
rio_writen(fd, buf, strlen(buf)); // 클라이언트로 응답 헤더 전송

여기 한 번 바꿔보세요? 라고 함. 바꿔 봄. 잘 됨.

그니까 아까는 멀쩡히 잘되던 로직이 갑자기 안돼서 그랬던 거임.

분명히 adder.c에서 문제가 됐던게
tiny.c에서는 문제가 안 되길래 이상했는데,
멀쩡히 잘 굴리다가 갑자기 안돼버림.

버퍼 관련 문제라고 추측은 되긴 함.
근데 껐다 켜도 안되던 건 뭔지 모르겠음.

✅ 완성 코드

/* 헤더 전송 */
void serve_header(int fd, char *filename, int filesize)
{
  int srcfd; // 파일 식별자 저장
  char *srcp, filetype[MAXLINE], buf[MAXBUF];

  /* 응답 헤더를 클라에게 보낸다 */
  get_filetype(filename, filetype);
  sprintf(buf, "HTTP/1.0 200 OK\r\n");
  sprintf(buf + strlen(buf), "Server: Tiny Web Server\r\n");
  sprintf(buf + strlen(buf), "Connection: close\r\n");
  sprintf(buf + strlen(buf), "Content-length: %d\r\n", filesize);
  sprintf(buf + strlen(buf), "Content-type: %s\r\n\r\n", filetype); // 두 개의 개행이 중요합니다
  rio_writen(fd, buf, strlen(buf)); // 클라이언트로 응답 헤더 전송

  printf("Response headers:\n"); // 디버깅을 위해 헤더 출력
  printf("%s", buf); 
}

serve_header 함수 만든 뒤 전방 선언 하고

if (!strcasecmp(method, "HEAD")) // HEAD 요청이라면
{
  if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))
  {
    clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file");
    return;
  }
  serve_header(fd, filename, sbuf.st_size);
  return;
}

doit에 추가한다.

if (strcasecmp(method, "GET") && strcasecmp(method, "HEAD")) // GET 요청 혹은 HEAD 요청인지 확인
{
  clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method"); // 아니면 꺼지라고 함
  return;
}

GET 요청 말고도 HEAD 요청이 될 수 있다는 것도 추가.

/* $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_header(int fd, char *filename, int filesize);
void serve_static(int fd, char *filename, int filesize);
void get_filetype(char *filename, char *filetype);
void serve_dynamic(int fd, char *filename, char *cgiargs);
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg);

/* 소켓 정보 가져오게 하고 할 일 하게하는 main 함수 */
int main(int argc, char **argv)
{
  int listenfd, connfd;                  // 듣기 식별자, 연결 식별자 (서버 소켓과 클라이언트 소켓)
  char hostname[MAXLINE], port[MAXLINE]; // 클라 호스트이름, 포트 (혹은 아이피 주소, 서비스 이름)
  socklen_t clientlen;                   // 클라 주소 크기 (sockaddr 구조체 크기)
  struct sockaddr_storage clientaddr;    // 클라 주소 정보 구조체 (IPv4/IPv6 호환)

  /* 인자 맞게 썼는지 확인 */
  if (argc != 2)
  {
    fprintf(stderr, "usage: %s <port>\n", argv[0]);
    exit(1);
  }

  listenfd = Open_listenfd(argv[1]); // 연결 대기 시작
  while (1)
  {
    clientlen = sizeof(clientaddr);
    connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);                       // line:netp:tiny:accept // 연결 수립
    Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE, port, MAXLINE, 0); // 소켓에서 클라 호스트 이름과 포트 번호 가져오기
    printf("Accepted connection from (%s, %s)\n", hostname, port);
    doit(connfd);  // line:netp:tiny:doit // 연결 수립 후 서버에서 할 일
    Close(connfd); // line:netp:tiny:close // 할 일 다 하면 연결 끝
  }
}

/* 오류 있는지, 컨텐츠가 정적인지 동적인지 확인하고 읽기/실행 */
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); // 버퍼에서 데이터 읽고 method, uri, version에 저장
  if (strcasecmp(method, "GET") && strcasecmp(method, "HEAD")) // GET 요청 혹은 HEAD 요청인지 확인
  {
    clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method"); // 아니면 꺼지라고 함
    return;
  }
  read_requesthdrs(&rio);

  /* URI에서 데이터 추출  */
  is_static = parse_uri(uri, filename, cgiargs); // URI에서 데이터 추출
  if (stat(filename, &sbuf) < 0)                 // 파일 읽어서 버퍼에 넣기
  {
    clienterror(fd, filename, "404", "Not found", "Tiny couldn't find this file"); // 오류 나면 끝내기
    return;
  }

  if (!strcasecmp(method, "HEAD")) // HEAD 요청이라면
  {
    if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))
    {
      clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file");
      return;
    }
    serve_header(fd, filename, sbuf.st_size);
    return;
  }

  if (is_static) // 정적 컨텐츠였다면
  {
    /* 일반 파일인지, 실행 권한이 있는지 확인 */
    // S_ISREG : st_mode 값이 일반 파일(regular file)인지 확인하는 매크로
    // sbuf에는 파일의 메타데이터가 들어가 있음. 파일의 권한, 파일 유형에 대한 정보를 비트 플래그로 저장함.
    // S_IRUSR : 사용자가 파일을 읽을 수 있는 권한을 확인하기 위한 상수
    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);
  }
  else // 동적 컨텐츠였다면
  {
    /* 일반 파일인지, 실행 권한이 있는지 확인 */
    if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))
    {
      clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run the CGI program");
      return;
    }
    serve_dynamic(fd, filename, cgiargs);
  }
}

/* 에러 메시지 띄우는 함수 */
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)
{
  char buf[MAXLINE], body[MAXBUF];

  /* HTTP 응답 중 body 만들기 */
  // body를 다시 추가해서 안 넣어주면 마지막 빼고 다 사라져버림.
  // sprintf는 기존에 있던 데이터에 추가되는 방식이 아니라 덮어씌워버리기 때문.
  // 응답을 문자열 하나로 만들어서 크기를 쉽게 알 수 있게 함.
  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 응답 출력 */
  // 응답 시작 라인, 응답 헤더 뿌려준 후에 응답 body 보내줌
  sprintf(buf, "HTTP/1.0 %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));
}

/* 요청 헤더를 읽는 함수 */
void read_requesthdrs(rio_t *rp)
{
  // tiny 서버는 요청 헤더를 실제로 사용하진 않고, 그냥 읽기만 함.
  // rp는 클라한테 받은 데이터들 들어있는 버퍼임.

  char buf[MAXLINE];

  rio_readlineb(rp, buf, MAXLINE); // rp에 있는걸 한 줄 읽어서 buf에 넣음
  while(strcmp(buf, "\r\n")) // 버퍼에 빈줄이 나올 때까지 반복
  {
    rio_readlineb(rp, buf, MAXLINE); // rp를 한 줄 읽어서 buf에 넣음
    printf("%s", buf);
  }
  return;
}

/* uri에 담긴 정보 추출 */
int parse_uri(char *uri, char *filename, char *cgiargs)
{
  char *ptr;

  if (!strstr(uri, "cgi-bin")) // 정적 컨텐츠를 요청했다면
  {
    strcpy(cgiargs, ""); // 초기화
    strcpy(filename, "."); // .으로 초기화
    strcat(filename, uri); // uri에서 요청하는 디렉토리로 감
    if (uri[strlen(uri)-1] == '/') // uri 마지막 문자가 / 이면
      strcat(filename, "home.html"); // home.html 꺼낸다
    return 1;
  }
  else // 동적 컨텐츠를 요청했다면
  {
    ptr = index(uri, '?'); // ? 뒤에는 프로그램 매개변수들이 나옴
    if (ptr) // 매개변수가 있으면
    {
      strcpy(cgiargs, ptr + 1); // cgi 매개변수 담는 곳에 넣어준다
      *ptr = '\0'; // 넣어주고 난 후엔 매개변수 정보들 삭제함
    }
    else // 매개변수가 없으면
      strcpy(cgiargs, ""); // 매개변수 없다고 초기화시키기
    strcpy(filename, "."); // filename에 . 넣고
    strcat(filename, uri); // .uri 만들기
    return 0;
  }
}

/* 헤더 전송 */
void serve_header(int fd, char *filename, int filesize)
{
  int srcfd; // 파일 식별자 저장
  char *srcp, filetype[MAXLINE], buf[MAXBUF];

  /* 응답 헤더를 클라에게 보낸다 */
  get_filetype(filename, filetype);
  sprintf(buf, "HTTP/1.0 200 OK\r\n");
  sprintf(buf + strlen(buf), "Server: Tiny Web Server\r\n");
  sprintf(buf + strlen(buf), "Connection: close\r\n");
  sprintf(buf + strlen(buf), "Content-length: %d\r\n", filesize);
  sprintf(buf + strlen(buf), "Content-type: %s\r\n\r\n", filetype); // 두 개의 개행이 중요합니다
  rio_writen(fd, buf, strlen(buf)); // 클라이언트로 응답 헤더 전송

  printf("Response headers:\n"); // 디버깅을 위해 헤더 출력
  printf("%s", buf); 
}

/* 정적 컨텐츠 전송 */
void serve_static(int fd, char *filename, int filesize)
{
  int srcfd; // 파일 식별자 저장
  char *srcp, filetype[MAXLINE], buf[MAXBUF];

  /* 응답 헤더를 클라에게 보낸다 */
  get_filetype(filename, filetype);
  sprintf(buf, "HTTP/1.0 200 OK\r\n");
  sprintf(buf + strlen(buf), "Server: Tiny Web Server\r\n");
  sprintf(buf + strlen(buf), "Connection: close\r\n");
  sprintf(buf + strlen(buf), "Content-length: %d\r\n", filesize);
  sprintf(buf + strlen(buf), "Content-type: %s\r\n\r\n", filetype); // 두 개의 개행이 중요합니다
  rio_writen(fd, buf, strlen(buf)); // 클라이언트로 응답 헤더 전송

  printf("Response headers:\n"); // 디버깅을 위해 헤더 출력
  printf("%s", buf); 

  /* 응답 body를 클라에 보낸다 */
  srcfd = open(filename, O_RDONLY, 0); // 파일을 읽기 전용 ('O_RDONLY')로 연다
  srcp = mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
  // 파일 내용을 메모리에 매핑, srcp는 매핑된 메모리의 시작 주소
  // PROT_READ: 매핑된 메모리 영역을 읽기 전용으로 설정
  // MAP_PRIVATE: 메모리의 변경 사항이 파일에 반영되지 않고, 다른 프로세스와 공유되지 않음
  // srcp = malloc(filesize);
  // rio_readn(srcfd, srcp, filesize);  
  close(srcfd); // 파일 식별자 닫기
  rio_writen(fd, srcp, filesize); // 클라에게 파일 데이터 전송
  munmap(srcp, filesize); // 할당했던 메모리 반환
}

/* 해당 파일의 MIME 타입 반환 */
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(filename, ".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 buf[MAXLINE], *emptylist[] = {NULL};

  /* HTTP 응답의 첫 부분 클라에 전송 */
  sprintf(buf, "HTTP/1.0 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) // 자식 프로세스라면 0을 반환함
  {
    setenv("QUERY_STRING", cgiargs, 1); // URL에서 전송된 쿼리 문자열을 QUERY_STRING에 설정
    dup2(fd, STDOUT_FILENO); // 자식이 출력하는거 전부 클라로 감 (클라 소켓 식별자를 표준 출력에 복사)
    execve(filename, emptylist, environ); // CGI 프로그램 실행
  }
  wait(NULL); // 자식이 꺼질 때 까지 부모가 기다려줌
}

tiny.c 완성 코드

HEAD 요청을 보내면 header만 보내준다.

0개의 댓글