Tinyhttpd:源码分析【3】

  • Tinyhttpd:源码分析【3】已关闭评论
  • 84 次浏览
  • A+
所属分类:linux技术
摘要

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


一、问题引入

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

二、解决过程

2-1 main()函数

主函数主要创建http的监听套接字,等待客户端的连接。一旦有新客户端连接http,则创建一个新线程与客户端通信,而主线程(即main函数)继续等待客户端的连接。

int main(void) {     int server_sock = -1;     u_short port = 10080;     int client_sock = -1;     struct sockaddr_in client_name;     socklen_t  client_name_len = sizeof(client_name);     pthread_t newthread;      server_sock = startup(&port);     printf("httpd running on port %dn", port);      while (1)     {         client_sock = accept(server_sock,(struct sockaddr *) &client_name, &client_name_len);         if (client_sock == -1)             error_die("accept");         /* accept_request(&client_sock); */         if (pthread_create(&newthread , NULL, (void *)accept_request, (void *)(intptr_t)client_sock) != 0)             perror("pthread_create");     }     close(server_sock);      return(0); } 

2-2 startup()函数

如果熟悉套接字编程,那么肯定对上面的代码肯定很眼熟。该函数的功能就是服务器在指定端口创建监听套接字。但同时对监听的端口做了冗余处理,若指定的端口为0,则动态的申请一个端口号。

int startup(u_short *port) {     int httpd = 0;     int on = 1;     struct sockaddr_in name;      httpd = socket(PF_INET, SOCK_STREAM, 0);     if (httpd == -1)         error_die("socket");     memset(&name, 0, sizeof(name));     name.sin_family = AF_INET;     name.sin_port = htons(*port);     name.sin_addr.s_addr = htonl(INADDR_ANY);     if ((setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0)  /* set port reuse */     {           error_die("setsockopt failed");     }     if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)         error_die("bind");     if (*port == 0)  /* if dynamically allocating a port */     {         socklen_t namelen = sizeof(name);         if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)             error_die("getsockname");         *port = ntohs(name.sin_port);     }     if (listen(httpd, 5) < 0)         error_die("listen");     return(httpd); } 

2-3 accept_request()函数

该线程回调函数是整个程序最核心、最关键的部分,它包含了如何解析客户端发送的http request和服务区根据客户端的请求如何发送http respond。

void accept_request(void *arg) {     int client = (intptr_t)arg;     char buf[1024];     size_t numchars;     char method[255];     char url[255];     char path[512];     size_t i, j;     struct stat st;     int cgi = 0;      /* becomes true if server decides this is a CGI                        * program */     char *query_string = NULL;      numchars = get_line(client, buf, sizeof(buf));     i = 0; j = 0;     while (!ISspace(buf[i]) && (i < sizeof(method) - 1))     {         method[i] = buf[i];         i++;     }     j=i;     method[i] = '';      if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))     {         unimplemented(client);         return;     }      if (strcasecmp(method, "POST") == 0)         cgi = 1;      i = 0;     while (ISspace(buf[j]) && (j < numchars))         j++;     while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars))     {         url[i] = buf[j];         i++; j++;     }     url[i] = '';      if (strcasecmp(method, "GET") == 0)     {         query_string = url;         while ((*query_string != '?') && (*query_string != ''))             query_string++;         if (*query_string == '?')         {             cgi = 1;             *query_string = '';             query_string++;         }     }      sprintf(path, "htdocs%s", url);     if (path[strlen(path) - 1] == '/')         strcat(path, "index.html");     if (stat(path, &st) == -1) {         while ((numchars > 0) && strcmp("n", buf))  /* read & discard headers */             numchars = get_line(client, buf, sizeof(buf));         not_found(client);     }     else     {         if ((st.st_mode & S_IFMT) == S_IFDIR)             strcat(path, "/index.html");         if ((st.st_mode & S_IXUSR) ||                 (st.st_mode & S_IXGRP) ||                 (st.st_mode & S_IXOTH)    )             cgi = 1;         if (!cgi)             serve_file(client, path);         else             execute_cgi(client, path, method, query_string);     }      close(client); } 

解析第一行

Tinyhttpd:源码分析【3】

http 请求行包括三个字段:请求方法、URL、协议版本。

解析请求方法:仅支持GET和POST。若为POST时,cgi标志位置1

numchars = get_line(client, buf, sizeof(buf)); i = 0; j = 0; while (!ISspace(buf[i]) && (i < sizeof(method) - 1)) {     method[i] = buf[i];     i++; } j=i; method[i] = '';  if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {     unimplemented(client);     return; }  if (strcasecmp(method, "POST") == 0)     cgi = 1; 

解析URL,并在GET方法时,将URL中的第一个?处替换为

i = 0; while (ISspace(buf[j]) && (j < numchars))     j++; while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars)) {     url[i] = buf[j];     i++; j++; } url[i] = '';  if (strcasecmp(method, "GET") == 0) {     query_string = url;     while ((*query_string != '?') && (*query_string != ''))         query_string++;     if (*query_string == '?')     {         cgi = 1;         *query_string = '';         query_string++;     } } 

判断URL是文件还是文件夹,若为文件夹,则拼接相对路径path htdocs/xxx/.../xxx/index.html
判断path是否存在,若不存在,先清空读缓存,执行函数 not_found();若存在且cgi标志位为1,执行函数 execute_cgi(),若存在且cgi不为1,执行函数 serve_file()

sprintf(path, "htdocs%s", url); if (path[strlen(path) - 1] == '/')     strcat(path, "index.html"); if (stat(path, &st) == -1) {     while ((numchars > 0) && strcmp("n", buf))  /* read & discard headers */         numchars = get_line(client, buf, sizeof(buf));     not_found(client); } else {     if ((st.st_mode & S_IFMT) == S_IFDIR)         strcat(path, "/index.html");     if ((st.st_mode & S_IXUSR) ||             (st.st_mode & S_IXGRP) ||             (st.st_mode & S_IXOTH)    )         cgi = 1;     if (!cgi)         serve_file(client, path);     else         execute_cgi(client, path, method, query_string); } 

三、反思总结

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

  • GET方法

可以处理请求格式:

  1. GET / HTTP/1.1 rn
  2. GET /index.html rn
  3. GET /color.cgi rn
  4. GET /color.cgi?color=pink rn
  • POST方法

可以处理请求格式:

  1. POST /color.cgi HTTP/1.1 rn + "color" = "green"

四、参考引用

EZLippi/Tinyhttpd