翼度科技»论坛 云主机 LINUX 查看内容

Tinyhttpd:源码分析【3】

5

主题

5

帖子

15

积分

新手上路

Rank: 1

积分
15
一、问题引入

通过 Tinyhttpd:运行测试【1】 和 抓包分析【2】,基本完成了对程序的功能测试和通信原理。此时可以进一步对源码进行分析,本文不考虑代码一行一行的分析,仅对关键部分代码解析。
二、解决过程

2-1 main()函数

主函数主要创建http的监听套接字,等待客户端的连接。一旦有新客户端连接http,则创建一个新线程与客户端通信,而主线程(即main函数)继续等待客户端的连接。
  1. int main(void)
  2. {
  3.     int server_sock = -1;
  4.     u_short port = 10080;
  5.     int client_sock = -1;
  6.     struct sockaddr_in client_name;
  7.     socklen_t  client_name_len = sizeof(client_name);
  8.     pthread_t newthread;
  9.     server_sock = startup(&port);
  10.     printf("httpd running on port %d\n", port);
  11.     while (1)
  12.     {
  13.         client_sock = accept(server_sock,(struct sockaddr *) &client_name, &client_name_len);
  14.         if (client_sock == -1)
  15.             error_die("accept");
  16.         /* accept_request(&client_sock); */
  17.         if (pthread_create(&newthread , NULL, (void *)accept_request, (void *)(intptr_t)client_sock) != 0)
  18.             perror("pthread_create");
  19.     }
  20.     close(server_sock);
  21.     return(0);
  22. }
复制代码
2-2 startup()函数

如果熟悉套接字编程,那么肯定对上面的代码肯定很眼熟。该函数的功能就是服务器在指定端口创建监听套接字。但同时对监听的端口做了冗余处理,若指定的端口为0,则动态的申请一个端口号。
  1. int startup(u_short *port)
  2. {
  3.     int httpd = 0;
  4.     int on = 1;
  5.     struct sockaddr_in name;
  6.     httpd = socket(PF_INET, SOCK_STREAM, 0);
  7.     if (httpd == -1)
  8.         error_die("socket");
  9.     memset(&name, 0, sizeof(name));
  10.     name.sin_family = AF_INET;
  11.     name.sin_port = htons(*port);
  12.     name.sin_addr.s_addr = htonl(INADDR_ANY);
  13.     if ((setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0)  /* set port reuse */
  14.     {  
  15.         error_die("setsockopt failed");
  16.     }
  17.     if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)
  18.         error_die("bind");
  19.     if (*port == 0)  /* if dynamically allocating a port */
  20.     {
  21.         socklen_t namelen = sizeof(name);
  22.         if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)
  23.             error_die("getsockname");
  24.         *port = ntohs(name.sin_port);
  25.     }
  26.     if (listen(httpd, 5) < 0)
  27.         error_die("listen");
  28.     return(httpd);
  29. }
复制代码
2-3 accept_request()函数

该线程回调函数是整个程序最核心、最关键的部分,它包含了如何解析客户端发送的http request和服务区根据客户端的请求如何发送http respond。
  1. void accept_request(void *arg)
  2. {
  3.     int client = (intptr_t)arg;
  4.     char buf[1024];
  5.     size_t numchars;
  6.     char method[255];
  7.     char url[255];
  8.     char path[512];
  9.     size_t i, j;
  10.     struct stat st;
  11.     int cgi = 0;      /* becomes true if server decides this is a CGI
  12.                        * program */
  13.     char *query_string = NULL;
  14.     numchars = get_line(client, buf, sizeof(buf));
  15.     i = 0; j = 0;
  16.     while (!ISspace(buf[i]) && (i < sizeof(method) - 1))
  17.     {
  18.         method[i] = buf[i];
  19.         i++;
  20.     }
  21.     j=i;
  22.     method[i] = '\0';
  23.     if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
  24.     {
  25.         unimplemented(client);
  26.         return;
  27.     }
  28.     if (strcasecmp(method, "POST") == 0)
  29.         cgi = 1;
  30.     i = 0;
  31.     while (ISspace(buf[j]) && (j < numchars))
  32.         j++;
  33.     while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars))
  34.     {
  35.         url[i] = buf[j];
  36.         i++; j++;
  37.     }
  38.     url[i] = '\0';
  39.     if (strcasecmp(method, "GET") == 0)
  40.     {
  41.         query_string = url;
  42.         while ((*query_string != '?') && (*query_string != '\0'))
  43.             query_string++;
  44.         if (*query_string == '?')
  45.         {
  46.             cgi = 1;
  47.             *query_string = '\0';
  48.             query_string++;
  49.         }
  50.     }
  51.     sprintf(path, "htdocs%s", url);
  52.     if (path[strlen(path) - 1] == '/')
  53.         strcat(path, "index.html");
  54.     if (stat(path, &st) == -1) {
  55.         while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
  56.             numchars = get_line(client, buf, sizeof(buf));
  57.         not_found(client);
  58.     }
  59.     else
  60.     {
  61.         if ((st.st_mode & S_IFMT) == S_IFDIR)
  62.             strcat(path, "/index.html");
  63.         if ((st.st_mode & S_IXUSR) ||
  64.                 (st.st_mode & S_IXGRP) ||
  65.                 (st.st_mode & S_IXOTH)    )
  66.             cgi = 1;
  67.         if (!cgi)
  68.             serve_file(client, path);
  69.         else
  70.             execute_cgi(client, path, method, query_string);
  71.     }
  72.     close(client);
  73. }
复制代码
解析第一行

http 请求行包括三个字段:请求方法、URL、协议版本。
解析请求方法:仅支持GET和POST。若为POST时,cgi标志位置1
  1. numchars = get_line(client, buf, sizeof(buf));
  2. i = 0; j = 0;
  3. while (!ISspace(buf[i]) && (i < sizeof(method) - 1))
  4. {
  5.     method[i] = buf[i];
  6.     i++;
  7. }
  8. j=i;
  9. method[i] = '\0';
  10. if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
  11. {
  12.     unimplemented(client);
  13.     return;
  14. }
  15. if (strcasecmp(method, "POST") == 0)
  16.     cgi = 1;
复制代码
解析URL,并在GET方法时,将URL中的第一个?处替换为\0
  1. i = 0;
  2. while (ISspace(buf[j]) && (j < numchars))
  3.     j++;
  4. while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars))
  5. {
  6.     url[i] = buf[j];
  7.     i++; j++;
  8. }
  9. url[i] = '\0';
  10. if (strcasecmp(method, "GET") == 0)
  11. {
  12.     query_string = url;
  13.     while ((*query_string != '?') && (*query_string != '\0'))
  14.         query_string++;
  15.     if (*query_string == '?')
  16.     {
  17.         cgi = 1;
  18.         *query_string = '\0';
  19.         query_string++;
  20.     }
  21. }
复制代码
判断URL是文件还是文件夹,若为文件夹,则拼接相对路径path htdocs/xxx/.../xxx/index.html。
判断path是否存在,若不存在,先清空读缓存,执行函数 not_found();若存在且cgi标志位为1,执行函数 execute_cgi(),若存在且cgi不为1,执行函数 serve_file()
  1. sprintf(path, "htdocs%s", url);
  2. if (path[strlen(path) - 1] == '/')
  3.     strcat(path, "index.html");
  4. if (stat(path, &st) == -1) {
  5.     while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
  6.         numchars = get_line(client, buf, sizeof(buf));
  7.     not_found(client);
  8. }
  9. else
  10. {
  11.     if ((st.st_mode & S_IFMT) == S_IFDIR)
  12.         strcat(path, "/index.html");
  13.     if ((st.st_mode & S_IXUSR) ||
  14.             (st.st_mode & S_IXGRP) ||
  15.             (st.st_mode & S_IXOTH)    )
  16.         cgi = 1;
  17.     if (!cgi)
  18.         serve_file(client, path);
  19.     else
  20.         execute_cgi(client, path, method, query_string);
  21. }
复制代码
三、反思总结

整体理解下来,httpd.c可以解析来自客户端的GET和POST请求。

  • GET方法
可以处理请求格式:

  • GET / HTTP/1.1 \r\n
  • GET /index.html \r\n
  • GET /color.cgi \r\n
  • GET /color.cgi?color=pink \r\n


  • POST方法
可以处理请求格式:

  • POST /color.cgi HTTP/1.1 \r\n + "color" = "green"
四、参考引用

EZLippi/Tinyhttpd

来源:https://www.cnblogs.com/caojun97/p/17276322.html
免责声明:由于采集信息均来自互联网,如果侵犯了您的权益,请联系我们【E-Mail:cb@itdo.tech】 我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x

举报 回复 使用道具