본문 바로가기
C언어/Tiny 웹 서버

Tiny 구현

by lacuca9 2024. 9. 18.

main 함수

웹 서버가 클라이언트의 요청을 처리하기 위한 전형적인 서버 루프를 구현

// argv: 문자열 포인터의 배열
int main(int argc, char **argv) {
  // listenfd: 리스팅 소켓 파일 드스크립터, connfd: 클라와 연결을 위한 소켓 파일 디스크립터
  int listenfd, connfd;
  // 호스트 및 포트 저장할 버퍼
  char hostname[MAXLINE], port[MAXLINE];
  // 클라 주소 길이 저장 변수
  socklen_t clientlen;
  // 클라 주소 저장할 구조체
  struct sockaddr_storage clientaddr;

  /* 명령줄 인수 확인 (하나의 인수만 요구 (제목 포함 2))*/
  if (argc != 2) {
    fprintf(stderr, "usage: %s <port>\n", argv[0]); // argv[0]은 실행된 프로그램의 이름
    exit(1);
  }

  // 리스닝 소켓 생성 
  listenfd = Open_listenfd(argv[1]);
  // 무한루프 안에서 클라 연결을 계속 수락, clientaddr 에는 클라의 주소 정보가 저장
  while (1) {
    clientlen = sizeof(clientaddr);
    connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);

    // 클라 정보 출력: clientaddr에서 호스트, 포트 추출해서 hostname, port에 저장
    Getnameinfo((SA *)&clientaddr, clientlen, hostname, MAXLINE,
    port, MAXLINE, 0);
    printf("Accepted connection from (%s, %s)\n", hostname, port);
    doit(connfd);   // 클라이언트 요청 처리
    Close(connfd);  // 클라이언트 연결 종료
  }
}

 

1. 변수 선언 및 초기화

  • listenfd : 리스닝 소켓의 파일 디스크립터저장합니다. (연결 요청 수신 소켓)
  • connfd : 클라이언트와 연결된 소켓의 파일 디스크립터를 저장합니다.
  • hostnameport : 클라이언트 호스트명과 포트 번호를 저장할 버퍼입니다.
  • clientlen : 클라이언트 주소의 길이를 저장합니다.
  • clientaddr : 클라이언트 주소를 저장할 구조체입니다.

2. 명령줄 인수 확인

  • 프로그램이 실행될 때 포트 번호가 인수로 제공되었는지 확인.
    그렇지 않으면 오류 메시지 출력하고 종료

3. 리스닝 소켓 열기

  • Open_listenfd 함수를 호출하여 지정된 포트에서 클라이언트 연결 요청을 수신하기 위해
    리스닝 소켓을 연다.

4. 무한 루프

  • 서버는 무한 루프를 돌면서 클라이언트의 연결 요청을 기다립니다.
  • Accept 함수를 호출하여 클라이언트의 연결 요청을 수락하고, 클라이언트의 주소 정보를 clientaddr에 저장합니다.
  • Getnameinfo 함수를 사용하여 클라이언트의 호스트명포트 번호가져옵니다.
  • printf를 사용하여 클라이언트의 연결 정보를 로그로 출력합니다.
  • doit 함수를 호출하여 클라이언트의 요청을 처리합니다.
  • 처리 후 Close 함수를 호출하여 연결을 종료합니다.

Tiny반복적인 서버로, 클라이언트의 요청을 기다리고 처리하는 무한 루프를 실행.

이 방식은 서버가 동시에 여러 클라이언트를 처리할 수 없지만, 단일 클라이언트의 요청을 순차적으로 처리.

 

# 추가

 

1. 파일 디스크립터(File Descriptor)

- 운영 체제에서 파일, 소켓, 파이프 등과 같은 입출력 리소스에 대한 식별자. (파일 관리를 위해)

  해당 리소스에 접근하거나 조작할 때 사용.

  • 파일 : 디스크에 저장된 파일
  • 소켓 : 네트워크 통신을 위한 소켓
  • 파이프 : 프로세스통신을 위한 파이프

운영 체제의 커널이 관리하며, 프로그램이 파일을 열 때 또는 소켓을 생성할 때

디스크립터를 반환받는다

 

2. 소켓 파일 디스크립터

- 네트워크 소켓을 식별하는 데 사용되는 파일 디스크립터.

  클라이언트와 서버 간의 데이터 전송을 위한 인터페이스를 제공.

  소켓생성하고, 연결을 수립하고, 데이터를 송수신하는 작업을 수행할 때 파일 디스크립터가 사용됨.


doit 함수

웹 서버가 클라이언트의 요청을 처리하는 함수.

클라이언트의 HTTP 요청을 분석하고, 정적/동적 판단하고 응답

// 서버에서 클라이언트의 HTTP 요청을 처리 역할.
// 요청 분석하고, 정적 파일을 제공하거나 CGI 프로그램 실행하여 동적 컨텐츠 생성
void doit(int fd)
{
  printf("doit함수호출\n");
  int is_static;          // 동적 정적?
  struct stat sbuf;       // 파일의 상태 정보를 담는 stat 구조체

  // buf: HTTP 요청 라인을 저장하는 버퍼 
  // method, uri, version: 요청의 메소드(GET, POST등),URI, HTTP 버전을 저장하는 버퍼
  char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
  // 요청된 파일 이름과 CGI 인수 저장 버퍼
  char filename[MAXLINE], cgiargs[MAXLINE];
  rio_t rio;              // 리드 연산을 위한 구조체

  Rio_readinitb(&rio,fd);             // rio 구조체 초기화
  Rio_readlineb(&rio, buf, MAXLINE); // 요청 라인 읽고 buf에 저장
  printf("요청라인:%s", buf);
  sscanf(buf, "%s %s %s", method, uri, version);  // 메소드, URI, HTTP 버전 추출

  // 요청 메소드가 GET이 아니면 오류 반환하고 종료
  if (strcasecmp(method, "GET")) {
    clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
    return;
  }

  // 요청 헤더 읽기:
  printf("Request headers:\n");
  read_requesthdrs(&rio); 

  // parse_uri 호출해서 URI분석 후, 파일이름, CGI 인수 추출
  // is_static을 설정하여 정적인지 동적인지 반환
  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); //정적 컨텐츠 제공
  }
  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);  //동적 컨텐츠 제공
  }
}

 

1. 함수 선언 및 변수 초기화

  • int is_static; : 이 변수는 요청된 자원이 정적(static)인지 동적(dynamic)인지 식별하는 데 사용됩니다.
  • struct stat sbuf; : 파일의 상태 정보를 저장하는 구조체로, 파일의 존재 여부 및 권한 등을 확인하는 데 사용됩니다.
  • char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE]; : HTTP 요청의 각 요소저장
  • char filename[MAXLINE], cgiargs[MAXLINE]; : 요청된 파일 이름CGI 인수저장
    buf - 요청 라인이나 헤더 라인을 읽음
    method - HTTP 요청 메소드(GET, POST)를 저장하는 버퍼
    uri - HTTP 요청에서 요청된 자원의 URI를 저장하는 버퍼 (/index.html)
    version - HTTP 요청의 버전을 저장 (HTTP/1.1)
    filename - URI를 분석하여 요청된 파일의 경로르 변수에 저장
    cgiargs - CGI 스크립트에 전달할 인수를 저장. URI에 포함된 쿼리문자열을 저장
  • rio_t rio; : 파일 디스크립터와 관련된 I/O 작업을 수행하는 데 사용되는 구조체입니다.

2. rio 구조체 초기화

  • Rio_readinitb : rio 구조체를 초기화하여 fd(파일 디스크립터)와 연관

3. HTTP 요청 라인 읽기

  • Rio_readlineb : 클라이언트로부터 HTTP 요청의 첫 번째 라인을 읽어 buf에 저장.
    보통 GET /index.html HTTP/1.1

4. 요청 라인 분석

  • sscanf : 요청 라인에서 메소드(GET), URI(/index.html), HTTP 버전(HTTP/1.1) 등을 추출하여 각 변수에 저장합니다.

5. 메소드 검증

  • 메소드가 "GET"이 아닌 경우, clienterror 함수를 호출하여 "501 Not Implemented" 오류를 반환

6. 요청 헤더 읽기

  • read_requesthdrs : 요청 헤더를 읽어 출력. 헤더는 여러 줄로 이루어져 있으며,
    빈 줄이 나타날 때까지 계속 읽음

7. URI 분석 및 파일 이름 및 CGI 인수 추출

  • parse_uri : URI를 분석하여 파일 이름과 CGI 인수를 추출합니다.
  • is_static : 변수는 요청된 자원이 정적인지 동적인지를 반환합니다.

8. 파일 존재 여부 확인

  • stat : filename에 해당하는 파일의 상태 정보sbuf에 저장합니다.
  • 파일이 존재하지 않으면, clienterror 함수를 호출하여 "404 Not Found" 오류를 반환합니다.

9. 정적(static) 또는 동적(dynamic) 콘텐츠 제공

  • is_static이 참이면 serve_static 함수를 호출하여 정적 콘텐츠를 제공합니다.
  • is_static이 거짓이면 serve_dynamic 함수를 호출하여 동적 콘텐츠를 제공합니다.

10. 정적 파일 처리 (is_static이 true 일 경우)

- 정적 파일의 조건 확인

  • S_ISREG(sbuf.st_mode): 파일이 일반 파일인지 확인. sbuf.st_mode는 stat 함수로 얻은 파일의
    상태 정보를 담고 있으며, 파일이 일반 파일인지 여부를 판단합니다.
  • S_IRUSR & sbuf.st_mode: 파일의 읽기 권한을 확인. S_IRUSR는 파일 소유자에게 읽기 권한이 있는지를 체크
    참이면, 파일을 읽을 권한이 있는 것

- 파일 읽기 권한이 없는 경우

  • 위의 조건이 만족하지 않으면 clienterror 함수를 호출

- 정적 파일 제공

  • 파일이 정상적으로 읽을 수 있는 경우, serve_static 함수를 호출하여 정적 파일을 클라이언트에게 제공합니다. filename은 요청된 파일의 경로, sbuf.st_size는 파일의 크기입니다.

11. 동적 파일 처리 (is_static이 false일 경우)

- 동적 파일의 조건 확인

  • S_ISREG(sbuf.st_mode): 파일이 일반 파일인지 확인
  • S_IXUSR & sbuf.st_mode: 파일이 실행 가능한지 확인합니다. S_IXUSR는 파일 소유자에게 실행 권한이 있는지를 체크하는 매크로

- 파일이 실행 불가능한 경우

  • 위의 조건이 만족하지 않으면 clienterror 함수를 호출

- 동적 파일 제공

  • 파일이 실행 가능하면, serve_dynamic 함수를 호출하여 동적 컨텐츠를 클라이언트에게 제공.
    filename은 요청된 CGI 스크립트의 경로, cgiargsCGI 프로그램에 전달할 인수입니다.

#추가

strcasecmp : 대소문자 구분 않고 문자열 비교해서 같으면 0반환

uri(Uniform Resource Identifier) : 인터넷 자원을 식별하기 위한 문자열.
웹 자원의 위치나 그 자원을 참조하는 방법을 명시

버퍼 : 데이터를 일시적으로 저장하는 메모리 공간
 - 속도 차이 보완, 데이터 조각화, I/O 작업의 효율성 향상

cgi(Common Gateway Interface) : 웹 서버와 외부 프로그램(스크립트) 간의 인터페이스를 정의하는 표준.

동적 컨텐츠를 생성하기 위해 CGI 스크립트를 실행


clienterror 함수

HTTP 오류 응답을 클라이언트에 전송하는 역할을 수행

// HTTP 에러 응답을 클라이언트에 전송하는 역할
void clienterror(int fd, char *cause, char *errnum,char *shortmsg, char *longmsg)
{
  // buf: HTTP 헤더와 관련 정보 저장 버퍼, body: HTML 에러 메시지 포함할 버퍼
  char buf[MAXLINE] , body[MAXBUF];

  /* Build the HTTP response 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);

  /* Print the HTTP response */
  // 상태 줄을 작성 (ex. HTTP/1.0 404 Not Found)
  sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
  // 상태 줄을 클라이언트에 전송
  Rio_writen(fd, buf, strlen(buf));

  // 응답 본문이 HTML 형식임을 나타내는 Content-type: text/html 헤더를 전송
  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));

}

 

함수 정의

  • fd: 클라이언트와의 연결을 나타내는 파일 디스크립터. 이 디스크립터를 통해 클라이언트와 통신합니다.
  • cause: 오류의 원인으로, 오류가 발생한 자원이나 요청을 설명하는 문자열입니다.
  • errnum: HTTP 오류 상태 코드를 나타내는 문자열입니다 (예: "404").
  • shortmsg: 오류 상태 코드에 대한 간단한 메시지입니다 (예: "Not Found").
  • longmsg: 오류에 대한 자세한 설명입니다 (예: "Tiny couldn’t find this file")

1. HTTP 응답 본문 구성

- body: 오류 응답의 HTML 본문을 구성하는 버퍼.
  sprintf 함수는 body에 문자열을 추가하여 오류 메시지와 HTML 태그를 만듦

  • 첫 번째 줄은 HTML 문서의 제목을 설정
  • 두 번째 줄은 HTML 문서의 배경색을 설정
  • 세 번째 줄은 오류 상태 코드와 간단한 메시지를 포함
  • 네 번째 줄은 오류에 대한 자세한 설명을 포함
  • 마지막 줄은 페이지 하단에 서버 이름을 추가

2. HTTP 응답 상태 줄 전송

  • buf: HTTP 응답의 상태 줄을 저장하는 버퍼
    sprintf를 사용하여 상태 줄을 형성. ex) "HTTP/1.0 404 Not Found".
  • Rio_writen(fd, buf, strlen(buf)): 상태 줄을 클라이언트에 전송합니다.

3. Content-type 헤더 전송

  • Content-type: text/html: 응답 본문이 HTML 형식임을 나타내는 헤더.
    클라이언트는 이 헤더를 보고 본문을 어떻게 처리할지 알 수 있습니다.

4. Content-length 헤더 전송

  • Content-length: 응답 본문의 길이를 바이트 단위로 지정하는 헤더.
    클라이언트는 이 값을 통해 응답 본문의 전체 길이를 파악할 수 있습니다.
  • \r\n\r\n: 헤더와 본문을 구분하는 빈 줄입니다.

5. 응답 본문 전송

  • body: 앞에서 구성한 HTML 본문을 클라이언트에 전송합니다.

# 추가

1. sprintf 함수 : 문자열을 포맷하여 특정 형식의 문자열을 생성하는데 사용되는 함수


read_requesthdrs 함수

HTTP 요청의 헤더를 읽고 출력하는 역할

// HTTP 요청의 헤더를 읽고 출력하는 역할
void read_requesthdrs(rio_t *rp)  // 요청을 읽기 위한 rio 구조체의 포인터
{
  printf("read_requesthdrs함수호출\n");
  char buf[MAXLINE];    // HTTP 요청 헤더를 읽기 위한 버퍼

  // 첫 번째 요청 헤더 읽기:
  // 요청 헤더의 첫 번째 줄을 buf에 읽어온다. rp는 rio_t 구조체의 포인터로 요청 데이터 읽는데 씀
  Rio_readlineb(rp, buf, MAXLINE);

  // buf와 빈 줄이 아닐 경우, 계쏙 다음 헤더 라인을 읽음
  while(strcmp(buf, "\r\n")) {   //buf 와 \r\n이 같으면 0.
    // 요청 헤더의 다음 줄을 읽고 출력
    Rio_readlineb(rp, buf, MAXLINE);
    printf("%s", buf);
  }
  return;
}

 

1. 헤더 읽기 준비

  • char buf[MAXLINE] : HTTP 요청 헤더를 저장하기 위한 버퍼
     MAXLINE은 이 버퍼의 크기로, HTTP 요청 헤더 한 줄의 최대 길이를 정의

2. 첫 번째 요청 헤더 읽기

  • Rio_readlineb(rp, buf, MAXLINE)를 호출하여 요청 헤더의 첫 번째 줄을 읽어 buf에 저장
    rp는 rio_t 구조체의 포인터로, 이 구조체는 리드 연산을 수행하는 데 필요한 정보를 담고 있습니다.

3. 헤더 출력

  • while(strcmp(buf, "\r\n"))는 읽어온 줄이 빈 줄이 아닌 동안 반복
    HTTP 요청 헤더는 각 헤더가 한 줄로 되어 있고, 헤더의 끝은 빈 줄("\r\n")로 표시
  • strcmp(buf, "\r\n")는 buf의 내용과 빈 줄을 비교하여, 같으면 0을 반환
    이 비교 결과가 0이 아니면, 빈 줄이 아니므로 반복문이 계속

4. 다음 헤더 읽기

  • 빈 줄이 아닐 경우, Rio_readlineb(rp, buf, MAXLINE)를 다시 호출하여 다음 헤더 라인을 읽음
  • printf("%s", buf)로 읽어온 헤더 라인을 출력합니다.

5. 함수 종료

  • 헤더의 끝인 빈 줄을 만날 때까지 반복문이 실행되며, 모든 요청 헤더를 읽고 출력한 후 함수가 종료

parse_uri 함수

주어진 URI를 분석하여 파일 경로아 CGI 인자를 추출하는 역할을 함

// URI 분석하여 파일 경로와 CGI인자들을 추출
int parse_uri(char *uri, char *filename, char *cgiargs)
{
  printf("parse_uri함수호출\n");
  char *ptr;    // 문자열 검색 결과를 저장할 포인터

  if (!strstr(uri, "cgi-bin")) { //uri가 cgi-bin을 포함하고 있지 않으면 static content
    strcpy(cgiargs, "");
    strcpy(filename, ".");
    strcat(filename, uri);
    if (uri[strlen(uri)-1] == '/')    // URI가 '/' 로 끝나면
      strcat(filename, "home.html");  // finename에 "home.html"을 추가하여 기본 파일을 지정
    return 1;
  }
  else {  /* Dynamic content */
    ptr = index(uri,'?');     // URI에 쿼리 문자열이 있는지 확인후 있으면 prt은 ? 의 위치 가리킴
    if (ptr) {
      strcpy(cgiargs, ptr+1); 
      *ptr = '\0';  // 문자열의 끝으로 바꾸어 쿼리 문자열을 URI에서 제거
                    // 이로 인해 filename이 CGI 프로그램의 경로를 정확하게 가리키게 됨
    }
    else                  // 쿼리 문자열이 없으면 
      strcpy(cgiargs,""); // cgiargs를 빈 문자열로 설정
    strcpy(filename,"."); 
    strcat(filename,uri);
    return 0;
  }
}

 

함수 설명

  1. 입력 파라미터 :
    • char *uri: 분석할 URI 문자열.
    • char *filename: 결과로서 파일 경로를 저장할 버퍼.
    • char *cgiargs: CGI 인자를 저장할 버퍼
  2. URI 분석 :
    • if (!strstr(uri, "cgi-bin")): URI에 "cgi-bin"이 포함되어 있지 않으면 정적 콘텐츠로 간주
      • strcpy(cgiargs, "");: CGI 인자는 없으므로 빈 문자열로 설정.
      • strcpy(filename, ".");: 현재 디렉토리(".")를 기본으로 설정.
      • strcat(filename, uri);: URI를 filename에 추가
      • if (uri[strlen(uri)-1] == '/'): URI가 '/'로 끝나면, 기본 파일인 home.html을 filename에 추가
      • return 1;: 정적 콘텐츠를 반환하는 신호로 1을 반환
  3. 동적 컨텐츠 처리 :
    • else: URI에 "cgi-bin"이 포함된 경우 동적 콘텐츠로 간주합니다.
      • ptr = index(uri, '?');: URI에서 쿼리 문자열의 시작 위치('?')를 찾습니다.
      • if (ptr): 쿼리 문자열이 있으면:
        • strcpy(cgiargs, ptr + 1);: 쿼리 문자열을 cgiargs에 복사합니다.
        • *ptr = '\0';: ptr 위치의 문자를 '\0'으로 바꾸어 URI에서 쿼리 문자열을 제거합니다.
      • else: 쿼리 문자열이 없으면:
        • strcpy(cgiargs, "");: CGI 인자를 빈 문자열로 설정.
      • strcpy(filename, ".");: 현재 디렉토리(".")로 초기화.
      • strcat(filename, uri);: URI를 filename에 추가합니다.
      • return 0;: 동적 콘텐츠를 반환하는 신호로 0을 반환합니다.

# 추가

쿼리 문자열이 없는 동적 컨텐츠 요청은 특정한 동작을 하지 않을 수 있지만,
여전히 서버는 해당 URI를 처리하기 위해 프로그램을 실행한다

 

cgiargs의 역할

- 클라이언트가 웹 폼을 통해 제출한 데이터나 URL 쿼리 문자열에서 가져온 정보를 저장
- URI가 /cgi-bin/script.cgi?param1=value1&param2=value2 인 경우,
  cgiargs에는 "param1=value1&param2=value2 "가 저장

 

strstr() : 문자열 안에서 문자열로 검색

 

 

strcpy() : 문자열 복사

 

 

strcat() : 문자열을 서로 붙일 수 있다

 


serve_static 함수

클라이언트에 정적 컨텐츠(HTML 파일, 이미지 파일 등) 제공하는 역할

// 정적 컨텐츠를 클라에게 제공
void serve_static(int fd, char *filename, int filesize)
{
  printf("serve_static함수호출\n");
  int srcfd;  // 파일의 디스크 상의 식별자
  // srcp: 파일을 메모리에 매핑한 시작 주소
  // filetype: 파일의 MIME 타입을 저장하는 버퍼
  // buf: HTTP 응답 헤더와 바디를 저장하는 버퍼
  char *srcp, filetype[MAXLINE], buf[MAXBUF];

  /* Send response headers to client */
  // 파일의 MIME 타입을 결정하여 filetype에 저장
  get_filetype(filename, filetype);
  // sprintf를 사용하여 HTTP 응답 헤더 구성
  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);
  // 파일의 MIME 타입을 지정
  sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
  // 생성한 응답 헤더를 클라이언트에게 전송
  Rio_writen(fd, buf, strlen(buf));

  printf("Response headers:\n");
  printf("%s", buf);

  /* Send response body to client */
  srcfd = Open(filename, O_RDONLY, 0); // 요청된 파일을 읽기 전용으로 연다 // srcfd는 파일의 디스크 식별자
  srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); //요청한 파일을 가상메모리에 매핑 // srcp는 매핑된 메모리의 시작 주소
  Close(srcfd);  //매핑했으니 이제 식별자 필요없음  
  Rio_writen(fd, srcp, filesize); // 주소 srcp에서 시작하는 filesize byte를 클라이언트의 연결식별자로 복사. (파일 전송)
  Munmap(srcp, filesize); //매핑된 가상메모리 영역을 free 
}

 

1. 변수 선언

 

  • int srcfd;: 요청된 파일을 열 때 사용하는 파일 식별자
  • char *srcp;: 파일을 메모리에 매핑한 시작 주소를 저장
  • char filetype[MAXLINE];: 파일의 MIME 타입을 저장하는 버퍼
  • char buf[MAXBUF];: HTTP 응답 헤더와 본문을 저장하는 버퍼

 

2. HTTP 응답 헤더 전송

  • 요청된 파일의 MIME 타입을 결정하여 filetype에 저장
  • HTTP 응답 상태 줄을 구성. 여기서는 요청이 성공했음을 나타내는 200ok를 사용
  • 서버 정보, 연결 상태, 콘텐츠 길이, MIME 타입을 포함한 HTTP 헤더를 구성
  • Rio_writen(fd, buf, strlen(buf));를 사용하여 생성한 응답 헤더를 클라이언트에게 전송

3. 응답 헤더 출력

  • 구성된 응답 헤더를 출력하여 디버깅에 도움을 줌

4. 응답 본문 전송

  • 요청된 파일을 읽기 전용으로 열어 파일 식별자를 srcfd에 저장
  • Mmap 함수를 사용하여 파일을 가상 메모리에 매핑
    이제 srcp는 파일 내용이 위치하는 메모리 주소를 가리킴
  • 파일 식별자는 더 이상 필요하지 않으므로 닫음
  • 매핑된 메모리 주소에서 시작하는 filesize 바이트를 클라이언트의 소켓으로 전송
  • 매핑된 가상 메모리 영역을 해제하여 메모리를 정리

get_filetype 함수

주어진 파일 이름에 따라 적절한 MIME 타입을 결정, 결과를 filetype 변수에 저장

void get_filetype(char *filename, char *filetype)
{
  printf("get_filetype함수호출\n");
  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
    strcpy(filetype, "text/plain");
}

 

1. 매개변수:

 

  • char *filename: MIME 타입을 결정할 파일의 이름을 포함하는 문자열
  • char *filetype: 결정된 MIME 타입저장할 버퍼

2. 동작:

 

  • printf("get_filetype함수호출\n");: 함수가 호출되었음을 알리는 로그 메시지를 출력
  • strstr(filename, ".html"): 파일 이름에 .html이 포함되어 있는지 확인
    • 포함되어 있다면, filetype에 "text/html"을 저장
  • 이와 유사하게, .gif, .png, .jpg 확장자를 체크하고, 각각에 대해 적절한 MIME 타입을 저장
  • 만약 어떤 확장자에도 해당하지 않으면, 기본값으로 "text/plain"을 저장

MIME 타입의 중요성

 

  • MIME 타입: 웹 브라우저가 서버에서 전송된 파일의 타입을 이해하고,
    그에 따라 적절한 방식으로 처리할 수 있도록 도와줌
    예를 들어, HTML 파일은 웹 페이지로 렌더링되고, 이미지 파일은 시각적으로 표시
  • 정적 파일 서버에서의 사용: 이 함수는 웹 서버에서 정적 파일을 제공할 때,
    클라이언트가 요청한 파일에 대한 적절한 응답을 구성하는 데 필수적

serve_dynamic 함수

클라이언트에게 동적 컨텐츠를 제공하기 위해 CGI 프로그램을 실행하는 역할

// 동적 컨텐츠를 클라이언트에 제공하기 위해 CGI 프로그램을 실행하는 기능 수행
void serve_dynamic(int fd, char *filename, char *cgiargs)
{
  char buf[MAXLINE], *emptylist[] = { NULL };

  /* Return first part of HTTP response */
  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) { //자식 프로세스 fork (자식 프로세스의 context에서 cgi 프로그램을 돌린다)
    setenv("QUERY_STRING", cgiargs, 1);  // QUERY_STRING 환경변수를 CGI 인자로 설정
    Dup2(fd, STDOUT_FILENO); // 자식의 표준 출력을 연결 식별자로 redirect.
    Execve(filename, emptylist, environ); // cgi 프로그램 실행. 
    // Execve가 성공하면, 다음 줄의 코드는 실행되지 않음
  }
  Wait(NULL); // 부모 프로세스는 자식 프로세스가 종료될 때까지 대기
}

 

1. HTTP 응답 헤더 전송:

  • sprintf를 사용하여 응답 헤더를 설정하고 Rio_writen으로 클라이언트에게 전송
  • 첫 번째로 HTTP 상태 줄인 "HTTP/1.0 200 OK\r\n"을 전송하여 요청이 성공했음을 알림
  • 두 번째로 서버 정보를 포함하는 헤더 "Server: Tiny Web Server\r\n"을 전송

2. 자식 프로세스 생성:

  • Fork() 함수를 호출하여 새로운 프로세스를 생성
  • 이때 반환값이 0이면 자식 프로세스의 실행 흐름에 있다

3. 환경 변수 설정:

  • setenv("QUERY_STRING", cgiargs, 1)를 호출하여 CGI 프로그램이 사용할 수 있도록
    쿼리 문자열을 환경 변수 QUERY_STRING에 저장
  • cgiargs는 클라이언트의 요청에서 받은 CGI 인자

4. 표준 출력 리디렉션:

  • Dup2(fd, STDOUT_FILENO)를 호출하여 자식 프로세스의 표준 출력을 클라이언트와의 소켓 연결로
    리디렉션함. 즉, CGI 프로그램이 출력하는 내용이 클라이언트에게 직접 전송

5. CGI 프로그램 실행:

  • Execve(filename, emptylist, environ)를 호출하여 지정된 CGI 프로그램을 실행
  • filename은 실행할 CGI 프로그램의 경로이고, emptylist인자를 전달하지 않음을 의미.
    environ현재 환경 변수를 사용
  • Execve가 성공하면, 이후의 코드는 실행되지 않음

6. 부모 프로세스 대기:

  • Wait(NULL)을 호출하여 부모 프로세스가 자식 프로세스의 종료를 기다림.
    자식 프로세스가 종료될 때까지 부모는 블록됩니다.

완성 코드

https://github.com/whirae/webproxy-jungle/blob/main/tiny/tiny.c

'C언어 > Tiny 웹 서버' 카테고리의 다른 글

Tiny 웹 서버 개요  (0) 2024.09.17
network 개요  (2) 2024.09.16