项目名称
搭建一个小型的http服务器,服务器能提供在线画图,整型间简单运算的服务
项目目标
服务器能接收用户发起的请求并做出响应,发送对应资源,或根据情况做异常处理。
应用技术
服务器能处理GET/POST请求,并根据请求资源的路径返回响应资源
能处理静态请求,能根据 URL 返回一个服务器上的静态文件(如画图板界面)
基于CGI技术处理HTTP请求中的参数,使用创建子进程和匿名管道处理CGI程序
使用线程池管理多个链接请求,提高了服务器的并发性
对访问不存在的资源,能返回404错误码和一个错误页面
效果图
主页效果图
访问某静态文件效果图
在线画图板效果图
简单CGI效果图
访问错误,返回404效果图
项目流程
服务器启动准备工作
程序启动,构建一个HttpServer对象。
HttpServer对象负责服务器的启动(创建套接字/绑定地址信息/进入监听状态),同时创建含有5个线程的线程池。
线程池内含一个任务队列,用来接收http请求,先初始化条件变量和互斥量,5个线程创建好后先线程分离,再依次进行互斥量的获取,判断队列是否有任务,无任务则挂起等待。
至此服务器准备就绪,开始让HttpServer接收HTTP请求。
服务器接收HTTP请求
当有一个HTTP请求到来时,HTTPServer 使用accept接收到任务,将其封装成任务(new_socket和RequestHandler指针),插入到任务队列中。
同时唤醒一个线程对任务进行处理,调用Entry类中的RequestHandler方法。
进入到Request方法后,先对HTTP请求进行解析。
调用RecvOneLine函数,获取请求行(要对"\n","\r\n","\r"都转换为\n) 知识点MSG_PEEK(窥探)
Unix系统里,每行结尾只有“”,即“\n”;Windows系统里面,每行结尾是“”,即“\r\n”;Mac系统里,每行结尾是“”。一个直接后果是,Unix/Mac系统下的文件在Windows里打开的话,所有文字会变成一行;而Windows里的文件在Unix/Mac下打开的话,在每行的结尾可能会多出一个符号^M(在Unix下多出一个\r,在Mac下多出一个\n)。
使用stringstream按照空格将请求行拆分后分别放入method,url,version变量中
对method进行解析(method检查方式是比较是否是GET/POST)
url分别进行解析(先查'?',如果是GET方法且URL中含'?',则说明该请求是cgi请求。并将路径path和参数param分别保存,如果是POST则URL全部是path)
(如果path最末是‘/’则是目录,在后面补上/index.html,path检查方式是调用stat函数,并对路径下文件是目录或可执行文件做分别处理,如果
如果是可执行文件则将更换为cgi模式,顺便将后缀也识别出来,这里是为了标识响应报头中的Content-Type)
同时记录资源大小,为了标识响应报头中的Content-Length.
11.5 检查path合法性,资源不存在将状态码code记录为404,并跳转至end
不正确则直接跳转到end,跳转前要记得先把报头读完
到此请求行解读完毕,开始解读报头.调用RecvOneLine直到遇到空行即可获得所有报头.
对报头的每一行进行读取,按照": "将各个键值对存入head_kv
15.读完报头,跳过空行,检查是否要读正文(只有POST方法才有正文,所以等同与判断method是否是"POST")
16.至此服务器解析HTTP请求结束,开始构建响应
服务器构建并返回HTTP响应
17.构建响应前,根据cgi的值有两种方式:非cgi响应和cgi响应
服务器构建并返回非CGI响应
18.构建响应首行,默认版本为HTTP/1.0 ,状态码是之前的code,状态码描述是code所对应的描述。
19.构建响应报头,本项目中只构建Content-Length和Content-Type,这两个的值之前都已经url解析中记录过了,在resource_size和suffix变量中
20.打开path所指的资源,一会儿资源内容作为相应正文
21.发送时调用send依次向对端socket发送首行,报头,空行,正文,正文的长度用resource_size控制.
服务器构建并返回CGI响应
18'. 构建两条匿名管道,再fork出子进程,两条管道对应父子进程间的数据传输.注意,由于我们要让子进程处理CGI,所以势必会调用exec函数族,一旦替换数据区和代码段,就无法找到操作描述符了(文件描述符表不会被替换,但是你无法知道那个文件描述符是你想要的),所以要先将管道描述符重定向到0,1,还要将参数的长度提前以自定义环境变量的形式进行保存.
19'. 父进程将参数param全部传入管道1,并等待子进程处理完毕,
20'. 子进程接收到参数后,然后调用exec函数执行path路径所指的文件.
21'. 子进程执行完毕后再通过管道2(cout标记的管道2)将数据结果反馈给父进程,父进程接收后用来组装成响应正文.
到这就跟非CGI一样了,直接构建响应并返回.
CGI模式示意图