首页 > 编程语言 >Tinyhttpd:源码分析【3】

Tinyhttpd:源码分析【3】

时间:2023-06-06 11:45:51浏览次数:53  
标签:分析 cgi client 源码 query st path Tinyhttpd buf

一、问题引入

通过 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 %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[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] = '\0';

    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] = '\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[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);
}

解析第一行

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] = '\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]) && (j < numchars))
    j++;
while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars))
{
    url[i] = buf[j];
    i++; j++;
}
url[i] = '\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[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 \r\n
  2. GET /index.html \r\n
  3. GET /color.cgi \r\n
  4. GET /color.cgi?color=pink \r\n
  • POST方法

可以处理请求格式:

  1. POST /color.cgi HTTP/1.1 \r\n + "color" = "green"

四、参考引用

EZLippi/Tinyhttpd

标签:分析,cgi,client,源码,query,st,path,Tinyhttpd,buf
From: https://www.cnblogs.com/caojun97/p/17276322.html

相关文章

  • 人工智能创新挑战赛:助力精准气象和海洋预测Baseline[2]:数据探索性分析(温度风场可视化)
    “AIEarth”人工智能创新挑战赛:助力精准气象和海洋预测Baseline[2]:数据探索性分析(温度风场可视化)、CNN+LSTM模型建模1.气象海洋预测-数据分析数据分析是解决一个数据挖掘任务的重要一环,通过数据分析,我们可以了解标签的分布、数据中存在的缺失值和异常值、特征与标签之间的相关......
  • flinkv1.14启动过程分析
    今天阅读了一下flinkv1.14的代码,首先分析一下flink启动的过程。首先分2种,一种是SessionClusterEntrypoint,一种是JobClusterEntrypoint。分别对应session模式和per-job模式。session模式就是一次启动,可以执行多个job,执行完job还有后台进程在等待用户提交新的job。per-job模式......
  • 对粮食产量进行大数据分析
    一、选题背景 近年来,我国各个省份的粮食总产量以及增量增速逐渐倍受关注,如何增加粮食产量也成为了人们关注的热点话题。通过互联网上的信息发布网站,我获取并整合了各省粮食产量数据。其中,“中国产业信息网”每年发布的国内新一年的粮食产量信息。网站发布的信息包括近些年的粮......
  • R语言ARMA-GARCH模型金融产品价格实证分析黄金价格时间序列
    全文链接:http://tecdat.cn/?p=32677原文出处:拓端数据部落公众号研究黄金价格的动态演变过程至关重要。文中以黄金交易市场下午定盘价格为基础,帮助客户利用时间序列的相关理论,建立了黄金价格的ARMA-GARCH模型,并对数据进行了实证分析,其结果非常接近。利用该模型可动态刻画黄金......
  • 对人力资源分析案例研究数据集进行数据分析
    一.选题背景近年就业面临着诸多挑战。一方面,经济的不景气和就业市场的不稳定性使得就业难度加大,就业形势越来越严峻。另一方面,高校毕业生的数量不断增加,而就业岗位的数量却没有相应增加,导致竞争激烈,难以找到合适的工作。此外,还有一些特殊的问题,如女性就业歧视、农村学生就业难等,......
  • Python爬取郑州安居客租房数据采集分析
    一、选题背景在现在,虽然我国实行楼市调控,使得总体的房价稳定下来,但是我国房价还是处于一个高水平之上。在这种情况下,大批在郑奋斗的年轻人选择租房,所以此次数据分析可以使在郑的年轻人了解郑州租房现状,让年轻人在租房时可以选到更加适合的房源。二、爬虫设计方案1、爬虫网址郑......
  • Github--源码管理工具介绍
    源代码管理工具在实际软件开发中具有极其重要的作用。相比于相互拷贝源码,使用源代码管理工具更方便开发成员之间进行开发,且使用源码管理工具具有更高的保密性。在此,将对目前相对流行的源代码管理工具--Github进行简要介绍。Github作为源码管理工具,主要由两部分组成:本地数......
  • win10,vs2015深度学习目标检测YOLOV5+deepsort C++多目标跟踪代码实现,源码注释,拿来即
    int8,FP16等选择,而且拿来即用,自己再win10安装上驱动可以立即使用,不用在自己配置,支持答疑。自己辛苦整理的,求大佬打赏一顿饭钱。苦苦苦、平时比较比忙,自己后期会继续发布真实场景项目;欢迎下载。优点:1、架构清晰,yolov5和sort是分开单独写的,可以随意拆解拼接,都是对外接口。2、支持答疑......
  • 【Python网络爬虫课程设计】B站up主——老番茄视频数据爬取+数据可视化分析
    一、选题背景1.背景随着大数据时代的来临,网络爬虫在互联网中的地位将越来越重要。互联网中的数据是海量的,如何自动高效地获取互联网中我们感兴趣的信息并为我们所用是一个重要的问题,而爬虫技术就是为了解决这些问题而生的。对于身为数据科学与大数据技术专业的学生来说,网络......
  • 大数据分析--南瓜籽品种分类
    大数据分析--南瓜籽品种分类 1.选题背景南瓜籽为南瓜的种子,葫芦科南瓜属植物南瓜CucurbitamoschataDuch.的种子。一端略尖,外表黄白色,边缘稍有棱,表面带有毛茸。除去种皮,可见绿色菲薄的胚乳,具有健脑提神、降压镇痛、驱虫等功效。南瓜籽因其含有足量的蛋白质、脂肪、碳水化合物......