zoukankan      html  css  js  c++  java
  • 源码分析之tinyhttpd-0.1

    1. 简介:

    tinyhttpd是使用c语言开发的超轻量级http服务器,通过代码流程可以了解http服务器的基本处理流程,

    并且涉及了网络套接字,线程,父子进程,管道等等知识点;

    项目地址:http://sourceforge.net/projects/tinyhttpd/

    2. 流程介绍:

    (1) 服务器启动,等待客户端请求到来;

    (2) 客户端请求到来,创建新线程处理该请求;

    (3) 读取httpHeader中的method,截取url,其中GET方法需要记录url问号之后的参数串;

    (4) 根据url构造完整路径,如果是/结尾,则指定为该目录下的index.html;

    (5) 获取文件信息,如果找不到文件,返回404,找到文件则判断文件权限;

    (6) 如果是GET请求并且没有参数,或者文件不可执行,则直接将文件内容构造http信息返回给客户端;

    (7) 如果是GET带参数,POST,文件可执行,则执行CGI;

    (8) GET请求略过httpHeader,POST方法需要记录httpHeader中的Content-Length:xx;

    (9) 创建管道用于父子进程通信,fork产生子进程;

    (10) 子进程设置环境变量,将标准输入和输出与管道相连,并且通过exec执行CGI;

    (11) 如果是POST,父进程将读到post内容发送给子进程,并且接收子进程的输出,输出给客户端;

    3. 管道说明:

    4. 代码注释:

      1 /* J. David's webserver */
      2 /* This is a simple webserver.
      3  * Created November 1999 by J. David Blackstone.
      4  * CSE 4344 (Network concepts), Prof. Zeigler
      5  * University of Texas at Arlington
      6  */
      7 /* This program compiles for Sparc Solaris 2.6.
      8  * To compile for Linux:
      9  *  1) Comment out the #include <pthread.h> line.
     10  *  2) Comment out the line that defines the variable newthread.
     11  *  3) Comment out the two lines that run pthread_create().
     12  *  4) Uncomment the line that runs accept_request().
     13  *  5) Remove -lsocket from the Makefile.
     14  */
     15 #include <stdio.h>
     16 #include <sys/socket.h>
     17 #include <sys/types.h>
     18 #include <netinet/in.h>
     19 #include <arpa/inet.h>
     20 #include <unistd.h>
     21 #include <ctype.h>
     22 #include <strings.h>
     23 #include <string.h>
     24 #include <sys/stat.h>
     25 #include <pthread.h>
     26 #include <sys/wait.h>
     27 #include <stdlib.h>
     28 
     29 #define ISspace(x) isspace((int)(x))
     30 
     31 #define SERVER_STRING "Server: jdbhttpd/0.1.0
    "
     32 
     33 void accept_request(int);
     34 void bad_request(int);
     35 void cat(int, FILE *);
     36 void cannot_execute(int);
     37 void error_die(const char *);
     38 void execute_cgi(int, const char *, const char *, const char *);
     39 int get_line(int, char *, int);
     40 void headers(int, const char *);
     41 void not_found(int);
     42 void serve_file(int, const char *);
     43 int startup(u_short *);
     44 void unimplemented(int);
     45 
     46 /**********************************************************************/
     47 /* A request has caused a call to accept() on the server port to
     48  * return.  Process the request appropriately.
     49  * Parameters: the socket connected to the client */
     50 /**********************************************************************/
     51 void accept_request(int client)
     52 {
     53     char buf[1024];
     54     int numchars;
     55     char method[255];
     56     char url[255];
     57     char path[512];
     58     size_t i, j;
     59     struct stat st;
     60     int cgi = 0;      /* becomes true if server decides this is a CGI
     61                * program */
     62     char *query_string = NULL;
     63 
     64     //读取第一行数据
     65     numchars = get_line(client, buf, sizeof(buf));
     66     i = 0; j = 0;
     67     //读取http的头部method字段,读到空白为止
     68     while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
     69     {
     70         method[i] = buf[j];
     71         i++; j++;
     72     }
     73     method[i] = '';
     74 
     75     //只支持GET和POST请求,其他请求方式返回未实现
     76     if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
     77     {
     78         unimplemented(client);
     79         return;
     80     }
     81     //如果是POST请求,设置cgi标志为1
     82     if (strcasecmp(method, "POST") == 0)
     83         cgi = 1;
     84 
     85     i = 0;
     86     //跳过空白字符
     87     while (ISspace(buf[j]) && (j < sizeof(buf)))
     88         j++;
     89     //读取url字串
     90     while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
     91     {
     92         url[i] = buf[j];
     93         i++; j++;
     94     }
     95     url[i] = '';
     96     //如果是GET请求,需要从url中解析参数
     97     if (strcasecmp(method, "GET") == 0)
     98     {
     99         query_string = url;
    100         //找到?位置
    101         while ((*query_string != '?') && (*query_string != ''))
    102             query_string++;
    103         //当前字符为?字符
    104         if (*query_string == '?')
    105         {
    106             cgi = 1; //标记cgi字段
    107             *query_string = ''; //将?替换成
    108             query_string++; //query_string指向get参数
    109         }
    110     }
    111     //连接url资源路径
    112     sprintf(path, "htdocs%s", url);
    113     //如果访问的是/结尾的目录,那么指定为目录下的index.html
    114     if (path[strlen(path) - 1] == '/')
    115         strcat(path, "index.html");
    116     //获取文件信息失败
    117     if (stat(path, &st) == -1) {
    118         //将header中的信息都丢弃
    119         while ((numchars > 0) && strcmp("
    ", buf))  /* read & discard headers */
    120             numchars = get_line(client, buf, sizeof(buf));
    121         //返回404
    122         not_found(client);
    123     }
    124     else
    125     {
    126         //如果访问的是目录,那么指定为目录下的index.html
    127         if ((st.st_mode & S_IFMT) == S_IFDIR)
    128             strcat(path, "/index.html");
    129         //如果具有可执行权限,标记cgi
    130         if ((st.st_mode & S_IXUSR) ||
    131                 (st.st_mode & S_IXGRP) ||
    132                 (st.st_mode & S_IXOTH)    )
    133             cgi = 1;
    134         //不需要cgi参与的文件直接进行服务
    135         if (!cgi)
    136             serve_file(client, path);
    137         //否则执行cgi
    138         else
    139             execute_cgi(client, path, method, query_string);
    140     }
    141 
    142     close(client);
    143 }
    144 
    145 /**********************************************************************/
    146 /* Inform the client that a request it has made has a problem.
    147  * Parameters: client socket */
    148 /**********************************************************************/
    149 void bad_request(int client)
    150 {
    151     char buf[1024];
    152     //发送BAD REQUEST提示信息到客户端
    153     sprintf(buf, "HTTP/1.0 400 BAD REQUEST
    ");
    154     send(client, buf, sizeof(buf), 0);
    155     sprintf(buf, "Content-type: text/html
    ");
    156     send(client, buf, sizeof(buf), 0);
    157     sprintf(buf, "
    ");
    158     send(client, buf, sizeof(buf), 0);
    159     sprintf(buf, "<P>Your browser sent a bad request, ");
    160     send(client, buf, sizeof(buf), 0);
    161     sprintf(buf, "such as a POST without a Content-Length.
    ");
    162     send(client, buf, sizeof(buf), 0);
    163 }
    164 
    165 /**********************************************************************/
    166 /* Put the entire contents of a file out on a socket.  This function
    167  * is named after the UNIX "cat" command, because it might have been
    168  * easier just to do something like pipe, fork, and exec("cat").
    169  * Parameters: the client socket descriptor
    170  *             FILE pointer for the file to cat */
    171 /**********************************************************************/
    172 void cat(int client, FILE *resource)
    173 {
    174     char buf[1024];
    175     //循环读取并发送文件内容
    176     fgets(buf, sizeof(buf), resource);
    177     while (!feof(resource))
    178     {
    179         send(client, buf, strlen(buf), 0);
    180         fgets(buf, sizeof(buf), resource);
    181     }
    182 }
    183 
    184 /**********************************************************************/
    185 /* Inform the client that a CGI script could not be executed.
    186  * Parameter: the client socket descriptor. */
    187 /**********************************************************************/
    188 void cannot_execute(int client)
    189 {
    190     char buf[1024];
    191     //发送500服务器内部错误到客户端
    192     sprintf(buf, "HTTP/1.0 500 Internal Server Error
    ");
    193     send(client, buf, strlen(buf), 0);
    194     sprintf(buf, "Content-type: text/html
    ");
    195     send(client, buf, strlen(buf), 0);
    196     sprintf(buf, "
    ");
    197     send(client, buf, strlen(buf), 0);
    198     sprintf(buf, "<P>Error prohibited CGI execution.
    ");
    199     send(client, buf, strlen(buf), 0);
    200 }
    201 
    202 /**********************************************************************/
    203 /* Print out an error message with perror() (for system errors; based
    204  * on value of errno, which indicates system call errors) and exit the
    205  * program indicating an error. */
    206 /**********************************************************************/
    207 void error_die(const char *sc)
    208 {
    209     //打印错误信息并退出
    210     perror(sc);
    211     exit(1);
    212 }
    213 
    214 /**********************************************************************/
    215 /* Execute a CGI script.  Will need to set environment variables as
    216  * appropriate.
    217  * Parameters: client socket descriptor
    218  *             path to the CGI script */
    219 /**********************************************************************/
    220 void execute_cgi(int client, const char *path,
    221         const char *method, const char *query_string)
    222 {
    223     char buf[1024];
    224     int cgi_output[2];
    225     int cgi_input[2];
    226     pid_t pid;
    227     int status;
    228     int i;
    229     char c;
    230     int numchars = 1;
    231     int content_length = -1;
    232 
    233     buf[0] = 'A'; buf[1] = '';
    234     //如果是GET请求则读取并丢掉头部信息
    235     if (strcasecmp(method, "GET") == 0)
    236         while ((numchars > 0) && strcmp("
    ", buf))  /* read & discard headers */
    237             numchars = get_line(client, buf, sizeof(buf));
    238     //如果是POST请求
    239     else    /* POST */
    240     {
    241         //读取一行数据
    242         numchars = get_line(client, buf, sizeof(buf));
    243         while ((numchars > 0) && strcmp("
    ", buf))
    244         {
    245             //截取Content-Length:字段
    246             buf[15] = '';
    247             //如果找到该字段,将该字段后面的字串转成整数长度
    248             if (strcasecmp(buf, "Content-Length:") == 0)
    249                 content_length = atoi(&(buf[16]));
    250             //读取头部内容
    251             numchars = get_line(client, buf, sizeof(buf));
    252         }
    253         //没有找到Content-Length,发送bad request
    254         if (content_length == -1) {
    255             bad_request(client);
    256             return;
    257         }
    258     }
    259     //发送http200头
    260     sprintf(buf, "HTTP/1.0 200 OK
    ");
    261     send(client, buf, strlen(buf), 0);
    262     //创建输出管道,构造父子进程通信
    263     if (pipe(cgi_output) < 0) {
    264         cannot_execute(client);
    265         return;
    266     }
    267     //创建输入管道,构造父子进程通信
    268     if (pipe(cgi_input) < 0) {
    269         cannot_execute(client);
    270         return;
    271     }
    272     //创建子进程
    273     if ( (pid = fork()) < 0 ) {
    274         cannot_execute(client);
    275         return;
    276     }
    277     //子进程执行CGI脚本
    278     if (pid == 0)  /* child: CGI script */
    279     {
    280         char meth_env[255];
    281         char query_env[255];
    282         char length_env[255];
    283 
    284         //子进程的标准输入输出与管道对接
    285         dup2(cgi_output[1], 1); //将标准输出重定向到cgi输出管道的写端
    286         dup2(cgi_input[0], 0); //将标准输入重定向到cgi输入管道的读端
    287 
    288         close(cgi_output[0]); //关闭cgi输出管道的读端
    289         close(cgi_input[1]); //关闭cgi输入管道的写端
    290         //设置method环境变量
    291         sprintf(meth_env, "REQUEST_METHOD=%s", method);
    292         putenv(meth_env);
    293         //如果是GET方式设置请求参数环境变量
    294         if (strcasecmp(method, "GET") == 0) {
    295             sprintf(query_env, "QUERY_STRING=%s", query_string);
    296             putenv(query_env);
    297         }
    298         //如果是POST方式设置内容长度环境变量
    299         else {   /* POST */
    300             sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
    301             putenv(length_env);
    302         }
    303         //执行CGI
    304         execl(path, path, NULL);
    305         exit(0);
    306     } else {    /* parent */
    307         close(cgi_output[1]); //关闭cgi输出管道的写端
    308         close(cgi_input[0]); //关闭cgi输入管道的读端
    309         //如果是POST请求,循环读取post内容,并且输入到cgi子进程
    310         if (strcasecmp(method, "POST") == 0)
    311             for (i = 0; i < content_length; i++) {
    312                 recv(client, &c, 1, 0);
    313                 write(cgi_input[1], &c, 1);
    314             }
    315         //从cgi中循环读取输出内容,发送到客户端
    316         while (read(cgi_output[0], &c, 1) > 0)
    317             send(client, &c, 1, 0);
    318 
    319         //关闭管道
    320         close(cgi_output[0]);
    321         close(cgi_input[1]);
    322         //等待子进程结束
    323         waitpid(pid, &status, 0);
    324     }
    325 }
    326 
    327 /**********************************************************************/
    328 /* Get a line from a socket, whether the line ends in a newline,
    329  * carriage return, or a CRLF combination.  Terminates the string read
    330  * with a null character.  If no newline indicator is found before the
    331  * end of the buffer, the string is terminated with a null.  If any of
    332  * the above three line terminators is read, the last character of the
    333  * string will be a linefeed and the string will be terminated with a
    334  * null character.
    335  * Parameters: the socket descriptor
    336  *             the buffer to save the data in
    337  *             the size of the buffer
    338  * Returns: the number of bytes stored (excluding null) */
    339 /**********************************************************************/
    340 int get_line(int sock, char *buf, int size)
    341 {
    342     int i = 0;
    343     char c = '';
    344     int n;
    345     //接收
    结束的一行数据或接收满缓冲区
    346     while ((i < size - 1) && (c != '
    '))
    347     {
    348         //接收一个字节
    349         n = recv(sock, &c, 1, 0);
    350         /* DEBUG printf("%02X
    ", c); */
    351         if (n > 0)
    352         {
    353             //如果接收到了
    符号
    354             if (c == '
    ')
    355             {
    356                 //将下一个字字符预取出来,注意MSG_PEEK本地接收窗口不滑动,下次读取仍然可以读取到该字符
    357                 n = recv(sock, &c, 1, MSG_PEEK);
    358                 /* DEBUG printf("%02X
    ", c); */
    359                 //如果下一个字符是
    的话,那么接收这个字符
    360                 if ((n > 0) && (c == '
    '))
    361                     recv(sock, &c, 1, 0);
    362                 //不是
    的话,那么将
    替换成
    
    363                 else
    364                     c = '
    ';
    365             }
    366             //字符存入buf,继续读取
    367             buf[i] = c;
    368             i++;
    369         }
    370         else
    371             c = '
    ';
    372     }
    373     //设置buf字符串结束符
    374     buf[i] = '';
    375 
    376     return(i);
    377 }
    378 
    379 /**********************************************************************/
    380 /* Return the informational HTTP headers about a file. */
    381 /* Parameters: the socket to print the headers on
    382  *             the name of the file */
    383 /**********************************************************************/
    384 void headers(int client, const char *filename)
    385 {
    386     char buf[1024];
    387     (void)filename;  /* could use filename to determine file type */
    388     //发送http头 http码 服务器信息 内容类型等头部信息
    389     strcpy(buf, "HTTP/1.0 200 OK
    ");
    390     send(client, buf, strlen(buf), 0);
    391     strcpy(buf, SERVER_STRING);
    392     send(client, buf, strlen(buf), 0);
    393     sprintf(buf, "Content-Type: text/html
    ");
    394     send(client, buf, strlen(buf), 0);
    395     strcpy(buf, "
    ");
    396     send(client, buf, strlen(buf), 0);
    397 }
    398 
    399 /**********************************************************************/
    400 /* Give a client a 404 not found status message. */
    401 /**********************************************************************/
    402 void not_found(int client)
    403 {
    404     char buf[1024];
    405     //发送http头 http码 服务器信息 内容类型等头部信息 html提示信息
    406     sprintf(buf, "HTTP/1.0 404 NOT FOUND
    ");
    407     send(client, buf, strlen(buf), 0);
    408     sprintf(buf, SERVER_STRING);
    409     send(client, buf, strlen(buf), 0);
    410     sprintf(buf, "Content-Type: text/html
    ");
    411     send(client, buf, strlen(buf), 0);
    412     sprintf(buf, "
    ");
    413     send(client, buf, strlen(buf), 0);
    414     sprintf(buf, "<HTML><TITLE>Not Found</TITLE>
    ");
    415     send(client, buf, strlen(buf), 0);
    416     sprintf(buf, "<BODY><P>The server could not fulfill
    ");
    417     send(client, buf, strlen(buf), 0);
    418     sprintf(buf, "your request because the resource specified
    ");
    419     send(client, buf, strlen(buf), 0);
    420     sprintf(buf, "is unavailable or nonexistent.
    ");
    421     send(client, buf, strlen(buf), 0);
    422     sprintf(buf, "</BODY></HTML>
    ");
    423     send(client, buf, strlen(buf), 0);
    424 }
    425 
    426 /**********************************************************************/
    427 /* Send a regular file to the client.  Use headers, and report
    428  * errors to client if they occur.
    429  * Parameters: a pointer to a file structure produced from the socket
    430  *              file descriptor
    431  *             the name of the file to serve */
    432 /**********************************************************************/
    433 void serve_file(int client, const char *filename)
    434 {
    435     FILE *resource = NULL;
    436     int numchars = 1;
    437     char buf[1024];
    438 
    439     //读取丢弃所有头部信息
    440     buf[0] = 'A'; buf[1] = '';
    441     while ((numchars > 0) && strcmp("
    ", buf))  /* read & discard headers */
    442         numchars = get_line(client, buf, sizeof(buf));
    443 
    444     //打开资源文件
    445     resource = fopen(filename, "r");
    446     //打开失败,发送404
    447     if (resource == NULL)
    448         not_found(client);
    449     else
    450     {
    451         headers(client, filename); //发送http头
    452         cat(client, resource); //发送资源文件内容
    453     }
    454     //关闭资源文件
    455     fclose(resource);
    456 }
    457 
    458 /**********************************************************************/
    459 /* This function starts the process of listening for web connections
    460  * on a specified port.  If the port is 0, then dynamically allocate a
    461  * port and modify the original port variable to reflect the actual
    462  * port.
    463  * Parameters: pointer to variable containing the port to connect on
    464  * Returns: the socket */
    465 /**********************************************************************/
    466 int startup(u_short *port)
    467 {
    468     int httpd = 0;
    469     struct sockaddr_in name;
    470     //创建tcp socket
    471     httpd = socket(PF_INET, SOCK_STREAM, 0);
    472     if (httpd == -1)
    473         error_die("socket");
    474     memset(&name, 0, sizeof(name));
    475     //设置sockaddr地址结构
    476     name.sin_family = AF_INET;
    477     name.sin_port = htons(*port);
    478     name.sin_addr.s_addr = htonl(INADDR_ANY);
    479     //绑定到本地地址port指定端口
    480     if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
    481         error_die("bind");
    482     //如果没有指定端口,则由系统指定,此处或得到系统指定的端口
    483     if (*port == 0)  /* if dynamically allocating a port */
    484     {
    485         int namelen = sizeof(name);
    486         if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
    487             error_die("getsockname");
    488         *port = ntohs(name.sin_port);
    489     }
    490     //服务器开始监听
    491     if (listen(httpd, 5) < 0)
    492         error_die("listen");
    493     return(httpd);
    494 }
    495 
    496 /**********************************************************************/
    497 /* Inform the client that the requested web method has not been
    498  * implemented.
    499  * Parameter: the client socket */
    500 /**********************************************************************/
    501 void unimplemented(int client)
    502 {
    503     char buf[1024];
    504     //发送未实现的请求方法和提示消息给客户端
    505     sprintf(buf, "HTTP/1.0 501 Method Not Implemented
    ");
    506     send(client, buf, strlen(buf), 0);
    507     sprintf(buf, SERVER_STRING);
    508     send(client, buf, strlen(buf), 0);
    509     sprintf(buf, "Content-Type: text/html
    ");
    510     send(client, buf, strlen(buf), 0);
    511     sprintf(buf, "
    ");
    512     send(client, buf, strlen(buf), 0);
    513     sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented
    ");
    514     send(client, buf, strlen(buf), 0);
    515     sprintf(buf, "</TITLE></HEAD>
    ");
    516     send(client, buf, strlen(buf), 0);
    517     sprintf(buf, "<BODY><P>HTTP request method not supported.
    ");
    518     send(client, buf, strlen(buf), 0);
    519     sprintf(buf, "</BODY></HTML>
    ");
    520     send(client, buf, strlen(buf), 0);
    521 }
    522 
    523 /**********************************************************************/
    524 
    525 int main(void)
    526 {
    527     int server_sock = -1;
    528     u_short port = 0;
    529     int client_sock = -1;
    530     struct sockaddr_in client_name;
    531     int client_name_len = sizeof(client_name);
    532     pthread_t newthread;
    533 
    534     server_sock = startup(&port);
    535     printf("httpd running on port %d
    ", port);
    536 
    537     while (1)
    538     {
    539         //等待客户端连接到来
    540         client_sock = accept(server_sock,
    541                 (struct sockaddr *)&client_name,
    542                 &client_name_len);
    543         if (client_sock == -1)
    544             error_die("accept");
    545         /* accept_request(client_sock); */
    546         //开启一个新线程处理客户端请求
    547         if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
    548             perror("pthread_create");
    549     }
    550     //关闭服务器
    551     close(server_sock);
    552 
    553     return(0);
    554 }
  • 相关阅读:
    MySQL学习之EXPLAIN执行计划详解及最佳实践
    MySQL学习之Mysql锁&事务隔离级别
    Mybatis学习之核心原理代码详解
    Mybatis学习之工作流程代码详解
    Mybatis学习之核心配置详解
    Mybatis学习之Mybatis Demo入门使用
    缓存穿透解决方案之布隆过滤器(Bloom Filter)原理及Guava中的实现
    Zookeeper学习之Jute序列化以及通信协议详解
    Zookeeper学习之Zab一致性协议
    Zookeeper学习之ZooKeeper源码分析
  • 原文地址:https://www.cnblogs.com/wanpengcoder/p/5304521.html
Copyright © 2011-2022 走看看