即席httpdを書いてみる

libtaskの例として、httpdを書いてみた。200行ぐらいだけど、コアの部分はこんな感じ。fdread/fdwriteはノンブロッキングI/Oなんだけど、普通のシングルスレッドプログラムのようにselectループを書く必要がない。どうなっているかというと、libtask内部でスケジューラが存在しているのだ。タスクがI/O待ちが必要になると、ライブラリ内で明示的にスケジューラに制御を移し、次のタスクに切り替える。

void
httpdtask(void *arg)
{
  int rfd, fd, n;
  int status;
  char *msg;
  struct stat stat;
  char fullpath[PATH_MAX], buf[4096];

  rfd = (int)arg;

  status = parserequest(rfd, fullpath, &stat);

  switch (status) {
  case HTTP_OK:
    fd = open(fullpath, O_RDONLY);
    if (fd < 0) {
      perror("open");
      goto end;
    }

    sendheader(rfd, status, strstatus(status), &stat);
    for (;;) {
      n = fdread(fd, buf, sizeof(buf));
      if (n <= 0)
	break;

      n = fdwrite(rfd, buf, n);
      if (n < 0)
	break;
    }
    close(fd);
    break;

  default:
    msg = strstatus(status);
    sendheader(rfd, status, msg, 0);
    n = snprintf(buf, BUFSIZ,
		 "<html><title>%d %s</title>"
		 "<body><h1>%d %s</h1></body></html>",
		 status, msg, status, msg);
    fdwrite(rfd, buf, n);
    break;
  }

  end:
  close(rfd);
}

void
taskmain(int argc, char **argv)
{
  int cfd, fd;
  int rport;
  char remote[16];

  if (argc != 3) {
    fprintf(stderr, "usage: httpd documentroot port\n");
    exit(1);
  }

  documentroot = argv[1];
  port = atoi(argv[2]);

  fd = netannounce(TCP, 0, port);
  if (fd < 0) {
    fprintf(stderr, "cannot announce on tcp port %d: %s\n",
	    port, strerror(errno));
    exit(1);
  }

  fdnoblock(fd);
  for (;;) {
    cfd = netaccept(fd, remote, &rport);
    if (cfd < 0)
      break;

    fdnoblock(cfd);
    taskcreate(httpdtask, (void *)cfd, 32768);
  }
}