본문 바로가기
  • 초부득3 - 어제보다 나은 내일을 위해
  • 꿈이 현실이 되는 날까지
sw사관학교 정글 2기/컴퓨터 시스템

Tiny 웹 서버 소스코드 분석 및 웹서버에 대한 것들.

by 금의야행 2021. 9. 24.

 

목차

     

    개요

     

    Tiny라고 부르는 작지만 동작하는 웹 서버입니다. 이는 프로세스 제어, Unix I/O, 소켓 인터페이스, HTTP 와 같은 많은 개념들을 결합했습니다. 

     

    fd = file descriptor 파일 식별자

     

     

    함수 

    1. 기초 선언
    2. main
    3. doit
    4. clienterror
    5. read_requesthdrs
    6. parse_uri
    7. get_filetype
    8. serve_static
    9. serve_dynamic
    10. adder.c

     

     

     

    0. 기초 선언

     

     

    /* $begin tinymain */
    /*
     * tiny.c - A simple, iterative HTTP/1.0 Web server that uses the
     *     GET method to serve static and dynamic content.
     *
     */
    #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);
    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);
    
    /* MAXLINE, MAXBUF == 8192 */

     

     

     

    1. main

     

    /* 설명 1.1 */
    // 입력 ./tiny 8000 / argc = 2, argv[0] = tiny, argv[1] = 8000
    int main(int argc, char **argv)
    {
      int listenfd, connfd;
      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);
      }
    
      /* Open_listenfd 함수를 호출해서 듣기 소켓을 오픈한다. 인자로 포트번호를 넘겨준다. */
      // Open_listenfd는 요청받을 준비가된 듣기 식별자를 리턴한다 = listenfd
      // 설명 1.2
      listenfd = Open_listenfd(argv[1]);
    
      /* 전형적인 무한 서버 루프를 실행*/
      while (1)
      {
      	//accpet 함수 인자에 넣기위한 주소길이를 계산
        clientlen = sizeof(clientaddr);
        
        /* 반복적으로 연결 요청을 접수 */
        // accept 함수는 1. emetl tlrqufwk, 2. 소켓주소구조체의 주소, 3. 주소(소켓구조체)의 길이를 인자로 받는다.
        connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); // line:netp:tiny:accept
        
        // Getaddrinfo는 호스트 이름: 호스트 주소, 서비스 이름: 포트 번호의 스트링 표시를 소켓 주소 구조체로 변환
        // Getnameinfo는 위를 반대로 소켓 주소 구조체에서 스트링 표시로 변환.
        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
      }
    }

     

    설명 1.1: 

    main 함수에 인자가?

     

    main함수에서 첫 번째 매개변수 argc는 옵션의 개수이며 argv는 옵션 문자열의 배열

    int main(int argc, char * argv[] (== char **argv) )

    It is fundamental to c that char** x and char* x[] are two ways of expressing the same thing. Both declare that the parameter receives a pointer to an array of pointers. Apart from the use of sizeof

     

     

     

    출처:

    https://dojang.io/mod/page/view.php?id=696 

    https://stackoverflow.com/questions/27213580/difference-between-char-argv-and-char-argv-for-the-second-argument-to-main

     

     

     

    설명 1.2

    소켓 및 소켓 인터페이스 및 소켓 주소 구조체

     

    소켓이란?

     

    소켓(Socket)이란 네트워크상에서 동작하는 프로그램 간 통신의 종착점(Endpoint)입니다.

     

    각 소켓은 인터넷 주소와  16비트 정수 포트*로 이루어진 소켓 주소를 가지며 이것은 address : port 로 나타낸다.

    *위의 포트는 소프트웨어 포트로 이는 네트워크 스위치와 라우터에서의 하드웨어 포트와는 관련이 없다.

     

    소켓 인터페이스 기반 네트워크 응용프로그램의 개요.

     

    open_listenfd소켓 인터페이스 ( 네트워크 응용을 만들기 위한 Unix I/O 함수들과 함께 사용되는 함수들의 집합 figure 11.12 참조) 의 getaddrinfo, socket, bind, listen의 역할들을 한번에 수행한다.

     

     

    인터넷 소켓 주소는 sockaddr_in 타입의 16바이트 구조체에 저장된다. 

    connect, bind, and accept 함수는 프로토콜에 특화된 소켓 주소 구조체를 가리키는 포인터를 필요로 한다.

     

    어떤 종류의 소켓 주소 구조체라도 받아들일 수 있도록 소켓 함수들이 sockaddr 구조체로의 포인터를 기대하도록하고, 응용이 프로토콜에 특화된 구조체로의 모든 포인터를 이 포괄적인 구조체로 캐스팅(형변환?)하도록 정의한다. 

    CSAPP 11.4

     

     

    리눅스/유닉스 시스템에서는 소켓(socket)의 통신 대상을 지정하기 위해 '주소(address)'를 사용한다.
    이 '주소' 라는 것을 저장하거나 표현하는데 사용하는 구조체가 바로, 본 포스팅에서 설명하고자 하는 'struct sockaddr' 이다.
    bind( ), connect( ) 와 같은 함수들이 2번째 매개변수로써 바로 이 'struct sockaddr *addr' 을 받는다.

     

    이 struct sockaddr은 기본 형태이고, 주소체계(Address family)값에 따라서 구조체를 형변환 해서 사용하면 편리하다.
    즉, 일반적으로 개발을 하다보면 struct sockaddr_in, struct sockaddr_un, struct sockaddr_in6 등의 구조체와 상호 형변환을 해서 사용하게 된다.


    출처: https://techlog.gurucat.net/292 [하얀쿠아의 이것저것 만들기 Blog]

     

     

    기본 소켓 주소 구조체 sockaddr는 sa_data에 Ip와 port 주소등이 함께 들어있기에 이를 구분하기 위해 socketaddr_in으로 캐스팅(형변환)을 해 사용하곤 한다.

     

    sockaddr 구조체는 소켓의 주소를 담는 기본 구조체 역할을 한다.

    속된말로, '소켓 주소의 와꾸를 잡는 녀석' 쯤으로 이해하면 쉽다.

     

     sa_family : 주소체계를 구분하기 위한 변수이며, 2 bytes 이다. 참고로, u_short는 unsigned short를 말한다.

     sa_data : 실제 주소를 저장하기 위한 변수다. 14 bytes 이다.

    즉, 이 구조체는 16 bytes 의 와꾸(틀, 크기)를 잡아주는 녀석이다.

     

    주소체계는 AF_INET, AF_INET6, AF_UNIX, AF_LOCAL, AF_LINK, AF_PACKET 등이 있고 sockaddr_in 은 AF_INET 만을 지원한다.

    추가적인 소켓 주소 구조체에 대해 궁금하다면 ↓

    출처: https://techlog.gurucat.net/292 [하얀쿠아의 이것저것 만들기 Blog]

     

     

     

     

    2. 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 설명 2.1
      // rio_readlineb를 위해 rio_t 타입(구조체)의 읽기 버퍼를 선언
      rio_t rio;
    
      /* Read request line and headers */
      /* Rio = Robust I/O */
      // rio_t 구조체를 초기화 해준다.
      Rio_readinitb(&rio, fd);
    
      /* Rio_readlineb(그림 10.8)를 통해 요청 라인을 읽어들이고, 분석한다. 70 +4 line */
      Rio_readlineb(&rio, buf, MAXLINE);
      printf("Request headers:\n");
      printf("%s", buf);
      //설명 2.2
      sscanf(buf, "%s %s %s", method, uri, version);
    
    
      /* Tiny 는 GET method 만 지원하기에 클라이언트가 다른 메소드 (Post 같은)를 
       *요청하면 에러메세지를 보내고, main routin으로 돌아온다
       * 79 +4 line
       */
       
      //strcmp() 문자열 비교 함수, 
      //strcasecmp() 대소문자를 무시하는 문자열 비교 함수 
      //strncasecmp() 대소문자를 무시하고, 지정한 길이만큼 문자열을 비교하는 함수
      if (strcasecmp(method, "GET"))
      {
        clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
        return;
      }
      
      
      /* GET method라면 읽어들이고, 다른 요청 헤더들을 무시한다. */
      read_requesthdrs(&rio);
    
    
      /* Parse URI form GET request */
      /* URI 를 파일 이름과 비어 있을 수도 있는 CGI 인자 스트링으로 분석하고, 요청이 정적 또는 동적 컨텐츠를 위한 것인지 나타내는 플래그를 설정한다.  */
      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;
      }
      
      
      /* Serve static content */
      if (is_static)
      {
        /* 동적 컨텐츠이고 (위if), 이 파일이 보통 파일인지, 읽기 권한을 가지고 있는지 검증한다. */
        if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode))
        {
          clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read this file");
          return;
        }
        /* 그렇다면 정적 컨텐츠를 클라이언트한테 제공. */
        serve_static(fd, filename, sbuf.st_size);
      }
      
      
      else /* Serve dynamic content */
      {
        /* 실행 가능한 파일인지 검증*/
        if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode))
        {
          clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read this file");
          return;
        }
        /* 그렇다면 동적 컨텐츠를 클라이언트에게 제공 */
        serve_dynamic(fd, filename, cgiargs);
      }
    }

     

     

    설명 2.1 

    Unix I/O 그리고 RIO (Robust input output) 란

     

    우선 unix I/O 의 개념들

     

     

    리눅스에서 파일은 연속된 m개의 바이트다.

    네트워크, 디스크, 터미널 같은 모든 I/O 디바이스들은 파일로 모델링 된다.

    모든 입력과 출력은 해달 파일을 읽거나 쓰는 형식으로 수행된다.

     

    • 읽기 연산은 현재 파일 위치 k에서 시작해서 n > 0 바이트를 파일에서 메모리로 복사하고 k를 n만큼 증가시킨다. 

    k > m(파일의 최대 길이) 의 읽기 연산을 수행하면 end-of-file EOF라고 알려진 조건이 발생하며 이것은 응용프로그램에서 감지 할 수 있다. 하지만 실제 파일 끝에서 "EOF 문자" 가 명시적으로 존재하는 것은 아니다.

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

     

    리눅스의 파일 타입 혹은 종류 

     

     

    파일의 타입은 여러개로 나뉜다.

    • Regular file : 일반 파일로, 2진수, text 등 데이터를 담고 있다. OS에서 이 파일을 보면, 정확한 포맷은 알지 못한 채, 그저 "일련의 바이트"라고 생각한다고 한다.
    • Directory file : 문서 파일이다. 파일의 이름과 주소를 담고 있다.
    • Character special and block special file : 말그대로 문자/블록 특수 파일이다. 사용자와 프로그램이 하드웨어(ex 키보드)등으로 소통하게 해주는 파일이다. 간단히 설명하자면, 키보드로 입력된 입력값과, 모니터에 나타낼 블록 단위의 값을 담고 있다고 보면 된다. 
    • Pipe : 로컬 프로세스 간 커뮤니케이션 용도로 사용된다.
    • Socket : 네트워크 커뮤니케이션 용도로 사용된다.
    • link : 바로가기라고 생각하면 편하다. 원본 파일을 가리키는 포인터의 역할을 한다.

     

    출처: https://suhwanc.tistory.com/131
    간단한 리눅스 파일 종류 설명 ↓
    https://linuxconfig.org/identifying-file-types-in-linux

     

     

     

     

     

    ls -al 커멘드로 보이는 정보에서 각 첫쨰 줄의 'd' '-'등이 파일의 type을 나타낸다.

     

     

     

     

    Rio , Robust I/O 라고 부르는 I/O 패키지. 

     

     

    Rio 패키지는 짧은 카운트(short count)를 자동으로 처리한다 (Unix I/O 와 차이) .

    짧은 카운트가 발생할 수 있는 네트워크 프로그램 같은 응용에서 편리하고 안정적이고 효율적인 I/O 패키지이다

     

     

     

     

    짧은 카운트란?

     

    일부의 경우에 unix io의 read, write 함수는 응용이 요청하는 것보다 더 적은 바이트를 전송한다. 이를 짧은 카운트라고 명시한다. 짧은 카운트는 에러를 나타내는 것은 아니다!

     

     

    보통 short counts는 다음과 같은 상황일 때 발생한다.

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

     

    1. 20바이트만 가지고 있는 파일의 현재 파일 위치에서 읽으려 할떄, 이 파일에서부터 50바이트를 한번에 읽으려고 할경우 read 의 경우는 short count 20을 리턴할 것이다. 추후 read는 계속해서 short count 0을 리턴하며 EOF 임을 알릴 것이다.
    2. 만일 열린 파일이 터미널과 연결되어 있으면(키보드 혹은 디스플레이) 각 read 함수는 한번에 한개의 텍스트 줄을 전송할 것이며, 텍스트 중의 길이와 동일한 short count를 리턴할 것이다.
    3. 네트워크의 경우 내부버퍼링 제약과 긴 네트워크 지연시간 때문에 read 와 write 모두 short count를 리턴할 수 있다. 

     

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

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

     

     

     

    RIO의 2가지 형태

     

     

    1) 2진 데이터로 이루어진 Unbuffered input / output (버퍼 없는 입력 및 출력 함수)

    -> 아래에선 rio_readn, rio_writen 함수로 이를 처리할 것이다.

     

    소켓 인터페이스 기반 네트워크 응용프로그램의 개요를 보면 알 수 있지만.

    rio_writen 은 서버 -> 클라이언트, 클라이언트 -> 서버로 데이터를 전송 할 때 사용한다.

     

     

     

    2) 2진 데이터와 텍스트로 이루어진 Buffered input (버퍼를 사용하는 입력 함수)

    -> 아래에선 rio_readlineb, rio_readnb 함수로 설명할 것이다.

    * 특히 2번 buffered는 그 전 (unix, standard)에서 하지 못하는 스레드 관리를 해주니 자세히 보도록 하자.

     

     

    Rio_readlineb 함수는 텍스트 줄을 파일 rp(종료 새 줄 문자를 포함해서)에서 부터 읽고, 읽은 것들을 메모리 위치 usrbuf로 복사하고, 읽은 텍스트 라인을 널(0) 문자로 바꾸고 종료시킨다. rio_readlineb 함수는 최대 maxlen-1개의 바이트를 읽는다. maxlen-1를 넘는 텍스트 라인들은 잘내서 널 문자로 종료시킨다.

    그리고 '\n' 개행 문자를 만날 경우 maxlen 전에 break 된다.!

    이는 위에서 rio_writen를 통해 서버면 클라이언트, 클라이언트면 서버에서 보내진 정보를 읽을 때 사용한다.

     

     

    추가적인 RIO 함수 설명 ↓
    https://suhwanc.tistory.com/131

     

     

     

     

    struct rio_t와 rio_readinitb 의 코드

     

     

     

    설명 2.2

    sscanf 는 뭔가요

    https://modoocode.com/67

    #include <stdio.h>  // C++ 에서는 <cstdio>
    
    int sscanf(const char* str, const char* format, ...);

    문자열에서 형식화 된 데이터를 읽어온다.

     str 에서 데이터를 형식 문자열(format)에서 지정하는 바에 따라 읽어와 그 데이터를 뒤에 부수적인 인자들이 가리키는 메모리 공간에 저장하게 된다.

    이 때, 데이터가 저장되는 방식 역시 형식 문자열에 의해 결정된다.

     

    str

    C 문자열로 sscanf 함수가 데이터를 얻어올 문자열이다 C 문자열로 sscanf 함수가 데이터를 얻어올 문자열이다.

     

     

    https://haruhiism.tistory.com/30

     

    scanf, sscanf, fscanf는 어떤 차이일까?

    지난번에 포스팅한 C언어의 출력 함수들에 이어서 이번에는 입력 함수들에 대해 포스팅해 보겠다. 2018/11/12 - [컴퓨터 공학/C,C++] - printf, fprintf, sprintf는 어떤 차이일까? printf, fprintf, sprintf는 어..

    haruhiism.tistory.com

     

     

    format 인자의 구체적인 설명부터는 아래 링크 참조 ↓
    https://modoocode.com/67

     

     

     

      char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    //생략...
    
      Rio_readlineb(&rio, buf, MAXLINE);
      
      sscanf(buf, "%s %s %s", method, uri, version);
      
    //생략..
    
    //
      printf("Request headers:\n");
      printf("%s", buf);
    //버퍼 출력
      Request headers:
      GET /mp4sample.mp4 HTTP/1.1
      
      method == 'GET'
      uri == '/mp4sample.mp4'
      version == 'HTTP/1.1'

     

    Rio_readlineb를 통해 요청 라인을 읽고 buf에 담았고,

    이 buf를 sscanf를 통해 공백을 기준으로 method, uri, version으로 분리해 각각의 인자에 담는다.

     

     

    strcasecmp는 또 뭐죠

      //strcmp( str1, str2 ) 문자열 비교 함수, 
      //strcasecmp() 대소문자를 무시하는 문자열 비교 함수 
      //strncasecmp() 대소문자를 무시하고, 지정한 길이만큼 문자열을 비교하는 함수

    IBM

    출처:

     

     

    3. clienterror

     

    /* 명백한 오류에 대해서 클라이언트에게 보고하는 함수. 
     * HTTP응답을 응답 라인에 적절한 상태 코드와 상태 메시지와 함께 클라이언트에게 보낸다.
     * 
     */
    void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg)
    {
      char buf[MAXLINE], body[MAXBUF];
    
      /* BUILD the HTTP response body */
      /* 브라우저 사용자에게 에러를 설명하는 응답 본체에 HTML도 함께 보낸다 */
      /* HTML 응답은 본체에서 컨텐츠의 크기와 타입을 나타내야하기에, HTMl 컨텐츠를 한 개의 스트링으로 만든다. */
      /* 이는 sprintf를 통해 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);
      // body 배열에 차곡차곡 html 을 쌓아 넣어주고
    
      /* Print the HTTP response */
      sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg);
      
      /* Rio_writen 그림 10.3 */
      //굳이 보내고 쌓고 보내고 쌓고가 아니라 위에 body처럼 쭉 쌓아서 한번에 보내도 되지않을까?
      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));
      // buf에 넣고 보내고 넣고 보내고
      
      // sprintf로 쌓아놓은 길쭉한 배열을 (body, buf)를 보내준다.
      Rio_writen(fd, buf, strlen(buf));
      Rio_writen(fd, body, strlen(body));
    }

     

    sprintf는? 

     

    #include <stdio.h>  // C++ 에서는 <cstdio>
    
    int sprintf(char* str, const char* format, ...);

     

    간단히 설명하자면 

     

    str라는 문자열 배열에 format(형식 문자열)에서 지정한 방식 대로 ... 에 들어갈 인자들을 넣어준다. 

    https://modoocode.com/66

     

    좀더 간단히는 ↓참고

    https://dojang.io/mod/page/view.php?id=352 

     

    C 언어 코딩 도장: 43.1 서식을 지정하여 배열 형태로 문자열 만들기

    43 문자열 만들기 프로그램에서 문자열이 중요하게 사용되는 만큼 C 언어에서는 다양한 문자열 처리 함수를 제공합니다. 이번에는 printf처럼 서식을 지정해서 문자열을 생성하는 방법을 알아보

    dojang.io

     

     

     

    4. read_requesthdrs

     

    /* Tiny는 요청 헤더 내의 어떤 정보도 사용하지 않는다
     * 단순히 이들을 읽고 무시한다. 
     */
    void read_requesthdrs(rio_t *rp)
    {
      char buf[MAXLINE];
    
      Rio_readlineb(rp, buf, MAXLINE);
      
      /* strcmp 두 문자열을 비교하는 함수 */
      /* 헤더의 마지막 줄은 비어있기에 \r\n 만 buf에 담겨있다면 while문을 탈출한다.  */
      while (strcmp(buf, "\r\n"))
      {
      	//rio 설명에 나와있다 싶이 rio_readlineb는 \n를 만날때 멈춘다.
        Rio_readlineb(rp, buf, MAXLINE);
        printf("%s", buf);
        // 멈춘 지점 까지 출력하고 다시 while
      }
      return;
    }

     

     

     

    요청 헤더에 대한 자세한 설명. 

    https://goddaehee.tistory.com/169

     

    [HTTP 기초_1] 헤더 (요청(Request) 헤더, 응답(Response)헤더)

    [HTTP 기초_1] 헤더 (요청(Request) 헤더, 응답(Response)헤더) 안녕하세요. 갓대희 입니다. 이번 포스팅은 [ HTTP란?, HTTP 헤더 입니다. : ) 헤더로 들어가기 앞서 HTTP가 무엇인지 부터 알아보자. ▶ HTTP..

    goddaehee.tistory.com

     

     

    5. parse_uri

     

      /* Parse URI form GET request */
      /* URI 를 파일 이름과 비어 있을 수도 있는 CGI 인자 스트링으로 분석하고, 요청이 정적 또는 동적 컨텐츠를 위한 것인지 나타내는 플래그를 설정한다.  */
      // URI 예시 static: /mp4sample.mp4 , / , /adder.html 
      //         dynamic: /cgi-bin/adder?first=1213&second=1232 
    int parse_uri(char *uri, char *filename, char *cgiargs)
    {
      char *ptr;
    
      /* strstr 으로 cgi-bin이 들어있는지 확인하고 양수값을 리턴하면 dynamic content를 요구하는 것이기에 조건문을 탈출 */
      if (!strstr(uri, "cgi-bin")) /* Static content */
      {
        strcpy(cgiargs, "");
        strcpy(filename, ".");
        strcat(filename, uri);
        //결과 cgiargs = "" 공백 문자열, filename = "./~~ or ./home.html
    
    	// uri 문자열 끝이 / 일 경우 허전하지 말라고 home.html을 filename에 붙혀준다.
        if (uri[strlen(uri) - 1] == '/')
          strcat(filename, "home.html");
        return 1;
      }
    
    // uri 예시: dynamic: /cgi-bin/adder?first=1213&second=1232 
      else /* Dynamic content */
      {
      	//설명 5.1
        //index 함수는 문자열에서 특정 문자의 위치를 반환한다.
        ptr = index(uri, '?');
        
        //?가 존재한다면
        if (ptr)
        {
          // 인자로 주어진 값들을 cgiargs 변수에 넣는다. 
          strcpy(cgiargs, ptr + 1);
          
          /* ptr 초기화 */
          *ptr = '\0';
        }
        else
          strcpy(cgiargs, "");
    
        strcpy(filename, ".");
        strcat(filename, uri);
    
        return 0;
      }
    }

     

     

    strstr, strcpy, strcat 은 또 뭐야.

     

    1. strstr()

    문자열 안에서 문자열로 검색하기!

     

    strstr(대상문자열, 검색할문자열)

    • char *strstr(char * const _String, char const * const _SubString);
    • 문자열을 찾았으면 문자열로 시작하는 문자열의 포인터를 반환, 문자열이 없으면 NULL을 반환

     

     

    https://dojang.io/mod/page/view.php?id=371 

     

     

     

    2. strcpy()

    문자열 복사하기!

    • strcpy(대상문자열, 원본문자열);
    • char *strcpy(char *_Dest, char const *_Source);
    • 대상문자열의 포인터를 반환

     

     

    https://dojang.io/mod/page/view.php?id=358 

     

     

     

    3. strcat()

    문자열은 strcat 함수를 사용하여 서로 붙일 수 있으며 함수 이름은 문자열을 연결시키다(string concatenate)에서 따왔습니다(string.h 헤더 파일에 선언되어 있습니다).

    • strcat(최종문자열, 붙일문자열);
    • char *strcat(char *_Destination, char const *_Source);
    • 최종 문자열의 포인터를 반환

     

    배열에 붙히기
    할당된 메모리에 붙히기

     

     

     

     

    설명 5.1

    HTTP 요청

    http 요청은 요청 라인 (시작줄), 요청 헤더 (read_requesthdrs), 헤더 리스트들을 종료하는 빈텍스트 줄이 따라온다.

     

    method URI version

     

    ex) GET / HTTP/1.1 , GET /mp4sample.mp4 HTTP/1.1

     

     

    클라이언트는 프로그램 인자들을 GET 요청일 경우 URI를 통해 서버에 전달된다.

     

    '?' 문자는 파일 이름과 인자를 구분하며, 각 인자는 '&' 문자로 구분한다. 

    빈칸은 인자들 사이에 허용되지 않으며, "%20" 스트링으로 표시해야한다. 

     

     

     

     

     

     

    6. get_filetype

     

    /*
     * get_filetype - Derive file type from filename
     * strstr 두번쨰 인자가 첫번째 인자에 들어있는지 확인
     */
    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");
      /* 11.7 숙제 문제 - Tiny 가 MPG  비디오 파일을 처리하도록 하기.  */
      else if (strstr(filename, ".mp4"))
        strcpy(filetype, "video/mp4");
      else
        strcpy(filetype, "text/plain");
    }

     

    HTML 서버가 처리 할 수 있는 파일 타입을 이 함수를 통해 제공한다. 11.7의 경우 비디오 파일 유형중 mp4인 파일을 요청할 경우 반환 할 수 있게끔 추가했다.

     

     

     

     

     

     

    7. serve_static

     

    void serve_static(int fd, char *filename, int filesize)
    {
      int srcfd;
      char *srcp, filetype[MAXLINE], buf[MAXBUF], *fbuf;
    
      /* Send response headers to client */
      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\r\n", buf, filetype);
    
      /* writen = client 쪽에 */
      Rio_writen(fd, buf, strlen(buf));
    
      /* 서버 쪽에 출력 */
      printf("Response headers:\n");
      printf("%s", buf);
    
      /* Send response body to client */
      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);
    
      /* 숙제문제 11.9 */
      fbuf = malloc(filesize);
      Rio_readn(srcfd, fbuf, filesize);
      Close(srcfd);
      Rio_writen(fd, fbuf, filesize);
      free(fbuf);
    }

     

    unix i/o 에서 Open 함수는 열려고하는 파일의 식별자 번호를 리턴하는데 이를 srcfd 에 담는다.

     

    1. 주석 처리된 첫 번쨰 경우 Mmap 함수를 이용해 바로 메모리를 할당하며 srcfd의 파일 값을 배정한다.

     

    2. malloc의 경우 filesize 만큼의 가상 메모리를 할당한 후 , Rio_readn 으로 할당된 가상 메모리 공간의 시작점인 fbuf를 기준으로 srcfd 파일을 읽어 복사해넣는다. 

     

    3. 양 쪽 모두 생성한 파일 식별자 번호인 srcfd 를 Close() 해주고

     

    4. Rio_writen 함수 (시스템 콜) 을 통해 클라이언트에게 전송한다.

     

    5. 마지막으로 Mmap은 Munmap, malloc은 free로 할당된 가상 메모리를 해제해준다.

     

     

     

     

    8. serve_dynamic

    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) /* Child process 생성 - 부모 프로세스(지금) 을 복사한*/
      {
        /* Real server would set all CGI vars here */
        setenv("QUERY_STRING", cgiargs, 1);   // 환경변수 설정
        Dup2(fd, STDOUT_FILENO);              /* Redirect stdout to client, 자식 프로세스의 표준 출력을 연결 파일 식별자로 재지정 */
        Execve(filename, emptylist, environ); /* Run CGI program */
      }
      Wait(NULL); /* Parent waits for and reaps child 부모 프로세스가 자식 프로세스가 종료될떄까지 대기하는 함수*/
    }

     

     

     

     

    9. adder.c

    /*
     * adder.c - a minimal CGI program that adds two numbers together
     */
    /* $begin adder */
    #include "csapp.h"
    
    int main(void)
    {
      char *buf, *p;
      char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
      int n1 = 0, n2 = 0;
    
      /* Extract the two arguments */
      if ((buf = getenv("QUERY_STRING")) != NULL)
      {
        p = strchr(buf, '&');
        *p = '\0';
        strcpy(arg1, buf);
        strcpy(arg2, p + 1);
        n1 = atoi(strchr(arg1, '=') + 1);
        n2 = atoi(strchr(arg2, '=') + 1);
      }
    
      /* Make the response body */
      // content 인자에 html body 를 담는다.
      sprintf(content, "QUERY_STRING=%s", buf);
      sprintf(content, "Welcome to add.com: ");
      sprintf(content, "%sTHE Internet addition portal.\r\n<p>", content);
      sprintf(content, "%sThe answer is: %d + %d = %d\r\n<p>",
              content, n1, n2, n1 + n2);
      sprintf(content, "%sThanks for visiting!\r\n", content);
    
      /* Generate the HTTP response */
      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);
    }
    /* $end adder */

     

     

    GET /cgi-bin/adder?first=123&second=234 HTTP/1.1

     

    라는 식의 헤더 시작줄이 들어오기 떄문에 adder.c 에서 n1, n2 값을 추출하기위해 atoi(strchr()) 을 사용해"=" 부터 읽는다.

    댓글