메아리

Tour de IOCCC: 2004/hibachi

#define B break;
#define Q if(
#define R return
#define V getenv(
#define T "DOCUMENT_ROOT"
#define S snprintf(
#define C strcspn(u,
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define Z               8192
char r[Z], q[Z], l[Z], w[Z], *o, *m = "404 Not Found", *e = "500 Error";
void
X(int t, char *u, long j)
{
                j = S 
                                q, Z,
                                "HTTP/1.0 %s\r\nContent-Length: %ld\r\n\r\n%s",
                                u, j ? j : C ""), j ? "" : u
                );
                send(t, q, j, 0);
}
void
A(char *i, char *j)
{
                char *u;
                Q (u = strstr(o, i + 5)))
                                sscanf(u + C ":") + 1, j, V i));
}
int
main(int c, char **p)
{
                int f, t, i;
                struct stat g;
                struct sockaddr_in a;
                char *v, *u;
                Q (v = V "SERVER_PORT")) == 0) {
                                R 4;
                }
                i = (int) strtol(v, 0, 10);
                a.sin_addr.s_addr = htonl(INADDR_ANY);
                a.sin_port = htons((unsigned short) i);
                a.sin_family = AF_INET;
                Q (f = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
                                R 5;
                }
                Q bind(f,  (void *) &a, sizeof a)) {
                                R 6;
                }
                Q listen(f, 10)) {
                                R 7;
                }
                for ( ; ; close(t)) {
                                for ( ; waitpid(0, 0, WNOHANG) > 0; )
                                                ;
                                i = sizeof a;
                                Q (t = accept(f,  (void *) &a, &i)) < 0)
                                                continue;
                                Q fork())
                                                continue;
                                for (f = i = 0; f < 2 && i < Z; i++) {
                                                Q recv(t, r + i, 1, 0) <= 0)
                                                                R 1;
                                                Q r[i] == '\n')
                                                                f++;
                                                Q r[i] == ':')
                                                                f = 0;
                                                Q f)
                                                                r[i] = r[i] == '-' ? '_' : toupper(r[i]);
                                }
                                Q Z <= i)
                                                B
                                r[i] = 0;
                                Q *r != 'G' && *r != 'H' && r[2] != 'S') {
                                                X(t, "501 Unsupported", 0);
                                                B
                                }
                                *l = 0;
                                Q (v = strstr(r, "HOST:")))
                                                sscanf(v+5, " %255[^:\r\n]", l);
                                S w, Z, T "=%s/%s", V T), l);
                                putenv(w);
                                u = r + 4 + (*r != 'G');
                                 Q *u != '/' || strstr(u, "../")) {
                                                X(t, m, 0);
                                                B
                                }
                                u[i = C " ")] = 0;
                                o = u + i + 1;
                                i = C "?");
                                Q u[i]) {
                                                S q, Z, "QUERY_STRING=%s", u+i+1);
                                                putenv(q);
                                                u[i] = 0;
                                }
                                S l, Z, "SCRIPT_FILENAME=%s%s", w+14, u);
                                v = l+16;
                                Q stat(v, &g) < 0) {
                                                X(t, m, 0);
                                                B
                                }
                                Q S_ISDIR(g.st_mode)) {
                                                struct dirent *d;
                                                DIR *D = opendir(v);
                                                for ( ; (d = readdir(D)); ) {
                                                                Q !strncmp(d->d_name, "index.", 6)) {
                                                                                strncat(v, d->d_name, Z-16);
                                                                                stat(v, &g);
                                                                                B
                                                                }
                                                }
                                                closedir(D);
                                                Q d == 0) {
                                                                X(t, m, 0);
                                                                B
                                                }
                                }
                                Q *r != 'H') {
                                                Q g.st_mode & 0111) {
                                                                putenv(l);
                                                                sscanf(r, "%4s", V "REQUEST_METHOD"));
                                                                sscanf(inet_ntoa(a.sin_addr), "%15s", V "REMOTE_ADDR"));
                                                                A("HTTP_COOKIE", "%*[ \t]%80[^\r\n]");
                                                                A("CONTENT_LENGTH", "%20s");
                                                                A("CONTENT_TYPE", "%60s");
                                                                dup2(t, 0);
                                                                dup2(t, 1);
                                                                Q system(v))
                                                                                X(t, e, 0);
                                                                B
                                                }
                                                Q (f = open(v, O_RDONLY)) < 0) {
                                                                X(t, e, 0);
                                                                B
                                                }
                                }
                                X(t, "200 OK", g.st_size);
                                Q *r != 'H') {
                                                for ( ; 0 < (i = read(f, r, Z)); )
                                                                send(t, r, i, 0);
                                }
                                B
                }
                shutdown(t, SHUT_WR);
                R 0;
}

가끔씩은 전혀 난해하지 않은 코드가 IOCCC에서 상을 타기도 합니다.

뭘 하는 프로그램인가?

이 프로그램은 웹 서버입니다. 게다가 소스 크기 제한에도 불구하고 CGI와 가상 호스트1를 지원하지요. (아마 이 "기능"때문에 심사위원들이 상을 주려고 했던 게 아닐까 싶습니다.)

이 프로그램은 그냥 다음과 같이 컴파일해도 되고...

$ gcc hibachi.c -o hibachi

아니면 첨부된 autoconf 스크립트를 써서 컴파일해도 됩니다. (네. 이 "작품"은 IOCCC에서 수상한 프로그램 중에서 유일하게 autoconf를 사용합니다. -_-;)

$ ./configure
$ make all

실행 과정은 조금 더 머리가 아픕니다. 첨부된 파일 중에 있는 hibachi-start.sh 스크립트를 사용하면 이 머리 아픈 과정을 간단하게 할 수 있습니다.

$ ./hibachi-start.sh

하지만 위에 있는 파일만으로 뭔가를 하려면, 다음과 같이 꽤 많은-_- 환경 변수를 설정해야 합니다.

$ IFS=' ' PATH='/usr/local/bin:/usr/bin:/bin' ENV='' CDPATH='' GATEWAY_INTERFACE='CGI/0.0' \
  SERVER_PORT=8008 DOCUMENT_ROOT='.' SERVER_NAME=127.0.0.1 SERVER_SOFTWARE='hibachi/1.0' \
  REQUEST_METHOD='xxxx' REMOTE_ADDR='xxx.xxx.xxx.xxx' CONTENT_LENGTH='xxxxxxxxxxxxxxxxxx' \
  CONTENT_TYPE='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  HTTP_COOKIE='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  SCRIPT_FILENAME='xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
  ./hibachi

실행 뒤 웹 브라우저를 켜고 http://localhost:8008/로 접속하면 내용을 구경할 수 있습니다. 서버를 끌 때는 그냥 Ctrl-C 같은 걸로 프로세스를 죽이면 알아서 꺼집니다.

만약 위의 파일만 가지고 진짜로 서버를 확인해 보고 싶다면, 현재 디렉토리(또는 DOCUMENT_ROOT가 가리키는 디렉토리) 안에 localhost와 같은 서버 이름으로 된 디렉토리를 만들고 그 안에 파일을 넣으면 됩니다. 아파치보다 쉽다는 게 최대의 장점입니다.

어떻게 동작하는가?

애초에 전처리기만 통과하면 거의 멀쩡한 소스 코드가 되므로 별도의 설명 없이 소스 코드에 주석으로 설명을 대신하겠습니다.

#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define Z 8192 /* 버퍼 크기. */

char r[Z], /* 읽기 버퍼. 처음에는 헤더를 읽는 데 사용된다. */
     q[Z], /* w가 사용 중일 때 대신 사용하는 버퍼. */
     l[Z], /* SCRIPT_FILENAME=... 가 저장될 버퍼. */
     w[Z], /* 다른 환경 변수를 저장하는 데 사용하는 버퍼. */
     *o, /* 헤더에서 메소드와 URL을 뺀 나머지 문자열에 대한 포인터. */
     *m = "404 Not Found",
     *e = "500 Error";

/* 미리 정해진 HTTP/1.0 응답과 Content-Length 헤더를 보낸다.
 * u는 HTTP/1.0 에러 코드와 메시지, j는 그 뒤에 반환될 데이터 길이이다. */
void
X(int t, char *u, long j)
{
    /* 다음과 같은 HTTP/1.0 응답을 보낸다.
     *
     *     HTTP/1.0 <코드와 메시지><CR><LF>
     *     Content-Length: <길이><CR><LF>
     *     <CR><LF>
     *     <데이터>
     *
     * 만약 j가 미리 설정되어 있으면, 이 뒤에 출력될 데이터는
     * 이 함수를 호출하는 쪽에서 출력할 것이기 때문에 이 함수에서는
     * 데이터를 출력하지 않는다, 반면 j가 0이면 에러 메시지(u)를
     * 데이터에도 함께 출력해야 하기 때문에 Content-Length를 새로
     * 계산한다. (strcspn(u, "")는 strlen(u)와 같다.)
     */
    j = snprintf(
        q, Z,
        "HTTP/1.0 %s\r\nContent-Length: %ld\r\n\r\n%s",
        u, j ? j : strcspn(u, ""), j ? "" : u
    );
    send(t, q, j, 0);
}

/* 지정된 헤더 이름을 헤더 버퍼에서 찾은 뒤, 만약 존재하면 해당하는 환경 변수
 * i에 그 텍스트를 복사한다. j는 처리해야 할 sscanf 포맷 문자열이다.
 * 헤더 이름은 i에서 맨 앞의 5바이트를 떼어낸 것으로 처리하는데, 이는
 * Cookie: 헤더와 HTTP_COOKIE와 같은 경우를 처리하기 위함이다. */
void
A(char *i, char *j)
{
    char *u;

    /* 환경 변수 이름에서 첫 5바이트 빼고 헤더에서 일치하면, */
    if ((u = strstr(o, i + 5)))
        /* 그 이름 뒤의 첫 콜론과 그 다음 공백을 빼고 sscanf에
         * 넘겨 해석한다. 해석된 문자열은 환경 변수 i에 저장된다. */
        sscanf(u + strcspn(u, ":") + 1, j, getenv(i));
}

int
main(int c, char **p)
{
    int f, t, i;
    struct stat g;
    struct sockaddr_in a;
    char *v, *u;

    /* 변수 i에 사용할 서버 포트를 설정한다. */
    if ((v = getenv("SERVER_PORT")) == 0) {
        return 4;
    }
    i = (int) strtol(v, 0, 10);

    /* a에 소켓 주소를 설정한다. */
    a.sin_addr.s_addr = htonl(INADDR_ANY);
    a.sin_port = htons((unsigned short) i);
    a.sin_family = AF_INET;

    /* 소켓을 열고, a가 가리키는 주소에 bind한 뒤 listen한다. */
    if ((f = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
        return 5;
    }
    if (bind(f, (void *) &a, sizeof a)) {
        return 6;
    }
    if (listen(f, 10)) {
        return 7;
    }

    /* 루프를 한 번 돌 때마다 request를 받으려 하고(accept),
     * 새 프로세스를 만든 뒤(fork) 부모 프로세스에서는 accept한 소켓 t를 닫는다.
     * 자식 프로세스는 t를 가지고 적절한 처리를 한 뒤 루프에서 빠져 나가
     * 프로세스를 종료한다. */
    for (; ; close(t)) {
        /* 좀비 프로세스를 만들지 않기 위해 waitpid를 실행한 뒤
         * 결과값은 버린다. (유닉스에서는 자식 프로세스가 죽었을 때
         * 부모 프로세스가 wait* 류 함수로 체크하지 않으면 좀비
         * 프로세스로 처리되어 남아 있는다.) */
        for (; waitpid(0, 0, WNOHANG) > 0; )
            ;

        /* 새 연결을 받아 소켓 t를 열고, 들어 오는 주소를 a에 저장한다. */
        i = sizeof a;
        if ((t = accept(f, (void *) &a, &i)) < 0)
            continue;

        /* 자식 프로세스를 만든다. fork()는 현재 프로세스를 복제해서
         * 자식 프로세스를 만든 뒤, 자식에서는 0을, 부모에서는 자식의
         * 프로세스 번호(pid)를 반환한다. 일단 fork되면 부모에서는
         * 할 일이 없으므로 다시 루프를 시작한다. */
        if (fork())
            continue;

        /* 소켓으로부터 HTTP 헤더를 읽는다. 이 헤더는 다음과 같은 구조이다:
         *
         *     METHOD /path/to/file HTTP/#.#<CR><LF>
         *     Name1: Value1<CR><LF>
         *     Name2: Value2<CR><LF>
         *     ...
         *     <CR><LF>
         *
         * f는 지금까지 나타난 <LF>의 갯수로, 콜론이 나오면 리셋된다.
         * 첫 줄에서 f는 0이고, Name1, Name2 지점에서 f는 1이 되며,
         * Value1, Value2 지점에서 f는 다시 0이 되고, 마지막으로 빈 줄이
         * 끝나면 (데이터가 시작되기 직전) f는 2가 되어 루프가 끝난다.
         * (물론 f가 2라고 현재 줄이 빈줄이라는 얘기는 아니지만,
         *  이런 헤더는 HTTP에서 허용하지 않으므로 상관 없다.)
         */
        for (f = i = 0; f < 2 && i < Z; i++) {
            /* r에 문자를 1바이트씩 읽는다. */
            if (recv(t, r + i, 1, 0) <= 0)
                return 1;

            /* f를 새로 계산한다. */
            if (r[i] == '\n')
                f++;
            if (r[i] == ':')
                f = 0;

            /* f가 1일 경우, 헤더 이름을 대문자로 바꾸고 하이픈(-)은
             * 밑줄(_)로 바꿔 정규화한다. 2일 때도 적용되는 코드이지만
             * 이 경우 toupper는 아무 일도 하지 않는다. */
            if (f)
                r[i] = r[i] == '-' ? '_' : toupper(r[i]);
        }

        /* 버퍼가 차도록 읽었지만 아직 빈 줄을 발견하지 못 했을 경우,
         * 처리를 하지 않고 종료한다. */
        if (Z <= i)
            break;

        /* 다음의 처리를 위해 버퍼를 널 문자로 끝나도록 바꾼다. */
        r[i] = 0;

        /* 읽은 문자열이 "GET"이나, "HEAD"나 "POST"로 시작하지 않으면
         * 501 Unsupported 에러를 내고 종료한다.
         * 조건식은 이 메소드 이름의 한 글자만을 비교하는데 이는
         * HTTP 메소드 이름들이 제한되어 있기 때문에 가능하다. */
        if (*r != 'G' && *r != 'H' && r[2] != 'S') {
            X(t, "501 Unsupported", 0);
            break;
        }

        /* 읽은 버퍼에서 "Host:"라는 문자열(대소문자 구분 안 함)을 찾아서,
         * 그 뒤에 나오는 문자열(호스트 이름)을 l에 저장한다. */
        *l = 0; /* l을 빈 문자열로 초기화 */
        if ((v = strstr(r, "HOST:")))
            /* sscanf는 생각보다(?) 많은 기능을 가지고 있다.
             * 여기 나오는 포맷 문자열에서, 맨 처음의 공백은 하나 또는
             * 그 이상의 공백에 대응되며, %<숫자>[<문자>]는 최대
             * <숫자> 갯수만큼 <문자>에 포함되는 문자열을 읽는다.
             * 이 경우 <문자>가 ^로 시작하므로 나머지 문자들에
             * "포함되지 않는" 문자열을 읽게 된다. */
            sscanf(v+5, " %255[^:\r\n]", l);

        /* DOCUMENT_ROOT 환경 변수에 "<루트경로>/<호스트이름>" 꼴의
         * 문자열을 집어 넣는다. */
        snprintf(w, Z, "DOCUMENT_ROOT" "=%s/%s", getenv("DOCUMENT_ROOT"), l);
        putenv(w);

        /* 메소드 이름 뒤에 나오는 URL에 대한 포인터를 u에 넣는다.
         * 이는 여기서 지원하는 메소드 이름이 GET 빼고 모두 4바이트이며,
         * 그 뒤에는 항상 하나의 공백만 나와야 하기 때문에 가능하다.
         * (아래 코드는 대략 u = r + (*r == 'G' ? 4 : 5);와 같다.) */
        u = r + 4 + (*r != 'G');

        /* URL이 /로 시작하거나, "../"를 포함하고 있으면 실제 파일이 있는
         * 경로보다 더 아랫쪽, 예를 들어 /etc/passwd 같은 중요한 파일을
         * 참조할 수 있으므로 파일이 없다고 보여 주고 끝낸다. */
        if (*u != '/' || strstr(u, "../")) {
            X(t, m, 0);
            break;
        }

        /* URL 뒤에 공백과 다른 문자열이 따라 오므로, URL만 접근하기 위해
         * URL 뒤에 나오는 첫번째 공백을 널문자로 바꾼다. */
        u[i = strcspn(u, " ")] = 0;

        /* o에 방금 전에 바꿨던 그 공백 뒤에 나오는 문자열의 포인터를
         * 저장한다. (HTTP 헤더의 경우, "HTTP/1.1\r\n..." 꼴) */
        o = u + i + 1;

        /* URL에서 물음표(?)로 시작하는 쿼리 문자열을 분리한다. */
        i = strcspn(u, "?");
        if (u[i]) { /* 물음표가 URL에 나타난다면, */
            /* 물음표 다음에 나오는 문자열을 QUERY_STRING 환경 변수에
             * 저장하고, 물음표는 널문자로 바꿔서 u가 쿼리 문자열을
             * 포함하지 않도록 바꾼다. */
            snprintf(q, Z, "QUERY_STRING=%s", u+i+1);
            putenv(q);
            u[i] = 0;
        }

        /* 쿼리 문자열을 제거한 URL을 앞에 DOCUMENT_ROOT 환경 변수의 값을
         * 붙여 SCRIPT_FILENAME 환경 변수에 집어 넣도록 준비한다.
         * (아직 putenv는 안 했으므로 설정은 안 된다)
         * 이전에 w는 "DOCUMENT_ROOT=..." 꼴의 문자열로 초기화되었으므로,
         * 앞의 14바이트를 뗀 나머지 내용이 DOCUMENT_ROOT에 들어 간
         * 문자열이 된다. */
        snprintf(l, Z, "SCRIPT_FILENAME=%s%s", w+14, u);

        /* 아까 전에 설정한 문자열에서 첫 16바이트를 뺀
         * SCRIPT_FILENAME에 들어 갈 파일 이름의 포인터를 v에 넣는다. */
        v = l+16;

        /* SCRIPT_FILENAME의 파일이 존재하지 않으면 404 Not Found를 반환한다.
         * 존재한다면 g 변수에는 파일에 대한 정보(크기 등)가 저장된다. */
        if (stat(v, &g) < 0) {
            X(t, m, 0);
            break;
        }

        /* 만약 해당 파일이 디렉토리라면, 디렉토리에서 "index."로 시작하는
         * 파일 이름을 찾아 v 뒤에 추가한다. (l도 함께 바뀐다.) */
        if (S_ISDIR(g.st_mode)) {
            struct dirent *d;
            DIR *D = opendir(v);

            /* 디렉토리에 있는 파일 이름을 하나씩 읽는다. */
            for (; (d = readdir(D)); ) {
                /* 첫 6바이트가 "index."이면, */
                if (!strncmp(d->d_name, "index.", 6)) {
                    /* 파일 이름을 v 뒤에 붙인 뒤 stat을 다시
                     * 실행해서 파일의 정보를 다시 얻는다. */
                    strncat(v, d->d_name, Z-16);
                    stat(v, &g);
                    break;
                }
            }
            closedir(D);

            /* 만약 디렉토리를 모두 읽었는데 "index."로 시작하는
             * 파일을 찾지 못 했다면 404 Not found를 반환한다. */
            if (d == 0) {
                X(t, m, 0);
                break;
            }
        }

        /* HEAD 메소드가 아니라면, */
        if (*r != 'H') {
            /* 파일이 실행 가능한지 체크한다. */
            if (g.st_mode & 0111) {
                /* 실행 가능하면 지금까지 유지하고 있던
                 * SCRIPT_FILENAME 환경 변수를 실제로 설정한다. */
                putenv(l);

                /* REQUEST_METHOD와 REMOTE_ADDR을 설정한다.
                 * getenv가 반환한 값은 일반적으로 프로그램이 바꾸면
                 * 안 되지만, 대부분의 환경에서는 환경 변수의 목록을
                 * environ과 같은 형태로 유지하고 있기 때문에 변경이
                 * 가능한 경우가 많다. 따라서 이 코드는 putenv와
                 * 사실상 같은 역할을 한다. */
                sscanf(r, "%4s", getenv("REQUEST_METHOD"));
                sscanf(inet_ntoa(a.sin_addr), "%15s", getenv("REMOTE_ADDR"));

                /* Cookie, Content-Length, Content-Type 헤더를
                 * (지정된 포맷으로) 버퍼에서 읽고 각각 대응되는
                 * 환경 변수에 저장한다.
                 * 아래에서 %*[...]는 %[...]와 같으나 해당하는
                 * 문자열을 저장하지 않는다. */
                A("HTTP_COOKIE", "%*[ \t]%80[^\r\n]");
                A("CONTENT_LENGTH", "%20s");
                A("CONTENT_TYPE", "%60s");

                /* CGI 프로그램은 표준 입출력을 통해 클라이언트와
                 * 소통한다. 따라서 소켓을 표준 입력(0) 및 출력(1)에
                 * 해당하는 파일 서술자로 복사한다. */
                dup2(t, 0);
                dup2(t, 1);

                /* 현재 프로세스의 파일 서술자와 환경 변수 목록을
                 * 물려 받아 해당 CGI 프로그램을 실행한다.
                 * 프로그램이 비정상적으로 끝났거나 0이 아닌 결과를
                 * 반환하면 500 에러를 반환한다. */
                if (system(v))
                    X(t, e, 0);

                break;
            }

            /* 해당 파일은 일반 파일이다. 파일을 읽기 전용 모드로
             * 열고 파일 서술자를 f에 저장한다. */
            if ((f = open(v, O_RDONLY)) < 0) {
                X(t, e, 0);
                break;
            }
        }

        /* 클라이언트에게 200 OK을 전달하고, 출력할 파일의 크기를
         * 함께 돌려 준다. */
        X(t, "200 OK", g.st_size);

        /* HEAD 메소드가 아닌 경우, */
        if (*r != 'H') {
            /* 앞에서 열었던 파일 서술자로부터 일정 크기씩 읽어서
             * 클라이언트에게 보낸다. */
            for (; 0 < (i = read(f, r, Z)); )
                send(t, r, i, 0);
        }

        break;
    }

    /* (부모 프로세스는 절대로 루프에서 빠져 나오지 않는다. 따라서 아래
     *  코드는 항상 자식에게만 적용된다.) */

    /* 클라이언트에게 더 이상의 출력이 없음을 알린다. */
    shutdown(t, SHUT_WR);

    /* 이 시점에서 열려 있던 다른 파일 서술자들은 자동으로 운영체제에 의해
     * 닫힌다. 따라서 close를 굳이 할 필요가 없다. */
    return 0;
}

눈여겨 볼 점

물론 이 코드에 눈여겨 볼 게 없는 건 아닙니다. 저자는 코드를 적절히 줄이기 위해서 C 표준 라이브러리의 함수를 자주 오용(?)했습니다. 이런 함수는 세 개가 있습니다.

또 하나 중요한 것은 이 프로그램은 의도적으로 HTTP/1.0 응답을 보낸다는 것입니다. 왜냐하면 HTTP/1.1에서는 keep-alive라 하여 한 연결에서 여러 번 요청/응답을 할 수 있도록 하는 기능을 지원하는데, 이것 때문에 응답에 Connection 헤더가 추가되어 더 요청을 할 수 있는지 없는지를 나타내도록 되어 있습니다. 하지만 이 프로그램은 Connection: close와 같은 헤더를 출력할 코드를 없애기 위해 일부러 HTTP/1.0을 사용해서 응답합니다.

따라서... 이 코드는 의도적으로 난해하게 만든 게 아니라 코드를 줄이려다 보니 부득이하게(!) 난해하게 되었다고 보는 편이 맞겠습니다. 뭐 이 정도 기능을 가지고 있는 서버에 이 정도 길이면 나쁘지 않죠?


  1. HTTP에서 한 서버에 여러 개의 호스트 이름이 대응되었을 때 서로 다른 사이트가 나오도록 하는 기능입니다. 이를 위해 HTTP에는 사용할 호스트 이름을 지정하는 Host 헤더가 있습니다.

  2. C 표준에서는 getenv가 반환한 포인터가 "임시" 문자열을 가리킬 수도 있다고 되어 있습니다. 이 경우 환경 변수의 목록은 내부적으로 관리하고 getenv가 호출될 때 임시 버퍼에 복사해 주는 역할만 할 수도 있습니다.


Copyright © 1999–2009, Kang Seonghoon.