中国梅西涛 发表于 2023-6-6 16:00:33

Tinyhttpd:源码分析【3】

一、问题引入

通过 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_tclient_name_len = sizeof(client_name);
    pthread_t newthread;

    server_sock = startup(&port);
    printf("httpd running on port %d\n", 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;
    size_t numchars;
    char method;
    char url;
    char path;
    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 < sizeof(method) - 1))
    {
      method = buf;
      i++;
    }
    j=i;
    method = '\0';

    if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
    {
      unimplemented(client);
      return;
    }

    if (strcasecmp(method, "POST") == 0)
      cgi = 1;

    i = 0;
    while (ISspace(buf) && (j < numchars))
      j++;
    while (!ISspace(buf) && (i < sizeof(url) - 1) && (j < numchars))
    {
      url = buf;
      i++; j++;
    }
    url = '\0';

    if (strcasecmp(method, "GET") == 0)
    {
      query_string = url;
      while ((*query_string != '?') && (*query_string != '\0'))
            query_string++;
      if (*query_string == '?')
      {
            cgi = 1;
            *query_string = '\0';
            query_string++;
      }
    }

    sprintf(path, "htdocs%s", url);
    if (path == '/')
      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);
}解析第一行

http 请求行包括三个字段:请求方法、URL、协议版本。
解析请求方法:仅支持GET和POST。若为POST时,cgi标志位置1
numchars = get_line(client, buf, sizeof(buf));
i = 0; j = 0;
while (!ISspace(buf) && (i < sizeof(method) - 1))
{
    method = buf;
    i++;
}
j=i;
method = '\0';

if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
{
    unimplemented(client);
    return;
}

if (strcasecmp(method, "POST") == 0)
    cgi = 1;解析URL,并在GET方法时,将URL中的第一个?处替换为\0
i = 0;
while (ISspace(buf) && (j < numchars))
    j++;
while (!ISspace(buf) && (i < sizeof(url) - 1) && (j < numchars))
{
    url = buf;
    i++; j++;
}
url = '\0';

if (strcasecmp(method, "GET") == 0)
{
    query_string = url;
    while ((*query_string != '?') && (*query_string != '\0'))
      query_string++;
    if (*query_string == '?')
    {
      cgi = 1;
      *query_string = '\0';
      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 == '/')
    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方法
可以处理请求格式:

[*]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】 我们会及时删除侵权内容,谢谢合作!
页: [1]
查看完整版本: Tinyhttpd:源码分析【3】