CSAPP这本书好厚,曾经一度以为可能买了就是吃灰,很难读完。每次拿起觉得太厚了,简单的翻翻又放下。后来我就不整体看,每周花个1-2个小时的时间,只看一章,看完一章在目录打个勾,而看书的顺序,完全看自己的喜好,没想到,还比较轻松,这么厚的书,可能还有2-3章就读完了,感觉这个办法对于读厚书来说还是不错的。以后就用这种方法读书吧。
这篇文章就是按照第11章的代码来实践和测试一个简单的web服务器,麻雀虽小,五脏俱全。通过简单的实现代码,可以帮助我们了解HTTP协议的基本内容,了解一般的web工作原理。
一 有什么用
除了了解web流程和http协议之外,这个web服务器有什么应用场景那,我想还是有的。比如:
嵌入在你的程序里面,可以做程序的内部状态的监控,前台需要了解程序的运行状态,调用你的http服务就可以了,还不用担心为此单独部署一个web服务器。
在家用路由器等小型的设备上,如果部署个常见的tomcat或jetty,nginx等服务器比较重,占内存和容量都是不可接受的,而且路由器管理的需要的功能有限,可以嵌入这种小型的服务器。
可以做个简单的后门程序
二 Web服务器工作原理
简化版本的http交互流程图如下:从上图可以看出,其实web服务器的功能从整体看还是比较简单,就是接受web请求,解析http协议请求内容,然后处理请求,返回。这里面处理分为两类:
静态内容处理,比如读取本地的html文件,css文件,图片文件直接返回。
动态内容处理,比如执行一个可执行文件,将结果返回给客户端。静态文件内容处理,没啥好说的,就是读取文件,把内容write发送给客户端;动态内容涉及到的问题比较多,如何把参数传递给服务器,服务器如何调用可执行文件执行,可执行文件的执行结果又以怎么样的形式返回给客户端。一旦有可能产生混乱的地方也就有了标准,CGI就是这个标准。
参数传递:CGI定义了很多环境变量,http服务器可以设置环境变量,调用程序时候,程序通过环境变量:QUERY_STRING 来获取参数。
执行结果返回:可以将CGI程序的输出重定向到客户端的描述符上,从而达到返回的目的。
三 实践
代码引用了书里面的封装的代码,本想把整个代码都拿出来的,发现可能也挺无聊的,找些重点的有意思的代码分析下,就够了:
// 处理静态文件
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);
}
其中:
srcfd = Open(filename, O_RDONLY, 0);
srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);
Close(srcfd);
Mmap函数创建一个新的只读的私有的虚拟存储器区域,将文件描述符srcfd指向的文件,映射到这个内存区域,返回的是指向新虚拟内存区域的地址,这样可以减少一次内存的拷贝。
看下调用CGI程序提供动态服务的函数:
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) {
// 设置CGI 应用使用的参数
setenv("QUERY_STRING", cgiargs, 1);
// 将CGI应用的输出重定向到客户端的socket上去
Dup2(fd, STDOUT_FILENO);
// 执行CGI应用
Execve(filename, emptylist, environ);
}
// 回收
Wait(NULL);
}
关键函数就是两步:
// 设置CGI 应用使用的参数
setenv("QUERY_STRING", cgiargs, 1);
// 将CGI应用的输出重定向到客户端的socket上去
Dup2(fd, STDOUT_FILENO);
像上面我们说的一样,CGI程序通过获取QUERY_STRING环境变量来获取它的执行参数。获取的时候:
// 获取环境变量
char * buf = getenv("QUERY_STRING");
五 测试
5.1 基本html测试:
![](https://i-blog.csdnimg.cn/blog_migrate/60fbf784a79d824718441442ed6410d7.png)
5.2 CGI测试
![](https://i-blog.csdnimg.cn/blog_migrate/c3443433d50c5d806ecc7790d3ad2ef7.png)
如果改成了系统的一些命令,再增加交互,就是一个后门了。
如果需要代码,可以联系我。
六诗词欣赏
雨霖铃·寒蝉凄切
[宋] [柳永]
寒蝉凄切,对长亭晚,骤雨初歇。
都门帐饮无绪,留恋处,兰舟催发。
执手相看泪眼,竟无语凝噎。
念去去,千里烟波,暮霭沉沉楚天阔。
多情自古伤离别,更那堪冷落清秋节!
今宵酒醒何处?杨柳岸,晓风残月。
此去经年,应是良辰好景虚设。
便纵有千种风情,更与何人说?