从零快速解析之Tinyhttp源码解析(二)

4.2 accept_request函数细节

顺着上一篇的接着分析,再次贴出来accept_request

void accept_request(int client)                                                                                      
{                                                                                                                    
 char buf[1024];                                                                                                     
 int 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));
 //这里得到是请求行,格式如 :请求方法 URL 协议版本\r\n
  
 //提取请求方法 GET或者POST Tinyhttp也只实现了这两个                                                                   
 i = 0; j = 0;                                                                                                       
 while (!ISspace(buf[j]) && (i < sizeof(method) - 1))                                                                
 {                                                                                                                   
  method[i] = buf[j];                                                                                                
  i++; j++;                                                                                                          
 }                                                                                                                   
 method[i] = '\0';                                                                                                   
 
 //判断是否方法为GET或者POST,如果不是直接调用unimplemented给client返回错误信息
 if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))                                                        
 {                                                                                                                   
  unimplemented(client);                                                                                             
  return;                                                                                                            
 }                                                                                                                   

//如果是POST方法,直接置位cgi标志,这里需要再回顾POST和GET方法的区别
 if (strcasecmp(method, "POST") == 0)                                                                                
  cgi = 1;                                                                                                           


//下面的就是提取请求行的URL
 i = 0;
 //处理空格                                                                                                              
 while (ISspace(buf[j]) && (j < sizeof(buf)))                                                                        
  j++;   
 //获取URL                                                                                                           
 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))                                              
 {                                                                                                                   
  url[i] = buf[j];                                                                                                   
  i++; j++;                                                                                                          
 }                                                                                                                   
 url[i] = '\0';                                                                                                      

//直接处理请求,首先判断是否请求方法为GET
 if (strcasecmp(method, "GET") == 0)                                                                                 
 {
                                                                                                      
  query_string = url;
  //提取url中?之前的url                                                                                              
  while ((*query_string != '?') && (*query_string != '\0'))                                                          
   query_string++;                                                                                                   
  if (*query_string == '?')                                                                                          
  { 
   //在GET方法中置位cgi,并且将url中的?替换为'\0',而query_string指向第一个key=value的地址                                                                                                                
   cgi = 1;                                                                                                          
   *query_string = '\0';                                                                                             
   query_string++;                                                                                                   
  }                                                                                                                  
 }                                                                                                                   

 //以打开主页为例,此时的url=/,此时path应为htdocs/
 sprintf(path, "htdocs%s", url); 
 //在htdocs/后面添加index.html,特别注意上面的sprintf代码为hard code!                                                                                 
 if (path[strlen(path) - 1] == '/')                                                                                  
  strcat(path, "index.html");
 //这里要注意进程的默认搜索目录为当前工作目录                                                                                  
 if (stat(path, &st) == -1) {
  //如果没有找到该资源,直接读取完整个请求头部,并向client发送相应的错误信息                                                                                        
  while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */                                          
   numchars = get_line(client, buf, sizeof(buf));                                                                    
  not_found(client);                                                                                                 
 }                                                                                                                   
 else                                                                                                                
 {
  //如果url请求的文件是个目录,则变成htdocs/xxx/index.html                                                                                                                  
  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标志。                                                                                                                                                                                                                                                      
   cgi = 1;                                                                                                          
  if (!cgi)                                                                                                          
   serve_file(client, path);                                                                                         
  else                                                                                                               
   execute_cgi(client, path, method, query_string);                                                                  
 }                                                                                                                   

 close(client);                                                                                                      
}    

稍微回顾下整体的逻辑:

  • 最开始读取报文请求行,或者METHOD,如果该方法不被支持返回错误信息,断开连接
  • 如果是POST方法,直接置位cgi标志
  • 无论是POST还是GET,接下来都提取请求行的URL
  • 如果是POST方法,且url含传参,则置位cgi标志
    • 这里的代码逻辑可以优化,将第二点和第四点合并下,可以清楚很多。
  • 无论是POST或者GET都在最后执行相应动作:
    • 重新定位资源位置
    • 判断资源是否可执行,如果可执行则调用execute_cgi,否则调用serve_file

cgi是原作者实现动态生成网页的工具,并不属于HTTP协议处理的逻辑范畴。

4.2.1 serve_file函数细节

/**********************************************************************/
/* Send a regular file to the client.  Use headers, and report
 * errors to client if they occur.
 * Parameters: a pointer to a file structure produced from the socket
 *              file descriptor
 *             the name of the file to serve */
/**********************************************************************/
void serve_file(int client, const char *filename)
{
 FILE *resource = NULL;
 int numchars = 1;
 char buf[1024];

 buf[0] = 'A'; buf[1] = '\0';
 //舍弃请求头部
 while ((numchars > 0) && strcmp("\n", buf))  /* read & discard headers */
  numchars = get_line(client, buf, sizeof(buf));

 //打开文件,读取资源
 resource = fopen(filename, "r");
 if (resource == NULL)
  not_found(client);
 else
 {
  headers(client, filename);
  cat(client, resource);
 }
 fclose(resource);
}

直接看看server响应报文头部信息,有固定的格式:

/**********************************************************************/
/* Return the informational HTTP headers about a file. */
/* Parameters: the socket to print the headers on
 *             the name of the file */
/**********************************************************************/
void headers(int client, const char *filename)
{
 char buf[1024];
 (void)filename;  /* could use filename to determine file type */
 
 //状态行
 strcpy(buf, "HTTP/1.0 200 OK\r\n");
 send(client, buf, strlen(buf), 0);
 //响应头部
 strcpy(buf, SERVER_STRING);
 send(client, buf, strlen(buf), 0);
 sprintf(buf, "Content-Type: text/html\r\n");
 send(client, buf, strlen(buf), 0);
 //空行
 strcpy(buf, "\r\n");
 send(client, buf, strlen(buf), 0);
}

而发送为响应状态和头部信息之后,就要回传给server数据:

/**********************************************************************/
/* Put the entire contents of a file out on a socket.  This function
 * is named after the UNIX "cat" command, because it might have been
 * easier just to do something like pipe, fork, and exec("cat").
 * Parameters: the client socket descriptor
 *             FILE pointer for the file to cat */
/**********************************************************************/
void cat(int client, FILE *resource)
{
 char buf[1024];

 fgets(buf, sizeof(buf), resource);
 while (!feof(resource))
 {
  send(client, buf, strlen(buf), 0);
  fgets(buf, sizeof(buf), resource);
 }
}

今天花了一个小时,就到这里,可以明显的知道,如果我们想静态的展示我们的自己的网页,只需要对源码进行稍微的修改,就可以实现该目的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值