• 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 }
  • 相关阅读:
    标签的讲解
    属性分类
    LeetCode 003. 无重复字符的最长子串 双指针
    Leetcode 136. 只出现一次的数字 异或性质
    Leetcode 231. 2的幂 数学
    LeetCode 21. 合并两个有序链表
    象棋博弈资源
    acwing 343. 排序 topsort floyd 传播闭包
    Leetcode 945 使数组唯一的最小增量 贪心
    Leetcode 785 判断二分图 BFS 二分染色
  • 原文地址:https://www.cnblogs.com/WindSun/p/11343490.html
Copyright © 2020-2023  润新知