zoukankan      html  css  js  c++  java
  • tinyhttpd ------ C 语言实现最简单的 HTTP 服务器

    工作流程:

    1>服务器启动,在指定端口或随机选取端口绑定httpd服务。

    2>收到一个http请求时(其实就是listen端口accept的时候),派生一个线程运行accept_request函数。

    3>取出http请求中method(getpost)url,对于get方法,如果有携带参数,则query_string指针指向url?后面的get参数。

    4>格式化urlpath数组,表示浏览器请求的文件路径,在tinyhttpd中服务器文件是在htdocs文件夹下。当url/结尾,或者url是个目录,则默认在path中加上index.thml,表示访问主页。

    5>如果文件路径合法,对于无参数的get请求,直接输出服务器文件到浏览器,即用http格式写到套接字上,跳到(10)。其他情况(带参数getpost方法,url为科执行文件),则调用execute_cgi函数执行cgi脚本。

    6>读取整个http请求并丢弃,如果是post则找出content-length,把http状态码200写到套接字里面。

    7>建立两个管道,cgi_inputcgi_output,fork一个子进程。

    8>在子进程中,把stdout重定向到cgi_output的写入端,把stdin重定向到cgi_input的读取端,关闭cgi_input的写入端和cgi_output的读取端,是指request_method的环境变量,get的话设置query_string的环境变量,post的话设置content-length的环境变量,这些环境变量都是为了给cgi脚本调用,接着用execl运行cgi程序。

    9>在父进程中,关闭cgi_input的读取端和cgi_output的写入端,如果post的话,把post数据写入到cgo_input,已被重定向到stdin读取cgi_output的管道输出到客户端,等待子进程结束。

    10>关闭与浏览器的链接,完成一次http请求与回应,因为http是无连接的。

      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 //处理从套接字上监听到的一个HTTP请求,在这里可以很大一部分的体现服务器处理请求的流程
     35 void bad_request(int);
     36 //返回给客户端这是个错误请求,HTTP状态码是400 BAD REQUEST
     37 void cat(int, FILE *);
     38 //读取服务器上某个文件写到socket套接字
     39 void cannot_execute(int);
     40 //主要执行在处理cgi程序的处理,也是个主要函数
     41 void error_die(const char *);
     42 //把错误信息写到perror并退出
     43 void execute_cgi(int, const char *, const char *, const char *);
     44 //运行cgi程序的处理,也是哥主函数
     45 int get_line(int, char *, int);
     46 //读取套接字的一行,把回车换行等情况都统一为换行符结束
     47 void headers(int, const char *);
     48 //把HTTP相应头写到套接字
     49 void not_found(int);
     50 //主要处理找不到请求的文件时的情况
     51 void serve_file(int, const char *);
     52 //调用cat把服务器文件返回给浏览器
     53 int startup(u_short *);
     54 //初始化httpd服务,包括建立套接字,绑定端口,进行监听等
     55 void unimplemented(int);
     56 //返回给浏览器表示接收到的http请求所用的method不被支持
     57 
     58 /**********************************************************************/
     59 /* A request has caused a call to accept() on the server port to
     60  * return.  Process the request appropriately.
     61  * Parameters: the socket connected to the client */
     62 /**********************************************************************/
     63 void accept_request(int client)
     64 {
     65     
     66     char buf[1024];
     67     int numchars;
     68     char method[255];
     69     char url[255];
     70     char path[512];
     71     size_t i, j;
     72     struct stat st;
     73     int cgi = 0;      /* becomes true if server decides this is a CGI
     74                     * program */
     75     char *query_string = NULL;
     76 
     77     numchars = get_line(client, buf, sizeof(buf));
     78     //读取 client端发送的数据并且 返回的参数是 numchars
     79     i = 0;
     80     j = 0;
     81     while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
     82     {
     83         method[i] = buf[j];
     84         i++;
     85         j++;
     86     }
     87     //得到传递的参数是post 还是get方法 还是其他
     88     method[i] = '';
     89 
     90     if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
     91     {
     92     //回给浏览器表明收到的 HTTP 请求所用的 method 不被支持
     93         unimplemented(client);
     94         return;
     95     }
     96 
     97     if (strcasecmp(method, "POST") == 0)
     98         cgi = 1;
     99 
    100     i = 0;
    101     //http请求行格式是 method urI http-version
    102     while (ISspace(buf[j]) && (j < sizeof(buf)))
    103         j++;
    104     while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
    105     {
    106         url[i] = buf[j]; //包含请求行中的URI
    107         i++;
    108         j++;
    109     }
    110     url[i] = '';
    111     ///获取发送数据中的URI
    112     if (strcasecmp(method, "GET") == 0)
    113     {
    114     //get方法 是将参数传递在URI中的?后面如果元素多用&进行链接
    115         query_string = url;
    116         while ((*query_string != '?') && (*query_string != ''))
    117             query_string++;
    118          // get 请求? 后面为参数
    119      if (*query_string == '?')
    120         {
    121             cgi = 1;
    122             *query_string = '';
    123             query_string++;
    124         }
    125     }
    126     //格式url存储在path数组 并且html存储在 htdocs文件中
    127     sprintf(path, "htdocs%s", url);
    128     if (path[strlen(path) - 1] == '/')
    129         strcat(path, "index.html");  //字符串进行衔接 + index.html
    130    //stat函数 根据path路径 获取文件内容 存储在 st 结构体中  成功返回0 错误返回-1
    131     if (stat(path, &st) == -1)
    132     {
    133         while ((numchars > 0) && strcmp("
    ", buf))  /* read & discard headers */
    134             numchars = get_line(client, buf, sizeof(buf));
    135         not_found(client);
    136     }
    137     else
    138     {
    139         if ((st.st_mode & S_IFMT) == S_IFDIR)
    140             strcat(path, "/index.html");
    141         //文件的权限 属主 属组 其它 三种任一个拥有执行权
    142     if ((st.st_mode & S_IXUSR) ||
    143                 (st.st_mode & S_IXGRP) ||
    144                 (st.st_mode & S_IXOTH)    )
    145             cgi = 1;
    146     //调用cat 把服务器文件返回给浏览器  post方法或者拥有执行权限
    147         if (!cgi)
    148             serve_file(client, path);
    149         else
    150             execute_cgi(client, path, method, query_string)
    151     //运行cgi程序的处理,也是个主要函数
    152     }
    153 
    154     close(client);
    155 }
    156 
    157 /**********************************************************************/
    158 /* Inform the client that a request it has made has a problem.
    159  * Parameters: client socket */
    160 /**********************************************************************/
    161 void bad_request(int client)
    162 {
    163     char buf[1024];
    164 
    165     sprintf(buf, "HTTP/1.0 400 BAD REQUEST
    ");
    166     send(client, buf, sizeof(buf), 0);
    167     sprintf(buf, "Content-type: text/html
    ");
    168     send(client, buf, sizeof(buf), 0);
    169     sprintf(buf, "
    ");
    170     send(client, buf, sizeof(buf), 0);
    171     sprintf(buf, "<P>Your browser sent a bad request, ");
    172     send(client, buf, sizeof(buf), 0);
    173     sprintf(buf, "such as a POST without a Content-Length.
    ");
    174     send(client, buf, sizeof(buf), 0);
    175 }
    176 
    177 /**********************************************************************/
    178 /* Put the entire contents of a file out on a socket.  This function
    179  * is named after the UNIX "cat" command, because it might have been
    180  * easier just to do something like pipe, fork, and exec("cat").
    181  * Parameters: the client socket descriptor
    182  *             FILE pointer for the file to cat */
    183 /**********************************************************************/
    184 //读取服务器上的某个文件 写到socket套接字上
    185 void cat(int client, FILE *resource)
    186 {
    187     char buf[1024];
    188 
    189     fgets(buf, sizeof(buf), resource);
    190     //检测流上的文件结束符  如果文件结束,则返回非0值,否则返回0,文件结束符只能被clearerr()清除。
    191     while (!feof(resource))
    192     {
    193         send(client, buf, strlen(buf), 0);
    194         fgets(buf, sizeof(buf), resource);
    195     }
    196 }
    197 
    198 /**********************************************************************/
    199 /* Inform the client that a CGI script could not be executed.
    200  * Parameter: the client socket descriptor. */
    201 /**********************************************************************/
    202 void cannot_execute(int client)
    203 {
    204     char buf[1024];
    205 
    206     sprintf(buf, "HTTP/1.0 500 Internal Server Error
    ");
    207     send(client, buf, strlen(buf), 0);
    208     sprintf(buf, "Content-type: text/html
    ");
    209     send(client, buf, strlen(buf), 0);
    210     sprintf(buf, "
    ");
    211     send(client, buf, strlen(buf), 0);
    212     sprintf(buf, "<P>Error prohibited CGI execution.
    ");
    213     send(client, buf, strlen(buf), 0);
    214 }
    215 
    216 /**********************************************************************/
    217 /* Print out an error message with perror() (for system errors; based
    218  * on value of errno, which indicates system call errors) and exit the
    219  * program indicating an error. */
    220 /**********************************************************************/
    221 void error_die(const char *sc)
    222 {
    223     perror(sc);
    224     exit(1);
    225 }
    226 
    227 /**********************************************************************/
    228 /* Execute a CGI script.  Will need to set environment variables as
    229  * appropriate.
    230  * Parameters: client socket descriptor
    231  *             path to the CGI script */
    232 /**********************************************************************/
    233 
    234 //运行cgi程序  也是个主函数
    235 void execute_cgi(int client, const char *path,
    236                  const char *method, const char *query_string)
    237 {
    238     //在父进程中,关闭cgi_input的读入端和cgi_output的写入端,如果post的话
    239     //把post数据写入到cgi_input,已被重定向到stdin,读取cgi_output的管道
    240     //输出到客户端,该管道输入是stdout,接着关闭所有管道,等待子进程结束。
    241     
    242     char buf[1024];
    243     int cgi_output[2];
    244     int cgi_input[2];
    245     //cgi_output[1] cgi_input[1] 为输入端
    246     //cgi_input[0] cgi_output[0] 为输出端
    247     pid_t pid;
    248     int status;
    249     int i;
    250     char c;
    251     int numchars = 1;
    252     int content_length = -1;
    253 
    254     buf[0] = 'A';
    255     buf[1] = '';
    256     //读入请求头
    257     if (strcasecmp(method, "GET") == 0)
    258         while ((numchars > 0) && strcmp("
    ", buf))  /* read & discard headers */
    259             numchars = get_line(client, buf, sizeof(buf));
    260     else    /* POST */
    261     {
    262         numchars = get_line(client, buf, sizeof(buf));
    263         while ((numchars > 0) && strcmp("
    ", buf))
    264         {
    265             buf[15] = '';
    266             if (strcasecmp(buf, "Content-Length:") == 0)
    267                 content_length = atoi(&(buf[16]));
    268             numchars = get_line(client, buf, sizeof(buf));
    269         }
    270         if (content_length == -1)
    271         {
    272             bad_request(client);
    273             return;
    274         }
    275     }
    276 
    277     sprintf(buf, "HTTP/1.0 200 OK
    ");
    278     send(client, buf, strlen(buf), 0);
    279 
    280     if (pipe(cgi_output) < 0)
    281     {
    282         cannot_execute(client);
    283         return;
    284     }
    285     if (pipe(cgi_input) < 0)
    286     {
    287         cannot_execute(client);
    288         return;
    289     }
    290 
    291     if ( (pid = fork()) < 0 )
    292     {
    293         cannot_execute(client);
    294         return;
    295     }
    296     if (pid == 0)  /* child: CGI script */
    297     {
    298         char meth_env[255];
    299         char query_env[255];
    300         char length_env[255];
    301     //把stdout重定向到cgi_output的写入端
    302         dup2(cgi_output[1], 1);
    303     //把stdin重定向到cgi_input的读入端
    304         dup2(cgi_input[0], 0);
    305     //关闭cgi_output的读入端 和 cgi_input的 写入端
    306         close(cgi_output[0]);
    307         close(cgi_input[1]);
    308     //设置request_method的环境变量
    309         sprintf(meth_env, "REQUEST_METHOD=%s", method);
    310         putenv(meth_env);
    311         if (strcasecmp(method, "GET") == 0)
    312         {
    313         //设置query_string的环境变量
    314             sprintf(query_env, "QUERY_STRING=%s", query_string);
    315             putenv(query_env);
    316         }
    317         else     /* POST */
    318         {
    319         //设置content_length的环境变量
    320             sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
    321             putenv(length_env);
    322         }
    323         //用execl运行cgi程序
    324     execl(path, path, NULL);
    325         exit(0);
    326     }
    327     else        /* parent */
    328     {
    329         close(cgi_output[1]);
    330         close(cgi_input[0]);
    331         //关闭cgi_output的 写入端 和 cgi_input的读取端
    332     if (strcasecmp(method, "POST") == 0)
    333        //接受post的数据
    334             for (i = 0; i < content_length; i++)
    335             {
    336                 recv(client, &c, 1, 0);
    337                 write(cgi_input[1], &c, 1);
    338                 //讲post数据写入cgi_input,并且重定向到stdin中
    339              }
    340     //读取cgi_output的管道输出到客户端,该管道输入时stdout
    341         while (read(cgi_output[0], &c, 1) > 0)
    342             send(client, &c, 1, 0);
    343     //关闭管道
    344         close(cgi_output[0]);
    345         close(cgi_input[1]);
    346         //等待子进程
    347     waitpid(pid, &status, 0);
    348     }
    349 }
    350 
    351 /**********************************************************************/
    352 /* Get a line from a socket, whether the line ends in a newline,
    353  * carriage return, or a CRLF combination.  Terminates the string read
    354  * with a null character.  If no newline indicator is found before the
    355  * end of the buffer, the string is terminated with a null.  If any of
    356  * the above three line terminators is read, the last character of the
    357  * string will be a linefeed and the string will be terminated with a
    358  * null character.
    359  * Parameters: the socket descriptor
    360  *             the buffer to save the data in
    361  *             the size of the buffer
    362  * Returns: the number of bytes stored (excluding null) */
    363 /**********************************************************************/
    364 int get_line(int sock, char *buf, int size)
    365 {
    366     int i = 0;
    367     char c = '';
    368     int n;
    369 
    370     while ((i < size - 1) && (c != '
    '))
    371     {
    372         //每次接受一个字符
    373     n = recv(sock, &c, 1, 0);
    374         /* DEBUG printf("%02X
    ", c); */
    375         if (n > 0)
    376         {
    377             if (c == '
    ')
    378             {
    379                 n = recv(sock, &c, 1, MSG_PEEK);
    380                 /* DEBUG printf("%02X
    ", c); */
    381                 if ((n > 0) && (c == '
    '))
    382                     recv(sock, &c, 1, 0);
    383                 else
    384                     c = '
    ';
    385             }
    386             buf[i] = c;
    387             i++;
    388         }
    389         else
    390             c = '
    ';
    391     }
    392     buf[i] = '';
    393 
    394     return(i);
    395 }
    396 
    397 /**********************************************************************/
    398 /* Return the informational HTTP headers about a file. */
    399 /* Parameters: the socket to print the headers on
    400  *             the name of the file */
    401 /**********************************************************************/
    402 //http 响应体
    403 void headers(int client, const char *filename)
    404 {
    405     char buf[1024];
    406     (void)filename;  /* could use filename to determine file type */
    407 
    408     strcpy(buf, "HTTP/1.0 200 OK
    ");//响应的状态行
    409     send(client, buf, strlen(buf), 0);
    410     strcpy(buf, SERVER_STRING);
    411     send(client, buf, strlen(buf), 0);
    412     sprintf(buf, "Content-Type: text/html
    "); //响应头
    413     send(client, buf, strlen(buf), 0);
    414     strcpy(buf, "
    ");        //响应正文段
    415     send(client, buf, strlen(buf), 0);
    416 }
    417 
    418 /**********************************************************************/
    419 /* Give a client a 404 not found status message. */
    420 /**********************************************************************/
    421 void not_found(int client)
    422 {
    423     //客户端发送的请求无法实现
    424     char buf[1024];
    425    //响应行
    426     sprintf(buf, "HTTP/1.0 404 NOT FOUND
    ");
    427     send(client, buf, strlen(buf), 0);
    428     sprintf(buf, SERVER_STRING);
    429     send(client, buf, strlen(buf), 0);
    430     //响应头
    431     sprintf(buf, "Content-Type: text/html
    ");
    432     send(client, buf, strlen(buf), 0);
    433     sprintf(buf, "
    ");
    434     //响应头和响应正文段之间有一个空行 表示响应行结束
    435     send(client, buf, strlen(buf), 0);
    436     //响应正文段 text/html
    437     sprintf(buf, "<HTML><TITLE>Not Found</TITLE>
    ");
    438     send(client, buf, strlen(buf), 0);
    439     sprintf(buf, "<BODY><P>The server could not fulfill
    ");
    440     send(client, buf, strlen(buf), 0);
    441     sprintf(buf, "your request because the resource specified
    ");
    442     send(client, buf, strlen(buf), 0);
    443     sprintf(buf, "is unavailable or nonexistent.
    ");
    444     send(client, buf, strlen(buf), 0);
    445     sprintf(buf, "</BODY></HTML>
    ");
    446     send(client, buf, strlen(buf), 0);
    447 }
    448 
    449 /**********************************************************************/
    450 /* Send a regular file to the client.  Use headers, and report
    451  * errors to client if they occur.
    452  * Parameters: a pointer to a file structure produced from the socket
    453  *              file descriptor
    454  *             the name of the file to serve */
    455 /**********************************************************************/
    456 void serve_file(int client, const char *filename)
    457 {
    458     FILE *resource = NULL;
    459     int numchars = 1;
    460     char buf[1024];
    461 
    462     buf[0] = 'A';
    463     buf[1] = '';
    464     while ((numchars > 0) && strcmp("
    ", buf))  /* read & discard headers */
    465         numchars = get_line(client, buf, sizeof(buf));
    466 
    467     resource = fopen(filename, "r");
    468     if (resource == NULL)
    469         not_found(client);
    470     else
    471     {
    472         headers(client, filename);
    473         cat(client, resource);
    474     }
    475     fclose(resource);
    476 }
    477 
    478 /**********************************************************************/
    479 /* This function starts the process of listening for web connections
    480  * on a specified port.  If the port is 0, then dynamically allo        //响应正文段cate a
    481  * port and modify the original port variable to reflect the actual
    482  * port.
    483  * Parameters: pointer to variable containing the port to connect on
    484  * Returns: the socket */
    485 /**********************************************************************/
    486 int startup(u_short *port)
    487 {
    488     int httpd = 0;
    489     struct sockaddr_in name;
    490     //建立套接字
    491     httpd = socket(PF_INET, SOCK_STREAM, 0);
    492     if (httpd == -1)
    493         error_die("socket");
    494     memset(&name, 0, sizeof(name));
    495     name.sin_family = AF_INET;
    496     name.sin_port = htons(*port);
    497     name.sin_addr.s_addr = htonl(INADDR_ANY);
    498     //绑定端口和ip
    499     if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
    500         error_die("bind");
    501     if (*port == 0)  /* if dynamically allocating a port */
    502     {
    503         int namelen = sizeof(name);
    504         if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
    505             error_die("getsockname");
    506         *port = ntohs(name.sin_port);
    507     }
    508     //监听
    509     if (listen(httpd, 5) < 0)
    510         error_die("listen");
    511     return(httpd);
    512 }
    513 
    514 /**********************************************************************/
    515 /* Inform the client that the requested web method has not been
    516  * implemented.
    517  * Parameter: the client socket */
    518 /**********************************************************************/
    519 void unimplemented(int client)
    520 {
    521     //发回响应信息  http的请求方法不被接受
    522     char buf[1024];
    523     //返回501 错误 未实现 (Not implemented)是指Web 服务器不理解或不支持发送给它的 HTTP 数据流中找到的 HTTP 方法
    524     sprintf(buf, "HTTP/1.0 501 Method Not Implemented
    ");
    525     send(client, buf, strlen(buf), 0);
    526     sprintf(buf, SERVER_STRING);
    527     send(client, buf, strlen(buf), 0);
    528     sprintf(buf, "Content-Type: text/html
    ");//响应头
    529     send(client, buf, strlen(buf), 0);
    530     sprintf(buf, "
    ");
    531     send(client, buf, strlen(buf), 0); //响应正文段
    532     sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented
    ");
    533     send(client, buf, strlen(buf), 0);
    534     sprintf(buf, "</TITLE></HEAD>
    ");
    535     send(client, buf, strlen(buf), 0);
    536     sprintf(buf, "<BODY><P>HTTP request method not supported.
    ");
    537     send(client, buf, strlen(buf), 0);
    538     sprintf(buf, "</BODY></HTML>
    ");
    539     send(client, buf, strlen(buf), 0);
    540 }
    541 
    542 /**********************************************************************/
    543 
    544 int main(void)
    545 {
    546     int server_sock = -1;
    547     u_short port = 0;
    548     int client_sock = -1;
    549     struct sockaddr_in client_name;
    550     int client_name_len = sizeof(client_name);
    551     pthread_t newthread;
    552 
    553     server_sock = startup(&port);
    554     printf("httpd running on port %d
    ", port);
    555 
    556     while (1)
    557     {
    558         client_sock = accept(server_sock,
    559                              (struct sockaddr *)&client_name,
    560                              &client_name_len);
    561         //建立链接
    562     if (client_sock == -1)
    563             error_die("accept");
    564         /* accept_request(client_sock); */
    565         //多线程进行控制
    566     if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
    567             perror("pthread_create");
    568     }
    569 
    570     close(server_sock);
    571 
    572     return(0);
    573 }
  • 相关阅读:
    PowerShell(0)Windows PowerShell交互界面
    WIN8的特性
    利用笔记本的WiFi做AP共享网络
    MD5和SHA加密测试
    WMI操作示例
    购买Microsoft Technet订阅,免费获得微软几乎所有的产品序列号“用于评估”,
    une petite bébé
    为ESX5主机配置多路径共享存储(以MD3000I为例)
    获取网卡信息
    CISCO交换机端口回环的处理方法备忘
  • 原文地址:https://www.cnblogs.com/chenyang920/p/5610052.html
Copyright © 2011-2022 走看看