[LINUX]TINY Webserverの作成(5)-TINY Webserverの設計(2)


5. parse_uri
その名の通りuriを共有する関数です.
uriをドメイン、path、cgiargsに分けます.
cgiargsは、動的コンテンツの実行可能ファイルのパラメータです.
マイクロサーバ上のすべての動的コンテンツの実行可能ファイルは、cgi-binという古典的な方法で静的コンテンツから分離されます.
したがって,uriにcgi−binというパスがあるかどうかを調べることで,静的コンテンツを送信すべきか動的コンテンツを送信すべきかを判断することができる.
int parse_uri(char *uri, char *filename, char *cgiargs)
{
    char *ptr;
    
    if (!strstr(uri, "cgi-bin")) { //static content
        strcpy(cgiargs, ""); //uri에 cgi-bin과 일치하는 문자열이 없다면 cgiargs에는 빈 문자열을 저장

        strcpy(filename, "."); //아래 줄과 더불어 상대 리눅스 경로이름으로 변환(./index.html과 같은 )
        strcat(filename, uri);

        if (uri[strlen(uri)-1] == '/') //uri가 / 문자로 끝난다면 기본 파일 이름 추가 -> /로 안끝나는 경우가 있나? 
            strcat(filename, "home.html");
        return 1;
    }
    else { //dynamic content
        ptr = index(uri, '?');
        if (ptr) { //cgi 인자 추출
            strcpy(cgiargs, ptr+1);
            *ptr = '\0';
        }
        else {
            strcpy(cgiargs, "");
        }
        strcpy(filename, ".");
        strcpy(filename, uri);
        return 0;
    }
}
6. serve_static
サーバ上のディスク・ファイルは静的コンテンツと呼ばれ、ディスク・ファイルをクライアントにインポートして渡す操作を処理します.
serve static関数の実行手順を以下に示します.
1.コンテンツを送信する前に、どのようなコンテンツを送信するか、およびどの程度のコンテンツを送信するかを含む応答ヘッダを送信する.
2.要求されたファイルを読み取り専用で開き、ファイルの内容を仮想メモリ領域に保存します.
3.仮想メモリに格納されているコンテンツをクライアントに関連付けられた接続識別子に書き込み、クライアントに送信します.
void serve_static(int fd, char *filename, int filesize)
{
    int srcfd;
    char *srcp, filetype[MAXLINE], buf[MAXBUF];

    //client에게 response header 보내기
    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);
    Rio_writen(fd, buf, strlen(buf));
    printf("Response headers:\n");
    printf("%s", buf);

    //O_RDONLY -> 파일을 읽기 전용으로 열기 <-> O_WRONLY, 둘 합치면 O_RDWR
    srcfd = Open(filename, O_RDONLY, 0);
    //mmap는 요청한 파일을 가상메모리 영역으로 매핑함
    //Mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
    //fd로 지정된 파일에서 offset을 시작으로 length바이트 만큼 start주소로 대응시키도록 한다. 
    //start주소는 단지 그 주소를 사용했으면 좋겠다는 정도로 보통 0을 지정한다. 
    //mmap는 지정된 영역이 대응된 실제 시작위치를 반환한다. 
    //prot 인자는 원하는 메모리 보호모드(:12)를 설정한다 
    //-> PROT_EXEC - 실행가능, PROT_READ - 읽기 가능, NONE - 접근 불가, WRITE - 쓰기 가능
    //flags는 대응된 객체의 타입, 대응 옵션, 대응된 페이지 복사본에 대한 수정이 그 프로세스에서만 보일 것인지, 다른 참조하는 프로세스와 공유할 것인지 설정 
    //MAP_FIXED - 지정한 주소만 사용, 사용 못한다면 실패 / MAP_SHARED - 대응된 객체를 다른 모든 프로세스와 공유 / MAP_PRIVATE - 다른 프로세스와 대응 영역 공유하지 않음 

    srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); //srcfd의 첫 번째 filesize 바이트를 주소 srcp에서 시작하는 사적 읽기-허용 가상메모리 영역으로 매핑함 
    Close(srcfd);
    Rio_writen(fd, srcp, filesize); //파일을 클라이언트에게 전송 -> 주소 srcp에서 시작하는 filesize 바이트를 클라이언트의 연결 식별자로 복사함.
    Munmap(srcp, filesize);//매핑된 가상메모리 주소를 반환
}
7. 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, "image/png"))
        strcpy(filetype, "image/png");
    else if (strstr(filename, "image/jpg"))
        strcpy(filetype, "image/jpeg");
    else
        strcpy(filetype, "text/plain");
}
8. serve_dynamic
最後に、ダイナミックコンテンツを処理する関数です.
初めて見た関数がたくさんあるので、以下の注釈を参考にして理解できます.
実行プロセスは次のとおりです.
1.親プロセスを継承する子プロセスを作成し、新しいプロセスを実行する準備をします.
2.execはforkとは異なり、メモリを継承しないため、渡す変数は環境変数として保存する必要があります.したがって,cgiargsはQUERY STRINGという環境変数に格納される.
3.クライアントに関連付けられたconnfdを標準出力に再設定します.これにより、CGIプログラムが標準出力に使用するすべてのコンテンツがクライアントプロセスに直接渡されます.
4.実行可能ファイル(CGIプログラム)を新しいプロセスとして実行します.環境を最後のパラメータとして使用する場合は、以前に設定したQUERY STRING変数をgetenv関数でプログラムで使用できます.
char buf[MAXLINE], *emptylist[] = { NULL };

    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));

    // fork 함수를 호출하는 프로세스는 부모 프로세스가 되고, 새롭게 생성되는 프로세스는 자식 프로세스가 됨
    // fork 함수에 의해 생성된 자식 프로세스는 부모 프로세스의 메모리를 그대로 복사하여 갖게 됨
    // fork 함수 호출 이후 코드부터 각자의 메모리를 사용해 실행 
    // fork()의 반환 값 = 부모는 자식프로세스의 PID(프로세스 아이디)값, 자식프로세스는 0
    // fork()의 값을 어디 변수에 저장해 놓는다면 조건문으로 부모와 자식 프로세스에서 원하는 것 따로 실행할 수 있음 
    // pid_t pid = fork() / if (pid > 0) (=부모){~~} else if (pid == 0) (=자식){~~}

    if (Fork() == 0) { //자식프로세스
        //setenv(const char* name, const char* value, int overwrite)
        //환경변수 "name"을 현재의 환경 리스트에 삽입 또는 재설정함 
        //overwrite가 0이면 재설정 되지 않음, 그 외의 경우 주어진 값에 재설정됨
        //exec 호출하면 명령줄 인수, 환경변수만 전달받음 
        //exec 호출하면 코드 영역에 있는 내용을 지우고, 새로우 코드로 바꿈. 또한 데이터 영역이 새로운 변수로 채워지고, 스택 영역이 리셋됨.
        setenv("QUERY_STRING", cgiargs, 1);

        //dup2(fd, fd2) = fd의 값을 fd2로 지정함 -> connfd를 STDOUT_FILENO로 바꿈 (연결을 바꿈)
        Dup2(fd, STDOUT_FILENO); //redirect stdout to client -> 프로세스가 로드되기 전에 표준 출력을 클라이언트와 연관된 연결식별자로 재지정함

        //경로 또는 파일 이름으로 지정한 실행 파일을 실행하여 프로세스 생성 
        //부모와 자식 다른 작업 하면서 둘 다 살아있게 하기 위해 사용
        //execve(실행 파일 or 명령어, argv(맨 뒤를 NULL로 넣어줘야 함(argc를 전달할 수 없기 때문에)), 전달할 환경변수 - environ넣으면 기존에 설정한 환경변수 사용)
        Execve(filename, emptylist, environ); // Run CGI program
    }
    Wait(NULL); // 자식프로세스가 종료되어 정리되는 것을 기다림
}
深く調べてみるとキリがなく、それだけで済み、不快なWebサーバの作成駐車は終了しています.
しかし、これはソフトウェアエンジニアが接触できる最下層段階なので、不快感を解消し、OSプロジェクトに努力しなければならない.
フィードバックを歓迎します.
-終了-