Tiny web


tiny webサーバのコードを記述するには、ネットワークプログラミングとシステムレベルのI/Oを理解する必要があります.まず、本のコードに基づいて、関連内容を順番に見てみましょう.
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);
  }

  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
  }
}
tiny.cのメインコード.まず,命令を実行するパラメータ(argc)が2つでない場合,使用方法を出力し,正しく使用したと非難する.正しいコマンドがある場合はlistenfdを生成します.Open listenfd関数を見てみましょう.
int Open_clientfd(char *hostname, char *port) {
    int rc;

    if ((rc = open_clientfd(hostname, port)) < 0) 
	unix_error("Open_clientfd error");
    return rc;
}
int open_listenfd(char *port) 
{
    struct addrinfo hints, *listp, *p;
    int listenfd, rc, optval=1;

    /* Get a list of potential server addresses */
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_socktype = SOCK_STREAM;             /* Accept connections */
    hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG; /* ... on any IP address */
    hints.ai_flags |= AI_NUMERICSERV;            /* ... using port number */
    if ((rc = getaddrinfo(NULL, port, &hints, &listp)) != 0) {
        fprintf(stderr, "getaddrinfo failed (port %s): %s\n", port, gai_strerror(rc));
        return -2;
    }

    /* Walk the list for one that we can bind to */
    for (p = listp; p; p = p->ai_next) {
        /* Create a socket descriptor */
        if ((listenfd = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) 
            continue;  /* Socket failed, try the next */

        /* Eliminates "Address already in use" error from bind */
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,    //line:netp:csapp:setsockopt
                   (const void *)&optval , sizeof(int));

        /* Bind the descriptor to the address */
        if (bind(listenfd, p->ai_addr, p->ai_addrlen) == 0)
            break; /* Success */
        if (close(listenfd) < 0) { /* Bind failed, try the next */
            fprintf(stderr, "open_listenfd close failed: %s\n", strerror(errno));
            return -1;
        }
    }


    /* Clean up */
    freeaddrinfo(listp);
    if (!p) /* No address worked */
        return -1;

    /* Make it a listening socket ready to accept connection requests */
    if (listen(listenfd, LISTENQ) < 0) {
        close(listenfd);
	return -1;
    }
    return listenfd;
}
関数の最初の字は大文字で、間違いと考えられ、関数で一度包みました.実際に実行されている小文字関数が表示されたら、まずこれまたはそれを設定し、getaddrinfo関数を実行します.その後listpから、socket関数とbind関数が次のアドレスに存在する場合に成功するかどうかを判断します.その後有効なlistenfdが生成されると、その値が返されるか、エラーコードが返されます.
その後while文で接続要求のconnfdを受信関数として作成し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_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);
 if(strcasecmp(method, "GET")){
   clienterror(fd, method, "501", "Not implemented", "Tiny does not implement this method");
   return;
 }
 read_requesthdrs(&rio);

 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);
 }
}
Doit関数におけるRobust I/Oに関する関数(Rio...)10章のCSAPPを参照して、read requestdrs関数を実行する操作と見なしてください.このコードは、GET以外の要求を処理できないようにエラーを設定します.
その後、parse uri関数を使用してuriを解析し、ファイルが見つからない場合は404 errorを返し、ファイルが静的ファイルか動的ファイルかに基づいて操作します.権限があるかどうかを判断し、serve static関数、serve dynamic関数をそれぞれ実行します.
void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg){
  char buf[MAXLINE], body[MAXBUF];

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

  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));
}
clineterror; エラー画面を制御します.
void read_requesthdrs(rio_t *rp){
  char buf[MAXLINE];

  Rio_readlineb(rp, buf, MAXLINE);
  while(strcmp(buf, "\r\n")){
    Rio_readlineb(rp, buf, MAXLINE);
    printf("%s", buf);
  }
  return;
}
read_requesthdrs; rio t構造体の内容を読みます.
int parse_uri(char *uri, char *filename, char *cgiargs){
  char *ptr;

  if(!strstr(uri, "cgi-bin")){
    strcpy(cgiargs, "");
    strcpy(filename, ".");
    strcat(filename, uri);
    if(uri[strlen(uri)-1] == '/')
      strcat(filename, "home.html");
    return 1;
  }else{
    ptr = index(uri, '?');
    if(ptr){
      strcpy(cgiargs, ptr+1);
      *ptr = '\0';
    }else{
      strcpy(cgiargs, "");
    }
    strcpy(filename, ".");
    strcpy(filename, uri);
    return 0;
  }
}
parse uri関数では、パラメータとして受信したuriに「cgi-bin」が含まれているかどうかのため、静的ファイルと動的ファイルを分離します.ファイルを各ファイルタイプに適したfilenameに変更すると、静的ファイルは1を返し、動的ファイルは0を返します.
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, "%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);

  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);
}
//filename의 확장자로 filetype 가져오는 함수
void get_filetype(char *filename, char *filetype){
  if(strstr(filename, ".html"))
    strcpy(filetype, "text/html");
  else if(strtstr(filename, ".gif"))
    strcpy(filetype, "image/gif");
  else if(strtstr(filename, ".png"))
    strcpy(filetype, "image/png");
  else if(strtstr(filename, ".jpg"))
    strcpy(filetype, "image/jpeg");
  else
    strcpy(filetype, "text/plain");
}
応答ヘッダを出力し、ファイルを開いて読み取り前に使用し、src fdに識別子を割り当てます.メモリをsrc pにマッピングした後、開いているファイルを再閉じてメモリに書き込み、メモリに戻ります.
void serve_dynamic(int fd, char *filename, char *cgiargs){
  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));

  if(Fork() == 0){
    setenv("QUERY_STRING", cgiargs, 1);
    Dup2(fd, STDOUT_FILENO);
    Execve(filename, emptylist, environ);
  }
  Wait(NULL);
}
ダイナミックファイルの場合、分岐プロセスは、標準出力識別子をfd識別子値に再指定し、サブプロセスでファイルを実行します.
「コンピュータシステムのジョブの問題」を参照してください。
エージェント実施上の注意事項