这是个不错的源码,用C 语言写的一个超轻量的HttpServer, 开发应该是用不上了,但是对于学习Unix 网络编程,套接字,进程等等来说相当够用。源代码大约500行,属于一个比较容易理解的代码量。

阅读C 源码的好处在于你不必去考虑类和对象,不用花时间去看类里有哪些成员变量和成员函数,不用费力去构建类图。只需要按照过程阅读就好了。

在源码上我进行了注释,在这里我对其中涉及到的Unix知识进行一点解释,也算是给啃UNP做一个预热。

打开httpd.c,这一长串函数,最重要的就是 startup, accept_request, 和 execute_cgi,以及get_line。 下面是每个函数的功能。
accept_request: 处理从套接字上监听到的一个 HTTP 请求,在这里可以很大一部分地体现服务器处理请求流程。

下面找到main函数,开始往下读。因为没有学习过Unix网络编程,所以中间一大把时间都花在了wiki的扩展。

首先将流程讲一下。

  1. startup()启动服务器,在指定的端口绑定httpd服务。
  2. 因为打开了listen, 当accept 到HTTP 请求时,那么久派生出一个进程去运行accept_request()
  3. accept_request() 中,取出HTTP 请求,获取它的method(可能是GET 或 POST)和url 。对于GET来说,若这个GET 请求带有参数,那么久会存在于url 中的「?」之后,将它保存在query_string中。
  4. 然后格式化url,将它保存到path数组中,也就是浏览器请求的服务器文件路径,如果url以/结尾,那就在路径结尾默认加上index.html,用来表示访问主页。
  5. 检验这个路径,如果不合法则返回错误。如果合法,再如果是一个没有参数的GET 请求,那我们直接输出服务器中的文件到浏览器就可以了,也就是用HTTP 格式写到套接字上,然后就可以直接结束了。对于其他情况(有参数的GET, POST, url是可执行文件),那么久继续调用 excute_cgi 来执行cgi.
  6. 进入excute_cgi(),读取HTTP请求,然后丢弃,如果他是一个POST 请求,那么有一个Content-Length, 找出来,然后把HTTP 200 状态码写到套接字。
  7. 用pipe建立两个管道,cgi_input,cgi_output,等下fork一个进程。管道是用来父进程和子进程通信的。
  8. 子进程中,STDOUT 也就是 1, 重定向到cgi_output的写入端,也就是cgi_output[1], STDIN 也就是 0, 重定向到cgi_input的读取端,也就是cgi_input[0]。子进程中关闭cgi_input的写入端,cgi_output的读取端。
  9. 仍然是在子进程中,设置request_method的环境变量,如果是GET 的话设置query_string 的环境变量,POST的话设置content_length 的环境变量,这个变量的作用是让cgi调用,在管道里进行通信,然后execl运行cgi,进行通信。
  10. 在父进程中,关闭cgi_input的读取端,cgi_output的写入端,如果请求是POST 的话,就把POST 写入cgi_input,在子进程中,他被重定向到了STDIN,读取cgi_output的管道输出到客户端,这个管道的输入被重定向到了STDOUT。然后关闭所有管道,等待子进程结束。
  11. 最后结束,关闭连接,完成了一次HTTP请求与回应。

关于CGI 技术,现在并不需要详细的学习CGI,只需要了解一下四点:

  1. web-server将POST数据重定向到CGI的标准输入,读用户POST的数据可以直接读stdin
  2. web-server将Http头设置成CGI的环境变量,所有读Http头信息可以通过getenv函数
  3. web-server将GET数据设置成CGI的环境变量,可以通过getenv(“QUERY_STRING”)得到
  4. web-server将CGI的标准输出重定向到浏览器,所以CGI输入内容直接向stdout输出就可以了。

未完待续

script>