500行的服务器程序,是不是已经迫不及待了!

脱掉外套

服务器的执行流程:

  • 首先使用startup()在端口port上监听网络连接
  • 然后进入循环,每次使用accept()接受一个请求
  • 对于每个请求,创建一个线程pthread_create()为其服务

脱掉毛线

对于具体的服务过程(accept_request()),首先它会解析请求的method,tinyhttpd只实现了POST和GET两个请求,如果请求其它method,它会给客户端程序返回一个服务尚未实现的消息(unimplemented());然后解析请求uri,如果是GET的method,取得它的query_string,如果是静态请求,使用serve_file()来为其服务,如果是动态请求,使用execute_cgi()来为其服务。

脱掉内衣

静态请求:打开文件,将文件中的数据传输给客户端程序。

void serve_file(int client, const char *filename){
    FILE *resource = fopen(filename, "r");
    headers(client, filename);
    cat(client, resource);
    fclose(resource);
}

void cat(int client, FILE *resource) {
    char buf[1024];

    fgets(buf, sizeof(buf), resource);
    while (!feof(resouce)) {
        send(client, buf, strlen(buf), 0);
        fgets(buf, sizeof(buf), resource);
    }
}

动态请求:使用了fork()和execl()组合,子进程去执行cgi程序,得到的输出通过管道cgi_output传送给父进程;如果method为POST,父进程会将读取到的数据通过管道cgi_input传送给子进程,粗看子进程好像没有任何调用读入的函数,其实读入过程发生在execl()之后,该进程会根据环境变量中设置的CONTENT_LENGTH知道需要读入的字节数。

void execuete_cgi(int client, const char *path, 
                    const char *method, const char *query_string)
{
    // POST
    while ((numchars > 0) && strcmp("\n", buf)){
        buf[15] = '\0';
        if (strcasecmp(buf, "Content-Length:") == 0)
            content_length = atoi(&(buf[16]));
        numchars = get_line(client, buf, sizeof(buf));
    }

    if (!(pid = fork())) {
        dup2(cgi_output[1], 1); // 子进程的输出重定向到cgi_output[1]
        dup2(cgi_input[0], 0);  // 输入重定向到cgi_input[0]
        close(cgi_output[0]);
        close(cgi_input[1]);

        snprintf(meth_env, "REQUEST_METHOD=%s", method);
        putenv(meth_env);
        if (!strcasecmp(method, "GET")) {
            sprintf(query_env, "QUERY_STRING=%s", query_string);
            putenv(query_env);
        } else {
            snprintf(length_env, "CONTENT_LENGTH=%d", content_length);
            putenv(length_env);
        }

        execl(path, path, NULL);
        exit(0);
    } else {
        close(cgi_output[1]);
        close(cgi_input[0]);
        if (!strcasecmp(method, "POST"))
            for (i = 0; i < content_length; i++) {
                recv(client, &c, 1, 0);
                write(cgi_input[1], &c, 1);
            }
        while (read(cgi_output[0], &c, 1) > 0) // 子进程执行execl()的输出
            send(client, &c, 1, 0);

        close(cgi_output[0]);
        close(cgi_input[1]);
        waitpid(pid, &status, 0);
    }

}

这大概是整个程序中最精妙的部分了,到此一个美女,不,一段代码一丝不挂的站在面前。看代码的过程很舒畅,只是要吐槽下代码中的缩进,缩进只用一个空格简直是不能忍受。

过两天我也要写一个tiny httpd服务器!

参考资料

Source code of Tinyhttpd

Comments

Powered by Pelican. Booler's Adventure © 不贰 2014-2016