这是个不错的源码,用C 语言写的一个超轻量的HttpServer, 开发应该是用不上了,但是对于学习Unix 网络编程,套接字,进程等等来说相当够用。源代码大约500行,属于一个比较容易理解的代码量。
阅读C 源码的好处在于你不必去考虑类和对象,不用花时间去看类里有哪些成员变量和成员函数,不用费力去构建类图。只需要按照过程阅读就好了。
在源码上我进行了注释,在这里我对其中涉及到的Unix知识进行一点解释,也算是给啃UNP做一个预热。
打开httpd.c,这一长串函数,最重要的就是 startup, accept_request, 和 execute_cgi,以及get_line。 下面是每个函数的功能。
accept_request: 处理从套接字上监听到的一个 HTTP 请求,在这里可以很大一部分地体现服务器处理请求流程。
- bad_request: 返回给客户端这是个错误请求,HTTP 状态吗 400 BAD REQUEST.
- cat: 读取服务器上某个文件写到 socket 套接字。
- cannot_execute: 主要处理发生在执行 cgi 程序时出现的错误。
- error_die: 把错误信息写到 perror 并退出。
- execute_cgi: 运行 cgi 程序的处理,也是个主要函数。
- get_line: 读取套接字的一行,把回车换行等情况都统一为换行符结束。
- headers: 把 HTTP 响应的头部写到套接字。
- not_found: 主要处理找不到请求的文件时的情况。
- sever_file: 调用 cat 把服务器文件返回给浏览器。
- startup: 初始化 httpd 服务,包括建立套接字,绑定端口,进行监听等。
- unimplemented: 返回给浏览器表明收到的 HTTP 请求所用的 method 不被支持。
下面找到main函数,开始往下读。因为没有学习过Unix网络编程,所以中间一大把时间都花在了wiki的扩展。
首先将流程讲一下。
- startup()启动服务器,在指定的端口绑定httpd服务。
- 因为打开了listen, 当accept 到HTTP 请求时,那么久派生出一个进程去运行accept_request()
- 在accept_request() 中,取出HTTP 请求,获取它的method(可能是GET 或 POST)和url 。对于GET来说,若这个GET 请求带有参数,那么久会存在于url 中的「?」之后,将它保存在query_string中。
- 然后格式化url,将它保存到path数组中,也就是浏览器请求的服务器文件路径,如果url以/结尾,那就在路径结尾默认加上index.html,用来表示访问主页。
- 检验这个路径,如果不合法则返回错误。如果合法,再如果是一个没有参数的GET 请求,那我们直接输出服务器中的文件到浏览器就可以了,也就是用HTTP 格式写到套接字上,然后就可以直接结束了。对于其他情况(有参数的GET, POST, url是可执行文件),那么久继续调用 excute_cgi 来执行cgi.
- 进入excute_cgi(),读取HTTP请求,然后丢弃,如果他是一个POST 请求,那么有一个Content-Length, 找出来,然后把HTTP 200 状态码写到套接字。
- 用pipe建立两个管道,cgi_input,cgi_output,等下fork一个进程。管道是用来父进程和子进程通信的。
- 子进程中,STDOUT 也就是 1, 重定向到cgi_output的写入端,也就是cgi_output[1], STDIN 也就是 0, 重定向到cgi_input的读取端,也就是cgi_input[0]。子进程中关闭cgi_input的写入端,cgi_output的读取端。
- 仍然是在子进程中,设置request_method的环境变量,如果是GET 的话设置query_string 的环境变量,POST的话设置content_length 的环境变量,这个变量的作用是让cgi调用,在管道里进行通信,然后execl运行cgi,进行通信。
- 在父进程中,关闭cgi_input的读取端,cgi_output的写入端,如果请求是POST 的话,就把POST 写入cgi_input,在子进程中,他被重定向到了STDIN,读取cgi_output的管道输出到客户端,这个管道的输入被重定向到了STDOUT。然后关闭所有管道,等待子进程结束。
- 最后结束,关闭连接,完成了一次HTTP请求与回应。
关于CGI 技术,现在并不需要详细的学习CGI,只需要了解一下四点:
- web-server将POST数据重定向到CGI的标准输入,读用户POST的数据可以直接读stdin
- web-server将Http头设置成CGI的环境变量,所有读Http头信息可以通过getenv函数
- web-server将GET数据设置成CGI的环境变量,可以通过getenv(“QUERY_STRING”)得到
- web-server将CGI的标准输出重定向到浏览器,所以CGI输入内容直接向stdout输出就可以了。
未完待续
script>