CSAPP Tiny web server源代码分析及搭建执行

1. Web基础

webclient和server之间的交互使用的是一个基于文本的应用级协议HTTP(超文本传输协议)。

一个webclient(即浏览器)打开一个到server的因特网连接,而且请求某些内容。server响应所请求的内容,然后关闭连接。浏览器读取这些内容。并把它显示在屏幕上。

对于webclient和server而言。内容是与一个MIME类型相关的字节序列。

常见的MIME类型:

MIME类型 描写叙述 text/html HTML页面 text/plain 无格式文本 image/gif GIF格式编码的二进制图像 image/jpeg JPEG格式编码的二进制图像

webserver以两种不同的方式向客服端提供内容:
1. 静态内容:取一个磁盘文件。并将它的内容返回给client
2. 动态内容:执行一个可执行文件,并将它的输出返回给client

统一资源定位符:URL

http://www.google.com:80/index.html

表示因特网主机 www.google.com 上一个称为 index.html 的HTML文件。它是由一个监听port80的Webserver所管理的。

HTTP默认port号为80

可执行文件的URL能够在文件名称后包含程序參数, “?”字符分隔文件名称和參数,而且每一个參数都用”&”字符分隔开。如:

http://www.ics.cs.cmu.edu:8000/cgi-bin/adder?
123&456

表示一个 /cgi-bin/adder 的可执行文件,带两个參数字符串为 123 和 456

确定一个URL指向的是静态内容还是动态内容没有标准的规则,常见的方法就是把全部的可执行文件都放在 cgi-bin 文件夹中

2. HTTP

HTTP标准要求每一个文本行都由一对回车和换行符来结束

CSAPP Tiny web server源代码分析及搭建执行

(1)HTTP请求

一个HTTP请求:一个请求行(request line) 后面尾随0个或多个请求报头(request header), 再尾随一个空的文本行来终止报头

请求行: <method> <uri> <version></version></uri></method>
HTTP支持很多方法。包含 GET,POST,PUT,DELETE,OPTIONS,HEAD,TRACE。

URI是对应URL的后缀,包含文件名称和可选參数
version 字段表示该请求所遵循的HTTP版本号

请求报头: <header name> : <header data></header></header> 为server提供了额外的信息。比如浏览器的版本号类型
HTTP 1.1中 一个IP地址的server能够是 多宿主主机。比如 www.host1.com www.host2.com 能够存在于同一server上。

HTTP 1.1 中必须有 host 请求报头,如 host:www.google.com:80 假设没有这个host请求报头,每一个主机名都仅仅有唯一IP,IP地址非常快将用尽。

(2)HTTP响应

一个HTTP响应:一个响应行(response line) 后面尾随0个或多个响应报头(response header)。再尾随一个空的文本行来终止报头,最后尾随一个响应主体(response body)

响应行: <version> <status code> <status message></status></status></version>
status code 是一个三位的正整数

状态代码 状态消息 描写叙述 200 成功 处理请求无误 301 永久移动 内容移动到位置头中指明的主机上 400 错误请求 server不能理解请求 403 禁止 server无权訪问所请求的文件 404 未发现 server不能找到所请求的文件 501 未实现 server不支持请求的方法 505 HTTP版本号不支持 server不支持请求的版本号

两个最重要的响应报头:
Content-Type 告诉client响应主体中内容的MIME类型
Content-Length 指示响应主体的字节大小
响应主体中包含着被请求的内容。

3.服务动态内容

(1) client怎样将程序參数传递给server

GET请求的參数在URI中传递, “?”字符分隔了文件名称和參数,每一个參数都用一个”&”分隔开,參数中不同意有空格,必须用字符串”%20″来表示
HTTP POST请求的參数是在请求主体中而不是 URI中传递的

(2)server怎样将參数传递给子进程

GET /cgi-bin/adder?123&456 HTTP/1.1

它调用 fork 来创建一个子进程。并调用 execve 在子进程的上下文中执行 /cgi-bin/adder 程序

在调用 execve 之前,子进程将CGI环境变量 QUERY_STRING 设置为”123&456″, adder 程序在执行时能够用unix getenv 函数来引用它

(3)server怎样将其它信息传递给子进程

环境变量 描写叙述 QUERY_STRING 程序參数 SERVER_PORT 父进程侦听的port REQUEST_METHOD GET 或 POST REMOTE_HOST client的域名 REMOTE_ADDR client的点分十进制IP地址 CONTENT_TYPE 仅仅对POST而言。请求体的MIME类型 CONTENT_LENGTH 仅仅对POST而言,请求体的字节大小

(4) 子进程将它的输出发送到哪里

一个CGI程序将它的动态内容发送到标准输出。在子进程载入并执行CGI程序之前,它使用UNIX dup2 函数将它标准输出重定向到和client相关连的已连接描写叙述符
因此,不论什么CGI程序写到标准输出的东西都会直接到达client

4. 综合: Tiny web server源代码及分析

(1) main程序

Tiny是一个迭代server,监听在命令行中传递来的port上的连接请求,在通过调用 open_listenfd 函数打开一个监听套接字以后。执行无限server循环,不断接受连接请求(第16行)。执行事务(第17行),并关闭连接它的那一端(第18行)

int main(int argc, char **argv)
{
        int listenfd, connfd, port, clientlen;
        struct sockaddr_in clientaddr;

        if (argc != 2) {
                fprintf(stderr, "usage: %s \n", argv[0]);
                exit(1);
        }
        port = atoi(argv[1]);

        listenfd = Open_listenfd(port);
        while (1) {
                clientlen = sizeof(clientaddr);
                connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);
                doit(connfd);
                Close(connfd);
        }
}

(2) doit函数

doit函数处理一个HTTP事物,首先读和解析请求行(request line)(第11-12行),注意,我们使用rio_readlineb函数读取请求行。
Tiny仅仅支持GET方法,假设client请求其它方法,发送一个错误信息。

然后将URI解析为一个文件名称和一个可能为空的CGI參数字符串。而且设置一个标志表明请求的是静态内容还是动态内容(第21行)
假设请求的是静态内容。就验证是否为普通文件,有读权限(第29行)
假设请求的是动态内容,就验证是否为可执行文件(第37行),假设是,就提供动态内容(第42行)

void doit(int fd)
{
    int is_static;
    struct stat sbuf;
    char buf[MAXLINE], method[MAXLINE], uri[MAXLINE], version[MAXLINE];
    char filename[MAXLINE], cgiargs[MAXLINE];
    rio_t rio;

    Rio_readinitb(&rio, fd);
    Rio_readlineb(&rio, buf, MAXLINE);
    sscanf(buf, "%s %s %s", method, uri, version);
    if (strcasecmp(method, "GET")) {
       clienterror(fd, method, "501", "Not Implemented",
                "Tiny does not implement this method");
        return;
    }
    read_requesthdrs(&rio);

    is_static = parse_uri(uri, filename, cgiargs);
    if (stat(filename, &sbuf) < 0) {
    clienterror(fd, filename, "404", "Not found",
            "Tiny couldn't find this file");
    return;
    }

    if (is_static) {
    if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) {
        clienterror(fd, filename, "403", "Forbidden",
            "Tiny couldn't read the file");
        return;
    }
    serve_static(fd, filename, sbuf.st_size);
    }
    else {
    if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) {
        clienterror(fd, filename, "403", "Forbidden",
            "Tiny couldn't run the CGI program");
        return;
    }
    serve_dynamic(fd, filename, cgiargs);
    }
}

(4)read_requesthdrs 函数

Tiny不使用请求报头中的不论什么信息。仅仅调用 read_requesthdrs函数来读取并忽略这些报头。
注意。终止请求报头的空文本行是由 回车和换行符组成的。在第6行中检查

void read_requesthdrs(rio_t *rp)
{
    char buf[MAXLINE];

    Rio_readlineb(rp, buf, MAXLINE);
    while(strcmp(buf, "\r\n")) {
    Rio_readlineb(rp, buf, MAXLINE);
    printf("%s", buf);
    }
    return;
}

(5)parse_uri 函数

Tiny假设静态内容的主文件夹就是当前文件夹,可执行文件的主文件夹是 ./cgi-bin/ 不论什么包含字符串 cgi-bin 的URI都觉得是对动态内容的请求。
首先将URI解析为一个文件名称和一个可选的CGI參数字符串。
假设请求的是静态内容(第5行)。就清除CGI參数串(第6行)。然后将URI转换为一个相对的unix 路径名,比如 ./index.html
假设URI是用’/’ 结尾的(第9行) ,我们就把默认的文件名称加在后面(第10行)
假设请求的是动态内容(第13行),就会抽取全部的CGI參数(第14-20行),并将URI剩下的部分转换为一个对应的unix文件名称(第21-22行)

int parse_uri(char *uri, char *filename, char *cgiargs)
{
    char *ptr;

    if (!strstr(uri, "cgi-bin")) {
    strcpy(cgiargs, "");
    strcpy(filename, ".");
    strcat(filename, uri);
    if (uri[strlen(uri)-1] == '/')
        strcat(filename, "home.html");
    return 1;
    }
    else {
    ptr = index(uri, '?');
    if (ptr) {
        strcpy(cgiargs, ptr+1);
        *ptr = '\0';
    }
    else
        strcpy(cgiargs, "");
    strcpy(filename, ".");
    strcat(filename, uri);
    return 0;
    }
}

(6)serve_static 函数

Tiny提供四种不同的静态内容:HTML文件、无格式的文本文件、GIF编码格式图片、JPEG编码格式图片
serve_static 函数发送一个HTTP响应,其主体包含一个本地文件的内容。
首先我们通过检查文件名称的后缀来推断文件类型(第7行)。而且发送响应行和响应报头给client(第8-12行)。

注意用一个空行终止报头
第16行,我们使用 unix mmap函数将被请求文件映射到一个虚拟问存储器空间,调用mmap将文件srcfd的前filesize个字节映射到一个从地址srcp開始的私有仅仅读虚拟存储器区域。
一旦文件映射到存储器,就不再须要它的描写叙述符了,关闭这个文件(第17行)。
第18行执行的是到client的实际文件传动。rio_writen 函数拷贝从srcp位置開始的filesize个字节(已经被映射到了所请求的文件) 到client的已连接描写叙述符。
第19行释放了映射的虚拟存储器区域,避免潜在的存储器泄漏

void serve_static(int fd, char *filename, int filesize)
{
    int srcfd;
    char *srcp, filetype[MAXLINE], buf[MAXBUF];

    get_filetype(filename, filetype);
    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);
    sprintf(buf, "%sContent-length: %d\r\n", buf, filesize);
    sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);
    Rio_writen(fd, buf, strlen(buf));

    srcfd = Open(filename, O_RDONLY, 0);
    srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
    Close(srcfd);
    Rio_writen(fd, srcp, filesize);
    Munmap(srcp, filesize);
}

void get_filetype(char *filename, char *filetype)
{
    if (strstr(filename, ".html"))
    strcpy(filetype, "text/html");
    else if (strstr(filename, ".gif"))
    strcpy(filetype, "image/gif");
    else if (strstr(filename, ".jpg"))
    strcpy(filetype, "image/jpeg");
    else
    strcpy(filetype, "text/plain");
}

(7)serve_dynamic 函数

Tiny通过派生一个子进程并在子进程的上下文中执行一个CGI程序。来提供各种类型的动态内容。
serve_dynamic函数一開始就向client发送一个表明成功的响应行,,同一时候还包含带有信息的server报头。

第13行,子进程用来自请求URI的CGI參数初始化QUERY_STRING环境变量
第14行,子进程重定向它的标准输出到已连接文件描写叙述符
第15行,载入并执行CGI程序。由于CGI程序执行在子进程的上下文中,它能够訪问全部在调用execve函数之前就存在的打开文件和环境变量
第17行,父进程堵塞在对wait的调用中,等待子进程终止的时候。回收操作系统那个分配给子进程的资源

void serve_dynamic(int fd, char *filename, char *cgiargs)
{
    char buf[MAXLINE], *emptylist[] = { NULL };

    sprintf(buf, "HTTP/1.0 200 OK\r\n");
    Rio_writen(fd, buf, strlen(buf));
    sprintf(buf, "Server: Tiny Web Server\r\n");
    Rio_writen(fd, buf, strlen(buf));

    if (Fork() == 0) {

    setenv("QUERY_STRING", cgiargs, 1);
    Dup2(fd, STDOUT_FILENO);
    Execve(filename, emptylist, environ);
    }
    Wait(NULL);
}

5.调试及执行

(1) 下载csapp.h 和 csapp.c

http://csapp.cs.cmu.edu/public/ics2/code/include/csapp.h
http://csapp.cs.cmu.edu/public/ics2/code/src/csapp.c
关于CSAPP代码下载的技巧:比方code/conc/sbuf.c,对应的下载地址在
http://csapp.cs.cmu.edu/public/ics2/code/conc/sbuf.c

(2) 编译

将全部源文件tiny.c、csapp.c和csapp.h放在同一个文件夹下。

$ gcc -o tiny tiny.c csapp.c -lpthread

注:加-lpthread是由于csapp.c中有些函数用了多线程库

(3) 执行前准备

  1. 将被訪问的文件放在tiny同级文件夹下(home.html、photo.jpg)
<html>
<head>
<title>Hello Worldtitle>
head>
<body>
<h1>Welcome to Tiny Web Serverh1>
body>
html>
  1. 将測试用CGI程序放到cgi-bin文件夹下。并编译成可执行程序
$ gcc -o adder adder.c

(4) 执行流程及其结果

  1. 执行Tiny程序,并指定port号(1024–49151可用,其它为知名port)
$ ./tiny 1024
  1. 浏览器訪问静态内容(home.html)
    CSAPP Tiny web server源代码分析及搭建执行
  2. 浏览器訪问不存在的内容
    CSAPP Tiny web server源代码分析及搭建执行
  3. 浏览器訪问动态内容
    CSAPP Tiny web server源代码分析及搭建执行
  4. 还能够訪问图片哦
    CSAPP Tiny web server源代码分析及搭建执行

(5) Telnet 測试

  1. 连接到Tinyserver
$ telnet localhost 1024
  1. 输入请求头(注意空行)
GET /home.html HTTP/1.0

  1. 验证结果(注意空行)
<span class="hljs-status">HTTP/1.0 <span class="hljs-number">200</span> OK</span>
<span class="hljs-attribute">Server</span>: <span class="hljs-string">Tiny Web Server</span>
<span class="hljs-attribute">Content-length</span>: <span class="hljs-string">108</span>
<span class="hljs-attribute">Content-type</span>: <span class="hljs-string">text/html</span>

<span class="xml"><span class="hljs-tag"><<span class="hljs-title">html</span>></span>
<span class="hljs-tag"><<span class="hljs-title">head</span>></span>
<span class="hljs-tag"><<span class="hljs-title">title</span>></span>Hello World<span class="hljs-tag">title</span>></span>
<span class="hljs-tag">head</span>>
<span class="hljs-tag"><<span class="hljs-title">body</span>></span>
<span class="hljs-tag"><<span class="hljs-title">h1</span>></span>Welcome to Tiny Web Server<span class="hljs-tag">h1</span>>
<span class="hljs-tag">body</span>>
<span class="hljs-tag">html</span>>
Connection closed by foreign host.

  1. 错误的返回
<span class="hljs-status">HTTP/1.0 <span class="hljs-number">404</span> Not found</span>
<span class="hljs-attribute">Content-type</span>: <span class="hljs-string">text/html</span>
<span class="hljs-attribute">Content-length</span>: <span class="hljs-string">143</span>

<span class="xml"><span class="hljs-tag"><<span class="hljs-title">html</span>></span><span class="hljs-tag"><<span class="hljs-title">title</span>></span>Tiny Error<span class="hljs-tag">title</span>></span><span class="hljs-tag"><<span class="hljs-title">body</span> <span class="hljs-attribute">bgcolor</span>=<span class="hljs-value">ffffff</span>></span>
404: Not found
<span class="hljs-tag"><<span class="hljs-title">p</span>></span>Tiny couldn't find this file: .kkk
<span class="hljs-tag"><<span class="hljs-title">hr</span>></span><span class="hljs-tag"><<span class="hljs-title">em</span>></span>The Tiny Web Server<span class="hljs-tag">em</span>>
Connection closed by foreign host.

(6) 提醒

须要注意的是 HTTP 协议的头部和数据之间有一个空行,假设浏览器无法查看到内容,而通过 Telnet 能够得到数据,则能够推断为少了一个空行。

Original: https://www.cnblogs.com/yfceshi/p/7401085.html
Author: yfceshi
Title: CSAPP Tiny web server源代码分析及搭建执行

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/554332/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 关于连接服务器redis的教程

    第一步:下载RedisDesktopManager 这个百度一搜就有了,但是现在的版本ssh用不了建议找可以用的版本,这个百度,懂得都懂。 第二步:服务器宝塔redis设置 在配置…

    技术杂谈 2023年7月25日
    066
  • 微软拼音输入法删除

    以 win10 21H1 为例说明,其他win10版本也大同小异,不再赘述。 主要设置位置在:设置—-时间和语言—-语言—-首选语言(选项)&#…

    技术杂谈 2023年5月31日
    098
  • 彻底学会Selenium元素定位

    转载请注明出处❤️ 作者:测试蔡坨坨 原文链接:caituotuo.top/63099961.html 你好,我是测试蔡坨坨。 最近收到不少初学UI自动化测试的小伙伴私信,对于元素…

    技术杂谈 2023年7月11日
    095
  • nodejs的http请求axios

    http相关modules HTTP – the Standard Library Request Axios SuperAgent 推荐使用axios 或者super agent…

    技术杂谈 2023年5月31日
    0101
  • mosquitto 常用命令

    原文:https://www.cnblogs.com/smartlife/articles/10182136.html 常用命令 订阅主题mosquitto_sub -h 192….

    技术杂谈 2023年6月1日
    085
  • 危险的赌注

    低代码应用平台(LCAP – Low Code Application Platforms)在多样、复杂的现代软件开发情势下应运而生。根据 Gartner 的数据,Me…

    技术杂谈 2023年6月21日
    0158
  • 【软考】软件架构

    1.软件架构风格 1.1 数据流风格 1.2 调用/返回风格 1.3 独立构件风格 1.4 虚拟机风格 1.5 仓库风格 2.相关考题 1.软件架构风格 软件架构分为以下几种风格:…

    技术杂谈 2023年5月31日
    093
  • IEEE浮点数向偶数舍

    CSAPP ​ 向偶数舍入初看上去好像是个相当随意的目标——有什么理由偏向取偶数呢?为什么不始终把位于两个可表示的值中间的值都向上舍入呢?使用这种方法的一个问题就是很容易假想到这样…

    技术杂谈 2023年7月11日
    086
  • python数据可视化-matplotlib入门(5)-饼图和堆叠图

    饼图常用于统计学模块,画饼图用到的方法为:pie( ) 一、pie()函数用来绘制饼图 pie(x, explode=None, labels=None, colors=None,…

    技术杂谈 2023年7月25日
    0107
  • 异步、邮件、定时任务

    异步、邮件、定时任务 14.1 异步任务 编写一个业务测试类 文件路径:com–dzj–service–AsynService.java @Se…

    技术杂谈 2023年6月21日
    0103
  • scala中 怎样 调用 java中的接口

    直接 继承 object GetCustomHandler extends ResultSetHandler[Int] { override def handle(rs: Resu…

    技术杂谈 2023年7月11日
    068
  • 数据库基础,看完这篇就够了!

    转载请注明出处❤️ 作者:测试蔡坨坨 原文链接:caituotuo.top/747a74ea.html 你好,我是测试蔡坨坨。 对于测试同学来说,除了知道测试基础知识外,还需要掌握…

    技术杂谈 2023年7月11日
    070
  • Zookeeper选举Leader源码剖析

    开始分析 【1】分析入口类做了什么 //org.apache.zookeeper.server.quorum包下QuorumPeerMain类 public static void…

    技术杂谈 2023年7月23日
    053
  • spring高版本循环依赖报错问题

    java;gutter:true;2.6.0后关闭了循环引用,需要开启报错信息: org.springframework.beans.factory.UnsatisfiedDepe…

    技术杂谈 2023年7月25日
    074
  • 在linux里部署OA项目环境

    1.首先要实现linux可以从windows系统里把文件拖到linux里 ①挂载光盘 [root@localhost ~]# mkdir /mnt/cdrom //创建挂载点 [r…

    技术杂谈 2023年7月11日
    065
  • HTB靶场记录之Jarvis

    1、靶机介绍 这次的靶机是Jarvis,很适合练手,难点在提权。 2、信息收集 这里我先用autorecon进行信息收集。 Autorecon集成nmap,nikto等操作可以比较…

    技术杂谈 2023年5月31日
    075
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球