• Tinyhttp源码分析


    简介

    Tinyhttp是一个轻量型Http Server,使用C语言开发,全部代码只500多行,还包括一个简单Client。

    Tinyhttp程序的逻辑为:一个无线循环,一个请求,创建一个线程,之后线程函数处理每个请求,然后解析HTTP请求,做一些判断,之后判断文件是否可执行,不可执行,打开文件,输出给客户端(浏览器),可执行就创建管道,父子进程进行通信。父子进程通信,用到了dup2和execl函数。

    模型图

    image

    源码剖析

      1 #include <stdio.h>
      2 #include <sys/socket.h>
      3 #include <sys/types.h>
      4 #include <netinet/in.h>
      5 #include <arpa/inet.h>
      6 #include <unistd.h>
      7 #include <ctype.h>
      8 #include <strings.h>
      9 #include <string.h>
     10 #include <sys/stat.h>
     11 #include <pthread.h>
     12 #include <sys/wait.h>
     13 #include <stdlib.h>
     14 
     15 #define ISspace(x) isspace((int)(x))
     16 
     17 #define SERVER_STRING "Server: jdbhttpd/0.1.0
    "
     18 
     19 void *accept_request(void *);
     20 void bad_request(int);
     21 void cat(int, FILE *);
     22 void cannot_execute(int);
     23 void error_die(const char *);
     24 void execute_cgi(int, const char *, const char *, const char *);
     25 int get_line(int, char *, int);
     26 void headers(int, const char *);
     27 void not_found(int);
     28 void serve_file(int, const char *);
     29 int startup(u_short *);
     30 void unimplemented(int);
     31 
     32 /**********************************************************************/
     33 /*功能:处理请求
     34  *参数:连接到客户端的套接字*/
     35 /**********************************************************************/
     36 void *accept_request(void *arg)
     37 {
     38     int client = *(int *)arg; //接收客户端的套接字
     39     char buf[1024];
     40     int numchars;
     41     char method[255];
     42     char url[255];
     43     char path[512];
     44     size_t i, j;
     45     struct stat st;
     46     int cgi = 0;
     47 
     48     char *query_string = NULL;
     49     // "GET /index.html HTTP/1.1
    ",'00' <repeats 319 times>...
     50     numchars = get_line(client, buf, sizeof(buf)); //读取一行存放buf中
     51     i = 0;
     52     j = 0;
     53     //判断buf中第一个空格前面的字符串的请求方式
     54     while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
     55     {
     56         method[i] = buf[j]; //解析出请求的方法放在method中
     57         i++;
     58         j++;
     59     }
     60     method[i] = '';
     61     //如果是其他的请求方式,除了GET和POST外,如:HEAD、DELETE等回复未实现方法
     62     if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) //strcasecmp用忽略大小写比较字符串
     63     {
     64         unimplemented(client); //回复请求的方法未实现
     65         return 0;
     66     }
     67 
     68     //如果是POST方式请求,表示执行cgi
     69     if (strcasecmp(method, "POST") == 0)
     70         cgi = 1;
     71 
     72     i = 0;
     73     //从上面的第一个空格后继续开始
     74     while (ISspace(buf[j]) && (j < sizeof(buf)))
     75         j++;
     76     //POST或者GET空格后面的内容 如:"GET /index.html HTTP/1.1
    "
     77     while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
     78     {
     79         url[i] = buf[j]; //解析出url要请求的地址,如:"/index.html"
     80         i++;
     81         j++;
     82     }
     83     url[i] = '';
     84 
     85     //如果是GET方式请求,如:/login.cgi?user=123&password=456
     86     if (strcasecmp(method, "GET") == 0)
     87     {
     88         query_string = url;
     89         while ((*query_string != '?') && (*query_string != ''))
     90             query_string++;
     91         if (*query_string == '?') //如果遇到了?表示执行cgi
     92         {
     93             cgi = 1;
     94             *query_string = '';
     95             query_string++; //?后面的内容是发送的信息(如用户名和密码信息)
     96         }
     97     }
     98     //拼接url地址路径
     99     sprintf(path, "htdocs%s", url); //文件的路径放在path中
    100 
    101     if (path[strlen(path) - 1] == '/') //判断是否是根目录
    102         strcat(path, "index.html");    // "htdocs/index.html"
    103 
    104     if (stat(path, &st) == -1)
    105     {                                                //获取文件信息到st结构体失败
    106         while ((numchars > 0) && strcmp("
    ", buf)) //读并丢弃标题(浏览器的信息),如果不读,浏览器发送来的数据,会使服务器处于阻塞状态
    107             numchars = get_line(client, buf, sizeof(buf));
    108         not_found(client); //给客户端一条404未找到的状态消息
    109     }
    110     else //成功获得文件信息
    111     {
    112         if ((st.st_mode & S_IFMT) == S_IFDIR) //是一个目录
    113             strcat(path, "/index.html");
    114 
    115         if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH)) //文件所有者具可执行权限||用户组具可执行权限||其他用户具可执行权限
    116             cgi = 1;                                                                    //是cgi程序
    117 
    118         if (!cgi) //根据cgi的值,为0执行文件
    119             serve_file(client, path);
    120         else //cgi为1,执行cgi
    121             execute_cgi(client, path, method, query_string);
    122     }
    123     close(client);
    124     return 0;
    125 }
    126 
    127 /**********************************************************************/
    128 /* 通知客户它提出的请求有问题
    129  * 参数: 接收客户端套接字描述符 */
    130 /**********************************************************************/
    131 void bad_request(int client)
    132 {
    133     char buf[1024];
    134 
    135     sprintf(buf, "HTTP/1.0 400 BAD REQUEST
    ");
    136     send(client, buf, sizeof(buf), 0);
    137     sprintf(buf, "Content-type: text/html
    ");
    138     send(client, buf, sizeof(buf), 0);
    139     sprintf(buf, "
    ");
    140     send(client, buf, sizeof(buf), 0);
    141     sprintf(buf, "<P>Your browser sent a bad request, ");
    142     send(client, buf, sizeof(buf), 0);
    143     sprintf(buf, "such as a POST without a Content-Length.
    ");
    144     send(client, buf, sizeof(buf), 0);
    145 }
    146 
    147 /**********************************************************************/
    148 /*不停的从resource所指的文件中读取内容,发送给客户端
    149  *参数:接受客户端套接字,请求的文件的指针*/
    150 /**********************************************************************/
    151 void cat(int client, FILE *resource)
    152 {
    153     char buf[1024];
    154 
    155     fgets(buf, sizeof(buf), resource); //
    156     while (!feof(resource))
    157     {
    158         send(client, buf, strlen(buf), 0);
    159         fgets(buf, sizeof(buf), resource);
    160     }
    161 }
    162 
    163 /**********************************************************************/
    164 /* 通知客户端无法执行CGI脚本。
    165  * 参数:客户端套接字描述符。*/
    166 /**********************************************************************/
    167 void cannot_execute(int client)
    168 {
    169     char buf[1024];
    170 
    171     sprintf(buf, "HTTP/1.0 500 Internal Server Error
    ");
    172     send(client, buf, strlen(buf), 0);
    173     sprintf(buf, "Content-type: text/html
    ");
    174     send(client, buf, strlen(buf), 0);
    175     sprintf(buf, "
    ");
    176     send(client, buf, strlen(buf), 0);
    177     sprintf(buf, "<P>Error prohibited CGI execution.
    ");
    178     send(client, buf, strlen(buf), 0);
    179 }
    180 
    181 /**********************************************************************/
    182 /* 打印带有perror()的错误消息并退出指示错误的程序。 */
    183 /**********************************************************************/
    184 void error_die(const char *sc)
    185 {
    186     perror(sc);
    187     exit(1);
    188 }
    189 
    190 /**********************************************************************/
    191 /* 执行一个cgi程序,需要环境的支持
    192  * 参数: 接受客户端套接字描述符client,拼接后的地址路径path,请求的方法method,接受的url?后面的内容*/
    193 /**********************************************************************/
    194 void execute_cgi(int client, const char *path, const char *method, const char *query_string)
    195 {
    196     char buf[1024];
    197     int cgi_output[2];
    198     int cgi_input[2];
    199     pid_t pid;
    200     int status;
    201     int i;
    202     char c;
    203     int numchars = 1;
    204     int content_length = -1;
    205 
    206     buf[0] = 'A';
    207     buf[1] = '';
    208     if (strcasecmp(method, "GET") == 0)
    209         while ((numchars > 0) && strcmp("
    ", buf)) //读并丢弃标题(浏览器的信息),如果不读,浏览器发送来的数据,会使服务器处于阻塞状态
    210             numchars = get_line(client, buf, sizeof(buf));
    211 
    212     else //POST
    213     {
    214         numchars = get_line(client, buf, sizeof(buf)); //读取一行到buf中
    215         while ((numchars > 0) && strcmp("
    ", buf))
    216         {
    217             buf[15] = '';
    218             if (strcasecmp(buf, "Content-Length:") == 0) //拷贝Content-Length:到字符串中
    219                 content_length = atoi(&(buf[16]));         //获得content_length内容长度
    220             numchars = get_line(client, buf, sizeof(buf));
    221         }
    222         if (content_length == -1)
    223         {
    224             bad_request(client);
    225             return;
    226         }
    227     }
    228 
    229     sprintf(buf, "HTTP/1.0 200 OK
    "); //给浏览器一个回复
    230     send(client, buf, strlen(buf), 0);
    231 
    232     //建立两根管道,分别是输出管道和输入管道
    233     if (pipe(cgi_output) < 0)
    234     {
    235         cannot_execute(client);
    236         return;
    237     }
    238     if (pipe(cgi_input) < 0)
    239     {
    240         cannot_execute(client);
    241         return;
    242     }
    243     //创建一个进程
    244     if ((pid = fork()) < 0)
    245     {
    246         cannot_execute(client);
    247         return;
    248     }
    249     if (pid == 0) //子进程
    250     {
    251         char meth_env[255];
    252         char query_env[255];
    253         char length_env[255];
    254 
    255         dup2(cgi_output[1], 1); //dup2做了重定向,Linux中0是标准输入(键盘),1是标准输出(屏幕)
    256         dup2(cgi_input[0], 0);  //将cgi_output[1]描述符拷贝到标准输出,原来输出到屏幕的,现在写到cgi_output[1管道中]
    257         //cgi_output[1]用来写
    258         //cgi_input[0]用来读
    259         close(cgi_output[0]);
    260         close(cgi_input[1]);
    261 
    262         sprintf(meth_env, "REQUEST_METHOD=%s", method); //将请求的方法加到环境变量
    263         putenv(meth_env);                                //putenv增加环境变量的内容
    264         if (strcasecmp(method, "GET") == 0)
    265         {
    266             sprintf(query_env, "QUERY_STRING=%s", query_string);
    267             putenv(query_env);
    268         }
    269         else
    270         { //POST
    271             sprintf(length_env, "CONTENT_LENGTH=%d", content_length);
    272             putenv(length_env);
    273         }
    274         execl(path, path, NULL); //execl执行给出的path路径下的path程序(cgi程序),l表示以list方式传参,经过重定向后,path路径中的程序执行后,读数据从cgi_intput[0],中读取,写数据到cgi_output[1]中欧个
    275         exit(0);
    276     }
    277     else
    278     { //父进程
    279         //cgi_output[0]管道用来读
    280         //cgi_input[1]管道用来写
    281         close(cgi_output[1]);
    282         close(cgi_input[0]);
    283         if (strcasecmp(method, "POST") == 0) //如果是POST,循环每次一个字符,写入管道
    284             for (i = 0; i < content_length; i++)
    285             {
    286                 recv(client, &c, 1, 0);
    287                 write(cgi_input[1], &c, 1);
    288             }
    289         while (read(cgi_output[0], &c, 1) > 0) //循环从管道中读出数据发送给客户端
    290             send(client, &c, 1, 0);
    291 
    292         close(cgi_output[0]);
    293         close(cgi_input[1]);
    294         waitpid(pid, &status, 0);
    295     }
    296 }
    297 
    298 /**********************************************************************/
    299 /*返回从接受客户端套接字中读取一行的字节数
    300  *参数:客户端套接字sock,缓冲区的指针buf,缓冲区大小size*/
    301 /**********************************************************************/
    302 int get_line(int sock, char *buf, int size)
    303 {
    304     int i = 0;
    305     char c = '';
    306     int n;
    307 
    308     while ((i < size - 1) && (c != '
    '))
    309     {
    310         n = recv(sock, &c, 1, 0);
    311 
    312         if (n > 0)
    313         {
    314             if (c == '
    ')
    315             {
    316                 n = recv(sock, &c, 1, MSG_PEEK);
    317 
    318                 if ((n > 0) && (c == '
    '))
    319                     recv(sock, &c, 1, 0);
    320                 else
    321                     c = '
    ';
    322             }
    323             buf[i] = c;
    324             i++;
    325         }
    326         else
    327             c = '
    ';
    328     }
    329     buf[i] = '';
    330 
    331     return (i);
    332 }
    333 
    334 /**********************************************************************/
    335 /*发送有关HTTP头文件的信息
    336  *参数:客户端接收套接字,文件的名称*/
    337 /**********************************************************************/
    338 void headers(int client, const char *filename)
    339 {
    340     char buf[1024];
    341     (void)filename; /*可以使用文件名来确定文件类型*/
    342 
    343     strcpy(buf, "HTTP/1.0 200 OK
    ");
    344     send(client, buf, strlen(buf), 0);
    345     strcpy(buf, SERVER_STRING);
    346     send(client, buf, strlen(buf), 0);
    347     sprintf(buf, "Content-Type: text/html
    ");
    348     send(client, buf, strlen(buf), 0);
    349     strcpy(buf, "
    ");
    350     send(client, buf, strlen(buf), 0);
    351 }
    352 
    353 /**********************************************************************/
    354 /* 给客户端一条404未找到的状态消息。*/
    355 /**********************************************************************/
    356 void not_found(int client)
    357 {
    358     char buf[1024];
    359 
    360     sprintf(buf, "HTTP/1.0 404 NOT FOUND
    ");
    361     send(client, buf, strlen(buf), 0);
    362     sprintf(buf, SERVER_STRING);
    363     send(client, buf, strlen(buf), 0);
    364     sprintf(buf, "Content-Type: text/html
    ");
    365     send(client, buf, strlen(buf), 0);
    366     sprintf(buf, "
    ");
    367     send(client, buf, strlen(buf), 0);
    368     sprintf(buf, "<HTML><TITLE>Not Found</TITLE>
    ");
    369     send(client, buf, strlen(buf), 0);
    370     sprintf(buf, "<BODY><P>The server could not fulfill
    ");
    371     send(client, buf, strlen(buf), 0);
    372     sprintf(buf, "your request because the resource specified
    ");
    373     send(client, buf, strlen(buf), 0);
    374     sprintf(buf, "is unavailable or nonexistent.
    ");
    375     send(client, buf, strlen(buf), 0);
    376     sprintf(buf, "</BODY></HTML>
    ");
    377     send(client, buf, strlen(buf), 0);
    378 }
    379 
    380 /**********************************************************************/
    381 /*向客户端发送一个常规文件。使用标头,并在出现错误时向客户端报告错误。
    382  *参数:客户端的接收文件描述符,要服务的文件的名称*/
    383 /**********************************************************************/
    384 void serve_file(int client, const char *filename)
    385 {
    386     FILE *resource = NULL;
    387     int numchars = 1;
    388     char buf[1024];
    389 
    390     buf[0] = 'A';
    391     buf[1] = '';
    392     while ((numchars > 0) && strcmp("
    ", buf)) //读并丢弃标题(浏览器的信息),如果不读,浏览器发送来的数据,会使服务器处于阻塞状态
    393         numchars = get_line(client, buf, sizeof(buf));
    394 
    395     resource = fopen(filename, "r"); //打开文件
    396     if (resource == NULL)
    397         not_found(client);
    398     else //正常打开
    399     {
    400         headers(client, filename); //返回一些头文件相关的信息
    401         cat(client, resource);     //将文件内容发给客户端
    402     }
    403     fclose(resource);
    404 }
    405 
    406 /**********************************************************************/
    407 /*返回创建指定的端口的监听套接字
    408  *参数:指定的端口port*/
    409 /**********************************************************************/
    410 int startup(u_short *port)
    411 {
    412     int httpd = 0;
    413     struct sockaddr_in name;
    414 
    415     httpd = socket(PF_INET, SOCK_STREAM, 0);
    416     if (httpd == -1)
    417         error_die("socket");
    418     memset(&name, 0, sizeof(name));
    419     name.sin_family = AF_INET;
    420     name.sin_port = htons(*port);
    421     name.sin_addr.s_addr = inet_addr("192.168.137.114");
    422 
    423     int on = 1;
    424     setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    425 
    426     if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
    427         error_die("bind");
    428     if (*port == 0) /* if dynamically allocating a port */
    429     {
    430         socklen_t namelen = sizeof(name);
    431         if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
    432             error_die("getsockname");
    433         *port = ntohs(name.sin_port);
    434     }
    435     if (listen(httpd, 5) < 0)
    436         error_die("listen");
    437     return (httpd);
    438 }
    439 
    440 /**********************************************************************/
    441 /*通知客户端,所请求的web方法尚未实现
    442  *参数:接受客户端套接字 */
    443 /**********************************************************************/
    444 void unimplemented(int client)
    445 {
    446     char buf[1024];
    447 
    448     sprintf(buf, "HTTP/1.0 501 Method Not Implemented
    ");
    449     send(client, buf, strlen(buf), 0);
    450     sprintf(buf, SERVER_STRING);
    451     send(client, buf, strlen(buf), 0);
    452     sprintf(buf, "Content-Type: text/html
    ");
    453     send(client, buf, strlen(buf), 0);
    454     sprintf(buf, "
    ");
    455     send(client, buf, strlen(buf), 0);
    456     sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented
    ");
    457     send(client, buf, strlen(buf), 0);
    458     sprintf(buf, "</TITLE></HEAD>
    ");
    459     send(client, buf, strlen(buf), 0);
    460     sprintf(buf, "<BODY><P>HTTP request method not supported.
    ");
    461     send(client, buf, strlen(buf), 0);
    462     sprintf(buf, "</BODY></HTML>
    ");
    463     send(client, buf, strlen(buf), 0);
    464 }
    465 
    466 /**********************************************************************/
    467 /*主函数入口*/
    468 /**********************************************************************/
    469 int main(void)
    470 {
    471     int server_sock = -1;
    472     u_short port = 8080;
    473     int client_sock = -1;
    474     struct sockaddr_in client_name;
    475     socklen_t client_name_len = sizeof(client_name);
    476     pthread_t newthread;
    477 
    478     server_sock = startup(&port);
    479     printf("httpd running on port %d
    ", port);
    480 
    481     while (1)
    482     {
    483         //父进程每接收一个客户端创建一个线程
    484         client_sock = accept(server_sock, (struct sockaddr *)&client_name, &client_name_len);
    485         if (client_sock == -1)
    486             error_die("accept");
    487         //线程标识符、线程属性、线程运行函数、运行函数参数(接收套接字)
    488         if (pthread_create(&newthread, NULL, accept_request, &client_sock) != 0)
    489             perror("pthread_create");
    490     }
    491 
    492     close(server_sock);
    493 
    494     return (0);
    495 }
  • 相关阅读:
    分享自Allen 《打开excel,word发送没反映OUTLOOK无法打开》
    AD域只禁用USB存储器而开放其他USB设备
    sql2000安装sp4补丁包教程_sql2000sp4
    为帮助保护您的安全,Internet Explorer已经阻止从此站点下载文件
    IExplore.exe应用程序错误解决方法
    单网卡双IP,同时上内外网
    关于
    七伤拳
    <转>在外企混的,一定要懂“外企潜台词”
    习惯
  • 原文地址:https://www.cnblogs.com/WindSun/p/11343490.html
Copyright © 2020-2023  润新知