本文将介绍如何使用 Boost.Asio 实现了一个简单的 http 服务器,可以简易的实现 GET 和 POST请求的处理,并且根据请求响应给客户端一个静态页面或者动态生成的页面。
客户端连接的处理
对于客户端连接的处理与上文 echo 服务器类似,使用 Server 类管理连接,( 本文中 ) 使用 Connection 类处理读写操作。使用智能指针延长对象的生命周期,继承 std::enable_shared_from_this 类解决两个智能指针指向同一个地址的问题。
本质上,http 服务器与 echo 服务器也十分相似,不同的只是发送的数据要是 HTTP 协议要求的格式。
客户端数据的处理
首先,要知道 HTTP 客户端发送的请求报文的基本格式如下:
————————————————————————————————
请 求 行| 请求方法 URI HTTP版本
————————————————————————————————
请求报头| key: value
请求报头| key: value
请求报头| key: value
请求报头| ...
————————————————————————————————
空行|
————————————————————————————————
请求正文| ...
常用的请求方法是 GET 和 POST ,本文也只对这两类请求进行处理。
GET 请求一般没有正文内容,它会将信息放在 URI 中,所以以这种方式发送请求可能会导致信息泄露,如同登录操作就不推荐使用这种请求方式。
POST 请求会将请求信息存放在正文当中,因此,这种请求方式要比 GET 请求更加安全。为了能完整地获取请求信息,POST 请求的报头中会有一个 Content-Length 字段保存了正文的长度。
在 Connection 类读取到客户端发送的请求后,RequestParser 类会对接收到的报文进行处理。
请求报文每一行都以结束符 \r 和换行符 \n 为结尾。因此对于报文的处理可以一行行的进行。
首先是第一行:请求行。三个字段会以空格分隔,我们读取重要的信息:请求方法和 URI。
如若是 GET 方法,我们所需要的请求信息都在 URI 中,因此报头信息和正文就不需要进行处理了,只需要解读 URI;如若是 POST 方法,我们需要在报头中寻找 Content-Length 字段,获得正文长度,从而获得正文内容。
响应数据的处理
得到了请求的信息,我们使用 RequestHandler 类依据请求获得相应的文件,但是我们发送的 HTTP 响应也是要符合响应报文的格式的。
————————————————————————————————
状 态 行| 协议版本号 状态码 状态描述符
————————————————————————————————
响应报头| key: value
响应报头| key: value
响应报头| key: value
响应报头| ...
————————————————————————————————
空行|
————————————————————————————————
响应正文| ...
我们通过 RequestHandler 类获取到的信息仅是正文部分,所以我们要增加状态行和响应报头的信息。其中响应报头部分需要的响应信息是 Content-Length 字段和 Content-Type 字段。
处理好的响应报文就可以使用 Connection 的写函数发送给客户端了。
CGI处理
CGI 是一种生成动态响应页面的技术。当客户端发来的请求报文中 URI 含有参数或者是 POST 请求时,我们可以使用 CGI 程序为客户响应他需要的页面。
如何执行 CGI 程序呢?
我们 fork 一个子进程,通过 exec 系列函数切换到 CGI 程序,通过匿名管道的技术进行父子进程的通信,从而获得响应页面的正文信息。
日志的处理
将日志划分为 debug、info、warn、error 四个等级。
通过宏 FILE 和 LINE 去获取当前运行到哪个文件哪个位置。