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)

大家都在看

  • PHP 程序员的技术成长规划

    本文按照目前主流技术做了一个基本的梳理,整个是假设PHP程序员不是基础非常扎实的情况进行的设定,并且所有设定都非常具体明确清晰,可能会让人觉得不适,请理解仅代表一家之言。(未来技术…

    技术杂谈 2023年5月31日
    094
  • lightdb生成随机数

    lightdb内置函数可生成 `[zjh@hs-10-20-30-193 data]$ ltsql -p9999ltsql (13.3-22.2)Type “help&…

    技术杂谈 2023年6月1日
    099
  • SpringSecurity中的CSRF解读

    从刚开始学习SpringSecurity时,在配置类中一直存在这样一行代码:http.csrfo.disable() 如果没有这行代码导致用户无法被认证。这行代码的含义是:关闭 c…

    技术杂谈 2023年7月11日
    079
  • 卡卡游戏引擎之快速入门

    前言 卡卡游戏引擎是一个跨平台的2d游戏引擎,并配有跨平台的卡卡编辑器作为游戏开发辅助工具。卡卡游戏引擎采用mvc开发模式,即模型(Model)-视图(View)-控制器(Cont…

    技术杂谈 2023年5月31日
    079
  • 用实例讲解RSA加密算法(精)

    RSA是第一个比较完善的 公开密钥算法,它既能用于加密,也能用于数字签名。RSA以它的三个发明者Ron Rivest, Adi Shamir, Leonard Adleman的名字…

    技术杂谈 2023年5月31日
    091
  • SOLR查询匹配关键词的方式

    如果在字段后直接写关键词,solr对条件关键词分词后,各分词之间按照OR的关系进行匹配 如果查询条件关键词用半角双引号括叫起来,则分词间用AND关系进行匹配 尽管使用双引号括起来,…

    技术杂谈 2023年5月31日
    0118
  • Weblogic页面应用查询oracle数据库后台报错或页面日期格式显示错误

    问题:在生产环境中有两台WEB服务器,分别为227和228,部署的应用代码都是每日同步的,两边完全一致,但是某些页面查询数据时,227无结果,并且后台报java数组越界的错误,而2…

    技术杂谈 2023年7月11日
    080
  • EBS QRCODE

    qrcode_java0.50beta10.tar [root@ebs12vis ~]# su – applmgr [applmgr@ebs12vis ~]$ cd $…

    技术杂谈 2023年6月1日
    089
  • 在win10上安装face_recognition(人脸识别)

    github上有个项目face_recognition,是用于人脸识别的 主要是window上安装这个项目会繁琐些,linux上据项目文档上介绍是妥妥的。 真机python版本:3…

    技术杂谈 2023年6月21日
    0122
  • 抓到 Netty 一个 Bug,顺带来透彻地聊一下 Netty 是如何高效接收网络连接的

    本系列Netty源码解析文章基于 4.1.56.Final版本,大家如果看到图片显示不了的话,可以查看公众号原文 对于一个高性能网络通讯框架来说,最最重要也是最核心的工作就是如何高…

    技术杂谈 2023年7月11日
    085
  • vue

    【Vant的Rem适配方案】 Rem适配 Vant 中的样式默认使用px作为单位,如果需要使用rem单位,推荐使用以下两个工具: postcss-pxtorem 是一款 postc…

    技术杂谈 2023年5月31日
    0100
  • 神秘的backlog参数与TCP连接队列

    原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。 简介 这要从一次压测项目说起,那是我们公司的系统与另几家同行公司的系统做性能比拼,性能数据会直接影响…

    技术杂谈 2023年7月25日
    089
  • vue部署在nginx后刷新404,nginx.conf一行代码解决掉

    直接 vim conf/nginx.conf文件 server {listen 8081;server_name localhost; charset koi8-r; access…

    技术杂谈 2023年6月21日
    0102
  • 博主-橄榄山软件创始人-其人其事

    在Autodesk工作了7年4个月,那是一段激情。开心的工作经历。14年前当我还是在读研时。从事AutoCAD上的二次开发(硕士课题是搭建一个有限元程序的前处理建模软件), 增加A…

    技术杂谈 2023年5月31日
    092
  • Nginx-初识篇

    一、Nginx的简介 1. Nginx是什么 Nginx(engine x)是一个具有高性能的【HTTP】和【反向代理】的【Web服务器】,同时也是一个【POP3/SMTP/IMA…

    技术杂谈 2023年7月25日
    083
  • LeetCode58.最后一个单词的长度

    给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。 单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。 示例 1: 输入:…

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